Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 8a622c3a

History | View | Annotate | Download (32.1 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
class Node( object ):
57
    """A virtual network node is simply a shell in a network namespace.
58
       We communicate with it using pipes."""
59

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

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

    
67
        # Make sure class actually works
68
        self.checkSetup()
69

    
70
        self.name = name
71
        self.inNamespace = inNamespace
72

    
73
        # Stash configuration parameters for future reference
74
        self.params = params
75

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

    
81
        # Start command interpreter shell
82
        self.shell = None
83
        self.startShell()
84

    
85
    # File descriptor to node mapping support
86
    # Class variables and methods
87

    
88
    inToNode = {}  # mapping of input fds to nodes
89
    outToNode = {}  # mapping of output fds to nodes
90

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

    
99
    # Automatic class setup support
100

    
101
    isSetup = False;
102

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

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

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

    
121
    # Command support via shell process in namespace
122

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

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

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

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

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

    
189
    def stop( self ):
190
        "Stop node."
191
        self.terminate()
192

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

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

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

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

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

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

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

    
283
    # Interface management, configuration, and routing
284

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

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

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

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

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

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

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

    
350
    # Routing support
351

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

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

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

    
373
    # Convenience and configuration methods
374

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

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

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

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

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

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

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

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

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

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

    
456
    # Other methods
457

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

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

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

    
470

    
471
class Host( Node ):
472
    "A host is simply a Node"
473
    pass
474

    
475

    
476
class CPULimitedHost( Host ):
477

    
478
    "CPU limited host"
479

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

    
489
    def cleanup( self ):
490
        "Clean up our cgroup"
491
        Host.cleanup( self )
492
        debug( '*** deleting cgroup', self.cgroup, '\n' )
493
        errFail( 'cgdelete -r ' + self.cgroup )
494

    
495
    def cgroupSet( self, param, value, resource='cpu' ):
496
        "Set a cgroup parameter and return its value"
497
        cmd = 'cgset -r %s.%s=%s /%s' % (
498
            resource, param, value, self.name )
499
        out = quietRun( cmd )
500
        nvalue = int( self.cgroupGet( param, resource ) )
501
        if nvalue != value:
502
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
503
                   % ( param, nvalue, value ) )
504
        return nvalue
505

    
506
    def cgroupGet( self, param, resource='cpu' ):
507
        cmd = 'cgget -r %s.%s /%s' % (
508
            resource, param, self.name )
509
        return quietRun( cmd ).split()[ -1 ]
510

    
511
    def chrt( self, prio=20 ):
512
        "Set RT scheduling priority"
513
        quietRun( 'chrt -p %s %s' % ( prio, self.pid ) )
514
        result = quietRun( 'chrt -p %s' % self.pid )
515
        firstline = result.split( '\n' )[ 0 ]
516
        lastword = firstline.split( ' ' )[ -1 ]
517
        if lastword != 'SCHED_RR':
518
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
519
        return lastword
520

    
521
    def rtInfo( self, f ):
522
        "Internal method: return parameters for RT bandwidth"
523
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
524
        # RT uses wall clock time for period and quota
525
        quota = int( self.period_us * f * numCores() )
526
        return pstr, qstr, self.period_us, quota
527

    
528
    def cfsInfo( self, f):
529
        "Internal method: return parameters for CFS bandwidth"
530
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
531
        # CFS uses wall clock time for period and CPU time for quota.
532
        quota = int( self.period_us * f * numCores() )
533
        period = self.period_us
534
        if f > 0 and quota < 1000:
535
            debug( '(cfsInfo: increasing default period) ' )
536
            quota = 1000
537
            period = int( quota / f / numCores() )
538
        return pstr, qstr, period, quota
539

    
540
    # BL comment:
541
    # This may not be the right API, 
542
    # since it doesn't specify CPU bandwidth in "absolute"
543
    # units the way link bandwidth is specified.
544
    # We should use MIPS or SPECINT or something instead.
545
    # Alternatively, we should change from system fraction
546
    # to CPU seconds per second, essentially assuming that
547
    # all CPUs are the same.
548

    
549
    def setCPUFrac( self, f=-1, sched=None):
550
        """Set overall CPU fraction for this host
551
           f: CPU bandwidth limit (fraction)
552
           sched: 'rt' or 'cfs'
553
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
554
        if not f:
555
            return
556
        if not sched:
557
            sched = self.sched
558
        if sched == 'rt':
559
            pstr, qstr, period, quota = self.rtInfo( f )
560
        elif sched == 'cfs':
561
            pstr, qstr, period, quota = self.cfsInfo( f )
562
        else:
563
            return
564
        if quota < 0:
565
            # Reset to unlimited
566
            quota = -1
567
        # Set cgroup's period and quota
568
        nperiod = self.cgroupSet( pstr, period )
569
        nquota = self.cgroupSet( qstr, quota )
570
        if sched == 'rt':
571
            # Set RT priority if necessary
572
            nchrt = self.chrt( prio=20 )
573
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
574

    
575
    def config( self, cpu=None, sched=None, **params ):
576
        """cpu: desired overall system CPU fraction
