Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 58324bdc

History | View | Annotate | Download (51.4 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
        self.cmd( 'set +m' )
164

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

    
173
    # Subshell I/O, commands and control
174

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

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

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

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

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

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

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

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

    
256
    def monitor( self, timeoutms=None, findPid=True ):
257
        """Monitor and return the output of a command.
258
           Set self.waiting to False if command has completed.
259
           timeoutms: timeout in ms or None to wait indefinitely."""
260
        self.waitReadable( timeoutms )
261
        data = self.read( 1024 )
262
        pidre = r'\[\d+\] \d+\r\n'
263
        # Look for PID
264
        marker = chr( 1 ) + r'\d+\r\n'
265
        if findPid and chr( 1 ) in data:
266
            # suppress the job and PID of a backgrounded command
267
            if re.findall( pidre, data ):
268
                data = re.sub( pidre, '', data )
269
            # Marker can be read in chunks; continue until all of it is read
270
            while not re.findall( marker, data ):
271
                data += self.read( 1024 )
272
            markers = re.findall( marker, data )
273
            if markers:
274
                self.lastPid = int( markers[ 0 ][ 1: ] )
275
                data = re.sub( marker, '', data )
276
        # Look for sentinel/EOF
277
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
278
            self.waiting = False
279
            data = data[ :-1 ]
280
        elif chr( 127 ) in data:
281
            self.waiting = False
282
            data = data.replace( chr( 127 ), '' )
283
        return data
284

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

    
299
    def cmd( self, *args, **kwargs ):
300
        """Send a command, wait for output, and return it.
301
           cmd: string"""
302
        verbose = kwargs.get( 'verbose', False )
303
        log = info if verbose else debug
304
        log( '*** %s : %s\n' % ( self.name, args ) )
305
        self.sendCmd( *args, **kwargs )
306
        return self.waitOutput( verbose )
307

    
308
    def cmdPrint( self, *args):
309
        """Call cmd and printing its output
310
           cmd: string"""
311
        return self.cmd( *args, **{ 'verbose': True } )
312

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

    
342
    def pexec( self, *args, **kwargs ):
343
        """Execute a command using popen
344
           returns: out, err, exitcode"""
345
        popen = self.popen( *args, **kwargs)
346
        out, err = popen.communicate()
347
        exitcode = popen.wait()
348
        return out, err, exitcode
349

    
350
    # Interface management, configuration, and routing
351

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

    
358
    def newPort( self ):
359
        "Return the next port number to allocate."
360
        if len( self.ports ) > 0:
361
            return max( self.ports.values() ) + 1
362
        return self.portBase
363

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

    
379
    def defaultIntf( self ):
380
        "Return interface for lowest port"
381
        ports = self.intfs.keys()
382
        if ports:
383
            return self.intfs[ min( ports ) ]
384
        else:
385
            warn( '*** defaultIntf: warning:', self.name,
386
                  'has no interfaces\n' )
387

    
388
    def intf( self, intf='' ):
389
        """Return our interface object with given string name,
390
           default intf if name is falsy (None, empty string, etc).
391
           or the input intf arg.
392

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

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

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

    
432
    # Routing support
433

    
434
    def setARP( self, ip, mac ):
435
        """Add an ARP entry.
436
           ip: IP address as string
437
           mac: MAC address as string"""
438
        result = self.cmd( 'arp', '-s', ip, mac )
439
        return result
440

    
441
    def setHostRoute( self, ip, intf ):
442
        """Add route to host.
443
           ip: IP address as dotted decimal
444
           intf: string, interface name"""
445
        return self.cmd( 'route add -host', ip, 'dev', intf )
446

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

    
458
    # Convenience and configuration methods
459

    
460
    def setMAC( self, mac, intf=None ):
461
        """Set the MAC address for an interface.
462
           intf: intf or intf name
463
           mac: MAC address as string"""
464
        return self.intf( intf ).setMAC( mac )
465

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

    
476
    def IP( self, intf=None ):
477
        "Return IP address of a node or specific interface."
478
        return self.intf( intf ).IP()
479

    
480
    def MAC( self, intf=None ):
481
        "Return MAC address of a node or specific interface."
482
        return self.intf( intf ).MAC()
483

    
484
    def intfIsUp( self, intf=None ):
485
        "Check if an interface is up."
486
        return self.intf( intf ).isUp()
487

    
488
    # The reason why we configure things in this way is so
489
    # That the parameters can be listed and documented in
490
    # the config method.
491
    # Dealing with subclasses and superclasses is slightly
492
    # annoying, but at least the information is there!
493

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

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

    
532
    def configDefault( self, **moreParams ):
533
        "Configure with default parameters"
534
        self.params.update( moreParams )
535
        self.config( **self.params )
536

    
537
    # This is here for backward compatibility
538
    def linkTo( self, node, link=Link ):
539
        """(Deprecated) Link to another node
540
           replace with Link( node1, node2)"""
541
        return link( self, node )
542

    
543
    # Other methods
544

    
545
    def intfList( self ):
546
        "List of our interfaces sorted by port number"
547
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
548

    
549
    def intfNames( self ):
550
        "The names of our interfaces sorted by port number"
551
        return [ str( i ) for i in self.intfList() ]
552

    
553
    def __repr__( self ):
554
        "More informative string representation"
555
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
556
                              for i in self.intfList() ] ) )
557
        return '<%s %s: %s pid=%s> ' % (
558
            self.__class__.__name__, self.name, intfs, self.pid )
559

    
560
    def __str__( self ):
561
        "Abbreviated string representation"
562
        return self.name
563

    
564
    # Automatic class setup support
565

    
566
    isSetup = False
567

    
568
    @classmethod
569
    def checkSetup( cls ):
570
        "Make sure our class and superclasses are set up"
571
        while cls and not getattr( cls, 'isSetup', True ):
572
            cls.setup()
573
            cls.isSetup = True
574
            # Make pylint happy
575
            cls = getattr( type( cls ), '__base__', None )
576

    
577
    @classmethod
578
    def setup( cls ):
579
        "Make sure our class dependencies are available"
580
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
581

    
582

    
583
class Host( Node ):
584
    "A host is simply a Node"
585
    pass
586

    
587

    
588
class CPULimitedHost( Host ):
589

    
590
    "CPU limited host"
591

    
592
    def __init__( self, name, sched='cfs', **kwargs ):
593
        Host.__init__( self, name, **kwargs )
594
        # Initialize class if necessary
595
        if not CPULimitedHost.inited:
596
            CPULimitedHost.init()
597
        # Create a cgroup and move shell into it
598
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
599
        errFail( 'cgcreate -g ' + self.cgroup )
600
        # We don't add ourselves to a cpuset because you must
601
        # specify the cpu and memory placement first
602
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
603
        # BL: Setting the correct period/quota is tricky, particularly
604
        # for RT. RT allows very small quotas, but the overhead
605
        # seems to be high. CFS has a mininimum quota of 1 ms, but
606
        # still does better with larger period values.
607
        self.period_us = kwargs.get( 'period_us', 100000 )
608
        self.sched = sched
609
        if self.sched == 'rt':
610
            release = quietRun( 'uname -r' ).strip('\r\n')
611
            output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
612
            if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
613
                error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
614
                exit( 1 )
615
        self.rtprio = 20
616

    
617
    def cgroupSet( self, param, value, resource='cpu' ):
618
        "Set a cgroup parameter and return its value"
619
        cmd = 'cgset -r %s.%s=%s /%s' % (
620
            resource, param, value, self.name )
621
        quietRun( cmd )
622
        nvalue = int( self.cgroupGet( param, resource ) )
623
        if nvalue != value:
624
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
625
                   % ( param, nvalue, value ) )
626
        return nvalue
627

    
628
    def cgroupGet( self, param, resource='cpu' ):
629
        "Return value of cgroup parameter"
630
        cmd = 'cgget -r %s.%s /%s' % (
631
            resource, param, self.name )
632
        return int( quietRun( cmd ).split()[ -1 ] )
633

    
634
    def cgroupDel( self ):
635
        "Clean up our cgroup"
636
        # info( '*** deleting cgroup', self.cgroup, '\n' )
637
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
638
        return exitcode != 0
639

    
640
    def popen( self, *args, **kwargs ):
641
        """Return a Popen() object in node's namespace
642
           args: Popen() args, single list, or string
643
           kwargs: Popen() keyword args"""
644
        # Tell mnexec to execute command in our cgroup
645
        mncmd = [ 'mnexec', '-da', str( self.pid ),
646
                  '-g', self.name ]
647
        if self.sched == 'rt':
648
            mncmd += [ '-r', str( self.rtprio ) ]
649
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
650

    
651
    def cleanup( self ):
652
        "Clean up Node, then clean up our cgroup"
653
        super( CPULimitedHost, self ).cleanup()
654
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
655

    
656
    def chrt( self ):
657
        "Set RT scheduling priority"
658
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
659
        result = quietRun( 'chrt -p %s' % self.pid )
660
        firstline = result.split( '\n' )[ 0 ]
661
        lastword = firstline.split( ' ' )[ -1 ]
662
        if lastword != 'SCHED_RR':
663
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
664
        return lastword
665

    
666
    def rtInfo( self, f ):
667
        "Internal method: return parameters for RT bandwidth"
668
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
669
        # RT uses wall clock time for period and quota
670
        quota = int( self.period_us * f * numCores() )
671
        return pstr, qstr, self.period_us, quota
672

    
673
    def cfsInfo( self, f):
674
        "Internal method: return parameters for CFS bandwidth"
675
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
676
        # CFS uses wall clock time for period and CPU time for quota.
677
        quota = int( self.period_us * f * numCores() )
678
        period = self.period_us
679
        if f > 0 and quota < 1000:
680
            debug( '(cfsInfo: increasing default period) ' )
681
            quota = 1000
682
            period = int( quota / f / numCores() )
683
        return pstr, qstr, period, quota
684

    
685
    # BL comment:
686
    # This may not be the right API,
687
    # since it doesn't specify CPU bandwidth in "absolute"
688
    # units the way link bandwidth is specified.
689
    # We should use MIPS or SPECINT or something instead.
690
    # Alternatively, we should change from system fraction
691
    # to CPU seconds per second, essentially assuming that
692
    # all CPUs are the same.
693

    
694
    def setCPUFrac( self, f=-1, sched=None):
695
        """Set overall CPU fraction for this host
696
           f: CPU bandwidth limit (fraction)
697
           sched: 'rt' or 'cfs'
698
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
699
        if not f:
700
            return
701
        if not sched:
702
            sched = self.sched
703
        if sched == 'rt':
704
            pstr, qstr, period, quota = self.rtInfo( f )
705
        elif sched == 'cfs':
706
            pstr, qstr, period, quota = self.cfsInfo( f )
707
        else:
708
            return
709
        if quota < 0:
710
            # Reset to unlimited
711
            quota = -1
712
        # Set cgroup's period and quota
713
        self.cgroupSet( pstr, period )
714
        self.cgroupSet( qstr, quota )
715
        if sched == 'rt':
716
            # Set RT priority if necessary
717
            self.chrt()
718
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
719

    
720
    def setCPUs( self, cores, mems=0 ):
721
        "Specify (real) cores that our cgroup can run on"
722
        if type( cores ) is list:
723
            cores = ','.join( [ str( c ) for c in cores ] )
724
        self.cgroupSet( resource='cpuset', param='cpus',
725
                        value=cores )
726
        # Memory placement is probably not relevant, but we
727
        # must specify it anyway
728
        self.cgroupSet( resource='cpuset', param='mems',
729
                        value=mems)
730
        # We have to do this here after we've specified
731
        # cpus and mems
732
        errFail( 'cgclassify -g cpuset:/%s %s' % (
733
                 self.name, self.pid ) )
734

    
735
    def config( self, cpu=None, cores=None, **params ):
736
        """cpu: desired overall system CPU fraction
737
           cores: (real) core(s) this host can run on
738
           params: parameters for Node.config()"""
739
        r = Node.config( self, **params )
740
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
741
        # that seems redundant
742
        self.setParam( r, 'setCPUFrac', cpu=cpu )
743
        self.setParam( r, 'setCPUs', cores=cores )
744
        return r
745

    
746
    inited = False
747

    
748
    @classmethod
749
    def init( cls ):
750
        "Initialization for CPULimitedHost class"
751
        mountCgroups()
752
        cls.inited = True
753

    
754
class HostWithPrivateDirs( Host ):
755
    "Host with private directories"
756

    
757
    def __init__( self, name, *args, **kwargs ):
758
        "privateDirs: list of private directory strings or tuples"
759
        self.name = name
760
        self.privateDirs = kwargs.pop( 'privateDirs', [] )
761
        Host.__init__( self, name, *args, **kwargs )
762
        self.mountPrivateDirs()
763

    
764
    def mountPrivateDirs( self ):
765
        "mount private directories"
766
        for directory in self.privateDirs:
767
            if isinstance( directory, tuple ):
768
                # mount given private directory
769
                privateDir = directory[ 1 ] % self.__dict__ 
770
                mountPoint = directory[ 0 ]
771
                self.cmd( 'mkdir -p %s' % privateDir )
772
                self.cmd( 'mkdir -p %s' % mountPoint )
773
                self.cmd( 'mount --bind %s %s' %
774
                               ( privateDir, mountPoint ) )
775
            else:
776
                # mount temporary filesystem on directory
777
                self.cmd( 'mkdir -p %s' % directory ) 
778
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
779

    
780

    
781

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

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

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

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

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

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

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

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

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

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

    
868
    dpidLen = 12
869

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

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

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

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

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

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

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

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

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

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

    
962

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

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

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

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

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

    
1012

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

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

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

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

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

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

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

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

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

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

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

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

    
1156

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

    
1164
OVSKernelSwitch = OVSSwitch
1165

    
1166

    
1167
class IVSSwitch(Switch):
1168
    """IVS virtual switch"""
1169

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

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

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

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

    
1209
        logfile = '/tmp/ivs.%s.log' % self.name
1210

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

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

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

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

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

    
1234

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

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

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

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

    
1281
    def stop( self ):
1282
        "Stop controller."
1283
        self.cmd( 'kill %' + self.command )
1284
        self.terminate()
1285

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

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

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

    
1313
class NOX( Controller ):
1314
    "Controller to run a NOX application."
1315

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

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

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

    
1338

    
1339
class RemoteController( Controller ):
1340
    "Controller running outside of Mininet's control."
1341

    
1342
    def __init__( self, name, ip='127.0.0.1',
1343
                  port=6633, **kwargs):
1344
        """Init.
1345
           name: name to give controller
1346
           ip: the IP address where the remote controller is
1347
           listening
1348
           port: the port where the remote controller is listening"""
1349
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1350

    
1351
    def start( self ):
1352
        "Overridden to do nothing."
1353
        return
1354

    
1355
    def stop( self ):
1356
        "Overridden to do nothing."
1357
        return
1358

    
1359
    def checkListening( self ):
1360
        "Warn if remote controller is not accessible"
1361
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1362
                              ( self.ip, self.port ) )
1363
        if 'Connected' not in listening:
1364
            warn( "Unable to contact the remote controller"
1365
                  " at %s:%d\n" % ( self.ip, self.port ) )
1366

    
1367
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1368
    "find any controller that is available and run it"
1369
    for controller in order:
1370
        if controller.isAvailable():
1371
            return controller( name, **kwargs )