Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 736db20c

History | View | Annotate | Download (52.9 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.
15

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

19
HostWithPrivateDirs: a virtual host that has user-specified private
20
    directories. These may be temporary directories stored as a tmpfs,
21
    or persistent directories that are mounted from another directory in
22
    the root filesystem.
23

24
Switch: superclass for switch nodes.
25

26
UserSwitch: a switch using the user-space switch from the OpenFlow
27
    reference implementation.
28

29
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
30
    implementation.
31

32
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
33
    implementation (openvswitch.org).
34

35
Controller: superclass for OpenFlow controllers. The default controller
36
    is controller(8) from the reference implementation.
37

38
NOXController: a controller node using NOX (noxrepo.org).
39

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

44
Future enhancements:
45

46
- Possibly make Node, Switch and Controller more abstract so that
47
  they can be used for both local and remote nodes
48

49
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
50
"""
51

    
52
import os
53
import pty
54
import re
55
import signal
56
import select
57
from subprocess import Popen, PIPE, STDOUT
58
from operator import or_
59
from time import sleep
60

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

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

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

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

    
81
        # Make sure class actually works
82
        self.checkSetup()
83

    
84
        self.name = params.get( 'name', name )
85
        self.privateDirs = params.get( 'privateDirs', [] )
86
        self.inNamespace = params.get( 'inNamespace', inNamespace )
87

    
88
        # Stash configuration parameters for future reference
89
        self.params = params
90

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

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

    
103
        # Start command interpreter shell
104
        self.startShell()
105
        self.mountPrivateDirs()
106

    
107
    # File descriptor to node mapping support
108
    # Class variables and methods
109

    
110
    inToNode = {}  # mapping of input fds to nodes
111
    outToNode = {}  # mapping of output fds to nodes
112

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

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

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

    
183
    def _popen( self, cmd, **params ):
184
        """Internal method: spawn and return a process
185
            cmd: command to run (list)
186
            params: parameters to Popen()"""
187
        return Popen( cmd, **params )
188

    
189
    def cleanup( self ):
190
        "Help python collect its garbage."
191
        # Intfs may end up in root NS
192
        for intfName in self.intfNames():
193
            if self.name in intfName:
194
                quietRun( 'ip link del ' + intfName )
195
        self.shell = None
196

    
197
    # Subshell I/O, commands and control
198

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

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

    
225
    def write( self, data ):
226
        """Write data to node.
227
           data: string"""
228
        os.write( self.stdin.fileno(), data )
229

    
230
    def terminate( self ):
231
        "Send kill signal to Node and clean up after it."
232
        if self.shell:
233
            if self.shell.poll() is None:
234
                os.killpg( self.shell.pid, signal.SIGHUP )
235
        self.cleanup()
236

    
237
    def stop( self ):
238
        "Stop node."
239
        self.terminate()
240

    
241
    def waitReadable( self, timeoutms=None ):
242
        """Wait until node's output is readable.
243
           timeoutms: timeout in ms or None to wait indefinitely."""
244
        if len( self.readbuf ) == 0:
245
            self.pollOut.poll( timeoutms )
246

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

    
277
    def sendInt( self, intr=chr( 3 ) ):
278
        "Interrupt running command."
279
        debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
280
        self.write( intr )
281

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

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

    
326
    def cmd( self, *args, **kwargs ):
327
        """Send a command, wait for output, and return it.
328
           cmd: string"""
329
        verbose = kwargs.get( 'verbose', False )
330
        log = info if verbose else debug
331
        log( '*** %s : %s\n' % ( self.name, args ) )
332
        self.sendCmd( *args, **kwargs )
333
        return self.waitOutput( verbose )
334

    
335
    def cmdPrint( self, *args):
336
        """Call cmd and printing its output
337
           cmd: string"""
338
        return self.cmd( *args, **{ 'verbose': True } )
339

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

    
368
    def pexec( self, *args, **kwargs ):
369
        """Execute a command using popen
370
           returns: out, err, exitcode"""
371
        popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
372
                           **kwargs )
373
        # Warning: this can fail with large numbers of fds!
374
        out, err = popen.communicate()
375
        exitcode = popen.wait()
376
        return out, err, exitcode
377

    
378
    # Interface management, configuration, and routing
379

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

    
386
    def newPort( self ):
387
        "Return the next port number to allocate."
388
        if len( self.ports ) > 0:
389
            return max( self.ports.values() ) + 1
390
        return self.portBase
391

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

    
409
    def defaultIntf( self ):
410
        "Return interface for lowest port"
411
        ports = self.intfs.keys()
412
        if ports:
413
            return self.intfs[ min( ports ) ]
414
        else:
415
            warn( '*** defaultIntf: warning:', self.name,
416
                  'has no interfaces\n' )
417

    
418
    def intf( self, intf='' ):
419
        """Return our interface object with given string name,
420
           default intf if name is falsy (None, empty string, etc).
421
           or the input intf arg.
422

423
        Having this fcn return its arg for Intf objects makes it
424
        easier to construct functions with flexible input args for
425
        interfaces (those that accept both string names and Intf objects).
426
        """
427
        if not intf:
428
            return self.defaultIntf()
429
        elif type( intf ) is str:
430
            return self.nameToIntf[ intf ]
431
        else:
432
            return intf
433

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

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

    
462
    # Routing support
463

    
464
    def setARP( self, ip, mac ):
465
        """Add an ARP entry.
466
           ip: IP address as string
467
           mac: MAC address as string"""
468
        result = self.cmd( 'arp', '-s', ip, mac )
469
        return result
470

    
471
    def setHostRoute( self, ip, intf ):
472
        """Add route to host.
473
           ip: IP address as dotted decimal
474
           intf: string, interface name"""
475
        return self.cmd( 'route add -host', ip, 'dev', intf )
476

    
477
    def setDefaultRoute( self, intf=None ):
478
        """Set the default route to go through intf.
479
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
480
        # Note setParam won't call us if intf is none
481
        if type( intf ) is str and ' ' in intf:
482
            params = intf
483
        else:
484
            params = 'dev %s' % intf
485
        self.cmd( 'ip route del default' )
486
        return self.cmd( 'ip route add default', params )
487

    
488
    # Convenience and configuration methods
489

    
490
    def setMAC( self, mac, intf=None ):
491
        """Set the MAC address for an interface.
492
           intf: intf or intf name
493
           mac: MAC address as string"""
494
        return self.intf( intf ).setMAC( mac )
495

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

    
506
    def IP( self, intf=None ):
507
        "Return IP address of a node or specific interface."
508
        return self.intf( intf ).IP()
509

    
510
    def MAC( self, intf=None ):
511
        "Return MAC address of a node or specific interface."
512
        return self.intf( intf ).MAC()
513

    
514
    def intfIsUp( self, intf=None ):
515
        "Check if an interface is up."
516
        return self.intf( intf ).isUp()
517

    
518
    # The reason why we configure things in this way is so
519
    # That the parameters can be listed and documented in
520
    # the config method.
521
    # Dealing with subclasses and superclasses is slightly
522
    # annoying, but at least the information is there!
523

    
524
    def setParam( self, results, method, **param ):
525
        """Internal method: configure a *single* parameter
526
           results: dict of results to update
527
           method: config method name
528
           param: arg=value (ignore if value=None)
529
           value may also be list or dict"""
530
        name, value = param.items()[ 0 ]
531
        f = getattr( self, method, None )
532
        if not f or value is None:
533
            return
534
        if type( value ) is list:
535
            result = f( *value )
536
        elif type( value ) is dict:
537
            result = f( **value )
538
        else:
539
            result = f( value )
540
        results[ name ] = result
541
        return result
542

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

    
562
    def configDefault( self, **moreParams ):
563
        "Configure with default parameters"
564
        self.params.update( moreParams )
565
        self.config( **self.params )
566

    
567
    # This is here for backward compatibility
568
    def linkTo( self, node, link=Link ):
569
        """(Deprecated) Link to another node
570
           replace with Link( node1, node2)"""
571
        return link( self, node )
572

    
573
    # Other methods
574

    
575
    def intfList( self ):
576
        "List of our interfaces sorted by port number"
577
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
578

    
579
    def intfNames( self ):
580
        "The names of our interfaces sorted by port number"
581
        return [ str( i ) for i in self.intfList() ]
582

    
583
    def __repr__( self ):
584
        "More informative string representation"
585
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
586
                              for i in self.intfList() ] ) )
