Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 149a1f56

History | View | Annotate | Download (35.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
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, warn, debug
54
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
55
                          numCores, retry, mountCgroups )
56
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
57
from mininet.link import Link, Intf, TCIntf
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
        # Make pylint happy
85
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
86
          self.lastPid, self.lastCmd, self.pollOut ) = (
87
            None, None, None, None, None, None, None, None )
88
        self.waiting = False
89
        self.readbuf = ''
90

    
91
        # Start command interpreter shell
92
        self.startShell()
93

    
94
    # File descriptor to node mapping support
95
    # Class variables and methods
96

    
97
    inToNode = {}  # mapping of input fds to nodes
98
    outToNode = {}  # mapping of output fds to nodes
99

    
100
    @classmethod
101
    def fdToNode( cls, fd ):
102
        """Return node corresponding to given file descriptor.
103
           fd: file descriptor
104
           returns: node"""
105
        node = cls.outToNode.get( fd )
106
        return node or cls.inToNode.get( fd )
107

    
108
    # Command support via shell process in namespace
109

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

    
140
    def cleanup( self ):
141
        "Help python collect its garbage."
142
        if not self.inNamespace:
143
            for intfName in self.intfNames():
144
                if self.name in intfName:
145
                    quietRun( 'ip link del ' + intfName )
146
        self.shell = None
147

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

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

    
174
    def write( self, data ):
175
        """Write data to node.
176
           data: string"""
177
        os.write( self.stdin.fileno(), data )
178

    
179
    def terminate( self ):
180
        "Send kill signal to Node and clean up after it."
181
        os.kill( self.pid, signal.SIGKILL )
182
        self.cleanup()
183

    
184
    def stop( self ):
185
        "Stop node."
186
        self.terminate()
187

    
188
    def waitReadable( self, timeoutms=None ):
189
        """Wait until node's output is readable.
190
           timeoutms: timeout in ms or None to wait indefinitely."""
191
        if len( self.readbuf ) == 0:
192
            self.pollOut.poll( timeoutms )
193

    
194
    def sendCmd( self, *args, **kwargs ):
195
        """Send a command, followed by a command to echo a sentinel,
196
           and return without waiting for the command to complete.
197
           args: command and arguments, or string
198
           printPid: print command's PID?"""
199
        assert not self.waiting
200
        printPid = kwargs.get( 'printPid', True )
201
        # Allow sendCmd( [ list ] )
202
        if len( args ) == 1 and type( args[ 0 ] ) is list:
203
            cmd = args[ 0 ]
204
        # Allow sendCmd( cmd, arg1, arg2... )
205
        elif len( args ) > 0:
206
            cmd = args
207
        # Convert to string
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
        self.lastCmd = cmd
214
        printPid = printPid and not isShellBuiltin( cmd )
215
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
216
            # print ^A{pid}\n{sentinel}
217
            cmd += ' printf "\\001%d\n\\177" $! \n'
218
        else:
219
            # print sentinel
220
            cmd += '; printf "\\177"'
221
            if printPid and not isShellBuiltin( cmd ):
222
                cmd = 'mnexec -p ' + cmd
223
        self.write( cmd + '\n' )
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
        else:
320
            warn( '*** defaultIntf: warning:', self.name,
321
                 'has no interfaces\n' )
322

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

    
333
    def connectionsTo( self, node):
334
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
335
        # We could optimize this if it is important
336
        connections = []
337
        for intf in self.intfList():
338
            link = intf.link
339
            if link:
340
                node1, node2 = link.intf1.node, link.intf2.node
341
                if node1 == self and node2 == node:
342
                    connections += [ ( intf, link.intf2 ) ]
343
                elif node1 == node and node2 == self:
344
                    connections += [ ( intf, link.intf1 ) ]
345
        return connections
346

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

    
358
    # Routing support
359

    
360
    def setARP( self, ip, mac ):
361
        """Add an ARP entry.
362
           ip: IP address as string
363
           mac: MAC address as string"""
364
        result = self.cmd( 'arp', '-s', ip, mac )
365
        return result
366

    
367
    def setHostRoute( self, ip, intf ):
368
        """Add route to host.
369
           ip: IP address as dotted decimal
370
           intf: string, interface name"""
371
        return self.cmd( 'route add -host', ip, 'dev', intf )
372

    
373
    def setDefaultRoute( self, intf=None ):
374
        """Set the default route to go through intf.
375
           intf: string, interface name"""
376
        if not intf:
377
            intf = self.defaultIntf()
378
        self.cmd( 'ip route flush root 0/0' )
379
        return self.cmd( 'route add default %s' % intf )
380

    
381
    # Convenience and configuration methods
382

    
383
    def setMAC( self, mac, intf=None ):
384
        """Set the MAC address for an interface.
385
           intf: intf or intf name
386
           mac: MAC address as string"""
387
        return self.intf( intf ).setMAC( mac )
388

    
389
    def setIP( self, ip, prefixLen=8, intf=None ):
390
        """Set the IP address for an interface.
391
           intf: interface name
392
           ip: IP address as a string
393
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
394
        # This should probably be rethought
395
        if '/' not in ip:
396
            ip = '%s/%s' % ( ip, prefixLen )
397
        return self.intf( intf ).setIP( ip )
398

    
399
    def IP( self, intf=None ):
400
        "Return IP address of a node or specific interface."
401
        return self.intf( intf ).IP()
402

    
403
    def MAC( self, intf=None ):
404
        "Return MAC address of a node or specific interface."
405
        return self.intf( intf ).IP()
406

    
407
    def intfIsUp( self, intf=None ):
408
        "Check if an interface is up."
409
        return self.intf( intf ).isUp()
410

    
411
    # The reason why we configure things in this way is so
412
    # That the parameters can be listed and documented in
413
    # the config method.
414
    # Dealing with subclasses and superclasses is slightly
415
    # annoying, but at least the information is there!
416

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

    
436
    def config( self, mac=None, ip=None,
437
                defaultRoute=None, lo='up', **_params ):
438
        """Configure Node according to (optional) parameters:
439
           mac: MAC address for default interface
440
           ip: IP address for default interface
441
           ifconfig: arbitrary interface configuration
442
           Subclasses should override this method and call
443
           the parent class's config(**params)"""
444
        # If we were overriding this method, we would call
445
        # the superclass config method here as follows:
446
        # r = Parent.config( **_params )
447
        r = {}
448
        self.setParam( r, 'setMAC', mac=mac )
449
        self.setParam( r, 'setIP', ip=ip )
450
        self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute )
451
        # This should be examined
452
        self.cmd( 'ifconfig lo ' + lo )
453
        return r
454

    
455
    def configDefault( self, **moreParams ):
456
        "Configure with default parameters"
457
        self.params.update( moreParams )
458
        self.config( **self.params )
459

    
460
    # This is here for backward compatibility
461
    def linkTo( self, node, link=Link ):
462
        """(Deprecated) Link to another node
463
           replace with Link( node1, node2)"""
464
        return link( self, node )
465

    
466
    # Other methods
467

    
468
    def intfList( self ):
469
        "List of our interfaces sorted by port number"
470
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
471

    
472
    def intfNames( self ):
473
        "The names of our interfaces sorted by port number"
474
        return [ str( i ) for i in self.intfList() ]
475

    
476
    def __repr__( self ):
477
        "More informative string representation"
478
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
479
                        for i in self.intfList() ] ) )
480
        return '<%s %s: %s pid=%s> ' % (
481
                self.__class__.__name__, self.name, intfs, self.pid )
482

    
483
    def __str__( self ):
484
        "Abbreviated string representation"
485
        return self.name
486

    
487
    # Automatic class setup support
488

    
489
    isSetup = False
490

    
491
    @classmethod
492
    def checkSetup( cls ):
493
        "Make sure our class and superclasses are set up"
494
        while cls and not getattr( cls, 'isSetup', True ):
495
            cls.setup()
496
            cls.isSetup = True
497
            # Make pylint happy
498
            cls = getattr( type( cls ), '__base__', None )
499

    
500
    @classmethod
501
    def setup( cls ):
502
        "Make sure our class dependencies are available"
503
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
504

    
505

    
506
class Host( Node ):
507
    "A host is simply a Node"
508
    pass
509

    
510

    
511
class CPULimitedHost( Host ):
512

    
513
    "CPU limited host"
514

    
515
    def __init__( self, name, sched='cfs', **kwargs ):
516
        Host.__init__( self, name, **kwargs )
517
        # Initialize class if necessary
518
        if not CPULimitedHost.inited:
519
            CPULimitedHost.init()
520
        # Create a cgroup and move shell into it
521
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
522
        errFail( 'cgcreate -g ' + self.cgroup )
523
        # We don't add ourselves to a cpuset because you must
524
        # specify the cpu and memory placement first
