Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ aa554d98

History | View | Annotate | Download (42.3 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
from operator import or_
53

    
54
from mininet.log import info, error, warn, debug
55
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
56
                           numCores, retry, mountCgroups )
57
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
58
from mininet.link import Link, Intf, TCIntf
59

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

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

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

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

    
74
        self.name = name
75
        self.inNamespace = inNamespace
76

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

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

    
85
        # Make pylint happy
86
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
87
            self.lastPid, self.lastCmd, self.pollOut ) = (
88
                None, None, None, None, None, None, None, None )
89
        self.waiting = False
90
        self.readbuf = ''
91

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

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

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

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

    
109
    # Command support via shell process in namespace
110

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

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

    
150
    # Subshell I/O, commands and control
151

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

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

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

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

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

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

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

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

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

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

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

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

    
289
    def popen( self, *args, **kwargs ):
290
        """Return a Popen() object in our namespace
291
           args: Popen() args, single list, or string
292
           kwargs: Popen() keyword args"""
293
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
294
                     'mncmd':
295
                     [ 'mnexec', '-da', str( self.pid ) ] }
296
        defaults.update( kwargs )
297
        if len( args ) == 1:
298
            if type( args[ 0 ] ) is list:
299
                # popen([cmd, arg1, arg2...])
300
                cmd = args[ 0 ]
301
            elif type( args[ 0 ] ) is str:
302
                # popen("cmd arg1 arg2...")
303
                cmd = args[ 0 ].split()
304
            else:
305
                raise Exception( 'popen() requires a string or list' )
306
        elif len( args ) > 0:
307
            # popen( cmd, arg1, arg2... )
308
            cmd = list( args )
309
        # Attach to our namespace  using mnexec -a
310
        mncmd = defaults[ 'mncmd' ]
311
        del defaults[ 'mncmd' ]
312
        cmd = mncmd + cmd
313
        # Shell requires a string, not a list!
314
        if defaults.get( 'shell', False ):
315
            cmd = ' '.join( cmd )
316
        return Popen( cmd, **defaults )
317

    
318
    def pexec( self, *args, **kwargs ):
319
        """Execute a command using popen
320
           returns: out, err, exitcode"""
321
        popen = self.popen( *args, **kwargs)
322
        out, err = popen.communicate()
323
        exitcode = popen.wait()
324
        return out, err, exitcode
325

    
326
    # Interface management, configuration, and routing
327

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

    
334
    def newPort( self ):
335
        "Return the next port number to allocate."
336
        if len( self.ports ) > 0:
337
            return max( self.ports.values() ) + 1
338
        return self.portBase
339

    
340
    def addIntf( self, intf, port=None ):
341
        """Add an interface.
342
           intf: interface
343
           port: port number (optional, typically OpenFlow port number)"""
344
        if port is None:
345
            port = self.newPort()
346
        self.intfs[ port ] = intf
347
        self.ports[ intf ] = port
348
        self.nameToIntf[ intf.name ] = intf
349
        debug( '\n' )
350
        debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
351
        if self.inNamespace:
352
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
353
            moveIntf( intf.name, self )
354

    
355
    def defaultIntf( self ):
356
        "Return interface for lowest port"
357
        ports = self.intfs.keys()
358
        if ports:
359
            return self.intfs[ min( ports ) ]
360
        else:
361
            warn( '*** defaultIntf: warning:', self.name,
362
                  'has no interfaces\n' )
363

    
364
    def intf( self, intf='' ):
365
        """Return our interface object with given string name,
366
           default intf if name is falsy (None, empty string, etc).
367
           or the input intf arg.
368

369
        Having this fcn return its arg for Intf objects makes it
370
        easier to construct functions with flexible input args for
371
        interfaces (those that accept both string names and Intf objects).
372
        """
373
        if not intf:
374
            return self.defaultIntf()
375
        elif type( intf ) is str:
376
            return self.nameToIntf[ intf ]
377
        else:
378
            return intf
379

    
380
    def connectionsTo( self, node):
