Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 216a4b7c

History | View | Annotate | Download (31.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, errFail, moveIntf, isShellBuiltin
52
from mininet.util import numCores
53
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
54
from mininet.link import Link
55

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

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

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

    
64
    def __init__( self, name, inNamespace=True, **params ):
65
        """name: name of node
66
           inNamespace: in network namespace?
67
           params: Node parameters (see config() for details)"""
68

    
69
        # Make sure class actually works
70
        self.checkSetup()
71

    
72
        self.name = name
73
        self.inNamespace = inNamespace
74

    
75
        # Stash configuration parameters for future reference
76
        self.params = params
77

    
78
        self.intfs = {}  # dict of port numbers to interfaces
79
        self.ports = {}  # dict of interfaces to port numbers
80
                         # replace with Port objects, eventually ?
81
        self.nameToIntf = {}  # dict of interface names to Intfs
82

    
83
        # Start command interpreter shell
84
        self.shell = None
85
        self.startShell()
86

    
87
    # File descriptor to node mapping support
88
    # Class variables and methods
89

    
90
    inToNode = {}  # mapping of input fds to nodes
91
    outToNode = {}  # mapping of output fds to nodes
92

    
93
    @classmethod
94
    def fdToNode( cls, fd ):
95
        """Return node corresponding to given file descriptor.
96
           fd: file descriptor
97
           returns: node"""
98
        node = cls.outToNode.get( fd )
99
        return node or cls.inToNode.get( fd )
100

    
101
    # Automatic class setup support
102

    
103
    isSetup = False;
104

    
105
    @classmethod
106
    def checkSetup( cls ):
107
        "Make sure our class and superclasses are set up"
108
        while cls and not getattr( cls, 'isSetup', True ):
109
            cls.setup()
110
            cls.isSetup = True
111
            # Make pylint happy
112
            cls = getattr( type( cls ), '__base__', None )
113

    
114
    @classmethod
115
    def setup( cls ):
116
        "Make sure our class dependencies are available"
117
        pathCheck( 'mnexec', 'ifconfig',  moduleName='Mininet')
118

    
119
    def cleanup( self ):
120
        "Help python collect its garbage."
121
        self.shell = None
122

    
123
    # Command support via shell process in namespace
124

    
125
    def startShell( self ):
126
        "Start a shell process for running commands"
127
        if self.shell:
128
            error( "%s: shell is already running" )
129
            return
130
        # mnexec: (c)lose descriptors, (d)etach from tty,
131
        # (p)rint pid, and run in (n)amespace 
132
        opts = '-cdp'
133
        if self.inNamespace:
134
            opts += 'n'
135
        # bash -m: enable job control
136
        cmd = [ 'mnexec', opts, 'bash', '-m' ]
137
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
138
            close_fds=True )
139
        self.stdin = self.shell.stdin
140
        self.stdout = self.shell.stdout
141
        self.pid = self.shell.pid
142
        self.pollOut = select.poll()
143
        self.pollOut.register( self.stdout )
144
        # Maintain mapping between file descriptors and nodes
145
        # This is useful for monitoring multiple nodes
146
        # using select.poll()
147
        self.outToNode[ self.stdout.fileno() ] = self
148
        self.inToNode[ self.stdin.fileno() ] = self
149
        self.execed = False
150
        self.lastCmd = None
151
        self.lastPid = None
152
        self.readbuf = ''
153
        self.waiting = False
154

    
155
    def read( self, bytes=1024 ):
156
        """Buffered read from node, non-blocking.
157
           bytes: maximum number of bytes to return"""
158
        count = len( self.readbuf )
159
        if count < bytes:
160
            data = os.read( self.stdout.fileno(), bytes - count )
161
            self.readbuf += data
162
        if bytes >= len( self.readbuf ):
163
            result = self.readbuf
164
            self.readbuf = ''
165
        else:
166
            result = self.readbuf[ :bytes ]
167
            self.readbuf = self.readbuf[ bytes: ]
168
        return result
169

    
170
    def readline( self ):
171
        """Buffered readline from node, non-blocking.
172
           returns: line (minus newline) or None"""
173
        self.readbuf += self.read( 1024 )
174
        if '\n' not in self.readbuf:
175
            return None
176
        pos = self.readbuf.find( '\n' )
177
        line = self.readbuf[ 0 : pos ]
178
        self.readbuf = self.readbuf[ pos + 1: ]
179
        return line
