Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 41a54f05

History | View | Annotate | Download (50.5 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
            while not re.findall( marker, data ):
265
                data += self.read( 1024 )
266
            markers = re.findall( marker, data )
267
            if markers:
268
                self.lastPid = int( markers[ 0 ][ 1: ] )
269
                data = re.sub( marker, '', data )
270
        # Look for sentinel/EOF
271
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
272
            self.waiting = False
273
            data = data[ :-1 ]
274
        elif chr( 127 ) in data:
275
            self.waiting = False
276
            data = data.replace( chr( 127 ), '' )
277
        return data
278

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

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

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

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

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

    
344
    # Interface management, configuration, and routing
345

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

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

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

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

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

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

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

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

    
426
    # Routing support
427

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

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

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

    
452
    # Convenience and configuration methods
453

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

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

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

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

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

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

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

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

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

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

    
537
    # Other methods
538

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

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

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

    
554
    def __str__( self ):
555
        "Abbreviated string representation"
556
        return self.name
557

    
558
    # Automatic class setup support
559

    
560
    isSetup = False
561

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

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

    
576

    
577
class Host( Node ):
578
    "A host is simply a Node"
579
    pass
580

    
581

    
582
class CPULimitedHost( Host ):
583

    
584
    "CPU limited host"
585

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

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

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

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

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

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

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

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

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

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

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

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

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

    
734
    inited = False
735

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

    
742
class HostWithPrivateDirs( Host ):
743
    "Host with private directories"
744

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

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

    
768

    
769

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

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

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

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

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

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

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

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

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

    
853
class UserSwitch( Switch ):
854
    "User-space switch."
855

    
856
    dpidLen = 12
857

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

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

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

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

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

    
902
            res = intf.config( **intf.params )
903

    
904
            if res is None: # link may not have TC parameters
905
                return
906

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

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

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

    
948

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

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

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

    
973
    def start( self, controllers ):
974
        "Start up kernel datapath."
975
        ofplog = '/tmp/' + self.name + '-ofp.log'
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 OVSSwitch( Switch ):
1000
    "Open vSwitch switch. Depends on ovs-vsctl."
1001

    
1002
    def __init__( self, name, failMode='secure', datapath='kernel',
1003
                 inband=False, **params ):
1004
        """Init.
1005
           name: name for switch
1006
           failMode: controller loss behavior (secure|open)
1007
           datapath: userspace or kernel mode (kernel|user)
1008
           inband: use in-band control (False)"""
1009
        Switch.__init__( self, name, **params )
1010
        self.failMode = failMode
1011
        self.datapath = datapath
1012
        self.inband = inband
1013

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

    
1036
    @classmethod
1037
    def isOldOVS( cls ):
1038
        return ( StrictVersion( cls.OVSVersion ) <
1039
             StrictVersion( '1.10' ) )
1040

    
1041
    @classmethod
1042
    def batchShutdown( cls, switches ):
1043
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1044
        quietRun( 'ovs-vsctl ' +
1045
                  ' -- '.join( '--if-exists del-br %s' % s
1046
                               for s in switches ) )
1047

    
1048
    def dpctl( self, *args ):
1049
        "Run ovs-ofctl command"
1050
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1051

    
1052
    @staticmethod
1053
    def TCReapply( intf ):
1054
        """Unfortunately OVS and Mininet are fighting
1055
           over tc queuing disciplines. As a quick hack/
1056
           workaround, we clear OVS's and reapply our own."""
1057
        if type( intf ) is TCIntf:
1058
            intf.config( **intf.params )
1059

    
1060
    def attach( self, intf ):
1061
        "Connect a data port"
1062
        self.cmd( 'ovs-vsctl add-port', self, intf )
1063
        self.cmd( 'ifconfig', intf, 'up' )
1064
        self.TCReapply( intf )
1065

    
1066
    def detach( self, intf ):
1067
        "Disconnect a data port"
1068
        self.cmd( 'ovs-vsctl del-port', self, intf )
1069

    
1070
    def controllerUUIDs( self ):
1071
        "Return ovsdb UUIDs for our controllers"
1072
        uuids = []
1073
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1074
                               'Controller' ).strip()