525
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
526
        # BL: Setting the correct period/quota is tricky, particularly
527
        # for RT. RT allows very small quotas, but the overhead
528
        # seems to be high. CFS has a mininimum quota of 1 ms, but
529
        # still does better with larger period values.
530
        self.period_us = kwargs.get( 'period_us', 100000 )
531
        self.sched = sched
532

    
533
    def cgroupSet( self, param, value, resource='cpu' ):
534
        "Set a cgroup parameter and return its value"
535
        cmd = 'cgset -r %s.%s=%s /%s' % (
536
            resource, param, value, self.name )
537
        quietRun( cmd )
538
        nvalue = int( self.cgroupGet( param, resource ) )
539
        if nvalue != value:
540
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
541
                   % ( param, nvalue, value ) )
542
        return nvalue
543

    
544
    def cgroupGet( self, param, resource='cpu' ):
545
        "Return value of cgroup parameter"
546
        cmd = 'cgget -r %s.%s /%s' % (
547
            resource, param, self.name )
548
        return int( quietRun( cmd ).split()[ -1 ] )
549

    
550
    def cgroupDel( self ):
551
        "Clean up our cgroup"
552
        # info( '*** deleting cgroup', self.cgroup, '\n' )
553
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
554
        return exitcode != 0
555

    
556
    def cleanup( self ):
557
        "Clean up our cgroup"
558
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
559

    
560
    def chrt( self, prio=20 ):
561
        "Set RT scheduling priority"
562
        quietRun( 'chrt -p %s %s' % ( prio, self.pid ) )
563
        result = quietRun( 'chrt -p %s' % self.pid )
564
        firstline = result.split( '\n' )[ 0 ]
565
        lastword = firstline.split( ' ' )[ -1 ]
566
        if lastword != 'SCHED_RR':
567
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
568
        return lastword
569

    
570
    def rtInfo( self, f ):
571
        "Internal method: return parameters for RT bandwidth"
572
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
573
        # RT uses wall clock time for period and quota
574
        quota = int( self.period_us * f * numCores() )
575
        return pstr, qstr, self.period_us, quota
576

    
577
    def cfsInfo( self, f):
578
        "Internal method: return parameters for CFS bandwidth"
579
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
580
        # CFS uses wall clock time for period and CPU time for quota.
581
        quota = int( self.period_us * f * numCores() )
582
        period = self.period_us
583
        if f > 0 and quota < 1000:
584
            debug( '(cfsInfo: increasing default period) ' )
585
            quota = 1000
586
            period = int( quota / f / numCores() )
587
        return pstr, qstr, period, quota
588

    
589
    # BL comment:
590
    # This may not be the right API,
591
    # since it doesn't specify CPU bandwidth in "absolute"
592
    # units the way link bandwidth is specified.
593
    # We should use MIPS or SPECINT or something instead.
594
    # Alternatively, we should change from system fraction
595
    # to CPU seconds per second, essentially assuming that
596
    # all CPUs are the same.
597

    
598
    def setCPUFrac( self, f=-1, sched=None):
599
        """Set overall CPU fraction for this host
600
           f: CPU bandwidth limit (fraction)
601
           sched: 'rt' or 'cfs'
602
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
603
        if not f:
604
            return
605
        if not sched:
606
            sched = self.sched
607
        if sched == 'rt':
608
            pstr, qstr, period, quota = self.rtInfo( f )
609
        elif sched == 'cfs':
610
            pstr, qstr, period, quota = self.cfsInfo( f )
611
        else:
612
            return
613
        if quota < 0:
614
            # Reset to unlimited
615
            quota = -1
616
        # Set cgroup's period and quota
617
        self.cgroupSet( pstr, period )
618
        self.cgroupSet( qstr, quota )
619
        if sched == 'rt':
620
            # Set RT priority if necessary
621
            self.chrt( prio=20 )
622
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
623

    
624
    def setCPUs( self, cores ):
625
        "Specify (real) cores that our cgroup can run on"
626
        if type( cores ) is list:
627
            cores = ','.join( [ str( c ) for c in cores ] )
628
        self.cgroupSet( resource='cpuset', param='cpus',
629
                        value= cores )
630
        # Memory placement is probably not relevant, but we
631
        # must specify it anyway
632
        self.cgroupSet( resource='cpuset', param='mems',
633
                        value= cores )
634
        # We have to do this here after we've specified
635
        # cpus and mems
636
        errFail( 'cgclassify -g cpuset:/%s %s' % (
637
                self.name, self.pid ) )
638

    
639
    def config( self, cpu=None, cores=None, **params ):
640
        """cpu: desired overall system CPU fraction
