Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ b9100834

History | View | Annotate | Download (38 KB)

1
"""
2
Node objects for Mininet.
3

4
Nodes provide a simple abstraction for interacting with hosts, switches
5
and controllers. Local nodes are simply one or more processes on the local
6
machine.
7

8
Node: superclass for all (primarily local) network nodes.
9

10
Host: a virtual host. By default, a host is simply a shell; commands
11
    may be sent using Cmd (which waits for output), or using sendCmd(),
12
    which returns immediately, allowing subsequent monitoring using
13
    monitor(). Examples of how to run experiments using this
14
    functionality are provided in the examples/ directory.
15

16
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
17
    RT or CFS bandwidth limiting.
18

19
Switch: superclass for switch nodes.
20

21
UserSwitch: a switch using the user-space switch from the OpenFlow
22
    reference implementation.
23

24
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
25
    implementation.
26

27
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
28
    implementation (openvswitch.org).
29

30
Controller: superclass for OpenFlow controllers. The default controller
31
    is controller(8) from the reference implementation.
32

33
NOXController: a controller node using NOX (noxrepo.org).
34

35
RemoteController: a remote controller node, which may use any
36
    arbitrary OpenFlow-compatible controller, and which is not
37
    created or managed by mininet.
38

39
Future enhancements:
40

41
- Possibly make Node, Switch and Controller more abstract so that
42
  they can be used for both local and remote nodes
43

44
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
45
"""
46

    
47
import os
48
import re
49
import signal
50
import select
51
from subprocess import Popen, PIPE, STDOUT
52

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

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

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

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

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

    
73
        self.name = name
74
        self.inNamespace = inNamespace
75

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

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

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

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

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

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

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

    
108
    # Command support via shell process in namespace
109

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

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

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

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

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

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

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

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

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

    
227
    def sendInt( self, sig=signal.SIGINT ):
228
        "Interrupt running command."
229
        if self.lastPid:
230
            try:
231
                os.kill( self.lastPid, sig )
232
            except OSError:
233
                pass
234

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

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

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

    
280
    def cmdPrint( self, *args):
281
        """Call cmd and printing its output
282
           cmd: string"""
283
        return self.cmd( *args, **{ 'verbose': True } )
284

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

    
314
    def pexec( self, *args, **kwargs ):
315
        """Execute a command using popen
316
           returns: out, err, exitcode"""
317
        popen = self.popen( *args, **kwargs)
318
        out, err = popen.communicate()
319
        exitcode = popen.wait()
320
        return out, err, exitcode
321

    
322
    # Interface management, configuration, and routing
323

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

    
330
    def newPort( self ):
331
        "Return the next port number to allocate."
332
        if len( self.ports ) > 0:
333
            return max( self.ports.values() ) + 1
334
        return self.portBase
335

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

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

    
360
    def intf( self, intf='' ):
361
        """Return our interface object with given name,
362
           or default intf if name is empty"""
363
        if not intf:
364
            return self.defaultIntf()
365
        elif type( intf) is str:
366
            return self.nameToIntf[ intf ]
367
        else:
368
            return None
369

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

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

    
395
    # Routing support
396

    
397
    def setARP( self, ip, mac ):
398
        """Add an ARP entry.
399
           ip: IP address as string
400
           mac: MAC address as string"""
401
        result = self.cmd( 'arp', '-s', ip, mac )
402
        return result
403

    
404
    def setHostRoute( self, ip, intf ):
405
        """Add route to host.
406
           ip: IP address as dotted decimal
407
           intf: string, interface name"""
408
        return self.cmd( 'route add -host', ip, 'dev', intf )
409

    
410
    def setDefaultRoute( self, intf=None ):
411
        """Set the default route to go through intf.
412
           intf: string, interface name"""
413
        if not intf:
414
            intf = self.defaultIntf()
415
        self.cmd( 'ip route flush root 0/0' )
416
        return self.cmd( 'route add default %s' % intf )
417

    
418
    # Convenience and configuration methods
419

    
420
    def setMAC( self, mac, intf=None ):
421
        """Set the MAC address for an interface.
422
           intf: intf or intf name
423
           mac: MAC address as string"""
424
        return self.intf( intf ).setMAC( mac )
425

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

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

    
440
    def MAC( self, intf=None ):
441
        "Return MAC address of a node or specific interface."
442
        return self.intf( intf ).IP()
443

    
444
    def intfIsUp( self, intf=None ):
445
        "Check if an interface is up."
446
        return self.intf( intf ).isUp()
447

    
448
    # The reason why we configure things in this way is so
449
    # That the parameters can be listed and documented in
450
    # the config method.
451
    # Dealing with subclasses and superclasses is slightly
452
    # annoying, but at least the information is there!
