Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ bdd43bea

History | View | Annotate | Download (41.3 KB)

1
"""
2
Node objects for Mininet.
3

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

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

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

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

19
Switch: superclass for switch nodes.
20

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

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

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

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

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

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

39
Future enhancements:
40

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

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

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

    
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
        # -s: pass $* to shell, and make process easy to find in ps
122
        cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
123
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
124
                            close_fds=True )
125
        self.stdin = self.shell.stdin
126
        self.stdout = self.shell.stdout
127
        self.pid = self.shell.pid
128
        self.pollOut = select.poll()
129
        self.pollOut.register( self.stdout )
130
        # Maintain mapping between file descriptors and nodes
131
        # This is useful for monitoring multiple nodes
132
        # using select.poll()
133
        self.outToNode[ self.stdout.fileno() ] = self
134
        self.inToNode[ self.stdin.fileno() ] = self
135
        self.execed = False
136
        self.lastCmd = None
137
        self.lastPid = None
138
        self.readbuf = ''
139
        self.waiting = False
140

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

    
149
    # Subshell I/O, commands and control
150

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
325
    # Interface management, configuration, and routing
326

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

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

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

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

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

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

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

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

    
404
    # Routing support
405

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

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

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

    
430
    # Convenience and configuration methods
431

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

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

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

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

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

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

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

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

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

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

    
515
    # Other methods
516

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

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

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

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

    
536
    # Automatic class setup support
537

    
538
    isSetup = False
539

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

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

    
554

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

    
559

    
560
class CPULimitedHost( Host ):
561

    
562
    "CPU limited host"
563

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

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

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

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

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

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

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

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

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

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

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

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

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

    
711
    inited = False
712

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

    
719

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

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

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

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

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

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

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

    
787
    def __repr__( self ):
788
        "More informative string representation"
789
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
790
                              for i in self.intfList() ] ) )
791
        return '<%s %s: %s pid=%s> ' % (
792
            self.__class__.__name__, self.name, intfs, self.pid )
793

    
794
class UserSwitch( Switch ):
795
    "User-space switch."
796

    
797
    dpidLen = 12
798

    
799
    def __init__( self, name, **kwargs ):
800
        """Init.
801
           name: name for the switch"""
802
        Switch.__init__( self, name, **kwargs )
803
        pathCheck( 'ofdatapath', 'ofprotocol',
804
                   moduleName='the OpenFlow reference user switch' +
805
                              '(openflow.org)' )
806
        if self.listenPort:
807
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
808

    
809
    @classmethod
810
    def setup( cls ):
811
        "Ensure any dependencies are loaded; if not, try to load them."
812
        if not os.path.exists( '/dev/net/tun' ):
813
            moduleDeps( add=TUN )
814

    
815
    def dpctl( self, *args ):
816
        "Run dpctl command"
817
        if not self.listenPort:
818
            return "can't run dpctl without passive listening port"
819
        return self.cmd( 'dpctl ' + ' '.join( args ) +
820
                         ' tcp:127.0.0.1:%i' % self.listenPort )
821

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

    
841
    def stop( self ):
842
        "Stop OpenFlow reference user datapath."
843
        self.cmd( 'kill %ofdatapath' )
844
        self.cmd( 'kill %ofprotocol' )
845
        self.deleteIntfs()
846

    
847

    
848
class OVSLegacyKernelSwitch( Switch ):
849
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
850
       Currently only works in the root namespace."""
851

    
852
    def __init__( self, name, dp=None, **kwargs ):
853
        """Init.
854
           name: name for switch
855
           dp: netlink id (0, 1, 2, ...)
856
           defaultMAC: default MAC as unsigned int; random value if None"""
857
        Switch.__init__( self, name, **kwargs )
858
        self.dp = dp if dp else self.name
859
        self.intf = self.dp
860
        if self.inNamespace:
861
            error( "OVSKernelSwitch currently only works"
862
                   " in the root namespace.\n" )
863
            exit( 1 )
864

    
865
    @classmethod
866
    def setup( cls ):
