Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 6a363f65

History | View | Annotate | Download (53.2 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 unmountPrivateDirs( self ):
184
        "mount private directories"
185
        for directory in self.privateDirs:
186
            if isinstance( directory, tuple ):
187
                self.cmd( 'umount ', directory[ 0 ] )
188
            else:
189
                self.cmd( 'umount ', directory )
190

    
191
    def _popen( self, cmd, **params ):
192
        """Internal method: spawn and return a process
193
            cmd: command to run (list)
194
            params: parameters to Popen()"""
195
        return Popen( cmd, **params )
196

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

    
205
    # Subshell I/O, commands and control
206

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

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

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

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

    
246
    def stop( self ):
247
        "Stop node."
248
        self.terminate()
249

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

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

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

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

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

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

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

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

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

    
387
    # Interface management, configuration, and routing
388

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

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

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

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

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

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

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

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

    
471
    # Routing support
472

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

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

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

    
497
    # Convenience and configuration methods
498

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

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

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

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

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

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

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

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

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

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

    
582
    # Other methods
583

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

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

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

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

    
603
    # Automatic class setup support
604

    
605
    isSetup = False
606

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

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

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

    
625
class CPULimitedHost( Host ):
626

    
627
    "CPU limited host"
628

    
629
    def __init__( self, name, sched='cfs', **kwargs ):
630
        Host.__init__( self, name, **kwargs )
631
        # Initialize class if necessary
632
        if not CPULimitedHost.inited:
633
            CPULimitedHost.init()
634
        # Create a cgroup and move shell into it
635
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
636
        errFail( 'cgcreate -g ' + self.cgroup )
637
        # We don't add ourselves to a cpuset because you must
638
        # specify the cpu and memory placement first
639
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
640
        # BL: Setting the correct period/quota is tricky, particularly
641
        # for RT. RT allows very small quotas, but the overhead
642
        # seems to be high. CFS has a mininimum quota of 1 ms, but
643
        # still does better with larger period values.
644
        self.period_us = kwargs.get( 'period_us', 100000 )
645
        self.sched = sched
646
        if self.sched == 'rt':
647
            release = quietRun( 'uname -r' ).strip('\r\n')
648
            output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
649
            if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
650
                error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
651
                exit( 1 )
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 self.sched == 'rt':
685
            mncmd += [ '-r', str( self.rtprio ) ]
686
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
687

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

    
693
    def chrt( self ):
694
        "Set RT scheduling priority"
695
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
696
        result = quietRun( 'chrt -p %s' % self.pid )
697
        firstline = result.split( '\n' )[ 0 ]
698
        lastword = firstline.split( ' ' )[ -1 ]
699
        if lastword != 'SCHED_RR':
700
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
701
        return lastword
702

    
703
    def rtInfo( self, f ):
704
        "Internal method: return parameters for RT bandwidth"
705
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
706
        # RT uses wall clock time for period and quota
707
        quota = int( self.period_us * f )
708
        return pstr, qstr, self.period_us, quota
709

    
710
    def cfsInfo( self, f):
711
        "Internal method: return parameters for CFS bandwidth"
712
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
713
        # CFS uses wall clock time for period and CPU time for quota.
714
        quota = int( self.period_us * f * numCores() )
715
        period = self.period_us
716
        if f > 0 and quota < 1000:
717
            debug( '(cfsInfo: increasing default period) ' )
718
            quota = 1000
719
            period = int( quota / f / numCores() )
720
        return pstr, qstr, period, quota
721

    
722
    # BL comment:
723
    # This may not be the right API,
724
    # since it doesn't specify CPU bandwidth in "absolute"
725
    # units the way link bandwidth is specified.
726
    # We should use MIPS or SPECINT or something instead.
727
    # Alternatively, we should change from system fraction
728
    # to CPU seconds per second, essentially assuming that
729
    # all CPUs are the same.
730

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

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

    
772
    def config( self, cpu=None, cores=None, **params ):
773
        """cpu: desired overall system CPU fraction
774
           cores: (real) core(s) this host can run on
775
           params: parameters for Node.config()"""
776
        r = Node.config( self, **params )
777
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
778
        # that seems redundant
779
        self.setParam( r, 'setCPUFrac', cpu=cpu )
780
        self.setParam( r, 'setCPUs', cores=cores )
781
        return r
782

    
783
    inited = False
784

    
785
    @classmethod
786
    def init( cls ):
787
        "Initialization for CPULimitedHost class"
788
        mountCgroups()
789
        cls.inited = True
790

    
791

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

    
812
class Switch( Node ):
813
    """A Switch is a Node that is running (or has execed?)
814
       an OpenFlow switch."""
815

    
816
    portBase = 1  # Switches start with port 1 in OpenFlow
817
    dpidLen = 16  # digits in dpid passed to switch
818

    
819
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
820
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
821
           opts: additional switch options
822
           listenPort: port to listen on for dpctl connections"""
823
        Node.__init__( self, name, **params )
824
        self.dpid = self.defaultDpid( dpid )
825
        self.opts = opts
826
        self.listenPort = listenPort
827
        if not self.inNamespace:
828
            self.controlIntf = Intf( 'lo', self, port=0 )
829

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

    
847
    def defaultIntf( self ):
848
        "Return control interface"
849
        if self.controlIntf:
850
            return self.controlIntf
851
        else:
852
            return Node.defaultIntf( self )
853

    
854
    def sendCmd( self, *cmd, **kwargs ):
855
        """Send command to Node.
856
           cmd: string"""
857
        kwargs.setdefault( 'printPid', False )
858
        if not self.execed:
859
            return Node.sendCmd( self, *cmd, **kwargs )
860
        else:
861
            error( '*** Error: %s has execed and cannot accept commands' %
862
                   self.name )
863

    
864
    def connected( self ):
865
        "Is the switch connected to a controller? (override this method)"
866
        return False and self  # satisfy pylint
867

    
868
    def __repr__( self ):
869
        "More informative string representation"
870
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
871
                              for i in self.intfList() ] ) )
872
        return '<%s %s: %s pid=%s> ' % (
873
            self.__class__.__name__, self.name, intfs, self.pid )
874

    
875
class UserSwitch( Switch ):
876
    "User-space switch."
877

    
878
    dpidLen = 12
879

    
880
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
881
        """Init.
882
           name: name for the switch
883
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
884
        Switch.__init__( self, name, **kwargs )
885
        pathCheck( 'ofdatapath', 'ofprotocol',
886
                   moduleName='the OpenFlow reference user switch' +
887
                              '(openflow.org)' )
888
        if self.listenPort:
889
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
890
        else:
891
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
892
        self.dpopts = dpopts
893

    
894
    @classmethod
895
    def setup( cls ):
896
        "Ensure any dependencies are loaded; if not, try to load them."
897
        if not os.path.exists( '/dev/net/tun' ):
898
            moduleDeps( add=TUN )
899

    
900
    def dpctl( self, *args ):
901
        "Run dpctl command"
902
        listenAddr = None
903
        if not self.listenPort:
904
            listenAddr = 'unix:/tmp/%s.listen' % self.name
905
        else:
906
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
907
        return self.cmd( 'dpctl ' + ' '.join( args ) +
908
                         ' ' + listenAddr )
909

    
910
    def connected( self ):
911
        "Is the switch connected to a controller?"
912
        status = self.dpctl( 'status' )
913
        return ( 'remote.is-connected=true' in status and
914
                 'local.is-connected=true' in status )
915

    
916
    @staticmethod
917
    def TCReapply( intf ):
918
        """Unfortunately user switch and Mininet are fighting
919
           over tc queuing disciplines. To resolve the conflict,
920
           we re-create the user switch's configuration, but as a
921
           leaf of the TCIntf-created configuration."""
922
        if type( intf ) is TCIntf:
923
            ifspeed = 10000000000 # 10 Gbps
924
            minspeed = ifspeed * 0.001
925

    
926
            res = intf.config( **intf.params )
927

    
928
            if res is None: # link may not have TC parameters
929
                return
930

    
931
            # Re-add qdisc, root, and default classes user switch created, but
932
            # with new parent, as setup by Mininet's TCIntf
933
            parent = res['parent']
934
            intf.tc( "%s qdisc add dev %s " + parent +
935
                     " handle 1: htb default 0xfffe" )
936
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
937
                     + str(ifspeed) )