453

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

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

    
492
    def configDefault( self, **moreParams ):
493
        "Configure with default parameters"
494
        self.params.update( moreParams )
495
        self.config( **self.params )
496

    
497
    # This is here for backward compatibility
498
    def linkTo( self, node, link=Link ):
499
        """(Deprecated) Link to another node
500
           replace with Link( node1, node2)"""
501
        return link( self, node )
502

    
503
    # Other methods
504

    
505
    def intfList( self ):
506
        "List of our interfaces sorted by port number"
507
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
508

    
509
    def intfNames( self ):
510
        "The names of our interfaces sorted by port number"
511
        return [ str( i ) for i in self.intfList() ]
512

    
513
    def __repr__( self ):
514
        "More informative string representation"
515
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
516
                        for i in self.intfList() ] ) )
517
        return '<%s %s: %s pid=%s> ' % (
518
                self.__class__.__name__, self.name, intfs, self.pid )
519

    
520
    def __str__( self ):
521
        "Abbreviated string representation"
522
        return self.name
523

    
524
    # Automatic class setup support
525

    
526
    isSetup = False
527

    
528
    @classmethod
529
    def checkSetup( cls ):
530
        "Make sure our class and superclasses are set up"
531
        while cls and not getattr( cls, 'isSetup', True ):
532
            cls.setup()
533
            cls.isSetup = True
534
            # Make pylint happy
535
            cls = getattr( type( cls ), '__base__', None )
536

    
537
    @classmethod
538
    def setup( cls ):
539
        "Make sure our class dependencies are available"
540
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
541

    
542

    
543
class Host( Node ):
544
    "A host is simply a Node"
545
    pass
546

    
547

    
548
class CPULimitedHost( Host ):
549

    
550
    "CPU limited host"
551

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

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

    
582
    def cgroupGet( self, param, resource='cpu' ):
583
        "Return value of cgroup parameter"
584
        cmd = 'cgget -r %s.%s /%s' % (
585
            resource, param, self.name )
586
        return int( quietRun( cmd ).split()[ -1 ] )
587

    
588
    def cgroupDel( self ):
589
        "Clean up our cgroup"
590
        # info( '*** deleting cgroup', self.cgroup, '\n' )
591
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
592
        return exitcode != 0
593

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

    
605
    def cleanup( self ):
606
        "Clean up our cgroup"
607
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
608

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

    
619
    def rtInfo( self, f ):
620
        "Internal method: return parameters for RT bandwidth"
621
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
622
        # RT uses wall clock time for period and quota
623
        quota = int( self.period_us * f * numCores() )
624
        return pstr, qstr, self.period_us, quota
625

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

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

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

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

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

    
699
    inited = False
700

    
701
    @classmethod
702
    def init( cls ):
703
        "Initialization for CPULimitedHost class"
704
        mountCgroups()
705
        cls.inited = True
706

    
707

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

    
728
class Switch( Node ):
729
    """A Switch is a Node that is running (or has execed?)
730
       an OpenFlow switch."""
731

    
732
    portBase = 1  # Switches start with port 1 in OpenFlow
733
    dpidLen = 16  # digits in dpid passed to switch
734

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

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

    
758
    def defaultIntf( self ):
759
        "Return control interface"
760
        if self.controlIntf:
761
            return self.controlIntf
762
        else:
763
            return Node.defaultIntf( self )
764

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

    
775
    def __repr__( self ):
776
        "More informative string representation"
777
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
778
                        for i in self.intfList() ] ) )
779
        return '<%s %s: %s pid=%s> ' % (
780
                self.__class__.__name__, self.name, intfs, self.pid )
781

    
782
class UserSwitch( Switch ):
783
    "User-space switch."
784

    
785
    dpidLen = 12
786

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

    
796
    @classmethod
797
    def setup( cls ):
798
        "Ensure any dependencies are loaded; if not, try to load them."
799
        if not os.path.exists( '/dev/net/tun' ):
800
            moduleDeps( add=TUN )
801

    
802
    def dpctl( self, *args ):
803
        "Run dpctl command"
804
        if not self.listenPort:
805
            return "can't run dpctl without passive listening port"
806
        return self.cmd( 'dpctl ' + ' '.join( args ) +
807
                         ' tcp:127.0.0.1:%i' % self.listenPort )
808

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

    
827
    def stop( self ):
828
        "Stop OpenFlow reference user datapath."
829
        self.cmd( 'kill %ofdatapath' )
830
        self.cmd( 'kill %ofprotocol' )
831
        self.deleteIntfs()
