Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ d254d749

History | View | Annotate | Download (58.5 KB)

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 )
66
from mininet.moduledeps import moduleDeps, pathCheck, TUN
67
from mininet.link import Link, Intf, TCIntf, OVSIntf
68
from re import findall
69
from distutils.version import StrictVersion
70

    
71
class Node( object ):
72
    """A virtual network node is simply a shell in a network namespace.
73
       We communicate with it using pipes."""
74

    
75
    portBase = 0  # Nodes always start with eth0/port0, even in OF 1.0
76

    
77
    def __init__( self, name, inNamespace=True, **params ):
78
        """name: name of node
79
           inNamespace: in network namespace?
80
           privateDirs: list of private directory strings or tuples
81
           params: Node parameters (see config() for details)"""
82

    
83
        # Make sure class actually works
84
        self.checkSetup()
85

    
86
        self.name = params.get( 'name', name )
87
        self.privateDirs = params.get( 'privateDirs', [] )
88
        self.inNamespace = params.get( 'inNamespace', inNamespace )
89

    
90
        # Stash configuration parameters for future reference
91
        self.params = params
92

    
93
        self.intfs = {}  # dict of port numbers to interfaces
94
        self.ports = {}  # dict of interfaces to port numbers
95
                         # replace with Port objects, eventually ?
96
        self.nameToIntf = {}  # dict of interface names to Intfs
97

    
98
        # Make pylint happy
99
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
100
            self.lastPid, self.lastCmd, self.pollOut ) = (
101
                None, None, None, None, None, None, None, None )
102
        self.waiting = False
103
        self.readbuf = ''
104

    
105
        # Start command interpreter shell
106
        self.startShell()
107
        self.mountPrivateDirs()
108

    
109
    # File descriptor to node mapping support
110
    # Class variables and methods
111

    
112
    inToNode = {}  # mapping of input fds to nodes
113
    outToNode = {}  # mapping of output fds to nodes
114

    
115
    @classmethod
116
    def fdToNode( cls, fd ):
117
        """Return node corresponding to given file descriptor.
118
           fd: file descriptor
119
           returns: node"""
120
        node = cls.outToNode.get( fd )
121
        return node or cls.inToNode.get( fd )
122

    
123
    # Command support via shell process in namespace
124
    def startShell( self, mnopts=None ):
125
        "Start a shell process for running commands"
126
        if self.shell:
127
            error( "%s: shell is already running\n" % self.name )
128
            return
129
        # mnexec: (c)lose descriptors, (d)etach from tty,
130
        # (p)rint pid, and run in (n)amespace
131
        opts = '-cd' if mnopts is None else mnopts
132
        if self.inNamespace:
133
            opts += 'n'
134
        # bash -i: force interactive
135
        # -s: pass $* to shell, and make process easy to find in ps
136
        # prompt is set to sentinel chr( 127 )
137
        cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ),
138
                'bash', '--norc', '-is', 'mininet:' + self.name ]
139
        # Spawn a shell subprocess in a pseudo-tty, to disable buffering
140
        # in the subprocess and insulate it from signals (e.g. SIGINT)
141
        # received by the parent
142
        master, slave = pty.openpty()
143
        self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave,
144
                                  close_fds=False )
145
        self.stdin = os.fdopen( master, 'rw' )
146
        self.stdout = self.stdin
147
        self.pid = self.shell.pid
148
        self.pollOut = select.poll()
149
        self.pollOut.register( self.stdout )
150
        # Maintain mapping between file descriptors and nodes
151
        # This is useful for monitoring multiple nodes
152
        # using select.poll()
153
        self.outToNode[ self.stdout.fileno() ] = self
154
        self.inToNode[ self.stdin.fileno() ] = self
155
        self.execed = False
156
        self.lastCmd = None
157
        self.lastPid = None
158
        self.readbuf = ''
159
        # Wait for prompt
160
        while True:
161
            data = self.read( 1024 )
162
            if data[ -1 ] == chr( 127 ):
163
                break
164
            self.pollOut.poll()
165
        self.waiting = False
166
        # +m: disable job control notification
167
        self.cmd( 'unset HISTFILE; stty -echo; set +m' )
168

    
169
    def mountPrivateDirs( self ):
170
        "mount private directories"
171
        # Avoid expanding a string into a list of chars
172
        assert not isinstance( self.privateDirs, basestring )
173
        for directory in self.privateDirs:
174
            if isinstance( directory, tuple ):
175
                # mount given private directory
176
                privateDir = directory[ 1 ] % self.__dict__
177
                mountPoint = directory[ 0 ]
178
                self.cmd( 'mkdir -p %s' % privateDir )
179
                self.cmd( 'mkdir -p %s' % mountPoint )
180
                self.cmd( 'mount --bind %s %s' %
181
                               ( privateDir, mountPoint ) )
182
            else:
183
                # mount temporary filesystem on directory
184
                self.cmd( 'mkdir -p %s' % directory )
185
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
186

    
187
    def unmountPrivateDirs( self ):
188
        "mount private directories"
189
        for directory in self.privateDirs:
190
            if isinstance( directory, tuple ):
191
                self.cmd( 'umount ', directory[ 0 ] )
192
            else:
193
                self.cmd( 'umount ', directory )
194

    
195
    def _popen( self, cmd, **params ):
196
        """Internal method: spawn and return a process
197
            cmd: command to run (list)
198
            params: parameters to Popen()"""
199
        # Leave this is as an instance method for now
200
        assert self
201
        return Popen( cmd, **params )
202

    
203
    def cleanup( self ):
204
        "Help python collect its garbage."
205
        # We used to do this, but it slows us down:
206
        # Intfs may end up in root NS
207
        # for intfName in self.intfNames():
208
        # if self.name in intfName:
209
        # quietRun( 'ip link del ' + intfName )
210
        self.shell = None
211

    
212
    # Subshell I/O, commands and control
213

    
214
    def read( self, maxbytes=1024 ):
215
        """Buffered read from node, non-blocking.
216
           maxbytes: maximum number of bytes to return"""
217
        count = len( self.readbuf )
218
        if count < maxbytes:
219
            data = os.read( self.stdout.fileno(), maxbytes - count )
220
            self.readbuf += data
221
        if maxbytes >= len( self.readbuf ):
222
            result = self.readbuf
223
            self.readbuf = ''
224
        else:
225
            result = self.readbuf[ :maxbytes ]
226
            self.readbuf = self.readbuf[ maxbytes: ]
227
        return result
228

    
229
    def readline( self ):
230
        """Buffered readline from node, non-blocking.
231
           returns: line (minus newline) or None"""
232
        self.readbuf += self.read( 1024 )
233
        if '\n' not in self.readbuf:
234
            return None
235
        pos = self.readbuf.find( '\n' )
236
        line = self.readbuf[ 0: pos ]
237
        self.readbuf = self.readbuf[ pos + 1: ]
238
        return line
239

    
240
    def write( self, data ):
241
        """Write data to node.
242
           data: string"""
243
        os.write( self.stdin.fileno(), data )
244

    
245
    def terminate( self ):
246
        "Send kill signal to Node and clean up after it."
247
        self.unmountPrivateDirs()
