Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ c273f490

History | View | Annotate | Download (54.7 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, STDOUT
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
        return Popen( cmd, **params )
193

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

    
202
    # Subshell I/O, commands and control
203

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

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

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

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

    
243
    def stop( self ):
244
        "Stop node."
245
        self.terminate()
246

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

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

    
283
    def sendInt( self, intr=chr( 3 ) ):
284
        "Interrupt running command."
285
        debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
286
        self.write( intr )
287

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

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

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

    
341
    def cmdPrint( self, *args):
342
        """Call cmd and printing its output
343
           cmd: string"""
344
        return self.cmd( *args, **{ 'verbose': True } )
345

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

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

    
384
    # Interface management, configuration, and routing
385

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

    
392
    def newPort( self ):
393
        "Return the next port number to allocate."
394
        if len( self.ports ) > 0:
395
            return max( self.ports.values() ) + 1
396
        return self.portBase
397

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

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

    
424
    def intf( self, intf='' ):
425
        """Return our interface object with given string name,
426
           default intf if name is falsy (None, empty string, etc).
427
           or the input intf arg.
428

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

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

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

    
468
    # Routing support
469

    
470
    def setARP( self, ip, mac ):
471
        """Add an ARP entry.
472
           ip: IP address as string
473
           mac: MAC address as string"""
474
        result = self.cmd( 'arp', '-s', ip, mac )
475
        return result
476

    
477
    def setHostRoute( self, ip, intf ):
478
        """Add route to host.
479
           ip: IP address as dotted decimal
480
           intf: string, interface name"""
481
        return self.cmd( 'route add -host', ip, 'dev', intf )
482

    
483
    def setDefaultRoute( self, intf=None ):
484
        """Set the default route to go through intf.
485
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
486
        # Note setParam won't call us if intf is none
487
        if isinstance( intf, basestring ) and ' ' in intf:
488
            params = intf
489
        else:
490
            params = 'dev %s' % intf
491
        self.cmd( 'ip route del default' )
492
        return self.cmd( 'ip route add default', params )
493

    
494
    # Convenience and configuration methods
495

    
496
    def setMAC( self, mac, intf=None ):
497
        """Set the MAC address for an interface.
498
           intf: intf or intf name
499
           mac: MAC address as string"""
500
        return self.intf( intf ).setMAC( mac )
501

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

    
512
    def IP( self, intf=None ):
513
        "Return IP address of a node or specific interface."
514
        return self.intf( intf ).IP()
515

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

    
520
    def intfIsUp( self, intf=None ):
521
        "Check if an interface is up."
522
        return self.intf( intf ).isUp()
523

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

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

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

    
570
    def configDefault( self, **moreParams ):
571
        "Configure with default parameters"
572
        self.params.update( moreParams )
573
        self.config( **self.params )
574

    
575
    # This is here for backward compatibility
576
    def linkTo( self, node, link=Link ):
577
        """(Deprecated) Link to another node
578
           replace with Link( node1, node2)"""
579
        return link( self, node )
580

    
581
    # Other methods
582

    
583
    def intfList( self ):
584
        "List of our interfaces sorted by port number"
585
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
586

    
587
    def intfNames( self ):
588
        "The names of our interfaces sorted by port number"
589
        return [ str( i ) for i in self.intfList() ]
590

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

    
598
    def __str__( self ):
599
        "Abbreviated string representation"
600
        return self.name
601

    
602
    # Automatic class setup support
603

    
604
    isSetup = False
605

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

    
615
    @classmethod
616
    def setup( cls ):
617
        "Make sure our class dependencies are available"
618
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
619

    
620
class Host( Node ):
621
    "A host is simply a Node"
622
    pass
623

    
624
class CPULimitedHost( Host ):
625

    
626
    "CPU limited host"
627

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

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

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

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

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

    
689
    def cleanup( self ):
690
        "Clean up Node, then clean up our cgroup"
691
        super( CPULimitedHost, self ).cleanup()
692
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
693

    
694
    _rtGroupSched = False   # internal class var: Is CONFIG_RT_GROUP_SCHED set?
695
    
696
    @classmethod
697
    def checkRtGroupSched( cls ):
698
        "Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT"
699
        if not cls._rtGroupSched:
700
            release = quietRun( 'uname -r' ).strip('\r\n')
701
            output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
702
            if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
703
                error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
704
                exit( 1 )
705
            cls._rtGroupSched = True
706

    
707
    def chrt( self ):
708
        "Set RT scheduling priority"
709
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
710
        result = quietRun( 'chrt -p %s' % self.pid )
711
        firstline = result.split( '\n' )[ 0 ]
712
        lastword = firstline.split( ' ' )[ -1 ]
713
        if lastword != 'SCHED_RR':
714
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
715
        return lastword
716

    
717
    def rtInfo( self, f ):
718
        "Internal method: return parameters for RT bandwidth"
719
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
720
        # RT uses wall clock time for period and quota
721
        quota = int( self.period_us * f )
722
        return pstr, qstr, self.period_us, quota
723

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

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

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

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

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

    
801
    inited = False
802

    
803
    @classmethod
804
    def init( cls ):
805
        "Initialization for CPULimitedHost class"
806
        mountCgroups()
807
        cls.inited = True
808

    
809

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

    
830
class Switch( Node ):
831
    """A Switch is a Node that is running (or has execed?)
832
       an OpenFlow switch."""
833

    
834
    portBase = 1  # Switches start with port 1 in OpenFlow
835
    dpidLen = 16  # digits in dpid passed to switch
836

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

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

    
865
    def defaultIntf( self ):
866
        "Return control interface"
867
        if self.controlIntf:
868
            return self.controlIntf
869
        else:
870
            return Node.defaultIntf( self )
871

    
872
    def sendCmd( self, *cmd, **kwargs ):
873
        """Send command to Node.
874
           cmd: string"""
875
        kwargs.setdefault( 'printPid', False )
876
        if not self.execed:
877
            return Node.sendCmd( self, *cmd, **kwargs )
878
        else:
879
            error( '*** Error: %s has execed and cannot accept commands' %
880
                   self.name )
881

    
882
    def connected( self ):
883
        "Is the switch connected to a controller? (override this method)"
884
        return False and self  # satisfy pylint
885

    
886
    def __repr__( self ):
887
        "More informative string representation"
888
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
889
                              for i in self.intfList() ] ) )