832

    
833

    
834
class OVSLegacyKernelSwitch( Switch ):
835
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
836
       Currently only works in the root namespace."""
837

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

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

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

    
877
    def stop( self ):
878
        "Terminate kernel datapath."
879
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
880
        self.cmd( 'kill %ovs-openflowd' )
881
        self.deleteIntfs()
882

    
883

    
884
class OVSSwitch( Switch ):
885
    "Open vSwitch switch. Depends on ovs-vsctl."
886

    
887
    def __init__( self, name, failMode='secure', **params ):
888
        """Init.
889
           name: name for switch
890
           failMode: controller loss behavior (secure|open)"""
891
        Switch.__init__( self, name, **params )
892
        self.failMode = failMode
893

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

    
912
    def dpctl( self, *args ):
913
        "Run ovs-dpctl command"
914
        return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
915

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

    
924
    def attach( self, intf ):
925
        "Connect a data port"
926
        self.cmd( 'ovs-vsctl add-port', self, intf )
927
        self.cmd( 'ifconfig', intf, 'up' )
928
        self.TCReapply( intf )
929

    
930
    def detach( self, intf ):
931
        "Disconnect a data port"
932
        self.cmd( 'ovs-vsctl del-port', self, intf )
933

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

    
956
    def stop( self ):
957
        "Terminate OVS switch."
958
        self.cmd( 'ovs-vsctl del-br', self )
959
        self.deleteIntfs()
960

    
961
OVSKernelSwitch = OVSSwitch
962

    
963

    
964
class Controller( Node ):
965
    """A Controller is a Node that is running (or has execed?) an
966
       OpenFlow controller."""
967

    
968
    def __init__( self, name, inNamespace=False, command='controller',
969
                 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
970
                 port=6633, **params ):
971
        self.command = command
972
        self.cargs = cargs
973
        self.cdir = cdir
974
        self.ip = ip
975
        self.port = port
976
        Node.__init__( self, name, inNamespace=inNamespace,
977
            ip=ip, **params  )
978

    
979
    def start( self ):
980
        """Start <controller> <args> on controller.
981
           Log to /tmp/cN.log"""
982
        pathCheck( self.command )
983
        cout = '/tmp/' + self.name + '.log'
984
        if self.cdir is not None:
985
            self.cmd( 'cd ' + self.cdir )
986
        self.cmd( self.command + ' ' + self.cargs % self.port +
987
            ' 1>' + cout + ' 2>' + cout + '&' )
988
        self.execed = False
989

    
990
    def stop( self ):
991
        "Stop controller."
992
        self.cmd( 'kill %' + self.command )
993
        self.terminate()
994

    
995
    def IP( self, intf=None ):
996
        "Return IP address of the Controller"
997
        if self.intfs:
998
            ip = Node.IP( self, intf )
999
        else:
1000
            ip = self.ip
1001
        return ip
1002

    
1003
    def __repr__( self ):
1004
        "More informative string representation"
1005
        return '<%s %s: %s:%s pid=%s> ' % (
1006
                self.__class__.__name__, self.name,
1007
                self.IP(), self.port, self.pid )
1008

    
1009

    
1010
class OVSController( Controller ):
1011
    "Open vSwitch controller"
1012
    def __init__( self, name, command='ovs-controller', **kwargs ):
1013
        Controller.__init__( self, name, command=command, **kwargs )
1014

    
1015

    
1016
class NOX( Controller ):
1017
    "Controller to run a NOX application."
1018

    
1019
    def __init__( self, name, *noxArgs, **kwargs ):
1020
        """Init.
1021
           name: name to give controller
1022
           noxArgs: arguments (strings) to pass to NOX"""
1023
        if not noxArgs:
1024
            warn( 'warning: no NOX modules specified; '
1025
                  'running packetdump only\n' )
1026
            noxArgs = [ 'packetdump' ]
1027
        elif type( noxArgs ) not in ( list, tuple ):
1028
            noxArgs = [ noxArgs ]
1029

    
1030
        if 'NOX_CORE_DIR' not in os.environ:
1031
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1032
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1033

    
1034
        Controller.__init__( self, name,
1035
            command=noxCoreDir + '/nox_core',
1036
            cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1037
                    ' '.join( noxArgs ),
1038
            cdir=noxCoreDir, **kwargs )
1039

    
1040

    
1041
class RemoteController( Controller ):
1042
    "Controller running outside of Mininet's control."
1043

    
1044
    def __init__( self, name, ip='127.0.0.1',
1045
                 port=6633, **kwargs):
1046
        """Init.
1047
           name: name to give controller
1048
           defaultIP: the IP address where the remote controller is
1049
           listening
1050
           port: the port where the remote controller is listening"""
1051
        Controller.__init__( self, name, ip=ip, port=port,
1052
            **kwargs )
1053

    
1054
    def start( self ):
1055
        "Overridden to do nothing."
1056
        return
1057

    
1058
    def stop( self ):
1059
        "Overridden to do nothing."
1060
        return