248
        if self.shell:
249
            if self.shell.poll() is None:
250
                os.killpg( self.shell.pid, signal.SIGHUP )
251
        self.cleanup()
252

    
253
    def stop( self, deleteIntfs=False ):
254
        """Stop node.
255
           deleteIntfs: delete interfaces? (False)"""
256
        if deleteIntfs:
257
            self.deleteIntfs()
258
        self.terminate()
259

    
260
    def waitReadable( self, timeoutms=None ):
261
        """Wait until node's output is readable.
262
           timeoutms: timeout in ms or None to wait indefinitely."""
263
        if len( self.readbuf ) == 0:
264
            self.pollOut.poll( timeoutms )
265

    
266
    def sendCmd( self, *args, **kwargs ):
267
        """Send a command, followed by a command to echo a sentinel,
268
           and return without waiting for the command to complete.
269
           args: command and arguments, or string
270
           printPid: print command's PID? (False)"""
271
        assert self.shell and not self.waiting
272
        printPid = kwargs.get( 'printPid', False )
273
        # Allow sendCmd( [ list ] )
274
        if len( args ) == 1 and isinstance( args[ 0 ], list ):
275
            cmd = args[ 0 ]
276
        # Allow sendCmd( cmd, arg1, arg2... )
277
        elif len( args ) > 0:
278
            cmd = args
279
        # Convert to string
280
        if not isinstance( cmd, str ):
281
            cmd = ' '.join( [ str( c ) for c in cmd ] )
282
        if not re.search( r'\w', cmd ):
283
            # Replace empty commands with something harmless
284
            cmd = 'echo -n'
285
        self.lastCmd = cmd
286
        # if a builtin command is backgrounded, it still yields a PID
287
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
288
            # print ^A{pid}\n so monitor() can set lastPid
289
            cmd += ' printf "\\001%d\\012" $! '
290
        elif printPid and not isShellBuiltin( cmd ):
291
            cmd = 'mnexec -p ' + cmd
292
        self.write( cmd + '\n' )
293
        self.lastPid = None
294
        self.waiting = True
295

    
296
    def sendInt( self, intr=chr( 3 ) ):
297
        "Interrupt running command."
298
        debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
299
        self.write( intr )
300

    
301
    def monitor( self, timeoutms=None, findPid=True ):
302
        """Monitor and return the output of a command.
303
           Set self.waiting to False if command has completed.
304
           timeoutms: timeout in ms or None to wait indefinitely
305
           findPid: look for PID from mnexec -p"""
306
        self.waitReadable( timeoutms )
307
        data = self.read( 1024 )
308
        pidre = r'\[\d+\] \d+\r\n'
309
        # Look for PID
310
        marker = chr( 1 ) + r'\d+\r\n'
311
        if findPid and chr( 1 ) in data:
312
            # suppress the job and PID of a backgrounded command
313
            if re.findall( pidre, data ):
314
                data = re.sub( pidre, '', data )
315
            # Marker can be read in chunks; continue until all of it is read
316
            while not re.findall( marker, data ):
317
                data += self.read( 1024 )
318
            markers = re.findall( marker, data )
319
            if markers:
320
                self.lastPid = int( markers[ 0 ][ 1: ] )
321
                data = re.sub( marker, '', data )
322
        # Look for sentinel/EOF
323
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
324
            self.waiting = False
325
            data = data[ :-1 ]
326
        elif chr( 127 ) in data:
327
            self.waiting = False
328
            data = data.replace( chr( 127 ), '' )
329
        return data
330

    
331
    def waitOutput( self, verbose=False, findPid=True ):
332
        """Wait for a command to complete.
333
           Completion is signaled by a sentinel character, ASCII(127)
334
           appearing in the output stream.  Wait for the sentinel and return
335
           the output, including trailing newline.
336
           verbose: print output interactively"""
337
        log = info if verbose else debug
338
        output = ''
339
        while self.waiting:
340
            data = self.monitor( findPid=findPid )
341
            output += data
342
            log( data )
343
        return output
344

    
345
    def cmd( self, *args, **kwargs ):
346
        """Send a command, wait for output, and return it.
347
           cmd: string"""
348
        verbose = kwargs.get( 'verbose', False )
349
        log = info if verbose else debug
350
        log( '*** %s : %s\n' % ( self.name, args ) )
351
        if self.shell:
352
            self.sendCmd( *args, **kwargs )
353
            return self.waitOutput( verbose )
354
        else:
355
            warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) )
356

    
357
    def cmdPrint( self, *args):
358
        """Call cmd and printing its output
359
           cmd: string"""
360
        return self.cmd( *args, **{ 'verbose': True } )
361

    
362
    def popen( self, *args, **kwargs ):
363
        """Return a Popen() object in our namespace
364
           args: Popen() args, single list, or string
365
           kwargs: Popen() keyword args"""
366
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
367
                     'mncmd':
368
                     [ 'mnexec', '-da', str( self.pid ) ] }
369
        defaults.update( kwargs )
370
        if len( args ) == 1:
371
            if isinstance( args[ 0 ], list ):
372
                # popen([cmd, arg1, arg2...])
373
                cmd = args[ 0 ]
374
            elif isinstance( args[ 0 ], basestring ):
375
                # popen("cmd arg1 arg2...")
376
                cmd = args[ 0 ].split()
377
            else:
378
                raise Exception( 'popen() requires a string or list' )
379
        elif len( args ) > 0:
380
            # popen( cmd, arg1, arg2... )
381
            cmd = list( args )
382
        # Attach to our namespace  using mnexec -a
383
        cmd = defaults.pop( 'mncmd' ) + cmd
384
        # Shell requires a string, not a list!
385
        if defaults.get( 'shell', False ):
386
            cmd = ' '.join( cmd )
387
        popen = self._popen( cmd, **defaults )
388
        return popen
389

    
390
    def pexec( self, *args, **kwargs ):
391
        """Execute a command using popen
392
           returns: out, err, exitcode"""
393
        popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
394
                            **kwargs )
395
        # Warning: this can fail with large numbers of fds!
396
        out, err = popen.communicate()
397
        exitcode = popen.wait()
398
        return out, err, exitcode
399

    
400
    # Interface management, configuration, and routing
401

    
402
    # BL notes: This might be a bit redundant or over-complicated.
403
    # However, it does allow a bit of specialization, including
404
    # changing the canonical interface names. It's also tricky since
405
    # the real interfaces are created as veth pairs, so we can't
406
    # make a single interface at a time.
407

    
408
    def newPort( self ):
409
        "Return the next port number to allocate."
410
        if len( self.ports ) > 0:
411
            return max( self.ports.values() ) + 1
412
        return self.portBase
413

    
414
    def addIntf( self, intf, port=None, moveIntfFn=moveIntf ):
415
        """Add an interface.
416
           intf: interface
417
           port: port number (optional, typically OpenFlow port number)
418
           moveIntfFn: function to move interface (optional)"""
419
        if port is None:
420
            port = self.newPort()
421
        self.intfs[ port ] = intf
422
        self.ports[ intf ] = port
423
        self.nameToIntf[ intf.name ] = intf
424
        debug( '\n' )