587
        return '<%s %s: %s pid=%s> ' % (
588
            self.__class__.__name__, self.name, intfs, self.pid )
589

    
590
    def __str__( self ):
591
        "Abbreviated string representation"
592
        return self.name
593

    
594
    # Automatic class setup support
595

    
596
    isSetup = False
597

    
598
    @classmethod
599
    def checkSetup( cls ):
600
        "Make sure our class and superclasses are set up"
601
        while cls and not getattr( cls, 'isSetup', True ):
602
            cls.setup()
603
            cls.isSetup = True
604
            # Make pylint happy
605
            cls = getattr( type( cls ), '__base__', None )
606

    
607
    @classmethod
608
    def setup( cls ):
609
        "Make sure our class dependencies are available"
610
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
611

    
612
class Host( Node ):
613
    "A host is simply a Node"
614
    pass
615

    
616
class CPULimitedHost( Host ):
617

    
618
    "CPU limited host"
619

    
620
    def __init__( self, name, sched='cfs', **kwargs ):
621
        Host.__init__( self, name, **kwargs )
622
        # Initialize class if necessary
623
        if not CPULimitedHost.inited:
624
            CPULimitedHost.init()
625
        # Create a cgroup and move shell into it
626
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
627
        errFail( 'cgcreate -g ' + self.cgroup )
