Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 03dd914e

History | View | Annotate | Download (25.7 KB)

1
"""
2
Node objects for Mininet.
3

4
Nodes provide a simple abstraction for interacting with hosts, switches
5
and controllers. Local nodes are simply one or more processes on the local
6
machine.
7

8
Node: superclass for all (primarily local) network nodes.
9

10
Host: a virtual host. By default, a host is simply a shell; commands
11
    may be sent using Cmd (which waits for output), or using sendCmd(),
12
    which returns immediately, allowing subsequent monitoring using
13
    monitor(). Examples of how to run experiments using this
14
    functionality are provided in the examples/ directory.
15

16
Switch: superclass for switch nodes.
17

18
UserSwitch: a switch using the user-space switch from the OpenFlow
19
    reference implementation.
20

21
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
22
    implementation.
23

24
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
25
    implementation (openvswitch.org).
26

27
Controller: superclass for OpenFlow controllers. The default controller
28
    is controller(8) from the reference implementation.
29

30
NOXController: a controller node using NOX (noxrepo.org).
31

32
RemoteController: a remote controller node, which may use any
33
    arbitrary OpenFlow-compatible controller, and which is not
34
    created or managed by mininet.
35

36
Future enhancements:
37

38
- Possibly make Node, Switch and Controller more abstract so that
39
  they can be used for both local and remote nodes
40

41
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
42
"""
43

    
44
import os
45
import re
46
import signal
47
import select
48
from subprocess import Popen, PIPE, STDOUT
49

    
50
from mininet.log import info, error, debug
51
from mininet.util import quietRun, errRun, moveIntf, isShellBuiltin
52
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
53
from mininet.link import Link
54

    
55
SWITCH_PORT_BASE = 1  # For OF > 0.9, switch ports start at 1 rather than zero
56

    
57
class Node( object ):
58
    """A virtual network node is simply a shell in a network namespace.
59
       We communicate with it using pipes."""
60

    
61
    inToNode = {}  # mapping of input fds to nodes
62
    outToNode = {}  # mapping of output fds to nodes
63

    
64
    portBase = 0  # Nodes always start with eth0/port0, even in OF 1.0
65

    
66
    def __init__( self, name, inNamespace=True,
67
        defaultMAC=None, defaultIP=None, **kwargs ):
68
        """name: name of node
69
           inNamespace: in network namespace?
70
           defaultMAC: default MAC address for intf 0
71
           defaultIP: default IP address for intf 0"""
72
        self.name = name
73
        self.inNamespace = inNamespace
74
        self.defaultIP = defaultIP
75
        self.defaultMAC = defaultMAC
76
        opts = '-cdp'
77
        if self.inNamespace:
78
            opts += 'n'
79
        cmd = [ 'mnexec', opts, 'bash', '-m' ]
80
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
81
            close_fds=True )
82
        self.stdin = self.shell.stdin
83
        self.stdout = self.shell.stdout
84
        self.pid = self.shell.pid
85
        self.pollOut = select.poll()
86
        self.pollOut.register( self.stdout )
87
        # Maintain mapping between file descriptors and nodes
88
        # This is useful for monitoring multiple nodes
89
        # using select.poll()
90
        self.outToNode[ self.stdout.fileno() ] = self
91
        self.inToNode[ self.stdin.fileno() ] = self
92
        self.intfs = {}  # dict of port numbers to interfaces
93
        self.ports = {}  # dict of interfaces to port numbers
94
                         # replace with Port objects, eventually ?
95
        self.nameToIntf = {}  # dict of interface names to Intfs
96
        self.execed = False
97
        self.lastCmd = None
98
        self.lastPid = None
99
        self.readbuf = ''
100
        self.waiting = False
101
        # Stash additional information as desired
102
        self.args = kwargs
103

    
104
    @classmethod
105
    def fdToNode( cls, fd ):
106
        """Return node corresponding to given file descriptor.
107
           fd: file descriptor
108
           returns: node"""
109
        node = Node.outToNode.get( fd )
110
        return node or Node.inToNode.get( fd )
111

    
112
    def cleanup( self ):
113
        "Help python collect its garbage."
114
        self.shell = None
115

    
116
    # Subshell I/O, commands and control
117
    def read( self, bytes=1024 ):
118
        """Buffered read from node, non-blocking.
119
           bytes: maximum number of bytes to return"""
120
        count = len( self.readbuf )
121
        if count < bytes:
122
            data = os.read( self.stdout.fileno(), bytes - count )
123
            self.readbuf += data
