Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 3df07feb

History | View | Annotate | Download (40.9 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: string, interface name"""
422
        if not intf:
423
            intf = self.defaultIntf()
424
        self.cmd( 'ip route flush root 0/0' )
425
        return self.cmd( 'route add default %s' % intf )
426

    
427
    # Convenience and configuration methods
428

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

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

    
445
    def IP( self, intf=None ):
446
        "Return IP address of a node or specific interface."
447
        return self.intf( intf ).IP()
448

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

    
453
    def intfIsUp( self, intf=None ):
454
        "Check if an interface is up."
455
        return self.intf( intf ).isUp()
456

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

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

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

    
501
    def configDefault( self, **moreParams ):
502
        "Configure with default parameters"
503
        self.params.update( moreParams )
504
        self.config( **self.params )
505

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

    
512
    # Other methods
513

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

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

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

    
529
    def __str__( self ):
530
        "Abbreviated string representation"
531
        return self.name
532

    
533
    # Automatic class setup support
534

    
535
    isSetup = False
536

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

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

    
551

    
552
class Host( Node ):
553
    "A host is simply a Node"
554
    pass
555

    
556

    
557
class CPULimitedHost( Host ):
558

    
559
    "CPU limited host"
560

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

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

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

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

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

    
614
    def cleanup( self ):
615
        "Clean up our cgroup"
616
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
617

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

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

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

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

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

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

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

    
708
    inited = False
709

    
710
    @classmethod
711
    def init( cls ):
712
        "Initialization for CPULimitedHost class"
713
        mountCgroups()
714
        cls.inited = True
715

    
716

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

    
737
class Switch( Node ):
738
    """A Switch is a Node that is running (or has execed?)
739
       an OpenFlow switch."""
740

    
741
    portBase = 1  # Switches start with port 1 in OpenFlow
742
    dpidLen = 16  # digits in dpid passed to switch
743

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

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

    
767
    def defaultIntf( self ):
768
        "Return control interface"
769
        if self.controlIntf:
770
            return self.controlIntf
771
        else:
772
            return Node.defaultIntf( self )
773

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

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

    
791
class UserSwitch( Switch ):
792
    "User-space switch."
793

    
794
    dpidLen = 12
795

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

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

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

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

    
838
    def stop( self ):
839
        "Stop OpenFlow reference user datapath."
840
        self.cmd( 'kill %ofdatapath' )
841
        self.cmd( 'kill %ofprotocol' )
842
        self.deleteIntfs()
843

    
844

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

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

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

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

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

    
895

    
896
class OVSSwitch( Switch ):
897
    "Open vSwitch switch. Depends on ovs-vsctl."
898

    
899
    def __init__( self, name, failMode='secure', **params ):
900
        """Init.
901
           name: name for switch
902
           failMode: controller loss behavior (secure|open)"""
903
        Switch.__init__( self, name, **params )
904
        self.failMode = failMode
905

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

    
926
    def dpctl( self, *args ):
927
        "Run ovs-ofctl command"
928
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
929

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

    
938
    def attach( self, intf ):
939
        "Connect a data port"
940
        self.cmd( 'ovs-vsctl add-port', self, intf )
941
        self.cmd( 'ifconfig', intf, 'up' )
942
        self.TCReapply( intf )
943

    
944
    def detach( self, intf ):
945
        "Disconnect a data port"
946
        self.cmd( 'ovs-vsctl del-port', self, intf )
947

    
948
    def start( self, controllers ):
949
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
950
        if self.inNamespace:
951
            raise Exception(
952
                'OVS kernel switch does not work in a namespace' )
953
        # We should probably call config instead, but this
954
        # requires some rethinking...
955
        self.cmd( 'ifconfig lo up' )
956
        # Annoyingly, --if-exists option seems not to work
957
        self.cmd( 'ovs-vsctl del-br', self )
958
        self.cmd( 'ovs-vsctl add-br', self )
959
        self.cmd( 'ovs-vsctl -- set Bridge', self,
960
                  'other_config:datapath-id=' + self.dpid )
961
        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
962
        for intf in self.intfList():
963
            if not intf.IP():
964
                self.attach( intf )
965
        # Add controllers
966
        clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
967
                            for c in controllers ] )
968
        if self.listenPort:
969
            clist += ' ptcp:%s' % self.listenPort
970
        self.cmd( 'ovs-vsctl set-controller', self, clist )
971
        # Set controllers to reconnect quickly
972
        controllers = self.cmd( 'ovs-vsctl --columns=controller find Bridge '
973
                                'name=' + str(self) ).split(':',2)[-1].strip()
974

    
975
        if controllers.startswith('[') and controllers.endswith(']'):
976
            controllers = [c.strip() for c in controllers[1:-1].split(',')]
977
            for uuid in controllers:
978
                if uuid.count('-') != 4:
979
                    # Doesn't look like a UUID
980
                    continue
981
                uuid = uuid.strip()
982
                self.cmd( 'ovs-vsctl set Controller', uuid,
983
                          'max_backoff=1000' )
984

    
985
    def stop( self ):
986
        "Terminate OVS switch."
987
        self.cmd( 'ovs-vsctl del-br', self )
988
        self.deleteIntfs()
989

    
990
OVSKernelSwitch = OVSSwitch
991

    
992

    
993
class Controller( Node ):
994
    """A Controller is a Node that is running (or has execed?) an
995
       OpenFlow controller."""
996

    
997
    def __init__( self, name, inNamespace=False, command='controller',
998
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
999
                  port=6633, **params ):
1000
        self.command = command
1001
        self.cargs = cargs
1002
        self.cdir = cdir
1003
        self.ip = ip
1004
        self.port = port
1005
        Node.__init__( self, name, inNamespace=inNamespace,
1006
                       ip=ip, **params  )
1007
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1008
        self.checkListening()
1009

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

    
1028
    def start( self ):
1029
        """Start <controller> <args> on controller.
1030
           Log to /tmp/cN.log"""
1031
        pathCheck( self.command )
1032
        cout = '/tmp/' + self.name + '.log'
1033
        if self.cdir is not None:
1034
            self.cmd( 'cd ' + self.cdir )
1035
        self.cmd( self.command + ' ' + self.cargs % self.port +
1036
                  ' 1>' + cout + ' 2>' + cout + '&' )
1037
        self.execed = False
1038

    
1039
    def stop( self ):
1040
        "Stop controller."
1041
        self.cmd( 'kill %' + self.command )
1042
        self.terminate()
1043

    
1044
    def IP( self, intf=None ):
1045
        "Return IP address of the Controller"
1046
        if self.intfs:
1047
            ip = Node.IP( self, intf )
1048
        else:
1049
            ip = self.ip
1050
        return ip
1051

    
1052
    def __repr__( self ):
1053
        "More informative string representation"
1054
        return '<%s %s: %s:%s pid=%s> ' % (
1055
            self.__class__.__name__, self.name,
1056
            self.IP(), self.port, self.pid )
1057

    
1058

    
1059
class OVSController( Controller ):
1060
    "Open vSwitch controller"
1061
    def __init__( self, name, command='ovs-controller', **kwargs ):
1062
        Controller.__init__( self, name, command=command, **kwargs )
1063

    
1064

    
1065
class NOX( Controller ):
1066
    "Controller to run a NOX application."
1067

    
1068
    def __init__( self, name, *noxArgs, **kwargs ):
1069
        """Init.
1070
           name: name to give controller
1071
           noxArgs: arguments (strings) to pass to NOX"""
1072
        if not noxArgs:
1073
            warn( 'warning: no NOX modules specified; '
1074
                  'running packetdump only\n' )
1075
            noxArgs = [ 'packetdump' ]
1076
        elif type( noxArgs ) not in ( list, tuple ):
1077
            noxArgs = [ noxArgs ]
1078

    
1079
        if 'NOX_CORE_DIR' not in os.environ:
1080
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1081
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1082

    
1083
        Controller.__init__( self, name,
1084
                             command=noxCoreDir + '/nox_core',
1085
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1086
                             ' '.join( noxArgs ),
1087
                             cdir=noxCoreDir,
1088
                             **kwargs )
1089

    
1090

    
1091
class RemoteController( Controller ):
1092
    "Controller running outside of Mininet's control."
1093

    
1094
    def __init__( self, name, ip='127.0.0.1',
1095
                  port=6633, **kwargs):
1096
        """Init.
1097
           name: name to give controller
1098
           ip: the IP address where the remote controller is
1099
           listening
1100
           port: the port where the remote controller is listening"""
1101
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1102

    
1103
    def start( self ):
1104
        "Overridden to do nothing."
1105
        return
1106

    
1107
    def stop( self ):
1108
        "Overridden to do nothing."
1109
        return
1110

    
1111
    def checkListening( self ):
1112
        "Warn if remote controller is not accessible"
1113
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1114
                              ( self.ip, self.port ) )
1115
        if 'Unable' in listening:
1116
            warn( "Unable to contact the remote controller"
1117
                  " at %s:%d\n" % ( self.ip, self.port ) )