Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 0d94548a

History | View | Annotate | Download (37.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
    def popen( self, *args, **kwargs ):
286
        """Return a Popen() object in our namespace
287
           args: Popen() args, single list, or string
288
           kwargs: Popen() keyword args"""
289
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
290
                     'mncmd':
291
                     [ 'mnexec', '-a', str( self.pid ) ] }
292
        defaults.update( kwargs )
293
        if len( args ) == 1:
294
            if type( args[ 0 ] ) is list:
295
                # popen([cmd, arg1, arg2...])
296
                cmd = args[ 0 ]
297
            elif type( args[ 0 ] ) is str:
298
                # popen("cmd arg1 arg2...")
299
                cmd = args[ 0 ].split()
300
        elif len( args ) > 0:
301
            # popen( cmd, arg1, arg2... )
302
            cmd = args
303
        # Attach to our namespace  using mnexec -a
304
        mncmd = defaults[ 'mncmd' ]
305
        del defaults[ 'mncmd' ]
306
        cmd = mncmd + cmd
307
        return Popen( cmd, **defaults )
308

    
309
    def pexec( self, *args, **kwargs ):
310
        """Execute a command using popen
311
           returns: out, err, exitcode"""
312
        popen = self.popen( *args, **kwargs)
313
        out, err = popen.communicate()
314
        exitcode = popen.wait()
315
        return out, err, exitcode
316

    
317
    # Interface management, configuration, and routing
318

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

    
325
    def newPort( self ):
326
        "Return the next port number to allocate."
327
        if len( self.ports ) > 0:
328
            return max( self.ports.values() ) + 1
329
        return self.portBase
330

    
331
    def addIntf( self, intf, port=None ):
332
        """Add an interface.
333
           intf: interface
334
           port: port number (optional, typically OpenFlow port number)"""
335
        if port is None:
336
            port = self.newPort()
337
        self.intfs[ port ] = intf
338
        self.ports[ intf ] = port
339
        self.nameToIntf[ intf.name ] = intf
340
        debug( '\n' )
341
        debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
342
        if self.inNamespace:
343
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
344
            moveIntf( intf.name, self )
345

    
346
    def defaultIntf( self ):
347
        "Return interface for lowest port"
348
        ports = self.intfs.keys()
349
        if ports:
350
            return self.intfs[ min( ports ) ]
351
        else:
352
            warn( '*** defaultIntf: warning:', self.name,
353
                 'has no interfaces\n' )
354

    
355
    def intf( self, intf='' ):
356
        """Return our interface object with given name,
357
           or default intf if name is empty"""
358
        if not intf:
359
            return self.defaultIntf()
360
        elif type( intf) is str:
361
            return self.nameToIntf[ intf ]
362
        else:
363
            return None
364

    
365
    def connectionsTo( self, node):
366
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
367
        # We could optimize this if it is important
368
        connections = []
369
        for intf in self.intfList():
370
            link = intf.link
371
            if link:
372
                node1, node2 = link.intf1.node, link.intf2.node
373
                if node1 == self and node2 == node:
374
                    connections += [ ( intf, link.intf2 ) ]
375
                elif node1 == node and node2 == self:
376
                    connections += [ ( intf, link.intf1 ) ]
377
        return connections
378

    
379
    def deleteIntfs( self ):
380
        "Delete all of our interfaces."
381
        # In theory the interfaces should go away after we shut down.
382
        # However, this takes time, so we're better off removing them
383
        # explicitly so that we won't get errors if we run before they
384
        # have been removed by the kernel. Unfortunately this is very slow,
385
        # at least with Linux kernels before 2.6.33
386
        for intf in self.intfs.values():
387
            intf.delete()
388
            info( '.' )
389

    
390
    # Routing support
391

    
392
    def setARP( self, ip, mac ):
393
        """Add an ARP entry.
394
           ip: IP address as string
395
           mac: MAC address as string"""
396
        result = self.cmd( 'arp', '-s', ip, mac )
397
        return result
