Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 4015e066

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

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

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

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

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

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

    
345
    # Interface management, configuration, and routing
346

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

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

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

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

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

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

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

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

    
427
    # Routing support
428

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

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

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

    
453
    # Convenience and configuration methods
454

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

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

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

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

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

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

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

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

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

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

    
538
    # Other methods
539

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

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

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

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

    
559
    # Automatic class setup support
560

    
561
    isSetup = False
562

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

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

    
577

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

    
582

    
583
class CPULimitedHost( Host ):
584

    
585
    "CPU limited host"
586

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

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

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

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

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

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

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

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

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

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

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

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

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

    
735
    inited = False
736

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

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

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

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

    
769

    
770

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

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

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

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

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

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

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

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

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

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

    
857
    dpidLen = 12
858

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

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

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

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

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

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

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

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

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

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

    
950

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

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

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

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

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

    
1001

    
1002
class OVSSwitch( Switch ):
1003
    "Open vSwitch switch. Depends on ovs-vsctl."
1004

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

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

    
1039
    @classmethod
1040
    def isOldOVS( cls ):
1041
        return ( StrictVersion( cls.OVSVersion ) <
1042
             StrictVersion( '1.10' ) )
1043

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

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

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

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

    
1069
    def detach( self, intf ):
1070
        "Disconnect a data port"
1071
        self.cmd( 'ovs-vsctl del-port', self, intf )
1072

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

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

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

    
1145

    
1146
    def stop( self ):
1147
        "Terminate OVS switch."
1148
        self.cmd( 'ovs-vsctl del-br', self )
1149
        if self.datapath == 'user':
1150
            self.cmd( 'ip link del', self )
1151
        self.deleteIntfs()
1152

    
1153
OVSKernelSwitch = OVSSwitch
1154

    
1155

    
1156
class IVSSwitch(Switch):
1157
    """IVS virtual switch"""
1158

    
1159
    def __init__( self, name, verbose=True, **kwargs ):
1160
        Switch.__init__( self, name, **kwargs )
1161
        self.verbose = verbose
1162

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

    
1176
    @classmethod
1177
    def batchShutdown( cls, switches ):
1178
        "Kill each IVS switch, to be waited on later in stop()"
1179
        for switch in switches:
1180
            switch.cmd( 'kill %ivs' )
1181

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

    
1198
        logfile = '/tmp/ivs.%s.log' % self.name
1199

    
1200
        self.cmd( 'ifconfig lo up' )
1201
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1202

    
1203
    def stop( self ):
1204
        "Terminate IVS switch."
1205
        self.cmd( 'kill %ivs' )
1206
        self.cmd( 'wait' )
1207
        self.deleteIntfs()
1208

    
1209
    def attach( self, intf ):
1210
        "Connect a data port"
1211
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1212

    
1213
    def detach( self, intf ):
1214
        "Disconnect a data port"
1215
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1216

    
1217
    def dpctl( self, *args ):
1218
        "Run dpctl command"
1219
        if not self.listenPort:
1220
            return "can't run dpctl without passive listening port"
1221
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1222
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1223

    
1224

    
1225
class Controller( Node ):
1226
    """A Controller is a Node that is running (or has execed?) an
1227
       OpenFlow controller."""
1228

    
1229
    def __init__( self, name, inNamespace=False, command='controller',
1230
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1231
                  port=6633, protocol='tcp', **params ):
1232
        self.command = command
1233
        self.cargs = cargs
1234
        self.cdir = cdir
1235
        self.ip = ip
1236
        self.port = port
1237
        self.protocol = protocol
1238
        Node.__init__( self, name, inNamespace=inNamespace,
1239
                       ip=ip, **params  )
1240
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1241
        self.checkListening()
1242

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

    
1261
    def start( self ):
1262
        """Start <controller> <args> on controller.
1263
           Log to /tmp/cN.log"""
1264
        pathCheck( self.command )
1265
        cout = '/tmp/' + self.name + '.log'
1266
        if self.cdir is not None:
1267
            self.cmd( 'cd ' + self.cdir )
1268
        self.cmd( self.command + ' ' + self.cargs % self.port +
1269
                  ' 1>' + cout + ' 2>' + cout + '&' )
1270
        self.execed = False
1271

    
1272
    def stop( self ):
1273
        "Stop controller."
1274
        self.cmd( 'kill %' + self.command )
1275
        self.terminate()
1276

    
1277
    def IP( self, intf=None ):
1278
        "Return IP address of the Controller"
1279
        if self.intfs:
1280
            ip = Node.IP( self, intf )
1281
        else:
1282
            ip = self.ip
1283
        return ip
1284

    
1285
    def __repr__( self ):
1286
        "More informative string representation"
1287
        return '<%s %s: %s:%s pid=%s> ' % (
1288
            self.__class__.__name__, self.name,
1289
            self.IP(), self.port, self.pid )
1290
    @classmethod
1291
    def isAvailable( self ):
1292
        return quietRun( 'which controller' )
1293

    
1294
class OVSController( Controller ):
1295
    "Open vSwitch controller"
1296
    def __init__( self, name, command='ovs-controller', **kwargs ):
1297
        if quietRun( 'which test-controller' ):
1298
            command = 'test-controller'
1299
        Controller.__init__( self, name, command=command, **kwargs )
1300
    @classmethod
1301
    def isAvailable( self ):
1302
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1303

    
1304
class NOX( Controller ):
1305
    "Controller to run a NOX application."
1306

    
1307
    def __init__( self, name, *noxArgs, **kwargs ):
1308
        """Init.
1309
           name: name to give controller
1310
           noxArgs: arguments (strings) to pass to NOX"""
1311
        if not noxArgs:
1312
            warn( 'warning: no NOX modules specified; '
1313
                  'running packetdump only\n' )
1314
            noxArgs = [ 'packetdump' ]
1315
        elif type( noxArgs ) not in ( list, tuple ):
1316
            noxArgs = [ noxArgs ]
1317

    
1318
        if 'NOX_CORE_DIR' not in os.environ:
1319
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1320
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1321

    
1322
        Controller.__init__( self, name,
1323
                             command=noxCoreDir + '/nox_core',
1324
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1325
                             ' '.join( noxArgs ),
1326
                             cdir=noxCoreDir,
1327
                             **kwargs )
1328

    
1329

    
1330
class RemoteController( Controller ):
1331
    "Controller running outside of Mininet's control."
1332

    
1333
    def __init__( self, name, ip='127.0.0.1',
1334
                  port=6633, **kwargs):
1335
        """Init.
1336
           name: name to give controller
1337
           ip: the IP address where the remote controller is
1338
           listening
1339
           port: the port where the remote controller is listening"""
1340
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1341

    
1342
    def start( self ):
1343
        "Overridden to do nothing."
1344
        return
1345

    
1346
    def stop( self ):
1347
        "Overridden to do nothing."
1348
        return
1349

    
1350
    def checkListening( self ):
1351
        "Warn if remote controller is not accessible"
1352
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1353
                              ( self.ip, self.port ) )
1354
        if 'Connected' not in listening:
1355
            warn( "Unable to contact the remote controller"
1356
                  " at %s:%d\n" % ( self.ip, self.port ) )
1357

    
1358
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1359
    "find any controller that is available and run it"
1360
    for controller in order:
1361
        if controller.isAvailable():
1362
            return controller( name, **kwargs )