Update and rename MantenerFIFO to MantenerFIFO.md
[vsorcdistro/.git] / mininet / mininet / node.py
1 """
2 Node objects for Mininet.
3
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
6 machine.
7
8 Node: superclass for all (primarily local) network nodes.
9
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
16     directories.
17
18 CPULimitedHost: a virtual host whose CPU bandwidth is limited by
19     RT or CFS bandwidth limiting.
20
21 Switch: superclass for switch nodes.
22
23 UserSwitch: a switch using the user-space switch from the OpenFlow
24     reference implementation.
25
26 OVSSwitch: a switch using the Open vSwitch OpenFlow-compatible switch
27     implementation (openvswitch.org).
28
29 OVSBridge: an Ethernet bridge implemented using Open vSwitch.
30     Supports STP.
31
32 IVSSwitch: OpenFlow switch using the Indigo Virtual Switch.
33
34 Controller: superclass for OpenFlow controllers. The default controller
35     is controller(8) from the reference implementation.
36
37 OVSController: The test controller from Open vSwitch.
38
39 NOXController: a controller node using NOX (noxrepo.org).
40
41 Ryu: The Ryu controller (https://osrg.github.io/ryu/)
42
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.
46
47 Future enhancements:
48
49 - Possibly make Node, Switch and Controller more abstract so that
50   they can be used for both local and remote nodes
51
52 - Create proxy objects for remote nodes (Mininet: Cluster Edition)
53 """
54
55 import os
56 import pty
57 import re
58 import signal
59 import select
60 from subprocess import Popen, PIPE
61 from time import sleep
62
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
71
72 class Node( object ):
73     """A virtual network node is simply a shell in a network namespace.
74        We communicate with it using pipes."""
75
76     portBase = 0  # Nodes always start with eth0/port0, even in OF 1.0
77
78     def __init__( self, name, inNamespace=True, **params ):
79         """name: name of node
80            inNamespace: in network namespace?
81            privateDirs: list of private directory strings or tuples
82            params: Node parameters (see config() for details)"""
83
84         # Make sure class actually works
85         self.checkSetup()
86
87         self.name = params.get( 'name', name )
88         self.privateDirs = params.get( 'privateDirs', [] )
89         self.inNamespace = params.get( 'inNamespace', inNamespace )
90
91         # Python 3 complains if we don't wait for shell exit
92         self.waitExited = params.get( 'waitExited', Python3 )
93
94         # Stash configuration parameters for future reference
95         self.params = params
96
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
101
102         # Make pylint happy
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 )
106         self.waiting = False
107         self.readbuf = ''
108
109         # Start command interpreter shell
110         self.master, self.slave = None, None  # pylint
111         self.startShell()
112         self.mountPrivateDirs()
113
114     # File descriptor to node mapping support
115     # Class variables and methods
116
117     inToNode = {}  # mapping of input fds to nodes
118     outToNode = {}  # mapping of output fds to nodes
119
120     @classmethod
121     def fdToNode( cls, fd ):
122         """Return node corresponding to given file descriptor.
123            fd: file descriptor
124            returns: node"""
125         node = cls.outToNode.get( fd )
126         return node or cls.inToNode.get( fd )
127
128     # Command support via shell process in namespace
129     def startShell( self, mnopts=None ):
130         "Start a shell process for running commands"
131         if self.shell:
132             error( "%s: shell is already running\n" % self.name )
133             return
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
137         if self.inNamespace:
138             opts += 'n'
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 ]
145
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
164         self.execed = False
165         self.lastCmd = None
166         self.lastPid = None
167         self.readbuf = ''
168         # Wait for prompt
169         while True:
170             data = self.read( 1024 )
171             if data[ -1 ] == chr( 127 ):
172                 break
173             self.pollOut.poll()
174         self.waiting = False
175         # +m: disable job control notification
176         self.cmd( 'unset HISTFILE; stty -echo; set +m' )
177
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 ) )
191             else:
192                 # mount temporary filesystem on directory
193                 self.cmd( 'mkdir -p %s' % directory )
194                 self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
195
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 ] )
201             else:
202                 self.cmd( 'umount ', directory )
203
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
209         assert self
210         popen = Popen( cmd, **params )
211         debug( '_popen', cmd, popen.pid )
212         return popen
213
214     def cleanup( self ):
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 )
221         if self.shell:
222             # Close ptys
223             self.stdin.close()
224             os.close(self.slave)
225             if self.waitExited:
226                 debug( 'waiting for', self.pid, 'to terminate\n' )
227                 self.shell.wait()
228         self.shell = None
229
230     # Subshell I/O, commands and control
231
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 )
236         if count < maxbytes:
237             data = decode( os.read( self.stdout.fileno(), maxbytes - count ) )
238             self.readbuf += data
239         if maxbytes >= len( self.readbuf ):
240             result = self.readbuf
241             self.readbuf = ''
242         else:
243             result = self.readbuf[ :maxbytes ]
244             self.readbuf = self.readbuf[ maxbytes: ]
245         return result
246
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:
252             return None
253         pos = self.readbuf.find( '\n' )
254         line = self.readbuf[ 0: pos ]
255         self.readbuf = self.readbuf[ pos + 1: ]
256         return line
257
258     def write( self, data ):
259         """Write data to node.
260            data: string"""
261         os.write( self.stdin.fileno(), encode( data ) )
262
263     def terminate( self ):
264         "Send kill signal to Node and clean up after it."
265         self.unmountPrivateDirs()
266         if self.shell:
267             if self.shell.poll() is None:
268                 os.killpg( self.shell.pid, signal.SIGHUP )
269         self.cleanup()
270
271     def stop( self, deleteIntfs=False ):
272         """Stop node.
273            deleteIntfs: delete interfaces? (False)"""
274         if deleteIntfs:
275             self.deleteIntfs()
276         self.terminate()
277
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 )
284
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 ):
294             cmd = args[ 0 ]
295         # Allow sendCmd( cmd, arg1, arg2... )
296         elif len( args ) > 0:
297             cmd = args
298         # Convert to string
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
303             cmd = 'echo -n'
304         self.lastCmd = cmd
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' )
312         self.lastPid = None
313         self.waiting = True
314
315     def sendInt( self, intr=chr( 3 ) ):
316         "Interrupt running command."
317         debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
318         self.write( intr )
319
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 )
326         if not ready:
327             return ''
328         data = self.read( 1024 )
329         pidre = r'\[\d+\] \d+\r\n'
330         # Look for PID
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 )
340             if markers:
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 ):
345             self.waiting = False
346             data = data[ :-1 ]
347         elif chr( 127 ) in data:
348             self.waiting = False
349             data = data.replace( chr( 127 ), '' )
350         return data
351
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
359         output = ''
360         while self.waiting:
361             data = self.monitor( findPid=findPid )
362             output += data
363             log( data )
364         return output
365
366     def cmd( self, *args, **kwargs ):
367         """Send a command, wait for output, and return it.
368            cmd: string"""
369         verbose = kwargs.get( 'verbose', False )
370         log = info if verbose else debug
371         log( '*** %s : %s\n' % ( self.name, args ) )
372         if self.shell:
373             self.sendCmd( *args, **kwargs )
374             return self.waitOutput( verbose )
375         else:
376             warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) )
377
378     def cmdPrint( self, *args):
379         """Call cmd and printing its output
380            cmd: string"""
381         return self.cmd( *args, **{ 'verbose': True } )
382
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,
388                      'mncmd':
389                      [ 'mnexec', '-da', str( self.pid ) ] }
390         defaults.update( kwargs )
391         shell = defaults.pop( 'shell', False )
392         if len( args ) == 1:
393             if isinstance( args[ 0 ], list ):
394                 # popen([cmd, arg1, arg2...])
395                 cmd = args[ 0 ]
396             elif isinstance( args[ 0 ], BaseString ):
397                 # popen("cmd arg1 arg2...")
398                 cmd = [ args[ 0 ] ] if shell else args[ 0 ].split()
399             else:
400                 raise Exception( 'popen() requires a string or list' )
401         elif len( args ) > 0:
402             # popen( cmd, arg1, arg2... )
403             cmd = list( args )
404         if shell:
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 )
409         return popen
410
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,
415                             **kwargs )
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
420
421     # Interface management, configuration, and routing
422
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.
428
429     def newPort( self ):
430         "Return the next port number to allocate."
431         if len( self.ports ) > 0:
432             return max( self.ports.values() ) + 1
433         return self.portBase
434
435     def addIntf( self, intf, port=None, moveIntfFn=moveIntf ):
436         """Add an interface.
437            intf: interface
438            port: port number (optional, typically OpenFlow port number)
439            moveIntfFn: function to move interface (optional)"""
440         if port is None:
441             port = self.newPort()
442         self.intfs[ port ] = intf
443         self.ports[ intf ] = port
444         self.nameToIntf[ intf.name ] = intf
445         debug( '\n' )
446         debug( 'added intf %s (%d) to node %s\n' % (
447                 intf, port, self.name ) )
448         if self.inNamespace:
449             debug( 'moving', intf, 'into namespace for', self.name, '\n' )
450             moveIntfFn( intf.name, self  )
451
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 )
456         if port is not None:
457             del self.intfs[ port ]
458             del self.ports[ intf ]
459             del self.nameToIntf[ intf.name ]
460
461     def defaultIntf( self ):
462         "Return interface for lowest port"
463         ports = self.intfs.keys()
464         if ports:
465             return self.intfs[ min( ports ) ]
466         else:
467             warn( '*** defaultIntf: warning:', self.name,
468                   'has no interfaces\n' )
469
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.
474
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).
478         """
479         if not intf:
480             return self.defaultIntf()
481         elif isinstance( intf, BaseString):
482             return self.nameToIntf[ intf ]
483         else:
484             return intf
485
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
489         connections = []
490         for intf in self.intfList():
491             link = intf.link
492             if link:
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 ) ]
498         return connections
499
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 ):
511                 intf.delete()
512                 info( '.' )
513
514     # Routing support
515
516     def setARP( self, ip, mac ):
517         """Add an ARP entry.
518            ip: IP address as string
519            mac: MAC address as string"""
520         result = self.cmd( 'arp', '-s', ip, mac )
521         return result
522
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 )
528
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:
534             params = intf
535         else:
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 )
539
540     # Convenience and configuration methods
541
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 )
547
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 )
555
556     def IP( self, intf=None ):
557         "Return IP address of a node or specific interface."
558         return self.intf( intf ).IP()
559
560     def MAC( self, intf=None ):
561         "Return MAC address of a node or specific interface."
562         return self.intf( intf ).MAC()
563
564     def intfIsUp( self, intf=None ):
565         "Check if an interface is up."
566         return self.intf( intf ).isUp()
567
568     # The reason why we configure things in this way is so
569     # That the parameters can be listed and documented in
570     # the config method.
571     # Dealing with subclasses and superclasses is slightly
572     # annoying, but at least the information is there!
573
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 ]
581         if value is None:
582             return
583         f = getattr( self, method, None )
584         if not f:
585             return
586         if isinstance( value, list ):
587             result = f( *value )
588         elif isinstance( value, dict ):
589             result = f( **value )
590         else:
591             result = f( value )
592         results[ name ] = result
593         return result
594
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 )
606         r = {}
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 )
612         return r
613
614     def configDefault( self, **moreParams ):
615         "Configure with default parameters"
616         self.params.update( moreParams )
617         self.config( **self.params )
618
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 )
624
625     # Other methods
626
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() ) ]
630
631     def intfNames( self ):
632         "The names of our interfaces sorted by port number"
633         return [ str( i ) for i in self.intfList() ]
634
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 )
641
642     def __str__( self ):
643         "Abbreviated string representation"
644         return self.name
645
646     # Automatic class setup support
647
648     isSetup = False
649
650     @classmethod
651     def checkSetup( cls ):
652         "Make sure our class and superclasses are set up"
653         while cls and not getattr( cls, 'isSetup', True ):
654             cls.setup()
655             cls.isSetup = True
656             # Make pylint happy
657             cls = getattr( type( cls ), '__base__', None )
658
659     @classmethod
660     def setup( cls ):
661         "Make sure our class dependencies are available"
662         pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
663
664 class Host( Node ):
665     "A host is simply a Node"
666     pass
667
668 class CPULimitedHost( Host ):
669
670     "CPU limited host"
671
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 )
688         self.sched = sched
689         if sched == 'rt':
690             self.checkRtGroupSched()
691             self.rtprio = 20
692
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 )
697         quietRun( cmd )
698         nvalue = int( self.cgroupGet( param, resource ) )
699         if nvalue != value:
700             error( '*** error: cgroupSet: %s set to %s instead of %s\n'
701                    % ( param, nvalue, value ) )
702         return nvalue
703
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 ] )
709
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() )
717
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 ) ]
730             else:
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 )
734
735     def cleanup( self ):
736         "Clean up Node, then clean up our cgroup"
737         super( CPULimitedHost, self ).cleanup()
738         retry( retries=3, delaySecs=.1, fn=self.cgroupDel )
739
740     _rtGroupSched = False   # internal class var: Is CONFIG_RT_GROUP_SCHED set?
741
742     @classmethod
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' %
748                                release )
749             if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
750                 error( '\n*** error: please enable RT_GROUP_SCHED '
751                        'in your kernel\n' )
752                 exit( 1 )
753             cls._rtGroupSched = True
754
755     def chrt( self ):
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 )
763         return lastword
764
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
771
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) ' )
780             quota = 1000
781             period = int( quota / f / numCores() )
782         # Reset to unlimited on negative quota
783         if quota < 0:
784             quota = -1
785         return pstr, qstr, period, quota
786
787     # BL comment:
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.
795
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)
799            sched: 'rt' or 'cfs'
800            Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
801            and 'rt' requires CONFIG_RT_GROUP_SCHED"""
802         if not sched:
803             sched = self.sched
804         if sched == 'rt':
805             if not f or f < 0:
806                 raise Exception( 'Please set a positive CPU fraction'
807                                  ' for sched=rt\n' )
808             pstr, qstr, period, quota = self.rtInfo( f )
809         elif sched == 'cfs':
810             pstr, qstr, period, quota = self.cfsInfo( f )
811         else:
812             return
813         # Set cgroup's period and quota
814         setPeriod = self.cgroupSet( pstr, period )
815         setQuota = self.cgroupSet( qstr, quota )
816         if sched == 'rt':
817             # Set RT priority if necessary
818             sched = self.chrt()
819         info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
820
821     def setCPUs( self, cores, mems=0 ):
822         "Specify (real) cores that our cgroup can run on"
823         if not cores:
824             return
825         if isinstance( cores, list ):
826             cores = ','.join( [ str( c ) for c in cores ] )
827         self.cgroupSet( resource='cpuset', param='cpus',
828                         value=cores )
829         # Memory placement is probably not relevant, but we
830         # must specify it anyway
831         self.cgroupSet( resource='cpuset', param='mems',
832                         value=mems)
833         # We have to do this here after we've specified
834         # cpus and mems
835         errFail( 'cgclassify -g cpuset:/%s %s' % (
836                  self.name, self.pid ) )
837
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 )
847         return r
848
849     inited = False
850
851     @classmethod
852     def init( cls ):
853         "Initialization for CPULimitedHost class"
854         mountCgroups()
855         cls.inited = True
856
857
858 # Some important things to note:
859 #
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!
868 #
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.
877
878 class Switch( Node ):
879     """A Switch is a Node that is running (or has execed?)
880        an OpenFlow switch."""
881
882     portBase = 1  # Switches start with port 1 in OpenFlow
883     dpidLen = 16  # digits in dpid passed to switch
884
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 )
891         self.opts = opts
892         self.listenPort = listenPort
893         if not self.inNamespace:
894             self.controlIntf = Intf( 'lo', self, port=0 )
895
896     def defaultDpid( self, dpid=None ):
897         "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
898         if dpid:
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
902         else:
903             # Use hex of the first number in the switch name
904             nums = re.findall( r'\d+', self.name )
905             if nums:
906                 dpid = hex( int( nums[ 0 ] ) )[ 2: ]
907             else:
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
913
914     def defaultIntf( self ):
915         "Return control interface"
916         if self.controlIntf:
917             return self.controlIntf
918         else:
919             return Node.defaultIntf( self )
920
921     def sendCmd( self, *cmd, **kwargs ):
922         """Send command to Node.
923            cmd: string"""
924         kwargs.setdefault( 'printPid', False )
925         if not self.execed:
926             return Node.sendCmd( self, *cmd, **kwargs )
927         else:
928             error( '*** Error: %s has execed and cannot accept commands' %
929                    self.name )
930
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' )
937         return True
938
939     def stop( self, deleteIntfs=True ):
940         """Stop switch
941            deleteIntfs: delete interfaces? (True)"""
942         if deleteIntfs:
943             self.deleteIntfs()
944
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 )
951
952
953 class UserSwitch( Switch ):
954     "User-space switch."
955
956     dpidLen = 12
957
958     def __init__( self, name, dpopts='--no-slicing', **kwargs ):
959         """Init.
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' +
965                               '(openflow.org)' )
966         if self.listenPort:
967             self.opts += ' --listen=ptcp:%i ' % self.listenPort
968         else:
969             self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
970         self.dpopts = dpopts
971
972     @classmethod
973     def setup( cls ):
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 )
977
978     def dpctl( self, *args ):
979         "Run dpctl command"
980         listenAddr = None
981         if not self.listenPort:
982             listenAddr = 'unix:/tmp/%s.listen' % self.name
983         else:
984             listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
985         return self.cmd( 'dpctl ' + ' '.join( args ) +
986                          ' ' + listenAddr )
987
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 )
993
994     @staticmethod
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
1003
1004             res = intf.config( **intf.params )
1005
1006             if res is None:  # link may not have TC parameters
1007                 return
1008
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 "
1015                      + str(ifspeed) )
1016             intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
1017                      "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
1018
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"""
1023         # Add controllers
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 +
1031                   self.dpopts +
1032                   ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
1033         self.cmd( 'ofprotocol unix:/tmp/' + self.name +
1034                   ' ' + clist +
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():
1041                 if not intf.IP():
1042                     self.TCReapply( intf )
1043
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 )
1050
1051
1052 class OVSSwitch( Switch ):
1053     "Open vSwitch switch. Depends on ovs-vsctl."
1054
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
1073         self.stp = stp
1074         self._uuids = []  # controller UUIDs
1075         self.batch = batch
1076         self.commands = []  # saved commands for batch startup
1077
1078     @classmethod
1079     def setup( cls ):
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' )
1087         if exitcode:
1088             error( out + err +
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' )
1096             exit( 1 )
1097         version = quietRun( 'ovs-vsctl --version' )
1098         cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
1099
1100     @classmethod
1101     def isOldOVS( cls ):
1102         "Is OVS ersion < 1.10?"
1103         return ( StrictVersion( cls.OVSVersion ) <
1104                  StrictVersion( '1.10' ) )
1105
1106     def dpctl( self, *args ):
1107         "Run ovs-ofctl command"
1108         return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1109
1110     def vsctl( self, *args, **kwargs ):
1111         "Run ovs-vsctl command (or queue for later execution)"
1112         if self.batch:
1113             cmd = ' '.join( str( arg ).strip() for arg in args )
1114             self.commands.append( cmd )
1115         else:
1116             return self.cmd( 'ovs-vsctl', *args, **kwargs )
1117
1118     @staticmethod
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 )
1125
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 )
1131
1132     def detach( self, intf ):
1133         "Disconnect a data port"
1134         self.vsctl( 'del-port', self, intf )
1135
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 ]
1144                 if controllers:
1145                     self._uuids = [ c.strip()
1146                                     for c in controllers.split( ',' ) ]
1147         return self._uuids
1148
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' ):
1154                 return True
1155         return self.failMode == 'standalone'
1156
1157     def intfOpts( self, intf ):
1158         "Return OVS interface options for intf"
1159         opts = ''
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
1169
1170     def bridgeOpts( self ):
1171         "Return OVS bridge options"
1172         opts = ( ' other_config:datapath-id=%s' % self.dpid +
1173                  ' fail_mode=%s' % self.failMode )
1174         if not self.inband:
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
1183         return opts
1184
1185     def start( self, controllers ):
1186         "Start up a new OVS OpenFlow switch using ovs-vsctl"
1187         if self.inNamespace:
1188             raise Exception(
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 ]
1200         if self.listenPort:
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!
1214         self.vsctl( cargs +
1215                     ' -- add-br %s' % self +
1216                     ' -- set bridge %s controller=[%s]' % ( self, cids  ) +
1217                     self.bridgeOpts() +
1218                     intfs )
1219         # If necessary, restore TC config overwritten by OVS
1220         if not self.batch:
1221             for intf in self.intfList():
1222                 self.TCReapply( intf )
1223
1224     # This should be ~ int( quietRun( 'getconf ARG_MAX' ) ),
1225     # but the real limit seems to be much lower
1226     argmax = 128000
1227
1228     @classmethod
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)"""
1233         info( '...' )
1234         cmds = 'ovs-vsctl'
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:
1240                 cmd = cmd.strip()
1241                 # Don't exceed ARG_MAX
1242                 if len( cmds ) + len( cmd ) >= cls.argmax:
1243                     run( cmds, shell=True )
1244                     cmds = 'ovs-vsctl'
1245                 cmds += ' ' + cmd
1246                 switch.cmds = []
1247                 switch.batch = False
1248         if cmds:
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 )
1255         return switches
1256
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 )
1264
1265     @classmethod
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
1272         run( 'ovs-vsctl ' +
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:
1278             switch.terminate()
1279         return switches
1280
1281
1282 OVSKernelSwitch = OVSSwitch
1283
1284
1285 class OVSBridge( OVSSwitch ):
1286     "OVSBridge is an OVSSwitch in standalone/bridge mode"
1287
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 )
1293
1294     def start( self, controllers ):
1295         "Start bridge, ignoring controllers argument"
1296         OVSSwitch.start( self, controllers=[] )
1297
1298     def connected( self ):
1299         "Are we forwarding yet?"
1300         if self.stp:
1301             status = self.dpctl( 'show' )
1302             return 'STP_FORWARD' in status and not 'STP_LEARN' in status
1303         else:
1304             return True
1305
1306
1307 class IVSSwitch( Switch ):
1308     "Indigo Virtual Switch"
1309
1310     def __init__( self, name, verbose=False, **kwargs ):
1311         Switch.__init__( self, name, **kwargs )
1312         self.verbose = verbose
1313
1314     @classmethod
1315     def setup( cls ):
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' )
1320         if exitcode:
1321             error( out + err +
1322                    'ivs-ctl exited with code %d\n' % exitcode +
1323                    '*** The openvswitch kernel module might '
1324                    'not be loaded. Try modprobe openvswitch.\n' )
1325             exit( 1 )
1326
1327     @classmethod
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' )
1332         return switches
1333
1334     def start( self, controllers ):
1335         "Start up a new IVS switch"
1336         args = ['ivs']
1337         args.extend( ['--name', self.name] )
1338         args.extend( ['--dpid', self.dpid] )
1339         if self.verbose:
1340             args.extend( ['--verbose'] )
1341         for intf in self.intfs.values():
1342             if not intf.IP():
1343                 args.extend( ['-i', intf.name] )
1344         for c in controllers:
1345             args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1346         if self.listenPort:
1347             args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1348         args.append( self.opts )
1349
1350         logfile = '/tmp/ivs.%s.log' % self.name
1351
1352         self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1353
1354     def stop( self, deleteIntfs=True ):
1355         """Terminate IVS switch.
1356            deleteIntfs: delete interfaces? (True)"""
1357         self.cmd( 'kill %ivs' )
1358         self.cmd( 'wait' )
1359         super( IVSSwitch, self ).stop( deleteIntfs )
1360
1361     def attach( self, intf ):
1362         "Connect a data port"
1363         self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1364
1365     def detach( self, intf ):
1366         "Disconnect a data port"
1367         self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1368
1369     def dpctl( self, *args ):
1370         "Run dpctl command"
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 )
1375
1376
1377 class Controller( Node ):
1378     """A Controller is a Node that is running (or has execed?) an
1379        OpenFlow controller."""
1380
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
1385         self.cargs = cargs
1386         self.cdir = cdir
1387         # Accept 'ip:port' syntax as shorthand
1388         if ':' in ip:
1389             ip, port = ip.split( ':' )
1390             port = int( port )
1391         self.ip = ip
1392         self.port = port
1393         self.protocol = protocol
1394         Node.__init__( self, name, inNamespace=inNamespace,
1395                        ip=ip, **params  )
1396         self.checkListening()
1397
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 "
1405                              "installed." )
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 ) )
1415
1416     def start( self ):
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 + ' &' )
1425         self.execed = False
1426
1427     def stop( self, *args, **kwargs ):
1428         "Stop controller."
1429         self.cmd( 'kill %' + self.command )
1430         self.cmd( 'wait %' + self.command )
1431         super( Controller, self ).stop( *args, **kwargs )
1432
1433     def IP( self, intf=None ):
1434         "Return IP address of the Controller"
1435         if self.intfs:
1436             ip = Node.IP( self, intf )
1437         else:
1438             ip = self.ip
1439         return ip
1440
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 )
1446
1447     @classmethod
1448     def isAvailable( cls ):
1449         "Is controller available?"
1450         return which( 'controller' )
1451
1452
1453 class OVSController( Controller ):
1454     "Open vSwitch controller"
1455     def __init__( self, name, **kwargs ):
1456         kwargs.setdefault( 'command', self.isAvailable() or
1457                            'ovs-controller' )
1458         Controller.__init__( self, name, **kwargs )
1459
1460     @classmethod
1461     def isAvailable( cls ):
1462         return (which( 'ovs-controller' ) or
1463                 which( 'test-controller' ) or
1464                 which( 'ovs-testcontroller' ))
1465
1466 class NOX( Controller ):
1467     "Controller to run a NOX application."
1468
1469     def __init__( self, name, *noxArgs, **kwargs ):
1470         """Init.
1471            name: name to give controller
1472            noxArgs: arguments (strings) to pass to NOX"""
1473         if not noxArgs:
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 ]
1479
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' ]
1483
1484         Controller.__init__( self, name,
1485                              command=noxCoreDir + '/nox_core',
1486                              cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1487                              ' '.join( noxArgs ),
1488                              cdir=noxCoreDir,
1489                              **kwargs )
1490
1491 class Ryu( Controller ):
1492     "Controller to run Ryu application"
1493     def __init__( self, name, *ryuArgs, **kwargs ):
1494         """Init.
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
1499         if not ryuArgs:
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 ]
1505
1506         Controller.__init__( self, name,
1507                              command='ryu-manager',
1508                              cargs='--ofp-tcp-listen-port %s ' +
1509                              ' '.join( ryuArgs ),
1510                              cdir=ryuCoreDir,
1511                              **kwargs )
1512
1513
1514 class RemoteController( Controller ):
1515     "Controller running outside of Mininet's control."
1516
1517     def __init__( self, name, ip='127.0.0.1',
1518                   port=None, **kwargs):
1519         """Init.
1520            name: name to give controller
1521            ip: the IP address where the remote controller is
1522            listening
1523            port: the port where the remote controller is listening"""
1524         Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1525
1526     def start( self ):
1527         "Overridden to do nothing."
1528         return
1529
1530     def stop( self ):
1531         "Overridden to do nothing."
1532         return
1533
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 )
1538         else:
1539             for port in 6653, 6633:
1540                 if self.isListening( self.ip, port ):
1541                     self.port = port
1542                     info( "Connecting to remote controller"
1543                           " at %s:%d\n" % ( self.ip, self.port ))
1544                     break
1545
1546         if self.port is None:
1547             self.port = 6653
1548             warn( "Setting remote controller"
1549                   " to %s:%d\n" % ( self.ip, self.port ))
1550
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 ) )
1557             return False
1558         else:
1559             return True
1560
1561 DefaultControllers = ( Controller, OVSController )
1562
1563 def findController( controllers=DefaultControllers ):
1564     "Return first available controller from list, if any"
1565     for controller in controllers:
1566         if controller.isAvailable():
1567             return controller
1568
1569 def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1570     "Find a controller that is available and instantiate it"
1571     controller = findController( controllers )
1572     if not controller:
1573         raise Exception( 'Could not find a default OpenFlow controller' )
1574     return controller( name, **kwargs )
1575
1576 def NullController( *_args, **_kwargs ):
1577     "Nonexistent controller - simply returns None"
1578     return None