425
        debug( 'added intf %s (%d) to node %s\n' % (
426
                intf, port, self.name ) )
427
        if self.inNamespace:
428
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
429
            moveIntfFn( intf.name, self  )
430

    
431
    def defaultIntf( self ):
432
        "Return interface for lowest port"
433
        ports = self.intfs.keys()
434
        if ports:
435
            return self.intfs[ min( ports ) ]
436
        else:
437
            warn( '*** defaultIntf: warning:', self.name,
438
                  'has no interfaces\n' )
439

    
440
    def intf( self, intf=None ):
441
        """Return our interface object with given string name,
442
           default intf if name is falsy (None, empty string, etc).
443
           or the input intf arg.
444

445
        Having this fcn return its arg for Intf objects makes it
446
        easier to construct functions with flexible input args for
447
        interfaces (those that accept both string names and Intf objects).
448
        """
449
        if not intf:
450
            return self.defaultIntf()
451
        elif isinstance( intf, basestring):
452
            return self.nameToIntf[ intf ]
453
        else:
454
            return intf
455

    
456
    def connectionsTo( self, node):
457
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
458
        # We could optimize this if it is important
459
        connections = []
460
        for intf in self.intfList():
461
            link = intf.link
462
            if link:
463
                node1, node2 = link.intf1.node, link.intf2.node
464
                if node1 == self and node2 == node:
465
                    connections += [ ( intf, link.intf2 ) ]
466
                elif node1 == node and node2 == self:
467
                    connections += [ ( intf, link.intf1 ) ]
468
        return connections
469

    
470
    def deleteIntfs( self, checkName=True ):
471
        """Delete all of our interfaces.
472
           checkName: only delete interfaces that contain our name"""
473
        # In theory the interfaces should go away after we shut down.
474
        # However, this takes time, so we're better off removing them
475
        # explicitly so that we won't get errors if we run before they
476
        # have been removed by the kernel. Unfortunately this is very slow,
477
        # at least with Linux kernels before 2.6.33
478
        for intf in self.intfs.values():
479
            # Protect against deleting hardware interfaces
480
            if ( self.name in intf.name ) or ( not checkName ):
481
                intf.delete()
482
                info( '.' )
483

    
484
    # Routing support
485

    
486
    def setARP( self, ip, mac ):
487
        """Add an ARP entry.
488
           ip: IP address as string
489
           mac: MAC address as string"""
490
        result = self.cmd( 'arp', '-s', ip, mac )
491
        return result
492

    
493
    def setHostRoute( self, ip, intf ):
494
        """Add route to host.
495
           ip: IP address as dotted decimal
496
           intf: string, interface name"""
497
        return self.cmd( 'route add -host', ip, 'dev', intf )
498

    
499
    def setDefaultRoute( self, intf=None ):
500
        """Set the default route to go through intf.
501
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
502
        # Note setParam won't call us if intf is none
503
        if isinstance( intf, basestring ) and ' ' in intf:
504
            params = intf
505
        else:
506
            params = 'dev %s' % intf
507
        # Do this in one line in case we're messing with the root namespace
508
        self.cmd( 'ip route del default; ip route add default', params )
509

    
510
    # Convenience and configuration methods
511

    
512
    def setMAC( self, mac, intf=None ):
513
        """Set the MAC address for an interface.
514
           intf: intf or intf name
515
           mac: MAC address as string"""
516
        return self.intf( intf ).setMAC( mac )
517

    
518
    def setIP( self, ip, prefixLen=8, intf=None, **kwargs ):
519
        """Set the IP address for an interface.
520
           intf: intf or intf name
521
           ip: IP address as a string
522
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs
523
           kwargs: any additional arguments for intf.setIP"""
524
        return self.intf( intf ).setIP( ip, prefixLen, **kwargs )
525

    
526
    def IP( self, intf=None ):
527
        "Return IP address of a node or specific interface."
528
        return self.intf( intf ).IP()
529

    
530
    def MAC( self, intf=None ):
531
        "Return MAC address of a node or specific interface."
532
        return self.intf( intf ).MAC()
533

    
534
    def intfIsUp( self, intf=None ):
535
        "Check if an interface is up."
536
        return self.intf( intf ).isUp()
537

    
538
    # The reason why we configure things in this way is so
539
    # That the parameters can be listed and documented in
540
    # the config method.
541
    # Dealing with subclasses and superclasses is slightly
542
    # annoying, but at least the information is there!
543

    
544
    def setParam( self, results, method, **param ):
545
        """Internal method: configure a *single* parameter
546
           results: dict of results to update
547
           method: config method name
548
           param: arg=value (ignore if value=None)
549
           value may also be list or dict"""
550
        name, value = param.items()[ 0 ]
551
        if value is None:
552
            return
553
        f = getattr( self, method, None )
554
        if not f:
555
            return
556
        if isinstance( value, list ):
557
            result = f( *value )
558
        elif isinstance( value, dict ):
559
            result = f( **value )
560
        else:
561
            result = f( value )
562
        results[ name ] = result
563
        return result
564

    
565
    def config( self, mac=None, ip=None,
566
                defaultRoute=None, lo='up', **_params ):
567
        """Configure Node according to (optional) parameters:
568
           mac: MAC address for default interface
569
           ip: IP address for default interface
570
           ifconfig: arbitrary interface configuration
571
           Subclasses should override this method and call
572
           the parent class's config(**params)"""
573
        # If we were overriding this method, we would call
574
        # the superclass config method here as follows:
575
        # r = Parent.config( **_params )
576
        r = {}
577
        self.setParam( r, 'setMAC', mac=mac )
578
        self.setParam( r, 'setIP', ip=ip )
579
        self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
580
        # This should be examined
581
        self.cmd( 'ifconfig lo ' + lo )
582
        return r
583

    
584
    def configDefault( self, **moreParams ):
585
        "Configure with default parameters"
586
        self.params.update( moreParams )
587
        self.config( **self.params )
588

    
589
    # This is here for backward compatibility
590
    def linkTo( self, node, link=Link ):
591
        """(Deprecated) Link to another node
592
           replace with Link( node1, node2)"""
593
        return link( self, node )
594

    
595
    # Other methods
596

    
597
    def intfList( self ):
598
        "List of our interfaces sorted by port number"
599
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
600

    
601
    def intfNames( self ):
602
        "The names of our interfaces sorted by port number"
603
        return [ str( i ) for i in self.intfList() ]
604

    
605
    def __repr__( self ):
606
        "More informative string representation"
607
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
608
                              for i in self.intfList() ] ) )
609
        return '<%s %s: %s pid=%s> ' % (
610
            self.__class__.__name__, self.name, intfs, self.pid )
611

    
612
    def __str__( self ):
613
        "Abbreviated string representation"
614
        return self.name
615

    
616
    # Automatic class setup support
617

    
618
    isSetup = False
619

    
620
    @classmethod
621
    def checkSetup( cls ):
622
        "Make sure our class and superclasses are set up"
623
        while cls and not getattr( cls, 'isSetup', True ):
624
            cls.setup()
625
            cls.isSetup = True
626
            # Make pylint happy
627
            cls = getattr( type( cls ), '__base__', None )