381
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
382
        # We could optimize this if it is important
383
        connections = []
384
        for intf in self.intfList():
385
            link = intf.link
386
            if link:
387
                node1, node2 = link.intf1.node, link.intf2.node
388
                if node1 == self and node2 == node:
389
                    connections += [ ( intf, link.intf2 ) ]
390
                elif node1 == node and node2 == self:
391
                    connections += [ ( intf, link.intf1 ) ]
392
        return connections
393

    
394
    def deleteIntfs( self ):
395
        "Delete all of our interfaces."
396
        # In theory the interfaces should go away after we shut down.
397
        # However, this takes time, so we're better off removing them
398
        # explicitly so that we won't get errors if we run before they
399
        # have been removed by the kernel. Unfortunately this is very slow,
400
        # at least with Linux kernels before 2.6.33
401
        for intf in self.intfs.values():
402
            intf.delete()
403
            info( '.' )
404

    
405
    # Routing support
406

    
407
    def setARP( self, ip, mac ):
408
        """Add an ARP entry.
409
           ip: IP address as string
410
           mac: MAC address as string"""
411
        result = self.cmd( 'arp', '-s', ip, mac )
412
        return result
413

    
414
    def setHostRoute( self, ip, intf ):
415
        """Add route to host.
416
           ip: IP address as dotted decimal
417
           intf: string, interface name"""
418
        return self.cmd( 'route add -host', ip, 'dev', intf )
419

    
420
    def setDefaultRoute( self, intf=None ):
421
        """Set the default route to go through intf.
422
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
423
        # Note setParam won't call us if intf is none
424
        if type( intf ) is str and ' ' in intf:
425
            params = intf
426
        else:
427
            params = 'dev %s' % intf
428
        self.cmd( 'ip route del default' )
429
        return self.cmd( 'ip route add default', params )
430

    
431
    # Convenience and configuration methods
432

    
433
    def setMAC( self, mac, intf=None ):
434
        """Set the MAC address for an interface.
435
           intf: intf or intf name
436
           mac: MAC address as string"""
437
        return self.intf( intf ).setMAC( mac )
438

    
439
    def setIP( self, ip, prefixLen=8, intf=None ):
440
        """Set the IP address for an interface.
441
           intf: intf or intf name
442
           ip: IP address as a string
443
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
444
        # This should probably be rethought
445
        if '/' not in ip:
446
            ip = '%s/%s' % ( ip, prefixLen )
447
        return self.intf( intf ).setIP( ip )
448

    
449
    def IP( self, intf=None ):
450
        "Return IP address of a node or specific interface."
451
        return self.intf( intf ).IP()
452

    
453
    def MAC( self, intf=None ):
454
        "Return MAC address of a node or specific interface."
455
        return self.intf( intf ).MAC()
456

    
457
    def intfIsUp( self, intf=None ):
458
        "Check if an interface is up."
459
        return self.intf( intf ).isUp()
460

    
461
    # The reason why we configure things in this way is so
462
    # That the parameters can be listed and documented in
463
    # the config method.
464
    # Dealing with subclasses and superclasses is slightly
465
    # annoying, but at least the information is there!
466

    
467
    def setParam( self, results, method, **param ):
468
        """Internal method: configure a *single* parameter
469
           results: dict of results to update
470
           method: config method name
471
           param: arg=value (ignore if value=None)
472
           value may also be list or dict"""
473
        name, value = param.items()[ 0 ]
474
        f = getattr( self, method, None )
475
        if not f or value is None:
476
            return
477
        if type( value ) is list:
478
            result = f( *value )
479
        elif type( value ) is dict:
480
            result = f( **value )
481
        else:
482
            result = f( value )
483
        results[ name ] = result
484
        return result
485

    
486
    def config( self, mac=None, ip=None,
487
                defaultRoute=None, lo='up', **_params ):
488
        """Configure Node according to (optional) parameters:
489
           mac: MAC address for default interface
490
           ip: IP address for default interface
491
           ifconfig: arbitrary interface configuration
492
           Subclasses should override this method and call
493
           the parent class's config(**params)"""
