Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 554abdd5

History | View | Annotate | Download (55.8 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 operator import or_
56
from time import sleep
57

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
204
    # Subshell I/O, commands and control
205

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
389
    # Interface management, configuration, and routing
390

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

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

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

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

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

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

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

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

    
473
    # Routing support
474

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

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

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

    
499
    # Convenience and configuration methods
500

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

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

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

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

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

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

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

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

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

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

    
586
    # Other methods
587

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

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

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

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

    
607
    # Automatic class setup support
608

    
609
    isSetup = False
610

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

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

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

    
629
class CPULimitedHost( Host ):
630

    
631
    "CPU limited host"
632

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
808
    inited = False
809

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

    
816

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

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

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

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

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

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

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

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

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

    
904

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

    
908
    dpidLen = 12
909

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1053

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

    
1057
    def __init__( self, name, failMode='secure', datapath='kernel',
1058
                 inband=False, protocols=None, **params ):
1059
        """Init.
1060
           name: name for switch
1061
           failMode: controller loss behavior (secure|open)
1062
           datapath: userspace or kernel mode (kernel|user)
1063
           inband: use in-band control (False)"""
1064
        Switch.__init__( self, name, **params )
1065
        self.failMode = failMode
1066
        self.datapath = datapath
1067
        self.inband = inband
1068
        self.protocols = protocols
1069

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

    
1092
    @classmethod
1093
    def isOldOVS( cls ):
1094
        "Is OVS ersion < 1.10?"
1095
        return ( StrictVersion( cls.OVSVersion ) <
1096
             StrictVersion( '1.10' ) )
1097

    
1098
    @classmethod
1099
    def batchShutdown( cls, switches ):
1100
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1101
        quietRun( 'ovs-vsctl ' +
1102
                  ' -- '.join( '--if-exists del-br %s' % s
1103
                               for s in switches ) )
1104

    
1105
    def dpctl( self, *args ):
1106
        "Run ovs-ofctl command"
1107
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1108

    
1109
    @staticmethod
1110
    def TCReapply( intf ):
1111
        """Unfortunately OVS and Mininet are fighting
1112
           over tc queuing disciplines. As a quick hack/
1113
           workaround, we clear OVS's and reapply our own."""
1114
        if isinstance( intf, TCIntf ):
1115
            intf.config( **intf.params )
1116

    
1117
    def attach( self, intf ):
1118
        "Connect a data port"
1119
        self.cmd( 'ovs-vsctl add-port', self, intf )
1120
        self.cmd( 'ifconfig', intf, 'up' )
1121
        self.TCReapply( intf )
1122

    
1123
    def detach( self, intf ):
1124
        "Disconnect a data port"
1125
        self.cmd( 'ovs-vsctl del-port', self, intf )
1126

    
1127
    def controllerUUIDs( self ):
1128
        "Return ovsdb UUIDs for our controllers"
1129
        uuids = []
1130
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1131
                               'Controller' ).strip()
1132
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1133
            controllers = controllers[ 1 : -1 ]
1134
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1135
        return uuids
1136

    
1137
    def connected( self ):
1138
        "Are we connected to at least one of our controllers?"
1139
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1140
                                         uuid, 'is_connected' )
1141
                    for uuid in self.controllerUUIDs() ]
1142
        return reduce( or_, results, False )
1143

    
1144
    def start( self, controllers ):
1145
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1146
        if self.inNamespace:
1147
            raise Exception(
1148
                'OVS kernel switch does not work in a namespace' )
1149
        # Annoyingly, --if-exists option seems not to work
1150
        self.cmd( 'ovs-vsctl del-br', self )
1151
        int( self.dpid, 16 ) # DPID must be a hex string
1152
        # Interfaces and controllers
1153
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
1154
                          '-- set Interface %s ' % intf +
1155
                          'ofport_request=%s ' % self.ports[ intf ]
1156
                         for intf in self.intfList()