867
        "Ensure any dependencies are loaded; if not, try to load them."
868
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
869
                   moduleName='Open vSwitch (openvswitch.org)')
870
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
871

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

    
892
    def stop( self ):
893
        "Terminate kernel datapath."
894
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
895
        self.cmd( 'kill %ovs-openflowd' )
896
        self.deleteIntfs()
897

    
898

    
899
class OVSSwitch( Switch ):
900
    "Open vSwitch switch. Depends on ovs-vsctl."
901

    
902
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
903
        """Init.
904
           name: name for switch
905
           failMode: controller loss behavior (secure|open)
906
           datapath: userspace or kernel mode (kernel|user)"""
907
        Switch.__init__( self, name, **params )
908
        self.failMode = failMode
909
        self.datapath = datapath
910

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

    
931
    def dpctl( self, *args ):
932
        "Run ovs-ofctl command"
933
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
934

    
935
    @staticmethod
936
    def TCReapply( intf ):
937
        """Unfortunately OVS and Mininet are fighting
938
           over tc queuing disciplines. As a quick hack/
939
           workaround, we clear OVS's and reapply our own."""
940
        if type( intf ) is TCIntf:
941
            intf.config( **intf.params )
942

    
943
    def attach( self, intf ):
944
        "Connect a data port"
945
        self.cmd( 'ovs-vsctl add-port', self, intf )
946
        self.cmd( 'ifconfig', intf, 'up' )
947
        self.TCReapply( intf )
948

    
949
    def detach( self, intf ):
950
        "Disconnect a data port"
951
        self.cmd( 'ovs-vsctl del-port', self, intf )
952

    
953
    def start( self, controllers ):
954
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
955
        if self.inNamespace:
956
            raise Exception(
957
                'OVS kernel switch does not work in a namespace' )
958
        # We should probably call config instead, but this
959
        # requires some rethinking...
960
        self.cmd( 'ifconfig lo up' )
961
        # Annoyingly, --if-exists option seems not to work
962
        self.cmd( 'ovs-vsctl del-br', self )
963
        self.cmd( 'ovs-vsctl add-br', self )
964
        if self.datapath == 'user':
965
            self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' )
966
        self.cmd( 'ovs-vsctl -- set Bridge', self,
967
                  'other_config:datapath-id=' + self.dpid )
968
        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
969
        for intf in self.intfList():
970
            if not intf.IP():
971
                self.attach( intf )
972
        # Add controllers
973
        clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
974
                            for c in controllers ] )
975
        if self.listenPort:
976
            clist += ' ptcp:%s' % self.listenPort
977
        self.cmd( 'ovs-vsctl set-controller', self, clist )
978
        # Set controllers to reconnect quickly
979
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
980
                               'Controller' ).strip()
981
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
982
            controllers = controllers[ 1 : -1 ]
983
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
984
            for uuid in uuids:
985
                if uuid.count('-') != 4:
986
                    # Doesn't look like a UUID
987
                    continue
988
                uuid = uuid.strip()
989
                self.cmd( 'ovs-vsctl set Controller', uuid,
990
                          'max_backoff=1000' )
991

    
992
    def stop( self ):
993
        "Terminate OVS switch."
994
        self.cmd( 'ovs-vsctl del-br', self )
995
        self.deleteIntfs()
996

    
997
OVSKernelSwitch = OVSSwitch
998

    
999

    
1000
class Controller( Node ):
1001
    """A Controller is a Node that is running (or has execed?) an
1002
       OpenFlow controller."""
1003

    
1004
    def __init__( self, name, inNamespace=False, command='controller',
1005
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1006
                  port=6633, **params ):
1007
        self.command = command
1008
        self.cargs = cargs
1009
        self.cdir = cdir
1010
        self.ip = ip
1011
        self.port = port
1012
        Node.__init__( self, name, inNamespace=inNamespace,
1013
                       ip=ip, **params  )
1014
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1015
        self.checkListening()
1016

    
1017
    def checkListening( self ):
1018
        "Make sure no controllers are running on our port"