494
        # If we were overriding this method, we would call
495
        # the superclass config method here as follows:
496
        # r = Parent.config( **_params )
497
        r = {}
498
        self.setParam( r, 'setMAC', mac=mac )
499
        self.setParam( r, 'setIP', ip=ip )
500
        self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
501
        # This should be examined
502
        self.cmd( 'ifconfig lo ' + lo )
503
        return r
504

    
505
    def configDefault( self, **moreParams ):
506
        "Configure with default parameters"
507
        self.params.update( moreParams )
508
        self.config( **self.params )
509

    
510
    # This is here for backward compatibility
511
    def linkTo( self, node, link=Link ):
512
        """(Deprecated) Link to another node
513
           replace with Link( node1, node2)"""
514
        return link( self, node )
515

    
516
    # Other methods
517

    
518
    def intfList( self ):
519
        "List of our interfaces sorted by port number"
520
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
521

    
522
    def intfNames( self ):
523
        "The names of our interfaces sorted by port number"
524
        return [ str( i ) for i in self.intfList() ]
525

    
526
    def __repr__( self ):
527
        "More informative string representation"
528
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
529
                              for i in self.intfList() ] ) )
530
        return '<%s %s: %s pid=%s> ' % (
531
            self.__class__.__name__, self.name, intfs, self.pid )
532

    
533
    def __str__( self ):
534
        "Abbreviated string representation"
535
        return self.name
536

    
537
    # Automatic class setup support
538

    
539
    isSetup = False
540

    
541
    @classmethod
542
    def checkSetup( cls ):
543
        "Make sure our class and superclasses are set up"
544
        while cls and not getattr( cls, 'isSetup', True ):
545
            cls.setup()
546
            cls.isSetup = True
547
            # Make pylint happy
548
            cls = getattr( type( cls ), '__base__', None )
549

    
550
    @classmethod
551
    def setup( cls ):
552
        "Make sure our class dependencies are available"
553
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
554

    
555

    
556
class Host( Node ):
557
    "A host is simply a Node"
558
    pass
559

    
560

    
561
class CPULimitedHost( Host ):
562

    
563
    "CPU limited host"
564

    
565
    def __init__( self, name, sched='cfs', **kwargs ):
566
        Host.__init__( self, name, **kwargs )
567
        # Initialize class if necessary
568
        if not CPULimitedHost.inited:
569
            CPULimitedHost.init()
570
        # Create a cgroup and move shell into it
571
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
572
        errFail( 'cgcreate -g ' + self.cgroup )
573
        # We don't add ourselves to a cpuset because you must
574
        # specify the cpu and memory placement first
575
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
576
        # BL: Setting the correct period/quota is tricky, particularly
577
        # for RT. RT allows very small quotas, but the overhead
578
        # seems to be high. CFS has a mininimum quota of 1 ms, but
579
        # still does better with larger period values.
580
        self.period_us = kwargs.get( 'period_us', 100000 )
581
        self.sched = sched
582
        self.rtprio = 20
583

    
584
    def cgroupSet( self, param, value, resource='cpu' ):
585
        "Set a cgroup parameter and return its value"
586
        cmd = 'cgset -r %s.%s=%s /%s' % (
587
            resource, param, value, self.name )
588
        quietRun( cmd )
589
        nvalue = int( self.cgroupGet( param, resource ) )
590
        if nvalue != value:
591
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
592
                   % ( param, nvalue, value ) )
593
        return nvalue
594

    
595
    def cgroupGet( self, param, resource='cpu' ):
596
        "Return value of cgroup parameter"
597
        cmd = 'cgget -r %s.%s /%s' % (
598
            resource, param, self.name )
599
        return int( quietRun( cmd ).split()[ -1 ] )
600

    
601
    def cgroupDel( self ):
602
        "Clean up our cgroup"
603
        # info( '*** deleting cgroup', self.cgroup, '\n' )
604
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
605
        return exitcode != 0
606

    
607
    def popen( self, *args, **kwargs ):