890
        return '<%s %s: %s pid=%s> ' % (
891
            self.__class__.__name__, self.name, intfs, self.pid )
892

    
893
class UserSwitch( Switch ):
894
    "User-space switch."
895

    
896
    dpidLen = 12
897

    
898
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
899
        """Init.
900
           name: name for the switch
901
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
902
        Switch.__init__( self, name, **kwargs )
903
        pathCheck( 'ofdatapath', 'ofprotocol',
904
                   moduleName='the OpenFlow reference user switch' +
905
                              '(openflow.org)' )
906
        if self.listenPort:
907
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
908
        else:
909
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
910
        self.dpopts = dpopts
911

    
912
    @classmethod
913
    def setup( cls ):
914
        "Ensure any dependencies are loaded; if not, try to load them."
915
        if not os.path.exists( '/dev/net/tun' ):
916
            moduleDeps( add=TUN )
917

    
918
    def dpctl( self, *args ):
919
        "Run dpctl command"
920
        listenAddr = None
921
        if not self.listenPort:
922
            listenAddr = 'unix:/tmp/%s.listen' % self.name
923
        else:
924
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
925
        return self.cmd( 'dpctl ' + ' '.join( args ) +
926
                         ' ' + listenAddr )
927

    
928
    def connected( self ):
929
        "Is the switch connected to a controller?"
930
        status = self.dpctl( 'status' )
931
        return ( 'remote.is-connected=true' in status and
932
                 'local.is-connected=true' in status )
933

    
934
    @staticmethod
935
    def TCReapply( intf ):
936
        """Unfortunately user switch and Mininet are fighting
