blob: 8749987a16ef728e85092e3034e2ad0ccc1ffa96 [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"""
2Node objects for Mininet.
3
4Nodes provide a simple abstraction for interacting with hosts, switches
5and controllers. Local nodes are simply one or more processes on the local
6machine.
7
8Node: superclass for all (primarily local) network nodes.
9
10Host: a virtual host. By default, a host is simply a shell; commands
11 may be sent using Cmd (which waits for output), or using sendCmd(),
12 which returns immediately, allowing subsequent monitoring using
13 monitor(). Examples of how to run experiments using this
14 functionality are provided in the examples/ directory.
15
16CPULimitedHost: a virtual host whose CPU bandwidth is limited by
17 RT or CFS bandwidth limiting.
18
19Switch: superclass for switch nodes.
20
21UserSwitch: a switch using the user-space switch from the OpenFlow
22 reference implementation.
23
24KernelSwitch: a switch using the kernel switch from the OpenFlow reference
25 implementation.
26
27OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
28 implementation (openvswitch.org).
29
30Controller: superclass for OpenFlow controllers. The default controller
31 is controller(8) from the reference implementation.
32
33NOXController: a controller node using NOX (noxrepo.org).
34
35RemoteController: a remote controller node, which may use any
36 arbitrary OpenFlow-compatible controller, and which is not
37 created or managed by mininet.
38
39Future enhancements:
40
41- Possibly make Node, Switch and Controller more abstract so that
42 they can be used for both local and remote nodes
43
44- Create proxy objects for remote nodes (Mininet: Cluster Edition)
45"""
46
47import os
48import re
49import signal
50import select
51from subprocess import Popen, PIPE, STDOUT
52
53from mininet.log import info, error, warn, debug
54from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
carlosmscabrale121a7b2013-02-18 18:14:53 -030055 numCores, retry, mountCgroups, run )
carlosmscabralf40ecd12013-02-01 18:15:58 -020056from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
57from mininet.link import Link, Intf, TCIntf
Caio188d2f32015-01-22 23:35:08 -020058import pdb
carlosmscabralf40ecd12013-02-01 18:15:58 -020059
60class Node( object ):
61 """A virtual network node is simply a shell in a network namespace.
62 We communicate with it using pipes."""
63
64 portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
65
66 def __init__( self, name, inNamespace=True, **params ):
67 """name: name of node
68 inNamespace: in network namespace?
69 params: Node parameters (see config() for details)"""
70
71 # Make sure class actually works
72 self.checkSetup()
73
74 self.name = name
75 self.inNamespace = inNamespace
76
77 # Stash configuration parameters for future reference
78 self.params = params
79
80 self.intfs = {} # dict of port numbers to interfaces
81 self.ports = {} # dict of interfaces to port numbers
82 # replace with Port objects, eventually ?
83 self.nameToIntf = {} # dict of interface names to Intfs
84
85 # Make pylint happy
86 ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
87 self.lastPid, self.lastCmd, self.pollOut ) = (
88 None, None, None, None, None, None, None, None )
89 self.waiting = False
90 self.readbuf = ''
91
92 # Start command interpreter shell
93 self.startShell()
94
95 # File descriptor to node mapping support
96 # Class variables and methods
97
98 inToNode = {} # mapping of input fds to nodes
99 outToNode = {} # mapping of output fds to nodes
100
101 @classmethod
102 def fdToNode( cls, fd ):
103 """Return node corresponding to given file descriptor.
104 fd: file descriptor
105 returns: node"""
106 node = cls.outToNode.get( fd )
107 return node or cls.inToNode.get( fd )
108
109 # Command support via shell process in namespace
110
111 def startShell( self ):
112 "Start a shell process for running commands"
113 if self.shell:
114 error( "%s: shell is already running" )
115 return
116 # mnexec: (c)lose descriptors, (d)etach from tty,
117 # (p)rint pid, and run in (n)amespace
118 opts = '-cdp'
119 if self.inNamespace:
120 opts += 'n'
121 # bash -m: enable job control
122 cmd = [ 'mnexec', opts, 'bash', '-m' ]
123 self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
124 close_fds=True )
125 self.stdin = self.shell.stdin
126 self.stdout = self.shell.stdout
127 self.pid = self.shell.pid
128 self.pollOut = select.poll()
129 self.pollOut.register( self.stdout )
130 # Maintain mapping between file descriptors and nodes
131 # This is useful for monitoring multiple nodes
132 # using select.poll()
133 self.outToNode[ self.stdout.fileno() ] = self
134 self.inToNode[ self.stdin.fileno() ] = self
135 self.execed = False
136 self.lastCmd = None
137 self.lastPid = None
138 self.readbuf = ''
139 self.waiting = False
140
141 def cleanup( self ):
142 "Help python collect its garbage."
143 if not self.inNamespace:
144 for intfName in self.intfNames():
145 if self.name in intfName:
146 quietRun( 'ip link del ' + intfName )
147 self.shell = None
148
149 # Subshell I/O, commands and control
150
151 def read( self, maxbytes=1024 ):
152 """Buffered read from node, non-blocking.
153 maxbytes: maximum number of bytes to return"""
154 count = len( self.readbuf )
155 if count < maxbytes:
156 data = os.read( self.stdout.fileno(), maxbytes - count )
157 self.readbuf += data
158 if maxbytes >= len( self.readbuf ):
159 result = self.readbuf
160 self.readbuf = ''
161 else:
162 result = self.readbuf[ :maxbytes ]
163 self.readbuf = self.readbuf[ maxbytes: ]
164 return result
165
166 def readline( self ):
167 """Buffered readline from node, non-blocking.
168 returns: line (minus newline) or None"""
169 self.readbuf += self.read( 1024 )
170 if '\n' not in self.readbuf:
171 return None
172 pos = self.readbuf.find( '\n' )
173 line = self.readbuf[ 0: pos ]
174 self.readbuf = self.readbuf[ pos + 1: ]
175 return line
176
177 def write( self, data ):
178 """Write data to node.
179 data: string"""
180 os.write( self.stdin.fileno(), data )
181
182 def terminate( self ):
183 "Send kill signal to Node and clean up after it."
184 os.kill( self.pid, signal.SIGKILL )
185 self.cleanup()
186
187 def stop( self ):
188 "Stop node."
189 self.terminate()
190
191 def waitReadable( self, timeoutms=None ):
192 """Wait until node's output is readable.
193 timeoutms: timeout in ms or None to wait indefinitely."""
194 if len( self.readbuf ) == 0:
195 self.pollOut.poll( timeoutms )
196
197 def sendCmd( self, *args, **kwargs ):
198 """Send a command, followed by a command to echo a sentinel,
199 and return without waiting for the command to complete.
200 args: command and arguments, or string
201 printPid: print command's PID?"""
202 assert not self.waiting
203 printPid = kwargs.get( 'printPid', True )
204 # Allow sendCmd( [ list ] )
205 if len( args ) == 1 and type( args[ 0 ] ) is list:
206 cmd = args[ 0 ]
207 # Allow sendCmd( cmd, arg1, arg2... )
208 elif len( args ) > 0:
209 cmd = args
210 # Convert to string
211 if not isinstance( cmd, str ):
212 cmd = ' '.join( [ str( c ) for c in cmd ] )
213 if not re.search( r'\w', cmd ):
214 # Replace empty commands with something harmless
215 cmd = 'echo -n'
216 self.lastCmd = cmd
217 printPid = printPid and not isShellBuiltin( cmd )
218 if len( cmd ) > 0 and cmd[ -1 ] == '&':
219 # print ^A{pid}\n{sentinel}
220 cmd += ' printf "\\001%d\n\\177" $! \n'
221 else:
222 # print sentinel
223 cmd += '; printf "\\177"'
224 if printPid and not isShellBuiltin( cmd ):
225 cmd = 'mnexec -p ' + cmd
226 self.write( cmd + '\n' )
227 self.lastPid = None
228 self.waiting = True
229
230 def sendInt( self, sig=signal.SIGINT ):
231 "Interrupt running command."
232 if self.lastPid:
233 try:
234 os.kill( self.lastPid, sig )
235 except OSError:
236 pass
237
238 def monitor( self, timeoutms=None ):
239 """Monitor and return the output of a command.
240 Set self.waiting to False if command has completed.
241 timeoutms: timeout in ms or None to wait indefinitely."""
242 self.waitReadable( timeoutms )
243 data = self.read( 1024 )
244 # Look for PID
245 marker = chr( 1 ) + r'\d+\n'
246 if chr( 1 ) in data:
247 markers = re.findall( marker, data )
248 if markers:
249 self.lastPid = int( markers[ 0 ][ 1: ] )
250 data = re.sub( marker, '', data )
251 # Look for sentinel/EOF
252 if len( data ) > 0 and data[ -1 ] == chr( 127 ):
253 self.waiting = False
254 data = data[ :-1 ]
255 elif chr( 127 ) in data:
256 self.waiting = False
257 data = data.replace( chr( 127 ), '' )
258 return data
259
260 def waitOutput( self, verbose=False ):
261 """Wait for a command to complete.
262 Completion is signaled by a sentinel character, ASCII(127)
263 appearing in the output stream. Wait for the sentinel and return
264 the output, including trailing newline.
265 verbose: print output interactively"""
266 log = info if verbose else debug
267 output = ''
268 while self.waiting:
269 data = self.monitor()
270 output += data
271 log( data )
272 return output
273
274 def cmd( self, *args, **kwargs ):
275 """Send a command, wait for output, and return it.
276 cmd: string"""
277 verbose = kwargs.get( 'verbose', False )
278 log = info if verbose else debug
279 log( '*** %s : %s\n' % ( self.name, args ) )
280 self.sendCmd( *args, **kwargs )
281 return self.waitOutput( verbose )
282
283 def cmdPrint( self, *args):
284 """Call cmd and printing its output
285 cmd: string"""
286 return self.cmd( *args, **{ 'verbose': True } )
287
288 def popen( self, *args, **kwargs ):
289 """Return a Popen() object in our namespace
290 args: Popen() args, single list, or string
291 kwargs: Popen() keyword args"""
292 defaults = { 'stdout': PIPE, 'stderr': PIPE,
293 'mncmd':
294 [ 'mnexec', '-a', str( self.pid ) ] }
295 defaults.update( kwargs )
296 if len( args ) == 1:
297 if type( args[ 0 ] ) is list:
298 # popen([cmd, arg1, arg2...])
299 cmd = args[ 0 ]
300 elif type( args[ 0 ] ) is str:
301 # popen("cmd arg1 arg2...")
302 cmd = args[ 0 ].split()
303 else:
304 raise Exception( 'popen() requires a string or list' )
305 elif len( args ) > 0:
306 # popen( cmd, arg1, arg2... )
307 cmd = list( args )
308 # Attach to our namespace using mnexec -a
309 mncmd = defaults[ 'mncmd' ]
310 del defaults[ 'mncmd' ]
311 cmd = mncmd + cmd
312 # Shell requires a string, not a list!
313 if defaults.get( 'shell', False ):
314 cmd = ' '.join( cmd )
315 return Popen( cmd, **defaults )
316
317 def pexec( self, *args, **kwargs ):
318 """Execute a command using popen
319 returns: out, err, exitcode"""
320 popen = self.popen( *args, **kwargs)
321 out, err = popen.communicate()
322 exitcode = popen.wait()
323 return out, err, exitcode
324
325 # Interface management, configuration, and routing
326
327 # BL notes: This might be a bit redundant or over-complicated.
328 # However, it does allow a bit of specialization, including
329 # changing the canonical interface names. It's also tricky since
330 # the real interfaces are created as veth pairs, so we can't
331 # make a single interface at a time.
332
333 def newPort( self ):
334 "Return the next port number to allocate."
335 if len( self.ports ) > 0:
336 return max( self.ports.values() ) + 1
337 return self.portBase
338
339 def addIntf( self, intf, port=None ):
340 """Add an interface.
341 intf: interface
342 port: port number (optional, typically OpenFlow port number)"""
343 if port is None:
344 port = self.newPort()
345 self.intfs[ port ] = intf
346 self.ports[ intf ] = port
347 self.nameToIntf[ intf.name ] = intf
348 debug( '\n' )
349 debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
350 if self.inNamespace:
351 debug( 'moving', intf, 'into namespace for', self.name, '\n' )
352 moveIntf( intf.name, self )
353
354 def defaultIntf( self ):
355 "Return interface for lowest port"
356 ports = self.intfs.keys()
357 if ports:
358 return self.intfs[ min( ports ) ]
359 else:
360 warn( '*** defaultIntf: warning:', self.name,
361 'has no interfaces\n' )
362
363 def intf( self, intf='' ):
364 """Return our interface object with given string name,
365 default intf if name is falsy (None, empty string, etc).
366 or the input intf arg.
367
368 Having this fcn return its arg for Intf objects makes it
369 easier to construct functions with flexible input args for
370 interfaces (those that accept both string names and Intf objects).
371 """
372 if not intf:
373 return self.defaultIntf()
374 elif type( intf) is str:
375 return self.nameToIntf[ intf ]
376 else:
377 return intf
378
379 def connectionsTo( self, node):
380 "Return [ intf1, intf2... ] for all intfs that connect self to node."
381 # We could optimize this if it is important
382 connections = []
383 for intf in self.intfList():
384 link = intf.link
385 if link:
386 node1, node2 = link.intf1.node, link.intf2.node
387 if node1 == self and node2 == node:
388 connections += [ ( intf, link.intf2 ) ]
389 elif node1 == node and node2 == self:
390 connections += [ ( intf, link.intf1 ) ]
391 return connections
392
393 def deleteIntfs( self ):
394 "Delete all of our interfaces."
395 # In theory the interfaces should go away after we shut down.
396 # However, this takes time, so we're better off removing them
397 # explicitly so that we won't get errors if we run before they
398 # have been removed by the kernel. Unfortunately this is very slow,
399 # at least with Linux kernels before 2.6.33
400 for intf in self.intfs.values():
401 intf.delete()
402 info( '.' )
403
404 # Routing support
405
406 def setARP( self, ip, mac ):
407 """Add an ARP entry.
408 ip: IP address as string
409 mac: MAC address as string"""
410 result = self.cmd( 'arp', '-s', ip, mac )
411 return result
412
413 def setHostRoute( self, ip, intf ):
414 """Add route to host.
415 ip: IP address as dotted decimal
416 intf: string, interface name"""
417 return self.cmd( 'route add -host', ip, 'dev', intf )
418
419 def setDefaultRoute( self, intf=None ):
420 """Set the default route to go through intf.
421 intf: string, interface name"""
422 if not intf:
423 intf = self.defaultIntf()
424 self.cmd( 'ip route flush root 0/0' )
425 return self.cmd( 'route add default %s' % intf )
426
427 # Convenience and configuration methods
428
429 def setMAC( self, mac, intf=None ):
430 """Set the MAC address for an interface.
431 intf: intf or intf name
432 mac: MAC address as string"""
433 return self.intf( intf ).setMAC( mac )
434
435 def setIP( self, ip, prefixLen=8, intf=None ):
436 """Set the IP address for an interface.
437 intf: intf or intf name
438 ip: IP address as a string
439 prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
440 # This should probably be rethought
441 if '/' not in ip:
442 ip = '%s/%s' % ( ip, prefixLen )
443 return self.intf( intf ).setIP( ip )
444
445 def IP( self, intf=None ):
446 "Return IP address of a node or specific interface."
447 return self.intf( intf ).IP()
448
449 def MAC( self, intf=None ):
450 "Return MAC address of a node or specific interface."
451 return self.intf( intf ).MAC()
452
453 def intfIsUp( self, intf=None ):
454 "Check if an interface is up."
455 return self.intf( intf ).isUp()
456
457 # The reason why we configure things in this way is so
458 # That the parameters can be listed and documented in
459 # the config method.
460 # Dealing with subclasses and superclasses is slightly
461 # annoying, but at least the information is there!
462
463 def setParam( self, results, method, **param ):
464 """Internal method: configure a *single* parameter
465 results: dict of results to update
466 method: config method name
467 param: arg=value (ignore if value=None)
468 value may also be list or dict"""
469 name, value = param.items()[ 0 ]
470 f = getattr( self, method, None )
471 if not f or value is None:
472 return
473 if type( value ) is list:
474 result = f( *value )
475 elif type( value ) is dict:
476 result = f( **value )
477 else:
478 result = f( value )
479 results[ name ] = result
480 return result
481
482 def config( self, mac=None, ip=None,
483 defaultRoute=None, lo='up', **_params ):
484 """Configure Node according to (optional) parameters:
485 mac: MAC address for default interface
486 ip: IP address for default interface
487 ifconfig: arbitrary interface configuration
488 Subclasses should override this method and call
489 the parent class's config(**params)"""
490 # If we were overriding this method, we would call
491 # the superclass config method here as follows:
492 # r = Parent.config( **_params )
493 r = {}
494 self.setParam( r, 'setMAC', mac=mac )
495 self.setParam( r, 'setIP', ip=ip )
496 self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute )
497 # This should be examined
498 self.cmd( 'ifconfig lo ' + lo )
499 return r
500
501 def configDefault( self, **moreParams ):
502 "Configure with default parameters"
503 self.params.update( moreParams )
504 self.config( **self.params )
505
506 # This is here for backward compatibility
507 def linkTo( self, node, link=Link ):
508 """(Deprecated) Link to another node
509 replace with Link( node1, node2)"""
510 return link( self, node )
511
512 # Other methods
513
514 def intfList( self ):
515 "List of our interfaces sorted by port number"
516 return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
517
518 def intfNames( self ):
519 "The names of our interfaces sorted by port number"
520 return [ str( i ) for i in self.intfList() ]
521
522 def __repr__( self ):
523 "More informative string representation"
524 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
525 for i in self.intfList() ] ) )
526 return '<%s %s: %s pid=%s> ' % (
527 self.__class__.__name__, self.name, intfs, self.pid )
528
529 def __str__( self ):
530 "Abbreviated string representation"
531 return self.name
532
533 # Automatic class setup support
534
535 isSetup = False
536
537 @classmethod
538 def checkSetup( cls ):
539 "Make sure our class and superclasses are set up"
540 while cls and not getattr( cls, 'isSetup', True ):
541 cls.setup()
542 cls.isSetup = True
543 # Make pylint happy
544 cls = getattr( type( cls ), '__base__', None )
545
546 @classmethod
547 def setup( cls ):
548 "Make sure our class dependencies are available"
549 pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
550
551
552class Host( Node ):
553 "A host is simply a Node"
554 pass
555
556
carlosmscabral6d3dd602013-03-23 11:12:34 -0300557
carlosmscabralf40ecd12013-02-01 18:15:58 -0200558class CPULimitedHost( Host ):
559
560 "CPU limited host"
561
562 def __init__( self, name, sched='cfs', **kwargs ):
563 Host.__init__( self, name, **kwargs )
564 # Initialize class if necessary
565 if not CPULimitedHost.inited:
566 CPULimitedHost.init()
567 # Create a cgroup and move shell into it
568 self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
569 errFail( 'cgcreate -g ' + self.cgroup )
570 # We don't add ourselves to a cpuset because you must
571 # specify the cpu and memory placement first
572 errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
573 # BL: Setting the correct period/quota is tricky, particularly
574 # for RT. RT allows very small quotas, but the overhead
575 # seems to be high. CFS has a mininimum quota of 1 ms, but
576 # still does better with larger period values.
577 self.period_us = kwargs.get( 'period_us', 100000 )
578 self.sched = sched
579 self.rtprio = 20
580
581 def cgroupSet( self, param, value, resource='cpu' ):
582 "Set a cgroup parameter and return its value"
583 cmd = 'cgset -r %s.%s=%s /%s' % (
584 resource, param, value, self.name )
585 quietRun( cmd )
586 nvalue = int( self.cgroupGet( param, resource ) )
587 if nvalue != value:
588 error( '*** error: cgroupSet: %s set to %s instead of %s\n'
589 % ( param, nvalue, value ) )
590 return nvalue
591
592 def cgroupGet( self, param, resource='cpu' ):
593 "Return value of cgroup parameter"
594 cmd = 'cgget -r %s.%s /%s' % (
595 resource, param, self.name )
carlosmscabral6d3dd602013-03-23 11:12:34 -0300596
carlosmscabrale121a7b2013-02-18 18:14:53 -0300597 return int(quietRun( cmd ).split()[ -1 ] )
carlosmscabralf40ecd12013-02-01 18:15:58 -0200598
599 def cgroupDel( self ):
600 "Clean up our cgroup"
601 # info( '*** deleting cgroup', self.cgroup, '\n' )
602 _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
603 return exitcode != 0
604
605 def popen( self, *args, **kwargs ):
606 """Return a Popen() object in node's namespace
607 args: Popen() args, single list, or string
608 kwargs: Popen() keyword args"""
609 # Tell mnexec to execute command in our cgroup
610 mncmd = [ 'mnexec', '-a', str( self.pid ),
611 '-g', self.name ]
612 if self.sched == 'rt':
613 mncmd += [ '-r', str( self.rtprio ) ]
614 return Host.popen( self, *args, mncmd=mncmd, **kwargs )
615
616 def cleanup( self ):
617 "Clean up our cgroup"
618 retry( retries=3, delaySecs=1, fn=self.cgroupDel )
619
620 def chrt( self ):
621 "Set RT scheduling priority"
622 quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
623 result = quietRun( 'chrt -p %s' % self.pid )
624 firstline = result.split( '\n' )[ 0 ]
625 lastword = firstline.split( ' ' )[ -1 ]
626 if lastword != 'SCHED_RR':
627 error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
628 return lastword
629
630 def rtInfo( self, f ):
631 "Internal method: return parameters for RT bandwidth"
632 pstr, qstr = 'rt_period_us', 'rt_runtime_us'
633 # RT uses wall clock time for period and quota
634 quota = int( self.period_us * f * numCores() )
635 return pstr, qstr, self.period_us, quota
636
637 def cfsInfo( self, f):
638 "Internal method: return parameters for CFS bandwidth"
639 pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
640 # CFS uses wall clock time for period and CPU time for quota.
641 quota = int( self.period_us * f * numCores() )
642 period = self.period_us
643 if f > 0 and quota < 1000:
644 debug( '(cfsInfo: increasing default period) ' )
645 quota = 1000
646 period = int( quota / f / numCores() )
647 return pstr, qstr, period, quota
648
649 # BL comment:
650 # This may not be the right API,
651 # since it doesn't specify CPU bandwidth in "absolute"
652 # units the way link bandwidth is specified.
653 # We should use MIPS or SPECINT or something instead.
654 # Alternatively, we should change from system fraction
655 # to CPU seconds per second, essentially assuming that
656 # all CPUs are the same.
657
658 def setCPUFrac( self, f=-1, sched=None):
659 """Set overall CPU fraction for this host
660 f: CPU bandwidth limit (fraction)
661 sched: 'rt' or 'cfs'
662 Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
663 if not f:
664 return
665 if not sched:
666 sched = self.sched
667 if sched == 'rt':
668 pstr, qstr, period, quota = self.rtInfo( f )
669 elif sched == 'cfs':
670 pstr, qstr, period, quota = self.cfsInfo( f )
671 else:
672 return
673 if quota < 0:
674 # Reset to unlimited
675 quota = -1
676 # Set cgroup's period and quota
677 self.cgroupSet( pstr, period )
678 self.cgroupSet( qstr, quota )
679 if sched == 'rt':
680 # Set RT priority if necessary
681 self.chrt()
682 info( '(%s %d/%dus) ' % ( sched, quota, period ) )
683
684 def setCPUs( self, cores, mems=0 ):
685 "Specify (real) cores that our cgroup can run on"
686 if type( cores ) is list:
687 cores = ','.join( [ str( c ) for c in cores ] )
688 self.cgroupSet( resource='cpuset', param='cpus',
689 value=cores )
690 # Memory placement is probably not relevant, but we
691 # must specify it anyway
692 self.cgroupSet( resource='cpuset', param='mems',
693 value=mems)
694 # We have to do this here after we've specified
695 # cpus and mems
696 errFail( 'cgclassify -g cpuset:/%s %s' % (
697 self.name, self.pid ) )
698
699 def config( self, cpu=None, cores=None, **params ):
700 """cpu: desired overall system CPU fraction
701 cores: (real) core(s) this host can run on
702 params: parameters for Node.config()"""
703 r = Node.config( self, **params )
704 # Was considering cpu={'cpu': cpu , 'sched': sched}, but
705 # that seems redundant
706
707 self.setParam( r, 'setCPUFrac', cpu=cpu )
708 self.setParam( r, 'setCPUs', cores=cores )
709
710 return r
711
712 inited = False
713
714 @classmethod
715 def init( cls ):
716 "Initialization for CPULimitedHost class"
717 mountCgroups()
718 cls.inited = True
719
720class CCNHost( Host ):
721 "CCNHost is a Host that always runs the ccnd daemon"
722
723 def __init__( self, name, **kwargs ):
724
carlosmscabral6d3dd602013-03-23 11:12:34 -0300725
carlosmscabralf40ecd12013-02-01 18:15:58 -0200726 Host.__init__( self, name, **kwargs )
727 if not CCNHost.inited:
728 CCNHost.init()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300729
carlosmscabralb9d85b72013-07-15 15:24:33 -0300730 self.cmd("export CCND_DEBUG=6")
731 self.cmd("export CCND_LOG=./log.{0}".format(self.name))
Caio188d2f32015-01-22 23:35:08 -0200732 #pdb.set_trace()
carlosmscabralb9d85b72013-07-15 15:24:33 -0300733# print self.params['cache']
734 if self.params['cache'] != None:
735 self.cmd("export CCND_CAP={0}".format(self.params['cache']))
736
carlosmscabralf40ecd12013-02-01 18:15:58 -0200737 self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
738 self.cmd("ccndstart")
739 self.peerList = {}
740
carlosmscabralb9d85b72013-07-15 15:24:33 -0300741 def config( self, fib=None, app=None, cache=None, **params ):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300742
carlosmscabralf40ecd12013-02-01 18:15:58 -0200743 r = Node.config( self, **params )
carlosmscabral6d3dd602013-03-23 11:12:34 -0300744
carlosmscabralf40ecd12013-02-01 18:15:58 -0200745 self.setParam( r, 'app', fib=fib )
746 self.setParam( r, 'fib', app=app)
carlosmscabralb9d85b72013-07-15 15:24:33 -0300747 self.setParam( r, 'cache', cache=cache )
carlosmscabral6d3dd602013-03-23 11:12:34 -0300748
carlosmscabralf40ecd12013-02-01 18:15:58 -0200749 return r
carlosmscabral6d3dd602013-03-23 11:12:34 -0300750
carlosmscabralf40ecd12013-02-01 18:15:58 -0200751 def configCCN(self):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300752
carlosmscabralf40ecd12013-02-01 18:15:58 -0200753 self.buildPeerIP()
754 self.setFIB()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300755
carlosmscabralf40ecd12013-02-01 18:15:58 -0200756 def buildPeerIP(self):
757 for iface in self.intfList():
758 link = iface.link
759 if link:
760 node1, node2 = link.intf1.node, link.intf2.node
761 if node1 == self:
762 self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
763 else:
764 self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300765
766
carlosmscabralf40ecd12013-02-01 18:15:58 -0200767 def setFIB(self):
768
769 for name in self.params['fib']:
770 if not name:
771 pass
772 else:
773 self.insert_fib(name[0],self.peerList[name[1]])
carlosmscabral6d3dd602013-03-23 11:12:34 -0300774
775
carlosmscabralf40ecd12013-02-01 18:15:58 -0200776 def insert_fib(self, uri, host):
777 self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
carlosmscabral6d3dd602013-03-23 11:12:34 -0300778 # self.cmd('ccndc add {0} udp {1}'.format(uri,host))
779
carlosmscabralf40ecd12013-02-01 18:15:58 -0200780 def terminate( self ):
781 "Stop node."
782 self.cmd('ccndstop')
carlosmscabral6d3dd602013-03-23 11:12:34 -0300783 self.cmd('killall -r zebra ospf')
carlosmscabralf40ecd12013-02-01 18:15:58 -0200784 Host.terminate(self)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300785
carlosmscabralf40ecd12013-02-01 18:15:58 -0200786 inited = False
787
carlosmscabral6d3dd602013-03-23 11:12:34 -0300788
carlosmscabralf40ecd12013-02-01 18:15:58 -0200789 @classmethod
790 def init( cls ):
791 "Initialization for CCNHost class"
792 cls.inited = True
carlosmscabral6d3dd602013-03-23 11:12:34 -0300793
carlosmscabralf40ecd12013-02-01 18:15:58 -0200794class CPULimitedCCNHost( CPULimitedHost ):
795 '''CPULimitedCCNHost is a Host that always runs the ccnd daemon and extends CPULimitedHost.
796 It should be used when one wants to limit the resources of CCN routers and hosts '''
carlosmscabral6d3dd602013-03-23 11:12:34 -0300797
carlosmscabralf40ecd12013-02-01 18:15:58 -0200798
799 def __init__( self, name, sched='cfs', **kwargs ):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300800
carlosmscabralf40ecd12013-02-01 18:15:58 -0200801 CPULimitedHost.__init__( self, name, sched, **kwargs )
802 if not CCNHost.inited:
803 CCNHost.init()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300804
carlosmscabralb9d85b72013-07-15 15:24:33 -0300805 self.cmd("export CCND_DEBUG=6")
806 self.cmd("export CCND_LOG=./log.{0}".format(self.name))
807 if self.params['cache'] != None:
808 self.cmd("export CCND_CAP={0}".format(self.params['cache']))
809
carlosmscabral6d3dd602013-03-23 11:12:34 -0300810 self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
carlosmscabralf40ecd12013-02-01 18:15:58 -0200811 self.cmd("ccndstart")
812 self.peerList = {}
813
carlosmscabralb9d85b72013-07-15 15:24:33 -0300814 def config( self, fib=None, app=None, cpu=None, cores=None, cache=None, **params):
carlosmscabralf40ecd12013-02-01 18:15:58 -0200815
816 r = CPULimitedHost.config(self,cpu,cores, **params)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300817
carlosmscabralf40ecd12013-02-01 18:15:58 -0200818 self.setParam( r, 'app', fib=fib )
819 self.setParam( r, 'fib', app=app)
carlosmscabralb9d85b72013-07-15 15:24:33 -0300820 self.setParam( r, 'cache', cache=cache)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300821
carlosmscabralf40ecd12013-02-01 18:15:58 -0200822 return r
carlosmscabral6d3dd602013-03-23 11:12:34 -0300823
carlosmscabralf40ecd12013-02-01 18:15:58 -0200824 def configCCN(self):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300825
carlosmscabralf40ecd12013-02-01 18:15:58 -0200826 self.buildPeerIP()
827 self.setFIB()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300828
carlosmscabralf40ecd12013-02-01 18:15:58 -0200829 def buildPeerIP(self):
830 for iface in self.intfList():
831 link = iface.link
832 if link:
833 node1, node2 = link.intf1.node, link.intf2.node
834 if node1 == self:
835 self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
836 else:
837 self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300838
839
carlosmscabralf40ecd12013-02-01 18:15:58 -0200840 def setFIB(self):
841
842 for name in self.params['fib']:
843 if not name:
844 pass
845 else:
846 self.insert_fib(name[0],self.peerList[name[1]])
carlosmscabral6d3dd602013-03-23 11:12:34 -0300847
848
carlosmscabralf40ecd12013-02-01 18:15:58 -0200849 def insert_fib(self, uri, host):
850 self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
carlosmscabral6d3dd602013-03-23 11:12:34 -0300851# self.cmd('ccndc add {0} udp {1}'.format(uri,host))
852
carlosmscabralf40ecd12013-02-01 18:15:58 -0200853 def terminate( self ):
854 "Stop node."
855 self.cmd('ccndstop')
carlosmscabral6d3dd602013-03-23 11:12:34 -0300856 self.cmd('killall -r zebra ospf')
carlosmscabralf40ecd12013-02-01 18:15:58 -0200857 Host.terminate(self)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300858
carlosmscabralf40ecd12013-02-01 18:15:58 -0200859 inited = False
860
carlosmscabral6d3dd602013-03-23 11:12:34 -0300861
carlosmscabralf40ecd12013-02-01 18:15:58 -0200862 @classmethod
863 def init( cls ):
864 "Initialization for CCNHost class"
865 cls.inited = True
866
867# Some important things to note:
868#
869# The "IP" address which setIP() assigns to the switch is not
870# an "IP address for the switch" in the sense of IP routing.
871# Rather, it is the IP address for the control interface,
872# on the control network, and it is only relevant to the
873# controller. If you are running in the root namespace
874# (which is the only way to run OVS at the moment), the
875# control interface is the loopback interface, and you
876# normally never want to change its IP address!
877#
878# In general, you NEVER want to attempt to use Linux's
879# network stack (i.e. ifconfig) to "assign" an IP address or
880# MAC address to a switch data port. Instead, you "assign"
881# the IP and MAC addresses in the controller by specifying
882# packets that you want to receive or send. The "MAC" address
883# reported by ifconfig for a switch data port is essentially
884# meaningless. It is important to understand this if you
885# want to create a functional router using OpenFlow.
886
887class Switch( Node ):
888 """A Switch is a Node that is running (or has execed?)
889 an OpenFlow switch."""
890
891 portBase = 1 # Switches start with port 1 in OpenFlow
892 dpidLen = 16 # digits in dpid passed to switch
893
894 def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
895 """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
896 opts: additional switch options
897 listenPort: port to listen on for dpctl connections"""
898 Node.__init__( self, name, **params )
899 self.dpid = dpid if dpid else self.defaultDpid()
900 self.opts = opts
901 self.listenPort = listenPort
902 if not self.inNamespace:
903 self.controlIntf = Intf( 'lo', self, port=0 )
904
905 def defaultDpid( self ):
906 "Derive dpid from switch name, s1 -> 1"
907 try:
908 dpid = int( re.findall( '\d+', self.name )[ 0 ] )
909 dpid = hex( dpid )[ 2: ]
910 dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
911 return dpid
912 except IndexError:
913 raise Exception( 'Unable to derive default datapath ID - '
914 'please either specify a dpid or use a '
915 'canonical switch name such as s23.' )
916
917 def defaultIntf( self ):
918 "Return control interface"
919 if self.controlIntf:
920 return self.controlIntf
921 else:
922 return Node.defaultIntf( self )
923
924 def sendCmd( self, *cmd, **kwargs ):
925 """Send command to Node.
926 cmd: string"""
927 kwargs.setdefault( 'printPid', False )
928 if not self.execed:
929 return Node.sendCmd( self, *cmd, **kwargs )
930 else:
931 error( '*** Error: %s has execed and cannot accept commands' %
932 self.name )
933
934 def __repr__( self ):
935 "More informative string representation"
936 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
937 for i in self.intfList() ] ) )
938 return '<%s %s: %s pid=%s> ' % (
939 self.__class__.__name__, self.name, intfs, self.pid )
940
941class UserSwitch( Switch ):
942 "User-space switch."
943
944 dpidLen = 12
945
946 def __init__( self, name, **kwargs ):
947 """Init.
948 name: name for the switch"""
949 Switch.__init__( self, name, **kwargs )
950 pathCheck( 'ofdatapath', 'ofprotocol',
951 moduleName='the OpenFlow reference user switch' +
952 '(openflow.org)' )
953 if self.listenPort:
954 self.opts += ' --listen=ptcp:%i ' % self.listenPort
955
956 @classmethod
957 def setup( cls ):
958 "Ensure any dependencies are loaded; if not, try to load them."
959 if not os.path.exists( '/dev/net/tun' ):
960 moduleDeps( add=TUN )
961
962 def dpctl( self, *args ):
963 "Run dpctl command"
964 if not self.listenPort:
965 return "can't run dpctl without passive listening port"
966 return self.cmd( 'dpctl ' + ' '.join( args ) +
967 ' tcp:127.0.0.1:%i' % self.listenPort )
968
969 def start( self, controllers ):
970 """Start OpenFlow reference user datapath.
971 Log to /tmp/sN-{ofd,ofp}.log.
972 controllers: list of controller objects"""
973 # Add controllers
974 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
975 for c in controllers ] )
976 ofdlog = '/tmp/' + self.name + '-ofd.log'
977 ofplog = '/tmp/' + self.name + '-ofp.log'
978 self.cmd( 'ifconfig lo up' )
979 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
980 self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
981 ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
982 ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
983 self.cmd( 'ofprotocol unix:/tmp/' + self.name +
984 ' ' + clist +
985 ' --fail=closed ' + self.opts +
986 ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
987
988 def stop( self ):
989 "Stop OpenFlow reference user datapath."
990 self.cmd( 'kill %ofdatapath' )
991 self.cmd( 'kill %ofprotocol' )
992 self.deleteIntfs()
993
994
995class OVSLegacyKernelSwitch( Switch ):
996 """Open VSwitch legacy kernel-space switch using ovs-openflowd.
997 Currently only works in the root namespace."""
998
999 def __init__( self, name, dp=None, **kwargs ):
1000 """Init.
1001 name: name for switch
1002 dp: netlink id (0, 1, 2, ...)
1003 defaultMAC: default MAC as unsigned int; random value if None"""
1004 Switch.__init__( self, name, **kwargs )
1005 self.dp = dp if dp else self.name
1006 self.intf = self.dp
1007 if self.inNamespace:
1008 error( "OVSKernelSwitch currently only works"
1009 " in the root namespace.\n" )
1010 exit( 1 )
1011
1012 @classmethod
1013 def setup( cls ):
1014 "Ensure any dependencies are loaded; if not, try to load them."
1015 pathCheck( 'ovs-dpctl', 'ovs-openflowd',
1016 moduleName='Open vSwitch (openvswitch.org)')
1017 moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1018
1019 def start( self, controllers ):
1020 "Start up kernel datapath."
1021 ofplog = '/tmp/' + self.name + '-ofp.log'
1022 quietRun( 'ifconfig lo up' )
1023 # Delete local datapath if it exists;
1024 # then create a new one monitoring the given interfaces
1025 self.cmd( 'ovs-dpctl del-dp ' + self.dp )
1026 self.cmd( 'ovs-dpctl add-dp ' + self.dp )
1027 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
1028 self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
1029 # Run protocol daemon
1030 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1031 for c in controllers ] )
1032 self.cmd( 'ovs-openflowd ' + self.dp +
1033 ' ' + clist +
1034 ' --fail=secure ' + self.opts +
1035 ' --datapath-id=' + self.dpid +
1036 ' 1>' + ofplog + ' 2>' + ofplog + '&' )
1037 self.execed = False
1038
1039 def stop( self ):
1040 "Terminate kernel datapath."
1041 quietRun( 'ovs-dpctl del-dp ' + self.dp )
1042 self.cmd( 'kill %ovs-openflowd' )
1043 self.deleteIntfs()
1044
1045
1046class OVSSwitch( Switch ):
1047 "Open vSwitch switch. Depends on ovs-vsctl."
1048
1049 def __init__( self, name, failMode='secure', **params ):
1050 """Init.
1051 name: name for switch
1052 failMode: controller loss behavior (secure|open)"""
1053 Switch.__init__( self, name, **params )
1054 self.failMode = failMode
1055
1056 @classmethod
1057 def setup( cls ):
1058 "Make sure Open vSwitch is installed and working"
1059 pathCheck( 'ovs-vsctl',
1060 moduleName='Open vSwitch (openvswitch.org)')
1061 # This should no longer be needed, and it breaks
1062 # with OVS 1.7 which has renamed the kernel module:
1063 # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1064 out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1065 if exitcode:
1066 error( out + err +
1067 'ovs-vsctl exited with code %d\n' % exitcode +
1068 '*** Error connecting to ovs-db with ovs-vsctl\n'
1069 'Make sure that Open vSwitch is installed, '
1070 'that ovsdb-server is running, and that\n'
1071 '"ovs-vsctl show" works correctly.\n'
1072 'You may wish to try '
1073 '"service openvswitch-switch start".\n' )
1074 exit( 1 )
1075
1076 def dpctl( self, *args ):
1077 "Run ovs-dpctl command"
1078 return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
1079
1080 @staticmethod
1081 def TCReapply( intf ):
1082 """Unfortunately OVS and Mininet are fighting
1083 over tc queuing disciplines. As a quick hack/
1084 workaround, we clear OVS's and reapply our own."""
1085 if type( intf ) is TCIntf:
1086 intf.config( **intf.params )
1087
1088 def attach( self, intf ):
1089 "Connect a data port"
1090 self.cmd( 'ovs-vsctl add-port', self, intf )
1091 self.cmd( 'ifconfig', intf, 'up' )
1092 self.TCReapply( intf )
1093
1094 def detach( self, intf ):
1095 "Disconnect a data port"
1096 self.cmd( 'ovs-vsctl del-port', self, intf )
1097
1098 def start( self, controllers ):
1099 "Start up a new OVS OpenFlow switch using ovs-vsctl"
1100 if self.inNamespace:
1101 raise Exception(
1102 'OVS kernel switch does not work in a namespace' )
1103 # We should probably call config instead, but this
1104 # requires some rethinking...
1105 self.cmd( 'ifconfig lo up' )
1106 # Annoyingly, --if-exists option seems not to work
1107 self.cmd( 'ovs-vsctl del-br', self )
1108 self.cmd( 'ovs-vsctl add-br', self )
1109 self.cmd( 'ovs-vsctl -- set Bridge', self,
1110 'other_config:datapath-id=' + self.dpid )
1111 self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
1112 for intf in self.intfList():
1113 if not intf.IP():
1114 self.attach( intf )
1115 # Add controllers
1116 clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1117 for c in controllers ] )
1118 if self.listenPort:
1119 clist += ' ptcp:%s' % self.listenPort
1120 self.cmd( 'ovs-vsctl set-controller', self, clist )
1121
1122 def stop( self ):
1123 "Terminate OVS switch."
1124 self.cmd( 'ovs-vsctl del-br', self )
1125 self.deleteIntfs()
1126
1127OVSKernelSwitch = OVSSwitch
1128
1129
1130class Controller( Node ):
1131 """A Controller is a Node that is running (or has execed?) an
1132 OpenFlow controller."""
1133
1134 def __init__( self, name, inNamespace=False, command='controller',
1135 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1136 port=6633, **params ):
1137 self.command = command
1138 self.cargs = cargs
1139 self.cdir = cdir
1140 self.ip = ip
1141 self.port = port
1142 Node.__init__( self, name, inNamespace=inNamespace,
1143 ip=ip, **params )
1144 self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
1145 self.checkListening()
1146
1147 def checkListening( self ):
1148 "Make sure no controllers are running on our port"
1149 # Verify that Telnet is installed first:
1150 out, _err, returnCode = errRun( "which telnet" )
1151 if 'telnet' not in out or returnCode != 0:
1152 raise Exception( "Error running telnet to check for listening "
1153 "controllers; please check that it is "
1154 "installed." )
1155 listening = self.cmd( "echo A | telnet -e A %s %d" %
1156 ( self.ip, self.port ) )
1157 if 'Unable' not in listening:
1158 servers = self.cmd( 'netstat -atp' ).split( '\n' )
1159 pstr = ':%d ' % self.port
1160 clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1161 raise Exception( "Please shut down the controller which is"
1162 " running on port %d:\n" % self.port +
1163 '\n'.join( clist ) )
1164
1165 def start( self ):
1166 """Start <controller> <args> on controller.
1167 Log to /tmp/cN.log"""
1168 pathCheck( self.command )
1169 cout = '/tmp/' + self.name + '.log'
1170 if self.cdir is not None:
1171 self.cmd( 'cd ' + self.cdir )
1172 self.cmd( self.command + ' ' + self.cargs % self.port +
1173 ' 1>' + cout + ' 2>' + cout + '&' )
1174 self.execed = False
1175
1176 def stop( self ):
1177 "Stop controller."
1178 self.cmd( 'kill %' + self.command )
1179 self.terminate()
1180
1181 def IP( self, intf=None ):
1182 "Return IP address of the Controller"
1183 if self.intfs:
1184 ip = Node.IP( self, intf )
1185 else:
1186 ip = self.ip
1187 return ip
1188
1189 def __repr__( self ):
1190 "More informative string representation"
1191 return '<%s %s: %s:%s pid=%s> ' % (
1192 self.__class__.__name__, self.name,
1193 self.IP(), self.port, self.pid )
1194
1195
1196class OVSController( Controller ):
1197 "Open vSwitch controller"
1198 def __init__( self, name, command='ovs-controller', **kwargs ):
1199 Controller.__init__( self, name, command=command, **kwargs )
1200
1201
1202class NOX( Controller ):
1203 "Controller to run a NOX application."
1204
1205 def __init__( self, name, *noxArgs, **kwargs ):
1206 """Init.
1207 name: name to give controller
1208 noxArgs: arguments (strings) to pass to NOX"""
1209 if not noxArgs:
1210 warn( 'warning: no NOX modules specified; '
1211 'running packetdump only\n' )
1212 noxArgs = [ 'packetdump' ]
1213 elif type( noxArgs ) not in ( list, tuple ):
1214 noxArgs = [ noxArgs ]
1215
1216 if 'NOX_CORE_DIR' not in os.environ:
1217 exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1218 noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1219
1220 Controller.__init__( self, name,
1221 command=noxCoreDir + '/nox_core',
1222 cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1223 ' '.join( noxArgs ),
1224 cdir=noxCoreDir,
1225 **kwargs )
1226
1227
1228class RemoteController( Controller ):
1229 "Controller running outside of Mininet's control."
1230
1231 def __init__( self, name, ip='127.0.0.1',
1232 port=6633, **kwargs):
1233 """Init.
1234 name: name to give controller
1235 ip: the IP address where the remote controller is
1236 listening
1237 port: the port where the remote controller is listening"""
1238 Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1239
1240 def start( self ):
1241 "Overridden to do nothing."
1242 return
1243
1244 def stop( self ):
1245 "Overridden to do nothing."
1246 return
1247
1248 def checkListening( self ):
1249 "Warn if remote controller is not accessible"
1250 listening = self.cmd( "echo A | telnet -e A %s %d" %
1251 ( self.ip, self.port ) )
1252 if 'Unable' in listening:
1253 warn( "Unable to contact the remote controller"
1254 " at %s:%d\n" % ( self.ip, self.port ) )