180

    
181
    def write( self, data ):
182
        """Write data to node.
183
           data: string"""
184
        os.write( self.stdin.fileno(), data )
185

    
186
    def terminate( self ):
187
        "Send kill signal to Node and clean up after it."
188
        os.kill( self.pid, signal.SIGKILL )
189
        self.cleanup()
190

    
191
    def stop( self ):
192
        "Stop node."
193
        self.terminate()
194

    
195
    def waitReadable( self, timeoutms=None ):
196
        """Wait until node's output is readable.
197
           timeoutms: timeout in ms or None to wait indefinitely."""
198
        if len( self.readbuf ) == 0:
199
            self.pollOut.poll( timeoutms )
200

    
201
    def sendCmd( self, *args, **kwargs ):
202
        """Send a command, followed by a command to echo a sentinel,
203
           and return without waiting for the command to complete.
204
           args: command and arguments, or string
205
           printPid: print command's PID?"""
206
        assert not self.waiting
207
        printPid = kwargs.get( 'printPid', True )
208
        if len( args ) > 0:
209
            cmd = args
210
        if not isinstance( cmd, str ):
211
            cmd = ' '.join( [ str( c ) for c in cmd ] )
212
        if not re.search( r'\w', cmd ):
213
            # Replace empty commands with something harmless
214
            cmd = 'echo -n'
215
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
216
            separator = '&'
217
            cmd = cmd[ :-1 ]
218
        else:
219
            separator = ';'
220
            if printPid and not isShellBuiltin( cmd ):
221
                cmd = 'mnexec -p ' + cmd
222
        self.write( cmd + separator + ' printf "\\177" \n' )
223
        self.lastCmd = cmd
224
        self.lastPid = None
225
        self.waiting = True
226

    
227
    def sendInt( self, sig=signal.SIGINT ):
228
        "Interrupt running command."
229
        if self.lastPid:
230
            try:
231
                os.kill( self.lastPid, sig )
232
            except OSError:
233
                pass
234

    
235
    def monitor( self, timeoutms=None ):
236
        """Monitor and return the output of a command.
237
           Set self.waiting to False if command has completed.
238
           timeoutms: timeout in ms or None to wait indefinitely."""
239
        self.waitReadable( timeoutms )
240
        data = self.read( 1024 )
241
        # Look for PID
242
        marker = chr( 1 ) + r'\d+\n'
243
        if chr( 1 ) in data:
244
            markers = re.findall( marker, data )
245
            if markers:
246
                self.lastPid = int( markers[ 0 ][ 1: ] )
247
                data = re.sub( marker, '', data )
248
        # Look for sentinel/EOF
249
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
250
            self.waiting = False
251
            data = data[ :-1 ]
252
        elif chr( 127 ) in data:
253
            self.waiting = False
254
            data = data.replace( chr( 127 ), '' )
255
        return data
256

    
257
    def waitOutput( self, verbose=False ):
258
        """Wait for a command to complete.
259
           Completion is signaled by a sentinel character, ASCII(127)
260
           appearing in the output stream.  Wait for the sentinel and return
261
           the output, including trailing newline.
262
           verbose: print output interactively"""
263
        log = info if verbose else debug
264
        output = ''
265
        while self.waiting:
266
            data = self.monitor()
267
            output += data
268
            log( data )
269
        return output
270

    
271
    def cmd( self, *args, **kwargs ):
272
        """Send a command, wait for output, and return it.
273
           cmd: string"""
274
        verbose = kwargs.get( 'verbose', False )
275
        log = info if verbose else debug
276
        log( '*** %s : %s\n' % ( self.name, args ) )
277
        self.sendCmd( *args, **kwargs )
278
        return self.waitOutput( verbose )
279

    
280
    def cmdPrint( self, *args):
281
        """Call cmd and printing its output
282
           cmd: string"""
283
        return self.cmd( *args, **{ 'verbose': True } )
284

    
285
    # Interface management, configuration, and routing
286

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

    
293
    def newPort( self ):
294
        "Return the next port number to allocate."
295
        if len( self.ports ) > 0:
296
            return max( self.ports.values() ) + 1
297
        return self.portBase
298

    
299
    def addIntf( self, intf, port=None ):
300
        """Add an interface.
301
           intf: interface
302
           port: port number (optional, typically OpenFlow port number)"""
303
        if port is None:
304
            port = self.newPort()
305
        self.intfs[ port ] = intf