937
           over tc queuing disciplines. To resolve the conflict,
938
           we re-create the user switch's configuration, but as a
939
           leaf of the TCIntf-created configuration."""
940
        if isinstance( intf, TCIntf ):
941
            ifspeed = 10000000000 # 10 Gbps
942
            minspeed = ifspeed * 0.001
943

    
944
            res = intf.config( **intf.params )
945

    
946
            if res is None: # link may not have TC parameters
947
                return
948

    
949
            # Re-add qdisc, root, and default classes user switch created, but
950
            # with new parent, as setup by Mininet's TCIntf
951
            parent = res['parent']
952
            intf.tc( "%s qdisc add dev %s " + parent +
953
                     " handle 1: htb default 0xfffe" )
954
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
955
                     + str(ifspeed) )
956
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
957
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
958

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

    
984
    def stop( self ):
985
        "Stop OpenFlow reference user datapath."
986
        self.cmd( 'kill %ofdatapath' )
987
        self.cmd( 'kill %ofprotocol' )
988
        self.deleteIntfs()
989

    
990

    
991
class OVSLegacyKernelSwitch( Switch ):
992
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
993
       Currently only works in the root namespace."""
994

    
995
    def __init__( self, name, dp=None, **kwargs ):
996
        """Init.
997
           name: name for switch
998
           dp: netlink id (0, 1, 2, ...)
999
           defaultMAC: default MAC as unsigned int; random value if None"""
1000
        Switch.__init__( self, name, **kwargs )
1001
        self.dp = dp if dp else self.name
1002
        self.intf = self.dp
1003
        if self.inNamespace:
1004
            error( "OVSKernelSwitch currently only works"
1005
                   " in the root namespace.\n" )
1006
            exit( 1 )
1007

    
1008
    @classmethod
1009
    def setup( cls ):
1010
        "Ensure any dependencies are loaded; if not, try to load them."
1011
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
1012
                   moduleName='Open vSwitch (openvswitch.org)')
1013
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1014

    
1015
    def start( self, controllers ):
1016
        "Start up kernel datapath."
1017
        ofplog = '/tmp/' + self.name + '-ofp.log'
1018
        # Delete local datapath if it exists;
1019
        # then create a new one monitoring the given interfaces
1020
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
1021
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
1022
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
1023
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
1024
        # Run protocol daemon
1025
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1026
                            for c in controllers ] )