628
        # We don't add ourselves to a cpuset because you must
629
        # specify the cpu and memory placement first
630
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
631
        # BL: Setting the correct period/quota is tricky, particularly
632
        # for RT. RT allows very small quotas, but the overhead
633
        # seems to be high. CFS has a mininimum quota of 1 ms, but
634
        # still does better with larger period values.
635
        self.period_us = kwargs.get( 'period_us', 100000 )
636
        self.sched = sched
637
        if self.sched == 'rt':
638
            release = quietRun( 'uname -r' ).strip('\r\n')
639
            output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
640
            if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
641
                error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
642
                exit( 1 )
643
        self.rtprio = 20
644

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

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

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

    
668
    def popen( self, *args, **kwargs ):
669
        """Return a Popen() object in node's namespace
670
           args: Popen() args, single list, or string
671
           kwargs: Popen() keyword args"""
672
        # Tell mnexec to execute command in our cgroup
673
        mncmd = [ 'mnexec', '-g', self.name,
674
                  '-da', str( self.pid ) ]
675
        if self.sched == 'rt':
676
            mncmd += [ '-r', str( self.rtprio ) ]
677
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
678

    
679
    def cleanup( self ):
680
        "Clean up Node, then clean up our cgroup"
681
        super( CPULimitedHost, self ).cleanup()
682
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
683

    
684
    def chrt( self ):
685
        "Set RT scheduling priority"
686
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
687
        result = quietRun( 'chrt -p %s' % self.pid )
688
        firstline = result.split( '\n' )[ 0 ]
689
        lastword = firstline.split( ' ' )[ -1 ]
690
        if lastword != 'SCHED_RR':
691
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
692
        return lastword
693

    
694
    def rtInfo( self, f ):
695
        "Internal method: return parameters for RT bandwidth"
696
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
697
        # RT uses wall clock time for period and quota
698
        quota = int( self.period_us * f )
699
        return pstr, qstr, self.period_us, quota
700

    
701
    def cfsInfo( self, f):
702
        "Internal method: return parameters for CFS bandwidth"
703
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
704
        # CFS uses wall clock time for period and CPU time for quota.
705
        quota = int( self.period_us * f * numCores() )
706
        period = self.period_us
707
        if f > 0 and quota < 1000:
708
            debug( '(cfsInfo: increasing default period) ' )
709
            quota = 1000
710
            period = int( quota / f / numCores() )
711
        return pstr, qstr, period, quota
712

    
713
    # BL comment:
714
    # This may not be the right API,
715
    # since it doesn't specify CPU bandwidth in "absolute"
716
    # units the way link bandwidth is specified.
717
    # We should use MIPS or SPECINT or something instead.
718
    # Alternatively, we should change from system fraction
719
    # to CPU seconds per second, essentially assuming that
720
    # all CPUs are the same.
721

    
722
    def setCPUFrac( self, f=-1, sched=None):
723
        """Set overall CPU fraction for this host
724
           f: CPU bandwidth limit (fraction)
725
           sched: 'rt' or 'cfs'
726
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
727
        if not f:
728
            return
729
        if not sched:
730
            sched = self.sched
731
        if sched == 'rt':
732
            pstr, qstr, period, quota = self.rtInfo( f )
733
        elif sched == 'cfs':
734
            pstr, qstr, period, quota = self.cfsInfo( f )
735
        else:
736
            return
737
        if quota < 0:
738
            # Reset to unlimited
739
            quota = -1
740
        # Set cgroup's period and quota
741
        self.cgroupSet( pstr, period )
742
        self.cgroupSet( qstr, quota )
743
        if sched == 'rt':
744
            # Set RT priority if necessary
745
            self.chrt()
746
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
747

    
748
    def setCPUs( self, cores, mems=0 ):
749
        "Specify (real) cores that our cgroup can run on"
750
        if type( cores ) is list:
751
            cores = ','.join( [ str( c ) for c in cores ] )
752
        self.cgroupSet( resource='cpuset', param='cpus',
753
                        value=cores )
754
        # Memory placement is probably not relevant, but we
755
        # must specify it anyway
756
        self.cgroupSet( resource='cpuset', param='mems',
757
                        value=mems)
758
        # We have to do this here after we've specified
759
        # cpus and mems
760
        errFail( 'cgclassify -g cpuset:/%s %s' % (
761
                 self.name, self.pid ) )
762

    
763
    def config( self, cpu=None, cores=None, **params ):
764
        """cpu: desired overall system CPU fraction