628

    
629
    @classmethod
630
    def setup( cls ):
631
        "Make sure our class dependencies are available"
632
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
633

    
634
class Host( Node ):
635
    "A host is simply a Node"
636
    pass
637

    
638
class CPULimitedHost( Host ):
639

    
640
    "CPU limited host"
641

    
642
    def __init__( self, name, sched='cfs', **kwargs ):
643
        Host.__init__( self, name, **kwargs )
644
        # Initialize class if necessary
645
        if not CPULimitedHost.inited:
646
            CPULimitedHost.init()
647
        # Create a cgroup and move shell into it
648
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
649
        errFail( 'cgcreate -g ' + self.cgroup )
650
        # We don't add ourselves to a cpuset because you must
651
        # specify the cpu and memory placement first
652
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
653
        # BL: Setting the correct period/quota is tricky, particularly
654
        # for RT. RT allows very small quotas, but the overhead
655
        # seems to be high. CFS has a mininimum quota of 1 ms, but
656
        # still does better with larger period values.
657
        self.period_us = kwargs.get( 'period_us', 100000 )
658
        self.sched = sched
659
        if sched == 'rt':
660
            self.checkRtGroupSched()
661
            self.rtprio = 20
662

    
663
    def cgroupSet( self, param, value, resource='cpu' ):
664
        "Set a cgroup parameter and return its value"
665
        cmd = 'cgset -r %s.%s=%s /%s' % (
666
            resource, param, value, self.name )
667
        quietRun( cmd )
668
        nvalue = int( self.cgroupGet( param, resource ) )
669
        if nvalue != value:
670
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
671
                   % ( param, nvalue, value ) )
672
        return nvalue
673

    
674
    def cgroupGet( self, param, resource='cpu' ):
675
        "Return value of cgroup parameter"
676
        cmd = 'cgget -r %s.%s /%s' % (
677
            resource, param, self.name )
678
        return int( quietRun( cmd ).split()[ -1 ] )
679

    
680
    def cgroupDel( self ):
681
        "Clean up our cgroup"
682
        # info( '*** deleting cgroup', self.cgroup, '\n' )
683
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
684
        return exitcode == 0  # success condition
685

    
686
    def popen( self, *args, **kwargs ):
687
        """Return a Popen() object in node's namespace
688
           args: Popen() args, single list, or string
689
           kwargs: Popen() keyword args"""
690
        # Tell mnexec to execute command in our cgroup
691
        mncmd = [ 'mnexec', '-g', self.name,
692
                  '-da', str( self.pid ) ]
693
        # if our cgroup is not given any cpu time,
694
        # we cannot assign the RR Scheduler.
695
        if self.sched == 'rt':
696
            if int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) ) <= 0:
697
                mncmd += [ '-r', str( self.rtprio ) ]
698
            else:
699
                debug( '*** error: not enough cpu time available for %s.' %
700
                       self.name, 'Using cfs scheduler for subprocess\n' )
701
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
702

    
703
    def cleanup( self ):
704
        "Clean up Node, then clean up our cgroup"
705
        super( CPULimitedHost, self ).cleanup()
706
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
707

    
708
    _rtGroupSched = False   # internal class var: Is CONFIG_RT_GROUP_SCHED set?
709

    
710
    @classmethod
711
    def checkRtGroupSched( cls ):
712
        "Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT"
713
        if not cls._rtGroupSched:
714
            release = quietRun( 'uname -r' ).strip('\r\n')
715
            output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' %
716
                               release )
717
            if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
718
                error( '\n*** error: please enable RT_GROUP_SCHED '
719
                       'in your kernel\n' )
720
                exit( 1 )
721
            cls._rtGroupSched = True
722

    
723
    def chrt( self ):
724
        "Set RT scheduling priority"
725
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
726
        result = quietRun( 'chrt -p %s' % self.pid )
727
        firstline = result.split( '\n' )[ 0 ]
728
        lastword = firstline.split( ' ' )[ -1 ]
729
        if lastword != 'SCHED_RR':
730
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
731
        return lastword
732

    
733
    def rtInfo( self, f ):
734
        "Internal method: return parameters for RT bandwidth"
735
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
736
        # RT uses wall clock time for period and quota
737
        quota = int( self.period_us * f )
738
        return pstr, qstr, self.period_us, quota
739

    
740
    def cfsInfo( self, f ):
741
        "Internal method: return parameters for CFS bandwidth"
742
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
743
        # CFS uses wall clock time for period and CPU time for quota.
744
        quota = int( self.period_us * f * numCores() )
745
        period = self.period_us
746
        if f > 0 and quota < 1000:
747
            debug( '(cfsInfo: increasing default period) ' )
748
            quota = 1000
749
            period = int( quota / f / numCores() )
750
        # Reset to unlimited on negative quota
751
        if quota < 0:
752
            quota = -1
753
        return pstr, qstr, period, quota
754

    
755
    # BL comment:
756
    # This may not be the right API,
757
    # since it doesn't specify CPU bandwidth in "absolute"
758
    # units the way link bandwidth is specified.
759
    # We should use MIPS or SPECINT or something instead.
760
    # Alternatively, we should change from system fraction
761
    # to CPU seconds per second, essentially assuming that
762
    # all CPUs are the same.
763

    
764
    def setCPUFrac( self, f, sched=None ):
765
        """Set overall CPU fraction for this host
766
           f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited)
767
           sched: 'rt' or 'cfs'
768
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
769
           and 'rt' requires CONFIG_RT_GROUP_SCHED"""
770
        if not sched:
771
            sched = self.sched
772
        if sched == 'rt':
773
            if not f or f < 0:
774
                raise Exception( 'Please set a positive CPU fraction'
775
                                 ' for sched=rt\n' )
776
            pstr, qstr, period, quota = self.rtInfo( f )
777
        elif sched == 'cfs':
778
            pstr, qstr, period, quota = self.cfsInfo( f )
779
        else:
780
            return
781
        # Set cgroup's period and quota
782
        setPeriod = self.cgroupSet( pstr, period )
783
        setQuota = self.cgroupSet( qstr, quota )
784
        if sched == 'rt':
785
            # Set RT priority if necessary
786
            sched = self.chrt()
787
        info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
788

    
789
    def setCPUs( self, cores, mems=0 ):
790
        "Specify (real) cores that our cgroup can run on"
791
        if not cores:
792
            return
793
        if isinstance( cores, list ):
794
            cores = ','.join( [ str( c ) for c in cores ] )
795
        self.cgroupSet( resource='cpuset', param='cpus',
796
                        value=cores )
797
        # Memory placement is probably not relevant, but we
798
        # must specify it anyway
799
        self.cgroupSet( resource='cpuset', param='mems',
800
                        value=mems)
801
        # We have to do this here after we've specified
802
        # cpus and mems
803
        errFail( 'cgclassify -g cpuset:/%s %s' % (
804
                 self.name, self.pid ) )
805

    
806
    def config( self, cpu=-1, cores=None, **params ):
807
        """cpu: desired overall system CPU fraction
808
           cores: (real) core(s) this host can run on
809
           params: parameters for Node.config()"""
810
        r = Node.config( self, **params )
