Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 88763cfb

History | View | Annotate | Download (32.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
import random
94
import copy
95
from time import sleep
96
from itertools import chain, groupby
97

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

    
106
# Mininet version: should be consistent with README and LICENSE
107
VERSION = "2.1.0+"
108

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

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

    
155
        self.hosts = []
156
        self.switches = []
157
        self.controllers = []
158

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

    
161
        self.terms = []  # list of spawned xterm processes
162

    
163
        Mininet.init()  # Initialize Mininet if necessary
164

    
165
        self.built = False
166
        if topo and build:
167
            self.build()
168

    
169

    
170
    def waitConnected( self, timeout=None, delay=.5 ):
171
        """wait for each switch to connect to a controller,
172
           up to 5 seconds
173
           timeout: time to wait, or None to wait indefinitely
174
           delay: seconds to sleep per iteration
175
           returns: True if all switches are connected"""
176
        info( '*** Waiting for switches to connect\n' )
177
        time = 0
178
        remaining = list( self.switches )
179
        while True:
180
            for switch in tuple( remaining ):
181
                if switch.connected():
182
                    info( '%s ' % switch )
183
                    remaining.remove( switch )
184
            if not remaining:
185
                info( '\n' )
186
                return True
187
            if time > timeout and timeout is not None:
188
                break
189
            sleep( delay )
190
            time += delay
191
        warn( 'Timed out after %d seconds\n' % time )
192
        for switch in remaining:
193
            if not switch.connected():
194
                warn( 'Warning: %s is not connected to a controller\n'
195
                      % switch.name )
196
            else:
197
                remaining.remove( switch )
198
        return not remaining
199

    
200
    def addHost( self, name, cls=None, **params ):
201
        """Add host.
202
           name: name of host to add
203
           cls: custom host class/constructor (optional)
204
           params: parameters for host
205
           returns: added host"""
206
        # Default IP and MAC addresses
207
        defaults = { 'ip': ipAdd( self.nextIP,
208
                                  ipBaseNum=self.ipBaseNum,
209
                                  prefixLen=self.prefixLen ) +
210
                                  '/%s' % self.prefixLen }
211
        if self.autoSetMacs:
212
            defaults[ 'mac'] = macColonHex( self.nextIP )
213
        if self.autoPinCpus:
214
            defaults[ 'cores' ] = self.nextCore
215
            self.nextCore = ( self.nextCore + 1 ) % self.numCores
216
        self.nextIP += 1
217
        defaults.update( params )
218
        if not cls:
219
            cls = self.host
220
        h = cls( name, **defaults )
221
        self.hosts.append( h )
222
        self.nameToNode[ name ] = h
223
        return h
224

    
225
    def addSwitch( self, name, cls=None, **params ):
226
        """Add switch.
227
           name: name of switch to add
228
           cls: custom switch class/constructor (optional)
229
           returns: added switch
230
           side effect: increments listenPort ivar ."""
231
        defaults = { 'listenPort': self.listenPort,
232
                     'inNamespace': self.inNamespace }
233
        defaults.update( params )
234
        if not cls:
235
            cls = self.switch
236
        sw = cls( name, **defaults )
237
        if not self.inNamespace and self.listenPort:
238
            self.listenPort += 1
239
        self.switches.append( sw )
240
        self.nameToNode[ name ] = sw
241
        return sw
242

    
243
    def addController( self, name='c0', controller=None, **params ):
244
        """Add controller.
245
           controller: Controller class"""
246
        # Get controller class
247
        if not controller:
248
            controller = self.controller
249
        # Construct new controller if one is not given
250
        if isinstance( name, Controller ):
251
            controller_new = name
252
            # Pylint thinks controller is a str()
253
            # pylint: disable=E1103
254
            name = controller_new.name
255
            # pylint: enable=E1103
256
        else:
257
            controller_new = controller( name, **params )
258
        # Add new controller to net
259
        if controller_new: # allow controller-less setups
260
            self.controllers.append( controller_new )
261
            self.nameToNode[ name ] = controller_new
262
        return controller_new
263

    
264
    # BL: We now have four ways to look up nodes
265
    # This may (should?) be cleaned up in the future.
266
    def getNodeByName( self, *args ):
267
        "Return node(s) with given name(s)"
268
        if len( args ) == 1:
269
            return self.nameToNode[ args[ 0 ] ]
270
        return [ self.nameToNode[ n ] for n in args ]
271

    
272
    def get( self, *args ):
273
        "Convenience alias for getNodeByName"
274
        return self.getNodeByName( *args )
275

    
276
    # Even more convenient syntax for node lookup and iteration
277
    def __getitem__( self, key ):
278
        """net [ name ] operator: Return node(s) with given name(s)"""
279
        return self.nameToNode[ key ]
280

    
281
    def __iter__( self ):
282
        "return iterator over node names"
283
        for node in chain( self.hosts, self.switches, self.controllers ):
284
            yield node.name
285

    
286
    def __len__( self ):
287
        "returns number of nodes in net"
288
        return ( len( self.hosts ) + len( self.switches ) +
289
                 len( self.controllers ) )
290

    
291
    def __contains__( self, item ):
292
        "returns True if net contains named node"
293
        return item in self.nameToNode
294

    
295
    def keys( self ):
296
        "return a list of all node names or net's keys"
297
        return list( self )
298

    
299
    def values( self ):
300
        "return a list of all nodes or net's values"
301
        return [ self[name] for name in self ]
302

    
303
    def items( self ):
304
        "return (key,value) tuple list for every node in net"
305
        return zip( self.keys(), self.values() )
306

    
307
    def addLink( self, node1, node2, port1=None, port2=None,
308
                 cls=None, **params ):
309
        """"Add a link from node1 to node2
310
            node1: source node
311
            node2: dest node
312
            port1: source port
313
            port2: dest port
314
            returns: link object"""
315
        mac1 = macColonHex( random.randint( 1, (2**24 - 1) ) )
316
        mac2 = macColonHex( random.randint( 1, (2**24 - 1) ) )
317
        defaults = { 'port1': port1,
318
                     'port2': port2,
319
                     'addr1': mac1,
320
                     'addr2': mac2,
321
                     'intf': self.intf }
322
        defaults.update( params )
323
        if not cls:
324
            cls = self.link
325
        return cls( node1, node2, **defaults )
326

    
327
    def configHosts( self ):
328
        "Configure a set of hosts."
329
        for host in self.hosts:
330
            info( host.name + ' ' )
331
            intf = host.defaultIntf()
332
            if intf:
333
                host.configDefault()
334
            else:
335
                # Don't configure nonexistent intf
336
                host.configDefault( ip=None, mac=None )
337
            # You're low priority, dude!
338
            # BL: do we want to do this here or not?
339
            # May not make sense if we have CPU lmiting...
340
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
341
            # This may not be the right place to do this, but
342
            # it needs to be done somewhere.
343
        info( '\n' )
344

    
345
    def buildFromTopo( self, topo=None ):
346
        """Build mininet from a topology object
347
           At the end of this function, everything should be connected
348
           and up."""
349

    
350
        # Possibly we should clean up here and/or validate
351
        # the topo
352
        if self.cleanup:
353
            pass
354

    
355
        info( '*** Creating network\n' )
356

    
357
        if not self.controllers and self.controller:
358
            # Add a default controller
359
            info( '*** Adding controller\n' )
360
            classes = self.controller
361
            if type( classes ) is not list:
362
                classes = [ classes ]
363
            for i, cls in enumerate( classes ):
364
                # Allow Controller objects because nobody understands currying
365
                if isinstance( cls, Controller ):
366
                    self.addController( cls )
367
                else:
368
                    self.addController( 'c%d' % i, cls )
369

    
370
        info( '*** Adding hosts:\n' )
371
        for hostName in topo.hosts():
372
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
373
            info( hostName + ' ' )
374

    
375
        info( '\n*** Adding switches:\n' )
376
        for switchName in topo.switches():
377
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
378
            info( switchName + ' ' )
379

    
380
        info( '\n*** Adding links:\n' )
381
        for srcName, dstName in topo.links(sort=True):
382
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
383
            params = topo.linkInfo( srcName, dstName )
384
            srcPort, dstPort = topo.port( srcName, dstName )
385
            self.addLink( src, dst, srcPort, dstPort, **params )
386
            info( '(%s, %s) ' % ( src.name, dst.name ) )
387
        
388
        info( '\n' )
389

    
390
    def configureControlNetwork( self ):
391
        "Control net config hook: override in subclass"
392
        raise Exception( 'configureControlNetwork: '
393
                         'should be overriden in subclass', self )
394

    
395
    def build( self ):
396
        "Build mininet."
397
        if self.topo:
398
            self.buildFromTopo( self.topo )
399
        if self.inNamespace:
400
            self.configureControlNetwork()
401
        info( '*** Configuring hosts\n' )
402
        self.configHosts()
403
        if self.xterms:
404
            self.startTerms()
405
        if self.autoStaticArp:
406
            self.staticArp()
407
        self.built = True
408

    
409
    def startTerms( self ):
410
        "Start a terminal for each node."
411
        if 'DISPLAY' not in os.environ:
412
            error( "Error starting terms: Cannot connect to display\n" )
413
            return
414
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
415
        cleanUpScreens()
416
        self.terms += makeTerms( self.controllers, 'controller' )
417
        self.terms += makeTerms( self.switches, 'switch' )
418
        self.terms += makeTerms( self.hosts, 'host' )
419

    
420
    def stopXterms( self ):
421
        "Kill each xterm."
422
        for term in self.terms:
423
            os.kill( term.pid, signal.SIGKILL )
424
        cleanUpScreens()
425

    
426
    def staticArp( self ):
427
        "Add all-pairs ARP entries to remove the need to handle broadcast."
428
        for src in self.hosts:
429
            for dst in self.hosts:
430
                if src != dst:
431
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
432

    
433
    def start( self ):
434
        "Start controller and switches."
435
        if not self.built:
436
            self.build()
437
        info( '*** Starting controller\n' )
438
        for controller in self.controllers:
439
            controller.start()
440
        info( '*** Starting %s switches\n' % len( self.switches ) )
441
        for switch in self.switches:
442
            info( switch.name + ' ')
443
            switch.start( self.controllers )
444
        info( '\n' )
445
        if self.waitConn:
446
            self.waitConnected()
447

    
448
    def stop( self ):
449
        "Stop the controller(s), switches and hosts"
450
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
451
        for controller in self.controllers:
452
            info( controller.name + ' ' )
453
            controller.stop()
454
        info( '\n' )
455
        if self.terms:
456
            info( '*** Stopping %i terms\n' % len( self.terms ) )
457
            self.stopXterms()
458
        info( '*** Stopping %i switches\n' % len( self.switches ) )
459
        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
460
            if hasattr( swclass, 'batchShutdown' ):
461
                swclass.batchShutdown( switches )
462
        for switch in self.switches:
463
            info( switch.name + ' ' )
464
            switch.stop()
465
        info( '\n' )
466
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
467
        for host in self.hosts:
468
            info( host.name + ' ' )
469
            host.terminate()
470
        info( '\n*** Done\n' )
471

    
472
    def run( self, test, *args, **kwargs ):
473
        "Perform a complete start/test/stop cycle."
474
        self.start()
475
        info( '*** Running test\n' )
476
        result = test( *args, **kwargs )
477
        self.stop()
478
        return result
479

    
480
    def monitor( self, hosts=None, timeoutms=-1 ):
481
        """Monitor a set of hosts (or all hosts by default),
482
           and return their output, a line at a time.
483
           hosts: (optional) set of hosts to monitor
484
           timeoutms: (optional) timeout value in ms
485
           returns: iterator which returns host, line"""
486
        if hosts is None:
487
            hosts = self.hosts
488
        poller = select.poll()
489
        Node = hosts[ 0 ]  # so we can call class method fdToNode
490
        for host in hosts:
491
            poller.register( host.stdout )
492
        while True:
493
            ready = poller.poll( timeoutms )
494
            for fd, event in ready:
495
                host = Node.fdToNode( fd )
496
                if event & select.POLLIN:
497
                    line = host.readline()
498
                    if line is not None:
499
                        yield host, line
500
            # Return if non-blocking
501
            if not ready and timeoutms >= 0:
502
                yield None, None
503

    
504
    # XXX These test methods should be moved out of this class.
505
    # Probably we should create a tests.py for them
506

    
507
    @staticmethod
508
    def _parsePing( pingOutput ):
509
        "Parse ping output and return packets sent, received."
510
        # Check for downed link
511
        if 'connect: Network is unreachable' in pingOutput:
512
            return 1, 0
513
        r = r'(\d+) packets transmitted, (\d+) received'
514
        m = re.search( r, pingOutput )
515
        if m is None:
516
            error( '*** Error: could not parse ping output: %s\n' %
517
                   pingOutput )
518
            return 1, 0
519
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
520
        return sent, received
521

    
522
    def ping( self, hosts=None, timeout=None ):
523
        """Ping between all specified hosts.
524
           hosts: list of hosts
525
           timeout: time to wait for a response, as string
526
           returns: ploss packet loss percentage"""
527
        # should we check if running?
528
        packets = 0
529
        lost = 0
530
        ploss = None
531
        if not hosts:
532
            hosts = self.hosts
533
            output( '*** Ping: testing ping reachability\n' )
534
        for node in hosts:
535
            output( '%s -> ' % node.name )
536
            for dest in hosts:
537
                if node != dest:
538
                    opts = ''
539
                    if timeout:
540
                        opts = '-W %s' % timeout
541
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
542
                    sent, received = self._parsePing( result )
543
                    packets += sent
544
                    if received > sent:
545
                        error( '*** Error: received too many packets' )
546
                        error( '%s' % result )
547
                        node.cmdPrint( 'route' )
548
                        exit( 1 )
549
                    lost += sent - received
550
                    output( ( '%s ' % dest.name ) if received else 'X ' )
551
            output( '\n' )
552
        if packets > 0:
553
            ploss = 100.0 * lost / packets
554
            received = packets - lost
555
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
556
                    ( ploss, received, packets ) )