765
           cores: (real) core(s) this host can run on
766
           params: parameters for Node.config()"""
767
        r = Node.config( self, **params )
768
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
769
        # that seems redundant
770
        self.setParam( r, 'setCPUFrac', cpu=cpu )
771
        self.setParam( r, 'setCPUs', cores=cores )
772
        return r
773

    
774
    inited = False
775

    
776
    @classmethod
777
    def init( cls ):
778
        "Initialization for CPULimitedHost class"
779
        mountCgroups()
780
        cls.inited = True
781

    
782

    
783
# Some important things to note:
784
#
785
# The "IP" address which setIP() assigns to the switch is not
786
# an "IP address for the switch" in the sense of IP routing.
787
# Rather, it is the IP address for the control interface,
788
# on the control network, and it is only relevant to the
789
# controller. If you are running in the root namespace
790
# (which is the only way to run OVS at the moment), the
791
# control interface is the loopback interface, and you
792
# normally never want to change its IP address!
793
#
794
# In general, you NEVER want to attempt to use Linux's
795
# network stack (i.e. ifconfig) to "assign" an IP address or
796
# MAC address to a switch data port. Instead, you "assign"
797
# the IP and MAC addresses in the controller by specifying
798
# packets that you want to receive or send. The "MAC" address
799
# reported by ifconfig for a switch data port is essentially
800
# meaningless. It is important to understand this if you
801
# want to create a functional router using OpenFlow.
802

    
803
class Switch( Node ):
804
    """A Switch is a Node that is running (or has execed?)
805
       an OpenFlow switch."""
806

    
807
    portBase = 1  # Switches start with port 1 in OpenFlow
808
    dpidLen = 16  # digits in dpid passed to switch
809

    
810
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
811
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
812
           opts: additional switch options
813
           listenPort: port to listen on for dpctl connections"""
814
        Node.__init__( self, name, **params )
815
        self.dpid = self.defaultDpid( dpid )
816
        self.opts = opts
817
        self.listenPort = listenPort
818
        if not self.inNamespace:
819
            self.controlIntf = Intf( 'lo', self, port=0 )
820

    
821
    def defaultDpid( self, dpid=None ):
822
        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
823
        if dpid:
824
            # Remove any colons and make sure it's a good hex number
825
            dpid = dpid.translate( None, ':' )
826
            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
827
        else:
828
            # Use hex of the first number in the switch name
829
            nums = re.findall( r'\d+', self.name )
830
            if nums:
831
                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
832
            else:
833
                raise Exception( 'Unable to derive default datapath ID - '
834
                                 'please either specify a dpid or use a '
835
                                 'canonical switch name such as s23.' )
836
        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
837

    
838
    def defaultIntf( self ):
839
        "Return control interface"
840
        if self.controlIntf:
841
            return self.controlIntf
842
        else:
843
            return Node.defaultIntf( self )
844

    
845
    def sendCmd( self, *cmd, **kwargs ):
846
        """Send command to Node.
847
           cmd: string"""
848
        kwargs.setdefault( 'printPid', False )
849
        if not self.execed:
850
            return Node.sendCmd( self, *cmd, **kwargs )
851
        else:
852
            error( '*** Error: %s has execed and cannot accept commands' %
853
                   self.name )
854

    
855
    def connected( self ):
856
        "Is the switch connected to a controller? (override this method)"
857
        return False and self  # satisfy pylint
858

    
859
    def __repr__( self ):
860
        "More informative string representation"
861
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
862
                              for i in self.intfList() ] ) )
863
        return '<%s %s: %s pid=%s> ' % (
864
            self.__class__.__name__, self.name, intfs, self.pid )
865

    
866
class UserSwitch( Switch ):
867
    "User-space switch."
868

    
869
    dpidLen = 12
870

    
871
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
872
        """Init.
873
           name: name for the switch
874
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
875
        Switch.__init__( self, name, **kwargs )
876
        pathCheck( 'ofdatapath', 'ofprotocol',
877
                   moduleName='the OpenFlow reference user switch' +
878
                              '(openflow.org)' )
879
        if self.listenPort:
880
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
881
        else:
882
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
883
        self.dpopts = dpopts
884

    
885
    @classmethod
886
    def setup( cls ):
887
        "Ensure any dependencies are loaded; if not, try to load them."
888
        if not os.path.exists( '/dev/net/tun' ):
889
            moduleDeps( add=TUN )
890

    
891
    def dpctl( self, *args ):
892
        "Run dpctl command"
893
        listenAddr = None
894
        if not self.listenPort:
895
            listenAddr = 'unix:/tmp/%s.listen' % self.name
896
        else:
897
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
898
        return self.cmd( 'dpctl ' + ' '.join( args ) +
899
                         ' ' + listenAddr )
900

    
901
    def connected( self ):
902
        "Is the switch connected to a controller?"
903
        status = self.dpctl( 'status' )
904
        return ( 'remote.is-connected=true' in status and
905
                 'local.is-connected=true' in status )
906

    
907
    @staticmethod
908
    def TCReapply( intf ):
909
        """Unfortunately user switch and Mininet are fighting