577
           params: parameters for Node.config()"""
578
        r = Node.config( self, **params )
579
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
580
        # that seems redundant
581
        self.setParam( r, 'setCPUFrac', cpu=cpu )
582
        return r
583

    
584
# Some important things to note:
585
#
586
# The "IP" address which we assign to the switch is not
587
# an "IP address for the switch" in the sense of IP routing.
588
# Rather, it is the IP address for a control interface if
589
# (and only if) you happen to be running the switch in a
590
# namespace, which is something we currently don't support
591
# for OVS!
592
#
593
# In general, you NEVER want to attempt to use Linux's
594
# network stack (i.e. ifconfig) to "assign" an IP address or
595
# MAC address to a switch data port. Instead, you "assign"
596
# the IP and MAC addresses in the controller by specifying
597
# packets that you want to receive or send. The "MAC" address
598
# reported by ifconfig for a switch data port is essentially
599
# meaningless.
600
#
601
# So, I'm trying changing the API to make it
602
# impossible to try this, since it will not work, since nobody
603
# ever makes separate control networks in Mininet, and indeed
604
# we don't even support running OVS in a namespace.
605
#
606
# Note if we have a separate control network, then it does
607
# make sense to have s1-eth0 as s1's control network interface,
608
# and we should set controlIntf accordingly.
609

    
610
class Switch( Node ):
611
    """A Switch is a Node that is running (or has execed?)
612
       an OpenFlow switch."""
613

    
614
    portBase = 1  # Switches start with port 1 in OpenFlow
615

    
616
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
617
        """dpid: dpid for switch (or None for default)
618
           opts: additional switch options
619
           listenPort: port to listen on for dpctl connections"""
620
        Node.__init__( self, name, **params )
621
        self.dpid = dpid if dpid else self.defaultDpid()
622
        self.opts = opts
623
        self.listenPort = listenPort
624
        if self.listenPort:
625
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
626
        self.controlIntf = None
627

    
628
    def defaultDpid( self ):
629
        "Derive dpid from switch name, s1 -> 1"
630
        dpid = int( re.findall( '\d+', self.name )[ 0 ] )
631
        dpid = hex( dpid )[ 2: ]
632
        dpid = '0' * ( 12 - len( dpid ) ) + dpid
633
        return dpid
634

    
635
    def defaultIntf( self ):
636
        "Return control interface, if any"
637
        if not self.inNamespace:
638
            error( "error: tried to access control interface of "
639
                   " switch %s in root namespace" % self.name )
640
        return self.controlIntf
641

    
642
    def sendCmd( self, *cmd, **kwargs ):
643
        """Send command to Node.
644
           cmd: string"""
645
        kwargs.setdefault( 'printPid', False )
646
        if not self.execed:
647
            return Node.sendCmd( self, *cmd, **kwargs )
648
        else:
649
            error( '*** Error: %s has execed and cannot accept commands' %
650
                     self.name )
651

    
652
class UserSwitch( Switch ):
653
    "User-space switch."
654

    
655
    def __init__( self, name, **kwargs ):
656
        """Init.
657
           name: name for the switch"""
658
        Switch.__init__( self, name, **kwargs )
659
        pathCheck( 'ofdatapath', 'ofprotocol',
660
            moduleName='the OpenFlow reference user switch (openflow.org)' )
661

    
662
    @staticmethod
663
    def setup():
664
        "Ensure any dependencies are loaded; if not, try to load them."
665
        if not os.path.exists( '/dev/net/tun' ):
666
            moduleDeps( add=TUN )
667

    
668
    def start( self, controllers ):
669
        """Start OpenFlow reference user datapath.
670
           Log to /tmp/sN-{ofd,ofp}.log.
671
           controllers: list of controller objects"""
672
        controller = controllers[ 0 ]
673
        ofdlog = '/tmp/' + self.name + '-ofd.log'
674
        ofplog = '/tmp/' + self.name + '-ofp.log'
675
        self.cmd( 'ifconfig lo up' )
676
        ports = sorted( self.ports.values() )
677
        intfs = [ str( self.intfs[ p ] ) for p in ports ]
678
        if self.inNamespace:
679
            intfs = intfs[ :-1 ]
680
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
681
            ' punix:/tmp/' + self.name + ' -d ' + self.dpid + 
682
            ' --no-slicing ' +
683
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
684
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
685
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
686
            ' --fail=closed ' + self.opts +
687
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
688

    
689
    def stop( self ):
690
        "Stop OpenFlow reference user datapath."
691
        self.cmd( 'kill %ofdatapath' )
692
        self.cmd( 'kill %ofprotocol' )
693
        self.deleteIntfs()
694

    
695

    
696
class OVSLegacyKernelSwitch( Switch ):
697
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
698
       Currently only works in the root namespace."""