557
        else:
558
            ploss = 0
559
            output( "*** Warning: No packets sent\n" )
560
        return ploss
561

    
562
    @staticmethod
563
    def _parsePingFull( pingOutput ):
564
        "Parse ping output and return all data."
565
        errorTuple = (1, 0, 0, 0, 0, 0)
566
        # Check for downed link
567
        r = r'[uU]nreachable'
568
        m = re.search( r, pingOutput )
569
        if m is not None:
570
            return errorTuple
571
        r = r'(\d+) packets transmitted, (\d+) received'
572
        m = re.search( r, pingOutput )
573
        if m is None:
574
            error( '*** Error: could not parse ping output: %s\n' %
575
                   pingOutput )
576
            return errorTuple
577
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
578
        r = r'rtt min/avg/max/mdev = '
579
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
580
        m = re.search( r, pingOutput )
581
        if m is None:
582
            error( '*** Error: could not parse ping output: %s\n' %
583
                   pingOutput )
584
            return errorTuple
585
        rttmin = float( m.group( 1 ) )
586
        rttavg = float( m.group( 2 ) )
587
        rttmax = float( m.group( 3 ) )
588
        rttdev = float( m.group( 4 ) )
589
        return sent, received, rttmin, rttavg, rttmax, rttdev
590

    
591
    def pingFull( self, hosts=None, timeout=None ):
