Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ b93cc989

History | View | Annotate | Download (57.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
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 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
        self.sendCmd( *args, **kwargs )
343
        return self.waitOutput( verbose )
344

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

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

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

    
388
    # Interface management, configuration, and routing
389

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

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

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

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

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

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

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

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

    
472
    # Routing support
473

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

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

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

    
498
    # Convenience and configuration methods
499

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

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

    
516
    def IP( self, intf=None ):
517
        "Return IP address of a node or specific interface."
518
        return self.intf( intf ).IP()
519

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

    
524
    def intfIsUp( self, intf=None ):
525
        "Check if an interface is up."
526
        return self.intf( intf ).isUp()
527

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

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

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

    
574
    def configDefault( self, **moreParams ):
575
        "Configure with default parameters"
576
        self.params.update( moreParams )
577
        self.config( **self.params )
578

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

    
585
    # Other methods
586

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

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

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

    
602
    def __str__( self ):
603
        "Abbreviated string representation"
604
        return self.name
605

    
606
    # Automatic class setup support
607

    
608
    isSetup = False
609

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

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

    
624
class Host( Node ):
625
    "A host is simply a Node"
626
    pass
627

    
628
class CPULimitedHost( Host ):
629

    
630
    "CPU limited host"
631

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

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

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

    
670
    def cgroupDel( self ):
671
        "Clean up our cgroup"
672
        # info( '*** deleting cgroup', self.cgroup, '\n' )
673
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
674
        return exitcode != 0
675

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

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

    
698
    _rtGroupSched = False   # internal class var: Is CONFIG_RT_GROUP_SCHED set?
699

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

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

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

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

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

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

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

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

    
807
    inited = False
808

    
809
    @classmethod
810
    def init( cls ):
811
        "Initialization for CPULimitedHost class"
812
        mountCgroups()
813
        cls.inited = True
814

    
815

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

    
836
class Switch( Node ):
837
    """A Switch is a Node that is running (or has execed?)
838
       an OpenFlow switch."""
839

    
840
    portBase = 1  # Switches start with port 1 in OpenFlow
841
    dpidLen = 16  # digits in dpid passed to switch
842

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

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

    
871
    def defaultIntf( self ):
872
        "Return control interface"
873
        if self.controlIntf:
874
            return self.controlIntf
875
        else:
876
            return Node.defaultIntf( self )
877

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

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

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

    
903

    
904
class UserSwitch( Switch ):
905
    "User-space switch."
906

    
907
    dpidLen = 12
908

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

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

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

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

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

    
955
            res = intf.config( **intf.params )
956

    
957
            if res is None:  # link may not have TC parameters
958
                return
959

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

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

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

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

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

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

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

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

    
1052

    
1053
class OVSSwitch( Switch ):
1054
    "Open vSwitch switch. Depends on ovs-vsctl."
1055

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

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

    
1098
    @classmethod
1099
    def isOldOVS( cls ):
1100
        "Is OVS ersion < 1.10?"
1101
        return ( StrictVersion( cls.OVSVersion ) <
1102
                 StrictVersion( '1.10' ) )
1103

    
1104
    @classmethod
1105
    def batchShutdown( cls, switches ):
1106
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1107
        quietRun( 'ovs-vsctl ' +
1108
                  ' -- '.join( '--if-exists del-br %s' % s
1109
                               for s in switches ) )
1110
        return True
1111

    
1112
    def dpctl( self, *args ):
1113
        "Run ovs-ofctl command"
1114
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1115

    
1116
    @staticmethod
1117
    def TCReapply( intf ):
1118
        """Unfortunately OVS and Mininet are fighting
1119
           over tc queuing disciplines. As a quick hack/
1120
           workaround, we clear OVS's and reapply our own."""
1121
        if isinstance( intf, TCIntf ):
1122
            intf.config( **intf.params )
1123

    
1124
    def attach( self, intf ):
1125
        "Connect a data port"
1126
        self.cmd( 'ovs-vsctl add-port', self, intf )
1127
        self.cmd( 'ifconfig', intf, 'up' )
1128
        self.TCReapply( intf )
1129

    
1130
    def detach( self, intf ):
1131
        "Disconnect a data port"
1132
        self.cmd( 'ovs-vsctl del-port', self, intf )
1133

    
1134
    def controllerUUIDs( self, update=False ):
1135
        """Return ovsdb UUIDs for our controllers
1136
           update: update cached value"""
1137
        if not self._uuids or update:
1138
            controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1139
                                    'Controller' ).strip()
1140
            if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1141
                controllers = controllers[ 1 : -1 ]
1142
                if controllers:
1143
                    self._uuids = [ c.strip()
1144
                                    for c in controllers.split( ',' ) ]