938
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
939
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
940

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

    
966
    def stop( self ):
967
        "Stop OpenFlow reference user datapath."
968
        self.cmd( 'kill %ofdatapath' )
969
        self.cmd( 'kill %ofprotocol' )
970
        self.deleteIntfs()
971

    
972

    
973
class OVSLegacyKernelSwitch( Switch ):
974
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
975
       Currently only works in the root namespace."""
976

    
977
    def __init__( self, name, dp=None, **kwargs ):
978
        """Init.
979
           name: name for switch
980
           dp: netlink id (0, 1, 2, ...)
981
           defaultMAC: default MAC as unsigned int; random value if None"""
982
        Switch.__init__( self, name, **kwargs )
983
        self.dp = dp if dp else self.name
984
        self.intf = self.dp
985
        if self.inNamespace:
986
            error( "OVSKernelSwitch currently only works"
987
                   " in the root namespace.\n" )
988
            exit( 1 )
989

    
990
    @classmethod
991
    def setup( cls ):
992
        "Ensure any dependencies are loaded; if not, try to load them."
993
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
994
                   moduleName='Open vSwitch (openvswitch.org)')
995
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
996

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

    
1016
    def stop( self ):
1017
        "Terminate kernel datapath."
1018
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
1019
        self.cmd( 'kill %ovs-openflowd' )
1020
        self.deleteIntfs()
1021

    
1022

    
1023
class OVSSwitch( Switch ):
1024
    "Open vSwitch switch. Depends on ovs-vsctl."
1025

    
1026
    def __init__( self, name, failMode='secure', datapath='kernel',
1027
                 inband=False, protocols=None, **params ):
1028
        """Init.