641
           cores: (real) core(s) this host can run on
642
           params: parameters for Node.config()"""
643
        r = Node.config( self, **params )
644
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
645
        # that seems redundant
646
        self.setParam( r, 'setCPUFrac', cpu=cpu )
647
        self.setParam( r, 'setCPUs', cores=cores )
648
        return r
649

    
650
    inited = False
651

    
652
    @classmethod
653
    def init( cls ):
654
        "Initialization for CPULimitedHost class"
655
        mountCgroups()
656
        cls.inited = True
657

    
658

    
659
# Some important things to note:
660
#
661
# The "IP" address which setIP() assigns to the switch is not
662
# an "IP address for the switch" in the sense of IP routing.
663
# Rather, it is the IP address for the control interface,
664
# on the control network, and it is only relevant to the
665
# controller. If you are running in the root namespace
666
# (which is the only way to run OVS at the moment), the
667
# control interface is the loopback interface, and you
668
# normally never want to change its IP address!
669
#
670
# In general, you NEVER want to attempt to use Linux's
671
# network stack (i.e. ifconfig) to "assign" an IP address or
672
# MAC address to a switch data port. Instead, you "assign"
673
# the IP and MAC addresses in the controller by specifying
674
# packets that you want to receive or send. The "MAC" address
675
# reported by ifconfig for a switch data port is essentially
676
# meaningless. It is important to understand this if you
677
# want to create a functional router using OpenFlow.
678

    
679
class Switch( Node ):
680
    """A Switch is a Node that is running (or has execed?)
681
       an OpenFlow switch."""
682

    
683
    portBase = 1  # Switches start with port 1 in OpenFlow
684

    
685
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
686
        """dpid: dpid for switch (or None for default)
687
           opts: additional switch options
688
           listenPort: port to listen on for dpctl connections"""
689
        Node.__init__( self, name, **params )
690
        self.dpid = dpid if dpid else self.defaultDpid()
691
        self.opts = opts
692
        self.listenPort = listenPort
693
        if not self.inNamespace:
694
            self.controlIntf = Intf( 'lo', self, port=0 )
695

    
696
    def defaultDpid( self ):
697
        "Derive dpid from switch name, s1 -> 1"
698
        dpid = int( re.findall( '\d+', self.name )[ 0 ] )
699
        dpid = hex( dpid )[ 2: ]
700
        dpid = '0' * ( 16 - len( dpid ) ) + dpid
701
        return dpid
702

    
703
    def defaultIntf( self ):
704
        "Return control interface"
705
        if self.controlIntf:
706
            return self.controlIntf
707
        else:
708
            return Node.defaultIntf( self )
709

    
710
    def sendCmd( self, *cmd, **kwargs ):
711
        """Send command to Node.
712
           cmd: string"""
713
        kwargs.setdefault( 'printPid', False )
714
        if not self.execed:
715
            return Node.sendCmd( self, *cmd, **kwargs )
716
        else:
717
            error( '*** Error: %s has execed and cannot accept commands' %
718
                     self.name )
719

    
720
    def __repr__( self ):
721
        "More informative string representation"
722
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
723
                        for i in self.intfList() ] ) )
724
        return '<%s %s: %s pid=%s> ' % (
725
                self.__class__.__name__, self.name, intfs, self.pid )
726

    
727
class UserSwitch( Switch ):
728
    "User-space switch."
729

    
730
    def __init__( self, name, **kwargs ):
731
        """Init.
732
           name: name for the switch"""
733
        Switch.__init__( self, name, **kwargs )
734
        pathCheck( 'ofdatapath', 'ofprotocol',
735
            moduleName='the OpenFlow reference user switch (openflow.org)' )
736
        if self.listenPort:
737
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
738

    
739
    @classmethod
740
    def setup( cls ):
741
        "Ensure any dependencies are loaded; if not, try to load them."
742
        if not os.path.exists( '/dev/net/tun' ):
743
            moduleDeps( add=TUN )
744

    
745
    def dpctl( self, *args ):
746
        "Run dpctl command"
747
        if not self.listenPort:
748
            return "can't run dpctl without passive listening port"
749
        return self.cmd( 'dpctl ' + ' '.join( args ) +
750
                         ' tcp:127.0.0.1:%i' % self.listenPort )
751

    
752
    def start( self, controllers ):
753
        """Start OpenFlow reference user datapath.