811
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
812
        # that seems redundant
813
        self.setParam( r, 'setCPUFrac', cpu=cpu )
814
        self.setParam( r, 'setCPUs', cores=cores )
815
        return r
816

    
817
    inited = False
818

    
819
    @classmethod
820
    def init( cls ):
821
        "Initialization for CPULimitedHost class"
822
        mountCgroups()
823
        cls.inited = True
824

    
825

    
826
# Some important things to note:
827
#
828
# The "IP" address which setIP() assigns to the switch is not
829
# an "IP address for the switch" in the sense of IP routing.
830
# Rather, it is the IP address for the control interface,
831
# on the control network, and it is only relevant to the
832
# controller. If you are running in the root namespace
833
# (which is the only way to run OVS at the moment), the
834
# control interface is the loopback interface, and you
835
# normally never want to change its IP address!
836
#
837
# In general, you NEVER want to attempt to use Linux's
838
# network stack (i.e. ifconfig) to "assign" an IP address or
839
# MAC address to a switch data port. Instead, you "assign"
840
# the IP and MAC addresses in the controller by specifying
841
# packets that you want to receive or send. The "MAC" address
842
# reported by ifconfig for a switch data port is essentially
843
# meaningless. It is important to understand this if you
844
# want to create a functional router using OpenFlow.
845

    
846
class Switch( Node ):
847
    """A Switch is a Node that is running (or has execed?)
848
       an OpenFlow switch."""
849

    
850
    portBase = 1  # Switches start with port 1 in OpenFlow
851
    dpidLen = 16  # digits in dpid passed to switch
852

    
853
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
854
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
855
           opts: additional switch options
856
           listenPort: port to listen on for dpctl connections"""
857
        Node.__init__( self, name, **params )
858
        self.dpid = self.defaultDpid( dpid )
859
        self.opts = opts
860
        self.listenPort = listenPort
861
        if not self.inNamespace:
862
            self.controlIntf = Intf( 'lo', self, port=0 )
863

    
864
    def defaultDpid( self, dpid=None ):
865
        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
866
        if dpid:
867
            # Remove any colons and make sure it's a good hex number
868
            dpid = dpid.translate( None, ':' )
869
            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
870
        else:
871
            # Use hex of the first number in the switch name
872
            nums = re.findall( r'\d+', self.name )
873
            if nums:
874
                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
875
            else:
876
                raise Exception( 'Unable to derive default datapath ID - '
877
                                 'please either specify a dpid or use a '
878
                                 'canonical switch name such as s23.' )
879
        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
880

    
881
    def defaultIntf( self ):
882
        "Return control interface"
883
        if self.controlIntf:
884
            return self.controlIntf
885
        else:
886
            return Node.defaultIntf( self )
887

    
888
    def sendCmd( self, *cmd, **kwargs ):
889
        """Send command to Node.
890
           cmd: string"""
891
        kwargs.setdefault( 'printPid', False )
892
        if not self.execed:
893
            return Node.sendCmd( self, *cmd, **kwargs )
894
        else:
895
            error( '*** Error: %s has execed and cannot accept commands' %
896
                   self.name )
897

    
898
    def connected( self ):
899
        "Is the switch connected to a controller? (override this method)"
900
        # Assume that we are connected by default to whatever we need to
901
        # be connected to. This should be overridden by any OpenFlow
902
        # switch, but not by a standalone bridge.
903
        debug( 'Assuming', repr( self ), 'is connected to a controller\n' )
904
        return True
905

    
906
    def stop( self, deleteIntfs=True ):
907
        """Stop switch
908
           deleteIntfs: delete interfaces? (True)"""
909
        if deleteIntfs:
910
            self.deleteIntfs()
911

    
912
    def __repr__( self ):
913
        "More informative string representation"
914
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
915
                              for i in self.intfList() ] ) )
916
        return '<%s %s: %s pid=%s> ' % (
917
            self.__class__.__name__, self.name, intfs, self.pid )
918

    
919

    
920
class UserSwitch( Switch ):
921
    "User-space switch."
922

    
923
    dpidLen = 12
924

    
925
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
926
        """Init.
927
           name: name for the switch
928
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
929
        Switch.__init__( self, name, **kwargs )
930
        pathCheck( 'ofdatapath', 'ofprotocol',
931
                   moduleName='the OpenFlow reference user switch' +
932
                              '(openflow.org)' )
933
        if self.listenPort:
934
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
935
        else:
936
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
937
        self.dpopts = dpopts
938

    
939
    @classmethod
940
    def setup( cls ):
941
        "Ensure any dependencies are loaded; if not, try to load them."
942
        if not os.path.exists( '/dev/net/tun' ):
943
            moduleDeps( add=TUN )
944

    
945
    def dpctl( self, *args ):
946
        "Run dpctl command"
947
        listenAddr = None
948
        if not self.listenPort:
949
            listenAddr = 'unix:/tmp/%s.listen' % self.name
950
        else:
951
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
952
        return self.cmd( 'dpctl ' + ' '.join( args ) +
953
                         ' ' + listenAddr )
954

    
955
    def connected( self ):
956
        "Is the switch connected to a controller?"
957
        status = self.dpctl( 'status' )
958
        return ( 'remote.is-connected=true' in status and
959
                 'local.is-connected=true' in status )
960

    
961
    @staticmethod
962
    def TCReapply( intf ):
963
        """Unfortunately user switch and Mininet are fighting
964
           over tc queuing disciplines. To resolve the conflict,
965
           we re-create the user switch's configuration, but as a
966
           leaf of the TCIntf-created configuration."""
967
        if isinstance( intf, TCIntf ):
968
            ifspeed = 10000000000  # 10 Gbps
969
            minspeed = ifspeed * 0.001
970

    
971
            res = intf.config( **intf.params )
972

    
973
            if res is None:  # link may not have TC parameters
974
                return
975

    
976
            # Re-add qdisc, root, and default classes user switch created, but
977
            # with new parent, as setup by Mininet's TCIntf
978
            parent = res['parent']
979
            intf.tc( "%s qdisc add dev %s " + parent +
980
                     " handle 1: htb default 0xfffe" )
981
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
982
                     + str(ifspeed) )
983
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
984
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
985

    
986
    def start( self, controllers ):
987
        """Start OpenFlow reference user datapath.
988
           Log to /tmp/sN-{ofd,ofp}.log.
989
           controllers: list of controller objects"""
990
        # Add controllers
991
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
992
                            for c in controllers ] )
993
        ofdlog = '/tmp/' + self.name + '-ofd.log'
994
        ofplog = '/tmp/' + self.name + '-ofp.log'
995
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
996
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
997
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
998
                  self.dpopts +
999
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
1000
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
1001
                  ' ' + clist +
1002
                  ' --fail=closed ' + self.opts +
1003
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
1004
        if "no-slicing" not in self.dpopts:
1005
            # Only TCReapply if slicing is enable
1006
            sleep(1)  # Allow ofdatapath to start before re-arranging qdisc's
1007
            for intf in self.intfList():
1008
                if not intf.IP():
1009
                    self.TCReapply( intf )
1010

    
1011
    def stop( self, deleteIntfs=True ):
