Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 27da832d

History | View | Annotate | Download (44.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, **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

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

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

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

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

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

    
856

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

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

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

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

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

    
907

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

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

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

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

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

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

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

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

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

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

    
1013
    def stop( self ):
1014
        "Terminate OVS switch."
1015
        self.cmd( 'ovs-vsctl del-br', self )
1016
        self.deleteIntfs()
1017

    
1018
OVSKernelSwitch = OVSSwitch
1019

    
1020

    
1021
class IVSSwitch(Switch):
1022
    """IVS virtual switch
1023
       Currently only works in the root namespace.
1024
    """
1025

    
1026
    def __init__( self, name, **kwargs ):
1027
        Switch.__init__( self, name, **kwargs )
1028
        self.process = None
1029
        if self.inNamespace:
1030
            error( "IVSSwitch currently only works"
1031
                   " in the root namespace.\n" )
1032
            exit( 1 )
1033

    
1034
    @classmethod
1035
    def setup( cls ):
1036
        "Make sure IVS is installed"
1037
        pathCheck( 'ivs-ctl', 'ivs',
1038
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1039
        out, err, exitcode = errRun( 'ivs-ctl show' )
1040
        if exitcode:
1041
            error( out + err +
1042
                   'ivs-ctl exited with code %d\n' % exitcode +
1043
                   '*** The openvswitch kernel module might '
1044
                   'not be loaded. Try modprobe openvswitch.\n' )
1045
            exit( 1 )
1046

    
1047
    def start( self, controllers ):
1048
        "Start up a new IVS switch"
1049
        args = ['ivs']
1050
        args.extend( ['--name', self.name] )
1051
        args.extend( ['--dpid', self.dpid] )
1052
        args.extend( ['--verbose'] )
1053
        for intf in self.intfs.values():
1054
            if not intf.IP():
1055
                args.extend( ['-i', intf.name] )
1056
        for c in controllers:
1057
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1058

    
1059
        with open( '/tmp/ivs.%s.log' % self.name, 'w' ) as logfile:
1060
            with open( '/dev/null', 'w' ) as nullfile:
1061
                self.process = Popen( args, stdout=logfile, stderr=STDOUT,
1062
                                      stdin=nullfile, preexec_fn=os.setsid )
1063
        self.execed = False
1064

    
1065
    def stop( self ):
1066
        "Terminate IVS switch."
1067
        if self.process:
1068
            self.process.terminate()
1069
            self.process.wait()
1070
            self.process = None
1071
        self.deleteIntfs()
1072

    
1073
    def attach( self, intf ):
1074
        "Connect a data port"
1075
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1076

    
1077
    def detach( self, intf ):
1078
        "Disconnect a data port"
1079
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1080

    
1081
    def dpctl( self, *args ):
1082
        "Run dpctl command"
1083
        return "dpctl not supported\n" or args or self # satisfy pylint
1084

    
1085

    
1086
class Controller( Node ):
1087
    """A Controller is a Node that is running (or has execed?) an
1088
       OpenFlow controller."""
1089

    
1090
    def __init__( self, name, inNamespace=False, command='controller',
1091
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1092
                  port=6633, **params ):
1093
        self.command = command
1094
        self.cargs = cargs
1095
        self.cdir = cdir
1096
        self.ip = ip
1097
        self.port = port
1098
        Node.__init__( self, name, inNamespace=inNamespace,
1099
                       ip=ip, **params  )
1100
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1101
        self.checkListening()
1102

    
1103
    def checkListening( self ):
1104
        "Make sure no controllers are running on our port"
1105
        # Verify that Telnet is installed first:
1106
        out, _err, returnCode = errRun( "which telnet" )
1107
        if 'telnet' not in out or returnCode != 0:
1108
            raise Exception( "Error running telnet to check for listening "
1109
                             "controllers; please check that it is "
1110
                             "installed." )
1111
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1112
                              ( self.ip, self.port ) )
1113
        if 'Unable' not in listening:
1114
            servers = self.cmd( 'netstat -atp' ).split( '\n' )
1115
            pstr = ':%d ' % self.port
1116
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1117
            raise Exception( "Please shut down the controller which is"
1118
                             " running on port %d:\n" % self.port +
1119
                             '\n'.join( clist ) )
1120

    
1121
    def start( self ):
1122
        """Start <controller> <args> on controller.
1123
           Log to /tmp/cN.log"""
1124
        pathCheck( self.command )
1125
        cout = '/tmp/' + self.name + '.log'
1126
        if self.cdir is not None:
1127
            self.cmd( 'cd ' + self.cdir )
1128
        self.cmd( self.command + ' ' + self.cargs % self.port +
1129
                  ' 1>' + cout + ' 2>' + cout + '&' )
1130
        self.execed = False
1131

    
1132
    def stop( self ):
1133
        "Stop controller."
1134
        self.cmd( 'kill %' + self.command )
1135
        self.terminate()
1136

    
1137
    def IP( self, intf=None ):
1138
        "Return IP address of the Controller"
1139
        if self.intfs:
1140
            ip = Node.IP( self, intf )
1141
        else:
1142
            ip = self.ip
1143
        return ip
1144

    
1145
    def __repr__( self ):
1146
        "More informative string representation"
1147
        return '<%s %s: %s:%s pid=%s> ' % (
1148
            self.__class__.__name__, self.name,
1149
            self.IP(), self.port, self.pid )
1150

    
1151

    
1152
class OVSController( Controller ):
1153
    "Open vSwitch controller"
1154
    def __init__( self, name, command='ovs-controller', **kwargs ):
1155
        Controller.__init__( self, name, command=command, **kwargs )
1156

    
1157

    
1158
class NOX( Controller ):
1159
    "Controller to run a NOX application."
1160

    
1161
    def __init__( self, name, *noxArgs, **kwargs ):
1162
        """Init.
1163
           name: name to give controller
1164
           noxArgs: arguments (strings) to pass to NOX"""
1165
        if not noxArgs:
1166
            warn( 'warning: no NOX modules specified; '
1167
                  'running packetdump only\n' )
1168
            noxArgs = [ 'packetdump' ]
1169
        elif type( noxArgs ) not in ( list, tuple ):
1170
            noxArgs = [ noxArgs ]
1171

    
1172
        if 'NOX_CORE_DIR' not in os.environ:
1173
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1174
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1175

    
1176
        Controller.__init__( self, name,
1177
                             command=noxCoreDir + '/nox_core',
1178
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1179
                             ' '.join( noxArgs ),
1180
                             cdir=noxCoreDir,
1181
                             **kwargs )
1182

    
1183

    
1184
class RemoteController( Controller ):
1185
    "Controller running outside of Mininet's control."
1186

    
1187
    def __init__( self, name, ip='127.0.0.1',
1188
                  port=6633, **kwargs):
1189
        """Init.
1190
           name: name to give controller
1191
           ip: the IP address where the remote controller is
1192
           listening
1193
           port: the port where the remote controller is listening"""
1194
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1195

    
1196
    def start( self ):
1197
        "Overridden to do nothing."
1198
        return
1199

    
1200
    def stop( self ):
1201
        "Overridden to do nothing."
1202
        return
1203

    
1204
    def checkListening( self ):
1205
        "Warn if remote controller is not accessible"
1206
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1207
                              ( self.ip, self.port ) )
1208
        if 'Unable' in listening:
1209
            warn( "Unable to contact the remote controller"
1210
                  " at %s:%d\n" % ( self.ip, self.port ) )