608
        """Return a Popen() object in node's namespace
609
           args: Popen() args, single list, or string
610
           kwargs: Popen() keyword args"""
611
        # Tell mnexec to execute command in our cgroup
612
        mncmd = [ 'mnexec', '-da', str( self.pid ),
613
                  '-g', self.name ]
614
        if self.sched == 'rt':
615
            mncmd += [ '-r', str( self.rtprio ) ]
616
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
617

    
618
    def cleanup( self ):
619
        "Clean up our cgroup"
620
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
621

    
622
    def chrt( self ):
623
        "Set RT scheduling priority"
624
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
625
        result = quietRun( 'chrt -p %s' % self.pid )
626
        firstline = result.split( '\n' )[ 0 ]
627
        lastword = firstline.split( ' ' )[ -1 ]
628
        if lastword != 'SCHED_RR':
629
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
630
        return lastword
631

    
632
    def rtInfo( self, f ):
633
        "Internal method: return parameters for RT bandwidth"
634
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
635
        # RT uses wall clock time for period and quota
636
        quota = int( self.period_us * f * numCores() )
637
        return pstr, qstr, self.period_us, quota
638

    
639
    def cfsInfo( self, f):
640
        "Internal method: return parameters for CFS bandwidth"
641
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
642
        # CFS uses wall clock time for period and CPU time for quota.
643
        quota = int( self.period_us * f * numCores() )
644
        period = self.period_us
645
        if f > 0 and quota < 1000:
646
            debug( '(cfsInfo: increasing default period) ' )
647
            quota = 1000
648
            period = int( quota / f / numCores() )
649
        return pstr, qstr, period, quota
650

    
651
    # BL comment:
652
    # This may not be the right API,
653
    # since it doesn't specify CPU bandwidth in "absolute"
654
    # units the way link bandwidth is specified.
655
    # We should use MIPS or SPECINT or something instead.
656
    # Alternatively, we should change from system fraction
657
    # to CPU seconds per second, essentially assuming that
658
    # all CPUs are the same.
659

    
660
    def setCPUFrac( self, f=-1, sched=None):
661
        """Set overall CPU fraction for this host
662
           f: CPU bandwidth limit (fraction)
663
           sched: 'rt' or 'cfs'
664
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
665
        if not f:
666
            return
667
        if not sched:
668
            sched = self.sched
669
        if sched == 'rt':
670
            pstr, qstr, period, quota = self.rtInfo( f )
671
        elif sched == 'cfs':
672
            pstr, qstr, period, quota = self.cfsInfo( f )
673
        else:
674
            return
675
        if quota < 0:
676
            # Reset to unlimited
677
            quota = -1
678
        # Set cgroup's period and quota
679
        self.cgroupSet( pstr, period )
680
        self.cgroupSet( qstr, quota )
681
        if sched == 'rt':
682
            # Set RT priority if necessary
683
            self.chrt()
684
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
685

    
686
    def setCPUs( self, cores, mems=0 ):
687
        "Specify (real) cores that our cgroup can run on"
688
        if type( cores ) is list:
689
            cores = ','.join( [ str( c ) for c in cores ] )
690
        self.cgroupSet( resource='cpuset', param='cpus',
691
                        value=cores )
692
        # Memory placement is probably not relevant, but we
693
        # must specify it anyway
694
        self.cgroupSet( resource='cpuset', param='mems',
695
                        value=mems)
696
        # We have to do this here after we've specified
697
        # cpus and mems
698
        errFail( 'cgclassify -g cpuset:/%s %s' % (
699
                 self.name, self.pid ) )
700

    
701
    def config( self, cpu=None, cores=None, **params ):
702
        """cpu: desired overall system CPU fraction
703
           cores: (real) core(s) this host can run on