398

    
399
    def setHostRoute( self, ip, intf ):
400
        """Add route to host.
401
           ip: IP address as dotted decimal
402
           intf: string, interface name"""
403
        return self.cmd( 'route add -host', ip, 'dev', intf )
404

    
405
    def setDefaultRoute( self, intf=None ):
406
        """Set the default route to go through intf.
407
           intf: string, interface name"""
408
        if not intf:
409
            intf = self.defaultIntf()
410
        self.cmd( 'ip route flush root 0/0' )
411
        return self.cmd( 'route add default %s' % intf )
412

    
413
    # Convenience and configuration methods
414

    
415
    def setMAC( self, mac, intf=None ):
416
        """Set the MAC address for an interface.
417
           intf: intf or intf name
418
           mac: MAC address as string"""
419
        return self.intf( intf ).setMAC( mac )
420

    
421
    def setIP( self, ip, prefixLen=8, intf=None ):
422
        """Set the IP address for an interface.
423
           intf: interface name
424
           ip: IP address as a string
425
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
426
        # This should probably be rethought
427
        if '/' not in ip:
428
            ip = '%s/%s' % ( ip, prefixLen )
429
        return self.intf( intf ).setIP( ip )
430

    
431
    def IP( self, intf=None ):
432
        "Return IP address of a node or specific interface."
433
        return self.intf( intf ).IP()
434

    
435
    def MAC( self, intf=None ):
436
        "Return MAC address of a node or specific interface."
437
        return self.intf( intf ).IP()
438

    
439
    def intfIsUp( self, intf=None ):
440
        "Check if an interface is up."
441
        return self.intf( intf ).isUp()
442

    
443
    # The reason why we configure things in this way is so
444
    # That the parameters can be listed and documented in
445
    # the config method.
446
    # Dealing with subclasses and superclasses is slightly
447
    # annoying, but at least the information is there!
448

    
449
    def setParam( self, results, method, **param ):
450
        """Internal method: configure a *single* parameter
451
           results: dict of results to update
452
           method: config method name
453
           param: arg=value (ignore if value=None)
454
           value may also be list or dict"""
455
        name, value = param.items()[ 0 ]
456
        f = getattr( self, method, None )
457
        if not f or value is None:
458
            return
459
        if type( value ) is list:
460
            result = f( *value )
461
        elif type( value ) is dict:
462
            result = f( **value )
463
        else:
464
            result = f( value )
465
        results[ name ] = result
466
        return result
467

    
468
    def config( self, mac=None, ip=None,
469
                defaultRoute=None, lo='up', **_params ):
470
        """Configure Node according to (optional) parameters:
471
           mac: MAC address for default interface
472
           ip: IP address for default interface
473
           ifconfig: arbitrary interface configuration
474
           Subclasses should override this method and call
475
           the parent class's config(**params)"""
476
        # If we were overriding this method, we would call
477
        # the superclass config method here as follows:
478
        # r = Parent.config( **_params )
479
        r = {}
480
        self.setParam( r, 'setMAC', mac=mac )
481
        self.setParam( r, 'setIP', ip=ip )
482
        self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute )
483
        # This should be examined
484
        self.cmd( 'ifconfig lo ' + lo )
485
        return r
486

    
487
    def configDefault( self, **moreParams ):
488
        "Configure with default parameters"
489
        self.params.update( moreParams )
490
        self.config( **self.params )
491

    
492
    # This is here for backward compatibility
493
    def linkTo( self, node, link=Link ):
494
        """(Deprecated) Link to another node
495
           replace with Link( node1, node2)"""
496
        return link( self, node )
497

    
498
    # Other methods
499

    
500
    def intfList( self ):
501
        "List of our interfaces sorted by port number"
502
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
503

    
504
    def intfNames( self ):
505
        "The names of our interfaces sorted by port number"
506
        return [ str( i ) for i in self.intfList() ]
507

    
508
    def __repr__( self ):
509
        "More informative string representation"
510
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
511
                        for i in self.intfList() ] ) )