1075
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1076
            controllers = controllers[ 1 : -1 ]
1077
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1078
        return uuids
1079

    
1080
    def connected( self ):
1081
        "Are we connected to at least one of our controllers?"
1082
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1083
                                         uuid, 'is_connected' )
1084
                    for uuid in self.controllerUUIDs() ]
1085
        return reduce( or_, results, False )
1086

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

    
1139

    
1140
    def stop( self ):
1141
        "Terminate OVS switch."
1142
        self.cmd( 'ovs-vsctl del-br', self )
1143
        if self.datapath == 'user':
1144
            self.cmd( 'ip link del', self )
1145
        self.deleteIntfs()
1146

    
1147
OVSKernelSwitch = OVSSwitch
1148

    
1149

    
1150
class IVSSwitch(Switch):
1151
    """IVS virtual switch"""
1152

    
1153
    def __init__( self, name, verbose=True, **kwargs ):
1154
        Switch.__init__( self, name, **kwargs )
1155
        self.verbose = verbose
1156

    
1157
    @classmethod
1158
    def setup( cls ):
1159
        "Make sure IVS is installed"
1160
        pathCheck( 'ivs-ctl', 'ivs',
1161
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1162
        out, err, exitcode = errRun( 'ivs-ctl show' )
1163
        if exitcode:
1164
            error( out + err +
1165
                   'ivs-ctl exited with code %d\n' % exitcode +
1166
                   '*** The openvswitch kernel module might '
1167
                   'not be loaded. Try modprobe openvswitch.\n' )
1168
            exit( 1 )
1169

    
1170
    @classmethod
1171
    def batchShutdown( cls, switches ):
1172
        "Kill each IVS switch, to be waited on later in stop()"
1173
        for switch in switches:
1174
            switch.cmd( 'kill %ivs' )
1175

    
1176
    def start( self, controllers ):
1177
        "Start up a new IVS switch"
1178
        args = ['ivs']
1179
        args.extend( ['--name', self.name] )
1180
        args.extend( ['--dpid', self.dpid] )
1181
        if self.verbose:
1182
            args.extend( ['--verbose'] )
1183
        for intf in self.intfs.values():
1184
            if not intf.IP():
1185
                args.extend( ['-i', intf.name] )
1186
        for c in controllers:
1187
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1188
        if self.listenPort:
1189
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1190
        args.append( self.opts )
1191

    
1192
        logfile = '/tmp/ivs.%s.log' % self.name
1193

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

    
1196
    def stop( self ):
1197
        "Terminate IVS switch."
1198
        self.cmd( 'kill %ivs' )
1199
        self.cmd( 'wait' )
1200
        self.deleteIntfs()
1201

    
1202
    def attach( self, intf ):
1203
        "Connect a data port"
1204
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1205

    
1206
    def detach( self, intf ):
1207
        "Disconnect a data port"
1208
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1209

    
1210
    def dpctl( self, *args ):
1211
        "Run dpctl command"
1212
        if not self.listenPort:
1213
            return "can't run dpctl without passive listening port"
1214
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1215
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1216

    
1217

    
1218
class Controller( Node ):
1219
    """A Controller is a Node that is running (or has execed?) an
1220
       OpenFlow controller."""
1221

    
1222
    def __init__( self, name, inNamespace=False, command='controller',
1223
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1224
                  port=6633, protocol='tcp', **params ):
1225
        self.command = command
1226
        self.cargs = cargs
1227
        self.cdir = cdir
1228
        self.ip = ip
1229
        self.port = port
1230
        self.protocol = protocol
1231
        Node.__init__( self, name, inNamespace=inNamespace,
1232
                       ip=ip, **params  )
1233
        self.checkListening()
1234

    
1235
    def checkListening( self ):
1236
        "Make sure no controllers are running on our port"
1237
        # Verify that Telnet is installed first:
1238
        out, _err, returnCode = errRun( "which telnet" )
1239
        if 'telnet' not in out or returnCode != 0:
1240
            raise Exception( "Error running telnet to check for listening "
1241
                             "controllers; please check that it is "
1242
                             "installed." )
1243
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1244
                              ( self.ip, self.port ) )
