Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ f51eddef

History | View | Annotate | Download (54.5 KB)

1
"""
2
Node objects for Mininet.
3

4
Nodes provide a simple abstraction for interacting with hosts, switches
5
and controllers. Local nodes are simply one or more processes on the local
6
machine.
7

8
Node: superclass for all (primarily local) network nodes.
9

10
Host: a virtual host. By default, a host is simply a shell; commands
11
    may be sent using Cmd (which waits for output), or using sendCmd(),
12
    which returns immediately, allowing subsequent monitoring using
13
    monitor(). Examples of how to run experiments using this
14
    functionality are provided in the examples/ directory. By default,
15
    hosts share the root file system, but they may also specify private
16
    directories.
17

18
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
19
    RT or CFS bandwidth limiting.
20

21
Switch: superclass for switch nodes.
22

23
UserSwitch: a switch using the user-space switch from the OpenFlow
24
    reference implementation.
25

26
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
27
    implementation.
28

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

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

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

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

41
Future enhancements:
42

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

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

    
49
import os
50
import pty
51
import re
52
import signal
53
import select
54
from subprocess import Popen, PIPE, 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 type( args[ 0 ] ) is 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 type( args[ 0 ] ) is list:
356
                # popen([cmd, arg1, arg2...])
357
                cmd = args[ 0 ]
358
            elif type( args[ 0 ] ) is str:
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 type( intf ) is str:
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 type( intf ) is str 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
        f = getattr( self, method, None )
538
        if not f:
539
            return
540
        if type( value ) is list:
541
            result = f( *value )
542
        elif type( value ) is dict:
543
            result = f( **value )
544
        else:
545
            result = f( value )
546
        results[ name ] = result
547
        return result
548

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

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

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

    
579
    # Other methods
580

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

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

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

    
596
    def __str__( self ):
597
        "Abbreviated string representation"
598
        return self.name
599

    
600
    # Automatic class setup support
601

    
602
    isSetup = False
603

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

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

    
618
class Host( Node ):
619
    "A host is simply a Node"
620
    pass
621

    
622
class CPULimitedHost( Host ):
623

    
624
    "CPU limited host"
625

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
799
    inited = False
800

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

    
807

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

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

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

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

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

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

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

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

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

    
891
class UserSwitch( Switch ):
892
    "User-space switch."
893

    
894
    dpidLen = 12
895

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

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

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

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

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

    
942
            res = intf.config( **intf.params )
943

    
944
            if res is None: # link may not have TC parameters
945
                return
946

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

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

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

    
988

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

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

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

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

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

    
1038

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

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

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

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

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

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

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

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

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

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

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

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

    
1183

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

    
1191

    
1192
OVSKernelSwitch = OVSSwitch
1193

    
1194

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

    
1205

    
1206
class IVSSwitch(Switch):
1207
    """IVS virtual switch"""
1208

    
1209
    def __init__( self, name, verbose=True, **kwargs ):
1210
        Switch.__init__( self, name, **kwargs )
1211
        self.verbose = verbose
1212

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

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

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

    
1248
        logfile = '/tmp/ivs.%s.log' % self.name
1249

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

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

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

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

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

    
1273

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

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

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

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

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

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

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

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

    
1353
class NOX( Controller ):
1354
    "Controller to run a NOX application."
1355

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

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

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

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

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

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

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

    
1412
    def start( self ):
1413
        "Overridden to do nothing."
1414
        return
1415

    
1416
    def stop( self ):
1417
        "Overridden to do nothing."
1418
        return
1419

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

    
1428

    
1429
DefaultControllers = [ Controller, OVSController ]
1430

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

    
1437
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
1438
    "Find a controller that is available and instantiate it"
1439
    return findController( controllers )( name, **kwargs )