512
        return '<%s %s: %s pid=%s> ' % (
513
                self.__class__.__name__, self.name, intfs, self.pid )
514

    
515
    def __str__( self ):
516
        "Abbreviated string representation"
517
        return self.name
518

    
519
    # Automatic class setup support
520

    
521
    isSetup = False
522

    
523
    @classmethod
524
    def checkSetup( cls ):
525
        "Make sure our class and superclasses are set up"
526
        while cls and not getattr( cls, 'isSetup', True ):
527
            cls.setup()
528
            cls.isSetup = True
529
            # Make pylint happy
530
            cls = getattr( type( cls ), '__base__', None )
531

    
532
    @classmethod
533
    def setup( cls ):
534
        "Make sure our class dependencies are available"
535
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
536

    
537

    
538
class Host( Node ):
539
    "A host is simply a Node"
540
    pass
541

    
542

    
543
class CPULimitedHost( Host ):
544

    
545
    "CPU limited host"
546

    
547
    def __init__( self, name, sched='cfs', **kwargs ):
548
        Host.__init__( self, name, **kwargs )
549
        # Initialize class if necessary
550
        if not CPULimitedHost.inited:
551
            CPULimitedHost.init()
552
        # Create a cgroup and move shell into it
553
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
554
        errFail( 'cgcreate -g ' + self.cgroup )
555
        # We don't add ourselves to a cpuset because you must
556
        # specify the cpu and memory placement first
557
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
558
        # BL: Setting the correct period/quota is tricky, particularly
559
        # for RT. RT allows very small quotas, but the overhead
560
        # seems to be high. CFS has a mininimum quota of 1 ms, but
561
        # still does better with larger period values.
562
        self.period_us = kwargs.get( 'period_us', 100000 )
563
        self.sched = sched
564
        self.rtprio = 20
565

    
566
    def cgroupSet( self, param, value, resource='cpu' ):
567
        "Set a cgroup parameter and return its value"
568
        cmd = 'cgset -r %s.%s=%s /%s' % (
569
            resource, param, value, self.name )
570
        quietRun( cmd )
571
        nvalue = int( self.cgroupGet( param, resource ) )
572
        if nvalue != value:
573
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
574
                   % ( param, nvalue, value ) )
575
        return nvalue
576

    
577
    def cgroupGet( self, param, resource='cpu' ):
578
        "Return value of cgroup parameter"
579
        cmd = 'cgget -r %s.%s /%s' % (
580
            resource, param, self.name )
581
        return int( quietRun( cmd ).split()[ -1 ] )
582

    
583
    def cgroupDel( self ):
584
        "Clean up our cgroup"
585
        # info( '*** deleting cgroup', self.cgroup, '\n' )
586
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
587
        return exitcode != 0
588

    
589
    def popen( self, *args, **kwargs ):
590
        """Return a Popen() object in node's namespace
591
           args: Popen() args, single list, or string
592
           kwargs: Popen() keyword args"""
593
        # Tell mnexec to execute command in our cgroup
594
        mncmd = [ 'mnexec', '-a', str( self.pid ),
595
                  '-g', self.name ]
596
        if self.sched == 'rt':
597
            mncmd += [ '-r', str( self.rtprio ) ]
598
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
599

    
600
    def cleanup( self ):
601
        "Clean up our cgroup"
602
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
603

    
604
    def chrt( self ):
605
        "Set RT scheduling priority"
606
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
607
        result = quietRun( 'chrt -p %s' % self.pid )
608
        firstline = result.split( '\n' )[ 0 ]
609
        lastword = firstline.split( ' ' )[ -1 ]
610
        if lastword != 'SCHED_RR':
611
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
612
        return lastword
613

    
614
    def rtInfo( self, f ):
615
        "Internal method: return parameters for RT bandwidth"
616
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
617
        # RT uses wall clock time for period and quota
618
        quota = int( self.period_us * f * numCores() )
619
        return pstr, qstr, self.period_us, quota
620

    
621
    def cfsInfo( self, f):