754
           Log to /tmp/sN-{ofd,ofp}.log.
755
           controllers: list of controller objects"""
756
        controller = controllers[ 0 ]
757
        ofdlog = '/tmp/' + self.name + '-ofd.log'
758
        ofplog = '/tmp/' + self.name + '-ofp.log'
759
        self.cmd( 'ifconfig lo up' )
760
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
761
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
762
            ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
763
            ' --no-slicing ' +
764
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
765
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
766
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
767
            ' --fail=closed ' + self.opts +
768
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
769

    
770
    def stop( self ):
771
        "Stop OpenFlow reference user datapath."
772
        self.cmd( 'kill %ofdatapath' )
773
        self.cmd( 'kill %ofprotocol' )
774
        self.deleteIntfs()
775

    
776

    
777
class OVSLegacyKernelSwitch( Switch ):
778
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
779
       Currently only works in the root namespace."""
780

    
781
    def __init__( self, name, dp=None, **kwargs ):
782
        """Init.
783
           name: name for switch
784
           dp: netlink id (0, 1, 2, ...)
785
           defaultMAC: default MAC as unsigned int; random value if None"""
786
        Switch.__init__( self, name, **kwargs )
787
        self.dp = dp if dp else self.name
788
        self.intf = self.dp
789
        if self.inNamespace:
790
            error( "OVSKernelSwitch currently only works"
791
                " in the root namespace.\n" )
792
            exit( 1 )
793

    
794
    @classmethod
795
    def setup( cls ):
796
        "Ensure any dependencies are loaded; if not, try to load them."
797
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
798
            moduleName='Open vSwitch (openvswitch.org)')
799
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
800

    
801
    def start( self, controllers ):
802
        "Start up kernel datapath."
803
        ofplog = '/tmp/' + self.name + '-ofp.log'
804
        quietRun( 'ifconfig lo up' )
805
        # Delete local datapath if it exists;
806
        # then create a new one monitoring the given interfaces
807
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
808
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
809
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
810
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
811
        # Run protocol daemon
812
        controller = controllers[ 0 ]
813
        self.cmd( 'ovs-openflowd ' + self.dp +
814
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
815
            ' --fail=secure ' + self.opts +
816
            ' --datapath-id=' + self.dpid +
817
            ' 1>' + ofplog + ' 2>' + ofplog + '&' )
818
        self.execed = False
819

    
820
    def stop( self ):
821
        "Terminate kernel datapath."
822
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
823
        self.cmd( 'kill %ovs-openflowd' )
824
        self.deleteIntfs()
825

    
826

    
827
class OVSSwitch( Switch ):
828
    "Open vSwitch switch. Depends on ovs-vsctl."
829

    
830
    def __init__( self, name, **params ):
831
        """Init.
832
           name: name for switch
833
           defaultMAC: default MAC as unsigned int; random value if None"""
834
        Switch.__init__( self, name, **params )
835

    
836
    @classmethod
837
    def setup( cls ):
838
        "Make sure Open vSwitch is installed and working"
839
        pathCheck( 'ovs-vsctl',
840
            moduleName='Open vSwitch (openvswitch.org)')
841
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
842
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
843
        if exitcode:
844
            error( out + err +
845
                   'ovs-vsctl exited with code %d\n' % exitcode +
846
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
847
                   'Make sure that Open vSwitch is installed, '
848
                   'that ovsdb-server is running, and that\n'
849
                   '"ovs-vsctl show" works correctly.\n'
850
                   'You may wish to try '
851
                   '"service openvswitch-switch start".\n' )
852
            exit( 1 )
853

    
854
    def dpctl( self, *args ):
855
        "Run ovs-dpctl command"
856
        return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
857

    
858
    @staticmethod
859
    def TCReapply( intf ):
860
        """Unfortunately OVS and Mininet are fighting
861
           over tc queuing disciplines. As a quick hack/
862
           workaround, we clear OVS's and reapply our own."""
863
        if type( intf ) is TCIntf:
864
            intf.config( **intf.params )
865

    
866
    def attach( self, intf ):
867
        "Connect a data port"
868
        self.cmd( 'ovs-vsctl add-port', self, intf )
869
        self.cmd( 'ifconfig', intf, 'up' )
870
        self.TCReapply( intf )
871

    
872
    def detach( self, intf ):
873
        "Disconnect a data port"
874
        self.cmd( 'ovs-vsctl del-port', self, intf )
