Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 4316be95

History | View | Annotate | Download (29.2 KB)

1
"""
2

3
    Mininet: A simple networking testbed for OpenFlow/SDN!
4

5
author: Bob Lantz (rlantz@cs.stanford.edu)
6
author: Brandon Heller (brandonh@stanford.edu)
7

8
Mininet creates scalable OpenFlow test networks by using
9
process-based virtualization and network namespaces.
10

11
Simulated hosts are created as processes in separate network
12
namespaces. This allows a complete OpenFlow network to be simulated on
13
top of a single Linux kernel.
14

15
Each host has:
16

17
A virtual console (pipes to a shell)
18
A virtual interfaces (half of a veth pair)
19
A parent shell (and possibly some child processes) in a namespace
20

21
Hosts have a network interface which is configured via ifconfig/ip
22
link/etc.
23

24
This version supports both the kernel and user space datapaths
25
from the OpenFlow reference implementation (openflowswitch.org)
26
as well as OpenVSwitch (openvswitch.org.)
27

28
In kernel datapath mode, the controller and switches are simply
29
processes in the root namespace.
30

31
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32
attached to the one side of a veth pair; the other side resides in the
33
host namespace. In this mode, switch processes can simply connect to the
34
controller via the loopback interface.
35

36
In user datapath mode, the controller and switches can be full-service
37
nodes that live in their own network namespaces and have management
38
interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39
currently routed although it could be bridged.)
40

41
In addition to a management interface, user mode switches also have
42
several switch interfaces, halves of veth pairs whose other halves
43
reside in the host nodes that the switches are connected to.
44

45
Consistent, straightforward naming is important in order to easily
46
identify hosts, switches and controllers, both from the CLI and
47
from program code. Interfaces are named to make it easy to identify
48
which interfaces belong to which node.
49

50
The basic naming scheme is as follows:
51

52
    Host nodes are named h1-hN
53
    Switch nodes are named s1-sN
54
    Controller nodes are named c0-cN
55
    Interfaces are named {nodename}-eth0 .. {nodename}-ethN
56

57
Note: If the network topology is created using mininet.topo, then
58
node numbers are unique among hosts and switches (e.g. we have
59
h1..hN and SN..SN+M) and also correspond to their default IP addresses
60
of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61
hN. This mapping allows easy determination of a node's IP
62
address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63

64
Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
65
"ping 10.1" is equivalent to "ping 10.0.0.1".
66

67
Currently we wrap the entire network in a 'mininet' object, which
68
constructs a simulated network based on a network topology created
69
using a topology object (e.g. LinearTopo) from mininet.topo or
70
mininet.topolib, and a Controller which the switches will connect
71
to. Several configuration options are provided for functions such as
72
automatically setting MAC addresses, populating the ARP table, or
73
even running a set of terminals to allow direct interaction with nodes.
74

75
After the network is created, it can be started using start(), and a
76
variety of useful tasks maybe performed, including basic connectivity
77
and bandwidth tests and running the mininet CLI.
78

79
Once the network is up and running, test code can easily get access
80
to host and switch objects which can then be used for arbitrary
81
experiments, typically involving running a series of commands on the
82
hosts.
83

84
After all desired tests or activities have been completed, the stop()
85
method may be called to shut down the network.
86

87
"""
88

    
89
import os
90
import re
91
import select
92
import signal
93
from time import sleep
94
from itertools import chain
95

    
96
from mininet.cli import CLI
97
from mininet.log import info, error, debug, output
98
from mininet.node import Host, OVSKernelSwitch, Controller
99
from mininet.link import Link, Intf
100
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
101
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
102
from mininet.term import cleanUpScreens, makeTerms
103

    
104
# Mininet version: should be consistent with README and LICENSE
105
VERSION = "2.0.0"
106

    
107
class Mininet( object ):
108
    "Network emulation with hosts spawned in network namespaces."
109

    
110
    def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
111
                  controller=Controller, link=Link, intf=Intf,
112
                  build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
113
                  inNamespace=False,