622
        "Internal method: return parameters for CFS bandwidth"
623
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
624
        # CFS uses wall clock time for period and CPU time for quota.
625
        quota = int( self.period_us * f * numCores() )
626
        period = self.period_us
627
        if f > 0 and quota < 1000:
628
            debug( '(cfsInfo: increasing default period) ' )
629
            quota = 1000
630
            period = int( quota / f / numCores() )
631
        return pstr, qstr, period, quota
632

    
633
    # BL comment:
634
    # This may not be the right API,
635
    # since it doesn't specify CPU bandwidth in "absolute"
636
    # units the way link bandwidth is specified.
637
    # We should use MIPS or SPECINT or something instead.
638
    # Alternatively, we should change from system fraction
639
    # to CPU seconds per second, essentially assuming that
640
    # all CPUs are the same.
641

    
642
    def setCPUFrac( self, f=-1, sched=None):
643
        """Set overall CPU fraction for this host
644
           f: CPU bandwidth limit (fraction)
645
           sched: 'rt' or 'cfs'
646
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
647
        if not f:
648
            return
649
        if not sched:
650
            sched = self.sched
651
        if sched == 'rt':
652
            pstr, qstr, period, quota = self.rtInfo( f )
653
        elif sched == 'cfs':
654
            pstr, qstr, period, quota = self.cfsInfo( f )
655
        else:
656
            return
657
        if quota < 0:
658
            # Reset to unlimited
659
            quota = -1
660
        # Set cgroup's period and quota
661
        self.cgroupSet( pstr, period )
662
        self.cgroupSet( qstr, quota )
663
        if sched == 'rt':
664
            # Set RT priority if necessary
665
            self.chrt()
666
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
667

    
668
    def setCPUs( self, cores, mems=0 ):
669
        "Specify (real) cores that our cgroup can run on"
670
        if type( cores ) is list:
671
            cores = ','.join( [ str( c ) for c in cores ] )
672
        self.cgroupSet( resource='cpuset', param='cpus',
673
                        value=cores )
674
        # Memory placement is probably not relevant, but we
675
        # must specify it anyway
676
        self.cgroupSet( resource='cpuset', param='mems',
677
                        value=mems)
678
        # We have to do this here after we've specified
679
        # cpus and mems
680
        errFail( 'cgclassify -g cpuset:/%s %s' % (
681
                self.name, self.pid ) )
682

    
683
    def config( self, cpu=None, cores=None, **params ):
684
        """cpu: desired overall system CPU fraction
685
           cores: (real) core(s) this host can run on
686
           params: parameters for Node.config()"""
687
        r = Node.config( self, **params )
688
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
689
        # that seems redundant
690
        self.setParam( r, 'setCPUFrac', cpu=cpu )
691
        self.setParam( r, 'setCPUs', cores=cores )
692
        return r
693

    
694
    inited = False
695

    
696
    @classmethod
697
    def init( cls ):
698
        "Initialization for CPULimitedHost class"
699
        mountCgroups()
700
        cls.inited = True
701

    
702

    
703
# Some important things to note:
704
#
705
# The "IP" address which setIP() assigns to the switch is not
706
# an "IP address for the switch" in the sense of IP routing.
707
# Rather, it is the IP address for the control interface,
708
# on the control network, and it is only relevant to the
709
# controller. If you are running in the root namespace
710
# (which is the only way to run OVS at the moment), the
711
# control interface is the loopback interface, and you
712
# normally never want to change its IP address!
713
#
714
# In general, you NEVER want to attempt to use Linux's
715
# network stack (i.e. ifconfig) to "assign" an IP address or
716
# MAC address to a switch data port. Instead, you "assign"
717
# the IP and MAC addresses in the controller by specifying
718
# packets that you want to receive or send. The "MAC" address
719
# reported by ifconfig for a switch data port is essentially
720
# meaningless. It is important to understand this if you
721
# want to create a functional router using OpenFlow.
722

    
723
class Switch( Node ):
724
    """A Switch is a Node that is running (or has execed?)