910
           over tc queuing disciplines. To resolve the conflict,
911
           we re-create the user switch's configuration, but as a
912
           leaf of the TCIntf-created configuration."""
913
        if type( intf ) is TCIntf:
914
            ifspeed = 10000000000 # 10 Gbps
915
            minspeed = ifspeed * 0.001
916

    
917
            res = intf.config( **intf.params )
918

    
919
            if res is None: # link may not have TC parameters
920
                return
921

    
922
            # Re-add qdisc, root, and default classes user switch created, but
923
            # with new parent, as setup by Mininet's TCIntf
924
            parent = res['parent']
925
            intf.tc( "%s qdisc add dev %s " + parent +
926
                     " handle 1: htb default 0xfffe" )
927
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
928
                     + str(ifspeed) )
929
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
930
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
931

    
932
    def start( self, controllers ):
933
        """Start OpenFlow reference user datapath.
934
           Log to /tmp/sN-{ofd,ofp}.log.
935
           controllers: list of controller objects"""
936
        # Add controllers
937
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
938
                            for c in controllers ] )
939
        ofdlog = '/tmp/' + self.name + '-ofd.log'
940
        ofplog = '/tmp/' + self.name + '-ofp.log'
941
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
942
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
943
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
944
                  self.dpopts +
945
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
946
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
947
                  ' ' + clist +
948
                  ' --fail=closed ' + self.opts +
949
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
950
        if "no-slicing" not in self.dpopts:
951
            # Only TCReapply if slicing is enable
952
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
953
            for intf in self.intfList():
954
                if not intf.IP():
955
                    self.TCReapply( intf )
956

    
957
    def stop( self ):
958
        "Stop OpenFlow reference user datapath."
959
        self.cmd( 'kill %ofdatapath' )
960
        self.cmd( 'kill %ofprotocol' )
961
        self.deleteIntfs()
962

    
963

    
964
class OVSLegacyKernelSwitch( Switch ):
965
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
966
       Currently only works in the root namespace."""
967

    
968
    def __init__( self, name, dp=None, **kwargs ):
969
        """Init.
970
           name: name for switch
971
           dp: netlink id (0, 1, 2, ...)
972
           defaultMAC: default MAC as unsigned int; random value if None"""
973
        Switch.__init__( self, name, **kwargs )
974
        self.dp = dp if dp else self.name
975
        self.intf = self.dp
976
        if self.inNamespace:
977
            error( "OVSKernelSwitch currently only works"
978
                   " in the root namespace.\n" )
979
            exit( 1 )
980

    
981
    @classmethod
982
    def setup( cls ):
983
        "Ensure any dependencies are loaded; if not, try to load them."
984
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
985
                   moduleName='Open vSwitch (openvswitch.org)')
986
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
987

    
988
    def start( self, controllers ):
989
        "Start up kernel datapath."
990
        ofplog = '/tmp/' + self.name + '-ofp.log'
991
        # Delete local datapath if it exists;
992
        # then create a new one monitoring the given interfaces
993
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
994
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
995
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
996
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
997
        # Run protocol daemon
998
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
999
                            for c in controllers ] )