875

    
876
    def start( self, controllers ):
877
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
878
        if self.inNamespace:
879
            raise Exception(
880
                'OVS kernel switch does not work in a namespace' )
881
        # We should probably call config instead, but this
882
        # requires some rethinking...
883
        self.cmd( 'ifconfig lo up' )
884
        # Annoyingly, --if-exists option seems not to work
885
        self.cmd( 'ovs-vsctl del-br', self )
886
        self.cmd( 'ovs-vsctl add-br', self )
887
        self.cmd( 'ovs-vsctl set-fail-mode', self, 'secure' )
888
        for intf in self.intfList():
889
            if not intf.IP():
890
                self.attach( intf )
891
        # Add controllers
892
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
893
                            for c in controllers ] )
894
        self.cmd( 'ovs-vsctl set-controller', self, clist )
895

    
896
    def stop( self ):
897
        "Terminate OVS switch."
898
        self.cmd( 'ovs-vsctl del-br', self )
899
        self.deleteIntfs()
900

    
901
OVSKernelSwitch = OVSSwitch
902

    
903

    
904
class Controller( Node ):
905
    """A Controller is a Node that is running (or has execed?) an
906
       OpenFlow controller."""
907

    
908
    def __init__( self, name, inNamespace=False, command='controller',
909
                 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
910
                 port=6633, **params ):
911
        self.command = command
912
        self.cargs = cargs
913
        self.cdir = cdir
914
        self.ip = ip
915
        self.port = port
916
        Node.__init__( self, name, inNamespace=inNamespace,
917
            ip=ip, **params  )
918

    
919
    def start( self ):
920
        """Start <controller> <args> on controller.
921
           Log to /tmp/cN.log"""
922
        pathCheck( self.command )
923
        cout = '/tmp/' + self.name + '.log'
924
        if self.cdir is not None:
925
            self.cmd( 'cd ' + self.cdir )
926
        self.cmd( self.command + ' ' + self.cargs % self.port +
927
            ' 1>' + cout + ' 2>' + cout + '&' )
928
        self.execed = False
929

    
930
    def stop( self ):
931
        "Stop controller."
932
        self.cmd( 'kill %' + self.command )
933
        self.terminate()
934

    
935
    def IP( self, intf=None ):
936
        "Return IP address of the Controller"
937
        if self.intfs:
938
            ip = Node.IP( self, intf )
939
        else:
940
            ip = self.ip
941
        return ip
942

    
943
    def __repr__( self ):
944
        "More informative string representation"
945
        return '<%s %s: %s:%s pid=%s> ' % (
946
                self.__class__.__name__, self.name,
947
                self.IP(), self.port, self.pid )
948

    
949

    
950
class OVSController( Controller ):
951
    "Open vSwitch controller"
952
    def __init__( self, name, command='ovs-controller', **kwargs ):
953
        Controller.__init__( self, name, command=command, **kwargs )
954

    
955

    
956
class NOX( Controller ):
957
    "Controller to run a NOX application."
958

    
959
    def __init__( self, name, *noxArgs, **kwargs ):
960
        """Init.
961
           name: name to give controller
962
           noxArgs: arguments (strings) to pass to NOX"""
963
        if not noxArgs:
964
            warn( 'warning: no NOX modules specified; '
965
                  'running packetdump only\n' )
966
            noxArgs = [ 'packetdump' ]
967
        elif type( noxArgs ) not in ( list, tuple ):
968
            noxArgs = [ noxArgs ]
969

    
970
        if 'NOX_CORE_DIR' not in os.environ:
971
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
972
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
973

    
974
        Controller.__init__( self, name,
975
            command=noxCoreDir + '/nox_core',
976
            cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
977
                    ' '.join( noxArgs ),
978
            cdir=noxCoreDir, **kwargs )
979

    
980

    
981
class RemoteController( Controller ):
982
    "Controller running outside of Mininet's control."
983

    
984
    def __init__( self, name, defaultIP='127.0.0.1',
985
                 port=6633, **kwargs):
986
        """Init.
987
           name: name to give controller
988
           defaultIP: the IP address where the remote controller is
989
           listening
990
           port: the port where the remote controller is listening"""
991
        Controller.__init__( self, name, defaultIP=defaultIP, port=port,
992
            **kwargs )
993

    
994
    def start( self ):
995
        "Overridden to do nothing."
996
        return
997

    
998
    def stop( self ):
999
        "Overridden to do nothing."
1000
        return