1012
        """Stop OpenFlow reference user datapath.
1013
           deleteIntfs: delete interfaces? (True)"""
1014
        self.cmd( 'kill %ofdatapath' )
1015
        self.cmd( 'kill %ofprotocol' )
1016
        super( UserSwitch, self ).stop( deleteIntfs )
1017

    
1018

    
1019
class OVSSwitch( Switch ):
1020
    "Open vSwitch switch. Depends on ovs-vsctl."
1021

    
1022
    def __init__( self, name, failMode='secure', datapath='kernel',
1023
                  inband=False, protocols=None,
1024
                  reconnectms=1000, stp=False, batch=False, **params ):
1025
        """name: name for switch
1026
           failMode: controller loss behavior (secure|open)
1027
           datapath: userspace or kernel mode (kernel|user)
1028
           inband: use in-band control (False)
1029
           protocols: use specific OpenFlow version(s) (e.g. OpenFlow13)
1030
                      Unspecified (or old OVS version) uses OVS default
1031
           reconnectms: max reconnect timeout in ms (0/None for default)
1032
           stp: enable STP (False, requires failMode=standalone)
1033
           batch: enable batch startup (False)"""
1034
        Switch.__init__( self, name, **params )
1035
        self.failMode = failMode
1036
        self.datapath = datapath
1037
        self.inband = inband
1038
        self.protocols = protocols
1039
        self.reconnectms = reconnectms
1040
        self.stp = stp
1041
        self._uuids = []  # controller UUIDs
1042
        self.batch = batch
1043
        self.commands = []  # saved commands for batch startup
1044

    
1045
    @classmethod
1046
    def setup( cls ):
1047
        "Make sure Open vSwitch is installed and working"
1048
        pathCheck( 'ovs-vsctl',
1049
                   moduleName='Open vSwitch (openvswitch.org)')
1050
        # This should no longer be needed, and it breaks
1051
        # with OVS 1.7 which has renamed the kernel module:
1052
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1053
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1054
        if exitcode:
1055
            error( out + err +
1056
                   'ovs-vsctl exited with code %d\n' % exitcode +
1057
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1058
                   'Make sure that Open vSwitch is installed, '
1059
                   'that ovsdb-server is running, and that\n'
1060
                   '"ovs-vsctl show" works correctly.\n'
1061
                   'You may wish to try '
1062
                   '"service openvswitch-switch start".\n' )
1063
            exit( 1 )
1064
        version = quietRun( 'ovs-vsctl --version' )
1065
        cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
1066

    
1067
    @classmethod
1068
    def isOldOVS( cls ):
1069
        "Is OVS ersion < 1.10?"
1070
        return ( StrictVersion( cls.OVSVersion ) <
1071
                 StrictVersion( '1.10' ) )
1072

    
1073
    def dpctl( self, *args ):
1074
        "Run ovs-ofctl command"
1075
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1076

    
1077
    def vsctl( self, *args, **kwargs ):
1078
        "Run ovs-vsctl command (or queue for later execution)"
1079
        if self.batch:
1080
            cmd = ' '.join( str( arg ).strip() for arg in args )
1081
            self.commands.append( cmd )
1082
        else:
1083
            return self.cmd( 'ovs-vsctl', *args, **kwargs )
1084

    
1085
    @staticmethod
1086
    def TCReapply( intf ):
1087
        """Unfortunately OVS and Mininet are fighting
1088
           over tc queuing disciplines. As a quick hack/
1089
           workaround, we clear OVS's and reapply our own."""
1090
        if isinstance( intf, TCIntf ):
1091
            intf.config( **intf.params )
1092

    
1093
    def attach( self, intf ):
1094
        "Connect a data port"
1095
        self.vsctl( 'add-port', self, intf )
1096
        self.cmd( 'ifconfig', intf, 'up' )
1097
        self.TCReapply( intf )
1098

    
1099
    def detach( self, intf ):
1100
        "Disconnect a data port"
1101
        self.vsctl( 'del-port', self, intf )
1102

    
1103
    def controllerUUIDs( self, update=False ):
1104
        """Return ovsdb UUIDs for our controllers
1105
           update: update cached value"""
1106
        if not self._uuids or update:
1107
            controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1108
                                    'Controller' ).strip()
1109
            if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1110
                controllers = controllers[ 1 : -1 ]
1111
                if controllers:
1112
                    self._uuids = [ c.strip()
1113
                                    for c in controllers.split( ',' ) ]
1114
        return self._uuids
1115

    
1116
    def connected( self ):
1117
        "Are we connected to at least one of our controllers?"
1118
        for uuid in self.controllerUUIDs():
1119
            if 'true' in self.vsctl( '-- get Controller',
1120
                                     uuid, 'is_connected' ):
1121
                return True
1122
        return self.failMode == 'standalone'
1123

    
1124
    def intfOpts( self, intf ):
1125
        "Return OVS interface options for intf"
1126
        opts = ''
1127
        if not self.isOldOVS():
1128
            # ofport_request is not supported on old OVS
1129
            opts += ' ofport_request=%s' % self.ports[ intf ]
1130
            # Patch ports don't work well with old OVS
1131
            if isinstance( intf, OVSIntf ):
1132
                intf1, intf2 = intf.link.intf1, intf.link.intf2
1133
                peer = intf1 if intf1 != intf else intf2
1134
                opts += ' type=patch options:peer=%s' % peer
1135
        return '' if not opts else ' -- set Interface %s' % intf + opts
1136

    
1137
    def bridgeOpts( self ):
1138
        "Return OVS bridge options"
1139
        opts = ( ' other_config:datapath-id=%s' % self.dpid +
1140
                 ' fail_mode=%s' % self.failMode )
1141
        if not self.inband:
1142
            opts += ' other-config:disable-in-band=true'
1143
        if self.datapath == 'user':
1144
            opts += ' datapath_type=netdev'
1145
        if self.protocols and not self.isOldOVS():
1146
            opts += ' protocols=%s' % self.protocols
1147
        if self.stp and self.failMode == 'standalone':
1148
            opts += ' stp_enable=true'
1149
        return opts
1150

    
1151
    def start( self, controllers ):
1152
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1153
        if self.inNamespace:
1154
            raise Exception(
1155
                'OVS kernel switch does not work in a namespace' )
1156
        int( self.dpid, 16 )  # DPID must be a hex string
1157
        # Command to add interfaces
1158
        intfs = ''.join( ' -- add-port %s %s' % ( self, intf ) +
1159
                         self.intfOpts( intf )
1160
                         for intf in self.intfList()
1161
                         if self.ports[ intf ] and not intf.IP() )
1162
        # Command to create controller entries
1163
        clist = [ ( self.name + c.name, '%s:%s:%d' %
1164
                  ( c.protocol, c.IP(), c.port ) )
1165
                  for c in controllers ]
1166
        if self.listenPort:
1167
            clist.append( ( self.name + '-listen',
1168
                            'ptcp:%s' % self.listenPort ) )
1169
        ccmd = '-- --id=@%s create Controller target=\\"%s\\"'
1170
        if self.reconnectms:
1171
            ccmd += ' max_backoff=%d' % self.reconnectms