1000
        self.cmd( 'ovs-openflowd ' + self.dp +
1001
                  ' ' + clist +
1002
                  ' --fail=secure ' + self.opts +
1003
                  ' --datapath-id=' + self.dpid +
1004
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
1005
        self.execed = False
1006

    
1007
    def stop( self ):
1008
        "Terminate kernel datapath."
1009
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
1010
        self.cmd( 'kill %ovs-openflowd' )
1011
        self.deleteIntfs()
1012

    
1013

    
1014
class OVSSwitch( Switch ):
1015
    "Open vSwitch switch. Depends on ovs-vsctl."
1016

    
1017
    def __init__( self, name, failMode='secure', datapath='kernel',
1018
                 inband=False, protocols=None, **params ):
1019
        """Init.
1020
           name: name for switch
1021
           failMode: controller loss behavior (secure|open)
1022
           datapath: userspace or kernel mode (kernel|user)
1023
           inband: use in-band control (False)"""
1024
        Switch.__init__( self, name, **params )
1025
        self.failMode = failMode
1026
        self.datapath = datapath
1027
        self.inband = inband
1028
        self.protocols = protocols
1029

    
1030
    @classmethod
1031
    def setup( cls ):
1032
        "Make sure Open vSwitch is installed and working"
1033
        pathCheck( 'ovs-vsctl',
1034
                   moduleName='Open vSwitch (openvswitch.org)')
1035
        # This should no longer be needed, and it breaks
1036
        # with OVS 1.7 which has renamed the kernel module:
1037
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1038
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1039
        if exitcode:
1040
            error( out + err +
1041
                   'ovs-vsctl exited with code %d\n' % exitcode +
1042
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1043
                   'Make sure that Open vSwitch is installed, '
1044
                   'that ovsdb-server is running, and that\n'
1045
                   '"ovs-vsctl show" works correctly.\n'
1046
                   'You may wish to try '
1047
                   '"service openvswitch-switch start".\n' )
1048
            exit( 1 )
1049
        info = quietRun( 'ovs-vsctl --version' )
1050
        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
1051

    
1052
    @classmethod
1053
    def isOldOVS( cls ):
1054
        return ( StrictVersion( cls.OVSVersion ) <
1055
             StrictVersion( '1.10' ) )
1056

    
1057
    @classmethod
1058
    def batchShutdown( cls, switches ):
1059
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1060
        quietRun( 'ovs-vsctl ' +
1061
                  ' -- '.join( '--if-exists del-br %s' % s
1062
                               for s in switches ) )
1063

    
1064
    def dpctl( self, *args ):
1065
        "Run ovs-ofctl command"
1066
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1067

    
1068
    @staticmethod
1069
    def TCReapply( intf ):
1070
        """Unfortunately OVS and Mininet are fighting
1071
           over tc queuing disciplines. As a quick hack/
1072
           workaround, we clear OVS's and reapply our own."""
1073
        if type( intf ) is TCIntf:
1074
            intf.config( **intf.params )
1075

    
1076
    def attach( self, intf ):
1077
        "Connect a data port"
1078
        self.cmd( 'ovs-vsctl add-port', self, intf )
1079
        self.cmd( 'ifconfig', intf, 'up' )
1080
        self.TCReapply( intf )
1081

    
1082
    def detach( self, intf ):
1083
        "Disconnect a data port"
1084
        self.cmd( 'ovs-vsctl del-port', self, intf )
1085

    
1086
    def controllerUUIDs( self ):
1087
        "Return ovsdb UUIDs for our controllers"
1088
        uuids = []
1089
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1090
                               'Controller' ).strip()
1091
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1092
            controllers = controllers[ 1 : -1 ]
1093
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1094
        return uuids
1095

    
1096
    def connected( self ):
1097
        "Are we connected to at least one of our controllers?"
1098
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1099
                                         uuid, 'is_connected' )
1100
                    for uuid in self.controllerUUIDs() ]
1101
        return reduce( or_, results, False )
1102

    
1103
    def start( self, controllers ):
1104
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1105
        if self.inNamespace:
1106
            raise Exception(
1107
                'OVS kernel switch does not work in a namespace' )
1108
        # Annoyingly, --if-exists option seems not to work
1109
        self.cmd( 'ovs-vsctl del-br', self )
1110
        int( self.dpid, 16 ) # DPID must be a hex string
1111
        # Interfaces and controllers
1112
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
1113
                          '-- set Interface %s ' % intf +
1114
                          'ofport_request=%s ' % self.ports[ intf ]
1115
                         for intf in self.intfList()
1116
                         if self.ports[ intf ] and not intf.IP() )
1117
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1118
                         for c in controllers )
1119
        if self.listenPort:
1120
            clist += ' ptcp:%s' % self.listenPort
1121
        # Construct big ovs-vsctl command for new versions of OVS
1122
        if not self.isOldOVS():
1123
            cmd = ( 'ovs-vsctl add-br %s ' % self +
1124
                    '-- set Bridge %s ' % self +
1125
                    'other_config:datapath-id=%s ' % self.dpid +
1126
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1127
                    intfs +
1128
                    '-- set-controller %s %s ' % ( self, clist ) )
1129
        # Construct ovs-vsctl commands for old versions of OVS
1130
        else:
1131
            self.cmd( 'ovs-vsctl add-br', self )
1132
            for intf in self.intfList():
1133
                if not intf.IP():
1134
                    self.cmd( 'ovs-vsctl add-port', self, intf )
1135
            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
1136
                    'other_config:datapath-id=%s ' % self.dpid +
1137
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1138
                    '-- set-controller %s %s ' % ( self, clist ) )