699

    
700
    def __init__( self, name, dp=None, **kwargs ):
701
        """Init.
702
           name: name for switch
703
           dp: netlink id (0, 1, 2, ...)
704
           defaultMAC: default MAC as unsigned int; random value if None"""
705
        Switch.__init__( self, name, **kwargs )
706
        self.dp = 'dp%i' % dp
707
        self.intf = self.dp
708
        if self.inNamespace:
709
            error( "OVSKernelSwitch currently only works"
710
                " in the root namespace.\n" )
711
            exit( 1 )
712

    
713
    @staticmethod
714
    def setup():
715
        "Ensure any dependencies are loaded; if not, try to load them."
716
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
717
            moduleName='Open vSwitch (openvswitch.org)')
718
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
719

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

    
743
    def stop( self ):
744
        "Terminate kernel datapath."
745
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
746
        self.cmd( 'kill %ovs-openflowd' )
747
        self.deleteIntfs()
748

    
749

    
750
class OVSSwitch( Switch ):
751
    "Open vSwitch switch. Depends on ovs-vsctl."
752

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

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

    
800
    def stop( self ):
801
        "Terminate OVS switch."
802
        self.cmd( 'ovs-vsctl del-br', self.dp )
803

    
804
OVSKernelSwitch = OVSSwitch
805

    
806

    
807
class Controller( Node ):
808
    """A Controller is a Node that is running (or has execed?) an
809
       OpenFlow controller."""
810

    
811
    def __init__( self, name, inNamespace=False, command='controller',
812
                 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
813
                 port=6633, **params ):
814
        self.command = command
815
        self.cargs = cargs
816
        self.cdir = cdir
817
        self.ip = ip
818
        self.port = port
819
        Node.__init__( self, name, inNamespace=inNamespace,
820
            ip=ip, **params  )
821

    
822
    def start( self ):
823
        """Start <controller> <args> on controller.
824
           Log to /tmp/cN.log"""
825
        pathCheck( self.command )
826
        cout = '/tmp/' + self.name + '.log'
827
        if self.cdir is not None:
828
            self.cmd( 'cd ' + self.cdir )
829
        self.cmd( self.command + ' ' + self.cargs % self.port +
830
            ' 1>' + cout + ' 2>' + cout + '&' )
831
        self.execed = False
832

    
833
    def stop( self ):
834
        "Stop controller."
835
        self.cmd( 'kill %' + self.command )
836
        self.terminate()
837

    
838
    def IP( self, intf=None ):
839
        "Return IP address of the Controller"
840
        if self.intfs:
841
            ip = Node.IP( self, intf )
842
        else:
843
            ip = self.ip
844
        return ip
845

    
846

    
847
# BL: This really seems to be poorly specified,
848
# so it's going to go away!
849

    
850
class ControllerParams( object ):
851
    "Container for controller IP parameters."
852

    
853
    def __init__( self, ip, prefixLen ):
854
        """Init.
855
           ip: string, controller IP address
856
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
857
        self.ip = ip
858
        self.prefixLen = prefixLen
859

    
860

    
861
class NOX( Controller ):
862
    "Controller to run a NOX application."
863

    
864
    def __init__( self, name, noxArgs=[], **kwargs ):
865
        """Init.
866
           name: name to give controller
867
           noxArgs: list of args, or single arg, to pass to NOX"""
868
        if not noxArgs:
869
            noxArgs = [ 'packetdump' ]
870
        elif type( noxArgs ) != list:
871
            noxArgs = [ noxArgs ]
872

    
873
        if 'NOX_CORE_DIR' not in os.environ:
874
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
875
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
876

    
877
        Controller.__init__( self, name,
878
            command=noxCoreDir + '/nox_core',
879
            cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
880
                    ' '.join( noxArgs ),
881
            cdir=noxCoreDir, **kwargs )
882

    
883

    
884
class RemoteController( Controller ):
885
    "Controller running outside of Mininet's control."
886

    
887
    def __init__( self, name, defaultIP='127.0.0.1',
888
                 port=6633, **kwargs):
889
        """Init.
890
           name: name to give controller
891
           defaultIP: the IP address where the remote controller is
892
           listening
893
           port: the port where the remote controller is listening"""
894
        Controller.__init__( self, name, defaultIP=defaultIP, port=port,
895
            **kwargs )
896

    
897
    def start( self ):
898
        "Overridden to do nothing."
899
        return
900

    
901
    def stop( self ):
902
        "Overridden to do nothing."
903
        return