1157
                         if self.ports[ intf ] and not intf.IP() )
1158
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1159
                         for c in controllers )
1160
        if self.listenPort:
1161
            clist += ' ptcp:%s' % self.listenPort
1162
        # Construct big ovs-vsctl command for new versions of OVS
1163
        if not self.isOldOVS():
1164
            cmd = ( 'ovs-vsctl add-br %s ' % self +
1165
                    '-- set Bridge %s ' % self +
1166
                    'other_config:datapath-id=%s ' % self.dpid +
1167
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1168
                    intfs +
1169
                    '-- set-controller %s %s ' % ( self, clist ) )
1170
        # Construct ovs-vsctl commands for old versions of OVS
1171
        else:
1172
            self.cmd( 'ovs-vsctl add-br', self )
1173
            for intf in self.intfList():
1174
                if not intf.IP():
1175
                    self.cmd( 'ovs-vsctl add-port', self, intf )
1176
            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
1177
                    'other_config:datapath-id=%s ' % self.dpid +
1178
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1179
                    '-- set-controller %s %s ' % ( self, clist ) )
1180
        if not self.inband:
1181
            cmd += ( '-- set bridge %s '
1182
                     'other-config:disable-in-band=true ' % self )
1183
        if self.datapath == 'user':
1184
            cmd += '-- set bridge %s datapath_type=netdev ' % self
1185
        if self.protocols:
1186
            cmd += '-- set bridge %s protocols=%s' % ( self, self.protocols )
1187
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1188
        for uuid in self.controllerUUIDs():
1189
            if uuid.count( '-' ) != 4:
1190
                # Doesn't look like a UUID
1191
                continue
1192
            uuid = uuid.strip()
1193
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1194
        # Do it!!
1195
        self.cmd( cmd )
1196
        for intf in self.intfList():
1197
            self.TCReapply( intf )
1198

    
1199

    
1200
    def stop( self, deleteIntfs=True ):
1201
        """Terminate OVS switch.
1202
           deleteIntfs: delete interfaces? (True)"""
1203
        self.cmd( 'ovs-vsctl del-br', self )
1204
        if self.datapath == 'user':
1205
            self.cmd( 'ip link del', self )
1206
        super( OVSSwitch, self ).stop( deleteIntfs )
1207

    
1208

    
1209
OVSKernelSwitch = OVSSwitch
1210

    
1211

    
1212
class OVSBridge( OVSSwitch ):
1213
    "OVSBridge is an OVSSwitch in standalone/bridge mode"
1214

    
1215
    def __init__( self, args, **kwargs ):
1216
        kwargs.update( failMode='standalone' )
1217
        OVSSwitch.__init__( self, args, **kwargs )
1218

    
1219
    def start( self, controllers ):
1220
        OVSSwitch.start( self, controllers=[] )
1221

    
1222

    
1223
class IVSSwitch( Switch ):
1224
    "Indigo Virtual Switch"
1225

    
1226
    def __init__( self, name, verbose=False, **kwargs ):
1227
        Switch.__init__( self, name, **kwargs )
1228
        self.verbose = verbose
1229

    
1230
    @classmethod
1231
    def setup( cls ):
1232
        "Make sure IVS is installed"