1139
        if not self.inband:
1140
            cmd += ( '-- set bridge %s '
1141
                     'other-config:disable-in-band=true ' % self )
1142
        if self.datapath == 'user':
1143
            cmd += '-- set bridge %s datapath_type=netdev ' % self
1144
        if self.protocols:
1145
            cmd += '-- set bridge %s protocols=%s' % ( self, self.protocols )
1146
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1147
        for uuid in self.controllerUUIDs():
1148
            if uuid.count( '-' ) != 4:
1149
                # Doesn't look like a UUID
1150
                continue
1151
            uuid = uuid.strip()
1152
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1153
        # Do it!!
1154
        self.cmd( cmd )
1155
        for intf in self.intfList():
1156
            self.TCReapply( intf )
1157

    
1158

    
1159
    def stop( self ):
1160
        "Terminate OVS switch."
1161
        self.cmd( 'ovs-vsctl del-br', self )
1162
        if self.datapath == 'user':
1163
            self.cmd( 'ip link del', self )
1164
        self.deleteIntfs()
1165

    
1166
OVSKernelSwitch = OVSSwitch
1167

    
1168

    
1169
class IVSSwitch(Switch):
1170
    """IVS virtual switch"""
1171

    
1172
    def __init__( self, name, verbose=True, **kwargs ):
1173
        Switch.__init__( self, name, **kwargs )
1174
        self.verbose = verbose
1175

    
1176
    @classmethod
1177
    def setup( cls ):
1178
        "Make sure IVS is installed"
1179
        pathCheck( 'ivs-ctl', 'ivs',
1180
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1181
        out, err, exitcode = errRun( 'ivs-ctl show' )
1182
        if exitcode:
1183
            error( out + err +
1184
                   'ivs-ctl exited with code %d\n' % exitcode +
1185
                   '*** The openvswitch kernel module might '
1186
                   'not be loaded. Try modprobe openvswitch.\n' )
1187
            exit( 1 )
1188

    
1189
    @classmethod
1190
    def batchShutdown( cls, switches ):
1191
        "Kill each IVS switch, to be waited on later in stop()"
1192
        for switch in switches:
1193
            switch.cmd( 'kill %ivs' )
1194

    
1195
    def start( self, controllers ):
1196
        "Start up a new IVS switch"
1197
        args = ['ivs']
1198
        args.extend( ['--name', self.name] )
1199
        args.extend( ['--dpid', self.dpid] )
1200
        if self.verbose:
1201
            args.extend( ['--verbose'] )
1202
        for intf in self.intfs.values():
1203
            if not intf.IP():
1204
                args.extend( ['-i', intf.name] )
1205
        for c in controllers:
1206
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1207
        if self.listenPort:
1208
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1209
        args.append( self.opts )
1210

    
1211
        logfile = '/tmp/ivs.%s.log' % self.name
1212

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

    
1215
    def stop( self ):
1216
        "Terminate IVS switch."
1217
        self.cmd( 'kill %ivs' )
1218
        self.cmd( 'wait' )
1219
        self.deleteIntfs()
1220

    
1221
    def attach( self, intf ):
1222
        "Connect a data port"
1223
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1224

    
1225
    def detach( self, intf ):
1226
        "Disconnect a data port"
1227
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1228

    
1229
    def dpctl( self, *args ):
1230
        "Run dpctl command"
1231
        if not self.listenPort:
1232
            return "can't run dpctl without passive listening port"
1233
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1234
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1235

    
1236

    
1237
class Controller( Node ):
1238
    """A Controller is a Node that is running (or has execed?) an
1239
       OpenFlow controller."""
1240

    
1241
    def __init__( self, name, inNamespace=False, command='controller',
1242
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1243
                  port=6633, protocol='tcp', **params ):
1244
        self.command = command
1245
        self.cargs = cargs
1246
        self.cdir = cdir
1247
        self.ip = ip
1248
        self.port = port
1249
        self.protocol = protocol
1250
        Node.__init__( self, name, inNamespace=inNamespace,
1251
                       ip=ip, **params  )
1252
        self.checkListening()
1253

    
1254
    def checkListening( self ):
1255
        "Make sure no controllers are running on our port"
1256
        # Verify that Telnet is installed first:
1257
        out, _err, returnCode = errRun( "which telnet" )
1258
        if 'telnet' not in out or returnCode != 0:
1259
            raise Exception( "Error running telnet to check for listening "
1260
                             "controllers; please check that it is "
1261
                             "installed." )
1262
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1263
                              ( self.ip, self.port ) )
1264
        if 'Connected' in listening:
1265
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1266
            pstr = ':%d ' % self.port