124
        if bytes >= len( self.readbuf ):
125
            result = self.readbuf
126
            self.readbuf = ''
127
        else:
128
            result = self.readbuf[ :bytes ]
129
            self.readbuf = self.readbuf[ bytes: ]
130
        return result
131

    
132
    def readline( self ):
133
        """Buffered readline from node, non-blocking.
134
           returns: line (minus newline) or None"""
135
        self.readbuf += self.read( 1024 )
136
        if '\n' not in self.readbuf:
137
            return None
138
        pos = self.readbuf.find( '\n' )
139
        line = self.readbuf[ 0 : pos ]
140
        self.readbuf = self.readbuf[ pos + 1: ]
141
        return line
142

    
143
    def write( self, data ):
144
        """Write data to node.
145
           data: string"""
146
        os.write( self.stdin.fileno(), data )
147

    
148
    def terminate( self ):
149
        "Send kill signal to Node and clean up after it."
150
        os.kill( self.pid, signal.SIGKILL )
151
        self.cleanup()
152

    
153
    def stop( self ):
154
        "Stop node."
155
        self.terminate()
156

    
157
    def waitReadable( self, timeoutms=None ):
158
        """Wait until node's output is readable.
159
           timeoutms: timeout in ms or None to wait indefinitely."""
160
        if len( self.readbuf ) == 0:
161
            self.pollOut.poll( timeoutms )
162

    
163
    def sendCmd( self, *args, **kwargs ):
164
        """Send a command, followed by a command to echo a sentinel,
165
           and return without waiting for the command to complete.
166
           args: command and arguments, or string
167
           printPid: print command's PID?"""
168
        assert not self.waiting
169
        printPid = kwargs.get( 'printPid', True )
170
        if len( args ) > 0:
171
            cmd = args
172
        if not isinstance( cmd, str ):
173
            cmd = ' '.join( [ str( c ) for c in cmd ] )
174
        if not re.search( r'\w', cmd ):
175
            # Replace empty commands with something harmless
176
            cmd = 'echo -n'
177
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
178
            separator = '&'
179
            cmd = cmd[ :-1 ]
180
        else:
181
            separator = ';'
182
            if printPid and not isShellBuiltin( cmd ):
183
                cmd = 'mnexec -p ' + cmd
184
        self.write( cmd + separator + ' printf "\\177" \n' )
185
        self.lastCmd = cmd
186
        self.lastPid = None
187
        self.waiting = True
188

    
189
    def sendInt( self, sig=signal.SIGINT ):
190
        "Interrupt running command."
191
        if self.lastPid:
192
            try:
193
                os.kill( self.lastPid, sig )
194
            except OSError:
195
                pass
196

    
197
    def monitor( self, timeoutms=None ):
198
        """Monitor and return the output of a command.
199
           Set self.waiting to False if command has completed.
200
           timeoutms: timeout in ms or None to wait indefinitely."""
201
        self.waitReadable( timeoutms )
202
        data = self.read( 1024 )
203
        # Look for PID
204
        marker = chr( 1 ) + r'\d+\n'
205
        if chr( 1 ) in data:
206
            markers = re.findall( marker, data )
207
            if markers:
208
                self.lastPid = int( markers[ 0 ][ 1: ] )
209
                data = re.sub( marker, '', data )
210
        # Look for sentinel/EOF
211
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
212
            self.waiting = False
213
            data = data[ :-1 ]
214
        elif chr( 127 ) in data:
215
            self.waiting = False
216
            data = data.replace( chr( 127 ), '' )
217
        return data
218

    
219
    def waitOutput( self, verbose=False ):
220
        """Wait for a command to complete.
221
           Completion is signaled by a sentinel character, ASCII(127)
222
           appearing in the output stream.  Wait for the sentinel and return
223
           the output, including trailing newline.
224
           verbose: print output interactively"""
225
        log = info if verbose else debug
226
        output = ''
227
        while self.waiting:
228
            data = self.monitor()
229
            output += data
230
            log( data )
231
        return output
232

    
233
    def cmd( self, *args, **kwargs ):
234
        """Send a command, wait for output, and return it.
235
           cmd: string"""
236
        verbose = kwargs.get( 'verbose', False )
237
        log = info if verbose else debug
238
        log( '*** %s : %s\n' % ( self.name, args ) )
239
        self.sendCmd( *args, **kwargs )
240
        return self.waitOutput( verbose )
241

    
242
    def cmdPrint( self, *args):
243
        """Call cmd and printing its output
244
           cmd: string"""
