2 Node objects for Mininet.
4 Nodes provide a simple abstraction for interacting with hosts, switches
5 and controllers. Local nodes are simply one or more processes on the local
8 Node: superclass for all (primarily local) network nodes.
10 Host: 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. By default,
15 hosts share the root file system, but they may also specify private
18 CPULimitedHost: a virtual host whose CPU bandwidth is limited by
19 RT or CFS bandwidth limiting.
21 Switch: superclass for switch nodes.
23 UserSwitch: a switch using the user-space switch from the OpenFlow
24 reference implementation.
26 OVSSwitch: a switch using the Open vSwitch OpenFlow-compatible switch
27 implementation (openvswitch.org).
29 OVSBridge: an Ethernet bridge implemented using Open vSwitch.
32 IVSSwitch: OpenFlow switch using the Indigo Virtual Switch.
34 Controller: superclass for OpenFlow controllers. The default controller
35 is controller(8) from the reference implementation.
37 OVSController: The test controller from Open vSwitch.
39 NOXController: a controller node using NOX (noxrepo.org).
41 Ryu: The Ryu controller (https://osrg.github.io/ryu/)
43 RemoteController: a remote controller node, which may use any
44 arbitrary OpenFlow-compatible controller, and which is not
45 created or managed by Mininet.
49 - Possibly make Node, Switch and Controller more abstract so that
50 they can be used for both local and remote nodes
52 - Create proxy objects for remote nodes (Mininet: Cluster Edition)
60 from subprocess import Popen, PIPE
61 from time import sleep
63 from mininet.log import info, error, warn, debug
64 from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
65 numCores, retry, mountCgroups, BaseString, decode,
66 encode, Python3, which )
67 from mininet.moduledeps import moduleDeps, pathCheck, TUN
68 from mininet.link import Link, Intf, TCIntf, OVSIntf
69 from re import findall
70 from distutils.version import StrictVersion
73 """A virtual network node is simply a shell in a network namespace.
74 We communicate with it using pipes."""
76 portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
78 def __init__( self, name, inNamespace=True, **params ):
80 inNamespace: in network namespace?
81 privateDirs: list of private directory strings or tuples
82 params: Node parameters (see config() for details)"""
84 # Make sure class actually works
87 self.name = params.get( 'name', name )
88 self.privateDirs = params.get( 'privateDirs', [] )
89 self.inNamespace = params.get( 'inNamespace', inNamespace )
91 # Python 3 complains if we don't wait for shell exit
92 self.waitExited = params.get( 'waitExited', Python3 )
94 # Stash configuration parameters for future reference
97 self.intfs = {} # dict of port numbers to interfaces
98 self.ports = {} # dict of interfaces to port numbers
99 # replace with Port objects, eventually ?
100 self.nameToIntf = {} # dict of interface names to Intfs
103 ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
104 self.lastPid, self.lastCmd, self.pollOut ) = (
105 None, None, None, None, None, None, None, None )
109 # Start command interpreter shell
110 self.master, self.slave = None, None # pylint
112 self.mountPrivateDirs()
114 # File descriptor to node mapping support
115 # Class variables and methods
117 inToNode = {} # mapping of input fds to nodes
118 outToNode = {} # mapping of output fds to nodes
121 def fdToNode( cls, fd ):
122 """Return node corresponding to given file descriptor.
125 node = cls.outToNode.get( fd )
126 return node or cls.inToNode.get( fd )
128 # Command support via shell process in namespace
129 def startShell( self, mnopts=None ):
130 "Start a shell process for running commands"
132 error( "%s: shell is already running\n" % self.name )
134 # mnexec: (c)lose descriptors, (d)etach from tty,
135 # (p)rint pid, and run in (n)amespace
136 opts = '-cd' if mnopts is None else mnopts
139 # bash -i: force interactive
140 # -s: pass $* to shell, and make process easy to find in ps
141 # prompt is set to sentinel chr( 127 )
142 cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ),
143 'bash', '--norc', '--noediting',
144 '-is', 'mininet:' + self.name ]
146 # Spawn a shell subprocess in a pseudo-tty, to disable buffering
147 # in the subprocess and insulate it from signals (e.g. SIGINT)
148 # received by the parent
149 self.master, self.slave = pty.openpty()
150 self.shell = self._popen( cmd, stdin=self.slave, stdout=self.slave,
151 stderr=self.slave, close_fds=False )
152 # XXX BL: This doesn't seem right, and we should also probably
153 # close our files when we exit...
154 self.stdin = os.fdopen( self.master, 'r' )
155 self.stdout = self.stdin
156 self.pid = self.shell.pid
157 self.pollOut = select.poll()
158 self.pollOut.register( self.stdout )
159 # Maintain mapping between file descriptors and nodes
160 # This is useful for monitoring multiple nodes
161 # using select.poll()
162 self.outToNode[ self.stdout.fileno() ] = self
163 self.inToNode[ self.stdin.fileno() ] = self
170 data = self.read( 1024 )
171 if data[ -1 ] == chr( 127 ):
175 # +m: disable job control notification
176 self.cmd( 'unset HISTFILE; stty -echo; set +m' )
178 def mountPrivateDirs( self ):
179 "mount private directories"
180 # Avoid expanding a string into a list of chars
181 assert not isinstance( self.privateDirs, BaseString )
182 for directory in self.privateDirs:
183 if isinstance( directory, tuple ):
184 # mount given private directory
185 privateDir = directory[ 1 ] % self.__dict__
186 mountPoint = directory[ 0 ]
187 self.cmd( 'mkdir -p %s' % privateDir )
188 self.cmd( 'mkdir -p %s' % mountPoint )
189 self.cmd( 'mount --bind %s %s' %
190 ( privateDir, mountPoint ) )
192 # mount temporary filesystem on directory
193 self.cmd( 'mkdir -p %s' % directory )
194 self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
196 def unmountPrivateDirs( self ):
197 "mount private directories"
198 for directory in self.privateDirs:
199 if isinstance( directory, tuple ):
200 self.cmd( 'umount ', directory[ 0 ] )
202 self.cmd( 'umount ', directory )
204 def _popen( self, cmd, **params ):
205 """Internal method: spawn and return a process
206 cmd: command to run (list)
207 params: parameters to Popen()"""
208 # Leave this is as an instance method for now
210 popen = Popen( cmd, **params )
211 debug( '_popen', cmd, popen.pid )
215 "Help python collect its garbage."
216 # We used to do this, but it slows us down:
217 # Intfs may end up in root NS
218 # for intfName in self.intfNames():
219 # if self.name in intfName:
220 # quietRun( 'ip link del ' + intfName )
226 debug( 'waiting for', self.pid, 'to terminate\n' )
230 # Subshell I/O, commands and control
232 def read( self, maxbytes=1024 ):
233 """Buffered read from node, potentially blocking.
234 maxbytes: maximum number of bytes to return"""
235 count = len( self.readbuf )
237 data = decode( os.read( self.stdout.fileno(), maxbytes - count ) )
239 if maxbytes >= len( self.readbuf ):
240 result = self.readbuf
243 result = self.readbuf[ :maxbytes ]
244 self.readbuf = self.readbuf[ maxbytes: ]
247 def readline( self ):
248 """Buffered readline from node, potentially blocking.
249 returns: line (minus newline) or None"""
250 self.readbuf += self.read( 1024 )
251 if '\n' not in self.readbuf:
253 pos = self.readbuf.find( '\n' )
254 line = self.readbuf[ 0: pos ]
255 self.readbuf = self.readbuf[ pos + 1: ]
258 def write( self, data ):
259 """Write data to node.
261 os.write( self.stdin.fileno(), encode( data ) )
263 def terminate( self ):
264 "Send kill signal to Node and clean up after it."
265 self.unmountPrivateDirs()
267 if self.shell.poll() is None:
268 os.killpg( self.shell.pid, signal.SIGHUP )
271 def stop( self, deleteIntfs=False ):
273 deleteIntfs: delete interfaces? (False)"""
278 def waitReadable( self, timeoutms=None ):
279 """Wait until node's output is readable.
280 timeoutms: timeout in ms or None to wait indefinitely.
281 returns: result of poll()"""
282 if len( self.readbuf ) == 0:
283 return self.pollOut.poll( timeoutms )
285 def sendCmd( self, *args, **kwargs ):
286 """Send a command, followed by a command to echo a sentinel,
287 and return without waiting for the command to complete.
288 args: command and arguments, or string
289 printPid: print command's PID? (False)"""
290 assert self.shell and not self.waiting
291 printPid = kwargs.get( 'printPid', False )
292 # Allow sendCmd( [ list ] )
293 if len( args ) == 1 and isinstance( args[ 0 ], list ):
295 # Allow sendCmd( cmd, arg1, arg2... )
296 elif len( args ) > 0:
299 if not isinstance( cmd, str ):
300 cmd = ' '.join( [ str( c ) for c in cmd ] )
301 if not re.search( r'\w', cmd ):
302 # Replace empty commands with something harmless
305 # if a builtin command is backgrounded, it still yields a PID
306 if len( cmd ) > 0 and cmd[ -1 ] == '&':
307 # print ^A{pid}\n so monitor() can set lastPid
308 cmd += ' printf "\\001%d\\012" $! '
309 elif printPid and not isShellBuiltin( cmd ):
310 cmd = 'mnexec -p ' + cmd
311 self.write( cmd + '\n' )
315 def sendInt( self, intr=chr( 3 ) ):
316 "Interrupt running command."
317 debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
320 def monitor( self, timeoutms=None, findPid=True ):
321 """Monitor and return the output of a command.
322 Set self.waiting to False if command has completed.
323 timeoutms: timeout in ms or None to wait indefinitely
324 findPid: look for PID from mnexec -p"""
325 ready = self.waitReadable( timeoutms )
328 data = self.read( 1024 )
329 pidre = r'\[\d+\] \d+\r\n'
331 marker = chr( 1 ) + r'\d+\r\n'
332 if findPid and chr( 1 ) in data:
333 # suppress the job and PID of a backgrounded command
334 if re.findall( pidre, data ):
335 data = re.sub( pidre, '', data )
336 # Marker can be read in chunks; continue until all of it is read
337 while not re.findall( marker, data ):
338 data += self.read( 1024 )
339 markers = re.findall( marker, data )
341 self.lastPid = int( markers[ 0 ][ 1: ] )
342 data = re.sub( marker, '', data )
343 # Look for sentinel/EOF
344 if len( data ) > 0 and data[ -1 ] == chr( 127 ):
347 elif chr( 127 ) in data:
349 data = data.replace( chr( 127 ), '' )
352 def waitOutput( self, verbose=False, findPid=True ):
353 """Wait for a command to complete.
354 Completion is signaled by a sentinel character, ASCII(127)
355 appearing in the output stream. Wait for the sentinel and return
356 the output, including trailing newline.
357 verbose: print output interactively"""
358 log = info if verbose else debug
361 data = self.monitor( findPid=findPid )
366 def cmd( self, *args, **kwargs ):
367 """Send a command, wait for output, and return it.
369 verbose = kwargs.get( 'verbose', False )
370 log = info if verbose else debug
371 log( '*** %s : %s\n' % ( self.name, args ) )
373 self.sendCmd( *args, **kwargs )
374 return self.waitOutput( verbose )
376 warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) )
378 def cmdPrint( self, *args):
379 """Call cmd and printing its output
381 return self.cmd( *args, **{ 'verbose': True } )
383 def popen( self, *args, **kwargs ):
384 """Return a Popen() object in our namespace
385 args: Popen() args, single list, or string
386 kwargs: Popen() keyword args"""
387 defaults = { 'stdout': PIPE, 'stderr': PIPE,
389 [ 'mnexec', '-da', str( self.pid ) ] }
390 defaults.update( kwargs )
391 shell = defaults.pop( 'shell', False )
393 if isinstance( args[ 0 ], list ):
394 # popen([cmd, arg1, arg2...])
396 elif isinstance( args[ 0 ], BaseString ):
397 # popen("cmd arg1 arg2...")
398 cmd = [ args[ 0 ] ] if shell else args[ 0 ].split()
400 raise Exception( 'popen() requires a string or list' )
401 elif len( args ) > 0:
402 # popen( cmd, arg1, arg2... )
405 cmd = [ os.environ[ 'SHELL' ], '-c' ] + [ ' '.join( cmd ) ]
406 # Attach to our namespace using mnexec -a
407 cmd = defaults.pop( 'mncmd' ) + cmd
408 popen = self._popen( cmd, **defaults )
411 def pexec( self, *args, **kwargs ):
412 """Execute a command using popen
413 returns: out, err, exitcode"""
414 popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
416 # Warning: this can fail with large numbers of fds!
417 out, err = popen.communicate()
418 exitcode = popen.wait()
419 return decode( out ), decode( err ), exitcode
421 # Interface management, configuration, and routing
423 # BL notes: This might be a bit redundant or over-complicated.
424 # However, it does allow a bit of specialization, including
425 # changing the canonical interface names. It's also tricky since
426 # the real interfaces are created as veth pairs, so we can't
427 # make a single interface at a time.
430 "Return the next port number to allocate."
431 if len( self.ports ) > 0:
432 return max( self.ports.values() ) + 1
435 def addIntf( self, intf, port=None, moveIntfFn=moveIntf ):
438 port: port number (optional, typically OpenFlow port number)
439 moveIntfFn: function to move interface (optional)"""
441 port = self.newPort()
442 self.intfs[ port ] = intf
443 self.ports[ intf ] = port
444 self.nameToIntf[ intf.name ] = intf
446 debug( 'added intf %s (%d) to node %s\n' % (
447 intf, port, self.name ) )
449 debug( 'moving', intf, 'into namespace for', self.name, '\n' )
450 moveIntfFn( intf.name, self )
452 def delIntf( self, intf ):
453 """Remove interface from Node's known interfaces
454 Note: to fully delete interface, call intf.delete() instead"""
455 port = self.ports.get( intf )
457 del self.intfs[ port ]
458 del self.ports[ intf ]
459 del self.nameToIntf[ intf.name ]
461 def defaultIntf( self ):
462 "Return interface for lowest port"
463 ports = self.intfs.keys()
465 return self.intfs[ min( ports ) ]
467 warn( '*** defaultIntf: warning:', self.name,
468 'has no interfaces\n' )
470 def intf( self, intf=None ):
471 """Return our interface object with given string name,
472 default intf if name is falsy (None, empty string, etc).
473 or the input intf arg.
475 Having this fcn return its arg for Intf objects makes it
476 easier to construct functions with flexible input args for
477 interfaces (those that accept both string names and Intf objects).
480 return self.defaultIntf()
481 elif isinstance( intf, BaseString):
482 return self.nameToIntf[ intf ]
486 def connectionsTo( self, node):
487 "Return [ intf1, intf2... ] for all intfs that connect self to node."
488 # We could optimize this if it is important
490 for intf in self.intfList():
493 node1, node2 = link.intf1.node, link.intf2.node
494 if node1 == self and node2 == node:
495 connections += [ ( intf, link.intf2 ) ]
496 elif node1 == node and node2 == self:
497 connections += [ ( intf, link.intf1 ) ]
500 def deleteIntfs( self, checkName=True ):
501 """Delete all of our interfaces.
502 checkName: only delete interfaces that contain our name"""
503 # In theory the interfaces should go away after we shut down.
504 # However, this takes time, so we're better off removing them
505 # explicitly so that we won't get errors if we run before they
506 # have been removed by the kernel. Unfortunately this is very slow,
507 # at least with Linux kernels before 2.6.33
508 for intf in list( self.intfs.values() ):
509 # Protect against deleting hardware interfaces
510 if ( self.name in intf.name ) or ( not checkName ):
516 def setARP( self, ip, mac ):
518 ip: IP address as string
519 mac: MAC address as string"""
520 result = self.cmd( 'arp', '-s', ip, mac )
523 def setHostRoute( self, ip, intf ):
524 """Add route to host.
525 ip: IP address as dotted decimal
526 intf: string, interface name"""
527 return self.cmd( 'route add -host', ip, 'dev', intf )
529 def setDefaultRoute( self, intf=None ):
530 """Set the default route to go through intf.
531 intf: Intf or {dev <intfname> via <gw-ip> ...}"""
532 # Note setParam won't call us if intf is none
533 if isinstance( intf, BaseString ) and ' ' in intf:
536 params = 'dev %s' % intf
537 # Do this in one line in case we're messing with the root namespace
538 self.cmd( 'ip route del default; ip route add default', params )
540 # Convenience and configuration methods
542 def setMAC( self, mac, intf=None ):
543 """Set the MAC address for an interface.
544 intf: intf or intf name
545 mac: MAC address as string"""
546 return self.intf( intf ).setMAC( mac )
548 def setIP( self, ip, prefixLen=8, intf=None, **kwargs ):
549 """Set the IP address for an interface.
550 intf: intf or intf name
551 ip: IP address as a string
552 prefixLen: prefix length, e.g. 8 for /8 or 16M addrs
553 kwargs: any additional arguments for intf.setIP"""
554 return self.intf( intf ).setIP( ip, prefixLen, **kwargs )
556 def IP( self, intf=None ):
557 "Return IP address of a node or specific interface."
558 return self.intf( intf ).IP()
560 def MAC( self, intf=None ):
561 "Return MAC address of a node or specific interface."
562 return self.intf( intf ).MAC()
564 def intfIsUp( self, intf=None ):
565 "Check if an interface is up."
566 return self.intf( intf ).isUp()
568 # The reason why we configure things in this way is so
569 # That the parameters can be listed and documented in
571 # Dealing with subclasses and superclasses is slightly
572 # annoying, but at least the information is there!
574 def setParam( self, results, method, **param ):
575 """Internal method: configure a *single* parameter
576 results: dict of results to update
577 method: config method name
578 param: arg=value (ignore if value=None)
579 value may also be list or dict"""
580 name, value = list( param.items() )[ 0 ]
583 f = getattr( self, method, None )
586 if isinstance( value, list ):
588 elif isinstance( value, dict ):
589 result = f( **value )
592 results[ name ] = result
595 def config( self, mac=None, ip=None,
596 defaultRoute=None, lo='up', **_params ):
597 """Configure Node according to (optional) parameters:
598 mac: MAC address for default interface
599 ip: IP address for default interface
600 ifconfig: arbitrary interface configuration
601 Subclasses should override this method and call
602 the parent class's config(**params)"""
603 # If we were overriding this method, we would call
604 # the superclass config method here as follows:
605 # r = Parent.config( **_params )
607 self.setParam( r, 'setMAC', mac=mac )
608 self.setParam( r, 'setIP', ip=ip )
609 self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
610 # This should be examined
611 self.cmd( 'ifconfig lo ' + lo )
614 def configDefault( self, **moreParams ):
615 "Configure with default parameters"
616 self.params.update( moreParams )
617 self.config( **self.params )
619 # This is here for backward compatibility
620 def linkTo( self, node, link=Link ):
621 """(Deprecated) Link to another node
622 replace with Link( node1, node2)"""
623 return link( self, node )
627 def intfList( self ):
628 "List of our interfaces sorted by port number"
629 return [ self.intfs[ p ] for p in sorted( self.intfs.keys() ) ]
631 def intfNames( self ):
632 "The names of our interfaces sorted by port number"
633 return [ str( i ) for i in self.intfList() ]
635 def __repr__( self ):
636 "More informative string representation"
637 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
638 for i in self.intfList() ] ) )
639 return '<%s %s: %s pid=%s> ' % (
640 self.__class__.__name__, self.name, intfs, self.pid )
643 "Abbreviated string representation"
646 # Automatic class setup support
651 def checkSetup( cls ):
652 "Make sure our class and superclasses are set up"
653 while cls and not getattr( cls, 'isSetup', True ):
657 cls = getattr( type( cls ), '__base__', None )
661 "Make sure our class dependencies are available"
662 pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
665 "A host is simply a Node"
668 class CPULimitedHost( Host ):
672 def __init__( self, name, sched='cfs', **kwargs ):
673 Host.__init__( self, name, **kwargs )
674 # Initialize class if necessary
675 if not CPULimitedHost.inited:
676 CPULimitedHost.init()
677 # Create a cgroup and move shell into it
678 self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
679 errFail( 'cgcreate -g ' + self.cgroup )
680 # We don't add ourselves to a cpuset because you must
681 # specify the cpu and memory placement first
682 errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
683 # BL: Setting the correct period/quota is tricky, particularly
684 # for RT. RT allows very small quotas, but the overhead
685 # seems to be high. CFS has a mininimum quota of 1 ms, but
686 # still does better with larger period values.
687 self.period_us = kwargs.get( 'period_us', 100000 )
690 self.checkRtGroupSched()
693 def cgroupSet( self, param, value, resource='cpu' ):
694 "Set a cgroup parameter and return its value"
695 cmd = 'cgset -r %s.%s=%s /%s' % (
696 resource, param, value, self.name )
698 nvalue = int( self.cgroupGet( param, resource ) )
700 error( '*** error: cgroupSet: %s set to %s instead of %s\n'
701 % ( param, nvalue, value ) )
704 def cgroupGet( self, param, resource='cpu' ):
705 "Return value of cgroup parameter"
706 cmd = 'cgget -r %s.%s /%s' % (
707 resource, param, self.name )
708 return int( quietRun( cmd ).split()[ -1 ] )
710 def cgroupDel( self ):
711 "Clean up our cgroup"
712 # info( '*** deleting cgroup', self.cgroup, '\n' )
713 _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
714 # Sometimes cgdelete returns a resource busy error but still
715 # deletes the group; next attempt will give "no such file"
716 return exitcode == 0 or ( 'no such file' in _err.lower() )
718 def popen( self, *args, **kwargs ):
719 """Return a Popen() object in node's namespace
720 args: Popen() args, single list, or string
721 kwargs: Popen() keyword args"""
722 # Tell mnexec to execute command in our cgroup
723 mncmd = kwargs.pop( 'mncmd', [ 'mnexec', '-g', self.name,
724 '-da', str( self.pid ) ] )
725 # if our cgroup is not given any cpu time,
726 # we cannot assign the RR Scheduler.
727 if self.sched == 'rt':
728 if int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) ) <= 0:
729 mncmd += [ '-r', str( self.rtprio ) ]
731 debug( '*** error: not enough cpu time available for %s.' %
732 self.name, 'Using cfs scheduler for subprocess\n' )
733 return Host.popen( self, *args, mncmd=mncmd, **kwargs )
736 "Clean up Node, then clean up our cgroup"
737 super( CPULimitedHost, self ).cleanup()
738 retry( retries=3, delaySecs=.1, fn=self.cgroupDel )
740 _rtGroupSched = False # internal class var: Is CONFIG_RT_GROUP_SCHED set?
743 def checkRtGroupSched( cls ):
744 "Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT"
745 if not cls._rtGroupSched:
746 release = quietRun( 'uname -r' ).strip('\r\n')
747 output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' %
749 if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
750 error( '\n*** error: please enable RT_GROUP_SCHED '
753 cls._rtGroupSched = True
756 "Set RT scheduling priority"
757 quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
758 result = quietRun( 'chrt -p %s' % self.pid )
759 firstline = result.split( '\n' )[ 0 ]
760 lastword = firstline.split( ' ' )[ -1 ]
761 if lastword != 'SCHED_RR':
762 error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
765 def rtInfo( self, f ):
766 "Internal method: return parameters for RT bandwidth"
767 pstr, qstr = 'rt_period_us', 'rt_runtime_us'
768 # RT uses wall clock time for period and quota
769 quota = int( self.period_us * f )
770 return pstr, qstr, self.period_us, quota
772 def cfsInfo( self, f ):
773 "Internal method: return parameters for CFS bandwidth"
774 pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
775 # CFS uses wall clock time for period and CPU time for quota.
776 quota = int( self.period_us * f * numCores() )
777 period = self.period_us
778 if f > 0 and quota < 1000:
779 debug( '(cfsInfo: increasing default period) ' )
781 period = int( quota / f / numCores() )
782 # Reset to unlimited on negative quota
785 return pstr, qstr, period, quota
788 # This may not be the right API,
789 # since it doesn't specify CPU bandwidth in "absolute"
790 # units the way link bandwidth is specified.
791 # We should use MIPS or SPECINT or something instead.
792 # Alternatively, we should change from system fraction
793 # to CPU seconds per second, essentially assuming that
794 # all CPUs are the same.
796 def setCPUFrac( self, f, sched=None ):
797 """Set overall CPU fraction for this host
798 f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited)
800 Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
801 and 'rt' requires CONFIG_RT_GROUP_SCHED"""
806 raise Exception( 'Please set a positive CPU fraction'
808 pstr, qstr, period, quota = self.rtInfo( f )
810 pstr, qstr, period, quota = self.cfsInfo( f )
813 # Set cgroup's period and quota
814 setPeriod = self.cgroupSet( pstr, period )
815 setQuota = self.cgroupSet( qstr, quota )
817 # Set RT priority if necessary
819 info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
821 def setCPUs( self, cores, mems=0 ):
822 "Specify (real) cores that our cgroup can run on"
825 if isinstance( cores, list ):
826 cores = ','.join( [ str( c ) for c in cores ] )
827 self.cgroupSet( resource='cpuset', param='cpus',
829 # Memory placement is probably not relevant, but we
830 # must specify it anyway
831 self.cgroupSet( resource='cpuset', param='mems',
833 # We have to do this here after we've specified
835 errFail( 'cgclassify -g cpuset:/%s %s' % (
836 self.name, self.pid ) )
838 def config( self, cpu=-1, cores=None, **params ):
839 """cpu: desired overall system CPU fraction
840 cores: (real) core(s) this host can run on
841 params: parameters for Node.config()"""
842 r = Node.config( self, **params )
843 # Was considering cpu={'cpu': cpu , 'sched': sched}, but
844 # that seems redundant
845 self.setParam( r, 'setCPUFrac', cpu=cpu )
846 self.setParam( r, 'setCPUs', cores=cores )
853 "Initialization for CPULimitedHost class"
858 # Some important things to note:
860 # The "IP" address which setIP() assigns to the switch is not
861 # an "IP address for the switch" in the sense of IP routing.
862 # Rather, it is the IP address for the control interface,
863 # on the control network, and it is only relevant to the
864 # controller. If you are running in the root namespace
865 # (which is the only way to run OVS at the moment), the
866 # control interface is the loopback interface, and you
867 # normally never want to change its IP address!
869 # In general, you NEVER want to attempt to use Linux's
870 # network stack (i.e. ifconfig) to "assign" an IP address or
871 # MAC address to a switch data port. Instead, you "assign"
872 # the IP and MAC addresses in the controller by specifying
873 # packets that you want to receive or send. The "MAC" address
874 # reported by ifconfig for a switch data port is essentially
875 # meaningless. It is important to understand this if you
876 # want to create a functional router using OpenFlow.
878 class Switch( Node ):
879 """A Switch is a Node that is running (or has execed?)
880 an OpenFlow switch."""
882 portBase = 1 # Switches start with port 1 in OpenFlow
883 dpidLen = 16 # digits in dpid passed to switch
885 def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
886 """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
887 opts: additional switch options
888 listenPort: port to listen on for dpctl connections"""
889 Node.__init__( self, name, **params )
890 self.dpid = self.defaultDpid( dpid )
892 self.listenPort = listenPort
893 if not self.inNamespace:
894 self.controlIntf = Intf( 'lo', self, port=0 )
896 def defaultDpid( self, dpid=None ):
897 "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
899 # Remove any colons and make sure it's a good hex number
900 dpid = dpid.replace( ':', '' )
901 assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
903 # Use hex of the first number in the switch name
904 nums = re.findall( r'\d+', self.name )
906 dpid = hex( int( nums[ 0 ] ) )[ 2: ]
908 self.terminate() # Python 3.6 crash workaround
909 raise Exception( 'Unable to derive default datapath ID - '
910 'please either specify a dpid or use a '
911 'canonical switch name such as s23.' )
912 return '0' * ( self.dpidLen - len( dpid ) ) + dpid
914 def defaultIntf( self ):
915 "Return control interface"
917 return self.controlIntf
919 return Node.defaultIntf( self )
921 def sendCmd( self, *cmd, **kwargs ):
922 """Send command to Node.
924 kwargs.setdefault( 'printPid', False )
926 return Node.sendCmd( self, *cmd, **kwargs )
928 error( '*** Error: %s has execed and cannot accept commands' %
931 def connected( self ):
932 "Is the switch connected to a controller? (override this method)"
933 # Assume that we are connected by default to whatever we need to
934 # be connected to. This should be overridden by any OpenFlow
935 # switch, but not by a standalone bridge.
936 debug( 'Assuming', repr( self ), 'is connected to a controller\n' )
939 def stop( self, deleteIntfs=True ):
941 deleteIntfs: delete interfaces? (True)"""
945 def __repr__( self ):
946 "More informative string representation"
947 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
948 for i in self.intfList() ] ) )
949 return '<%s %s: %s pid=%s> ' % (
950 self.__class__.__name__, self.name, intfs, self.pid )
953 class UserSwitch( Switch ):
958 def __init__( self, name, dpopts='--no-slicing', **kwargs ):
960 name: name for the switch
961 dpopts: additional arguments to ofdatapath (--no-slicing)"""
962 Switch.__init__( self, name, **kwargs )
963 pathCheck( 'ofdatapath', 'ofprotocol',
964 moduleName='the OpenFlow reference user switch' +
967 self.opts += ' --listen=ptcp:%i ' % self.listenPort
969 self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
974 "Ensure any dependencies are loaded; if not, try to load them."
975 if not os.path.exists( '/dev/net/tun' ):
976 moduleDeps( add=TUN )
978 def dpctl( self, *args ):
981 if not self.listenPort:
982 listenAddr = 'unix:/tmp/%s.listen' % self.name
984 listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
985 return self.cmd( 'dpctl ' + ' '.join( args ) +
988 def connected( self ):
989 "Is the switch connected to a controller?"
990 status = self.dpctl( 'status' )
991 return ( 'remote.is-connected=true' in status and
992 'local.is-connected=true' in status )
995 def TCReapply( intf ):
996 """Unfortunately user switch and Mininet are fighting
997 over tc queuing disciplines. To resolve the conflict,
998 we re-create the user switch's configuration, but as a
999 leaf of the TCIntf-created configuration."""
1000 if isinstance( intf, TCIntf ):
1001 ifspeed = 10000000000 # 10 Gbps
1002 minspeed = ifspeed * 0.001
1004 res = intf.config( **intf.params )
1006 if res is None: # link may not have TC parameters
1009 # Re-add qdisc, root, and default classes user switch created, but
1010 # with new parent, as setup by Mininet's TCIntf
1011 parent = res['parent']
1012 intf.tc( "%s qdisc add dev %s " + parent +
1013 " handle 1: htb default 0xfffe" )
1014 intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
1016 intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
1017 "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
1019 def start( self, controllers ):
1020 """Start OpenFlow reference user datapath.
1021 Log to /tmp/sN-{ofd,ofp}.log.
1022 controllers: list of controller objects"""
1024 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1025 for c in controllers ] )
1026 ofdlog = '/tmp/' + self.name + '-ofd.log'
1027 ofplog = '/tmp/' + self.name + '-ofp.log'
1028 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
1029 self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
1030 ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
1032 ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
1033 self.cmd( 'ofprotocol unix:/tmp/' + self.name +
1035 ' --fail=closed ' + self.opts +
1036 ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
1037 if "no-slicing" not in self.dpopts:
1038 # Only TCReapply if slicing is enable
1039 sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
1040 for intf in self.intfList():
1042 self.TCReapply( intf )
1044 def stop( self, deleteIntfs=True ):
1045 """Stop OpenFlow reference user datapath.
1046 deleteIntfs: delete interfaces? (True)"""
1047 self.cmd( 'kill %ofdatapath' )
1048 self.cmd( 'kill %ofprotocol' )
1049 super( UserSwitch, self ).stop( deleteIntfs )
1052 class OVSSwitch( Switch ):
1053 "Open vSwitch switch. Depends on ovs-vsctl."
1055 def __init__( self, name, failMode='secure', datapath='kernel',
1056 inband=False, protocols=None,
1057 reconnectms=1000, stp=False, batch=False, **params ):
1058 """name: name for switch
1059 failMode: controller loss behavior (secure|standalone)
1060 datapath: userspace or kernel mode (kernel|user)
1061 inband: use in-band control (False)
1062 protocols: use specific OpenFlow version(s) (e.g. OpenFlow13)
1063 Unspecified (or old OVS version) uses OVS default
1064 reconnectms: max reconnect timeout in ms (0/None for default)
1065 stp: enable STP (False, requires failMode=standalone)
1066 batch: enable batch startup (False)"""
1067 Switch.__init__( self, name, **params )
1068 self.failMode = failMode
1069 self.datapath = datapath
1070 self.inband = inband
1071 self.protocols = protocols
1072 self.reconnectms = reconnectms
1074 self._uuids = [] # controller UUIDs
1076 self.commands = [] # saved commands for batch startup
1080 "Make sure Open vSwitch is installed and working"
1081 pathCheck( 'ovs-vsctl',
1082 moduleName='Open vSwitch (openvswitch.org)')
1083 # This should no longer be needed, and it breaks
1084 # with OVS 1.7 which has renamed the kernel module:
1085 # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1086 out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1089 'ovs-vsctl exited with code %d\n' % exitcode +
1090 '*** Error connecting to ovs-db with ovs-vsctl\n'
1091 'Make sure that Open vSwitch is installed, '
1092 'that ovsdb-server is running, and that\n'
1093 '"ovs-vsctl show" works correctly.\n'
1094 'You may wish to try '
1095 '"service openvswitch-switch start".\n' )
1097 version = quietRun( 'ovs-vsctl --version' )
1098 cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
1101 def isOldOVS( cls ):
1102 "Is OVS ersion < 1.10?"
1103 return ( StrictVersion( cls.OVSVersion ) <
1104 StrictVersion( '1.10' ) )
1106 def dpctl( self, *args ):
1107 "Run ovs-ofctl command"
1108 return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1110 def vsctl( self, *args, **kwargs ):
1111 "Run ovs-vsctl command (or queue for later execution)"
1113 cmd = ' '.join( str( arg ).strip() for arg in args )
1114 self.commands.append( cmd )
1116 return self.cmd( 'ovs-vsctl', *args, **kwargs )
1119 def TCReapply( intf ):
1120 """Unfortunately OVS and Mininet are fighting
1121 over tc queuing disciplines. As a quick hack/
1122 workaround, we clear OVS's and reapply our own."""
1123 if isinstance( intf, TCIntf ):
1124 intf.config( **intf.params )
1126 def attach( self, intf ):
1127 "Connect a data port"
1128 self.vsctl( 'add-port', self, intf )
1129 self.cmd( 'ifconfig', intf, 'up' )
1130 self.TCReapply( intf )
1132 def detach( self, intf ):
1133 "Disconnect a data port"
1134 self.vsctl( 'del-port', self, intf )
1136 def controllerUUIDs( self, update=False ):
1137 """Return ovsdb UUIDs for our controllers
1138 update: update cached value"""
1139 if not self._uuids or update:
1140 controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1141 'Controller' ).strip()
1142 if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1143 controllers = controllers[ 1 : -1 ]
1145 self._uuids = [ c.strip()
1146 for c in controllers.split( ',' ) ]
1149 def connected( self ):
1150 "Are we connected to at least one of our controllers?"
1151 for uuid in self.controllerUUIDs():
1152 if 'true' in self.vsctl( '-- get Controller',
1153 uuid, 'is_connected' ):
1155 return self.failMode == 'standalone'
1157 def intfOpts( self, intf ):
1158 "Return OVS interface options for intf"
1160 if not self.isOldOVS():
1161 # ofport_request is not supported on old OVS
1162 opts += ' ofport_request=%s' % self.ports[ intf ]
1163 # Patch ports don't work well with old OVS
1164 if isinstance( intf, OVSIntf ):
1165 intf1, intf2 = intf.link.intf1, intf.link.intf2
1166 peer = intf1 if intf1 != intf else intf2
1167 opts += ' type=patch options:peer=%s' % peer
1168 return '' if not opts else ' -- set Interface %s' % intf + opts
1170 def bridgeOpts( self ):
1171 "Return OVS bridge options"
1172 opts = ( ' other_config:datapath-id=%s' % self.dpid +
1173 ' fail_mode=%s' % self.failMode )
1175 opts += ' other-config:disable-in-band=true'
1176 if self.datapath == 'user':
1177 opts += ' datapath_type=netdev'
1178 if self.protocols and not self.isOldOVS():
1179 opts += ' protocols=%s' % self.protocols
1180 if self.stp and self.failMode == 'standalone':
1181 opts += ' stp_enable=true'
1182 opts += ' other-config:dp-desc=%s' % self.name
1185 def start( self, controllers ):
1186 "Start up a new OVS OpenFlow switch using ovs-vsctl"
1187 if self.inNamespace:
1189 'OVS kernel switch does not work in a namespace' )
1190 int( self.dpid, 16 ) # DPID must be a hex string
1191 # Command to add interfaces
1192 intfs = ''.join( ' -- add-port %s %s' % ( self, intf ) +
1193 self.intfOpts( intf )
1194 for intf in self.intfList()
1195 if self.ports[ intf ] and not intf.IP() )
1196 # Command to create controller entries
1197 clist = [ ( self.name + c.name, '%s:%s:%d' %
1198 ( c.protocol, c.IP(), c.port ) )
1199 for c in controllers ]
1201 clist.append( ( self.name + '-listen',
1202 'ptcp:%s' % self.listenPort ) )
1203 ccmd = '-- --id=@%s create Controller target=\\"%s\\"'
1204 if self.reconnectms:
1205 ccmd += ' max_backoff=%d' % self.reconnectms
1206 cargs = ' '.join( ccmd % ( name, target )
1207 for name, target in clist )
1208 # Controller ID list
1209 cids = ','.join( '@%s' % name for name, _target in clist )
1210 # Try to delete any existing bridges with the same name
1211 if not self.isOldOVS():
1212 cargs += ' -- --if-exists del-br %s' % self
1213 # One ovs-vsctl command to rule them all!
1215 ' -- add-br %s' % self +
1216 ' -- set bridge %s controller=[%s]' % ( self, cids ) +
1219 # If necessary, restore TC config overwritten by OVS
1221 for intf in self.intfList():
1222 self.TCReapply( intf )
1224 # This should be ~ int( quietRun( 'getconf ARG_MAX' ) ),
1225 # but the real limit seems to be much lower
1229 def batchStartup( cls, switches, run=errRun ):
1230 """Batch startup for OVS
1231 switches: switches to start up
1232 run: function to run commands (errRun)"""
1235 for switch in switches:
1236 if switch.isOldOVS():
1237 # Ideally we'd optimize this also
1238 run( 'ovs-vsctl del-br %s' % switch )
1239 for cmd in switch.commands:
1241 # Don't exceed ARG_MAX
1242 if len( cmds ) + len( cmd ) >= cls.argmax:
1243 run( cmds, shell=True )
1247 switch.batch = False
1249 run( cmds, shell=True )
1250 # Reapply link config if necessary...
1251 for switch in switches:
1252 for intf in switch.intfs.values():
1253 if isinstance( intf, TCIntf ):
1254 intf.config( **intf.params )
1257 def stop( self, deleteIntfs=True ):
1258 """Terminate OVS switch.
1259 deleteIntfs: delete interfaces? (True)"""
1260 self.cmd( 'ovs-vsctl del-br', self )
1261 if self.datapath == 'user':
1262 self.cmd( 'ip link del', self )
1263 super( OVSSwitch, self ).stop( deleteIntfs )
1266 def batchShutdown( cls, switches, run=errRun ):
1267 "Shut down a list of OVS switches"
1268 delcmd = 'del-br %s'
1269 if switches and not switches[ 0 ].isOldOVS():
1270 delcmd = '--if-exists ' + delcmd
1271 # First, delete them all from ovsdb
1273 ' -- '.join( delcmd % s for s in switches ) )
1274 # Next, shut down all of the processes
1275 pids = ' '.join( str( switch.pid ) for switch in switches )
1276 run( 'kill -HUP ' + pids )
1277 for switch in switches:
1282 OVSKernelSwitch = OVSSwitch
1285 class OVSBridge( OVSSwitch ):
1286 "OVSBridge is an OVSSwitch in standalone/bridge mode"
1288 def __init__( self, *args, **kwargs ):
1289 """stp: enable Spanning Tree Protocol (False)
1290 see OVSSwitch for other options"""
1291 kwargs.update( failMode='standalone' )
1292 OVSSwitch.__init__( self, *args, **kwargs )
1294 def start( self, controllers ):
1295 "Start bridge, ignoring controllers argument"
1296 OVSSwitch.start( self, controllers=[] )
1298 def connected( self ):
1299 "Are we forwarding yet?"
1301 status = self.dpctl( 'show' )
1302 return 'STP_FORWARD' in status and not 'STP_LEARN' in status
1307 class IVSSwitch( Switch ):
1308 "Indigo Virtual Switch"
1310 def __init__( self, name, verbose=False, **kwargs ):
1311 Switch.__init__( self, name, **kwargs )
1312 self.verbose = verbose
1316 "Make sure IVS is installed"
1317 pathCheck( 'ivs-ctl', 'ivs',
1318 moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1319 out, err, exitcode = errRun( 'ivs-ctl show' )
1322 'ivs-ctl exited with code %d\n' % exitcode +
1323 '*** The openvswitch kernel module might '
1324 'not be loaded. Try modprobe openvswitch.\n' )
1328 def batchShutdown( cls, switches ):
1329 "Kill each IVS switch, to be waited on later in stop()"
1330 for switch in switches:
1331 switch.cmd( 'kill %ivs' )
1334 def start( self, controllers ):
1335 "Start up a new IVS switch"
1337 args.extend( ['--name', self.name] )
1338 args.extend( ['--dpid', self.dpid] )
1340 args.extend( ['--verbose'] )
1341 for intf in self.intfs.values():
1343 args.extend( ['-i', intf.name] )
1344 for c in controllers:
1345 args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1347 args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1348 args.append( self.opts )
1350 logfile = '/tmp/ivs.%s.log' % self.name
1352 self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1354 def stop( self, deleteIntfs=True ):
1355 """Terminate IVS switch.
1356 deleteIntfs: delete interfaces? (True)"""
1357 self.cmd( 'kill %ivs' )
1359 super( IVSSwitch, self ).stop( deleteIntfs )
1361 def attach( self, intf ):
1362 "Connect a data port"
1363 self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1365 def detach( self, intf ):
1366 "Disconnect a data port"
1367 self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1369 def dpctl( self, *args ):
1371 if not self.listenPort:
1372 return "can't run dpctl without passive listening port"
1373 return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1374 ' tcp:127.0.0.1:%i' % self.listenPort )
1377 class Controller( Node ):
1378 """A Controller is a Node that is running (or has execed?) an
1379 OpenFlow controller."""
1381 def __init__( self, name, inNamespace=False, command='controller',
1382 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1383 port=6653, protocol='tcp', **params ):
1384 self.command = command
1387 # Accept 'ip:port' syntax as shorthand
1389 ip, port = ip.split( ':' )
1393 self.protocol = protocol
1394 Node.__init__( self, name, inNamespace=inNamespace,
1396 self.checkListening()
1398 def checkListening( self ):
1399 "Make sure no controllers are running on our port"
1400 # Verify that Telnet is installed first:
1401 out, _err, returnCode = errRun( "which telnet" )
1402 if 'telnet' not in out or returnCode != 0:
1403 raise Exception( "Error running telnet to check for listening "
1404 "controllers; please check that it is "
1406 listening = self.cmd( "echo A | telnet -e A %s %d" %
1407 ( self.ip, self.port ) )
1408 if 'Connected' in listening:
1409 servers = self.cmd( 'netstat -natp' ).split( '\n' )
1410 pstr = ':%d ' % self.port
1411 clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1412 raise Exception( "Please shut down the controller which is"
1413 " running on port %d:\n" % self.port +
1414 '\n'.join( clist ) )
1417 """Start <controller> <args> on controller.
1418 Log to /tmp/cN.log"""
1419 pathCheck( self.command )
1420 cout = '/tmp/' + self.name + '.log'
1421 if self.cdir is not None:
1422 self.cmd( 'cd ' + self.cdir )
1423 self.cmd( self.command + ' ' + self.cargs % self.port +
1424 ' 1>' + cout + ' 2>' + cout + ' &' )
1427 def stop( self, *args, **kwargs ):
1429 self.cmd( 'kill %' + self.command )
1430 self.cmd( 'wait %' + self.command )
1431 super( Controller, self ).stop( *args, **kwargs )
1433 def IP( self, intf=None ):
1434 "Return IP address of the Controller"
1436 ip = Node.IP( self, intf )
1441 def __repr__( self ):
1442 "More informative string representation"
1443 return '<%s %s: %s:%s pid=%s> ' % (
1444 self.__class__.__name__, self.name,
1445 self.IP(), self.port, self.pid )
1448 def isAvailable( cls ):
1449 "Is controller available?"
1450 return which( 'controller' )
1453 class OVSController( Controller ):
1454 "Open vSwitch controller"
1455 def __init__( self, name, **kwargs ):
1456 kwargs.setdefault( 'command', self.isAvailable() or
1458 Controller.__init__( self, name, **kwargs )
1461 def isAvailable( cls ):
1462 return (which( 'ovs-controller' ) or
1463 which( 'test-controller' ) or
1464 which( 'ovs-testcontroller' ))
1466 class NOX( Controller ):
1467 "Controller to run a NOX application."
1469 def __init__( self, name, *noxArgs, **kwargs ):
1471 name: name to give controller
1472 noxArgs: arguments (strings) to pass to NOX"""
1474 warn( 'warning: no NOX modules specified; '
1475 'running packetdump only\n' )
1476 noxArgs = [ 'packetdump' ]
1477 elif type( noxArgs ) not in ( list, tuple ):
1478 noxArgs = [ noxArgs ]
1480 if 'NOX_CORE_DIR' not in os.environ:
1481 exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1482 noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1484 Controller.__init__( self, name,
1485 command=noxCoreDir + '/nox_core',
1486 cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1487 ' '.join( noxArgs ),
1491 class Ryu( Controller ):
1492 "Controller to run Ryu application"
1493 def __init__( self, name, *ryuArgs, **kwargs ):
1495 name: name to give controller.
1496 ryuArgs: arguments and modules to pass to Ryu"""
1497 homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1498 ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1500 warn( 'warning: no Ryu modules specified; '
1501 'running simple_switch only\n' )
1502 ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1503 elif type( ryuArgs ) not in ( list, tuple ):
1504 ryuArgs = [ ryuArgs ]
1506 Controller.__init__( self, name,
1507 command='ryu-manager',
1508 cargs='--ofp-tcp-listen-port %s ' +
1509 ' '.join( ryuArgs ),
1514 class RemoteController( Controller ):
1515 "Controller running outside of Mininet's control."
1517 def __init__( self, name, ip='127.0.0.1',
1518 port=None, **kwargs):
1520 name: name to give controller
1521 ip: the IP address where the remote controller is
1523 port: the port where the remote controller is listening"""
1524 Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1527 "Overridden to do nothing."
1531 "Overridden to do nothing."
1534 def checkListening( self ):
1535 "Warn if remote controller is not accessible"
1536 if self.port is not None:
1537 self.isListening( self.ip, self.port )
1539 for port in 6653, 6633:
1540 if self.isListening( self.ip, port ):
1542 info( "Connecting to remote controller"
1543 " at %s:%d\n" % ( self.ip, self.port ))
1546 if self.port is None:
1548 warn( "Setting remote controller"
1549 " to %s:%d\n" % ( self.ip, self.port ))
1551 def isListening( self, ip, port ):
1552 "Check if a remote controller is listening at a specific ip and port"
1553 listening = self.cmd( "echo A | telnet -e A %s %d" % ( ip, port ) )
1554 if 'Connected' not in listening:
1555 warn( "Unable to contact the remote controller"
1556 " at %s:%d\n" % ( ip, port ) )
1561 DefaultControllers = ( Controller, OVSController )
1563 def findController( controllers=DefaultControllers ):
1564 "Return first available controller from list, if any"
1565 for controller in controllers:
1566 if controller.isAvailable():
1569 def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1570 "Find a controller that is available and instantiate it"
1571 controller = findController( controllers )
1573 raise Exception( 'Could not find a default OpenFlow controller' )
1574 return controller( name, **kwargs )
1576 def NullController( *_args, **_kwargs ):
1577 "Nonexistent controller - simply returns None"