Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ e8623fdc

History | View | Annotate | Download (51.7 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
           params: Node parameters (see config() for details)"""
79

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

    
83
        self.name = name
84
        self.inNamespace = inNamespace
85

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

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

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

    
101
        # Start command interpreter shell
102
        self.startShell()
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

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

    
164
    def cleanup( self ):
165
        "Help python collect its garbage."
166
        # Intfs may end up in root NS
167
        for intfName in self.intfNames():
168
            if self.name in intfName:
169
                quietRun( 'ip link del ' + intfName )
170
        self.shell = None
171

    
172
    # Subshell I/O, commands and control
173

    
174
    def read( self, maxbytes=1024 ):
175
        """Buffered read from node, non-blocking.
176
           maxbytes: maximum number of bytes to return"""
177
        count = len( self.readbuf )
178
        if count < maxbytes:
179
            data = os.read( self.stdout.fileno(), maxbytes - count )
180
            self.readbuf += data
181
        if maxbytes >= len( self.readbuf ):
182
            result = self.readbuf
183
            self.readbuf = ''
184
        else:
185
            result = self.readbuf[ :maxbytes ]
186
            self.readbuf = self.readbuf[ maxbytes: ]
187
        return result
188

    
189
    def readline( self ):
190
        """Buffered readline from node, non-blocking.
191
           returns: line (minus newline) or None"""
192
        self.readbuf += self.read( 1024 )
193
        if '\n' not in self.readbuf:
194
            return None
195
        pos = self.readbuf.find( '\n' )
196
        line = self.readbuf[ 0: pos ]
197
        self.readbuf = self.readbuf[ pos + 1: ]
198
        return line
199

    
200
    def write( self, data ):
201
        """Write data to node.
202
           data: string"""
203
        os.write( self.stdin.fileno(), data )
204

    
205
    def terminate( self ):
206
        "Send kill signal to Node and clean up after it."
207
        if self.shell:
208
            os.killpg( self.pid, signal.SIGKILL )
209
        self.cleanup()
210

    
211
    def stop( self ):
212
        "Stop node."
213
        self.terminate()
214

    
215
    def waitReadable( self, timeoutms=None ):
216
        """Wait until node's output is readable.
217
           timeoutms: timeout in ms or None to wait indefinitely."""
218
        if len( self.readbuf ) == 0:
219
            self.pollOut.poll( timeoutms )
220

    
221
    def sendCmd( self, *args, **kwargs ):
222
        """Send a command, followed by a command to echo a sentinel,
223
           and return without waiting for the command to complete.
224
           args: command and arguments, or string
225
           printPid: print command's PID?"""
226
        assert not self.waiting
227
        printPid = kwargs.get( 'printPid', True )
228
        # Allow sendCmd( [ list ] )
229
        if len( args ) == 1 and type( args[ 0 ] ) is list:
230
            cmd = args[ 0 ]
231
        # Allow sendCmd( cmd, arg1, arg2... )
232
        elif len( args ) > 0:
233
            cmd = args
234
        # Convert to string
235
        if not isinstance( cmd, str ):
236
            cmd = ' '.join( [ str( c ) for c in cmd ] )
237
        if not re.search( r'\w', cmd ):
238
            # Replace empty commands with something harmless
239
            cmd = 'echo -n'
240
        self.lastCmd = cmd
241
        if printPid and not isShellBuiltin( cmd ):
242
            if len( cmd ) > 0 and cmd[ -1 ] == '&':
243
                # print ^A{pid}\n so monitor() can set lastPid
244
                cmd += ' printf "\\001%d\n" $! \n'
245
            else:
246
                cmd = 'mnexec -p ' + cmd
247
        self.write( cmd + '\n' )
248
        self.lastPid = None
249
        self.waiting = True
250

    
251
    def sendInt( self, intr=chr( 3 ) ):
252
        "Interrupt running command."
253
        self.write( intr )
254

    
255
    def monitor( self, timeoutms=None, findPid=True ):
256
        """Monitor and return the output of a command.
257
           Set self.waiting to False if command has completed.
258
           timeoutms: timeout in ms or None to wait indefinitely."""
259
        self.waitReadable( timeoutms )
260
        data = self.read( 1024 )
261
        # Look for PID
262
        marker = chr( 1 ) + r'\d+\r\n'
263
        if findPid and chr( 1 ) in data:
264
            markers = re.findall( marker, data )
265
            if markers:
266
                self.lastPid = int( markers[ 0 ][ 1: ] )
267
                data = re.sub( marker, '', data )
268
        # Look for sentinel/EOF
269
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
270
            self.waiting = False
271
            data = data[ :-1 ]
272
        elif chr( 127 ) in data:
273
            self.waiting = False
274
            data = data.replace( chr( 127 ), '' )
275
        return data
276

    
277
    def waitOutput( self, verbose=False ):
278
        """Wait for a command to complete.
279
           Completion is signaled by a sentinel character, ASCII(127)
280
           appearing in the output stream.  Wait for the sentinel and return
281
           the output, including trailing newline.
282
           verbose: print output interactively"""
283
        log = info if verbose else debug
284
        output = ''
285
        while self.waiting:
286
            data = self.monitor()
287
            output += data
288
            log( data )
289
        return output
290

    
291
    def cmd( self, *args, **kwargs ):
292
        """Send a command, wait for output, and return it.
293
           cmd: string"""
294
        verbose = kwargs.get( 'verbose', False )
295
        log = info if verbose else debug
296
        log( '*** %s : %s\n' % ( self.name, args ) )
297
        self.sendCmd( *args, **kwargs )
298
        return self.waitOutput( verbose )
299

    
300
    def cmdPrint( self, *args):
301
        """Call cmd and printing its output
302
           cmd: string"""
303
        return self.cmd( *args, **{ 'verbose': True } )
304

    
305
    def popen( self, *args, **kwargs ):
306
        """Return a Popen() object in our namespace
307
           args: Popen() args, single list, or string
308
           kwargs: Popen() keyword args"""
309
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
310
                     'mncmd':
311
                     [ 'mnexec', '-da', str( self.pid ) ] }