704
           params: parameters for Node.config()"""
705
        r = Node.config( self, **params )
706
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
707
        # that seems redundant
708
        self.setParam( r, 'setCPUFrac', cpu=cpu )
709
        self.setParam( r, 'setCPUs', cores=cores )
710
        return r
711

    
712
    inited = False
713

    
714
    @classmethod
715
    def init( cls ):
716
        "Initialization for CPULimitedHost class"
717
        mountCgroups()
718
        cls.inited = True
719

    
720

    
721
# Some important things to note:
722
#
723
# The "IP" address which setIP() assigns to the switch is not
724
# an "IP address for the switch" in the sense of IP routing.
725
# Rather, it is the IP address for the control interface,
726
# on the control network, and it is only relevant to the
727
# controller. If you are running in the root namespace
728
# (which is the only way to run OVS at the moment), the
729
# control interface is the loopback interface, and you
730
# normally never want to change its IP address!
731
#
732
# In general, you NEVER want to attempt to use Linux's
733
# network stack (i.e. ifconfig) to "assign" an IP address or
734
# MAC address to a switch data port. Instead, you "assign"
735
# the IP and MAC addresses in the controller by specifying
736
# packets that you want to receive or send. The "MAC" address
737
# reported by ifconfig for a switch data port is essentially
738
# meaningless. It is important to understand this if you
739
# want to create a functional router using OpenFlow.
740

    
741
class Switch( Node ):
742
    """A Switch is a Node that is running (or has execed?)
743
       an OpenFlow switch."""
744

    
745
    portBase = 1  # Switches start with port 1 in OpenFlow
746
    dpidLen = 16  # digits in dpid passed to switch
747

    
748
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
749
        """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
750
           opts: additional switch options
751
           listenPort: port to listen on for dpctl connections"""
752
        Node.__init__( self, name, **params )
753
        self.dpid = dpid if dpid else self.defaultDpid()
754
        self.opts = opts
755
        self.listenPort = listenPort
756
        if not self.inNamespace:
757
            self.controlIntf = Intf( 'lo', self, port=0 )
758

    
759
    def defaultDpid( self ):
760
        "Derive dpid from switch name, s1 -> 1"
761
        try:
762
            dpid = int( re.findall( r'\d+', self.name )[ 0 ] )
763
            dpid = hex( dpid )[ 2: ]
764
            dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
765
            return dpid
766
        except IndexError:
767
            raise Exception( 'Unable to derive default datapath ID - '
768
                             'please either specify a dpid or use a '
769
                             'canonical switch name such as s23.' )
770

    
771
    def defaultIntf( self ):
772
        "Return control interface"
773
        if self.controlIntf:
774
            return self.controlIntf
775
        else:
776
            return Node.defaultIntf( self )
777

    
778
    def sendCmd( self, *cmd, **kwargs ):
779
        """Send command to Node.
780
           cmd: string"""
781
        kwargs.setdefault( 'printPid', False )
782
        if not self.execed:
783
            return Node.sendCmd( self, *cmd, **kwargs )
784
        else:
785
            error( '*** Error: %s has execed and cannot accept commands' %
786
                   self.name )
787

    
788
    def connected( self ):
789
        "Is the switch connected to a controller? (override this method)"
790
        return False and self  # satisfy pylint
791

    
792
    def __repr__( self ):
793
        "More informative string representation"
794
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
795
                              for i in self.intfList() ] ) )
796
        return '<%s %s: %s pid=%s> ' % (
797
            self.__class__.__name__, self.name, intfs, self.pid )
798

    
799
class UserSwitch( Switch ):
800
    "User-space switch."
801

    
802
    dpidLen = 12
803

    
804
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
805
        """Init.
806
           name: name for the switch
807
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
808
        Switch.__init__( self, name, **kwargs )
809
        pathCheck( 'ofdatapath', 'ofprotocol',
810
                   moduleName='the OpenFlow reference user switch' +
811
                              '(openflow.org)' )
812
        if self.listenPort:
813
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
814
        self.dpopts = dpopts
815

    
816
    @classmethod
817
    def setup( cls ):
818
        "Ensure any dependencies are loaded; if not, try to load them."
819
        if not os.path.exists( '/dev/net/tun' ):
820
            moduleDeps( add=TUN )
821

    
822
    def dpctl( self, *args ):
