Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 9ca63226

History | View | Annotate | Download (59.3 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
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
27
    implementation.
28

29
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
30
    implementation (openvswitch.org).
31

32
Controller: superclass for OpenFlow controllers. The default controller
33
    is controller(8) from the reference implementation.
34

35
NOXController: a controller node using NOX (noxrepo.org).
36

37
RemoteController: a remote controller node, which may use any
38
    arbitrary OpenFlow-compatible controller, and which is not
39
    created or managed by mininet.
40

41
Future enhancements:
42

43
- Possibly make Node, Switch and Controller more abstract so that
44
  they can be used for both local and remote nodes
45

46
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
47
"""
48

    
49
import os
50
import pty
51
import re
52
import signal
53
import select
54
from subprocess import Popen, PIPE
55
from time import sleep
56

    
57
from mininet.log import info, error, warn, debug
58
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
59
                           numCores, retry, mountCgroups )
60
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
61
from mininet.link import Link, Intf, TCIntf, OVSIntf
62
from re import findall
63
from distutils.version import StrictVersion
64

    
65
class Node( object ):
66
    """A virtual network node is simply a shell in a network namespace.
67
       We communicate with it using pipes."""
68

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

    
71
    def __init__( self, name, inNamespace=True, **params ):
72
        """name: name of node
73
           inNamespace: in network namespace?
74
           privateDirs: list of private directory strings or tuples
75
           params: Node parameters (see config() for details)"""
76

    
77
        # Make sure class actually works
78
        self.checkSetup()
79

    
80
        self.name = params.get( 'name', name )
81
        self.privateDirs = params.get( 'privateDirs', [] )
82
        self.inNamespace = params.get( 'inNamespace', inNamespace )
83

    
84
        # Stash configuration parameters for future reference
85
        self.params = params
86

    
87
        self.intfs = {}  # dict of port numbers to interfaces
88
        self.ports = {}  # dict of interfaces to port numbers
89
                         # replace with Port objects, eventually ?
90
        self.nameToIntf = {}  # dict of interface names to Intfs
91

    
92
        # Make pylint happy
93
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
94
            self.lastPid, self.lastCmd, self.pollOut ) = (
95
                None, None, None, None, None, None, None, None )
96
        self.waiting = False
97
        self.readbuf = ''
98

    
99
        # Start command interpreter shell
100
        self.startShell()
101
        self.mountPrivateDirs()
102

    
103
    # File descriptor to node mapping support
104
    # Class variables and methods
105

    
106
    inToNode = {}  # mapping of input fds to nodes
107
    outToNode = {}  # mapping of output fds to nodes
108

    
109
    @classmethod
110
    def fdToNode( cls, fd ):
111
        """Return node corresponding to given file descriptor.
112
           fd: file descriptor
113
           returns: node"""
114
        node = cls.outToNode.get( fd )
115
        return node or cls.inToNode.get( fd )
116

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

    
163
    def mountPrivateDirs( self ):
164
        "mount private directories"
165
        for directory in self.privateDirs:
166
            if isinstance( directory, tuple ):
167
                # mount given private directory
168
                privateDir = directory[ 1 ] % self.__dict__
169
                mountPoint = directory[ 0 ]
170
                self.cmd( 'mkdir -p %s' % privateDir )
171
                self.cmd( 'mkdir -p %s' % mountPoint )
172
                self.cmd( 'mount --bind %s %s' %
173
                               ( privateDir, mountPoint ) )
174
            else:
175
                # mount temporary filesystem on directory
176
                self.cmd( 'mkdir -p %s' % directory )
177
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
178

    
179
    def unmountPrivateDirs( self ):
180
        "mount private directories"
181
        for directory in self.privateDirs:
182
            if isinstance( directory, tuple ):
183
                self.cmd( 'umount ', directory[ 0 ] )
184
            else:
185
                self.cmd( 'umount ', directory )
186

    
187
    def _popen( self, cmd, **params ):
188
        """Internal method: spawn and return a process
189
            cmd: command to run (list)
190
            params: parameters to Popen()"""
191
        # Leave this is as an instance method for now
192
        assert self
193
        return Popen( cmd, **params )
194

    
195
    def cleanup( self ):
196
        "Help python collect its garbage."
197
        # Intfs may end up in root NS
198
        for intfName in self.intfNames():
199
            if self.name in intfName:
200
                quietRun( 'ip link del ' + intfName )
201
        self.shell = None
202

    
203
    # Subshell I/O, commands and control
204

    
205
    def read( self, maxbytes=1024 ):
206
        """Buffered read from node, non-blocking.
207
           maxbytes: maximum number of bytes to return"""
208
        count = len( self.readbuf )
209
        if count < maxbytes:
210
            data = os.read( self.stdout.fileno(), maxbytes - count )
211
            self.readbuf += data
212
        if maxbytes >= len( self.readbuf ):
213
            result = self.readbuf
214
            self.readbuf = ''
215
        else:
216
            result = self.readbuf[ :maxbytes ]
217
            self.readbuf = self.readbuf[ maxbytes: ]
218
        return result
219

    
220
    def readline( self ):
221
        """Buffered readline from node, non-blocking.
222
           returns: line (minus newline) or None"""
223
        self.readbuf += self.read( 1024 )
224
        if '\n' not in self.readbuf:
225
            return None
226
        pos = self.readbuf.find( '\n' )
227
        line = self.readbuf[ 0: pos ]
228
        self.readbuf = self.readbuf[ pos + 1: ]
229
        return line
230

    
231
    def write( self, data ):
232
        """Write data to node.
233
           data: string"""
234
        os.write( self.stdin.fileno(), data )
235

    
236
    def terminate( self ):
237
        "Send kill signal to Node and clean up after it."
238
        self.unmountPrivateDirs()
239
        if self.shell:
240
            if self.shell.poll() is None:
241
                os.killpg( self.shell.pid, signal.SIGHUP )
242
        self.cleanup()
243

    
244
    def stop( self, deleteIntfs=False ):
245
        """Stop node.
246
           deleteIntfs: delete interfaces? (False)"""
247
        if deleteIntfs:
248
            self.deleteIntfs()
249
        self.terminate()
250

    
251
    def waitReadable( self, timeoutms=None ):
252
        """Wait until node's output is readable.
253
           timeoutms: timeout in ms or None to wait indefinitely."""
254
        if len( self.readbuf ) == 0:
255
            self.pollOut.poll( timeoutms )
256

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

    
287
    def sendInt( self, intr=chr( 3 ) ):
288
        "Interrupt running command."
289
        debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
290
        self.write( intr )
291

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

    
322
    def waitOutput( self, verbose=False, findPid=True ):
323
        """Wait for a command to complete.
324
           Completion is signaled by a sentinel character, ASCII(127)
325
           appearing in the output stream.  Wait for the sentinel and return
326
           the output, including trailing newline.
327
           verbose: print output interactively"""
328
        log = info if verbose else debug
329
        output = ''
330
        while self.waiting:
331
            data = self.monitor( findPid=findPid )
332
            output += data
333
            log( data )
334
        return output
335

    
336
    def cmd( self, *args, **kwargs ):
337
        """Send a command, wait for output, and return it.
338
           cmd: string"""
339
        verbose = kwargs.get( 'verbose', False )
340
        log = info if verbose else debug
341
        log( '*** %s : %s\n' % ( self.name, args ) )
342
        if self.shell:
343
            self.sendCmd( *args, **kwargs )
344
            return self.waitOutput( verbose )
345
        else:
346
            warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) )
347

    
348
    def cmdPrint( self, *args):
349
        """Call cmd and printing its output
350
           cmd: string"""
351
        return self.cmd( *args, **{ 'verbose': True } )
352

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

    
381
    def pexec( self, *args, **kwargs ):
382
        """Execute a command using popen
383
           returns: out, err, exitcode"""
384
        popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
385
                            **kwargs )
386
        # Warning: this can fail with large numbers of fds!
387
        out, err = popen.communicate()
388
        exitcode = popen.wait()
389
        return out, err, exitcode
390

    
391
    # Interface management, configuration, and routing
392

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

    
399
    def newPort( self ):
400
        "Return the next port number to allocate."
401
        if len( self.ports ) > 0:
402
            return max( self.ports.values() ) + 1
403
        return self.portBase
404

    
405
    def addIntf( self, intf, port=None, moveIntfFn=moveIntf ):
406
        """Add an interface.
407
           intf: interface
408
           port: port number (optional, typically OpenFlow port number)
409
           moveIntfFn: function to move interface (optional)"""
410
        if port is None:
411
            port = self.newPort()
412
        self.intfs[ port ] = intf
413
        self.ports[ intf ] = port
414
        self.nameToIntf[ intf.name ] = intf
415
        debug( '\n' )
416
        debug( 'added intf %s (%d) to node %s\n' % (
417
                intf, port, self.name ) )
418
        if self.inNamespace:
419
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
420
            moveIntfFn( intf.name, self  )
421

    
422
    def defaultIntf( self ):
423
        "Return interface for lowest port"
424
        ports = self.intfs.keys()
425
        if ports:
426
            return self.intfs[ min( ports ) ]
427
        else:
428
            warn( '*** defaultIntf: warning:', self.name,
429
                  'has no interfaces\n' )
430

    
431
    def intf( self, intf=None ):
432
        """Return our interface object with given string name,
433
           default intf if name is falsy (None, empty string, etc).
434
           or the input intf arg.
435

436
        Having this fcn return its arg for Intf objects makes it
437
        easier to construct functions with flexible input args for
438
        interfaces (those that accept both string names and Intf objects).
439
        """
440
        if not intf:
441
            return self.defaultIntf()
442
        elif isinstance( intf, basestring):
443
            return self.nameToIntf[ intf ]
444
        else:
445
            return intf
446

    
447
    def connectionsTo( self, node):
448
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
449
        # We could optimize this if it is important
450
        connections = []
451
        for intf in self.intfList():
452
            link = intf.link
453
            if link:
454
                node1, node2 = link.intf1.node, link.intf2.node
455
                if node1 == self and node2 == node:
456
                    connections += [ ( intf, link.intf2 ) ]
457
                elif node1 == node and node2 == self:
458
                    connections += [ ( intf, link.intf1 ) ]
459
        return connections
460

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

    
475
    # Routing support
476

    
477
    def setARP( self, ip, mac ):
478
        """Add an ARP entry.
479
           ip: IP address as string
480
           mac: MAC address as string"""
481
        result = self.cmd( 'arp', '-s', ip, mac )
482
        return result
483

    
484
    def setHostRoute( self, ip, intf ):
485
        """Add route to host.
486
           ip: IP address as dotted decimal
487
           intf: string, interface name"""
488
        return self.cmd( 'route add -host', ip, 'dev', intf )
489

    
490
    def setDefaultRoute( self, intf=None ):
491
        """Set the default route to go through intf.
492
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
493
        # Note setParam won't call us if intf is none
494
        if isinstance( intf, basestring ) and ' ' in intf:
495
            params = intf
496
        else:
497
            params = 'dev %s' % intf
498
        # Do this in one line in case we're messing with the root namespace
499
        self.cmd( 'ip route del default; ip route add default', params )
500

    
501
    # Convenience and configuration methods
502

    
503
    def setMAC( self, mac, intf=None ):
504
        """Set the MAC address for an interface.
505
           intf: intf or intf name
506
           mac: MAC address as string"""
507
        return self.intf( intf ).setMAC( mac )
508

    
509
    def setIP( self, ip, prefixLen=8, intf=None ):
510
        """Set the IP address for an interface.
511
           intf: intf or intf name
512
           ip: IP address as a string
513
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
514
        # This should probably be rethought
515
        if '/' not in ip:
516
            ip = '%s/%s' % ( ip, prefixLen )
517
        return self.intf( intf ).setIP( ip )
518

    
519
    def IP( self, intf=None ):
520
        "Return IP address of a node or specific interface."
521
        return self.intf( intf ).IP()
522

    
523
    def MAC( self, intf=None ):
524
        "Return MAC address of a node or specific interface."
525
        return self.intf( intf ).MAC()
526

    
527
    def intfIsUp( self, intf=None ):
528
        "Check if an interface is up."
529
        return self.intf( intf ).isUp()
530

    
531
    # The reason why we configure things in this way is so
532
    # That the parameters can be listed and documented in
533
    # the config method.
534
    # Dealing with subclasses and superclasses is slightly
535
    # annoying, but at least the information is there!
536

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

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

    
577
    def configDefault( self, **moreParams ):
578
        "Configure with default parameters"
579
        self.params.update( moreParams )
580
        self.config( **self.params )
581

    
582
    # This is here for backward compatibility
583
    def linkTo( self, node, link=Link ):
584
        """(Deprecated) Link to another node
585
           replace with Link( node1, node2)"""
586
        return link( self, node )
587

    
588
    # Other methods
589

    
590
    def intfList( self ):
591
        "List of our interfaces sorted by port number"
592
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
593

    
594
    def intfNames( self ):
595
        "The names of our interfaces sorted by port number"
596
        return [ str( i ) for i in self.intfList() ]
597

    
598
    def __repr__( self ):
599
        "More informative string representation"
600
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
601
                              for i in self.intfList() ] ) )
602
        return '<%s %s: %s pid=%s> ' % (
603
            self.__class__.__name__, self.name, intfs, self.pid )
604

    
605
    def __str__( self ):
606
        "Abbreviated string representation"
607
        return self.name
608

    
609
    # Automatic class setup support
610

    
611
    isSetup = False
612

    
613
    @classmethod
614
    def checkSetup( cls ):
615
        "Make sure our class and superclasses are set up"
616
        while cls and not getattr( cls, 'isSetup', True ):
617
            cls.setup()
618
            cls.isSetup = True
619
            # Make pylint happy
620
            cls = getattr( type( cls ), '__base__', None )
621

    
622
    @classmethod
623
    def setup( cls ):
624
        "Make sure our class dependencies are available"
625
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
626

    
627
class Host( Node ):
628
    "A host is simply a Node"
629
    pass
630

    
631
class CPULimitedHost( Host ):
632

    
633
    "CPU limited host"
634

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

    
656
    def cgroupSet( self, param, value, resource='cpu' ):
657
        "Set a cgroup parameter and return its value"
658
        cmd = 'cgset -r %s.%s=%s /%s' % (
659
            resource, param, value, self.name )
660
        quietRun( cmd )
661
        nvalue = int( self.cgroupGet( param, resource ) )
662
        if nvalue != value:
663
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
664
                   % ( param, nvalue, value ) )
665
        return nvalue
666

    
667
    def cgroupGet( self, param, resource='cpu' ):
668
        "Return value of cgroup parameter"
669
        cmd = 'cgget -r %s.%s /%s' % (
670
            resource, param, self.name )
671
        return int( quietRun( cmd ).split()[ -1 ] )
672

    
673
    def cgroupDel( self ):
674
        "Clean up our cgroup"
675
        # info( '*** deleting cgroup', self.cgroup, '\n' )
676
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
677
        return exitcode == 0  # success condition
678

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

    
696
    def cleanup( self ):
697
        "Clean up Node, then clean up our cgroup"
698
        super( CPULimitedHost, self ).cleanup()
699
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
700

    
701
    _rtGroupSched = False   # internal class var: Is CONFIG_RT_GROUP_SCHED set?
702

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

    
716
    def chrt( self ):
717
        "Set RT scheduling priority"
718
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
719
        result = quietRun( 'chrt -p %s' % self.pid )
720
        firstline = result.split( '\n' )[ 0 ]
721
        lastword = firstline.split( ' ' )[ -1 ]
722
        if lastword != 'SCHED_RR':
723
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
724
        return lastword
725

    
726
    def rtInfo( self, f ):
727
        "Internal method: return parameters for RT bandwidth"
728
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
729
        # RT uses wall clock time for period and quota
730
        quota = int( self.period_us * f )
731
        return pstr, qstr, self.period_us, quota
732

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

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

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

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

    
799
    def config( self, cpu=-1, cores=None, **params ):
800
        """cpu: desired overall system CPU fraction
801
           cores: (real) core(s) this host can run on
802
           params: parameters for Node.config()"""
803
        r = Node.config( self, **params )
804
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
805
        # that seems redundant
806
        self.setParam( r, 'setCPUFrac', cpu=cpu )
807
        self.setParam( r, 'setCPUs', cores=cores )
808
        return r
809

    
810
    inited = False
811

    
812
    @classmethod
813
    def init( cls ):
814
        "Initialization for CPULimitedHost class"
815
        mountCgroups()
816
        cls.inited = True
817

    
818

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

    
839
class Switch( Node ):
840
    """A Switch is a Node that is running (or has execed?)
841
       an OpenFlow switch."""
842

    
843
    portBase = 1  # Switches start with port 1 in OpenFlow
844
    dpidLen = 16  # digits in dpid passed to switch
845

    
846
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
847
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
848
           opts: additional switch options
849
           listenPort: port to listen on for dpctl connections"""
850
        Node.__init__( self, name, **params )
851
        self.dpid = self.defaultDpid( dpid )
852
        self.opts = opts
853
        self.listenPort = listenPort
854
        if not self.inNamespace:
855
            self.controlIntf = Intf( 'lo', self, port=0 )
856

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

    
874
    def defaultIntf( self ):
875
        "Return control interface"
876
        if self.controlIntf:
877
            return self.controlIntf
878
        else:
879
            return Node.defaultIntf( self )
880

    
881
    def sendCmd( self, *cmd, **kwargs ):
882
        """Send command to Node.
883
           cmd: string"""
884
        kwargs.setdefault( 'printPid', False )
885
        if not self.execed:
886
            return Node.sendCmd( self, *cmd, **kwargs )
887
        else:
888
            error( '*** Error: %s has execed and cannot accept commands' %
889
                   self.name )
890

    
891
    def connected( self ):
892
        "Is the switch connected to a controller? (override this method)"
893
        # Assume that we are connected by default to whatever we need to
894
        # be connected to. This should be overridden by any OpenFlow
895
        # switch, but not by a standalone bridge.
896
        debug( 'Assuming', repr( self ), 'is connected to a controller\n' )
897
        return True
898

    
899
    def __repr__( self ):
900
        "More informative string representation"
901
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
902
                              for i in self.intfList() ] ) )
