Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 0dbfd3a6

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
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
17
    RT or CFS bandwidth limiting.
18

19
Switch: superclass for switch nodes.
20

21
UserSwitch: a switch using the user-space switch from the OpenFlow
22
    reference implementation.
23

24
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
25
    implementation.
26

27
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
28
    implementation (openvswitch.org).
29

30
Controller: superclass for OpenFlow controllers. The default controller
31
    is controller(8) from the reference implementation.
32

33
NOXController: a controller node using NOX (noxrepo.org).
34

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

39
Future enhancements:
40

41
- Possibly make Node, Switch and Controller more abstract so that
42
  they can be used for both local and remote nodes
43

44
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
45
"""
46

    
47
import os
48
import re
49
import signal
50
import select
51
from subprocess import Popen, PIPE, STDOUT
52

    
53
from mininet.log import info, error, debug
54
from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin
55
from mininet.util import numCores
56
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
57
from mininet.link import Link
58

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

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

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

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

    
73
        self.name = name
74
        self.inNamespace = inNamespace
75

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

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

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

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

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

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

    
102
    # Automatic class setup support
103

    
104
    isSetup = False;
105

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

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

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

    
124
    # Command support via shell process in namespace
125

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

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

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

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

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

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

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

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

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

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

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

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

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

    
286
    # Interface management, configuration, and routing
287

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

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

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

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

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

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

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

    
353
    # Routing support
354

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

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

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

    
376
    # Convenience and configuration methods
377

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

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

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

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

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

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

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

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

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

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

    
459
    # Other methods
460

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

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

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

    
473

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

    
478

    
479
class CPULimitedHost( Host ):
480

    
481
    "CPU limited host"
482

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

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

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

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

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

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

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

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

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

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

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

    
613
class Switch( Node ):
614
    """A Switch is a Node that is running (or has execed?)
615
       an OpenFlow switch."""
616

    
617
    portBase = 1  # Switches start with port 1 in OpenFlow
618

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

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

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

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

    
655
class UserSwitch( Switch ):
656
    "User-space switch."
657

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

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

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

    
692
    def stop( self ):
693
        "Stop OpenFlow reference user datapath."
694
        self.cmd( 'kill %ofdatapath' )
695
        self.cmd( 'kill %ofprotocol' )
696
        self.deleteIntfs()
697

    
698

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

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

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

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

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

    
752

    
753
class OVSSwitch( Switch ):
754
    "Open vSwitch switch. Depends on ovs-vsctl."
755

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

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

    
803
    def stop( self ):
804
        "Terminate OVS switch."
805
        self.cmd( 'ovs-vsctl del-br', self.dp )
806

    
807
OVSKernelSwitch = OVSSwitch
808

    
809

    
810
class Controller( Node ):
811
    """A Controller is a Node that is running (or has execed?) an
812
       OpenFlow controller."""
813

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

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

    
836
    def stop( self ):
837
        "Stop controller."
838
        self.cmd( 'kill %' + self.command )
839
        self.terminate()
840

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

    
849

    
850
# BL: This really seems to be poorly specified,
851
# so it's going to go away!
852

    
853
class ControllerParams( object ):
854
    "Container for controller IP parameters."
855

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

    
863

    
864
class NOX( Controller ):
865
    "Controller to run a NOX application."
866

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

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

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

    
886

    
887
class RemoteController( Controller ):
888
    "Controller running outside of Mininet's control."
889

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

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

    
904
    def stop( self ):
905
        "Overridden to do nothing."
906
        return