823
        "Run dpctl command"
824
        if not self.listenPort:
825
            return "can't run dpctl without passive listening port"
826
        return self.cmd( 'dpctl ' + ' '.join( args ) +
827
                         ' tcp:127.0.0.1:%i' % self.listenPort )
828

    
829
    def connected( self ):
830
        "Is the switch connected to a controller?"
831
        return 'remote.is-connected=true' in self.dpctl( 'status' )
832

    
833
    def start( self, controllers ):
834
        """Start OpenFlow reference user datapath.
835
           Log to /tmp/sN-{ofd,ofp}.log.
836
           controllers: list of controller objects"""
837
        # Add controllers
838
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
839
                            for c in controllers ] )
840
        ofdlog = '/tmp/' + self.name + '-ofd.log'
841
        ofplog = '/tmp/' + self.name + '-ofp.log'
842
        self.cmd( 'ifconfig lo up' )
843
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
844
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
845
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
846
                  self.dpopts +
847
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
848
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
849
                  ' ' + clist +
850
                  ' --fail=closed ' + self.opts +
851
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
852

    
853
    def stop( self ):
854
        "Stop OpenFlow reference user datapath."
855
        self.cmd( 'kill %ofdatapath' )
856
        self.cmd( 'kill %ofprotocol' )
857
        self.deleteIntfs()
858

    
859

    
860
class OVSLegacyKernelSwitch( Switch ):
861
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
862
       Currently only works in the root namespace."""
863

    
864
    def __init__( self, name, dp=None, **kwargs ):
865
        """Init.
866
           name: name for switch
867
           dp: netlink id (0, 1, 2, ...)
868
           defaultMAC: default MAC as unsigned int; random value if None"""
869
        Switch.__init__( self, name, **kwargs )
870
        self.dp = dp if dp else self.name
871
        self.intf = self.dp
872
        if self.inNamespace:
873
            error( "OVSKernelSwitch currently only works"
874
                   " in the root namespace.\n" )
875
            exit( 1 )
876

    
877
    @classmethod
878
    def setup( cls ):
879
        "Ensure any dependencies are loaded; if not, try to load them."
880
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
881
                   moduleName='Open vSwitch (openvswitch.org)')
882
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
883

    
884
    def start( self, controllers ):
885
        "Start up kernel datapath."
886
        ofplog = '/tmp/' + self.name + '-ofp.log'
887
        quietRun( 'ifconfig lo up' )
888
        # Delete local datapath if it exists;
889
        # then create a new one monitoring the given interfaces
890
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
891
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
892
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
893
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
894
        # Run protocol daemon
895
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
896
                            for c in controllers ] )
897
        self.cmd( 'ovs-openflowd ' + self.dp +
898
                  ' ' + clist +
899
                  ' --fail=secure ' + self.opts +
900
                  ' --datapath-id=' + self.dpid +
901
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
902
        self.execed = False
903

    
904
    def stop( self ):
905
        "Terminate kernel datapath."
906
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
907
        self.cmd( 'kill %ovs-openflowd' )
908
        self.deleteIntfs()
909

    
910

    
911
class OVSSwitch( Switch ):
912
    "Open vSwitch switch. Depends on ovs-vsctl."
913

    
914
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
915
        """Init.
916
           name: name for switch
917
           failMode: controller loss behavior (secure|open)
918
           datapath: userspace or kernel mode (kernel|user)"""
919
        Switch.__init__( self, name, **params )
920
        self.failMode = failMode
921
        self.datapath = datapath
922

    
923
    @classmethod
924
    def setup( cls ):
925
        "Make sure Open vSwitch is installed and working"
926
        pathCheck( 'ovs-vsctl',
927
                   moduleName='Open vSwitch (openvswitch.org)')
928
        # This should no longer be needed, and it breaks
929
        # with OVS 1.7 which has renamed the kernel module:
930
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
931
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
932
        if exitcode:
933
            error( out + err +
934
                   'ovs-vsctl exited with code %d\n' % exitcode +
935
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
936
                   'Make sure that Open vSwitch is installed, '
937
                   'that ovsdb-server is running, and that\n'
938
                   '"ovs-vsctl show" works correctly.\n'
939
                   'You may wish to try '
940
                   '"service openvswitch-switch start".\n' )
941
            exit( 1 )
942

    
943
    def dpctl( self, *args ):
944
        "Run ovs-ofctl command"
945
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
946

    
947
    @staticmethod
948
    def TCReapply( intf ):
949
        """Unfortunately OVS and Mininet are fighting