1267
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1268
            raise Exception( "Please shut down the controller which is"
1269
                             " running on port %d:\n" % self.port +
1270
                             '\n'.join( clist ) )
1271

    
1272
    def start( self ):
1273
        """Start <controller> <args> on controller.
1274
           Log to /tmp/cN.log"""
1275
        pathCheck( self.command )
1276
        cout = '/tmp/' + self.name + '.log'
1277
        if self.cdir is not None:
1278
            self.cmd( 'cd ' + self.cdir )
1279
        self.cmd( self.command + ' ' + self.cargs % self.port +
1280
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1281
        self.execed = False
1282

    
1283
    def stop( self ):
1284
        "Stop controller."
1285
        self.cmd( 'kill %' + self.command )
1286
        self.cmd( 'wait %' + self.command )
1287
        self.terminate()
1288

    
1289
    def IP( self, intf=None ):
1290
        "Return IP address of the Controller"
1291
        if self.intfs:
1292
            ip = Node.IP( self, intf )
1293
        else:
1294
            ip = self.ip
1295
        return ip
1296

    
1297
    def __repr__( self ):
1298
        "More informative string representation"
1299
        return '<%s %s: %s:%s pid=%s> ' % (
1300
            self.__class__.__name__, self.name,
1301
            self.IP(), self.port, self.pid )
1302
    @classmethod
1303
    def isAvailable( self ):
1304
        return quietRun( 'which controller' )
1305

    
1306
class OVSController( Controller ):
1307
    "Open vSwitch controller"
1308
    def __init__( self, name, command='ovs-controller', **kwargs ):
1309
        if quietRun( 'which test-controller' ):
1310
            command = 'test-controller'
1311
        Controller.__init__( self, name, command=command, **kwargs )
1312
    @classmethod
1313
    def isAvailable( self ):
1314
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1315

    
1316
class NOX( Controller ):
1317
    "Controller to run a NOX application."
1318

    
1319
    def __init__( self, name, *noxArgs, **kwargs ):
1320
        """Init.
1321
           name: name to give controller
1322
           noxArgs: arguments (strings) to pass to NOX"""
1323
        if not noxArgs:
1324
            warn( 'warning: no NOX modules specified; '
1325
                  'running packetdump only\n' )
1326
            noxArgs = [ 'packetdump' ]
1327
        elif type( noxArgs ) not in ( list, tuple ):
1328
            noxArgs = [ noxArgs ]
1329

    
1330
        if 'NOX_CORE_DIR' not in os.environ:
1331
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1332
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1333

    
1334
        Controller.__init__( self, name,
1335
                             command=noxCoreDir + '/nox_core',
1336
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1337
                             ' '.join( noxArgs ),
1338
                             cdir=noxCoreDir,
1339
                             **kwargs )
1340

    
1341
class RYU( Controller ):
1342
    "Controller to run Ryu application"
1343
    def __init__( self, name, *ryuArgs, **kwargs ):
1344
        """Init.
1345
        name: name to give controller.
1346
        ryuArgs: arguments and modules to pass to Ryu"""
1347
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1348
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1349
        if not ryuArgs:
1350
            warn( 'warning: no Ryu modules specified; '
1351
                  'running simple_switch only\n' )
1352
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1353
        elif type( ryuArgs ) not in ( list, tuple ):
1354
            ryuArgs = [ ryuArgs ]
1355

    
1356
        Controller.__init__( self, name,
1357
                         command='ryu-manager',
1358
                         cargs='--ofp-tcp-listen-port %s ' + 
1359
                         ' '.join( ryuArgs ),
1360
                         cdir=ryuCoreDir,
1361
                         **kwargs )
1362

    
1363
class RemoteController( Controller ):
1364
    "Controller running outside of Mininet's control."
1365

    
1366
    def __init__( self, name, ip='127.0.0.1',
1367
                  port=6633, **kwargs):
1368
        """Init.
1369
           name: name to give controller
1370
           ip: the IP address where the remote controller is
1371
           listening
1372
           port: the port where the remote controller is listening"""
1373
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1374

    
1375
    def start( self ):
1376
        "Overridden to do nothing."
1377
        return
1378

    
1379
    def stop( self ):
1380
        "Overridden to do nothing."
1381
        return
1382

    
1383
    def checkListening( self ):
1384
        "Warn if remote controller is not accessible"
1385
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1386
                              ( self.ip, self.port ) )
1387
        if 'Connected' not in listening:
1388
            warn( "Unable to contact the remote controller"
1389
                  " at %s:%d\n" % ( self.ip, self.port ) )
1390

    
1391
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1392
    "find any controller that is available and run it"
1393
    for controller in order:
1394
        if controller.isAvailable():
1395
            return controller( name, **kwargs )