903
        return '<%s %s: %s pid=%s> ' % (
904
            self.__class__.__name__, self.name, intfs, self.pid )
905

    
906

    
907
class UserSwitch( Switch ):
908
    "User-space switch."
909

    
910
    dpidLen = 12
911

    
912
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
913
        """Init.
914
           name: name for the switch
915
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
916
        Switch.__init__( self, name, **kwargs )
917
        pathCheck( 'ofdatapath', 'ofprotocol',
918
                   moduleName='the OpenFlow reference user switch' +
919
                              '(openflow.org)' )
920
        if self.listenPort:
921
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
922
        else:
923
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
924
        self.dpopts = dpopts
925

    
926
    @classmethod
927
    def setup( cls ):
928
        "Ensure any dependencies are loaded; if not, try to load them."
929
        if not os.path.exists( '/dev/net/tun' ):
930
            moduleDeps( add=TUN )
931

    
932
    def dpctl( self, *args ):
933
        "Run dpctl command"
934
        listenAddr = None
935
        if not self.listenPort:
936
            listenAddr = 'unix:/tmp/%s.listen' % self.name
937
        else:
938
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
939
        return self.cmd( 'dpctl ' + ' '.join( args ) +
940
                         ' ' + listenAddr )
941

    
942
    def connected( self ):
943
        "Is the switch connected to a controller?"
944
        status = self.dpctl( 'status' )
945
        return ( 'remote.is-connected=true' in status and
946
                 'local.is-connected=true' in status )
947

    
948
    @staticmethod
949
    def TCReapply( intf ):
950
        """Unfortunately user switch and Mininet are fighting