114
                  autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
115
                  listenPort=None ):
116
        """Create Mininet object.
117
           topo: Topo (topology) object or None
118
           switch: default Switch class
119
           host: default Host class/constructor
120
           controller: default Controller class/constructor
121
           link: default Link class/constructor
122
           intf: default Intf class/constructor
123
           ipBase: base IP address for hosts,
124
           build: build now from topo?
125
           xterms: if build now, spawn xterms?
126
           cleanup: if build now, cleanup before creating?
127
           inNamespace: spawn switches and controller in net namespaces?
128
           autoSetMacs: set MAC addrs automatically like IP addresses?
129
           autoStaticArp: set all-pairs static MAC addrs?
130
           autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
131
           listenPort: base listening port to open; will be incremented for
132
               each additional switch in the net if inNamespace=False"""
133
        self.topo = topo
134
        self.switch = switch
135
        self.host = host
136
        self.controller = controller
137
        self.link = link
138
        self.intf = intf
139
        self.ipBase = ipBase
140
        self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
141
        self.nextIP = 1  # start for address allocation
142
        self.inNamespace = inNamespace
143
        self.xterms = xterms
144
        self.cleanup = cleanup
145
        self.autoSetMacs = autoSetMacs
146
        self.autoStaticArp = autoStaticArp
147
        self.autoPinCpus = autoPinCpus
148
        self.numCores = numCores()
149
        self.nextCore = 0  # next core for pinning hosts to CPUs
150
        self.listenPort = listenPort
151

    
152
        self.hosts = []
153
        self.switches = []
154
        self.controllers = []
155

    
156
        self.nameToNode = {}  # name to Node (Host/Switch) objects
157

    
158
        self.terms = []  # list of spawned xterm processes
159

    
160
        Mininet.init()  # Initialize Mininet if necessary
161

    
162
        self.built = False
163
        if topo and build:
164
            self.build()
165

    
166
    def addHost( self, name, cls=None, **params ):
167
        """Add host.
168
           name: name of host to add
169
           cls: custom host class/constructor (optional)
170
           params: parameters for host
171
           returns: added host"""
172
        # Default IP and MAC addresses
173
        defaults = { 'ip': ipAdd( self.nextIP,
174
                                  ipBaseNum=self.ipBaseNum,
175
                                  prefixLen=self.prefixLen ) +
176
                                  '/%s' % self.prefixLen }
177
        if self.autoSetMacs:
178
            defaults[ 'mac'] = macColonHex( self.nextIP )
179
        if self.autoPinCpus:
180
            defaults[ 'cores' ] = self.nextCore
181
            self.nextCore = ( self.nextCore + 1 ) % self.numCores
182
        self.nextIP += 1
183
        defaults.update( params )
184
        if not cls:
185
            cls = self.host
186
        h = cls( name, **defaults )
187
        self.hosts.append( h )
188
        self.nameToNode[ name ] = h
189
        return h
190

    
191
    def addSwitch( self, name, cls=None, **params ):
192
        """Add switch.
193
           name: name of switch to add
194
           cls: custom switch class/constructor (optional)
195
           returns: added switch
196
           side effect: increments listenPort ivar ."""
197
        defaults = { 'listenPort': self.listenPort,
198
                     'inNamespace': self.inNamespace }
199
        defaults.update( params )
200
        if not cls:
201
            cls = self.switch
202
        sw = cls( name, **defaults )
203
        if not self.inNamespace and self.listenPort:
204
            self.listenPort += 1
205
        self.switches.append( sw )
206
        self.nameToNode[ name ] = sw
207
        return sw
208

    
209
    def addController( self, name='c0', controller=None, **params ):
210
        """Add controller.
211
           controller: Controller class"""
212
        if not controller:
213
            controller = self.controller
214
        controller_new = controller( name, **params )
215
        if controller_new:  # allow controller-less setups
216
            self.controllers.append( controller_new )
217
            self.nameToNode[ name ] = controller_new
218
        return controller_new