1029
           name: name for switch
1030
           failMode: controller loss behavior (secure|open)
1031
           datapath: userspace or kernel mode (kernel|user)
1032
           inband: use in-band control (False)"""
1033
        Switch.__init__( self, name, **params )
1034
        self.failMode = failMode
1035
        self.datapath = datapath
1036
        self.inband = inband
1037
        self.protocols = protocols
1038

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

    
1061
    @classmethod
1062
    def isOldOVS( cls ):
1063
        return ( StrictVersion( cls.OVSVersion ) <
1064
             StrictVersion( '1.10' ) )
1065

    
1066
    @classmethod
1067
    def batchShutdown( cls, switches ):
1068
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1069
        quietRun( 'ovs-vsctl ' +
1070
                  ' -- '.join( '--if-exists del-br %s' % s
1071
                               for s in switches ) )
1072

    
1073
    def dpctl( self, *args ):
1074
        "Run ovs-ofctl command"
1075
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1076

    
1077
    @staticmethod
1078
    def TCReapply( intf ):
1079
        """Unfortunately OVS and Mininet are fighting
1080
           over tc queuing disciplines. As a quick hack/
1081
           workaround, we clear OVS's and reapply our own."""
1082
        if type( intf ) is TCIntf:
1083
            intf.config( **intf.params )
1084

    
1085
    def attach( self, intf ):
1086
        "Connect a data port"
1087
        self.cmd( 'ovs-vsctl add-port', self, intf )
1088
        self.cmd( 'ifconfig', intf, 'up' )
1089
        self.TCReapply( intf )
1090

    
1091
    def detach( self, intf ):
1092
        "Disconnect a data port"
1093
        self.cmd( 'ovs-vsctl del-port', self, intf )
1094

    
1095
    def controllerUUIDs( self ):
1096
        "Return ovsdb UUIDs for our controllers"
1097
        uuids = []
1098
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1099
                               'Controller' ).strip()
1100
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1101
            controllers = controllers[ 1 : -1 ]
1102
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1103
        return uuids
1104

    
1105
    def connected( self ):
