Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 4f8aa1d8

History | View | Annotate | Download (54 KB)

1
"""
2
Node objects for Mininet.
3

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

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

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

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

21
Switch: superclass for switch nodes.
22

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

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

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

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

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

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

41
Future enhancements:
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
202
    # Subshell I/O, commands and control
203

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
384
    # Interface management, configuration, and routing
385

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

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

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

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

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

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

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

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

    
468
    # Routing support
469

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

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

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

    
494
    # Convenience and configuration methods
495

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

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

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

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

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

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

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

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

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

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

    
579
    # Other methods
580

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

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

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

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

    
600
    # Automatic class setup support
601

    
602
    isSetup = False
603

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

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

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

    
622
class CPULimitedHost( Host ):
623

    
624
    "CPU limited host"
625

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
799
    inited = False
800

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

    
807

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

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

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

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

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

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

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

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

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

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

    
894
    dpidLen = 12
895

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

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

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

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

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

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

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

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

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

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

    
988

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

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

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

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

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

    
1038

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

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

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

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

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

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

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

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

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

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

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

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

    
1183

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

    
1191
OVSKernelSwitch = OVSSwitch
1192

    
1193

    
1194
class IVSSwitch(Switch):
1195
    """IVS virtual switch"""
1196

    
1197
    def __init__( self, name, verbose=True, **kwargs ):
1198
        Switch.__init__( self, name, **kwargs )
1199
        self.verbose = verbose
1200

    
1201
    @classmethod
1202
    def setup( cls ):
1203
        "Make sure IVS is installed"
1204
        pathCheck( 'ivs-ctl', 'ivs',
1205
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1206
        out, err, exitcode = errRun( 'ivs-ctl show' )
1207
        if exitcode:
1208
            error( out + err +
1209
                   'ivs-ctl exited with code %d\n' % exitcode +
1210
                   '*** The openvswitch kernel module might '
1211
                   'not be loaded. Try modprobe openvswitch.\n' )
1212
            exit( 1 )
1213

    
1214
    @classmethod
1215
    def batchShutdown( cls, switches ):
1216
        "Kill each IVS switch, to be waited on later in stop()"
1217
        for switch in switches:
1218
            switch.cmd( 'kill %ivs' )
1219

    
1220
    def start( self, controllers ):
1221
        "Start up a new IVS switch"
1222
        args = ['ivs']
1223
        args.extend( ['--name', self.name] )
1224
        args.extend( ['--dpid', self.dpid] )
1225
        if self.verbose:
1226
            args.extend( ['--verbose'] )
1227
        for intf in self.intfs.values():
1228
            if not intf.IP():
1229
                args.extend( ['-i', intf.name] )
1230
        for c in controllers:
1231
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1232
        if self.listenPort:
1233
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1234
        args.append( self.opts )
1235

    
1236
        logfile = '/tmp/ivs.%s.log' % self.name
1237

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

    
1240
    def stop( self ):
1241
        "Terminate IVS switch."
1242
        self.cmd( 'kill %ivs' )
1243
        self.cmd( 'wait' )
1244
        self.deleteIntfs()
1245

    
1246
    def attach( self, intf ):
1247
        "Connect a data port"
1248
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1249

    
1250
    def detach( self, intf ):
1251
        "Disconnect a data port"
1252
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1253

    
1254
    def dpctl( self, *args ):
1255
        "Run dpctl command"
1256
        if not self.listenPort:
1257
            return "can't run dpctl without passive listening port"
1258
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1259
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1260

    
1261

    
1262
class Controller( Node ):
1263
    """A Controller is a Node that is running (or has execed?) an
1264
       OpenFlow controller."""
1265

    
1266
    def __init__( self, name, inNamespace=False, command='controller',
1267
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1268
                  port=6633, protocol='tcp', **params ):
1269
        self.command = command
1270
        self.cargs = cargs
1271
        self.cdir = cdir
1272
        self.ip = ip
1273
        self.port = port
1274
        self.protocol = protocol
1275
        Node.__init__( self, name, inNamespace=inNamespace,
1276
                       ip=ip, **params  )
1277
        self.checkListening()
1278

    
1279
    def checkListening( self ):
1280
        "Make sure no controllers are running on our port"
1281
        # Verify that Telnet is installed first:
1282
        out, _err, returnCode = errRun( "which telnet" )
1283
        if 'telnet' not in out or returnCode != 0:
1284
            raise Exception( "Error running telnet to check for listening "
1285
                             "controllers; please check that it is "
1286
                             "installed." )
1287
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1288
                              ( self.ip, self.port ) )
1289
        if 'Connected' in listening:
1290
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1291
            pstr = ':%d ' % self.port
1292
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1293
            raise Exception( "Please shut down the controller which is"
1294
                             " running on port %d:\n" % self.port +
1295
                             '\n'.join( clist ) )
1296

    
1297
    def start( self ):
1298
        """Start <controller> <args> on controller.