219

    
220
    # BL: We now have four ways to look up nodes
221
    # This may (should?) be cleaned up in the future.
222
    def getNodeByName( self, *args ):
223
        "Return node(s) with given name(s)"
224
        if len( args ) == 1:
225
            return self.nameToNode[ args[ 0 ] ]
226
        return [ self.nameToNode[ n ] for n in args ]
227

    
228
    def get( self, *args ):
229
        "Convenience alias for getNodeByName"
230
        return self.getNodeByName( *args )
231

    
232
    # Even more convenient syntax for node lookup and iteration
233
    def __getitem__( self, *args ):
234
        """net [ name ] operator: Return node(s) with given name(s)"""
235
        return self.getNodeByName( *args )
236

    
237
    def __iter__( self ):
238
        "return iterator over nodes"
239
        return chain( self.hosts, self.switches, self.controllers )
240

    
241
    def addLink( self, node1, node2, port1=None, port2=None,
242
                 cls=None, **params ):
243
        """"Add a link from node1 to node2
244
            node1: source node
245
            node2: dest node
246
            port1: source port
247
            port2: dest port
248
            returns: link object"""
249
        defaults = { 'port1': port1,
250
                     'port2': port2,
251
                     'intf': self.intf }
252
        defaults.update( params )
253
        if not cls:
254
            cls = self.link
255
        return cls( node1, node2, **defaults )
256

    
257
    def configHosts( self ):
258
        "Configure a set of hosts."
259
        for host in self.hosts:
260
            info( host.name + ' ' )
261
            intf = host.defaultIntf()
262
            if intf:
263
                host.configDefault()
264
            else:
265
                # Don't configure nonexistent intf
266
                host.configDefault( ip=None, mac=None )
267
            # You're low priority, dude!
268
            # BL: do we want to do this here or not?
269
            # May not make sense if we have CPU lmiting...
270
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
271
            # This may not be the right place to do this, but
272
            # it needs to be done somewhere.
273
            host.cmd( 'ifconfig lo up' )
274
        info( '\n' )
275

    
276
    def buildFromTopo( self, topo=None ):
277
        """Build mininet from a topology object
278
           At the end of this function, everything should be connected
279
           and up."""
280

    
281
        # Possibly we should clean up here and/or validate
282
        # the topo
283
        if self.cleanup:
284
            pass
285

    
286
        info( '*** Creating network\n' )
287

    
288
        if not self.controllers:
289
            # Add a default controller
290
            info( '*** Adding controller\n' )
291
            classes = self.controller
292
            if type( classes ) is not list:
293
                classes = [ classes ]
294
            for i, cls in enumerate( classes ):
295
                self.addController( 'c%d' % i, cls )
296

    
297
        info( '*** Adding hosts:\n' )
298
        for hostName in topo.hosts():
299
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
300
            info( hostName + ' ' )
301

    
302
        info( '\n*** Adding switches:\n' )
303
        for switchName in topo.switches():
304
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
305
            info( switchName + ' ' )
306

    
307
        info( '\n*** Adding links:\n' )
308
        for srcName, dstName in topo.links(sort=True):
309
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
310
            params = topo.linkInfo( srcName, dstName )
311
            srcPort, dstPort = topo.port( srcName, dstName )
312
            self.addLink( src, dst, srcPort, dstPort, **params )
313
            info( '(%s, %s) ' % ( src.name, dst.name ) )
314

    
315
        info( '\n' )
316

    
317
    def configureControlNetwork( self ):
318
        "Control net config hook: override in subclass"
319
        raise Exception( 'configureControlNetwork: '
320
                         'should be overriden in subclass', self )
321

    
322
    def build( self ):
323
        "Build mininet."
324
        if self.topo:
325
            self.buildFromTopo( self.topo )
326
        if ( self.inNamespace ):
327
            self.configureControlNetwork()
328
        info( '*** Configuring hosts\n' )
329
        self.configHosts()
330
        if self.xterms:
331
            self.startTerms()
332
        if self.autoStaticArp:
333
            self.staticArp()
334
        self.built = True
335

    
336
    def startTerms( self ):
337
        "Start a terminal for each node."
338
        if 'DISPLAY' not in os.environ:
339
            error( "Error starting terms: Cannot connect to display\n" )
340
            return
341
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
342
        cleanUpScreens()
343
        self.terms += makeTerms( self.controllers, 'controller' )
344
        self.terms += makeTerms( self.switches, 'switch' )
345
        self.terms += makeTerms( self.hosts, 'host' )
346

    
347
    def stopXterms( self ):
348
        "Kill each xterm."
349
        for term in self.terms:
350
            os.kill( term.pid, signal.SIGKILL )
351
        cleanUpScreens()
352

    
353
    def staticArp( self ):
354
        "Add all-pairs ARP entries to remove the need to handle broadcast."
355
        for src in self.hosts:
356
            for dst in self.hosts:
357
                if src != dst:
358
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
359

    
360
    def start( self ):
361
        "Start controller and switches."
362
        if not self.built:
363
            self.build()
364
        info( '*** Starting controller\n' )
365
        for controller in self.controllers:
366
            controller.start()
367
        info( '*** Starting %s switches\n' % len( self.switches ) )
368
        for switch in self.switches:
369
            info( switch.name + ' ')
370
            switch.start( self.controllers )
371
        info( '\n' )
372

    
373
    def stop( self ):
374
        "Stop the controller(s), switches and hosts"
375
        if self.terms:
376
            info( '*** Stopping %i terms\n' % len( self.terms ) )
377
            self.stopXterms()
378
        info( '*** Stopping %i switches\n' % len( self.switches ) )
379
        for switch in self.switches:
380
            info( switch.name + ' ' )
381
            switch.stop()
382
        info( '\n' )
383
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
384
        for host in self.hosts:
385
            info( host.name + ' ' )
386
            host.terminate()
387
        info( '\n' )
388
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
389
        for controller in self.controllers:
390
            info( controller.name + ' ' )
391
            controller.stop()
392
        info( '\n*** Done\n' )
393

    
394
    def run( self, test, *args, **kwargs ):
395
        "Perform a complete start/test/stop cycle."
396
        self.start()
397
        info( '*** Running test\n' )
398
        result = test( *args, **kwargs )
399
        self.stop()
400
        return result
401

    
402
    def monitor( self, hosts=None, timeoutms=-1 ):
403
        """Monitor a set of hosts (or all hosts by default),
404
           and return their output, a line at a time.
405
           hosts: (optional) set of hosts to monitor
406
           timeoutms: (optional) timeout value in ms
407
           returns: iterator which returns host, line"""
408
        if hosts is None:
409
            hosts = self.hosts
410
        poller = select.poll()
411
        Node = hosts[ 0 ]  # so we can call class method fdToNode
412
        for host in hosts:
413
            poller.register( host.stdout )
414
        while True:
415
            ready = poller.poll( timeoutms )
416
            for fd, event in ready:
417
                host = Node.fdToNode( fd )
418
                if event & select.POLLIN:
419
                    line = host.readline()
420
                    if line is not None:
421
                        yield host, line
422
            # Return if non-blocking
423
            if not ready and timeoutms >= 0:
424
                yield None, None
425

    
426
    # XXX These test methods should be moved out of this class.
427
    # Probably we should create a tests.py for them
428

    
429
    @staticmethod
430
    def _parsePing( pingOutput ):
431
        "Parse ping output and return packets sent, received."
432
        # Check for downed link
433
        if 'connect: Network is unreachable' in pingOutput:
434
            return (1, 0)
435
        r = r'(\d+) packets transmitted, (\d+) received'
436
        m = re.search( r, pingOutput )
437
        if m is None:
438
            error( '*** Error: could not parse ping output: %s\n' %
439
                   pingOutput )
440
            return (1, 0)
441
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
442
        return sent, received
443

    
444
    def ping( self, hosts=None, timeout=None ):
