Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 859bfea5

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

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

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

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

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

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

    
347
    # Interface management, configuration, and routing
348

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

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

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

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

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

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

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

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

    
429
    # Routing support
430

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

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

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

    
455
    # Convenience and configuration methods
456

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

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

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

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

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

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

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

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

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

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

    
540
    # Other methods
541

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

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

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

    
557
    def __str__( self ):
558
        "Abbreviated string representation"
559
        return self.name
560

    
561
    # Automatic class setup support
562

    
563
    isSetup = False
564

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

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

    
579

    
580
class Host( Node ):
581
    "A host is simply a Node"
582
    pass
583

    
584

    
585
class CPULimitedHost( Host ):
586

    
587
    "CPU limited host"
588

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

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

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

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

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

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

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

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

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

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

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

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

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

    
737
    inited = False
738

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

    
745
class HostWithPrivateDirs( Host ):
746
    "Host with private directories"
747

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

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

    
771

    
772

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

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

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

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

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

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

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

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

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

    
856
class UserSwitch( Switch ):
857
    "User-space switch."
858

    
859
    dpidLen = 12
860

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

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

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

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

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

    
905
            res = intf.config( **intf.params )
906

    
907
            if res is None: # link may not have TC parameters
908
                return
909

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

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

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

    
952

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

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

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

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

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

    
1003

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

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

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

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

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

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

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

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

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

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

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

    
1092
    def start( self, controllers ):
1093
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1094
        if self.inNamespace:
1095
            raise Exception(
1096
                'OVS kernel switch does not work in a namespace' )
1097
        # Annoyingly, --if-exists option seems not to work
1098
        self.sendCmd( 'ovs-vsctl del-br', self )
1099
        self.waiting = False
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.sendCmd( 'ovs-vsctl add-br', self )
1121
            self.waiting = False
1122
            for intf in self.intfList():
1123
                if not intf.IP():
1124
                    self.sendCmd( 'ovs-vsctl add-port', self, intf )
1125
                    self.waiting = False
1126
            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
1127
                    'other_config:datapath-id=%s ' % self.dpid +
1128
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1129
                    '-- set-controller %s %s ' % ( self, clist ) )
1130
        if not self.inband:
1131
            cmd += ( '-- set bridge %s '
1132
                     'other-config:disable-in-band=true ' % self )
1133
        if self.datapath == 'user':
1134
            cmd += '-- set bridge %s datapath_type=netdev ' % self
1135
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1136
        for uuid in self.controllerUUIDs():
1137
            if uuid.count( '-' ) != 4:
1138
                # Doesn't look like a UUID
1139
                continue
1140
            uuid = uuid.strip()
1141
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1142
        # Do it!!
1143
        self.cmd( cmd )
1144
        for intf in self.intfList():
1145
            self.TCReapply( intf )
1146

    
1147

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

    
1155
OVSKernelSwitch = OVSSwitch
1156

    
1157

    
1158
class IVSSwitch(Switch):
1159
    """IVS virtual switch"""
1160

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

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

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

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

    
1200
        logfile = '/tmp/ivs.%s.log' % self.name
1201
        self.cmd( 'ifconfig lo up' )
1202
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1203

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

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

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

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

    
1225

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

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

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

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

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

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

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

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

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

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

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

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

    
1330

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

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

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

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

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

    
1359

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