Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ b7268856

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

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

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

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

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

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

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

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

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

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

    
168

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

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

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

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

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

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

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

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

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

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

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

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

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

    
306
    def addLink( self, node1, node2, port1=None, port2=None,
307
                 cls=None, **params ):
308
        """"Add a link from node1 to node2
309
            node1: source node
310
            node2: dest node
311
            port1: source port
312
            port2: dest port
313
            returns: link object"""
314
        defaults = { 'port1': port1,
315
                     'port2': port2,
316
                     'intf': self.intf }
317
        defaults.update( params )
318
        if not cls:
319
            cls = self.link
320
        return cls( node1, node2, **defaults )
321

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

    
341
    def buildFromTopo( self, topo=None ):
342
        """Build mininet from a topology object
343
           At the end of this function, everything should be connected
344
           and up."""
345

    
346
        # Possibly we should clean up here and/or validate
347
        # the topo
348
        if self.cleanup:
349
            pass
350

    
351
        info( '*** Creating network\n' )
352

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

    
366
        info( '*** Adding hosts:\n' )
367
        for hostName in topo.hosts():
368
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
369
            info( hostName + ' ' )
370

    
371
        info( '\n*** Adding switches:\n' )
372
        for switchName in topo.switches():
373
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
374
            info( switchName + ' ' )
375

    
376
        info( '\n*** Adding links:\n' )
377
        for srcName, dstName in topo.links(sort=True):
378
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
379
            params = topo.linkInfo( srcName, dstName )
380
            srcPort, dstPort = topo.port( srcName, dstName )
381
            self.addLink( src, dst, srcPort, dstPort, **params )
382
            info( '(%s, %s) ' % ( src.name, dst.name ) )
383

    
384
        info( '\n' )
385

    
386
    def configureControlNetwork( self ):
387
        "Control net config hook: override in subclass"
388
        raise Exception( 'configureControlNetwork: '
389
                         'should be overriden in subclass', self )
390

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

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

    
416
    def stopXterms( self ):
417
        "Kill each xterm."
418
        for term in self.terms:
419
            os.kill( term.pid, signal.SIGKILL )
420
        cleanUpScreens()
421

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

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

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

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

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

    
500
    # XXX These test methods should be moved out of this class.
501
    # Probably we should create a tests.py for them
502

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

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

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

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

    
620
    def pingAll( self, timeout=None ):
621
        """Ping between all hosts.
622
           returns: ploss packet loss percentage"""
623
        return self.ping( timeout=timeout )
624

    
625
    def pingPair( self ):
626
        """Ping between first two hosts, useful for testing.
627
           returns: ploss packet loss percentage"""
628
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
629
        return self.ping( hosts=hosts )
630

    
631
    def pingAllFull( self ):
632
        """Ping between all hosts.
633
           returns: ploss packet loss percentage"""
634
        return self.pingFull()
635

    
636
    def pingPairFull( self ):
637
        """Ping between first two hosts, useful for testing.
638
           returns: ploss packet loss percentage"""
639
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
640
        return self.pingFull( hosts=hosts )
641

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

    
656
    # XXX This should be cleaned up
657

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

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

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

    
767
    def interact( self ):
768
        "Start network and run our simple CLI."
769
        self.start()
770
        result = CLI( self )
771
        self.stop()
772
        return result
773

    
774
    inited = False
775

    
776
    @classmethod
777
    def init( cls ):
778
        "Initialize Mininet"
779
        if cls.inited:
780
            return
781
        ensureRoot()
782
        fixLimits()
783
        cls.inited = True
784

    
785

    
786
class MininetWithControlNet( Mininet ):
787

    
788
    """Control network support:
789

790
       Create an explicit control network. Currently this is only
791
       used/usable with the user datapath.
792

793
       Notes:
794

795
       1. If the controller and switches are in the same (e.g. root)
796
          namespace, they can just use the loopback connection.
797

798
       2. If we can get unix domain sockets to work, we can use them
799
          instead of an explicit control network.
800

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

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

807
       5. Basically nobody ever used this code, so it has been moved
808
          into its own class.
809

810
       6. Ultimately we may wish to extend this to allow us to create a
811
          control network which every node's control interface is
812
          attached to."""
813

    
814
    def configureControlNetwork( self ):
815
        "Configure control network."
816
        self.configureRoutedControlNetwork()
817

    
818
    # We still need to figure out the right way to pass
819
    # in the control network location.
820

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