306
        self.ports[ intf ] = port
307
        self.nameToIntf[ intf.name ] = intf
308
        debug( '\n' )
309
        debug( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
310
        if self.inNamespace:
311
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
312
            moveIntf( intf.name, self )
313

    
314
    def defaultIntf( self ):
315
        "Return interface for lowest port"
316
        ports = self.intfs.keys()
317
        if ports:
318
            return self.intfs[ min( ports ) ]
319

    
320
    def intf( self, intf='' ):
321
        """Return our interface object with given name,x
322
           or default intf if name is empty"""
323
        if not intf:
324
            return self.defaultIntf()
325
        elif type( intf) is str:
326
            return self.nameToIntf[ intf ]
327
        else:
328
            return intf
329

    
330
    def linksTo( self, node):
331
        "Return [ link1, link2...] for all links from self to node."
332
        # We could optimize this if it is important
333
        links = []
334
        for intf in self.intfs:
335
            link = intf.link
336
            nodes = ( link.intf1.node, link.intf2.node )
337
            if self in nodes and node in nodes:
338
                links.append( link )
339
        return links
340

    
341
    def deleteIntfs( self ):
342
        "Delete all of our interfaces."
343
        # In theory the interfaces should go away after we shut down.
344
        # However, this takes time, so we're better off removing them
345
        # explicitly so that we won't get errors if we run before they
346
        # have been removed by the kernel. Unfortunately this is very slow,
347
        # at least with Linux kernels before 2.6.33
348
        for intf in self.intfs.values():
349
            intf.delete()
350
            info( '.' )
351

    
352
    # Routing support
353

    
354
    def setARP( self, ip, mac ):
355
        """Add an ARP entry.
356
           ip: IP address as string
357
           mac: MAC address as string"""
358
        result = self.cmd( 'arp', '-s', ip, mac )
359
        return result
360

    
361
    def setHostRoute( self, ip, intf ):
362
        """Add route to host.
363
           ip: IP address as dotted decimal
364
           intf: string, interface name"""
365
        return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
366

    
367
    def setDefaultRoute( self, intf=None ):
368
        """Set the default route to go through intf.
369
           intf: string, interface name"""
370
        if not intf:
371
            intf = self.defaultIntf()
372
        self.cmd( 'ip route flush root 0/0' )
373
        return self.cmd( 'route add default %s' % intf )
374

    
375
    # Convenience and configuration methods
376

    
377
    def setMAC( self, mac, intf=''):
378
        """Set the MAC address for an interface.
379
           intf: intf or intf name
380
           mac: MAC address as string"""
381
        return self.intf( intf ).setMAC( mac )
382

    
383
    def setIP( self, ip, prefixLen=8, intf='' ):
384
        """Set the IP address for an interface.
385
           intf: interface name
386
           ip: IP address as a string
387
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
388
        # This should probably be rethought:
389
        ipSub = '%s/%s' % ( ip, prefixLen )
390
        return self.intf( intf ).setIP( ipSub )
391

    
392
    def IP( self, intf=None ):
393
        "Return IP address of a node or specific interface."
394
        return self.intf( intf ).IP()
395

    
396
    def MAC( self, intf=None ):
397
        "Return MAC address of a node or specific interface."
398
        return self.intf( intf ).MAC()
399

    
400
    def intfIsUp( self, intf=None ):
401
        "Check if an interface is up."
402
        return self.intf( intf ).isUp()
403

    
404
    # The reason why we configure things in this way is so
405
    # That the parameters can be listed and documented in
406
    # the config method.
407
    # Dealing with subclasses and superclasses is slightly
408
    # annoying, but at least the information is there!
409

    
410
    def setParam( self, results, method, **param ):
411
        """Internal method: configure a *single* parameter
412
           results: dict of results to update
413
           method: config method name
414
           param: arg=value (ignore if value=None)
415
           value may also be list or dict"""
416
        name, value = param.items()[ 0 ]
417
        f = getattr( self, method, None )
418
        if not f or value is None:
419
            return
420
        if type( value ) is list:
421
            result = f( *value )
422
        elif type( value ) is dict:
423
            result = f( **value )
424
        else:
425
            result = f( value )
426
        results[ name ] = result
427
        return result
428

    
429
    def config( self, mac=None, ip=None, ifconfig=None, 
430
                defaultRoute=None, **params):
431
        """Configure Node according to (optional) parameters:
432
           mac: MAC address for default interface
433
           ip: IP address for default interface
434
           ifconfig: arbitrary interface configuration
435
           Subclasses should override this method and call
436
           the parent class's config(**params)"""
437
        # If we were overriding this method, we would call
438
        # the superclass config method here as follows:
439
        # r = Parent.config( **params )
440
        r = {}
441
        self.setParam( r, 'setMAC', mac=mac )
442
        self.setParam( r, 'setIP', ip=ip )
443
        self.setParam( r, 'ifconfig', ifconfig=ifconfig )
444
        self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute )
445
        return r
446

    
447
    def configDefault( self, **moreParams ):
448
        "Configure with default parameters"
449
        self.params.update( moreParams )
450
        self.config( **self.params )
451

    
452
    # This is here for backward compatibility
453
    def linkTo( self, node, link=Link ):
454
        """(Deprecated) Link to another node
455
           replace with Link( node1, node2)"""
456
        return link( self, node )
457

    
458
    # Other methods
459

    
460
    def intfList( self ):
461
        "List of our interfaces sorted by port number"
462
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
463

    
464
    def intfNames( self ):
465
        "The names of our interfaces sorted by port number"
466
        return [ str( i ) for i in self.intfList() ]
467

    
468
    def __str__( self ):
469
        return '%s: IP=%s intfs=%s pid=%s' % (
470
            self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
471

    
472

    
473
class Host( Node ):
474
    "A host is simply a Node"
475
    pass
476

    
477

    
478
class CPULimitedHost( Host ):
479

    
480
    "CPU limited host"
481

    
482
    def __init__( self, *args, **kwargs ):
483
        Node.__init__( self, *args, **kwargs )
484
        # Create a cgroup and move shell into it
485
        cgroup = 'cpu,cpuacct:/' + self.name
486
        errFail( 'cgcreate -g ' + cgroup )
487
        errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) )
488
        self.period_us = kwargs.get( 'period_us', 10000 )
489
        self.sched = kwargs.get( 'sched', 'rt' )
490

    
491
    def cgroupSet( self, param, value, resource='cpu' ):
492
        "Set a cgroup parameter and return its value"
493
        cmd = 'cgset -r %s.%s=%s /%s' % (
494
            resource, param, value, self.name )
495
        return quietRun( cmd )
496

    
497
    def cgroupGet( self, param, resource='cpu' ):
498
        cmd = 'cgget -r %s.%s /%s' % (
499
            resource, param, self.name )
500
        return quietRun( cmd ).split()[ -1 ]
501

    
502
    def chrt( self, prio=20 ):
503
        "Set RT scheduling priority"
504
        quietRun( 'chrt -p %s %s' % ( prio, self.pid ) )
505
        result = quietRun( 'chrt -p %s' % self.pid )
506
        firstline = result.split( '\n' )[ 0 ]
507
        lastword = firstline.split( ' ' )[ -1 ]
508
        return lastword
509

    
510
    # BL comment:
511
    # This may not be the right API, 
512
    # since it doesn't specify CPU bandwidth in "absolute"
513
    # units the way link bandwidth is specified.
514
    # We should use MIPS or SPECINT or something instead.
515
    # Alternatively, we should change from system fraction
516
    # to CPU seconds per second, essentially assuming that
517
    # all CPUs are the same.
518
    
519
    def setCPUFrac( self, f=-1, sched=None):
520
        """Set overall CPU fraction for this host
521
           f: CPU bandwidth limit (fraction)
522
           sched: 'rt' or 'cfs'
523
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
524
        if not f:
525
            return
526
        if not sched:
527
            sched = self.sched
528
        period = self.period_us
529
        if sched == 'rt':
530
            pstr, qstr = 'rt_period_us', 'rt_runtime_us'
531
            # RT uses system time for period and quota
532
            quota = int( period * f * numCores() )
533
        elif sched == 'cfs':
534
            pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
535
            # CFS uses wall clock time for period and CPU time for quota.
536
            quota = int( self.period_us * f * numCores() )
537
            if f > 0 and quota < 1000:
538
                info( '*** setCPUFrac: quota too small - adjusting period\n' )
539
                quota = 1000
540
                period = int( quota / f / numCores() )
541
        else:
542
            return
543
        if quota < 0:
544
            # Reset to unlimited
545
            quota = -1
546
        # Set cgroup's period and quota
547
        self.cgroupSet( pstr, period )
548
        nquota = int ( self.cgroupGet( qstr ) )
549
        self.cgroupSet( qstr, quota )
550
        nperiod = int( self.cgroupGet( pstr ) )
551
        # Make sure it worked
552
        if nperiod != self.period_us:
553
            error( '*** error: period is %s rather than %s\n' % (
554
                    nperiod, self.period_us ) )
555
        if nquota != quota:
556
            error( '*** error: quota is %s rather than %s\n' % (
557
                    nquota, quota ) )
558
        if sched == 'rt':
559
            # Set RT priority if necessary
560
            nchrt = self.chrt( prio=20 )
561
            # Nake sure it worked
562
            if sched == 'SCHED_RR' not in nchrt:
563
                error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
564
            info( '( period', nperiod, 'quota', nquota, nchrt, ') ' )
565
        else:
566
            info( '( period', nperiod, 'quota', nquota, ') ' )
567

    
568
    def config( self, cpu=None, sched=None, **params ):
569
        """cpu: desired overall system CPU fraction
