Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 18aab5b7

History | View | Annotate | Download (55.6 KB)

1
"""
2
Node objects for Mininet.
3

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

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

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

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

21
Switch: superclass for switch nodes.
22

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

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

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

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

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

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

41
Future enhancements:
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
204
    # Subshell I/O, commands and control
205

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
389
    # Interface management, configuration, and routing
390

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

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

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

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

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

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

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

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

    
473
    # Routing support
474

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

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

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

    
499
    # Convenience and configuration methods
500

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

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

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

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

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

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

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

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

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

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

    
586
    # Other methods
587

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

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

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

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

    
607
    # Automatic class setup support
608

    
609
    isSetup = False
610

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

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

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

    
629
class CPULimitedHost( Host ):
630

    
631
    "CPU limited host"
632

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
808
    inited = False
809

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

    
816

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

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

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

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

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

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

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

    
889
    def connected( self ):
890
        "Is the switch connected to a controller? (override this method)"
891
        warn( "Warning: connected() needs to be implemented in"
892
              " Switch subclass %s\n" % self.__class__ )
893
        return True
894

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

    
902

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

    
906
    dpidLen = 12
907

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

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

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

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

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

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

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

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

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

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

    
1002

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

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

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

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

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

    
1054

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

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

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

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

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

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

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

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

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

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

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

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

    
1200

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

    
1210

    
1211
OVSKernelSwitch = OVSSwitch
1212

    
1213

    
1214
class OVSBridge( OVSSwitch ):
1215
    "OVSBridge is an OVSSwitch in standalone/bridge mode"
1216

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

    
1221
    def start( self, controllers ):
1222
        OVSSwitch.start( self, controllers=[] )
1223

    
1224

    
1225
class IVSSwitch( Switch ):
1226
    """IVS virtual switch"""
1227

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

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

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

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

    
1267
        logfile = '/tmp/ivs.%s.log' % self.name
1268

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

    
1271
    def stop( self, deleteIntfs=True ):
1272
        """Terminate IVS switch.
1273
           deleteIntfs: delete interfaces? (True)"""
1274
        self.cmd( 'kill %ivs' )
1275
        self.cmd( 'wait' )
1276
        if deleteIntfs:
1277
            self.deleteIntfs()
1278

    
1279
    def attach( self, intf ):
1280
        "Connect a data port"
1281
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1282

    
1283
    def detach( self, intf ):
1284
        "Disconnect a data port"
1285
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1286

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

    
1294

    
1295
class Controller( Node ):
1296
    """A Controller is a Node that is running (or has execed?) an
1297
       OpenFlow controller."""
1298

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

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

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

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

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

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

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

    
1376
class NOX( Controller ):
1377
    "Controller to run a NOX application."
1378

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

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

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

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

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

    
1423
class RemoteController( Controller ):
1424
    "Controller running outside of Mininet's control."
1425

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

    
1435
    def start( self ):
1436
        "Overridden to do nothing."
1437
        return
1438

    
1439
    def stop( self ):
1440
        "Overridden to do nothing."
1441
        return
1442

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

    
1451

    
1452
DefaultControllers = ( Controller, OVSController )
1453

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

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