1145
        return self._uuids
1146

    
1147
    def connected( self ):
1148
        "Are we connected to at least one of our controllers?"
1149
        for uuid in self.controllerUUIDs():
1150
            if 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1151
                                            uuid, 'is_connected' ):
1152
                return True
1153
        return self.failMode == 'standalone'
1154

    
1155
    @staticmethod
1156
    def patchOpts( intf ):
1157
        "Return OVS patch port options (if any) for intf"
1158
        if not isinstance( intf, OVSIntf ):
1159
            # Ignore if it's not a patch link
1160
            return ''
1161
        intf1, intf2 = intf.link.intf1, intf.link.intf2
1162
        peer = intf1 if intf1 != intf else intf2
1163
        return ( '-- set Interface %s type=patch '
1164
                 '-- set Interface %s options:peer=%s ' %
1165
                 ( intf, intf, peer ) )
1166

    
1167
    # pylint: disable=too-many-branches
1168
    def start( self, controllers ):
1169
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1170
        if self.inNamespace:
1171
            raise Exception(
1172
                'OVS kernel switch does not work in a namespace' )
1173
        int( self.dpid, 16 )  # DPID must be a hex string
1174
        # Interfaces and controllers
1175
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
1176
                          '-- set Interface %s ' % intf +
1177
                          'ofport_request=%s ' % self.ports[ intf ]
1178
                          + self.patchOpts( intf )
1179
                          for intf in self.intfList()
1180
                          if self.ports[ intf ] and not intf.IP() )
1181
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1182
                          for c in controllers )
1183
        if self.listenPort:
1184
            clist += ' ptcp:%s' % self.listenPort
1185
        # Construct big ovs-vsctl command for new versions of OVS
1186
        if not self.isOldOVS():
1187
            cmd = ( 'ovs-vsctl --if-exists del-br %s ' % self +
1188
                    '-- add-br %s ' % self +
1189
                    '-- set Bridge %s ' % self +
1190
                    'other_config:datapath-id=%s ' % self.dpid +
1191
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1192
                    intfs +
1193
                    '-- set-controller %s %s ' % ( self, clist ) )
1194
        # Construct ovs-vsctl commands for old versions of OVS
1195
        else:
1196
            # Annoyingly, --if-exists option seems not to work
1197
            self.cmd( 'ovs-vsctl del-br', self )
1198
            self.cmd( 'ovs-vsctl add-br', self )
1199
            for intf in self.intfList():
1200
                if not intf.IP():
1201
                    self.cmd( 'ovs-vsctl add-port', self, intf )
1202
            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
1203
                    'other_config:datapath-id=%s ' % self.dpid +
1204
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1205
                    '-- set-controller %s %s ' % ( self, clist ) )
1206
        if not self.inband:
1207
            cmd += ( '-- set bridge %s '
1208
                     'other-config:disable-in-band=true ' % self )
1209
        if self.datapath == 'user':
1210
            cmd += '-- set bridge %s datapath_type=netdev ' % self
1211
        if self.protocols and not self.isOldOVS():
1212
            cmd += '-- set bridge %s protocols=%s ' % ( self, self.protocols )
1213
        if self.stp and self.failMode == 'standalone':
1214
            cmd += '-- set bridge %s stp_enable=true ' % self
1215
        # Do it!!
1216
        self.cmd( cmd )
1217
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1218
        if self.reconnectms:
1219
            uuids = [ '-- set Controller %s max_backoff=%d' %
1220
                      ( uuid, self.reconnectms )
1221
                      for uuid in self.controllerUUIDs() ]
1222
            if uuids:
1223
                self.cmd( 'ovs-vsctl', *uuids )
1224
        # If necessary, restore TC config overwritten by OVS
1225
        for intf in self.intfList():
1226
            self.TCReapply( intf )
1227
    # pylint: enable=too-many-branches
1228

    
1229
    def stop( self, deleteIntfs=True ):
1230
        """Terminate OVS switch.
1231
           deleteIntfs: delete interfaces? (True)"""
1232
        self.cmd( 'ovs-vsctl del-br', self )
1233
        if self.datapath == 'user':
1234
            self.cmd( 'ip link del', self )
1235
        super( OVSSwitch, self ).stop( deleteIntfs )
1236

    
1237

    
1238
OVSKernelSwitch = OVSSwitch
1239

    
1240

    
1241
class OVSBridge( OVSSwitch ):
1242
    "OVSBridge is an OVSSwitch in standalone/bridge mode"
1243

    
1244
    def __init__( self, args, **kwargs ):
1245
        kwargs.update( failMode='standalone' )
1246
        OVSSwitch.__init__( self, args, **kwargs )