312
        defaults.update( kwargs )
313
        if len( args ) == 1:
314
            if type( args[ 0 ] ) is list:
315
                # popen([cmd, arg1, arg2...])
316
                cmd = args[ 0 ]
317
            elif type( args[ 0 ] ) is str:
318
                # popen("cmd arg1 arg2...")
319
                cmd = args[ 0 ].split()
320
            else:
321
                raise Exception( 'popen() requires a string or list' )
322
        elif len( args ) > 0:
323
            # popen( cmd, arg1, arg2... )
324
            cmd = list( args )
325
        # Attach to our namespace  using mnexec -a
326
        mncmd = defaults[ 'mncmd' ]
327
        del defaults[ 'mncmd' ]
328
        cmd = mncmd + cmd
329
        # Shell requires a string, not a list!
330
        if defaults.get( 'shell', False ):
331
            cmd = ' '.join( cmd )
332
        return Popen( cmd, **defaults )
333

    
334
    def pexec( self, *args, **kwargs ):
335
        """Execute a command using popen
336
           returns: out, err, exitcode"""
337
        popen = self.popen( *args, **kwargs)
338
        out, err = popen.communicate()
339
        exitcode = popen.wait()
340
        return out, err, exitcode
341

    
342
    # Interface management, configuration, and routing
343

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

    
350
    def newPort( self ):
351
        "Return the next port number to allocate."
352
        if len( self.ports ) > 0:
353
            return max( self.ports.values() ) + 1
354
        return self.portBase
355

    
356
    def addIntf( self, intf, port=None ):
357
        """Add an interface.
358
           intf: interface
359
           port: port number (optional, typically OpenFlow port number)"""
360
        if port is None:
361
            port = self.newPort()
362
        self.intfs[ port ] = intf
363
        self.ports[ intf ] = port
364
        self.nameToIntf[ intf.name ] = intf
365
        debug( '\n' )
366
        debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
367
        if self.inNamespace:
368
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
369
            moveIntf( intf.name, self )
370

    
371
    def defaultIntf( self ):
372
        "Return interface for lowest port"
373
        ports = self.intfs.keys()
374
        if ports:
375
            return self.intfs[ min( ports ) ]
376
        else:
377
            warn( '*** defaultIntf: warning:', self.name,
378
                  'has no interfaces\n' )
379

    
380
    def intf( self, intf='' ):
381
        """Return our interface object with given string name,
382
           default intf if name is falsy (None, empty string, etc).
383
           or the input intf arg.
384

385
        Having this fcn return its arg for Intf objects makes it
386
        easier to construct functions with flexible input args for
387
        interfaces (those that accept both string names and Intf objects).
388
        """
389
        if not intf:
390
            return self.defaultIntf()
391
        elif type( intf ) is str:
392
            return self.nameToIntf[ intf ]
393
        else:
394
            return intf
395

    
396
    def connectionsTo( self, node):
397
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
398
        # We could optimize this if it is important
399
        connections = []
400
        for intf in self.intfList():
401
            link = intf.link
402
            if link:
403
                node1, node2 = link.intf1.node, link.intf2.node
404
                if node1 == self and node2 == node:
405
                    connections += [ ( intf, link.intf2 ) ]
406
                elif node1 == node and node2 == self:
407
                    connections += [ ( intf, link.intf1 ) ]
408
        return connections
409

    
410
    def deleteIntfs( self, checkName=True ):
411
        """Delete all of our interfaces.
412
           checkName: only delete interfaces that contain our name"""
413
        # In theory the interfaces should go away after we shut down.
414
        # However, this takes time, so we're better off removing them
415
        # explicitly so that we won't get errors if we run before they
416
        # have been removed by the kernel. Unfortunately this is very slow,
417
        # at least with Linux kernels before 2.6.33
418
        for intf in self.intfs.values():
419
            # Protect against deleting hardware interfaces
420
            if ( self.name in intf.name ) or ( not checkName ):
421
                intf.delete()
422
                info( '.' )
423

    
424
    # Routing support