592
        """Ping between all specified hosts and return all data.
593
           hosts: list of hosts
594
           timeout: time to wait for a response, as string
595
           returns: all ping data; see function body."""
596
        # should we check if running?
597
        # Each value is a tuple: (src, dsd, [all ping outputs])
598
        all_outputs = []
599
        if not hosts:
600
            hosts = self.hosts
601
            output( '*** Ping: testing ping reachability\n' )
602
        for node in hosts:
603
            output( '%s -> ' % node.name )
604
            for dest in hosts:
605
                if node != dest:
606
                    opts = ''
607
                    if timeout:
608
                        opts = '-W %s' % timeout
609
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
610
                    outputs = self._parsePingFull( result )
611
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
612
                    all_outputs.append( (node, dest, outputs) )
613
                    output( ( '%s ' % dest.name ) if received else 'X ' )
614
            output( '\n' )
615
        output( "*** Results: \n" )
616
        for outputs in all_outputs:
617
            src, dest, ping_outputs = outputs
618
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
619
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
620
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
621
                    (rttmin, rttavg, rttmax, rttdev) )
622
        return all_outputs
623

    
624
    def pingAll( self, timeout=None ):
625
        """Ping between all hosts.
626
           returns: ploss packet loss percentage"""
627
        return self.ping( timeout=timeout )