1247

    
1248
    def start( self, controllers ):
1249
        OVSSwitch.start( self, controllers=[] )
1250

    
1251
    def connected( self ):
1252
        "Are we forwarding yet?"
1253
        if self.stp:
1254
            status = self.dpctl( 'show' )
1255
            return 'STP_FORWARD' in status and not 'STP_LEARN' in status
1256
        else:
1257
            return True
1258

    
1259

    
1260
class IVSSwitch( Switch ):
1261
    "Indigo Virtual Switch"
1262

    
1263
    def __init__( self, name, verbose=False, **kwargs ):
1264
        Switch.__init__( self, name, **kwargs )
1265
        self.verbose = verbose
1266

    
1267
    @classmethod
1268
    def setup( cls ):
1269
        "Make sure IVS is installed"
1270
        pathCheck( 'ivs-ctl', 'ivs',
1271
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1272
        out, err, exitcode = errRun( 'ivs-ctl show' )
1273
        if exitcode:
1274
            error( out + err +
1275
                   'ivs-ctl exited with code %d\n' % exitcode +
1276
                   '*** The openvswitch kernel module might '
1277
                   'not be loaded. Try modprobe openvswitch.\n' )
1278
            exit( 1 )
1279

    
1280
    @classmethod
1281
    def batchShutdown( cls, switches ):
1282
        "Kill each IVS switch, to be waited on later in stop()"
1283
        for switch in switches:
1284
            switch.cmd( 'kill %ivs' )
1285

    
1286
    def start( self, controllers ):
1287
        "Start up a new IVS switch"
1288
        args = ['ivs']
1289
        args.extend( ['--name', self.name] )
1290
        args.extend( ['--dpid', self.dpid] )
1291
        if self.verbose:
1292
            args.extend( ['--verbose'] )
1293
        for intf in self.intfs.values():
1294
            if not intf.IP():
1295
                args.extend( ['-i', intf.name] )
1296
        for c in controllers:
1297
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1298
        if self.listenPort:
1299
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1300
        args.append( self.opts )
1301

    
1302
        logfile = '/tmp/ivs.%s.log' % self.name
1303

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

    
1306
    def stop( self, deleteIntfs=True ):
1307
        """Terminate IVS switch.
1308
           deleteIntfs: delete interfaces? (True)"""
1309
        self.cmd( 'kill %ivs' )
1310
        self.cmd( 'wait' )
1311
        super( IVSSwitch, self ).stop( deleteIntfs )
1312

    
1313
    def attach( self, intf ):
1314
        "Connect a data port"
1315
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1316

    
1317
    def detach( self, intf ):
1318
        "Disconnect a data port"
1319
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1320

    
1321
    def dpctl( self, *args ):
1322
        "Run dpctl command"
1323
        if not self.listenPort:
1324
            return "can't run dpctl without passive listening port"
1325
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1326
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1327

    
1328

    
1329
class Controller( Node ):
1330
    """A Controller is a Node that is running (or has execed?) an
1331
       OpenFlow controller."""
1332

    
1333
    def __init__( self, name, inNamespace=False, command='controller',
1334
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1335
                  port=6633, protocol='tcp', **params ):
1336
        self.command = command
1337
        self.cargs = cargs
1338
        self.cdir = cdir
1339
        self.ip = ip
1340
        self.port = port
1341
        self.protocol = protocol
1342
        Node.__init__( self, name, inNamespace=inNamespace,
1343
                       ip=ip, **params  )
1344
        self.checkListening()
1345

    
1346
    def checkListening( self ):
1347
        "Make sure no controllers are running on our port"
1348
        # Verify that Telnet is installed first:
1349
        out, _err, returnCode = errRun( "which telnet" )
1350
        if 'telnet' not in out or returnCode != 0:
1351
            raise Exception( "Error running telnet to check for listening "
1352
                             "controllers; please check that it is "
1353
                             "installed." )
1354
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1355
                              ( self.ip, self.port ) )
1356
        if 'Connected' in listening:
1357
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1358
            pstr = ':%d ' % self.port
1359
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1360
            raise Exception( "Please shut down the controller which is"
1361
                             " running on port %d:\n" % self.port +
1362
                             '\n'.join( clist ) )
1363

    
1364
    def start( self ):
1365
        """Start <controller> <args> on controller.
1366
           Log to /tmp/cN.log"""
1367
        pathCheck( self.command )
1368
        cout = '/tmp/' + self.name + '.log'
1369
        if self.cdir is not None:
1370
            self.cmd( 'cd ' + self.cdir )