1233
        pathCheck( 'ivs-ctl', 'ivs',
1234
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1235
        out, err, exitcode = errRun( 'ivs-ctl show' )
1236
        if exitcode:
1237
            error( out + err +
1238
                   'ivs-ctl exited with code %d\n' % exitcode +
1239
                   '*** The openvswitch kernel module might '
1240
                   'not be loaded. Try modprobe openvswitch.\n' )
1241
            exit( 1 )
1242

    
1243
    @classmethod
1244
    def batchShutdown( cls, switches ):
1245
        "Kill each IVS switch, to be waited on later in stop()"
1246
        for switch in switches:
1247
            switch.cmd( 'kill %ivs' )
1248

    
1249
    def start( self, controllers ):
1250
        "Start up a new IVS switch"
1251
        args = ['ivs']
1252
        args.extend( ['--name', self.name] )
1253
        args.extend( ['--dpid', self.dpid] )
1254
        if self.verbose:
1255
            args.extend( ['--verbose'] )
1256
        for intf in self.intfs.values():
1257
            if not intf.IP():
1258
                args.extend( ['-i', intf.name] )
1259
        for c in controllers:
1260
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1261
        if self.listenPort:
1262
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1263
        args.append( self.opts )
1264

    
1265
        logfile = '/tmp/ivs.%s.log' % self.name
1266

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

    
1269
    def stop( self, deleteIntfs=True ):
1270
        """Terminate IVS switch.
1271
           deleteIntfs: delete interfaces? (True)"""
1272
        self.cmd( 'kill %ivs' )
1273
        self.cmd( 'wait' )
1274
        super( IVSSwitch, self ).stop( deleteIntfs )
1275

    
1276
    def attach( self, intf ):
1277
        "Connect a data port"
1278
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1279

    
1280
    def detach( self, intf ):
1281
        "Disconnect a data port"
1282
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1283

    
1284
    def dpctl( self, *args ):
1285
        "Run dpctl command"
1286
        if not self.listenPort:
1287
            return "can't run dpctl without passive listening port"
1288
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1289
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1290

    
1291

    
1292
class Controller( Node ):
1293
    """A Controller is a Node that is running (or has execed?) an
1294
       OpenFlow controller."""
1295

    
1296
    def __init__( self, name, inNamespace=False, command='controller',
1297
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1298
                  port=6633, protocol='tcp', **params ):
1299
        self.command = command
1300
        self.cargs = cargs
1301
        self.cdir = cdir
1302
        self.ip = ip
1303
        self.port = port
1304
        self.protocol = protocol
1305
        Node.__init__( self, name, inNamespace=inNamespace,
1306
                       ip=ip, **params  )
1307
        self.checkListening()
1308

    
1309
    def checkListening( self ):
1310
        "Make sure no controllers are running on our port"
1311
        # Verify that Telnet is installed first:
1312
        out, _err, returnCode = errRun( "which telnet" )
1313
        if 'telnet' not in out or returnCode != 0:
1314
            raise Exception( "Error running telnet to check for listening "
1315
                             "controllers; please check that it is "
1316
                             "installed." )
1317
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1318
                              ( self.ip, self.port ) )
1319
        if 'Connected' in listening:
1320
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1321
            pstr = ':%d ' % self.port
1322
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1323
            raise Exception( "Please shut down the controller which is"
1324
                             " running on port %d:\n" % self.port +
1325
                             '\n'.join( clist ) )
1326

    
1327
    def start( self ):
1328
        """Start <controller> <args> on controller.
1329
           Log to /tmp/cN.log"""
1330
        pathCheck( self.command )
1331
        cout = '/tmp/' + self.name + '.log'
1332
        if self.cdir is not None:
1333
            self.cmd( 'cd ' + self.cdir )