1027
        self.cmd( 'ovs-openflowd ' + self.dp +
1028
                  ' ' + clist +
1029
                  ' --fail=secure ' + self.opts +
1030
                  ' --datapath-id=' + self.dpid +
1031
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
1032
        self.execed = False
1033

    
1034
    def stop( self ):
1035
        "Terminate kernel datapath."
1036
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
1037
        self.cmd( 'kill %ovs-openflowd' )
1038
        self.deleteIntfs()
1039

    
1040

    
1041
class OVSSwitch( Switch ):
1042
    "Open vSwitch switch. Depends on ovs-vsctl."
1043

    
1044
    def __init__( self, name, failMode='secure', datapath='kernel',
1045
                 inband=False, protocols=None, **params ):
1046
        """Init.
1047
           name: name for switch
1048
           failMode: controller loss behavior (secure|open)
1049
           datapath: userspace or kernel mode (kernel|user)
1050
           inband: use in-band control (False)"""
1051
        Switch.__init__( self, name, **params )
1052
        self.failMode = failMode
1053
        self.datapath = datapath
1054
        self.inband = inband
1055
        self.protocols = protocols
1056

    
1057
    @classmethod
1058
    def setup( cls ):
1059
        "Make sure Open vSwitch is installed and working"
1060
        pathCheck( 'ovs-vsctl',
1061
                   moduleName='Open vSwitch (openvswitch.org)')
1062
        # This should no longer be needed, and it breaks
1063
        # with OVS 1.7 which has renamed the kernel module:
1064
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1065
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1066
        if exitcode:
1067
            error( out + err +
1068
                   'ovs-vsctl exited with code %d\n' % exitcode +
1069
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1070
                   'Make sure that Open vSwitch is installed, '
1071
                   'that ovsdb-server is running, and that\n'
1072
                   '"ovs-vsctl show" works correctly.\n'
1073
                   'You may wish to try '
1074
                   '"service openvswitch-switch start".\n' )
1075
            exit( 1 )
1076
        info = quietRun( 'ovs-vsctl --version' )
1077
        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
1078

    
1079
    @classmethod
1080
    def isOldOVS( cls ):
1081
        return ( StrictVersion( cls.OVSVersion ) <
1082
             StrictVersion( '1.10' ) )
1083

    
1084
    @classmethod
1085
    def batchShutdown( cls, switches ):
1086
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1087
        quietRun( 'ovs-vsctl ' +
1088
                  ' -- '.join( '--if-exists del-br %s' % s
1089
                               for s in switches ) )
1090

    
1091
    def dpctl( self, *args ):
1092
        "Run ovs-ofctl command"
1093
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1094

    
1095
    @staticmethod
1096
    def TCReapply( intf ):
1097
        """Unfortunately OVS and Mininet are fighting
1098
           over tc queuing disciplines. As a quick hack/
1099
           workaround, we clear OVS's and reapply our own."""
1100
        if isinstance( intf, TCIntf ):
1101
            intf.config( **intf.params )
1102

    
1103
    def attach( self, intf ):
1104
        "Connect a data port"
1105
        self.cmd( 'ovs-vsctl add-port', self, intf )
1106
        self.cmd( 'ifconfig', intf, 'up' )
1107
        self.TCReapply( intf )
1108

    
1109
    def detach( self, intf ):
1110
        "Disconnect a data port"
1111
        self.cmd( 'ovs-vsctl del-port', self, intf )
1112

    
1113
    def controllerUUIDs( self ):
1114
        "Return ovsdb UUIDs for our controllers"
1115
        uuids = []
1116
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1117
                               'Controller' ).strip()
1118
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1119
            controllers = controllers[ 1 : -1 ]
1120
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1121
        return uuids
1122

    
1123
    def connected( self ):
1124
        "Are we connected to at least one of our controllers?"
1125
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1126
                                         uuid, 'is_connected' )
1127
                    for uuid in self.controllerUUIDs() ]
1128
        return reduce( or_, results, False )
1129

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

    
1185

    
1186
    def stop( self ):
1187
        "Terminate OVS switch."
1188
        self.cmd( 'ovs-vsctl del-br', self )
1189
        if self.datapath == 'user':
1190
            self.cmd( 'ip link del', self )
1191
        self.deleteIntfs()
1192

    
1193

    
1194
OVSKernelSwitch = OVSSwitch
1195

    
1196

    
1197
class OVSBridge( OVSSwitch ):
1198
    "OVSBridge is an OVSSwitch in standalone/bridge mode"
1199
    
1200
    def __init__( self, args, **kwargs ):
1201
        kwargs.update( failMode='standalone' )
1202
        OVSSwitch.__init__( self, args, **kwargs )
1203
    
1204
    def start( self, controllers ):
1205
        OVSSwitch.start( self, controllers=[] )
1206

    
1207

    
1208
class IVSSwitch(Switch):
1209
    """IVS virtual switch"""
1210

    
1211
    def __init__( self, name, verbose=False, **kwargs ):
1212
        Switch.__init__( self, name, **kwargs )
1213
        self.verbose = verbose
1214

    
1215
    @classmethod
1216
    def setup( cls ):
1217
        "Make sure IVS is installed"