1019
        # Verify that Telnet is installed first:
1020
        out, _err, returnCode = errRun( "which telnet" )
1021
        if 'telnet' not in out or returnCode != 0:
1022
            raise Exception( "Error running telnet to check for listening "
1023
                             "controllers; please check that it is "
1024
                             "installed." )
1025
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1026
                              ( self.ip, self.port ) )
1027
        if 'Unable' not in listening:
1028
            servers = self.cmd( 'netstat -atp' ).split( '\n' )
1029
            pstr = ':%d ' % self.port
1030
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1031
            raise Exception( "Please shut down the controller which is"
1032
                             " running on port %d:\n" % self.port +
1033
                             '\n'.join( clist ) )
1034

    
1035
    def start( self ):
1036
        """Start <controller> <args> on controller.
1037
           Log to /tmp/cN.log"""
1038
        pathCheck( self.command )
1039
        cout = '/tmp/' + self.name + '.log'
1040
        if self.cdir is not None:
1041
            self.cmd( 'cd ' + self.cdir )
1042
        self.cmd( self.command + ' ' + self.cargs % self.port +
1043
                  ' 1>' + cout + ' 2>' + cout + '&' )
1044
        self.execed = False
1045

    
1046
    def stop( self ):
1047
        "Stop controller."
1048
        self.cmd( 'kill %' + self.command )
1049
        self.terminate()
1050

    
1051
    def IP( self, intf=None ):
1052
        "Return IP address of the Controller"
1053
        if self.intfs:
1054
            ip = Node.IP( self, intf )
1055
        else:
1056
            ip = self.ip
1057
        return ip
1058

    
1059
    def __repr__( self ):
1060
        "More informative string representation"
1061
        return '<%s %s: %s:%s pid=%s> ' % (
1062
            self.__class__.__name__, self.name,
1063
            self.IP(), self.port, self.pid )
1064

    
1065

    
1066
class OVSController( Controller ):
1067
    "Open vSwitch controller"
1068
    def __init__( self, name, command='ovs-controller', **kwargs ):
1069
        Controller.__init__( self, name, command=command, **kwargs )
1070

    
1071

    
1072
class NOX( Controller ):
1073
    "Controller to run a NOX application."
1074

    
1075
    def __init__( self, name, *noxArgs, **kwargs ):
1076
        """Init.
1077
           name: name to give controller
1078
           noxArgs: arguments (strings) to pass to NOX"""
1079
        if not noxArgs:
1080
            warn( 'warning: no NOX modules specified; '
1081
                  'running packetdump only\n' )
1082
            noxArgs = [ 'packetdump' ]
1083
        elif type( noxArgs ) not in ( list, tuple ):
1084
            noxArgs = [ noxArgs ]
1085

    
1086
        if 'NOX_CORE_DIR' not in os.environ:
1087
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1088
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1089

    
1090
        Controller.__init__( self, name,
1091
                             command=noxCoreDir + '/nox_core',
1092
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1093
                             ' '.join( noxArgs ),
1094
                             cdir=noxCoreDir,
1095
                             **kwargs )
1096

    
1097

    
1098
class RemoteController( Controller ):
1099
    "Controller running outside of Mininet's control."
1100

    
1101
    def __init__( self, name, ip='127.0.0.1',
1102
                  port=6633, **kwargs):
1103
        """Init.
1104
           name: name to give controller
1105
           ip: the IP address where the remote controller is
1106
           listening
1107
           port: the port where the remote controller is listening"""
1108
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1109

    
1110
    def start( self ):
1111
        "Overridden to do nothing."
1112
        return
1113

    
1114
    def stop( self ):
1115
        "Overridden to do nothing."
1116
        return
1117

    
1118
    def checkListening( self ):
1119
        "Warn if remote controller is not accessible"
1120
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1121
                              ( self.ip, self.port ) )
1122
        if 'Unable' in listening:
1123
            warn( "Unable to contact the remote controller"
1124
                  " at %s:%d\n" % ( self.ip, self.port ) )