1172
        cargs = ' '.join( ccmd % ( name, target )
1173
                          for name, target in clist )
1174
        # Controller ID list
1175
        cids = ','.join( '@%s' % name for name, _target in clist )
1176
        # Try to delete any existing bridges with the same name
1177
        if not self.isOldOVS():
1178
            cargs += ' -- --if-exists del-br %s' % self
1179
        # One ovs-vsctl command to rule them all!
1180
        self.vsctl( cargs +
1181
                    ' -- add-br %s' % self +
1182
                    ' -- set bridge %s controller=[%s]' % ( self, cids  ) +
1183
                    self.bridgeOpts() +
1184
                    intfs )
1185
        # If necessary, restore TC config overwritten by OVS
1186
        if not self.batch:
1187
            for intf in self.intfList():
1188
                self.TCReapply( intf )
1189

    
1190
    # This should be ~ int( quietRun( 'getconf ARG_MAX' ) ),
1191
    # but the real limit seems to be much lower
1192
    argmax = 128000
1193

    
1194
    @classmethod
1195
    def batchStartup( cls, switches, run=errRun ):
1196
        """Batch startup for OVS
1197
           switches: switches to start up
1198
           run: function to run commands (errRun)"""
1199
        info( '...' )
1200
        cmds = 'ovs-vsctl'
1201
        for switch in switches:
1202
            if switch.isOldOVS():
1203
                # Ideally we'd optimize this also
1204
                run( 'ovs-vsctl del-br %s' % switch )
1205
            for cmd in switch.commands:
1206
                cmd = cmd.strip()
1207
                # Don't exceed ARG_MAX
1208
                if len( cmds ) + len( cmd ) >= cls.argmax:
1209
                    run( cmds, shell=True )
1210
                    cmds = 'ovs-vsctl'
1211
                cmds += ' ' + cmd
1212
                switch.cmds = []
1213
                switch.batch = False
1214
        if cmds:
1215
            run( cmds, shell=True )
1216
        # Reapply link config if necessary...
1217
        for switch in switches:
1218
            for intf in switch.intfs.itervalues():
1219
                if isinstance( intf, TCIntf ):
1220
                    intf.config( **intf.params )
1221
        return switches
1222

    
1223
    def stop( self, deleteIntfs=True ):
1224
        """Terminate OVS switch.
1225
           deleteIntfs: delete interfaces? (True)"""
1226
        self.cmd( 'ovs-vsctl del-br', self )
1227
        if self.datapath == 'user':
1228
            self.cmd( 'ip link del', self )
1229
        super( OVSSwitch, self ).stop( deleteIntfs )
1230

    
1231
    @classmethod
1232
    def batchShutdown( cls, switches, run=errRun ):
1233
        "Shut down a list of OVS switches"
1234
        delcmd = 'del-br %s'
1235
        if switches and not switches[ 0 ].isOldOVS():
1236
            delcmd = '--if-exists ' + delcmd
1237
        # First, delete them all from ovsdb
1238
        run( 'ovs-vsctl ' +
1239
             ' -- '.join( delcmd % s for s in switches ) )
1240
        # Next, shut down all of the processes
1241
        pids = ' '.join( str( switch.pid ) for switch in switches )
1242
        run( 'kill -HUP ' + pids )
1243
        for switch in switches:
1244
            switch.shell = None
1245
        return switches
1246

    
1247

    
1248
OVSKernelSwitch = OVSSwitch
1249

    
1250

    
1251
class OVSBridge( OVSSwitch ):
1252
    "OVSBridge is an OVSSwitch in standalone/bridge mode"
1253

    
1254
    def __init__( self, *args, **kwargs ):
1255
        """stp: enable Spanning Tree Protocol (False)
1256
           see OVSSwitch for other options"""
1257
        kwargs.update( failMode='standalone' )
1258
        OVSSwitch.__init__( self, *args, **kwargs )
1259

    
1260
    def start( self, controllers ):
1261
        "Start bridge, ignoring controllers argument"
1262
        OVSSwitch.start( self, controllers=[] )
1263

    
1264
    def connected( self ):
1265
        "Are we forwarding yet?"
1266
        if self.stp:
1267
            status = self.dpctl( 'show' )
1268
            return 'STP_FORWARD' in status and not 'STP_LEARN' in status
1269
        else:
1270
            return True
1271

    
1272

    
1273
class IVSSwitch( Switch ):
1274
    "Indigo Virtual Switch"
1275

    
1276
    def __init__( self, name, verbose=False, **kwargs ):
1277
        Switch.__init__( self, name, **kwargs )
1278
        self.verbose = verbose
1279

    
1280
    @classmethod
1281
    def setup( cls ):
1282
        "Make sure IVS is installed"
1283
        pathCheck( 'ivs-ctl', 'ivs',
1284
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1285
        out, err, exitcode = errRun( 'ivs-ctl show' )
1286
        if exitcode:
1287
            error( out + err +
1288
                   'ivs-ctl exited with code %d\n' % exitcode +
1289
                   '*** The openvswitch kernel module might '
1290
                   'not be loaded. Try modprobe openvswitch.\n' )
1291
            exit( 1 )
1292

    
1293
    @classmethod
1294
    def batchShutdown( cls, switches ):
1295
        "Kill each IVS switch, to be waited on later in stop()"
1296
        for switch in switches:
1297
            switch.cmd( 'kill %ivs' )
1298
        return switches
1299

    
1300
    def start( self, controllers ):
1301
        "Start up a new IVS switch"
1302
        args = ['ivs']
1303
        args.extend( ['--name', self.name] )
1304
        args.extend( ['--dpid', self.dpid] )
1305
        if self.verbose:
1306
            args.extend( ['--verbose'] )
1307
        for intf in self.intfs.values():
1308
            if not intf.IP():
1309
                args.extend( ['-i', intf.name] )
1310
        for c in controllers:
1311
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1312
        if self.listenPort:
1313
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1314
        args.append( self.opts )
1315

    
1316
        logfile = '/tmp/ivs.%s.log' % self.name
1317

    
1318
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1319

    
1320
    def stop( self, deleteIntfs=True ):
1321
        """Terminate IVS switch.
1322
           deleteIntfs: delete interfaces? (True)"""
1323
        self.cmd( 'kill %ivs' )
1324
        self.cmd( 'wait' )
1325
        super( IVSSwitch, self ).stop( deleteIntfs )
1326

    
1327
    def attach( self, intf ):
1328
        "Connect a data port"
1329
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1330

    
1331
    def detach( self, intf ):
1332
        "Disconnect a data port"
1333
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1334

    
1335
    def dpctl( self, *args ):
1336
        "Run dpctl command"
1337
        if not self.listenPort:
1338
            return "can't run dpctl without passive listening port"
1339
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1340
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1341

    
1342

    
1343
class Controller( Node ):
1344
    """A Controller is a Node that is running (or has execed?) an
1345
       OpenFlow controller."""
1346

    
1347
    def __init__( self, name, inNamespace=False, command='controller',
1348
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1349
                  port=6653, protocol='tcp', **params ):
1350
        self.command = command
1351
        self.cargs = cargs
1352
        self.cdir = cdir
1353
        # Accept 'ip:port' syntax as shorthand
1354
        if ':' in ip:
1355
            ip, port = ip.split( ':' )
1356
            port = int( port )
1357
        self.ip = ip
1358
        self.port = port
1359
        self.protocol = protocol
1360
        Node.__init__( self, name, inNamespace=inNamespace,
1361
                       ip=ip, **params  )
1362
        self.checkListening()
1363

    
1364
    def checkListening( self ):
1365
        "Make sure no controllers are running on our port"
1366
        # Verify that Telnet is installed first:
1367
        out, _err, returnCode = errRun( "which telnet" )
1368
        if 'telnet' not in out or returnCode != 0:
1369
            raise Exception( "Error running telnet to check for listening "
1370
                             "controllers; please check that it is "
1371
                             "installed." )
1372
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1373
                              ( self.ip, self.port ) )