425

    
426
    def setARP( self, ip, mac ):
427
        """Add an ARP entry.
428
           ip: IP address as string
429
           mac: MAC address as string"""
430
        result = self.cmd( 'arp', '-s', ip, mac )
431
        return result
432

    
433
    def setHostRoute( self, ip, intf ):
434
        """Add route to host.
435
           ip: IP address as dotted decimal
436
           intf: string, interface name"""
437
        return self.cmd( 'route add -host', ip, 'dev', intf )
438

    
439
    def setDefaultRoute( self, intf=None ):
440
        """Set the default route to go through intf.
441
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
442
        # Note setParam won't call us if intf is none
443
        if type( intf ) is str and ' ' in intf:
444
            params = intf
445
        else:
446
            params = 'dev %s' % intf
447
        self.cmd( 'ip route del default' )
448
        return self.cmd( 'ip route add default', params )
449

    
450
    # Convenience and configuration methods
451

    
452
    def setMAC( self, mac, intf=None ):
453
        """Set the MAC address for an interface.
454
           intf: intf or intf name
455
           mac: MAC address as string"""
456
        return self.intf( intf ).setMAC( mac )
457

    
458
    def setIP( self, ip, prefixLen=8, intf=None ):
459
        """Set the IP address for an interface.
460
           intf: intf or intf name
461
           ip: IP address as a string
462
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
463
        # This should probably be rethought
464
        if '/' not in ip:
465
            ip = '%s/%s' % ( ip, prefixLen )
466
        return self.intf( intf ).setIP( ip )
467

    
468
    def IP( self, intf=None ):
469
        "Return IP address of a node or specific interface."
470
        return self.intf( intf ).IP()
471

    
472
    def MAC( self, intf=None ):
473
        "Return MAC address of a node or specific interface."
474
        return self.intf( intf ).MAC()
475

    
476
    def intfIsUp( self, intf=None ):
477
        "Check if an interface is up."
478
        return self.intf( intf ).isUp()
479

    
480
    # The reason why we configure things in this way is so
481
    # That the parameters can be listed and documented in
482
    # the config method.
483
    # Dealing with subclasses and superclasses is slightly
484
    # annoying, but at least the information is there!
485

    
486
    def setParam( self, results, method, **param ):
487
        """Internal method: configure a *single* parameter
488
           results: dict of results to update
489
           method: config method name
490
           param: arg=value (ignore if value=None)
491
           value may also be list or dict"""
492
        name, value = param.items()[ 0 ]
493
        f = getattr( self, method, None )
494
        if not f or value is None:
495
            return
496
        if type( value ) is list:
497
            result = f( *value )
498
        elif type( value ) is dict:
499
            result = f( **value )
500
        else:
501
            result = f( value )
502
        results[ name ] = result
503
        return result
504

    
505
    def config( self, mac=None, ip=None,
506
                defaultRoute=None, lo='up', **_params ):
507
        """Configure Node according to (optional) parameters:
508
           mac: MAC address for default interface
509
           ip: IP address for default interface
510
           ifconfig: arbitrary interface configuration
511
           Subclasses should override this method and call
512
           the parent class's config(**params)"""
513
        # If we were overriding this method, we would call
514
        # the superclass config method here as follows:
515
        # r = Parent.config( **_params )
516
        r = {}
517
        self.setParam( r, 'setMAC', mac=mac )
518
        self.setParam( r, 'setIP', ip=ip )
519
        self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
520
        # This should be examined
521
        self.cmd( 'ifconfig lo ' + lo )
522
        return r
523

    
524
    def configDefault( self, **moreParams ):
525
        "Configure with default parameters"
526
        self.params.update( moreParams )
527
        self.config( **self.params )
528

    
529
    # This is here for backward compatibility
530
    def linkTo( self, node, link=Link ):
531
        """(Deprecated) Link to another node
532
           replace with Link( node1, node2)"""
533
        return link( self, node )
534

    
535
    # Other methods
536

    
537
    def intfList( self ):
538
        "List of our interfaces sorted by port number"
539
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
540

    
541
    def intfNames( self ):
542
        "The names of our interfaces sorted by port number"
543
        return [ str( i ) for i in self.intfList() ]
544

    
545
    def __repr__( self ):
546
        "More informative string representation"
547
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
548
                              for i in self.intfList() ] ) )
549
        return '<%s %s: %s pid=%s> ' % (
550
            self.__class__.__name__, self.name, intfs, self.pid )
551

    
552
    def __str__( self ):
553
        "Abbreviated string representation"
554
        return self.name
555

    
556
    # Automatic class setup support
557

    
558
    isSetup = False
559

    
560
    @classmethod
561
    def checkSetup( cls ):
562
        "Make sure our class and superclasses are set up"
563
        while cls and not getattr( cls, 'isSetup', True ):
564
            cls.setup()
565
            cls.isSetup = True
566
            # Make pylint happy
567
            cls = getattr( type( cls ), '__base__', None )
568

    
569
    @classmethod
570
    def setup( cls ):
571
        "Make sure our class dependencies are available"