445
        """Ping between all specified hosts.
446
           hosts: list of hosts
447
           timeout: time to wait for a response, as string
448
           returns: ploss packet loss percentage"""
449
        # should we check if running?
450
        packets = 0
451
        lost = 0
452
        ploss = None
453
        if not hosts:
454
            hosts = self.hosts
455
            output( '*** Ping: testing ping reachability\n' )
456
        for node in hosts:
457
            output( '%s -> ' % node.name )
458
            for dest in hosts:
459
                if node != dest:
460
                    opts = ''
461
                    if timeout:
462
                        opts = '-W %s' % timeout
463
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
464
                    sent, received = self._parsePing( result )
465
                    packets += sent
466
                    if received > sent:
467
                        error( '*** Error: received too many packets' )
468
                        error( '%s' % result )
469
                        node.cmdPrint( 'route' )
470
                        exit( 1 )
471
                    lost += sent - received
472
                    output( ( '%s ' % dest.name ) if received else 'X ' )
473
            output( '\n' )
474
        if packets > 0:
475
            ploss = 100 * lost / packets
476
            output( "*** Results: %i%% dropped (%d/%d lost)\n" %
477
                    ( ploss, lost, packets ) )
478
        else:
479
            ploss = 0
480
            output( "*** Warning: No packets sent\n" )
481
        return ploss
482

    
483
    @staticmethod
484
    def _parsePingFull( pingOutput ):
485
        "Parse ping output and return all data."
486
        errorTuple = (1, 0, 0, 0, 0, 0)
487
        # Check for downed link
488
        r = r'[uU]nreachable'
489
        m = re.search( r, pingOutput )
490
        if m is not None:
491
            return errorTuple
492
        r = r'(\d+) packets transmitted, (\d+) received'
493
        m = re.search( r, pingOutput )
494
        if m is None:
495
            error( '*** Error: could not parse ping output: %s\n' %
496
                   pingOutput )
497
            return errorTuple
498
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
499
        r = r'rtt min/avg/max/mdev = '
500
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
501
        m = re.search( r, pingOutput )
502
        if m is None:
503
            error( '*** Error: could not parse ping output: %s\n' %
504
                   pingOutput )
505
            return errorTuple
506
        rttmin = float( m.group( 1 ) )
507
        rttavg = float( m.group( 2 ) )
508
        rttmax = float( m.group( 3 ) )
509
        rttdev = float( m.group( 4 ) )
510
        return sent, received, rttmin, rttavg, rttmax, rttdev
511

    
512
    def pingFull( self, hosts=None, timeout=None ):
513
        """Ping between all specified hosts and return all data.
514
           hosts: list of hosts
515
           timeout: time to wait for a response, as string
516
           returns: all ping data; see function body."""
517
        # should we check if running?
518
        # Each value is a tuple: (src, dsd, [all ping outputs])
519
        all_outputs = []
520
        if not hosts:
521
            hosts = self.hosts
522
            output( '*** Ping: testing ping reachability\n' )
523
        for node in hosts:
524
            output( '%s -> ' % node.name )
525
            for dest in hosts:
526
                if node != dest:
527
                    opts = ''
528
                    if timeout:
529
                        opts = '-W %s' % timeout
530
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
531
                    outputs = self._parsePingFull( result )
532
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
533
                    all_outputs.append( (node, dest, outputs) )
534
                    output( ( '%s ' % dest.name ) if received else 'X ' )
535
            output( '\n' )
536
        output( "*** Results: \n" )
537
        for outputs in all_outputs:
538
            src, dest, ping_outputs = outputs
539
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
540
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
541
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
542
                    (rttmin, rttavg, rttmax, rttdev) )
543
        return all_outputs
544

    
545
    def pingAll( self ):
546
        """Ping between all hosts.
547
           returns: ploss packet loss percentage"""
548
        return self.ping()
549

    
550
    def pingPair( self ):
551
        """Ping between first two hosts, useful for testing.
552
           returns: ploss packet loss percentage"""