245
        return self.cmd( *args, **{ 'verbose': True } )
246

    
247
    # Interface management, configuration, and routing
248

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

    
255
    def newPort( self ):
256
        "Return the next port number to allocate."
257
        if len( self.ports ) > 0:
258
            return max( self.ports.values() ) + 1
259
        return self.portBase
260

    
261
    def addIntf( self, intf, port=None ):
262
        """Add an interface.
263
           intf: interface
264
           port: port number (optional, typically OpenFlow port number)"""
265
        if port is None:
266
            port = self.newPort()
267
        self.intfs[ port ] = intf
268
        self.ports[ intf ] = port
269
        self.nameToIntf[ intf.name ] = intf
270
        info( '\n' )
271
        info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
272
        if self.inNamespace:
273
            info( 'moving', intf, 'into namespace for', self.name, '\n' )
274
            moveIntf( intf.name, self )
275

    
276
    def defaultIntf( self ):
277
        "Return interface for lowest port"
278
        ports = self.intfs.keys()
279
        if ports:
280
            return self.intfs[ min( ports ) ]
281

    
282
    def intf( self, intf='' ):
283
        """Return our interface object with given name,x
284
           or default intf if name is empty"""
285
        if not intf:
286
            return self.defaultIntf()
287
        elif type( intf) is str:
288
            return self.nameToIntf[ intf ]
289
        else:
290
            return intf
291

    
292
    def linksTo( self, node):
293
        "Return [ link1, link2...] for all links from self to node."
294
        # We could optimize this if it is important
295
        links = []
296
        for intf in self.intfs:
297
            link = intf.link
298
            nodes = ( link.intf1.node, link.intf2.node )
299
            if self in nodes and node in nodes:
300
                links.append( link )
301
        return links
302

    
303
    def deleteIntfs( self ):
304
        "Delete all of our interfaces."
305
        # In theory the interfaces should go away after we shut down.
306
        # However, this takes time, so we're better off removing them
307
        # explicitly so that we won't get errors if we run before they
308
        # have been removed by the kernel. Unfortunately this is very slow,
309
        # at least with Linux kernels before 2.6.33
310
        for intf in self.intfs.values():
311
            intf.delete()
312
            info( '.' )
313

    
314
    # Routing support
315

    
316
    def setARP( self, ip, mac ):
317
        """Add an ARP entry.
318
           ip: IP address as string
319
           mac: MAC address as string"""
320
        result = self.cmd( 'arp', '-s', ip, mac )
321
        return result
322

    
323
    def setHostRoute( self, ip, intf ):
324
        """Add route to host.
325
           ip: IP address as dotted decimal
326
           intf: string, interface name"""
327
        return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
328

    
329
    def setDefaultRoute( self, intf ):
330
        """Set the default route to go through intf.
331
           intf: string, interface name"""
332
        self.cmd( 'ip route flush root 0/0' )
333
        return self.cmd( 'route add default %s' % intf )
334

    
335
    # Convenience methods
336

    
337
    def setMAC( self, mac, intf=''):
338
        """Set the MAC address for an interface.
339
           intf: intf or intf name
340
           mac: MAC address as string"""
341
        return self.intf( intf ).setMAC( mac )
342

    
343
    def setIP( self, ip, prefixLen=8, intf='' ):
344
        """Set the IP address for an interface.
345
           intf: interface name
346
           ip: IP address as a string
347
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
348
        # This should probably be rethought:
349
        ipSub = '%s/%s' % ( ip, prefixLen )
350
        return self.intf( intf ).setIP( ipSub )
351

    
352
    def IP( self, intf=None ):
353
        "Return IP address of a node or specific interface."
354
        return self.intf( intf ).IP()
355

    
356
    def MAC( self, intf=None ):
357
        "Return MAC address of a node or specific interface."
358
        return self.intf( intf ).MAC()
359

    
360
    def intfIsUp( self, intf=None ):
361
        "Check if an interface is up."
362
        return self.intf( intf ).isUp()
363

    
364
    # This is here for backward compatibility
365
    def linkTo( self, node, link=Link ):
366
        """(Deprecated) Link to another node
367
           replace with Link( node1, node2)"""
368
        return link( self, node )
369

    
370
    # Other methods
371

    
372
    def intfList( self ):
373
        "List of our interfaces sorted by port number"
374
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
375

    
376
    def intfNames( self ):
377
        "The names of our interfaces sorted by port number"
378
        return [ str( i ) for i in self.intfList() ]
379

    
380
    def __str__( self ):
381
        return '%s: IP=%s intfs=%s pid=%s' % (
382
            self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
383

    
384

    
385
class Host( Node ):
386
    "A host is simply a Node."
387

    
388

    
389
class Switch( Node ):
390
    """A Switch is a Node that is running (or has execed?)