950
           over tc queuing disciplines. As a quick hack/
951
           workaround, we clear OVS's and reapply our own."""
952
        if type( intf ) is TCIntf:
953
            intf.config( **intf.params )
954

    
955
    def attach( self, intf ):
956
        "Connect a data port"
957
        self.cmd( 'ovs-vsctl add-port', self, intf )
958
        self.cmd( 'ifconfig', intf, 'up' )
959
        self.TCReapply( intf )
960

    
961
    def detach( self, intf ):
962
        "Disconnect a data port"
963
        self.cmd( 'ovs-vsctl del-port', self, intf )
964

    
965
    def controllerUUIDs( self ):
966
        "Return ovsdb UUIDs for our controllers"
967
        uuids = []
968
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
969
                               'Controller' ).strip()
970
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
971
            controllers = controllers[ 1 : -1 ]
972
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
973
        return uuids
974

    
975
    def connected( self ):
976
        "Are we connected to at least one of our controllers?"
977
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
978
                                         uuid, 'is_connected' )
979
                    for uuid in self.controllerUUIDs() ]
980
        return reduce( or_, results, False )
981

    
982
    def start( self, controllers ):
983
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
984
        if self.inNamespace:
985
            raise Exception(
986
                'OVS kernel switch does not work in a namespace' )
987
        # We should probably call config instead, but this
988
        # requires some rethinking...
989
        self.cmd( 'ifconfig lo up' )
990
        # Annoyingly, --if-exists option seems not to work
991
        self.cmd( 'ovs-vsctl del-br', self )
992
        self.cmd( 'ovs-vsctl add-br', self )
993
        if self.datapath == 'user':
994
            self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' )
995
        int( self.dpid, 16 ) # DPID must be a hex string
996
        self.cmd( 'ovs-vsctl -- set Bridge', self,
997
                  'other_config:datapath-id=' + self.dpid )
998
        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
999
        for intf in self.intfList():
1000
            if not intf.IP():
1001
                self.attach( intf )
1002
        # Add controllers
1003
        clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1004
                            for c in controllers ] )
1005
        if self.listenPort:
1006
            clist += ' ptcp:%s' % self.listenPort
1007
        self.cmd( 'ovs-vsctl set-controller', self, clist )
1008
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1009
        for uuid in self.controllerUUIDs():
1010
            if uuid.count( '-' ) != 4:
1011
                # Doesn't look like a UUID
1012
                continue
1013
            uuid = uuid.strip()
1014
            self.cmd( 'ovs-vsctl set Controller', uuid,
1015
                      'max_backoff=1000' )
1016

    
1017
    def stop( self ):
1018
        "Terminate OVS switch."
1019
        self.cmd( 'ovs-vsctl del-br', self )
1020
        self.deleteIntfs()
1021

    
1022
OVSKernelSwitch = OVSSwitch
1023

    
1024

    
1025
class Controller( Node ):
1026
    """A Controller is a Node that is running (or has execed?) an
1027
       OpenFlow controller."""
1028

    
1029
    def __init__( self, name, inNamespace=False, command='controller',
1030
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1031
                  port=6633, **params ):
1032
        self.command = command
1033
        self.cargs = cargs
1034
        self.cdir = cdir
1035
        self.ip = ip
1036
        self.port = port
1037
        Node.__init__( self, name, inNamespace=inNamespace,
1038
                       ip=ip, **params  )
1039
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1040
        self.checkListening()
1041

    
1042
    def checkListening( self ):
1043
        "Make sure no controllers are running on our port"