1299
           Log to /tmp/cN.log"""
1300
        pathCheck( self.command )
1301
        cout = '/tmp/' + self.name + '.log'
1302
        if self.cdir is not None:
1303
            self.cmd( 'cd ' + self.cdir )
1304
        self.cmd( self.command + ' ' + self.cargs % self.port +
1305
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1306
        self.execed = False
1307

    
1308
    def stop( self ):
1309
        "Stop controller."
1310
        self.cmd( 'kill %' + self.command )
1311
        self.cmd( 'wait %' + self.command )
1312
        self.terminate()
1313

    
1314
    def IP( self, intf=None ):
1315
        "Return IP address of the Controller"
1316
        if self.intfs:
1317
            ip = Node.IP( self, intf )
1318
        else:
1319
            ip = self.ip
1320
        return ip
1321

    
1322
    def __repr__( self ):
1323
        "More informative string representation"
1324
        return '<%s %s: %s:%s pid=%s> ' % (
1325
            self.__class__.__name__, self.name,
1326
            self.IP(), self.port, self.pid )
1327
    @classmethod
1328
    def isAvailable( self ):
1329
        return quietRun( 'which controller' )
1330

    
1331
class OVSController( Controller ):
1332
    "Open vSwitch controller"
1333
    def __init__( self, name, command='ovs-controller', **kwargs ):
1334
        if quietRun( 'which test-controller' ):
1335
            command = 'test-controller'
1336
        Controller.__init__( self, name, command=command, **kwargs )
1337
    @classmethod
1338
    def isAvailable( self ):
1339
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1340

    
1341
class NOX( Controller ):
1342
    "Controller to run a NOX application."
1343

    
1344
    def __init__( self, name, *noxArgs, **kwargs ):
1345
        """Init.
1346
           name: name to give controller
1347
           noxArgs: arguments (strings) to pass to NOX"""
1348
        if not noxArgs:
1349
            warn( 'warning: no NOX modules specified; '
1350
                  'running packetdump only\n' )
1351
            noxArgs = [ 'packetdump' ]
1352
        elif type( noxArgs ) not in ( list, tuple ):
1353
            noxArgs = [ noxArgs ]
1354

    
1355
        if 'NOX_CORE_DIR' not in os.environ:
1356
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1357
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1358

    
1359
        Controller.__init__( self, name,
1360
                             command=noxCoreDir + '/nox_core',
1361
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1362
                             ' '.join( noxArgs ),
1363
                             cdir=noxCoreDir,
1364
                             **kwargs )
1365

    
1366
class RYU( Controller ):
1367
    "Controller to run Ryu application"
1368
    def __init__( self, name, *ryuArgs, **kwargs ):
1369
        """Init.
1370
        name: name to give controller.
1371
        ryuArgs: arguments and modules to pass to Ryu"""
1372
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1373
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1374
        if not ryuArgs:
1375
            warn( 'warning: no Ryu modules specified; '
1376
                  'running simple_switch only\n' )
1377
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1378
        elif type( ryuArgs ) not in ( list, tuple ):
1379
            ryuArgs = [ ryuArgs ]
1380

    
1381
        Controller.__init__( self, name,
1382
                         command='ryu-manager',
1383
                         cargs='--ofp-tcp-listen-port %s ' +
1384
                         ' '.join( ryuArgs ),
1385
                         cdir=ryuCoreDir,
1386
                         **kwargs )
1387

    
1388
class RemoteController( Controller ):
1389
    "Controller running outside of Mininet's control."
1390

    
1391
    def __init__( self, name, ip='127.0.0.1',
1392
                  port=6633, **kwargs):
1393
        """Init.
1394
           name: name to give controller
1395
           ip: the IP address where the remote controller is
1396
           listening
1397
           port: the port where the remote controller is listening"""
1398
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1399

    
1400
    def start( self ):
1401
        "Overridden to do nothing."
1402
        return
1403

    
1404
    def stop( self ):
1405
        "Overridden to do nothing."
1406
        return
1407

    
1408
    def checkListening( self ):
1409
        "Warn if remote controller is not accessible"
1410
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1411
                              ( self.ip, self.port ) )
1412
        if 'Connected' not in listening:
1413
            warn( "Unable to contact the remote controller"
1414
                  " at %s:%d\n" % ( self.ip, self.port ) )
1415

    
1416
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1417
    "find any controller that is available and run it"
1418
    for controller in order:
1419
        if controller.isAvailable():
1420
            return controller( name, **kwargs )