725
       an OpenFlow switch."""
726

    
727
    portBase = 1  # Switches start with port 1 in OpenFlow
728
    dpidLen = 16  # digits in dpid passed to switch
729

    
730
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
731
        """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
732
           opts: additional switch options
733
           listenPort: port to listen on for dpctl connections"""
734
        Node.__init__( self, name, **params )
735
        self.dpid = dpid if dpid else self.defaultDpid()
736
        self.opts = opts
737
        self.listenPort = listenPort
738
        if not self.inNamespace:
739
            self.controlIntf = Intf( 'lo', self, port=0 )
740

    
741
    def defaultDpid( self ):
742
        "Derive dpid from switch name, s1 -> 1"
743
        try:
744
            dpid = int( re.findall( '\d+', self.name )[ 0 ] )
745
            dpid = hex( dpid )[ 2: ]
746
            dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
747
            return dpid
748
        except IndexError:
749
            raise Exception( 'Unable to derive default datapath ID - '
750
                             'please either specify a dpid or use a '
751
                             'canonical switch name such as s23.' )
752

    
753
    def defaultIntf( self ):
754
        "Return control interface"
755
        if self.controlIntf:
756
            return self.controlIntf
757
        else:
758
            return Node.defaultIntf( self )
759

    
760
    def sendCmd( self, *cmd, **kwargs ):
761
        """Send command to Node.
762
           cmd: string"""
763
        kwargs.setdefault( 'printPid', False )
764
        if not self.execed:
765
            return Node.sendCmd( self, *cmd, **kwargs )
766
        else:
767
            error( '*** Error: %s has execed and cannot accept commands' %
768
                     self.name )
769

    
770
    def __repr__( self ):
771
        "More informative string representation"
772
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
773
                        for i in self.intfList() ] ) )
774
        return '<%s %s: %s pid=%s> ' % (
775
                self.__class__.__name__, self.name, intfs, self.pid )
776

    
777
class UserSwitch( Switch ):
778
    "User-space switch."
779

    
780
    dpidLen = 12
781

    
782
    def __init__( self, name, **kwargs ):
783
        """Init.
784
           name: name for the switch"""
785
        Switch.__init__( self, name, **kwargs )
786
        pathCheck( 'ofdatapath', 'ofprotocol',
787
            moduleName='the OpenFlow reference user switch (openflow.org)' )
788
        if self.listenPort:
789
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
790

    
791
    @classmethod
792
    def setup( cls ):
793
        "Ensure any dependencies are loaded; if not, try to load them."
794
        if not os.path.exists( '/dev/net/tun' ):
795
            moduleDeps( add=TUN )
796

    
797
    def dpctl( self, *args ):
798
        "Run dpctl command"
799
        if not self.listenPort:
800
            return "can't run dpctl without passive listening port"
801
        return self.cmd( 'dpctl ' + ' '.join( args ) +
802
                         ' tcp:127.0.0.1:%i' % self.listenPort )
803

    
804
    def start( self, controllers ):
805
        """Start OpenFlow reference user datapath.
806
           Log to /tmp/sN-{ofd,ofp}.log.
807
           controllers: list of controller objects"""
808
        controller = controllers[ 0 ]
809
        ofdlog = '/tmp/' + self.name + '-ofd.log'
810
        ofplog = '/tmp/' + self.name + '-ofp.log'
811
        self.cmd( 'ifconfig lo up' )
812
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
813
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
814
            ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
815
            ' --no-slicing ' +
816
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
817
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
818
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
819
            ' --fail=closed ' + self.opts +
820
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
821

    
822
    def stop( self ):
823
        "Stop OpenFlow reference user datapath."
824
        self.cmd( 'kill %ofdatapath' )
825
        self.cmd( 'kill %ofprotocol' )
826
        self.deleteIntfs()
827

    
828

    
829
class OVSLegacyKernelSwitch( Switch ):
830
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
831
       Currently only works in the root namespace."""
832

    
833
    def __init__( self, name, dp=None, **kwargs ):