1218
        pathCheck( 'ivs-ctl', 'ivs',
1219
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1220
        out, err, exitcode = errRun( 'ivs-ctl show' )
1221
        if exitcode:
1222
            error( out + err +
1223
                   'ivs-ctl exited with code %d\n' % exitcode +
1224
                   '*** The openvswitch kernel module might '
1225
                   'not be loaded. Try modprobe openvswitch.\n' )
1226
            exit( 1 )
1227

    
1228
    @classmethod
1229
    def batchShutdown( cls, switches ):
1230
        "Kill each IVS switch, to be waited on later in stop()"
1231
        for switch in switches:
1232
            switch.cmd( 'kill %ivs' )
1233

    
1234
    def start( self, controllers ):
1235
        "Start up a new IVS switch"
1236
        args = ['ivs']
1237
        args.extend( ['--name', self.name] )
1238
        args.extend( ['--dpid', self.dpid] )
1239
        if self.verbose:
1240
            args.extend( ['--verbose'] )
1241
        for intf in self.intfs.values():
1242
            if not intf.IP():
1243
                args.extend( ['-i', intf.name] )
1244
        for c in controllers:
1245
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1246
        if self.listenPort:
1247
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1248
        args.append( self.opts )
1249

    
1250
        logfile = '/tmp/ivs.%s.log' % self.name
1251

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

    
1254
    def stop( self ):
1255
        "Terminate IVS switch."
1256
        self.cmd( 'kill %ivs' )
1257
        self.cmd( 'wait' )
1258
        self.deleteIntfs()
1259

    
1260
    def attach( self, intf ):
1261
        "Connect a data port"
1262
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1263

    
1264
    def detach( self, intf ):
1265
        "Disconnect a data port"
1266
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1267

    
1268
    def dpctl( self, *args ):
1269
        "Run dpctl command"
1270
        if not self.listenPort:
1271
            return "can't run dpctl without passive listening port"
1272
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1273
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1274

    
1275

    
1276
class Controller( Node ):
1277
    """A Controller is a Node that is running (or has execed?) an
1278
       OpenFlow controller."""
1279

    
1280
    def __init__( self, name, inNamespace=False, command='controller',
1281
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1282
                  port=6633, protocol='tcp', **params ):
1283
        self.command = command
1284
        self.cargs = cargs
1285
        self.cdir = cdir
1286
        self.ip = ip
1287
        self.port = port
1288
        self.protocol = protocol
1289
        Node.__init__( self, name, inNamespace=inNamespace,
1290
                       ip=ip, **params  )
1291
        self.checkListening()
1292

    
1293
    def checkListening( self ):
1294
        "Make sure no controllers are running on our port"
1295
        # Verify that Telnet is installed first:
1296
        out, _err, returnCode = errRun( "which telnet" )
1297
        if 'telnet' not in out or returnCode != 0:
1298
            raise Exception( "Error running telnet to check for listening "
1299
                             "controllers; please check that it is "
1300
                             "installed." )
1301
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1302
                              ( self.ip, self.port ) )
1303
        if 'Connected' in listening:
1304
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1305
            pstr = ':%d ' % self.port
1306
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1307
            raise Exception( "Please shut down the controller which is"
1308
                             " running on port %d:\n" % self.port +
1309
                             '\n'.join( clist ) )
1310

    
1311
    def start( self ):
1312
        """Start <controller> <args> on controller.
1313
           Log to /tmp/cN.log"""
1314
        pathCheck( self.command )
1315
        cout = '/tmp/' + self.name + '.log'
1316
        if self.cdir is not None:
1317
            self.cmd( 'cd ' + self.cdir )