1245
        if 'Connected' in listening:
1246
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1247
            pstr = ':%d ' % self.port
1248
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1249
            raise Exception( "Please shut down the controller which is"
1250
                             " running on port %d:\n" % self.port +
1251
                             '\n'.join( clist ) )
1252

    
1253
    def start( self ):
1254
        """Start <controller> <args> on controller.
1255
           Log to /tmp/cN.log"""
1256
        pathCheck( self.command )
1257
        cout = '/tmp/' + self.name + '.log'
1258
        if self.cdir is not None:
1259
            self.cmd( 'cd ' + self.cdir )
1260
        self.cmd( self.command + ' ' + self.cargs % self.port +
1261
                  ' 1>' + cout + ' 2>' + cout + '&' )
1262
        self.execed = False
1263

    
1264
    def stop( self ):
1265
        "Stop controller."
1266
        self.cmd( 'kill %' + self.command )
1267
        self.terminate()
1268

    
1269
    def IP( self, intf=None ):
1270
        "Return IP address of the Controller"
1271
        if self.intfs:
1272
            ip = Node.IP( self, intf )
1273
        else:
1274
            ip = self.ip
1275
        return ip
1276

    
1277
    def __repr__( self ):
1278
        "More informative string representation"
1279
        return '<%s %s: %s:%s pid=%s> ' % (
1280
            self.__class__.__name__, self.name,
1281
            self.IP(), self.port, self.pid )
1282
    @classmethod
1283
    def isAvailable( self ):
1284
        return quietRun( 'which controller' )
1285

    
1286
class OVSController( Controller ):
1287
    "Open vSwitch controller"
1288
    def __init__( self, name, command='ovs-controller', **kwargs ):
1289
        if quietRun( 'which test-controller' ):
1290
            command = 'test-controller'
1291
        Controller.__init__( self, name, command=command, **kwargs )
1292
    @classmethod
1293
    def isAvailable( self ):
1294
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1295

    
1296
class NOX( Controller ):
1297
    "Controller to run a NOX application."
1298

    
1299
    def __init__( self, name, *noxArgs, **kwargs ):
1300
        """Init.
1301
           name: name to give controller
1302
           noxArgs: arguments (strings) to pass to NOX"""
1303
        if not noxArgs:
1304
            warn( 'warning: no NOX modules specified; '
1305
                  'running packetdump only\n' )
1306
            noxArgs = [ 'packetdump' ]
1307
        elif type( noxArgs ) not in ( list, tuple ):
1308
            noxArgs = [ noxArgs ]
1309

    
1310
        if 'NOX_CORE_DIR' not in os.environ:
1311
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1312
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1313

    
1314
        Controller.__init__( self, name,
1315
                             command=noxCoreDir + '/nox_core',
1316
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1317
                             ' '.join( noxArgs ),
1318
                             cdir=noxCoreDir,
1319
                             **kwargs )
1320

    
1321

    
1322
class RemoteController( Controller ):
1323
    "Controller running outside of Mininet's control."
1324

    
1325
    def __init__( self, name, ip='127.0.0.1',
1326
                  port=6633, **kwargs):
1327
        """Init.
1328
           name: name to give controller
1329
           ip: the IP address where the remote controller is
1330
           listening
1331
           port: the port where the remote controller is listening"""
1332
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1333

    
1334
    def start( self ):
1335
        "Overridden to do nothing."
1336
        return
1337

    
1338
    def stop( self ):
1339
        "Overridden to do nothing."
1340
        return
1341

    
1342
    def checkListening( self ):
1343
        "Warn if remote controller is not accessible"
1344
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1345
                              ( self.ip, self.port ) )
1346
        if 'Connected' not in listening:
1347
            warn( "Unable to contact the remote controller"
1348
                  " at %s:%d\n" % ( self.ip, self.port ) )
1349

    
1350

    
1351
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1352
    "find any controller that is available and run it"
1353
    for controller in order:
1354
        if controller.isAvailable():
1355
            return controller( name, **kwargs )