553
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
554
        return self.ping( hosts=hosts )
555

    
556
    def pingAllFull( self ):
557
        """Ping between all hosts.
558
           returns: ploss packet loss percentage"""
559
        return self.pingFull()
560

    
561
    def pingPairFull( self ):
562
        """Ping between first two hosts, useful for testing.
563
           returns: ploss packet loss percentage"""
564
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
565
        return self.pingFull( hosts=hosts )
566

    
567
    @staticmethod
568
    def _parseIperf( iperfOutput ):
569
        """Parse iperf output and return bandwidth.
570
           iperfOutput: string
571
           returns: result string"""
572
        r = r'([\d\.]+ \w+/sec)'
573
        m = re.findall( r, iperfOutput )
574
        if m:
575
            return m[-1]
576
        else:
577
            # was: raise Exception(...)
578
            error( 'could not parse iperf output: ' + iperfOutput )
579
            return ''
580

    
581
    # XXX This should be cleaned up
582

    
583
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
584
        """Run iperf between two hosts.
585
           hosts: list of hosts; if None, uses opposite hosts
586
           l4Type: string, one of [ TCP, UDP ]
587
           returns: results two-element array of server and client speeds"""
588
        if not quietRun( 'which telnet' ):
589
            error( 'Cannot find telnet in $PATH - required for iperf test' )
590
            return
591
        if not hosts:
592
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
593
        else:
594
            assert len( hosts ) == 2
595
        client, server = hosts
596
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
597
        output( "%s and %s\n" % ( client.name, server.name ) )
598
        server.cmd( 'killall -9 iperf' )
599
        iperfArgs = 'iperf '
600
        bwArgs = ''
601
        if l4Type == 'UDP':
602
            iperfArgs += '-u '
603
            bwArgs = '-b ' + udpBw + ' '
604
        elif l4Type != 'TCP':
605
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
606
        server.sendCmd( iperfArgs + '-s', printPid=True )
607
        servout = ''
608
        while server.lastPid is None:
609
            servout += server.monitor()
610
        if l4Type == 'TCP':
611
            while 'Connected' not in client.cmd(
612
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
613
                output('waiting for iperf to start up...')
614
                sleep(.5)
615
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
616
                             bwArgs )
617
        debug( 'Client output: %s\n' % cliout )
618
        server.sendInt()
619
        servout += server.waitOutput()
620
        debug( 'Server output: %s\n' % servout )
621
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
622
        if l4Type == 'UDP':
623
            result.insert( 0, udpBw )
624
        output( '*** Results: %s\n' % result )
625
        return result
626

    
627
    def runCpuLimitTest( self, cpu, duration=5 ):
628
        """run CPU limit test with 'while true' processes.
629
        cpu: desired CPU fraction of each host
630
        duration: test duration in seconds
631
        returns a single list of measured CPU fractions as floats.
632
        """
633
        pct = cpu * 100
634
        info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
635
        hosts = self.hosts
636
        for h in hosts:
637
            h.cmd( 'while true; do a=1; done &' )
638
        pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
639
        pids_str = ",".join(["%s" % pid for pid in pids])
640
        cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
641
        # It's a shame that this is what pylint prefers
642
        outputs = []
643
        for _ in range( duration ):
644
            sleep( 1 )
645
            outputs.append( quietRun( cmd ).strip() )
646
        for h in hosts:
647
            h.cmd( 'kill %1' )
648
        cpu_fractions = []
649
        for test_output in outputs:
650
            # Split by line.  Ignore first line, which looks like this:
651
            # PID %CPU COMMAND\n
652
            for line in test_output.split('\n')[1:]:
653
                r = r'\d+\s*(\d+\.\d+)'
654
                m = re.search( r, line )
655
                if m is None:
656
                    error( '*** Error: could not extract CPU fraction: %s\n' %
657
                           line )
658
                    return None
659
                cpu_fractions.append( float( m.group( 1 ) ) )
660
        output( '*** Results: %s\n' % cpu_fractions )
661
        return cpu_fractions
