Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 804c4bbf

History | View | Annotate | Download (42.2 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='', **kwargs ):
805
        """Init.
806
           name: name for the switch"""
807
        Switch.__init__( self, name, **kwargs )
808
        pathCheck( 'ofdatapath', 'ofprotocol',
809
                   moduleName='the OpenFlow reference user switch' +
810
                              '(openflow.org)' )
811
        if self.listenPort:
812
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
813
        self.dpopts = dpopts
814

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

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

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

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

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

    
858

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

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

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

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

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

    
909

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

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

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

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

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

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

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

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

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

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

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

    
1021
OVSKernelSwitch = OVSSwitch
1022

    
1023

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

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

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

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

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

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

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

    
1089

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

    
1095

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

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

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

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

    
1121

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

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

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

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

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