834
        """Init.
835
           name: name for switch
836
           dp: netlink id (0, 1, 2, ...)
837
           defaultMAC: default MAC as unsigned int; random value if None"""
838
        Switch.__init__( self, name, **kwargs )
839
        self.dp = dp if dp else self.name
840
        self.intf = self.dp
841
        if self.inNamespace:
842
            error( "OVSKernelSwitch currently only works"
843
                " in the root namespace.\n" )
844
            exit( 1 )
845

    
846
    @classmethod
847
    def setup( cls ):
848
        "Ensure any dependencies are loaded; if not, try to load them."
849
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
850
            moduleName='Open vSwitch (openvswitch.org)')
851
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
852

    
853
    def start( self, controllers ):
854
        "Start up kernel datapath."
855
        ofplog = '/tmp/' + self.name + '-ofp.log'
856
        quietRun( 'ifconfig lo up' )
857
        # Delete local datapath if it exists;
858
        # then create a new one monitoring the given interfaces
859
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
860
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
861
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
862
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
863
        # Run protocol daemon
864
        controller = controllers[ 0 ]
865
        self.cmd( 'ovs-openflowd ' + self.dp +
866
            ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
867
            ' --fail=secure ' + self.opts +
868
            ' --datapath-id=' + self.dpid +
869
            ' 1>' + ofplog + ' 2>' + ofplog + '&' )
870
        self.execed = False
871

    
872
    def stop( self ):
873
        "Terminate kernel datapath."
874
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
875
        self.cmd( 'kill %ovs-openflowd' )
876
        self.deleteIntfs()
877

    
878

    
879
class OVSSwitch( Switch ):
880
    "Open vSwitch switch. Depends on ovs-vsctl."
881

    
882
    def __init__( self, name, failMode='secure', **params ):
883
        """Init.
884
           name: name for switch
885
           failMode: controller loss behavior (secure|open)"""
886
        Switch.__init__( self, name, **params )
887
        self.failMode = failMode
888

    
889
    @classmethod
890
    def setup( cls ):
891
        "Make sure Open vSwitch is installed and working"
892
        pathCheck( 'ovs-vsctl',
893
            moduleName='Open vSwitch (openvswitch.org)')
894
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
895
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
896
        if exitcode:
897
            error( out + err +
898
                   'ovs-vsctl exited with code %d\n' % exitcode +
899
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
900
                   'Make sure that Open vSwitch is installed, '
901
                   'that ovsdb-server is running, and that\n'
902
                   '"ovs-vsctl show" works correctly.\n'
903
                   'You may wish to try '
904
                   '"service openvswitch-switch start".\n' )
905
            exit( 1 )
906

    
907
    def dpctl( self, *args ):
908
        "Run ovs-dpctl command"
909
        return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
910

    
911
    @staticmethod
912
    def TCReapply( intf ):
913
        """Unfortunately OVS and Mininet are fighting
914
           over tc queuing disciplines. As a quick hack/
915
           workaround, we clear OVS's and reapply our own."""
916
        if type( intf ) is TCIntf:
917
            intf.config( **intf.params )
918

    
919
    def attach( self, intf ):
920
        "Connect a data port"
921
        self.cmd( 'ovs-vsctl add-port', self, intf )
922
        self.cmd( 'ifconfig', intf, 'up' )
923
        self.TCReapply( intf )
924

    
925
    def detach( self, intf ):
926
        "Disconnect a data port"
927
        self.cmd( 'ovs-vsctl del-port', self, intf )
928

    
929
    def start( self, controllers ):
930
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
931
        if self.inNamespace:
932
            raise Exception(
933
                'OVS kernel switch does not work in a namespace' )
934
        # We should probably call config instead, but this
935
        # requires some rethinking...
936
        self.cmd( 'ifconfig lo up' )
937
        # Annoyingly, --if-exists option seems not to work
938
        self.cmd( 'ovs-vsctl del-br', self )
939
        self.cmd( 'ovs-vsctl add-br', self )