391
       an OpenFlow switch."""
392

    
393
    portBase = SWITCH_PORT_BASE  # 0 for OF < 1.0, 1 for OF >= 1.0
394

    
395
    def __init__( self, name, opts='', listenPort=None, **kwargs):
396
        Node.__init__( self, name, **kwargs )
397
        self.opts = opts
398
        self.listenPort = listenPort
399
        if self.listenPort:
400
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
401

    
402
    def defaultIntf( self ):
403
        "Return interface for HIGHEST port"
404
        ports = self.intfs.keys()
405
        if ports:
406
            intf = self.intfs[ max( ports ) ]
407
        return intf
408

    
409
    def sendCmd( self, *cmd, **kwargs ):
410
        """Send command to Node.
411
           cmd: string"""
412
        kwargs.setdefault( 'printPid', False )
413
        if not self.execed:
414
            return Node.sendCmd( self, *cmd, **kwargs )
415
        else:
416
            error( '*** Error: %s has execed and cannot accept commands' %
417
                     self.name )
418

    
419
class UserSwitch( Switch ):
420
    "User-space switch."
421

    
422
    def __init__( self, name, **kwargs ):
423
        """Init.
424
           name: name for the switch"""
425
        Switch.__init__( self, name, **kwargs )
426
        pathCheck( 'ofdatapath', 'ofprotocol',
427
            moduleName='the OpenFlow reference user switch (openflow.org)' )
428

    
429
    @staticmethod
430
    def setup():
431
        "Ensure any dependencies are loaded; if not, try to load them."
432
        if not os.path.exists( '/dev/net/tun' ):
433
            moduleDeps( add=TUN )
434

    
435
    def start( self, controllers ):
436
        """Start OpenFlow reference user datapath.
437
           Log to /tmp/sN-{ofd,ofp}.log.
438
           controllers: list of controller objects"""
439
        controller = controllers[ 0 ]
440
        ofdlog = '/tmp/' + self.name + '-ofd.log'
441
        ofplog = '/tmp/' + self.name + '-ofp.log'
442
        self.cmd( 'ifconfig lo up' )
443
        mac_str = ''
444
        if self.defaultMAC:
445
            # ofdatapath expects a string of hex digits with no colons.
446
            mac_str = ' -d ' + ''.join( self.defaultMAC.split( ':' ) )
447
        intfs = sorted( self.intfs.values() )
448
        if self.inNamespace:
449
            intfs = intfs[ :-1 ]
450
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
451
            ' punix:/tmp/' + self.name + mac_str + ' --no-slicing ' +
452
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
453
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
454
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
455
            ' --fail=closed ' + self.opts +
456
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
457

    
458
    def stop( self ):
459
        "Stop OpenFlow reference user datapath."
460
        self.cmd( 'kill %ofdatapath' )
461
        self.cmd( 'kill %ofprotocol' )
462
        self.deleteIntfs()
463

    
464
class KernelSwitch( Switch ):
465
    """Kernel-space switch.
466
       Currently only works in root namespace."""
467

    
468
    def __init__( self, name, dp=None, **kwargs ):
469
        """Init.
470
           name: name for switch
471
           dp: netlink id (0, 1, 2, ...)