570
           params: parameters for Node.config()"""
571
        r = Node.config( self, **params )
572
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
573
        # that seems redundant
574
        self.setParam( r, 'setCPUFrac', cpu=cpu )
575
        return r
576

    
577
# Some important things to note:
578
#
579
# The "IP" address which we assign to the switch is not
580
# an "IP address for the switch" in the sense of IP routing.
581
# Rather, it is the IP address for a control interface if
582
# (and only if) you happen to be running the switch in a
583
# namespace, which is something we currently don't support
584
# for OVS!
585
#
586
# In general, you NEVER want to attempt to use Linux's
587
# network stack (i.e. ifconfig) to "assign" an IP address or
588
# MAC address to a switch data port. Instead, you "assign"
589
# the IP and MAC addresses in the controller by specifying
590
# packets that you want to receive or send. The "MAC" address
591
# reported by ifconfig for a switch data port is essentially
592
# meaningless.
593
#
594
# So, I'm tyring changing the API to make it
595
# impossible to try this, since it will not work, since nobody
596
# ever makes separate control networks in Mininet, and indeed
597
# we don't even support running OVS in a namespace.
598

    
599
class Switch( Node ):
600
    """A Switch is a Node that is running (or has execed?)
601
       an OpenFlow switch."""
602

    
603
    portBase = SWITCH_PORT_BASE  # 0 for OF < 1.0, 1 for OF >= 1.0
604

    
605
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
606
        """dpid: dpid for switch (or None for default)
607
           opts: additional switch options
608
           listenPort: port to listen on for dpctl connections"""
609
        Node.__init__( self, name, **params )
610
        self.dpid = dpid if dpid else self.defaultDpid()
611
        self.opts = opts
612
        self.listenPort = listenPort
613
        if self.listenPort:
614
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
615
        self.controlIntf = None
616

    
617
    def defaultDpid( self ):
618
        "Derive dpid from switch name, s1 -> 1"
619
        dpid = int( re.findall( '\d+', self.name )[ 0 ] )
620
        dpid = hex( dpid )[ 2: ]
621
        dpid = '0' * ( 12 - len( dpid ) ) + dpid
622
        return dpid
623

    
624
    def defaultIntf( self ):
625
        "Return control interface, if any"
626
        if not self.inNamespace:
627
            error( "error: tried to access control interface of "
628
                   " switch %s in root namespace" % self.name )
629
        return self.controlIntf
630

    
631
    def sendCmd( self, *cmd, **kwargs ):
632
        """Send command to Node.
633
           cmd: string"""
634
        kwargs.setdefault( 'printPid', False )
635
        if not self.execed:
636
            return Node.sendCmd( self, *cmd, **kwargs )
637
        else:
638
            error( '*** Error: %s has execed and cannot accept commands' %
639
                     self.name )
640

    
641
class UserSwitch( Switch ):
642
    "User-space switch."
643

    
644
    def __init__( self, name, **kwargs ):
645
        """Init.
646
           name: name for the switch"""
647
        Switch.__init__( self, name, **kwargs )
648
        pathCheck( 'ofdatapath', 'ofprotocol',
649
            moduleName='the OpenFlow reference user switch (openflow.org)' )
650

    
651
    @staticmethod
652
    def setup():
653
        "Ensure any dependencies are loaded; if not, try to load them."
654
        if not os.path.exists( '/dev/net/tun' ):
655
            moduleDeps( add=TUN )
656

    
657
    def start( self, controllers ):
658
        """Start OpenFlow reference user datapath.