1318
        self.cmd( self.command + ' ' + self.cargs % self.port +
1319
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1320
        self.execed = False
1321

    
1322
    def stop( self ):
1323
        "Stop controller."
1324
        self.cmd( 'kill %' + self.command )
1325
        self.cmd( 'wait %' + self.command )
1326
        self.terminate()
1327

    
1328
    def IP( self, intf=None ):
1329
        "Return IP address of the Controller"
1330
        if self.intfs:
1331
            ip = Node.IP( self, intf )
1332
        else:
1333
            ip = self.ip
1334
        return ip
1335

    
1336
    def __repr__( self ):
1337
        "More informative string representation"
1338
        return '<%s %s: %s:%s pid=%s> ' % (
1339
            self.__class__.__name__, self.name,
1340
            self.IP(), self.port, self.pid )
1341
    @classmethod
1342
    def isAvailable( self ):
1343
        return quietRun( 'which controller' )
1344

    
1345
class OVSController( Controller ):
1346
    "Open vSwitch controller"
1347
    def __init__( self, name, command='ovs-controller', **kwargs ):
1348
        if quietRun( 'which test-controller' ):
1349
            command = 'test-controller'
1350
        Controller.__init__( self, name, command=command, **kwargs )
1351
    @classmethod
1352
    def isAvailable( self ):
1353
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1354

    
1355
class NOX( Controller ):
1356
    "Controller to run a NOX application."
1357

    
1358
    def __init__( self, name, *noxArgs, **kwargs ):
1359
        """Init.
1360
           name: name to give controller
1361
           noxArgs: arguments (strings) to pass to NOX"""
1362
        if not noxArgs:
1363
            warn( 'warning: no NOX modules specified; '
1364
                  'running packetdump only\n' )
1365
            noxArgs = [ 'packetdump' ]
1366
        elif type( noxArgs ) not in ( list, tuple ):
1367
            noxArgs = [ noxArgs ]
1368

    
1369
        if 'NOX_CORE_DIR' not in os.environ:
1370
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1371
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1372

    
1373
        Controller.__init__( self, name,
1374
                             command=noxCoreDir + '/nox_core',
1375
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1376
                             ' '.join( noxArgs ),
1377
                             cdir=noxCoreDir,
1378
                             **kwargs )
1379

    
1380
class RYU( Controller ):
1381
    "Controller to run Ryu application"
1382
    def __init__( self, name, *ryuArgs, **kwargs ):
1383
        """Init.
1384
        name: name to give controller.
1385
        ryuArgs: arguments and modules to pass to Ryu"""
1386
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1387
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1388
        if not ryuArgs:
1389
            warn( 'warning: no Ryu modules specified; '
1390
                  'running simple_switch only\n' )
1391
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1392
        elif type( ryuArgs ) not in ( list, tuple ):
1393
            ryuArgs = [ ryuArgs ]
1394

    
1395
        Controller.__init__( self, name,
1396
                         command='ryu-manager',
1397
                         cargs='--ofp-tcp-listen-port %s ' +
1398
                         ' '.join( ryuArgs ),
1399
                         cdir=ryuCoreDir,
1400
                         **kwargs )
1401

    
1402
class RemoteController( Controller ):
1403
    "Controller running outside of Mininet's control."
1404

    
1405
    def __init__( self, name, ip='127.0.0.1',
1406
                  port=6633, **kwargs):
1407
        """Init.
1408
           name: name to give controller
1409
           ip: the IP address where the remote controller is
1410
           listening
1411
           port: the port where the remote controller is listening"""
1412
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1413

    
1414
    def start( self ):
1415
        "Overridden to do nothing."
1416
        return
1417

    
1418
    def stop( self ):
1419
        "Overridden to do nothing."
1420
        return
1421

    
1422
    def checkListening( self ):
1423
        "Warn if remote controller is not accessible"
1424
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1425
                              ( self.ip, self.port ) )
1426
        if 'Connected' not in listening:
1427
            warn( "Unable to contact the remote controller"
1428
                  " at %s:%d\n" % ( self.ip, self.port ) )
1429

    
1430

    
1431
DefaultControllers = [ Controller, OVSController ]
1432

    
1433
def findController( controllers=DefaultControllers ):
1434
    "Return first available controller from list, if any"
1435
    for controller in controllers:
1436
        if controller.isAvailable():
1437
            return controller
1438

    
1439
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1440
    "Find a controller that is available and instantiate it"
1441
    controller = findController( controllers )
1442
    if not controller:
1443
        raise Exception( 'Could not find a default OpenFlow controller' )
1444
    return controller( name, **kwargs )
1445