472
           defaultMAC: default MAC as string; random value if None"""
473
        Switch.__init__( self, name, **kwargs )
474
        self.dp = 'nl:%i' % dp
475
        self.intf = 'of%i' % dp
476
        if self.inNamespace:
477
            error( "KernelSwitch currently only works"
478
                " in the root namespace." )
479
            exit( 1 )
480

    
481
    @staticmethod
482
    def setup():
483
        "Ensure any dependencies are loaded; if not, try to load them."
484
        pathCheck( 'ofprotocol',
485
            moduleName='the OpenFlow reference kernel switch'
486
            ' (openflow.org) (NOTE: not available in OpenFlow 1.0!)' )
487
        moduleDeps( subtract=OVS_KMOD, add=OF_KMOD )
488

    
489
    def start( self, controllers ):
490
        "Start up reference kernel datapath."
491
        ofplog = '/tmp/' + self.name + '-ofp.log'
492
        quietRun( 'ifconfig lo up' )
493
        # Delete local datapath if it exists;
494
        # then create a new one monitoring the given interfaces
495
        quietRun( 'dpctl deldp ' + self.dp )
496
        self.cmd( 'dpctl adddp ' + self.dp )
497
        if self.defaultMAC:
498
            self.cmd( 'ifconfig', self.intf, 'hw', 'ether', self.defaultMAC )
499
        ports = sorted( self.ports.values() )
500
        if len( ports ) != ports[ -1 ] + 1 - self.portBase:
501
            raise Exception( 'only contiguous, zero-indexed port ranges'
502
                            'supported: %s' % ports )
503
        intfs = [ self.intfs[ port ] for port in ports ]
504
        self.cmd( 'dpctl', 'addif', self.dp, ' '.join( intfs ) )
505
        # Run protocol daemon
506
        controller = controllers[ 0 ]
507
        self.cmd( 'ofprotocol ' + self.dp +
508
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
509
            ' --fail=closed ' + self.opts +
510
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
511
        self.execed = False
512

    
513
    def stop( self ):
514
        "Terminate kernel datapath."
515
        quietRun( 'dpctl deldp ' + self.dp )
516
        self.cmd( 'kill %ofprotocol' )
517
        self.deleteIntfs()
518

    
519

    
520
class OVSLegacyKernelSwitch( Switch ):
521
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
522
       Currently only works in the root namespace."""
523

    
524
    def __init__( self, name, dp=None, **kwargs ):
525
        """Init.
526
           name: name for switch
527
           dp: netlink id (0, 1, 2, ...)
528
           defaultMAC: default MAC as unsigned int; random value if None"""
529
        Switch.__init__( self, name, **kwargs )
530
        self.dp = 'dp%i' % dp
531
        self.intf = self.dp
532
        if self.inNamespace:
533
            error( "OVSKernelSwitch currently only works"
534
                " in the root namespace.\n" )
535
            exit( 1 )
536

    
537
    @staticmethod
538
    def setup():
539
        "Ensure any dependencies are loaded; if not, try to load them."
540
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
541
            moduleName='Open vSwitch (openvswitch.org)')
542
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
543

    
544
    def start( self, controllers ):
545
        "Start up kernel datapath."
546
        ofplog = '/tmp/' + self.name + '-ofp.log'
547
        quietRun( 'ifconfig lo up' )
548
        # Delete local datapath if it exists;
549
        # then create a new one monitoring the given interfaces
550
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
551
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
552
        mac_str = ''
553
        if self.defaultMAC:
554
            # ovs-openflowd expects a string of exactly 16 hex digits with no
555
            # colons.
556
            mac_str = ' --datapath-id=0000' + \
557
                      ''.join( self.defaultMAC.split( ':' ) ) + ' '
558
        ports = sorted( self.ports.values() )
559
        if len( ports ) != ports[ -1 ] + 1 - self.portBase:
560
            raise Exception( 'only contiguous, one-indexed port ranges '
561
                            'supported: %s' % self.intfs )
562
        intfs = [ self.intfs[ port ] for port in ports ]
563
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
564
        # Run protocol daemon
565
        controller = controllers[ 0 ]
566
        self.cmd( 'ovs-openflowd ' + self.dp +
567
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
568
            ' --fail=secure ' + self.opts + mac_str +
569
            ' 1>' + ofplog + ' 2>' + ofplog + '&' )
570
        self.execed = False
571

    
572
    def stop( self ):
573
        "Terminate kernel datapath."
574
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
575
        self.cmd( 'kill %ovs-openflowd' )
576
        self.deleteIntfs()
577

    
578

    
579
class OVSSwitch( Switch ):
580
    "Open vSwitch switch. Depends on ovs-vsctl."
581

    
582
    def __init__( self, name, dp=None, **kwargs ):
583
        """Init.
584
           name: name for switch
585
           defaultMAC: default MAC as unsigned int; random value if None"""
586
        Switch.__init__( self, name, **kwargs )
587
        self.dp = name
588

    
589
    @staticmethod
590
    def setup():
591
        "Make sure Open vSwitch is installed and working"
592
        pathCheck( 'ovs-vsctl', 
593
            moduleName='Open vSwitch (openvswitch.org)')
594
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
595
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
596
        if exitcode:
597
            error( out + err + 
598
                   'ovs-vsctl exited with code %d\n' % exitcode +
599
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
600
                   'Make sure that Open vSwitch is installed, '
601
                   'that ovsdb-server is running, and that\n'
602
                   '"ovs-vsctl show" works correctly.\n'
603
                   'You may wish to try "service openvswitch-switch start".\n' )
