mininet / mininet / node.py @ 376bcba4
History | View | Annotate | Download (12 KB)
1 | 89bf3103 | Brandon Heller | #!/usr/bin/env python
|
---|---|---|---|
2 | '''Node objects for Mininet.'''
|
||
3 | |||
4 | from subprocess import Popen, PIPE, STDOUT |
||
5 | import os, signal, sys, select |
||
6 | flush = sys.stdout.flush |
||
7 | |||
8 | from mininet.logging_mod import lg |
||
9 | 376bcba4 | Brandon Heller | from mininet.util import quietRun, macColonHex, ipStr |
10 | 89bf3103 | Brandon Heller | |
11 | class Node(object): |
||
12 | '''A virtual network node is simply a shell in a network namespace.
|
||
13 | We communicate with it using pipes.'''
|
||
14 | inToNode = {} |
||
15 | outToNode = {} |
||
16 | |||
17 | def __init__(self, name, inNamespace = True): |
||
18 | self.name = name
|
||
19 | closeFds = False # speed vs. memory use |
||
20 | # xpg_echo is needed so we can echo our sentinel in sendCmd
|
||
21 | cmd = ['/bin/bash', '-O', 'xpg_echo'] |
||
22 | self.inNamespace = inNamespace
|
||
23 | if self.inNamespace: |
||
24 | cmd = ['netns'] + cmd
|
||
25 | self.shell = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = STDOUT,
|
||
26 | close_fds = closeFds) |
||
27 | self.stdin = self.shell.stdin |
||
28 | self.stdout = self.shell.stdout |
||
29 | self.pollOut = select.poll()
|
||
30 | self.pollOut.register(self.stdout) |
||
31 | # Maintain mapping between file descriptors and nodes
|
||
32 | # This could be useful for monitoring multiple nodes
|
||
33 | # using select.poll()
|
||
34 | self.outToNode[self.stdout.fileno()] = self |
||
35 | self.inToNode[self.stdin.fileno()] = self |
||
36 | self.pid = self.shell.pid |
||
37 | self.intfCount = 0 |
||
38 | self.intfs = [] # list of interface names, as strings |
||
39 | self.ips = {}
|
||
40 | self.connection = {}
|
||
41 | self.waiting = False |
||
42 | self.execed = False |
||
43 | |||
44 | def fdToNode(self, f): |
||
45 | '''Insert docstring.
|
||
46 |
|
||
47 | @param f unknown
|
||
48 | @return bool unknown
|
||
49 | '''
|
||
50 | node = self.outToNode.get(f)
|
||
51 | return node or self.inToNode.get(f) |
||
52 | |||
53 | def cleanup(self): |
||
54 | '''Help python collect its garbage.'''
|
||
55 | self.shell = None |
||
56 | |||
57 | # Subshell I/O, commands and control
|
||
58 | def read(self, fileno_max): |
||
59 | '''Insert docstring.
|
||
60 |
|
||
61 | @param fileno_max unknown
|
||
62 | '''
|
||
63 | return os.read(self.stdout.fileno(), fileno_max) |
||
64 | |||
65 | def write(self, data): |
||
66 | '''Write data to node.
|
||
67 |
|
||
68 | @param data string
|
||
69 | '''
|
||
70 | os.write(self.stdin.fileno(), data)
|
||
71 | |||
72 | def terminate(self): |
||
73 | '''Send kill signal to Node and cleanup after it.'''
|
||
74 | os.kill(self.pid, signal.SIGKILL)
|
||
75 | self.cleanup()
|
||
76 | |||
77 | def stop(self): |
||
78 | '''Stop node.'''
|
||
79 | self.terminate()
|
||
80 | |||
81 | def waitReadable(self): |
||
82 | '''Poll on node.'''
|
||
83 | self.pollOut.poll()
|
||
84 | |||
85 | def sendCmd(self, cmd): |
||
86 | '''Send a command, followed by a command to echo a sentinel,
|
||
87 | and return without waiting for the command to complete.'''
|
||
88 | assert not self.waiting |
||
89 | if cmd[-1] == '&': |
||
90 | separator = '&'
|
||
91 | cmd = cmd[:-1]
|
||
92 | else:
|
||
93 | separator = ';'
|
||
94 | if isinstance(cmd, list): |
||
95 | cmd = ' '.join(cmd)
|
||
96 | self.write(cmd + separator + ' echo -n "\\0177" \n') |
||
97 | self.waiting = True |
||
98 | |||
99 | def monitor(self): |
||
100 | '''Monitor the output of a command, returning (done, data).'''
|
||
101 | assert self.waiting |
||
102 | self.waitReadable()
|
||
103 | data = self.read(1024) |
||
104 | if len(data) > 0 and data[-1] == chr(0177): |
||
105 | self.waiting = False |
||
106 | return True, data[:-1] |
||
107 | else:
|
||
108 | return False, data |
||
109 | |||
110 | def sendInt(self): |
||
111 | '''Send ^C, hopefully interrupting a running subprocess.'''
|
||
112 | self.write(chr(3)) |
||
113 | |||
114 | def waitOutput(self): |
||
115 | '''Wait for a command to complete.
|
||
116 |
|
||
117 | Completion is signaled by a sentinel character, ASCII(127) appearing in
|
||
118 | the output stream. Wait for the sentinel and return the output,
|
||
119 | including trailing newline.
|
||
120 | '''
|
||
121 | assert self.waiting |
||
122 | output = ''
|
||
123 | while True: |
||
124 | self.waitReadable()
|
||
125 | data = self.read(1024) |
||
126 | if len(data) > 0 and data[-1] == chr(0177): |
||
127 | output += data[:-1]
|
||
128 | break
|
||
129 | else: output += data
|
||
130 | self.waiting = False |
||
131 | return output
|
||
132 | |||
133 | def cmd(self, cmd): |
||
134 | '''Send a command, wait for output, and return it.
|
||
135 |
|
||
136 | @param cmd string
|
||
137 | '''
|
||
138 | self.sendCmd(cmd)
|
||
139 | return self.waitOutput() |
||
140 | |||
141 | def cmdPrint(self, cmd): |
||
142 | '''Call cmd and printing its output
|
||
143 |
|
||
144 | @param cmd string
|
||
145 | '''
|
||
146 | #lg.info('*** %s : %s', self.name, cmd)
|
||
147 | result = self.cmd(cmd)
|
||
148 | #lg.info('%s\n', result)
|
||
149 | return result
|
||
150 | |||
151 | # Interface management, configuration, and routing
|
||
152 | def intfName(self, n): |
||
153 | '''Construct a canonical interface name node-intf for interface N.'''
|
||
154 | return self.name + '-eth' + repr(n) |
||
155 | |||
156 | def newIntf(self): |
||
157 | '''Reserve and return a new interface name.'''
|
||
158 | intfName = self.intfName(self.intfCount) |
||
159 | self.intfCount += 1 |
||
160 | self.intfs += [intfName]
|
||
161 | return intfName
|
||
162 | |||
163 | 376bcba4 | Brandon Heller | def setMAC(self, intf, mac): |
164 | '''Set the MAC address for an interface.
|
||
165 |
|
||
166 | @param mac MAC address as unsigned int
|
||
167 | '''
|
||
168 | mac_str = macColonHex(mac) |
||
169 | result = self.cmd(['ifconfig', intf, 'down']) |
||
170 | result += self.cmd(['ifconfig', intf, 'hw', 'ether', mac_str]) |
||
171 | result += self.cmd(['ifconfig', intf, 'up']) |
||
172 | return result
|
||
173 | |||
174 | def setARP(self, ip, mac): |
||
175 | '''Add an ARP entry.
|
||
176 |
|
||
177 | @param ip IP address as unsigned int
|
||
178 | @param mac MAC address as unsigned int
|
||
179 | '''
|
||
180 | ip_str = ipStr(ip) |
||
181 | mac_str = macColonHex(mac) |
||
182 | result = self.cmd(['arp', '-s', ip_str, mac_str]) |
||
183 | return result
|
||
184 | |||
185 | 89bf3103 | Brandon Heller | def setIP(self, intf, ip, bits): |
186 | '''Set the IP address for an interface.
|
||
187 |
|
||
188 | @param intf string, interface name
|
||
189 | @param ip IP address as integer
|
||
190 | @param bits
|
||
191 | '''
|
||
192 | result = self.cmd(['ifconfig', intf, ip + bits, 'up']) |
||
193 | self.ips[intf] = ip
|
||
194 | return result
|
||
195 | |||
196 | def setHostRoute(self, ip, intf): |
||
197 | '''Add route to host.
|
||
198 |
|
||
199 | @param ip IP address as dotted decimal
|
||
200 | @param intf string, interface name
|
||
201 | '''
|
||
202 | return self.cmd('route add -host ' + ip + ' dev ' + intf) |
||
203 | |||
204 | def setDefaultRoute(self, intf): |
||
205 | '''Set the default route to go through intf.
|
||
206 |
|
||
207 | @param intf string, interface name
|
||
208 | '''
|
||
209 | self.cmd('ip route flush') |
||
210 | return self.cmd('route add default ' + intf) |
||
211 | |||
212 | def IP(self): |
||
213 | '''Return IP address of first interface'''
|
||
214 | if len(self.intfs) > 0: |
||
215 | return self.ips.get(self.intfs[ 0 ], None) |
||
216 | |||
217 | def intfIsUp(self): |
||
218 | '''Check if one of our interfaces is up.'''
|
||
219 | return 'UP' in self.cmd('ifconfig ' + self.intfs[0]) |
||
220 | |||
221 | # Other methods
|
||
222 | def __str__(self): |
||
223 | result = self.name + ':' |
||
224 | if self.IP(): |
||
225 | result += ' IP=' + self.IP() |
||
226 | result += ' intfs=' + ','.join(self.intfs) |
||
227 | result += ' waiting=' + repr(self.waiting) |
||
228 | return result
|
||
229 | |||
230 | |||
231 | class Host(Node): |
||
232 | '''A host is simply a Node.'''
|
||
233 | pass
|
||
234 | |||
235 | |||
236 | 1bb4412f | Brandon Heller | class Switch(Node): |
237 | '''A Switch is a Node that is running (or has execed)
|
||
238 | an OpenFlow switch.'''
|
||
239 | 89bf3103 | Brandon Heller | |
240 | 1bb4412f | Brandon Heller | def sendCmd(self, cmd): |
241 | '''Send command to Node.
|
||
242 | 89bf3103 | Brandon Heller |
|
243 | 1bb4412f | Brandon Heller | @param cmd string
|
244 | 89bf3103 | Brandon Heller | '''
|
245 | 1bb4412f | Brandon Heller | if not self.execed: |
246 | return Node.sendCmd(self, cmd) |
||
247 | else:
|
||
248 | lg.error('*** Error: %s has execed and cannot accept commands' %
|
||
249 | self.name)
|
||
250 | 89bf3103 | Brandon Heller | |
251 | 1bb4412f | Brandon Heller | def monitor(self): |
252 | '''Monitor node.'''
|
||
253 | if not self.execed: |
||
254 | return Node.monitor(self) |
||
255 | else:
|
||
256 | return True, '' |
||
257 | 89bf3103 | Brandon Heller | |
258 | 1bb4412f | Brandon Heller | class UserSwitch(Switch): |
259 | 89bf3103 | Brandon Heller | |
260 | 1bb4412f | Brandon Heller | def __init__(self, name): |
261 | 89bf3103 | Brandon Heller | '''Init.
|
262 |
|
||
263 | @param name
|
||
264 | '''
|
||
265 | 1bb4412f | Brandon Heller | Node.__init__(self, name, inNamespace = True) |
266 | 89bf3103 | Brandon Heller | |
267 | 1bb4412f | Brandon Heller | def start(self, controllers): |
268 | 89bf3103 | Brandon Heller | '''Start OpenFlow reference user datapath.
|
269 |
|
||
270 | Log to /tmp/sN-{ofd,ofp}.log.
|
||
271 |
|
||
272 | 16c57ddb | Brandon Heller | @param controllers dict of controller names to objects
|
273 | 89bf3103 | Brandon Heller | '''
|
274 | 16c57ddb | Brandon Heller | if 'c0' not in controller: |
275 | raise Exception('User datapath start() requires controller c0') |
||
276 | controller = controllers['c0']
|
||
277 | 89bf3103 | Brandon Heller | ofdlog = '/tmp/' + self.name + '-ofd.log' |
278 | ofplog = '/tmp/' + self.name + '-ofp.log' |
||
279 | self.cmd('ifconfig lo up') |
||
280 | intfs = self.intfs[1:] # 0 is mgmt interface |
||
281 | self.cmdPrint('ofdatapath -i ' + ','.join(intfs) + |
||
282 | ' ptcp: 1> ' + ofdlog + ' 2> ' + ofdlog + ' &') |
||
283 | self.cmdPrint('ofprotocol tcp:' + controller.IP() + |
||
284 | ' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' + |
||
285 | ofplog + ' &')
|
||
286 | |||
287 | 1bb4412f | Brandon Heller | def stop(self): |
288 | 89bf3103 | Brandon Heller | '''Stop OpenFlow reference user datapath.'''
|
289 | self.cmd('kill %ofdatapath') |
||
290 | self.cmd('kill %ofprotocol') |
||
291 | |||
292 | 1bb4412f | Brandon Heller | |
293 | class KernelSwitch(Switch): |
||
294 | |||
295 | def __init__(self, name, datapath = None): |
||
296 | '''Init.
|
||
297 |
|
||
298 | @param name
|
||
299 | @param datapath string, datapath name
|
||
300 | '''
|
||
301 | self.dp = datapath
|
||
302 | Node.__init__(self, name, inNamespace = (datapath == None)) |
||
303 | |||
304 | def start(self, ignore): |
||
305 | 89bf3103 | Brandon Heller | '''Start up reference kernel datapath.'''
|
306 | ofplog = '/tmp/' + self.name + '-ofp.log' |
||
307 | quietRun('ifconfig lo up')
|
||
308 | # Delete local datapath if it exists;
|
||
309 | # then create a new one monitoring the given interfaces
|
||
310 | quietRun('dpctl deldp ' + self.dp) |
||
311 | self.cmdPrint('dpctl adddp ' + self.dp) |
||
312 | self.cmdPrint('dpctl addif ' + self.dp + ' ' + ' '.join(self.intfs)) |
||
313 | # Run protocol daemon
|
||
314 | self.cmdPrint('ofprotocol' + |
||
315 | ' ' + self.dp + ' tcp:127.0.0.1 ' + |
||
316 | ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &') |
||
317 | self.execed = False # XXX until I fix it |
||
318 | |||
319 | 1bb4412f | Brandon Heller | def stop(self): |
320 | 89bf3103 | Brandon Heller | '''Terminate reference kernel datapath.'''
|
321 | quietRun('dpctl deldp ' + self.dp) |
||
322 | # In theory the interfaces should go away after we shut down.
|
||
323 | # However, this takes time, so we're better off to remove them
|
||
324 | # explicitly so that we won't get errors if we run before they
|
||
325 | # have been removed by the kernel. Unfortunately this is very slow.
|
||
326 | self.cmd('kill %ofprotocol') |
||
327 | for intf in self.intfs: |
||
328 | quietRun('ip link del ' + intf)
|
||
329 | lg.info('.')
|
||
330 | |||
331 | |||
332 | 1bb4412f | Brandon Heller | class Controller(Node): |
333 | '''A Controller is a Node that is running (or has execed) an
|
||
334 | OpenFlow controller.'''
|
||
335 | |||
336 | def __init__(self, name, inNamespace = False, controller = 'controller', |
||
337 | cargs = '-v ptcp:', cdir = None): |
||
338 | self.controller = controller
|
||
339 | self.cargs = cargs
|
||
340 | self.cdir = cdir
|
||
341 | Node.__init__(self, name, inNamespace = inNamespace)
|
||
342 | |||
343 | def start(self): |
||
344 | '''Start <controller> <args> on controller.
|
||
345 |
|
||
346 | Log to /tmp/cN.log
|
||
347 | 89bf3103 | Brandon Heller | '''
|
348 | 1bb4412f | Brandon Heller | cout = '/tmp/' + self.name + '.log' |
349 | if self.cdir is not None: |
||
350 | self.cmdPrint('cd ' + self.cdir) |
||
351 | self.cmdPrint(self.controller + ' ' + self.cargs + |
||
352 | ' 1> ' + cout + ' 2> ' + cout + ' &') |
||
353 | self.execed = False # XXX Until I fix it |
||
354 | 89bf3103 | Brandon Heller | |
355 | def stop(self): |
||
356 | 1bb4412f | Brandon Heller | '''Stop controller.'''
|
357 | self.cmd('kill %' + self.controller) |
||
358 | self.terminate()
|
||
359 | 89bf3103 | Brandon Heller | |
360 | |||
361 | 1bb4412f | Brandon Heller | class ControllerParams(object): |
362 | '''Container for controller IP parameters.'''
|
||
363 | def __init__(self, ip, subnet_size): |
||
364 | '''Init.
|
||
365 | 89bf3103 | Brandon Heller |
|
366 | 1bb4412f | Brandon Heller | @param ip integer, controller IP
|
367 | @param subnet_size integer, ex 8 for slash-8, covering 17M
|
||
368 | '''
|
||
369 | self.ip = ip
|
||
370 | self.subnet_size = subnet_size
|
||
371 | 8b5062a3 | Brandon Heller | |
372 | |||
373 | 4804237f | Brandon Heller | class NOX(Controller): |
374 | 8b5062a3 | Brandon Heller | '''Controller to run a NOX application.'''
|
375 | 4804237f | Brandon Heller | def __init__(self, name, inNamespace = False, nox_args = None, **kwargs): |
376 | 8b5062a3 | Brandon Heller | '''Init.
|
377 |
|
||
378 | @param name name to give controller
|
||
379 | 4804237f | Brandon Heller | @param nox_args list of args, or single arg, to pass to NOX
|
380 | 8b5062a3 | Brandon Heller | '''
|
381 | 4804237f | Brandon Heller | if type(nox_args) != list: |
382 | nox_args = [nox_args] |
||
383 | 8b5062a3 | Brandon Heller | if not nox_args: |
384 | nox_args = ['packetdump']
|
||
385 | nox_core_dir = os.environ['NOX_CORE_DIR']
|
||
386 | if not nox_core_dir: |
||
387 | raise Exception('please set NOX_CORE_DIR env var\n') |
||
388 | Controller.__init__(self, name,
|
||
389 | controller = nox_core_dir + '/nox_core',
|
||
390 | cargs = '--libdir=/usr/local/lib -v -i ptcp: ' + \
|
||
391 | ' '.join(nox_args),
|
||
392 | 1bb4412f | Brandon Heller | cdir = nox_core_dir, **kwargs) |