1044
        # Verify that Telnet is installed first:
1045
        out, _err, returnCode = errRun( "which telnet" )
1046
        if 'telnet' not in out or returnCode != 0:
1047
            raise Exception( "Error running telnet to check for listening "
1048
                             "controllers; please check that it is "
1049
                             "installed." )
1050
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1051
                              ( self.ip, self.port ) )
1052
        if 'Unable' not in listening:
1053
            servers = self.cmd( 'netstat -atp' ).split( '\n' )
1054
            pstr = ':%d ' % self.port
1055
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1056
            raise Exception( "Please shut down the controller which is"
1057
                             " running on port %d:\n" % self.port +
1058
                             '\n'.join( clist ) )
1059

    
1060
    def start( self ):
1061
        """Start <controller> <args> on controller.
1062
           Log to /tmp/cN.log"""
1063
        pathCheck( self.command )
1064
        cout = '/tmp/' + self.name + '.log'
1065
        if self.cdir is not None:
1066
            self.cmd( 'cd ' + self.cdir )
1067
        self.cmd( self.command + ' ' + self.cargs % self.port +
1068
                  ' 1>' + cout + ' 2>' + cout + '&' )
1069
        self.execed = False
1070

    
1071
    def stop( self ):
1072
        "Stop controller."
1073
        self.cmd( 'kill %' + self.command )
1074
        self.terminate()
1075

    
1076
    def IP( self, intf=None ):
1077
        "Return IP address of the Controller"
1078
        if self.intfs:
1079
            ip = Node.IP( self, intf )
1080
        else:
1081
            ip = self.ip
1082
        return ip
1083

    
1084
    def __repr__( self ):
1085
        "More informative string representation"
1086
        return '<%s %s: %s:%s pid=%s> ' % (
1087
            self.__class__.__name__, self.name,
1088
            self.IP(), self.port, self.pid )
1089

    
1090

    
1091
class OVSController( Controller ):
1092
    "Open vSwitch controller"
1093
    def __init__( self, name, command='ovs-controller', **kwargs ):
1094
        Controller.__init__( self, name, command=command, **kwargs )
1095

    
1096

    
1097
class NOX( Controller ):
1098
    "Controller to run a NOX application."
1099

    
1100
    def __init__( self, name, *noxArgs, **kwargs ):
1101
        """Init.
1102
           name: name to give controller
1103
           noxArgs: arguments (strings) to pass to NOX"""
1104
        if not noxArgs:
1105
            warn( 'warning: no NOX modules specified; '
1106
                  'running packetdump only\n' )
1107
            noxArgs = [ 'packetdump' ]
1108
        elif type( noxArgs ) not in ( list, tuple ):
1109
            noxArgs = [ noxArgs ]
1110

    
1111
        if 'NOX_CORE_DIR' not in os.environ:
1112
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1113
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1114

    
1115
        Controller.__init__( self, name,
1116
                             command=noxCoreDir + '/nox_core',
1117
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1118
                             ' '.join( noxArgs ),
1119
                             cdir=noxCoreDir,
1120
                             **kwargs )
1121

    
1122

    
1123
class RemoteController( Controller ):
1124
    "Controller running outside of Mininet's control."
1125

    
1126
    def __init__( self, name, ip='127.0.0.1',
1127
                  port=6633, **kwargs):
1128
        """Init.
1129
           name: name to give controller
1130
           ip: the IP address where the remote controller is
1131
           listening
1132
           port: the port where the remote controller is listening"""
1133
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1134

    
1135
    def start( self ):
1136
        "Overridden to do nothing."
1137
        return
1138

    
1139
    def stop( self ):
1140
        "Overridden to do nothing."
1141
        return
1142

    
1143
    def checkListening( self ):
1144
        "Warn if remote controller is not accessible"
1145
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1146
                              ( self.ip, self.port ) )
1147
        if 'Unable' in listening:
1148
            warn( "Unable to contact the remote controller"
1149
                  " at %s:%d\n" % ( self.ip, self.port ) )