604
            exit( 1 )
605

    
606
    def start( self, controllers ):
607
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
608
        # Annoyingly, --if-exists option seems not to work
609
        self.cmd( 'ovs-vsctl del-br ', self.dp )
610
        self.cmd( 'ovs-vsctl add-br', self.dp )
611
        self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' )
612
        # Add ports
613
        ports = sorted( self.ports.values() )
614
        intfs = [ self.intfs[ port ] for port in ports ]
615
        # XXX: Ugly check - we should probably fix this!
616
        if ports and ( len( ports ) != ports[ -1 ] + 1 - self.portBase ):
617
            raise Exception( 'only contiguous, one-indexed port ranges '
618
                            'supported: %s' % self.intfs )
619
        for intf in intfs:
620
            self.cmd( 'ovs-vsctl add-port', self.dp, intf )
621
            self.cmd( 'ifconfig', intf, 'up' )
622
        # Add controllers
623
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) for c in controllers ] )
624
        self.cmd( 'ovs-vsctl set-controller', self.dp, clist )
625

    
626
    def stop( self ):
627
        "Terminate OVS switch."
628
        self.cmd( 'ovs-vsctl del-br', self.dp )
629

    
630
OVSKernelSwitch = OVSSwitch
631

    
632
class Controller( Node ):
633
    """A Controller is a Node that is running (or has execed?) an
634
       OpenFlow controller."""
635

    
636
    def __init__( self, name, inNamespace=False, command='controller',
637
                 cargs='-v ptcp:%d', cdir=None, defaultIP="127.0.0.1",
638
                 port=6633 ):
639
        self.command = command
640
        self.cargs = cargs
641
        self.cdir = cdir
642
        self.port = port
643
        Node.__init__( self, name, inNamespace=inNamespace,
644
            defaultIP=defaultIP )
645

    
646
    def start( self ):
647
        """Start <controller> <args> on controller.
648
           Log to /tmp/cN.log"""
649
        pathCheck( self.command )
650
        cout = '/tmp/' + self.name + '.log'
651
        if self.cdir is not None:
652
            self.cmd( 'cd ' + self.cdir )
653
        self.cmd( self.command + ' ' + self.cargs % self.port +
654
            ' 1>' + cout + ' 2>' + cout + '&' )
655
        self.execed = False
656

    
657
    def stop( self ):
658
        "Stop controller."
659
        self.cmd( 'kill %' + self.command )
660
        self.terminate()
661

    
662
    def IP( self, intf=None ):
663
        "Return IP address of the Controller"
664
        if self.intfs:
665
            ip = Node.IP( self, intf )
666
        else:
667
            ip = self.defaultIP
668
        return ip
669

    
670
class ControllerParams( object ):
671
    "Container for controller IP parameters."
672

    
673
    def __init__( self, ip, prefixLen ):
674
        """Init.
675
           ip: string, controller IP address
676
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
677
        self.ip = ip
678
        self.prefixLen = prefixLen
679

    
680

    
681
class NOX( Controller ):
682
    "Controller to run a NOX application."
683

    
684
    def __init__( self, name, noxArgs=None, **kwargs ):
685
        """Init.
686
           name: name to give controller
687
           noxArgs: list of args, or single arg, to pass to NOX"""
688
        if not noxArgs:
689
            noxArgs = [ 'packetdump' ]
690
        elif type( noxArgs ) != list:
691
            noxArgs = [ noxArgs ]
692

    
693
        if 'NOX_CORE_DIR' not in os.environ:
694
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
695
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
696

    
697
        Controller.__init__( self, name,
698
            command=noxCoreDir + '/nox_core',
699
            cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
700
                    ' '.join( noxArgs ),
701
            cdir=noxCoreDir, **kwargs )
702

    
703

    
704
class RemoteController( Controller ):
705
    "Controller running outside of Mininet's control."
706

    
707
    def __init__( self, name, defaultIP='127.0.0.1',
708
                 port=6633, **kwargs):
709
        """Init.
710
           name: name to give controller
711
           defaultIP: the IP address where the remote controller is
712
           listening
713
           port: the port where the remote controller is listening"""
714
        Controller.__init__( self, name, defaultIP=defaultIP, port=port,
715
            **kwargs )
716

    
717
    def start( self ):
718
        "Overridden to do nothing."
719
        return
720

    
721
    def stop( self ):
722
        "Overridden to do nothing."
723
        return