572
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
573

    
574

    
575
class Host( Node ):
576
    "A host is simply a Node"
577
    pass
578

    
579

    
580
class CPULimitedHost( Host ):
581

    
582
    "CPU limited host"
583

    
584
    def __init__( self, name, sched='cfs', **kwargs ):
585
        Host.__init__( self, name, **kwargs )
586
        # Initialize class if necessary
587
        if not CPULimitedHost.inited:
588
            CPULimitedHost.init()
589
        # Create a cgroup and move shell into it
590
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
591
        errFail( 'cgcreate -g ' + self.cgroup )
592
        # We don't add ourselves to a cpuset because you must
593
        # specify the cpu and memory placement first
594
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
595
        # BL: Setting the correct period/quota is tricky, particularly
596
        # for RT. RT allows very small quotas, but the overhead
597
        # seems to be high. CFS has a mininimum quota of 1 ms, but
598
        # still does better with larger period values.
599
        self.period_us = kwargs.get( 'period_us', 100000 )
600
        self.sched = sched
601
        self.rtprio = 20
602

    
603
    def cgroupSet( self, param, value, resource='cpu' ):
604
        "Set a cgroup parameter and return its value"
605
        cmd = 'cgset -r %s.%s=%s /%s' % (
606
            resource, param, value, self.name )
607
        quietRun( cmd )
608
        nvalue = int( self.cgroupGet( param, resource ) )
609
        if nvalue != value:
610
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
611
                   % ( param, nvalue, value ) )
612
        return nvalue
613

    
614
    def cgroupGet( self, param, resource='cpu' ):
615
        "Return value of cgroup parameter"
616
        cmd = 'cgget -r %s.%s /%s' % (
617
            resource, param, self.name )
618
        return int( quietRun( cmd ).split()[ -1 ] )
619

    
620
    def cgroupDel( self ):
621
        "Clean up our cgroup"
622
        # info( '*** deleting cgroup', self.cgroup, '\n' )
623
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
624
        return exitcode != 0
625

    
626
    def popen( self, *args, **kwargs ):
627
        """Return a Popen() object in node's namespace
628
           args: Popen() args, single list, or string
629
           kwargs: Popen() keyword args"""
630
        # Tell mnexec to execute command in our cgroup
631
        mncmd = [ 'mnexec', '-da', str( self.pid ),
632
                  '-g', self.name ]
633
        if self.sched == 'rt':
634
            mncmd += [ '-r', str( self.rtprio ) ]
635
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
636

    
637
    def cleanup( self ):
638
        "Clean up Node, then clean up our cgroup"
639
        super( CPULimitedHost, self ).cleanup()
640
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
641

    
642
    def chrt( self ):
643
        "Set RT scheduling priority"
644
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
645
        result = quietRun( 'chrt -p %s' % self.pid )
646
        firstline = result.split( '\n' )[ 0 ]
647
        lastword = firstline.split( ' ' )[ -1 ]
648
        if lastword != 'SCHED_RR':
649
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
650
        return lastword
651

    
652
    def rtInfo( self, f ):
653
        "Internal method: return parameters for RT bandwidth"
654
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
655
        # RT uses wall clock time for period and quota
656
        quota = int( self.period_us * f * numCores() )
657
        return pstr, qstr, self.period_us, quota
658

    
659
    def cfsInfo( self, f):
660
        "Internal method: return parameters for CFS bandwidth"
661
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
662
        # CFS uses wall clock time for period and CPU time for quota.
663
        quota = int( self.period_us * f * numCores() )
664
        period = self.period_us
665
        if f > 0 and quota < 1000:
666
            debug( '(cfsInfo: increasing default period) ' )
667
            quota = 1000
668
            period = int( quota / f / numCores() )
669
        return pstr, qstr, period, quota
670

    
671
    # BL comment:
672
    # This may not be the right API,
673
    # since it doesn't specify CPU bandwidth in "absolute"
674
    # units the way link bandwidth is specified.
675
    # We should use MIPS or SPECINT or something instead.
676
    # Alternatively, we should change from system fraction
677
    # to CPU seconds per second, essentially assuming that
678
    # all CPUs are the same.
679

    
680
    def setCPUFrac( self, f=-1, sched=None):
681
        """Set overall CPU fraction for this host
682
           f: CPU bandwidth limit (fraction)
683
           sched: 'rt' or 'cfs'
684
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
685
        if not f:
686
            return
687
        if not sched:
688
            sched = self.sched
689
        if sched == 'rt':
690
            pstr, qstr, period, quota = self.rtInfo( f )
691
        elif sched == 'cfs':
692
            pstr, qstr, period, quota = self.cfsInfo( f )
693
        else:
694
            return
695
        if quota < 0:
696
            # Reset to unlimited
697
            quota = -1
698
        # Set cgroup's period and quota
699
        self.cgroupSet( pstr, period )
700
        self.cgroupSet( qstr, quota )
701
        if sched == 'rt':
702
            # Set RT priority if necessary
703
            self.chrt()
704
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
705

    
706
    def setCPUs( self, cores, mems=0 ):
707
        "Specify (real) cores that our cgroup can run on"
708
        if type( cores ) is list:
709
            cores = ','.join( [ str( c ) for c in cores ] )
710
        self.cgroupSet( resource='cpuset', param='cpus',
711
                        value=cores )
712
        # Memory placement is probably not relevant, but we
713
        # must specify it anyway
714
        self.cgroupSet( resource='cpuset', param='mems',
715
                        value=mems)
716
        # We have to do this here after we've specified
717
        # cpus and mems
718
        errFail( 'cgclassify -g cpuset:/%s %s' % (
719
                 self.name, self.pid ) )
720

    
721
    def config( self, cpu=None, cores=None, **params ):
722
        """cpu: desired overall system CPU fraction