659
           Log to /tmp/sN-{ofd,ofp}.log.
660
           controllers: list of controller objects"""
661
        controller = controllers[ 0 ]
662
        ofdlog = '/tmp/' + self.name + '-ofd.log'
663
        ofplog = '/tmp/' + self.name + '-ofp.log'
664
        self.cmd( 'ifconfig lo up' )
665
        ports = sorted( self.ports.values() )
666
        intfs = [ str( self.intfs[ p ] ) for p in ports ]
667
        if self.inNamespace:
668
            intfs = intfs[ :-1 ]
669
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
670
            ' punix:/tmp/' + self.name + ' -d ' + self.dpid + 
671
            ' --no-slicing ' +
672
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
673
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
674
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
675
            ' --fail=closed ' + self.opts +
676
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
677

    
678
    def stop( self ):
679
        "Stop OpenFlow reference user datapath."
680
        self.cmd( 'kill %ofdatapath' )
681
        self.cmd( 'kill %ofprotocol' )
682
        self.deleteIntfs()
683

    
684

    
685
class OVSLegacyKernelSwitch( Switch ):
686
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
687
       Currently only works in the root namespace."""
688

    
689
    def __init__( self, name, dp=None, **kwargs ):
690
        """Init.
691
           name: name for switch
692
           dp: netlink id (0, 1, 2, ...)
693
           defaultMAC: default MAC as unsigned int; random value if None"""
694
        Switch.__init__( self, name, **kwargs )
695
        self.dp = 'dp%i' % dp
696
        self.intf = self.dp
697
        if self.inNamespace:
698
            error( "OVSKernelSwitch currently only works"
699
                " in the root namespace.\n" )
700
            exit( 1 )
701

    
702
    @staticmethod
703
    def setup():
704
        "Ensure any dependencies are loaded; if not, try to load them."
705
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
706
            moduleName='Open vSwitch (openvswitch.org)')
707
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
708

    
709
    def start( self, controllers ):
710
        "Start up kernel datapath."
711
        ofplog = '/tmp/' + self.name + '-ofp.log'
712
        quietRun( 'ifconfig lo up' )
713
        # Delete local datapath if it exists;
714
        # then create a new one monitoring the given interfaces
715
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
716
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
717
        ports = sorted( self.ports.values() )
718
        if len( ports ) != ports[ -1 ] + 1 - self.portBase:
719
            raise Exception( 'only contiguous, one-indexed port ranges '
720
                            'supported: %s' % self.intfs )
721
        intfs = [ self.intfs[ port ] for port in ports ]
722
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
723
        # Run protocol daemon
724
        controller = controllers[ 0 ]
725
        self.cmd( 'ovs-openflowd ' + self.dp +
726
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
727
            ' --fail=secure ' + self.opts + 
728
            ' --datapath-id=' + self.dpid +
729
            ' 1>' + ofplog + ' 2>' + ofplog + '&' )
730
        self.execed = False
731

    
732
    def stop( self ):
733
        "Terminate kernel datapath."
734
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
735
        self.cmd( 'kill %ovs-openflowd' )
736
        self.deleteIntfs()
737

    
738

    
739
class OVSSwitch( Switch ):
740
    "Open vSwitch switch. Depends on ovs-vsctl."
741

    
742
    def __init__( self, name, **params ):
743
        """Init.
744
           name: name for switch
745
           defaultMAC: default MAC as unsigned int; random value if None"""
746
        Switch.__init__( self, name, **params )
747
        # self.dp is the text name for the datapath that
748
        # we use for ovs-vsctl. This is different from the
749
        # dpid, which is a 64-bit numerical value used by
750
        # the openflow protocol.
751
        self.dp = name
752
        
753
    @staticmethod
754
    def setup():
755
        "Make sure Open vSwitch is installed and working"
756
        pathCheck( 'ovs-vsctl', 
757
            moduleName='Open vSwitch (openvswitch.org)')
758
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
759
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
760
        if exitcode:
761
            error( out + err + 
762
                   'ovs-vsctl exited with code %d\n' % exitcode +
763
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
764
                   'Make sure that Open vSwitch is installed, '
765
                   'that ovsdb-server is running, and that\n'
766
                   '"ovs-vsctl show" works correctly.\n'
767
                   'You may wish to try "service openvswitch-switch start".\n' )