1334
        self.cmd( self.command + ' ' + self.cargs % self.port +
1335
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1336
        self.execed = False
1337

    
1338
    def stop( self, *args, **kwargs ):
1339
        "Stop controller."
1340
        self.cmd( 'kill %' + self.command )
1341
        self.cmd( 'wait %' + self.command )
1342
        super( Controller, self ).stop( *args, **kwargs )
1343

    
1344
    def IP( self, intf=None ):
1345
        "Return IP address of the Controller"
1346
        if self.intfs:
1347
            ip = Node.IP( self, intf )
1348
        else:
1349
            ip = self.ip
1350
        return ip
1351

    
1352
    def __repr__( self ):
1353
        "More informative string representation"
1354
        return '<%s %s: %s:%s pid=%s> ' % (
1355
            self.__class__.__name__, self.name,
1356
            self.IP(), self.port, self.pid )
1357
    @classmethod
1358
    def isAvailable( cls ):
1359
        "Is controller available?"
1360
        return quietRun( 'which controller' )
1361

    
1362
class OVSController( Controller ):
1363
    "Open vSwitch controller"
1364
    def __init__( self, name, command='ovs-controller', **kwargs ):
1365
        if quietRun( 'which test-controller' ):
1366
            command = 'test-controller'
1367
        Controller.__init__( self, name, command=command, **kwargs )
1368
    @classmethod
1369
    def isAvailable( cls ):
1370
        return ( quietRun( 'which ovs-controller' ) or
1371
                 quietRun( 'which test-controller' ) )
1372

    
1373
class NOX( Controller ):
1374
    "Controller to run a NOX application."
1375

    
1376
    def __init__( self, name, *noxArgs, **kwargs ):
1377
        """Init.
1378
           name: name to give controller
1379
           noxArgs: arguments (strings) to pass to NOX"""
1380
        if not noxArgs:
1381
            warn( 'warning: no NOX modules specified; '
1382
                  'running packetdump only\n' )
1383
            noxArgs = [ 'packetdump' ]
1384
        elif type( noxArgs ) not in ( list, tuple ):
1385
            noxArgs = [ noxArgs ]
1386

    
1387
        if 'NOX_CORE_DIR' not in os.environ:
1388
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1389
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1390

    
1391
        Controller.__init__( self, name,
1392
                             command=noxCoreDir + '/nox_core',
1393
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1394
                             ' '.join( noxArgs ),
1395
                             cdir=noxCoreDir,
1396
                             **kwargs )
1397

    
1398
class RYU( Controller ):
1399
    "Controller to run Ryu application"
1400
    def __init__( self, name, *ryuArgs, **kwargs ):
1401
        """Init.
1402
        name: name to give controller.
1403
        ryuArgs: arguments and modules to pass to Ryu"""
1404
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1405
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1406
        if not ryuArgs:
1407
            warn( 'warning: no Ryu modules specified; '
1408
                  'running simple_switch only\n' )
1409
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1410
        elif type( ryuArgs ) not in ( list, tuple ):
1411
            ryuArgs = [ ryuArgs ]
1412

    
1413
        Controller.__init__( self, name,
1414
                         command='ryu-manager',
1415
                         cargs='--ofp-tcp-listen-port %s ' +
1416
                         ' '.join( ryuArgs ),
1417
                         cdir=ryuCoreDir,
1418
                         **kwargs )
1419

    
1420
class RemoteController( Controller ):
1421
    "Controller running outside of Mininet's control."
1422

    
1423
    def __init__( self, name, ip='127.0.0.1',
1424
                  port=6633, **kwargs):
1425
        """Init.
1426
           name: name to give controller
1427
           ip: the IP address where the remote controller is
1428
           listening
1429
           port: the port where the remote controller is listening"""
1430
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1431

    
1432
    def start( self ):
1433
        "Overridden to do nothing."
1434
        return
1435

    
1436
    def stop( self ):
1437
        "Overridden to do nothing."
1438
        return
1439

    
1440
    def checkListening( self ):
1441
        "Warn if remote controller is not accessible"
1442
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1443
                              ( self.ip, self.port ) )
1444
        if 'Connected' not in listening:
1445
            warn( "Unable to contact the remote controller"
1446
                  " at %s:%d\n" % ( self.ip, self.port ) )
1447

    
1448

    
1449
DefaultControllers = ( Controller, OVSController )
1450

    
1451
def findController( controllers=DefaultControllers ):
1452
    "Return first available controller from list, if any"
1453
    for controller in controllers:
1454
        if controller.isAvailable():
1455
            return controller
1456

    
1457
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1458
    "Find a controller that is available and instantiate it"
1459
    controller = findController( controllers )
1460
    if not controller:
1461
        raise Exception( 'Could not find a default OpenFlow controller' )
1462
    return controller( name, **kwargs )