723
           cores: (real) core(s) this host can run on
724
           params: parameters for Node.config()"""
725
        r = Node.config( self, **params )
726
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
727
        # that seems redundant
728
        self.setParam( r, 'setCPUFrac', cpu=cpu )
729
        self.setParam( r, 'setCPUs', cores=cores )
730
        return r
731

    
732
    inited = False
733

    
734
    @classmethod
735
    def init( cls ):
736
        "Initialization for CPULimitedHost class"
737
        mountCgroups()
738
        cls.inited = True
739

    
740
class HostWithPrivateDirs( Host ):
741
    "Host with private directories"
742

    
743
    def __init__( self, name, *args, **kwargs ):
744
        "privateDirs: list of private directory strings or tuples"
745
        self.name = name
746
        self.privateDirs = kwargs.pop( 'privateDirs', [] )
747
        Host.__init__( self, name, *args, **kwargs )
748
        self.mountPrivateDirs()
749

    
750
    def mountPrivateDirs( self ):
751
        "mount private directories"
752
        for directory in self.privateDirs:
753
            if isinstance( directory, tuple ):
754
                # mount given private directory
755
                privateDir = directory[ 1 ] % self.__dict__ 
756
                mountPoint = directory[ 0 ]
757
                self.cmd( 'mkdir -p %s' % privateDir )
758
                self.cmd( 'mkdir -p %s' % mountPoint )
759
                self.cmd( 'mount --bind %s %s' %
760
                               ( privateDir, mountPoint ) )
761
            else:
762
                # mount temporary filesystem on directory
763
                self.cmd( 'mkdir -p %s' % directory ) 
764
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
765

    
766

    
767

    
768
# Some important things to note:
769
#
770
# The "IP" address which setIP() assigns to the switch is not
771
# an "IP address for the switch" in the sense of IP routing.
772
# Rather, it is the IP address for the control interface,
773
# on the control network, and it is only relevant to the
774
# controller. If you are running in the root namespace
775
# (which is the only way to run OVS at the moment), the
776
# control interface is the loopback interface, and you
777
# normally never want to change its IP address!
778
#
779
# In general, you NEVER want to attempt to use Linux's
780
# network stack (i.e. ifconfig) to "assign" an IP address or
781
# MAC address to a switch data port. Instead, you "assign"
782
# the IP and MAC addresses in the controller by specifying
783
# packets that you want to receive or send. The "MAC" address
784
# reported by ifconfig for a switch data port is essentially
785
# meaningless. It is important to understand this if you
786
# want to create a functional router using OpenFlow.
787

    
788
class Switch( Node ):
789
    """A Switch is a Node that is running (or has execed?)
790
       an OpenFlow switch."""
791

    
792
    portBase = 1  # Switches start with port 1 in OpenFlow
793
    dpidLen = 16  # digits in dpid passed to switch
794

    
795
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
796
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
797
           opts: additional switch options
798
           listenPort: port to listen on for dpctl connections"""
799
        Node.__init__( self, name, **params )
800
        self.dpid = self.defaultDpid( dpid )
801
        self.opts = opts
802
        self.listenPort = listenPort
803
        if not self.inNamespace:
804
            self.controlIntf = Intf( 'lo', self, port=0 )
805

    
806
    def defaultDpid( self, dpid=None ):
807
        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
808
        if dpid:
809
            # Remove any colons and make sure it's a good hex number
810
            dpid = dpid.translate( None, ':' )
811
            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
812
        else:
813
            # Use hex of the first number in the switch name
814
            nums = re.findall( r'\d+', self.name )
815
            if nums:
816
                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
817
            else:
818
                raise Exception( 'Unable to derive default datapath ID - '
819
                                 'please either specify a dpid or use a '
820
                                 'canonical switch name such as s23.' )
821
        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
822

    
823
    def defaultIntf( self ):
824
        "Return control interface"
825
        if self.controlIntf:
826
            return self.controlIntf
827
        else:
828
            return Node.defaultIntf( self )
829

    
830
    def sendCmd( self, *cmd, **kwargs ):
831
        """Send command to Node.
832
           cmd: string"""
833
        kwargs.setdefault( 'printPid', False )
834
        if not self.execed:
835
            return Node.sendCmd( self, *cmd, **kwargs )
836
        else:
837
            error( '*** Error: %s has execed and cannot accept commands' %
838
                   self.name )
839

    
840
    def connected( self ):
841
        "Is the switch connected to a controller? (override this method)"
842
        return False and self  # satisfy pylint
843

    
844
    def __repr__( self ):
845
        "More informative string representation"
846
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
847
                              for i in self.intfList() ] ) )
848
        return '<%s %s: %s pid=%s> ' % (
849
            self.__class__.__name__, self.name, intfs, self.pid )
850

    
851
class UserSwitch( Switch ):
852
    "User-space switch."
853

    
854
    dpidLen = 12
855

    
856
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
857
        """Init.
858
           name: name for the switch
859
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
860
        Switch.__init__( self, name, **kwargs )
861
        pathCheck( 'ofdatapath', 'ofprotocol',
862
                   moduleName='the OpenFlow reference user switch' +
863
                              '(openflow.org)' )
864
        if self.listenPort:
865
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
866
        else:
867
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
868
        self.dpopts = dpopts
869

    
870
    @classmethod
871
    def setup( cls ):
872
        "Ensure any dependencies are loaded; if not, try to load them."
873
        if not os.path.exists( '/dev/net/tun' ):
874
            moduleDeps( add=TUN )
875

    
876
    def dpctl( self, *args ):
877
        "Run dpctl command"
878
        listenAddr = None
879
        if not self.listenPort:
880
            listenAddr = 'unix:/tmp/%s.listen' % self.name
881
        else:
882
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
883
        return self.cmd( 'dpctl ' + ' '.join( args ) +
884
                         ' ' + listenAddr )
885

    
886
    def connected( self ):
887
        "Is the switch connected to a controller?"
888
        return 'remote.is-connected=true' in self.dpctl( 'status' )
889

    
890
    @staticmethod
891
    def TCReapply( intf ):
892
        """Unfortunately user switch and Mininet are fighting
893
           over tc queuing disciplines. To resolve the conflict,
894
           we re-create the user switch's configuration, but as a
895
           leaf of the TCIntf-created configuration."""
896
        if type( intf ) is TCIntf:
897
            ifspeed = 10000000000 # 10 Gbps
898
            minspeed = ifspeed * 0.001
899

    
900
            res = intf.config( **intf.params )
901

    
902
            if res is None: # link may not have TC parameters
903
                return
904

    
905
            # Re-add qdisc, root, and default classes user switch created, but
906
            # with new parent, as setup by Mininet's TCIntf
907
            parent = res['parent']
908
            intf.tc( "%s qdisc add dev %s " + parent +
909
                     " handle 1: htb default 0xfffe" )
910
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
911
                     + str(ifspeed) )
912
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
913
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
914

    
915
    def start( self, controllers ):
916
        """Start OpenFlow reference user datapath.
917
           Log to /tmp/sN-{ofd,ofp}.log.
918
           controllers: list of controller objects"""
919
        # Add controllers
920
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
921
                            for c in controllers ] )
922
        ofdlog = '/tmp/' + self.name + '-ofd.log'
923
        ofplog = '/tmp/' + self.name + '-ofp.log'
924
        self.cmd( 'ifconfig lo up' )
925
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
926
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
927
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
928
                  self.dpopts +
929
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
930
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
931
                  ' ' + clist +
932
                  ' --fail=closed ' + self.opts +
933
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
934
        if "no-slicing" not in self.dpopts:
935
            # Only TCReapply if slicing is enable
936
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
937
            for intf in self.intfList():
938
                if not intf.IP():
939
                    self.TCReapply( intf )
940

    
941
    def stop( self ):
942
        "Stop OpenFlow reference user datapath."
943
        self.cmd( 'kill %ofdatapath' )
944
        self.cmd( 'kill %ofprotocol' )
945
        self.deleteIntfs()
946

    
947

    
948
class OVSLegacyKernelSwitch( Switch ):
949
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
950
       Currently only works in the root namespace."""
951

    
952
    def __init__( self, name, dp=None, **kwargs ):
953
        """Init.
954
           name: name for switch
955
           dp: netlink id (0, 1, 2, ...)
956
           defaultMAC: default MAC as unsigned int; random value if None"""
957
        Switch.__init__( self, name, **kwargs )
958
        self.dp = dp if dp else self.name
959
        self.intf = self.dp
960
        if self.inNamespace:
961
            error( "OVSKernelSwitch currently only works"
962
                   " in the root namespace.\n" )
963
            exit( 1 )
964

    
965
    @classmethod
966
    def setup( cls ):
967
        "Ensure any dependencies are loaded; if not, try to load them."
968
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
969
                   moduleName='Open vSwitch (openvswitch.org)')
970
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
971

    
972
    def start( self, controllers ):
973
        "Start up kernel datapath."
974
        ofplog = '/tmp/' + self.name + '-ofp.log'
975
        quietRun( 'ifconfig lo up' )
976
        # Delete local datapath if it exists;
977
        # then create a new one monitoring the given interfaces
978
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
979
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
980
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
981
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
982
        # Run protocol daemon
983
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
984
                            for c in controllers ] )