768
            exit( 1 )
769

    
770
    def start( self, controllers ):
771
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
772
        # Annoyingly, --if-exists option seems not to work
773
        self.cmd( 'ovs-vsctl del-br ', self.dp )
774
        self.cmd( 'ovs-vsctl add-br', self.dp )
775
        self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' )
776
        ports = sorted( self.ports.values() )
777
        intfs = [ self.intfs[ port ] for port in ports ]
778
        # XXX: Ugly check - we should probably fix this!
779
        if ports and ( len( ports ) != ports[ -1 ] + 1 - self.portBase ):
780
            raise Exception( 'only contiguous, one-indexed port ranges '
781
                            'supported: %s' % self.intfs )
782
        for intf in intfs:
783
            self.cmd( 'ovs-vsctl add-port', self.dp, intf )
784
            self.cmd( 'ifconfig', intf, 'up' )
785
        # Add controllers
786
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) for c in controllers ] )
787
        self.cmd( 'ovs-vsctl set-controller', self.dp, clist )
788

    
789
    def stop( self ):
790
        "Terminate OVS switch."
791
        self.cmd( 'ovs-vsctl del-br', self.dp )
792

    
793
OVSKernelSwitch = OVSSwitch
794

    
795

    
796
class Controller( Node ):
797
    """A Controller is a Node that is running (or has execed?) an
798
       OpenFlow controller."""
799

    
800
    def __init__( self, name, inNamespace=False, command='controller',
801
                 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
802
                 port=6633, **params ):
803
        self.command = command
804
        self.cargs = cargs
805
        self.cdir = cdir
806
        self.ip = ip
807
        self.port = port
808
        Node.__init__( self, name, inNamespace=inNamespace,
809
            ip=ip, **params  )
810

    
811
    def start( self ):
812
        """Start <controller> <args> on controller.
813
           Log to /tmp/cN.log"""
814
        pathCheck( self.command )
815
        cout = '/tmp/' + self.name + '.log'
816
        if self.cdir is not None:
817
            self.cmd( 'cd ' + self.cdir )
818
        self.cmd( self.command + ' ' + self.cargs % self.port +
819
            ' 1>' + cout + ' 2>' + cout + '&' )
820
        self.execed = False
821

    
822
    def stop( self ):
823
        "Stop controller."
824
        self.cmd( 'kill %' + self.command )
825
        self.terminate()
826

    
827
    def IP( self, intf=None ):
828
        "Return IP address of the Controller"
829
        if self.intfs:
830
            ip = Node.IP( self, intf )
831
        else:
832
            ip = self.ip
833
        return ip
834

    
835

    
836
# BL: This really seems to be poorly specified,
837
# so it's going to go away!
838

    
839
class ControllerParams( object ):
840
    "Container for controller IP parameters."
841

    
842
    def __init__( self, ip, prefixLen ):
843
        """Init.
844
           ip: string, controller IP address
845
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
846
        self.ip = ip
847
        self.prefixLen = prefixLen
848

    
849

    
850
class NOX( Controller ):
851
    "Controller to run a NOX application."
852

    
853
    def __init__( self, name, noxArgs=[], **kwargs ):
854
        """Init.
855
           name: name to give controller
856
           noxArgs: list of args, or single arg, to pass to NOX"""
857
        if not noxArgs:
858
            noxArgs = [ 'packetdump' ]
859
        elif type( noxArgs ) != list:
860
            noxArgs = [ noxArgs ]
861

    
862
        if 'NOX_CORE_DIR' not in os.environ:
863
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
864
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
865

    
866
        Controller.__init__( self, name,
867
            command=noxCoreDir + '/nox_core',
868
            cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
869
                    ' '.join( noxArgs ),
870
            cdir=noxCoreDir, **kwargs )
871

    
872

    
873
class RemoteController( Controller ):
874
    "Controller running outside of Mininet's control."
875

    
876
    def __init__( self, name, defaultIP='127.0.0.1',
877
                 port=6633, **kwargs):
878
        """Init.
879
           name: name to give controller
880
           defaultIP: the IP address where the remote controller is
881
           listening
882
           port: the port where the remote controller is listening"""
883
        Controller.__init__( self, name, defaultIP=defaultIP, port=port,
884
            **kwargs )
885

    
886
    def start( self ):
887
        "Overridden to do nothing."
888
        return
889

    
890
    def stop( self ):
891
        "Overridden to do nothing."
892
        return