662

    
663
    # BL: I think this can be rewritten now that we have
664
    # a real link class.
665
    def configLinkStatus( self, src, dst, status ):
666
        """Change status of src <-> dst links.
667
           src: node name
668
           dst: node name
669
           status: string {up, down}"""
670
        if src not in self.nameToNode:
671
            error( 'src not in network: %s\n' % src )
672
        elif dst not in self.nameToNode:
673
            error( 'dst not in network: %s\n' % dst )
674
        else:
675
            if type( src ) is str:
676
                src = self.nameToNode[ src ]
677
            if type( dst ) is str:
678
                dst = self.nameToNode[ dst ]
679
            connections = src.connectionsTo( dst )
680
            if len( connections ) == 0:
681
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
682
            for srcIntf, dstIntf in connections:
683
                result = srcIntf.ifconfig( status )
684
                if result:
685
                    error( 'link src status change failed: %s\n' % result )
686
                result = dstIntf.ifconfig( status )
687
                if result:
688
                    error( 'link dst status change failed: %s\n' % result )
689

    
690
    def interact( self ):
691
        "Start network and run our simple CLI."
692
        self.start()
693
        result = CLI( self )
694
        self.stop()
695
        return result
696

    
697
    inited = False
698

    
699
    @classmethod
700
    def init( cls ):
701
        "Initialize Mininet"
702
        if cls.inited:
703
            return
704
        ensureRoot()
705
        fixLimits()
706
        cls.inited = True
707

    
708

    
709
class MininetWithControlNet( Mininet ):
710

    
711
    """Control network support:
712

713
       Create an explicit control network. Currently this is only
714
       used/usable with the user datapath.
715

716
       Notes:
717

718
       1. If the controller and switches are in the same (e.g. root)
719
          namespace, they can just use the loopback connection.
720

721
       2. If we can get unix domain sockets to work, we can use them
722
          instead of an explicit control network.
723

724
       3. Instead of routing, we could bridge or use 'in-band' control.
725

726
       4. Even if we dispense with this in general, it could still be
727
          useful for people who wish to simulate a separate control
728
          network (since real networks may need one!)
729

730
       5. Basically nobody ever used this code, so it has been moved
731
          into its own class.
732

733
       6. Ultimately we may wish to extend this to allow us to create a
734
          control network which every node's control interface is
735
          attached to."""
736

    
737
    def configureControlNetwork( self ):
738
        "Configure control network."
739
        self.configureRoutedControlNetwork()
740

    
741
    # We still need to figure out the right way to pass
742
    # in the control network location.
743

    
744
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
745
                                       prefixLen=16 ):
746
        """Configure a routed control network on controller and switches.
747
           For use with the user datapath only right now."""
748
        controller = self.controllers[ 0 ]
749
        info( controller.name + ' <->' )
750
        cip = ip
751
        snum = ipParse( ip )
752
        for switch in self.switches:
753
            info( ' ' + switch.name )
754
            link = self.link( switch, controller, port1=0 )
755
            sintf, cintf = link.intf1, link.intf2
756
            switch.controlIntf = sintf
757
            snum += 1
758
            while snum & 0xff in [ 0, 255 ]:
759
                snum += 1
760
            sip = ipStr( snum )
761
            cintf.setIP( cip, prefixLen )
762
            sintf.setIP( sip, prefixLen )
763
            controller.setHostRoute( sip, cintf )
764
            switch.setHostRoute( cip, sintf )
765
        info( '\n' )
766
        info( '*** Testing control network\n' )
767
        while not cintf.isUp():
768
            info( '*** Waiting for', cintf, 'to come up\n' )
769
            sleep( 1 )
770
        for switch in self.switches:
771
            while not sintf.isUp():
772
                info( '*** Waiting for', sintf, 'to come up\n' )
773
                sleep( 1 )
774
            if self.ping( hosts=[ switch, controller ] ) != 0:
775
                error( '*** Error: control network test failed\n' )
776
                exit( 1 )
777
        info( '\n' )