985
        self.cmd( 'ovs-openflowd ' + self.dp +
986
                  ' ' + clist +
987
                  ' --fail=secure ' + self.opts +
988
                  ' --datapath-id=' + self.dpid +
989
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
990
        self.execed = False
991

    
992
    def stop( self ):
993
        "Terminate kernel datapath."
994
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
995
        self.cmd( 'kill %ovs-openflowd' )
996
        self.deleteIntfs()
997

    
998

    
999
class OVSSwitchBase( Switch ):
1000
    'a base class for OVS switches; does not contain OpenFlow code at all'
1001

    
1002
    @classmethod
1003
    def setup( cls ):
1004
        "Make sure Open vSwitch is installed and working"
1005
        pathCheck( 'ovs-vsctl',
1006
                   moduleName='Open vSwitch (openvswitch.org)')
1007
        # This should no longer be needed, and it breaks
1008
        # with OVS 1.7 which has renamed the kernel module:
1009
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1010
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1011
        if exitcode:
1012
            error( out + err +
1013
                   'ovs-vsctl exited with code %d\n' % exitcode +
1014
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1015
                   'Make sure that Open vSwitch is installed, '
1016
                   'that ovsdb-server is running, and that\n'
1017
                   '"ovs-vsctl show" works correctly.\n'
1018
                   'You may wish to try '
1019
                   '"service openvswitch-switch start".\n' )
1020
            exit( 1 )
1021
        info = quietRun( 'ovs-vsctl --version' )
1022
        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
1023

    
1024
    @classmethod
1025
    def isOldOVS( cls ):
1026
        return ( StrictVersion( cls.OVSVersion ) <
1027
             StrictVersion( '1.10' ) )
1028

    
1029
    @classmethod
1030
    def batchShutdown( cls, switches ):
1031
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1032
        quietRun( 'ovs-vsctl ' +
1033
                  ' -- '.join( '--if-exists del-br %s' % s
1034
                               for s in switches ) )
1035

    
1036
    @staticmethod
1037
    def TCReapply( intf ):
1038
        """Unfortunately OVS and Mininet are fighting
1039
           over tc queuing disciplines. As a quick hack/
1040
           workaround, we clear OVS's and reapply our own."""
1041
        if type( intf ) is TCIntf:
1042
            intf.config( **intf.params )
1043

    
1044
    def attach( self, intf ):
1045
        "Connect a data port"
1046
        self.cmd( 'ovs-vsctl add-port', self, intf )
1047
        self.cmd( 'ifconfig', intf, 'up' )
1048
        self.TCReapply( intf )
1049

    
1050
    def detach( self, intf ):
1051
        "Disconnect a data port"
1052
        self.cmd( 'ovs-vsctl del-port', self, intf )
1053

    
1054

    
1055
class OVSBridge( OVSSwitchBase ):
1056
    "OpenVSwitch Bridge (similar to OVSSwitch, but no OpenFlow)"
1057

    
1058
    def __init__( self, name, **kwargs ):
1059
        OVSSwitchBase.__init__( self, name, **kwargs )
1060

    
1061
    def connected( self ):
1062
        return True
1063

    
1064
    def start( self, controllers ):
1065
        if self.inNamespace:
1066
            raise Exception(
1067
                'OVS kernel switch does not work in a namespace' )
1068
        self.cmd( 'ovs-vsctl del-br', self )
1069
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf )
1070
                         for intf in self.intfList() if not intf.IP() )
1071
        cmd = ( 'ovs-vsctl add-br %s ' % self + intfs )
1072
        self.cmd( cmd )
1073
        for intf in self.intfList():
1074
            self.TCReapply( intf )
1075

    
1076
    def stop( self ):
1077
        self.cmd( 'ovs-vsctl del-br', self )
1078
        self.deleteIntfs()
1079

    
1080

    
1081
class OVSSwitch( OVSSwitchBase ):
1082
    "Open vSwitch switch using OpenFlow controller. Depends on ovs-vsctl."
1083

    
1084
    def __init__( self, name, failMode='secure', datapath='kernel',
1085
                 inband=False, **params ):
1086
        """Init.
1087
           name: name for switch
1088
           failMode: controller loss behavior (secure|open)
1089
           datapath: userspace or kernel mode (kernel|user)
1090
           inband: use in-band control (False)"""
1091
        OVSSwitchBase.__init__( self, name, **params )
1092
        self.failMode = failMode
1093
        self.datapath = datapath
1094
        self.inband = inband
1095

    
1096
    def dpctl( self, *args ):
1097
        "Run ovs-ofctl command"
1098
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1099

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

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

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

    
1172

    
1173
    def stop( self ):
1174
        "Terminate OVS switch."
1175
        self.cmd( 'ovs-vsctl del-br', self )
1176
        if self.datapath == 'user':
1177
            self.cmd( 'ip link del', self )
1178
        self.deleteIntfs()
1179

    
1180
OVSKernelSwitch = OVSSwitch
1181

    
1182

    
1183
class IVSSwitch(Switch):
1184
    """IVS virtual switch"""