628

    
629
    def pingPair( self ):
630
        """Ping between first two hosts, useful for testing.
631
           returns: ploss packet loss percentage"""
632
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
633
        return self.ping( hosts=hosts )
634

    
635
    def pingAllFull( self ):
636
        """Ping between all hosts.
637
           returns: ploss packet loss percentage"""
638
        return self.pingFull()
639

    
640
    def pingPairFull( self ):
641
        """Ping between first two hosts, useful for testing.
642
           returns: ploss packet loss percentage"""
643
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
644
        return self.pingFull( hosts=hosts )
645

    
646
    @staticmethod
647
    def _parseIperf( iperfOutput ):
648
        """Parse iperf output and return bandwidth.
649
           iperfOutput: string
650
           returns: result string"""
651
        r = r'([\d\.]+ \w+/sec)'
652
        m = re.findall( r, iperfOutput )
653
        if m:
654
            return m[-1]
655
        else:
656
            # was: raise Exception(...)
657
            error( 'could not parse iperf output: ' + iperfOutput )
658
            return ''
659

    
660
    # XXX This should be cleaned up
661

    
662
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
663
        """Run iperf between two hosts.
664
           hosts: list of hosts; if None, uses opposite hosts
665
           l4Type: string, one of [ TCP, UDP ]
666
           returns: results two-element array of server and client speeds"""