1371
        self.cmd( self.command + ' ' + self.cargs % self.port +
1372
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1373
        self.execed = False
1374

    
1375
    def stop( self, *args, **kwargs ):
1376
        "Stop controller."
1377
        self.cmd( 'kill %' + self.command )
1378
        self.cmd( 'wait %' + self.command )
1379
        super( Controller, self ).stop( *args, **kwargs )
1380

    
1381
    def IP( self, intf=None ):
1382
        "Return IP address of the Controller"
1383
        if self.intfs:
1384
            ip = Node.IP( self, intf )
1385
        else:
1386
            ip = self.ip
1387
        return ip
1388

    
1389
    def __repr__( self ):
1390
        "More informative string representation"
1391
        return '<%s %s: %s:%s pid=%s> ' % (
1392
            self.__class__.__name__, self.name,
1393
            self.IP(), self.port, self.pid )
1394

    
1395
    @classmethod
1396
    def isAvailable( cls ):
1397
        "Is controller available?"
1398
        return quietRun( 'which controller' )
1399

    
1400

    
1401
class OVSController( Controller ):
1402
    "Open vSwitch controller"
1403
    def __init__( self, name, command='ovs-controller', **kwargs ):
1404
        if quietRun( 'which test-controller' ):
1405
            command = 'test-controller'
1406
        Controller.__init__( self, name, command=command, **kwargs )
1407

    
1408
    @classmethod
1409
    def isAvailable( cls ):
1410
        return ( quietRun( 'which ovs-controller' ) or
1411
                 quietRun( 'which test-controller' ) )
1412

    
1413
class NOX( Controller ):
1414
    "Controller to run a NOX application."
1415

    
1416
    def __init__( self, name, *noxArgs, **kwargs ):
1417
        """Init.
1418
           name: name to give controller
1419
           noxArgs: arguments (strings) to pass to NOX"""
1420
        if not noxArgs:
1421
            warn( 'warning: no NOX modules specified; '
1422
                  'running packetdump only\n' )
1423
            noxArgs = [ 'packetdump' ]
1424
        elif type( noxArgs ) not in ( list, tuple ):
1425
            noxArgs = [ noxArgs ]
1426

    
1427
        if 'NOX_CORE_DIR' not in os.environ:
1428
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1429
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1430

    
1431
        Controller.__init__( self, name,
1432
                             command=noxCoreDir + '/nox_core',
1433
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1434
                             ' '.join( noxArgs ),
1435
                             cdir=noxCoreDir,
1436
                             **kwargs )
1437

    
1438
class RYU( Controller ):
1439
    "Controller to run Ryu application"
1440
    def __init__( self, name, *ryuArgs, **kwargs ):
1441
        """Init.
1442
        name: name to give controller.
1443
        ryuArgs: arguments and modules to pass to Ryu"""
1444
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1445
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1446
        if not ryuArgs:
1447
            warn( 'warning: no Ryu modules specified; '
1448
                  'running simple_switch only\n' )
1449
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1450
        elif type( ryuArgs ) not in ( list, tuple ):
1451
            ryuArgs = [ ryuArgs ]
1452

    
1453
        Controller.__init__( self, name,
1454
                             command='ryu-manager',
1455
                             cargs='--ofp-tcp-listen-port %s ' +
1456
                             ' '.join( ryuArgs ),
1457
                             cdir=ryuCoreDir,
1458
                             **kwargs )
1459

    
1460
class RemoteController( Controller ):
1461
    "Controller running outside of Mininet's control."
1462

    
1463
    def __init__( self, name, ip='127.0.0.1',
1464
                  port=6633, **kwargs):
1465
        """Init.
1466
           name: name to give controller
1467
           ip: the IP address where the remote controller is
1468
           listening
1469
           port: the port where the remote controller is listening"""
1470
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1471

    
1472
    def start( self ):
1473
        "Overridden to do nothing."
1474
        return
1475

    
1476
    def stop( self ):
1477
        "Overridden to do nothing."
1478
        return
1479

    
1480
    def checkListening( self ):
1481
        "Warn if remote controller is not accessible"
1482
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1483
                              ( self.ip, self.port ) )
1484
        if 'Connected' not in listening:
1485
            warn( "Unable to contact the remote controller"
1486
                  " at %s:%d\n" % ( self.ip, self.port ) )
1487

    
1488

    
1489
DefaultControllers = ( Controller, OVSController )
1490

    
1491
def findController( controllers=DefaultControllers ):
1492
    "Return first available controller from list, if any"
1493
    for controller in controllers:
1494
        if controller.isAvailable():
1495
            return controller
1496

    
1497
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1498
    "Find a controller that is available and instantiate it"
1499
    controller = findController( controllers )
1500
    if not controller:
1501
        raise Exception( 'Could not find a default OpenFlow controller' )
1502
    return controller( name, **kwargs )