1106
        "Are we connected to at least one of our controllers?"
1107
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1108
                                         uuid, 'is_connected' )
1109
                    for uuid in self.controllerUUIDs() ]
1110
        return reduce( or_, results, False )
1111

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

    
1167

    
1168
    def stop( self ):
1169
        "Terminate OVS switch."
1170
        self.cmd( 'ovs-vsctl del-br', self )
1171
        if self.datapath == 'user':
1172
            self.cmd( 'ip link del', self )
1173
        self.deleteIntfs()
1174

    
1175
OVSKernelSwitch = OVSSwitch
1176

    
1177

    
1178
class IVSSwitch(Switch):
1179
    """IVS virtual switch"""
1180

    
1181
    def __init__( self, name, verbose=True, **kwargs ):
1182
        Switch.__init__( self, name, **kwargs )
1183
        self.verbose = verbose
1184

    
1185
    @classmethod
1186
    def setup( cls ):
1187
        "Make sure IVS is installed"
1188
        pathCheck( 'ivs-ctl', 'ivs',
1189
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1190
        out, err, exitcode = errRun( 'ivs-ctl show' )
1191
        if exitcode:
1192
            error( out + err +
1193
                   'ivs-ctl exited with code %d\n' % exitcode +
1194
                   '*** The openvswitch kernel module might '
1195
                   'not be loaded. Try modprobe openvswitch.\n' )
1196
            exit( 1 )
1197

    
1198
    @classmethod
1199
    def batchShutdown( cls, switches ):
1200
        "Kill each IVS switch, to be waited on later in stop()"
1201
        for switch in switches:
1202
            switch.cmd( 'kill %ivs' )
1203

    
1204
    def start( self, controllers ):
1205
        "Start up a new IVS switch"
1206
        args = ['ivs']
1207
        args.extend( ['--name', self.name] )
1208
        args.extend( ['--dpid', self.dpid] )
1209
        if self.verbose:
1210
            args.extend( ['--verbose'] )
1211
        for intf in self.intfs.values():
1212
            if not intf.IP():
1213
                args.extend( ['-i', intf.name] )
1214
        for c in controllers:
1215
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1216
        if self.listenPort:
1217
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1218
        args.append( self.opts )
1219

    
1220
        logfile = '/tmp/ivs.%s.log' % self.name
1221

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

    
1224
    def stop( self ):
1225
        "Terminate IVS switch."
1226
        self.cmd( 'kill %ivs' )
1227
        self.cmd( 'wait' )
1228
        self.deleteIntfs()
1229

    
1230
    def attach( self, intf ):
1231
        "Connect a data port"
1232
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1233

    
1234
    def detach( self, intf ):
1235
        "Disconnect a data port"
1236
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1237

    
1238
    def dpctl( self, *args ):
1239
        "Run dpctl command"
1240
        if not self.listenPort:
1241
            return "can't run dpctl without passive listening port"
1242
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1243
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1244

    
1245

    
1246
class Controller( Node ):
1247
    """A Controller is a Node that is running (or has execed?) an
1248
       OpenFlow controller."""
1249

    
1250
    def __init__( self, name, inNamespace=False, command='controller',
1251
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1252
                  port=6633, protocol='tcp', **params ):
1253
        self.command = command
1254
        self.cargs = cargs
1255
        self.cdir = cdir
1256
        self.ip = ip
1257
        self.port = port
1258
        self.protocol = protocol
1259
        Node.__init__( self, name, inNamespace=inNamespace,
1260
                       ip=ip, **params  )
1261
        self.checkListening()
1262

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

    
1281
    def start( self ):
1282
        """Start <controller> <args> on controller.
1283
           Log to /tmp/cN.log"""
1284
        pathCheck( self.command )
1285
        cout = '/tmp/' + self.name + '.log'
1286
        if self.cdir is not None:
1287
            self.cmd( 'cd ' + self.cdir )
1288
        self.cmd( self.command + ' ' + self.cargs % self.port +
1289
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1290
        self.execed = False
1291

    
1292
    def stop( self ):
1293
        "Stop controller."
1294
        self.cmd( 'kill %' + self.command )
1295
        self.cmd( 'wait %' + self.command )
1296
        self.terminate()
1297

    
1298
    def IP( self, intf=None ):
1299
        "Return IP address of the Controller"
1300
        if self.intfs:
1301
            ip = Node.IP( self, intf )
1302
        else:
1303
            ip = self.ip
1304
        return ip
1305

    
1306
    def __repr__( self ):
1307
        "More informative string representation"
1308
        return '<%s %s: %s:%s pid=%s> ' % (
1309
            self.__class__.__name__, self.name,
1310
            self.IP(), self.port, self.pid )
1311
    @classmethod
1312
    def isAvailable( self ):
1313
        return quietRun( 'which controller' )
1314

    
1315
class OVSController( Controller ):
1316
    "Open vSwitch controller"
1317
    def __init__( self, name, command='ovs-controller', **kwargs ):
1318
        if quietRun( 'which test-controller' ):
1319
            command = 'test-controller'
1320
        Controller.__init__( self, name, command=command, **kwargs )
1321
    @classmethod
1322
    def isAvailable( self ):
1323
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1324

    
1325
class NOX( Controller ):
1326
    "Controller to run a NOX application."
1327

    
1328
    def __init__( self, name, *noxArgs, **kwargs ):
1329
        """Init.
1330
           name: name to give controller
1331
           noxArgs: arguments (strings) to pass to NOX"""
1332
        if not noxArgs:
1333
            warn( 'warning: no NOX modules specified; '
1334
                  'running packetdump only\n' )
1335
            noxArgs = [ 'packetdump' ]
1336
        elif type( noxArgs ) not in ( list, tuple ):
1337
            noxArgs = [ noxArgs ]
1338

    
1339
        if 'NOX_CORE_DIR' not in os.environ:
1340
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1341
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1342

    
1343
        Controller.__init__( self, name,
1344
                             command=noxCoreDir + '/nox_core',
1345
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1346
                             ' '.join( noxArgs ),
1347
                             cdir=noxCoreDir,
1348
                             **kwargs )
1349

    
1350
class RYU( Controller ):
1351
    "Controller to run Ryu application"
1352
    def __init__( self, name, *ryuArgs, **kwargs ):
1353
        """Init.
1354
        name: name to give controller.
1355
        ryuArgs: arguments and modules to pass to Ryu"""
1356
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1357
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1358
        if not ryuArgs:
1359
            warn( 'warning: no Ryu modules specified; '
1360
                  'running simple_switch only\n' )
1361
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1362
        elif type( ryuArgs ) not in ( list, tuple ):
1363
            ryuArgs = [ ryuArgs ]
1364

    
1365
        Controller.__init__( self, name,
1366
                         command='ryu-manager',
1367
                         cargs='--ofp-tcp-listen-port %s ' + 
1368
                         ' '.join( ryuArgs ),
1369
                         cdir=ryuCoreDir,
1370
                         **kwargs )
1371

    
1372
class RemoteController( Controller ):
1373
    "Controller running outside of Mininet's control."
1374

    
1375
    def __init__( self, name, ip='127.0.0.1',
1376
                  port=6633, **kwargs):
1377
        """Init.
1378
           name: name to give controller
1379
           ip: the IP address where the remote controller is
1380
           listening
1381
           port: the port where the remote controller is listening"""
1382
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1383

    
1384
    def start( self ):
1385
        "Overridden to do nothing."
1386
        return
1387

    
1388
    def stop( self ):
1389
        "Overridden to do nothing."
1390
        return
1391

    
1392
    def checkListening( self ):
1393
        "Warn if remote controller is not accessible"
1394
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1395
                              ( self.ip, self.port ) )
1396
        if 'Connected' not in listening:
1397
            warn( "Unable to contact the remote controller"
1398
                  " at %s:%d\n" % ( self.ip, self.port ) )
1399

    
1400
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1401
    "find any controller that is available and run it"
1402
    for controller in order:
1403
        if controller.isAvailable():
1404
            return controller( name, **kwargs )