1374
        if 'Connected' in listening:
1375
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1376
            pstr = ':%d ' % self.port
1377
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1378
            raise Exception( "Please shut down the controller which is"
1379
                             " running on port %d:\n" % self.port +
1380
                             '\n'.join( clist ) )
1381

    
1382
    def start( self ):
1383
        """Start <controller> <args> on controller.
1384
           Log to /tmp/cN.log"""
1385
        pathCheck( self.command )
1386
        cout = '/tmp/' + self.name + '.log'
1387
        if self.cdir is not None:
1388
            self.cmd( 'cd ' + self.cdir )
1389
        self.cmd( self.command + ' ' + self.cargs % self.port +
1390
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1391
        self.execed = False
1392

    
1393
    def stop( self, *args, **kwargs ):
1394
        "Stop controller."
1395
        self.cmd( 'kill %' + self.command )
1396
        self.cmd( 'wait %' + self.command )
1397
        super( Controller, self ).stop( *args, **kwargs )
1398

    
1399
    def IP( self, intf=None ):
1400
        "Return IP address of the Controller"
1401
        if self.intfs:
1402
            ip = Node.IP( self, intf )
1403
        else:
1404
            ip = self.ip
1405
        return ip
1406

    
1407
    def __repr__( self ):
1408
        "More informative string representation"
1409
        return '<%s %s: %s:%s pid=%s> ' % (
1410
            self.__class__.__name__, self.name,
1411
            self.IP(), self.port, self.pid )
1412

    
1413
    @classmethod
1414
    def isAvailable( cls ):
1415
        "Is controller available?"
1416
        return quietRun( 'which controller' )
1417

    
1418

    
1419
class OVSController( Controller ):
1420
    "Open vSwitch controller"
1421
    def __init__( self, name, command='ovs-controller', **kwargs ):
1422
        if quietRun( 'which test-controller' ):
1423
            command = 'test-controller'
1424
        Controller.__init__( self, name, command=command, **kwargs )
1425

    
1426
    @classmethod
1427
    def isAvailable( cls ):
1428
        return ( quietRun( 'which ovs-controller' ) or
1429
                 quietRun( 'which test-controller' ) )
1430

    
1431
class NOX( Controller ):
1432
    "Controller to run a NOX application."
1433

    
1434
    def __init__( self, name, *noxArgs, **kwargs ):
1435
        """Init.
1436
           name: name to give controller
1437
           noxArgs: arguments (strings) to pass to NOX"""
1438
        if not noxArgs:
1439
            warn( 'warning: no NOX modules specified; '
1440
                  'running packetdump only\n' )
1441
            noxArgs = [ 'packetdump' ]
1442
        elif type( noxArgs ) not in ( list, tuple ):
1443
            noxArgs = [ noxArgs ]
1444

    
1445
        if 'NOX_CORE_DIR' not in os.environ:
1446
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1447
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1448

    
1449
        Controller.__init__( self, name,
1450
                             command=noxCoreDir + '/nox_core',
1451
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1452
                             ' '.join( noxArgs ),
1453
                             cdir=noxCoreDir,
1454
                             **kwargs )
1455

    
1456
class Ryu( Controller ):
1457
    "Controller to run Ryu application"
1458
    def __init__( self, name, *ryuArgs, **kwargs ):
1459
        """Init.
1460
        name: name to give controller.
1461
        ryuArgs: arguments and modules to pass to Ryu"""
1462
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1463
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1464
        if not ryuArgs:
1465
            warn( 'warning: no Ryu modules specified; '
1466
                  'running simple_switch only\n' )
1467
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1468
        elif type( ryuArgs ) not in ( list, tuple ):
1469
            ryuArgs = [ ryuArgs ]
1470

    
1471
        Controller.__init__( self, name,
1472
                             command='ryu-manager',
1473
                             cargs='--ofp-tcp-listen-port %s ' +
1474
                             ' '.join( ryuArgs ),
1475
                             cdir=ryuCoreDir,
1476
                             **kwargs )
1477

    
1478

    
1479
class RemoteController( Controller ):
1480
    "Controller running outside of Mininet's control."
1481

    
1482
    def __init__( self, name, ip='127.0.0.1',
1483
                  port=None, **kwargs):
1484
        """Init.
1485
           name: name to give controller
1486
           ip: the IP address where the remote controller is
1487
           listening
1488
           port: the port where the remote controller is listening"""
1489
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1490

    
1491
    def start( self ):
1492
        "Overridden to do nothing."
1493
        return
1494

    
1495
    def stop( self ):
1496
        "Overridden to do nothing."
1497
        return
1498

    
1499
    def checkListening( self ):
1500
        "Warn if remote controller is not accessible"
1501
        if self.port is not None:
1502
            self.isListening( self.ip, self.port )
1503
        else:
1504
            for port in 6653, 6633:
1505
                if self.isListening( self.ip, port ):
1506
                    self.port = port
1507
                    info( "Connecting to remote controller"
1508
                          " at %s:%d\n" % ( self.ip, self.port ))
1509
                    break
1510

    
1511
        if self.port is None:
1512
            self.port = 6653
1513
            warn( "Setting remote controller"
1514
                  " to %s:%d\n" % ( self.ip, self.port ))
1515

    
1516
    def isListening( self, ip, port ):
1517
        "Check if a remote controller is listening at a specific ip and port"
1518
        listening = self.cmd( "echo A | telnet -e A %s %d" % ( ip, port ) )
1519
        if 'Connected' not in listening:
1520
            warn( "Unable to contact the remote controller"
1521
                  " at %s:%d\n" % ( ip, port ) )
1522
            return False
1523
        else:
1524
            return True
1525

    
1526
DefaultControllers = ( Controller, OVSController )
1527

    
1528
def findController( controllers=DefaultControllers ):
1529
    "Return first available controller from list, if any"
1530
    for controller in controllers:
1531
        if controller.isAvailable():
1532
            return controller
1533

    
1534
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1535
    "Find a controller that is available and instantiate it"
1536
    controller = findController( controllers )
1537
    if not controller:
1538
        raise Exception( 'Could not find a default OpenFlow controller' )
1539
    return controller( name, **kwargs )
1540

    
1541
def NullController( *_args, **_kwargs ):
1542
    "Nonexistent controller - simply returns None"
1543
    return None