951
           over tc queuing disciplines. To resolve the conflict,
952
           we re-create the user switch's configuration, but as a
953
           leaf of the TCIntf-created configuration."""
954
        if isinstance( intf, TCIntf ):
955
            ifspeed = 10000000000  # 10 Gbps
956
            minspeed = ifspeed * 0.001
957

    
958
            res = intf.config( **intf.params )
959

    
960
            if res is None:  # link may not have TC parameters
961
                return
962

    
963
            # Re-add qdisc, root, and default classes user switch created, but
964
            # with new parent, as setup by Mininet's TCIntf
965
            parent = res['parent']
966
            intf.tc( "%s qdisc add dev %s " + parent +
967
                     " handle 1: htb default 0xfffe" )
968
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
969
                     + str(ifspeed) )
970
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
971
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
972

    
973
    def start( self, controllers ):
974
        """Start OpenFlow reference user datapath.
975
           Log to /tmp/sN-{ofd,ofp}.log.
976
           controllers: list of controller objects"""
977
        # Add controllers
978
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
979
                            for c in controllers ] )
980
        ofdlog = '/tmp/' + self.name + '-ofd.log'
981
        ofplog = '/tmp/' + self.name + '-ofp.log'
982
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
983
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
984
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
985
                  self.dpopts +
986
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
987
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
988
                  ' ' + clist +
989
                  ' --fail=closed ' + self.opts +
990
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
991
        if "no-slicing" not in self.dpopts:
992
            # Only TCReapply if slicing is enable
993
            sleep(1)  # Allow ofdatapath to start before re-arranging qdisc's
994
            for intf in self.intfList():
995
                if not intf.IP():
996
                    self.TCReapply( intf )
997

    
998
    def stop( self, deleteIntfs=True ):
999
        """Stop OpenFlow reference user datapath.