667
        if not quietRun( 'which telnet' ):
668
            error( 'Cannot find telnet in $PATH - required for iperf test' )
669
            return
670
        if not hosts:
671
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
672
        else:
673
            assert len( hosts ) == 2
674
        client, server = hosts
675
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
676
        output( "%s and %s\n" % ( client.name, server.name ) )
677
        server.cmd( 'killall -9 iperf' )
678
        iperfArgs = 'iperf '
679
        bwArgs = ''
680
        if l4Type == 'UDP':
681
            iperfArgs += '-u '
682
            bwArgs = '-b ' + udpBw + ' '
683
        elif l4Type != 'TCP':
684
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
685
        if format:
686
          iperfArgs += '-f %s ' %format
687
        server.sendCmd( iperfArgs + '-s', printPid=True )
688
        servout = ''
689
        while server.lastPid is None:
690
            servout += server.monitor()
691
        if l4Type == 'TCP':
692
            while 'Connected' not in client.cmd(
693
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
694
                info( 'Waiting for iperf to start up...' )
695
                sleep(.5)
696
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
697
                             bwArgs )
698
        debug( 'Client output: %s\n' % cliout )
699
        server.sendInt()
700
        servout += server.waitOutput()
701
        debug( 'Server output: %s\n' % servout )
702
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
703
        if l4Type == 'UDP':
704
            result.insert( 0, udpBw )
705
        output( '*** Results: %s\n' % result )
706
        return result
707

    
708
    def runCpuLimitTest( self, cpu, duration=5 ):
709
        """run CPU limit test with 'while true' processes.
710
        cpu: desired CPU fraction of each host
711
        duration: test duration in seconds
712
        returns a single list of measured CPU fractions as floats.
713
        """
714
        pct = cpu * 100
715
        info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
716
        hosts = self.hosts
717
        for h in hosts:
718
            h.cmd( 'while true; do a=1; done &' )
719
        pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
720
        pids_str = ",".join(["%s" % pid for pid in pids])
721
        cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
722
        # It's a shame that this is what pylint prefers
723
        outputs = []
724
        for _ in range( duration ):
725
            sleep( 1 )
726
            outputs.append( quietRun( cmd ).strip() )
727
        for h in hosts:
728
            h.cmd( 'kill %1' )
729
        cpu_fractions = []
730
        for test_output in outputs:
731
            # Split by line.  Ignore first line, which looks like this:
732
            # PID %CPU COMMAND\n
733
            for line in test_output.split('\n')[1:]:
734
                r = r'\d+\s*(\d+\.\d+)'
735
                m = re.search( r, line )
736
                if m is None:
737
                    error( '*** Error: could not extract CPU fraction: %s\n' %
738
                           line )
739
                    return None
740
                cpu_fractions.append( float( m.group( 1 ) ) )
741
        output( '*** Results: %s\n' % cpu_fractions )
742
        return cpu_fractions
743

    
744
    # BL: I think this can be rewritten now that we have
745
    # a real link class.
746
    def configLinkStatus( self, src, dst, status ):
747
        """Change status of src <-> dst links.
748
           src: node name
749
           dst: node name
750
           status: string {up, down}"""
751
        if src not in self.nameToNode:
752
            error( 'src not in network: %s\n' % src )
753
        elif dst not in self.nameToNode:
754
            error( 'dst not in network: %s\n' % dst )
755
        else:
756
            if type( src ) is str:
757
                src = self.nameToNode[ src ]
758
            if type( dst ) is str:
759
                dst = self.nameToNode[ dst ]
760
            connections = src.connectionsTo( dst )
761
            if len( connections ) == 0:
762
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
763
            for srcIntf, dstIntf in connections:
764
                result = srcIntf.ifconfig( status )
765
                if result:
766
                    error( 'link src status change failed: %s\n' % result )
767
                result = dstIntf.ifconfig( status )
768
                if result:
769
                    error( 'link dst status change failed: %s\n' % result )
770

    
771
    def interact( self ):
772
        "Start network and run our simple CLI."
773
        self.start()
774
        result = CLI( self )
775
        self.stop()
776
        return result
777

    
778
    inited = False
779

    
780
    @classmethod
781
    def init( cls ):
782
        "Initialize Mininet"
783
        if cls.inited:
784
            return
785
        ensureRoot()
786
        fixLimits()
787
        cls.inited = True
788

    
789

    
790
class MininetWithControlNet( Mininet ):
791

    
792
    """Control network support:
793

794
       Create an explicit control network. Currently this is only
795
       used/usable with the user datapath.
796

797
       Notes:
798

799
       1. If the controller and switches are in the same (e.g. root)
800
          namespace, they can just use the loopback connection.
801

802
       2. If we can get unix domain sockets to work, we can use them
803
          instead of an explicit control network.
804

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

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

811
       5. Basically nobody ever used this code, so it has been moved
812
          into its own class.
813

814
       6. Ultimately we may wish to extend this to allow us to create a
815
          control network which every node's control interface is
816
          attached to."""
817

    
818
    def configureControlNetwork( self ):
819
        "Configure control network."
820
        self.configureRoutedControlNetwork()
821

    
822
    # We still need to figure out the right way to pass
823
    # in the control network location.
824

    
825
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
826
                                       prefixLen=16 ):
827
        """Configure a routed control network on controller and switches.
828
           For use with the user datapath only right now."""
829
        controller = self.controllers[ 0 ]
830
        info( controller.name + ' <->' )
831
        cip = ip
832
        snum = ipParse( ip )
833
        for switch in self.switches:
834
            info( ' ' + switch.name )
835
            link = self.link( switch, controller, port1=0 )
836
            sintf, cintf = link.intf1, link.intf2
837
            switch.controlIntf = sintf
838
            snum += 1
839
            while snum & 0xff in [ 0, 255 ]:
840
                snum += 1
841
            sip = ipStr( snum )
842
            cintf.setIP( cip, prefixLen )
843
            sintf.setIP( sip, prefixLen )
844
            controller.setHostRoute( sip, cintf )
845
            switch.setHostRoute( cip, sintf )
846
        info( '\n' )
847
        info( '*** Testing control network\n' )
848
        while not cintf.isUp():
849
            info( '*** Waiting for', cintf, 'to come up\n' )
850
            sleep( 1 )
851
        for switch in self.switches:
852
            while not sintf.isUp():
853
                info( '*** Waiting for', sintf, 'to come up\n' )
854
                sleep( 1 )
855
            if self.ping( hosts=[ switch, controller ] ) != 0:
856
                error( '*** Error: control network test failed\n' )
857
                exit( 1 )
858
        info( '\n' )