940
        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
941
        for intf in self.intfList():
942
            if not intf.IP():
943
                self.attach( intf )
944
        # Add controllers
945
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
946
                            for c in controllers ] )
947
        self.cmd( 'ovs-vsctl set-controller', self, clist )
948

    
949
    def stop( self ):
950
        "Terminate OVS switch."
951
        self.cmd( 'ovs-vsctl del-br', self )
952
        self.deleteIntfs()
953

    
954
OVSKernelSwitch = OVSSwitch
955

    
956

    
957
class Controller( Node ):
958
    """A Controller is a Node that is running (or has execed?) an
959
       OpenFlow controller."""
960

    
961
    def __init__( self, name, inNamespace=False, command='controller',
962
                 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
963
                 port=6633, **params ):
964
        self.command = command
965
        self.cargs = cargs
966
        self.cdir = cdir
967
        self.ip = ip
968
        self.port = port
969
        Node.__init__( self, name, inNamespace=inNamespace,
970
            ip=ip, **params  )
971

    
972
    def start( self ):
973
        """Start <controller> <args> on controller.
974
           Log to /tmp/cN.log"""
975
        pathCheck( self.command )
976
        cout = '/tmp/' + self.name + '.log'
977
        if self.cdir is not None:
978
            self.cmd( 'cd ' + self.cdir )
979
        self.cmd( self.command + ' ' + self.cargs % self.port +
980
            ' 1>' + cout + ' 2>' + cout + '&' )
981
        self.execed = False
982

    
983
    def stop( self ):
984
        "Stop controller."
985
        self.cmd( 'kill %' + self.command )
986
        self.terminate()
987

    
988
    def IP( self, intf=None ):
989
        "Return IP address of the Controller"
990
        if self.intfs:
991
            ip = Node.IP( self, intf )
992
        else:
993
            ip = self.ip
994
        return ip
995

    
996
    def __repr__( self ):
997
        "More informative string representation"
998
        return '<%s %s: %s:%s pid=%s> ' % (
999
                self.__class__.__name__, self.name,
1000
                self.IP(), self.port, self.pid )
1001

    
1002

    
1003
class OVSController( Controller ):
1004
    "Open vSwitch controller"
1005
    def __init__( self, name, command='ovs-controller', **kwargs ):
1006
        Controller.__init__( self, name, command=command, **kwargs )
1007

    
1008

    
1009
class NOX( Controller ):
1010
    "Controller to run a NOX application."
1011

    
1012
    def __init__( self, name, *noxArgs, **kwargs ):
1013
        """Init.
1014
           name: name to give controller
1015
           noxArgs: arguments (strings) to pass to NOX"""
1016
        if not noxArgs:
1017
            warn( 'warning: no NOX modules specified; '
1018
                  'running packetdump only\n' )
1019
            noxArgs = [ 'packetdump' ]
1020
        elif type( noxArgs ) not in ( list, tuple ):
1021
            noxArgs = [ noxArgs ]
1022

    
1023
        if 'NOX_CORE_DIR' not in os.environ:
1024
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1025
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1026

    
1027
        Controller.__init__( self, name,
1028
            command=noxCoreDir + '/nox_core',
1029
            cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1030
                    ' '.join( noxArgs ),
1031
            cdir=noxCoreDir, **kwargs )
1032

    
1033

    
1034
class RemoteController( Controller ):
1035
    "Controller running outside of Mininet's control."
1036

    
1037
    def __init__( self, name, ip='127.0.0.1',
1038
                 port=6633, **kwargs):
1039
        """Init.
1040
           name: name to give controller
1041
           defaultIP: the IP address where the remote controller is
1042
           listening
1043
           port: the port where the remote controller is listening"""
1044
        Controller.__init__( self, name, ip=ip, port=port,
1045
            **kwargs )
1046

    
1047
    def start( self ):
1048
        "Overridden to do nothing."
1049
        return
1050

    
1051
    def stop( self ):
1052
        "Overridden to do nothing."
1053
        return