1000
           deleteIntfs: delete interfaces? (True)"""
1001
        self.cmd( 'kill %ofdatapath' )
1002
        self.cmd( 'kill %ofprotocol' )
1003
        super( UserSwitch, self ).stop( deleteIntfs )
1004

    
1005
class OVSLegacyKernelSwitch( Switch ):
1006
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
1007
       Currently only works in the root namespace."""
1008

    
1009
    def __init__( self, name, dp=None, **kwargs ):
1010
        """Init.
1011
           name: name for switch
1012
           dp: netlink id (0, 1, 2, ...)
1013
           defaultMAC: default MAC as unsigned int; random value if None"""
1014
        Switch.__init__( self, name, **kwargs )
1015
        self.dp = dp if dp else self.name
1016
        self.intf = self.dp
1017
        if self.inNamespace:
1018
            error( "OVSKernelSwitch currently only works"
1019
                   " in the root namespace.\n" )
1020
            exit( 1 )
1021

    
1022
    @classmethod
1023
    def setup( cls ):
1024
        "Ensure any dependencies are loaded; if not, try to load them."
1025
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
1026
                   moduleName='Open vSwitch (openvswitch.org)')
1027
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1028

    
1029
    def start( self, controllers ):
1030
        "Start up kernel datapath."
1031
        ofplog = '/tmp/' + self.name + '-ofp.log'