1185

    
1186
    def __init__( self, name, verbose=True, **kwargs ):
1187
        Switch.__init__( self, name, **kwargs )
1188
        self.verbose = verbose
1189

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

    
1203
    @classmethod
1204
    def batchShutdown( cls, switches ):
1205
        "Kill each IVS switch, to be waited on later in stop()"
1206
        for switch in switches:
1207
            switch.cmd( 'kill %ivs' )
1208

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

    
1225
        logfile = '/tmp/ivs.%s.log' % self.name
1226

    
1227
        self.cmd( 'ifconfig lo up' )
1228
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1229

    
1230
    def stop( self ):
1231
        "Terminate IVS switch."
1232
        self.cmd( 'kill %ivs' )
1233
        self.cmd( 'wait' )
1234
        self.deleteIntfs()
1235

    
1236
    def attach( self, intf ):
1237
        "Connect a data port"
1238
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1239

    
1240
    def detach( self, intf ):
1241
        "Disconnect a data port"
1242
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1243

    
1244
    def dpctl( self, *args ):
1245
        "Run dpctl command"
1246
        if not self.listenPort:
1247
            return "can't run dpctl without passive listening port"
1248
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1249
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1250

    
1251

    
1252
class Controller( Node ):
1253
    """A Controller is a Node that is running (or has execed?) an
1254
       OpenFlow controller."""
1255

    
1256
    def __init__( self, name, inNamespace=False, command='controller',
1257
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1258
                  port=6633, protocol='tcp', **params ):
1259
        self.command = command
1260
        self.cargs = cargs
1261
        self.cdir = cdir
1262
        self.ip = ip
1263
        self.port = port
1264
        self.protocol = protocol
1265
        Node.__init__( self, name, inNamespace=inNamespace,
1266
                       ip=ip, **params  )
1267
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1268
        self.checkListening()
1269

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

    
1288
    def start( self ):
1289
        """Start <controller> <args> on controller.
1290
           Log to /tmp/cN.log"""
1291
        pathCheck( self.command )
1292
        cout = '/tmp/' + self.name + '.log'
1293
        if self.cdir is not None:
1294
            self.cmd( 'cd ' + self.cdir )
1295
        self.cmd( self.command + ' ' + self.cargs % self.port +
1296
                  ' 1>' + cout + ' 2>' + cout + '&' )
1297
        self.execed = False
1298

    
1299
    def stop( self ):
1300
        "Stop controller."
1301
        self.cmd( 'kill %' + self.command )
1302
        self.terminate()
1303

    
1304
    def IP( self, intf=None ):
1305
        "Return IP address of the Controller"
1306
        if self.intfs:
1307
            ip = Node.IP( self, intf )
1308
        else:
1309
            ip = self.ip
1310
        return ip
1311

    
1312
    def __repr__( self ):
1313
        "More informative string representation"
1314
        return '<%s %s: %s:%s pid=%s> ' % (
1315
            self.__class__.__name__, self.name,
1316
            self.IP(), self.port, self.pid )
1317
    @classmethod
1318
    def isAvailable( self ):
1319
        return quietRun( 'which controller' )
1320

    
1321
class OVSController( Controller ):
1322
    "Open vSwitch controller"
1323
    def __init__( self, name, command='ovs-controller', **kwargs ):
1324
        if quietRun( 'which test-controller' ):
1325
            command = 'test-controller'
1326
        Controller.__init__( self, name, command=command, **kwargs )
1327
    @classmethod
1328
    def isAvailable( self ):
1329
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1330

    
1331
class NOX( Controller ):
1332
    "Controller to run a NOX application."
1333

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

    
1345
        if 'NOX_CORE_DIR' not in os.environ:
1346
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1347
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1348

    
1349
        Controller.__init__( self, name,
1350
                             command=noxCoreDir + '/nox_core',
1351
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1352
                             ' '.join( noxArgs ),
1353
                             cdir=noxCoreDir,
1354
                             **kwargs )
1355

    
1356

    
1357
class RemoteController( Controller ):
1358
    "Controller running outside of Mininet's control."
1359

    
1360
    def __init__( self, name, ip='127.0.0.1',
1361
                  port=6633, **kwargs):
1362
        """Init.
1363
           name: name to give controller
1364
           ip: the IP address where the remote controller is
1365
           listening
1366
           port: the port where the remote controller is listening"""
1367
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1368

    
1369
    def start( self ):
1370
        "Overridden to do nothing."
1371
        return
1372

    
1373
    def stop( self ):
1374
        "Overridden to do nothing."
1375
        return
1376

    
1377
    def checkListening( self ):
1378
        "Warn if remote controller is not accessible"
1379
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1380
                              ( self.ip, self.port ) )
1381
        if 'Connected' not in listening:
1382
            warn( "Unable to contact the remote controller"
1383
                  " at %s:%d\n" % ( self.ip, self.port ) )
1384

    
1385

    
1386
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1387
    "find any controller that is available and run it"
1388
    for controller in order:
1389
        if controller.isAvailable():
1390
            return controller( name, **kwargs )