1032
        # Delete local datapath if it exists;
1033
        # then create a new one monitoring the given interfaces
1034
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
1035
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
1036
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
1037
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
1038
        # Run protocol daemon
1039
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1040
                            for c in controllers ] )
1041
        self.cmd( 'ovs-openflowd ' + self.dp +
1042
                  ' ' + clist +
1043
                  ' --fail=secure ' + self.opts +
1044
                  ' --datapath-id=' + self.dpid +
1045
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
1046
        self.execed = False
1047

    
1048
    def stop( self, deleteIntfs=True ):
1049
        """Terminate kernel datapath."
1050
           deleteIntfs: delete interfaces? (True)"""
1051
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
1052
        self.cmd( 'kill %ovs-openflowd' )
1053
        super( OVSLegacyKernelSwitch, self ).stop( deleteIntfs )
1054

    
1055

    
1056
class OVSSwitch( Switch ):
1057
    "Open vSwitch switch. Depends on ovs-vsctl."
1058

    
1059
    def __init__( self, name, failMode='secure', datapath='kernel',
1060
                  inband=False, protocols=None,
1061
                  reconnectms=1000, stp=False, **params ):
1062
        """name: name for switch
1063
           failMode: controller loss behavior (secure|open)
1064
           datapath: userspace or kernel mode (kernel|user)
1065
           inband: use in-band control (False)
1066
           protocols: use specific OpenFlow version(s) (e.g. OpenFlow13)
1067
                      Unspecified (or old OVS version) uses OVS default
1068
           reconnectms: max reconnect timeout in ms (0/None for default)
1069
           stp: enable STP (False, requires failMode=standalone)"""
1070
        Switch.__init__( self, name, **params )
1071
        self.failMode = failMode
1072
        self.datapath = datapath
1073
        self.inband = inband
1074
        self.protocols = protocols
1075
        self.reconnectms = reconnectms
1076
        self.stp = stp
1077
        self._uuids = []  # controller UUIDs
1078

    
1079
    @classmethod
1080
    def setup( cls ):
1081
        "Make sure Open vSwitch is installed and working"
1082
        pathCheck( 'ovs-vsctl',
1083
                   moduleName='Open vSwitch (openvswitch.org)')
1084
        # This should no longer be needed, and it breaks
1085
        # with OVS 1.7 which has renamed the kernel module:
1086
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1087
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1088
        if exitcode:
1089
            error( out + err +
1090
                   'ovs-vsctl exited with code %d\n' % exitcode +
1091
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1092
                   'Make sure that Open vSwitch is installed, '
1093
                   'that ovsdb-server is running, and that\n'
1094
                   '"ovs-vsctl show" works correctly.\n'
1095
                   'You may wish to try '
1096
                   '"service openvswitch-switch start".\n' )
1097
            exit( 1 )
1098
        version = quietRun( 'ovs-vsctl --version' )
1099
        cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
1100

    
1101
    @classmethod
1102
    def isOldOVS( cls ):
1103
        "Is OVS ersion < 1.10?"
1104
        return ( StrictVersion( cls.OVSVersion ) <
1105
                 StrictVersion( '1.10' ) )
1106

    
1107
    @classmethod
1108
    def batchShutdown( cls, switches ):
1109
        "Shut down a list of OVS switches"
1110
        delcmd = 'del-br %s'
1111
        if not cls.isOldOVS():
1112
            delcmd = '--if-exists ' + delcmd
1113
        # First, delete them all from ovsdb
1114
        quietRun( 'ovs-vsctl ' +
1115
                  ' -- '.join( delcmd % s for s in switches ) )
1116
        # Next, shut down all of the processes
1117
        pids = ' '.join( str( switch.pid ) for switch in switches )
1118
        quietRun( 'kill -HUP ' + pids )
1119
        for switch in switches:
1120
            switch.shell = None
1121
        return True
1122

    
1123
    def dpctl( self, *args ):
1124
        "Run ovs-ofctl command"
1125
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1126

    
1127
    def vsctl( self, *args, **kwargs ):
1128
        "Run ovs-vsctl command"
1129
        return self.cmd( 'ovs-vsctl', *args, **kwargs )
1130

    
1131
    @staticmethod
1132
    def TCReapply( intf ):
1133
        """Unfortunately OVS and Mininet are fighting
1134
           over tc queuing disciplines. As a quick hack/
1135
           workaround, we clear OVS's and reapply our own."""
1136
        if isinstance( intf, TCIntf ):
1137
            intf.config( **intf.params )
1138

    
1139
    def attach( self, intf ):
1140
        "Connect a data port"
1141
        self.vsctl( 'add-port', self, intf )
1142
        self.cmd( 'ifconfig', intf, 'up' )
1143
        self.TCReapply( intf )
1144

    
1145
    def detach( self, intf ):
1146
        "Disconnect a data port"
1147
        self.vsctl( 'del-port', self, intf )
1148

    
1149
    def controllerUUIDs( self, update=False ):
1150
        """Return ovsdb UUIDs for our controllers
1151
           update: update cached value"""
1152
        if not self._uuids or update:
1153
            controllers = self.vsctl( '-- get Bridge', self,
1154
                                    'Controller' ).strip()
1155
            if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1156
                controllers = controllers[ 1 : -1 ]
1157
                if controllers:
1158
                    self._uuids = [ c.strip()
1159
                                    for c in controllers.split( ',' ) ]
1160
        return self._uuids
1161

    
1162
    def connected( self ):
1163
        "Are we connected to at least one of our controllers?"
1164
        for uuid in self.controllerUUIDs():
1165
            if 'true' in self.vsctl( '-- get Controller',
1166
                                     uuid, 'is_connected' ):
1167
                return True
1168
        return self.failMode == 'standalone'
1169

    
1170
    def intfOpts( self, intf ):
1171
        "Return OVS interface options for intf"
1172
        opts = ''
1173
        if not self.isOldOVS():
1174
            # ofport_request is not supported
1175
            opts += ' ofport_request=%s' % self.ports[ intf ]
1176
            # Patch ports don't work well with old OVS
1177
            if isinstance( intf, OVSIntf ):
1178
                intf1, intf2 = intf.link.intf1, intf.link.intf2
1179
                peer = intf1 if intf1 != intf else intf2
1180
                opts += ' type=patch options:peer=%s' % peer
1181
        return '' if not opts else ' -- set Interface %s' % intf + opts
1182

    
1183
    def bridgeOpts( self ):
1184
        "Return OVS bridge options"
1185
        opts = ''
1186
        if not self.inband:
1187
            opts += ' other-config:disable-in-band=true'
1188
        if self.datapath == 'user':
1189
            opts += ' datapath_type=netdev' % self
1190
        if self.protocols and not self.isOldOVS():
1191
            opts += ' protocols=%s' % ( self, self.protocols )
1192
        if self.stp and self.failMode == 'standalone':
1193
            opts += ' stp_enable=true' % self
1194
        return opts
1195

    
1196
    # pylint: disable=too-many-branches
1197
    def start( self, controllers ):
1198
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1199
        if self.inNamespace:
1200
            raise Exception(
1201
                'OVS kernel switch does not work in a namespace' )
1202
        int( self.dpid, 16 )  # DPID must be a hex string
1203
        # Interfaces and controllers
1204
        intfs = ''.join( ' -- add-port %s %s' % ( self, intf ) +
1205
                         self.intfOpts( intf )
1206
                         for intf in self.intfList()
1207
                         if self.ports[ intf ] and not intf.IP() )
1208
        # Construct big ovs-vsctl command
1209
        clist = [ ( self.name + c.name, '%s:%s:%d' %
1210
                  ( c.protocol, c.IP(), c.port ) )
1211
                  for c in controllers ]
1212
        if self.listenPort:
1213
            clist.append( ( self.name + '-listen',
1214
                            'ptcp:%s' % self.listenPort ) )
1215
        ccmd = '-- --id=@%s create Controller target=\\"%s\\"'
1216
        if self.reconnectms:
1217
            ccmd += ' max_backoff=%d' % self.reconnectms
1218
        cargs = ' '.join( ccmd % ( name, target )
1219
                         for name, target in clist )
1220
        cids = ','.join( '@%s' % name for name, _target in clist )
1221
        if not self.isOldOVS():
1222
            cargs += ' -- --if-exists del-br %s' % self
1223
        cmd = ( cargs +
1224
                ' -- add-br %s' % self +
1225
                ' -- set bridge %s controller=[%s]' % ( self, cids  ) +
1226
                self.bridgeOpts() +
1227
                intfs )
1228
        # Do it!!
1229
        self.vsctl( cmd )
1230
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1231
        if self.isOldOVS() and self.reconnectms:
1232
            uuids = [ '-- set Controller %s max_backoff=%d' %
1233
                      ( uuid, self.reconnectms )
1234
                      for uuid in self.controllerUUIDs() ]
1235
            if uuids:
1236
                self.vsctl( *uuids )
1237
        # If necessary, restore TC config overwritten by OVS
1238
        for intf in self.intfList():
1239
            self.TCReapply( intf )
1240
    # pylint: enable=too-many-branches
1241

    
1242
    def stop( self, deleteIntfs=True ):
1243
        """Terminate OVS switch.
1244
           deleteIntfs: delete interfaces? (True)"""
1245
        self.cmd( 'ovs-vsctl del-br', self )
1246
        if self.datapath == 'user':
1247
            self.cmd( 'ip link del', self )
1248
        super( OVSSwitch, self ).stop( deleteIntfs )
1249

    
1250

    
1251
OVSKernelSwitch = OVSSwitch
1252

    
1253

    
1254
class OVSBridge( OVSSwitch ):
1255
    "OVSBridge is an OVSSwitch in standalone/bridge mode"
1256

    
1257
    def __init__( self, args, **kwargs ):
1258
        kwargs.update( failMode='standalone' )
1259
        OVSSwitch.__init__( self, args, **kwargs )
1260

    
1261
    def start( self, controllers ):
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 OVSBatch( OVSSwitch ):
1274
    "Experiment: batch startup of OVS switches"
1275

    
1276
    # This should be ~ int( quietRun( 'getconf ARG_MAX' ) ),
1277
    # but the real limit seems to be much lower
1278
    argmax = 128000
1279
    
1280
    def __init__( self, *args, **kwargs ):
1281
        self.commands = []
1282
        self.started = False
1283
        super( OVSBatch, self ).__init__( *args, **kwargs )
1284

    
1285
    @classmethod
1286
    def batchStartup( cls, switches ):
1287
        "Batch startup for OVS"
1288
        info( '...' )
1289
        cmds = 'ovs-vsctl '
1290
        for switch in switches:
1291
            if cls.isOldOVS():
1292
                quietRun( 'ovs-vsctl del-br %s' % switch )
1293
            for cmd in switch.commands:
1294
                cmd = cmd.strip()
1295
                # Don't exceed ARG_MAX
1296
                if len( cmds ) + len( cmd ) >= cls.argmax:
1297
                    errRun( cmds, shell=True )
1298
                    cmds = 'ovs-vsctl'
1299
                cmds += ' ' + cmd
1300
                switch.started = True
1301
        if cmds:
1302
            errRun( cmds, shell=True )
1303
        return True
1304

    
1305
    def vsctl( self, *args, **kwargs ):
1306
        "Append ovs-vsctl command to list for later execution"
1307
        if self.started:
1308
            return OVSSwitch.vsctl( self, *args, **kwargs )
1309
        cmd = ' '.join( str( arg ).strip() for arg in args )
1310
        self.commands.append( cmd )
1311

    
1312
    def start( self, *args, **kwargs ):
1313
        super( OVSBatch, self ).start( *args, **kwargs )
1314
        self.started = True
1315
 
1316
    def stop( self, *args, **kwargs ):
1317
        super( OVSBatch, self ).stop( *args, **kwargs )
1318
        self.started = False
1319
         
1320
    def cleanup( self):
1321
        "Don't bother to clean up"
1322
        return
1323

    
1324

    
1325
class IVSSwitch( Switch ):
1326
    "Indigo Virtual Switch"
1327

    
1328
    def __init__( self, name, verbose=False, **kwargs ):
1329
        Switch.__init__( self, name, **kwargs )
1330
        self.verbose = verbose
1331

    
1332
    @classmethod
1333
    def setup( cls ):
1334
        "Make sure IVS is installed"
1335
        pathCheck( 'ivs-ctl', 'ivs',
1336
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1337
        out, err, exitcode = errRun( 'ivs-ctl show' )
1338
        if exitcode:
1339
            error( out + err +
1340
                   'ivs-ctl exited with code %d\n' % exitcode +
1341
                   '*** The openvswitch kernel module might '
1342
                   'not be loaded. Try modprobe openvswitch.\n' )
1343
            exit( 1 )
1344

    
1345
    @classmethod
1346
    def batchShutdown( cls, switches ):
1347
        "Kill each IVS switch, to be waited on later in stop()"
1348
        for switch in switches:
1349
            switch.cmd( 'kill %ivs' )
1350

    
1351
    def start( self, controllers ):
1352
        "Start up a new IVS switch"
1353
        args = ['ivs']
1354
        args.extend( ['--name', self.name] )
1355
        args.extend( ['--dpid', self.dpid] )
1356
        if self.verbose:
1357
            args.extend( ['--verbose'] )
1358
        for intf in self.intfs.values():
1359
            if not intf.IP():
1360
                args.extend( ['-i', intf.name] )
1361
        for c in controllers:
1362
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1363
        if self.listenPort:
1364
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1365
        args.append( self.opts )
1366

    
1367
        logfile = '/tmp/ivs.%s.log' % self.name
1368

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

    
1371
    def stop( self, deleteIntfs=True ):
1372
        """Terminate IVS switch.
1373
           deleteIntfs: delete interfaces? (True)"""
1374
        self.cmd( 'kill %ivs' )
1375
        self.cmd( 'wait' )
1376
        super( IVSSwitch, self ).stop( deleteIntfs )
1377

    
1378
    def attach( self, intf ):
1379
        "Connect a data port"
1380
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1381

    
1382
    def detach( self, intf ):
1383
        "Disconnect a data port"
1384
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1385

    
1386
    def dpctl( self, *args ):
1387
        "Run dpctl command"
1388
        if not self.listenPort:
1389
            return "can't run dpctl without passive listening port"
1390
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1391
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1392

    
1393

    
1394
class Controller( Node ):
1395
    """A Controller is a Node that is running (or has execed?) an
1396
       OpenFlow controller."""
1397

    
1398
    def __init__( self, name, inNamespace=False, command='controller',
1399
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1400
                  port=6633, protocol='tcp', **params ):
1401
        self.command = command
1402
        self.cargs = cargs
1403
        self.cdir = cdir
1404
        self.ip = ip
1405
        self.port = port
1406
        self.protocol = protocol
1407
        Node.__init__( self, name, inNamespace=inNamespace,
1408
                       ip=ip, **params  )
1409
        self.checkListening()
1410

    
1411
    def checkListening( self ):
1412
        "Make sure no controllers are running on our port"
1413
        # Verify that Telnet is installed first:
1414
        out, _err, returnCode = errRun( "which telnet" )
1415
        if 'telnet' not in out or returnCode != 0:
1416
            raise Exception( "Error running telnet to check for listening "
1417
                             "controllers; please check that it is "
1418
                             "installed." )
1419
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1420
                              ( self.ip, self.port ) )
1421
        if 'Connected' in listening:
1422
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1423
            pstr = ':%d ' % self.port
1424
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1425
            raise Exception( "Please shut down the controller which is"
1426
                             " running on port %d:\n" % self.port +
1427
                             '\n'.join( clist ) )
1428

    
1429
    def start( self ):
1430
        """Start <controller> <args> on controller.
1431
           Log to /tmp/cN.log"""
1432
        pathCheck( self.command )
1433
        cout = '/tmp/' + self.name + '.log'
1434
        if self.cdir is not None:
1435
            self.cmd( 'cd ' + self.cdir )
1436
        self.cmd( self.command + ' ' + self.cargs % self.port +
1437
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1438
        self.execed = False
1439

    
1440
    def stop( self, *args, **kwargs ):
1441
        "Stop controller."
1442
        self.cmd( 'kill %' + self.command )
1443
        self.cmd( 'wait %' + self.command )
1444
        kwargs.update( deleteIntfs=False )
1445
        super( Controller, self ).stop( *args, **kwargs )
1446

    
1447
    def IP( self, intf=None ):
1448
        "Return IP address of the Controller"
1449
        if self.intfs:
1450
            ip = Node.IP( self, intf )
1451
        else:
1452
            ip = self.ip
1453
        return ip
1454

    
1455
    def __repr__( self ):
1456
        "More informative string representation"
1457
        return '<%s %s: %s:%s pid=%s> ' % (
1458
            self.__class__.__name__, self.name,
1459
            self.IP(), self.port, self.pid )
1460

    
1461
    @classmethod
1462
    def isAvailable( cls ):
1463
        "Is controller available?"
1464
        return quietRun( 'which controller' )
1465

    
1466

    
1467
class OVSController( Controller ):
1468
    "Open vSwitch controller"
1469
    def __init__( self, name, command='ovs-controller', **kwargs ):
1470
        if quietRun( 'which test-controller' ):
1471
            command = 'test-controller'
1472
        Controller.__init__( self, name, command=command, **kwargs )
1473

    
1474
    @classmethod
1475
    def isAvailable( cls ):
1476
        return ( quietRun( 'which ovs-controller' ) or
1477
                 quietRun( 'which test-controller' ) )
1478

    
1479
class NOX( Controller ):
1480
    "Controller to run a NOX application."
1481

    
1482
    def __init__( self, name, *noxArgs, **kwargs ):
1483
        """Init.
1484
           name: name to give controller
1485
           noxArgs: arguments (strings) to pass to NOX"""
1486
        if not noxArgs:
1487
            warn( 'warning: no NOX modules specified; '
1488
                  'running packetdump only\n' )
1489
            noxArgs = [ 'packetdump' ]
1490
        elif type( noxArgs ) not in ( list, tuple ):
1491
            noxArgs = [ noxArgs ]
1492

    
1493
        if 'NOX_CORE_DIR' not in os.environ:
1494
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1495
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1496

    
1497
        Controller.__init__( self, name,
1498
                             command=noxCoreDir + '/nox_core',
1499
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1500
                             ' '.join( noxArgs ),
1501
                             cdir=noxCoreDir,
1502
                             **kwargs )
1503

    
1504
class RYU( Controller ):
1505
    "Controller to run Ryu application"
1506
    def __init__( self, name, *ryuArgs, **kwargs ):
1507
        """Init.
1508
        name: name to give controller.
1509
        ryuArgs: arguments and modules to pass to Ryu"""
1510
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1511
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1512
        if not ryuArgs:
1513
            warn( 'warning: no Ryu modules specified; '
1514
                  'running simple_switch only\n' )
1515
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1516
        elif type( ryuArgs ) not in ( list, tuple ):
1517
            ryuArgs = [ ryuArgs ]
1518

    
1519
        Controller.__init__( self, name,
1520
                             command='ryu-manager',
1521
                             cargs='--ofp-tcp-listen-port %s ' +
1522
                             ' '.join( ryuArgs ),
1523
                             cdir=ryuCoreDir,
1524
                             **kwargs )
1525

    
1526
class RemoteController( Controller ):
1527
    "Controller running outside of Mininet's control."
1528

    
1529
    def __init__( self, name, ip='127.0.0.1',
1530
                  port=6633, **kwargs):
1531
        """Init.
1532
           name: name to give controller
1533
           ip: the IP address where the remote controller is
1534
           listening
1535
           port: the port where the remote controller is listening"""
1536
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1537

    
1538
    def start( self ):
1539
        "Overridden to do nothing."
1540
        return
1541

    
1542
    def stop( self ):
1543
        "Overridden to do nothing."
1544
        return
1545

    
1546
    def checkListening( self ):
1547
        "Warn if remote controller is not accessible"
1548
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1549
                              ( self.ip, self.port ) )
1550
        if 'Connected' not in listening:
1551
            warn( "Unable to contact the remote controller"
1552
                  " at %s:%d\n" % ( self.ip, self.port ) )
1553

    
1554

    
1555
DefaultControllers = ( Controller, OVSController )
1556

    
1557
def findController( controllers=DefaultControllers ):
1558
    "Return first available controller from list, if any"
1559
    for controller in controllers:
1560
        if controller.isAvailable():
1561
            return controller
1562

    
1563
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1564
    "Find a controller that is available and instantiate it"
1565
    controller = findController( controllers )
1566
    if not controller:
1567
        raise Exception( 'Could not find a default OpenFlow controller' )
1568
    return controller( name, **kwargs )