Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 3f2355a3

History | View | Annotate | Download (31.5 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, NAT
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.1.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
        # TODO: clean this up
185
        if params.get( 'isNAT', False ):
186
            print "***** &&&&&& !!!! nat nat nat"
187
            cls = NAT
188
        if not cls:
189
            cls = self.host
190
        h = cls( name, **defaults )
191
        self.hosts.append( h )
192
        self.nameToNode[ name ] = h
193
        return h
194

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

    
213
    def addController( self, name='c0', controller=None, **params ):
214
        """Add controller.
215
           controller: Controller class"""
216
        # Get controller class
217
        if not controller:
218
            controller = self.controller
219
        # Construct new controller if one is not given
220
        if isinstance(name, Controller):
221
            controller_new = name
222
            # Pylint thinks controller is a str()
223
            # pylint: disable=E1103
224
            name = controller_new.name
225
            # pylint: enable=E1103
226
        else:
227
            controller_new = controller( name, **params )
228
        # Add new controller to net
229
        if controller_new:  # allow controller-less setups
230
            self.controllers.append( controller_new )
231
            self.nameToNode[ name ] = controller_new
232
        return controller_new
233

    
234
    # TODO: incomplete
235
    def addNAT( self, name='nat0', connect=True, **params ):
236
        nat = self.addHost( name, cls=NAT, **params )
237
        # find first switch and create link
238
        print "******* &&&&&& net/addNAT"
239
        if connect:
240
            #connect the nat to the first switch
241
            self.addLink( nat, self.switches[ 0 ] )
242
        return nat
243

    
244
    # BL: We now have four ways to look up nodes
245
    # This may (should?) be cleaned up in the future.
246
    def getNodeByName( self, *args ):
247
        "Return node(s) with given name(s)"
248
        if len( args ) == 1:
249
            return self.nameToNode[ args[ 0 ] ]
250
        return [ self.nameToNode[ n ] for n in args ]
251

    
252
    def get( self, *args ):
253
        "Convenience alias for getNodeByName"
254
        return self.getNodeByName( *args )
255

    
256
    # Even more convenient syntax for node lookup and iteration
257
    def __getitem__( self, key ):
258
        """net [ name ] operator: Return node(s) with given name(s)"""
259
        return self.nameToNode[ key ]
260

    
261
    def __iter__( self ):
262
        "return iterator over node names"
263
        for node in chain( self.hosts, self.switches, self.controllers ):
264
            yield node.name
265

    
266
    def __len__( self ):
267
        "returns number of nodes in net"
268
        return ( len( self.hosts ) + len( self.switches ) +
269
                 len( self.controllers ) )
270

    
271
    def __contains__( self, item ):
272
        "returns True if net contains named node"
273
        return item in self.nameToNode
274

    
275
    def keys( self ):
276
        "return a list of all node names or net's keys"
277
        return list( self )
278

    
279
    def values( self ):
280
        "return a list of all nodes or net's values"
281
        return [ self[name] for name in self ]
282

    
283
    def items( self ):
284
        "return (key,value) tuple list for every node in net"
285
        return zip( self.keys(), self.values() )
286

    
287
    def addLink( self, node1, node2, port1=None, port2=None,
288
                 cls=None, **params ):
289
        """"Add a link from node1 to node2
290
            node1: source node
291
            node2: dest node
292
            port1: source port
293
            port2: dest port
294
            returns: link object"""
295
        defaults = { 'port1': port1,
296
                     'port2': port2,
297
                     'intf': self.intf }
298
        defaults.update( params )
299
        if not cls:
300
            cls = self.link
301
        return cls( node1, node2, **defaults )
302

    
303
    def configHosts( self ):
304
        "Configure a set of hosts."
305
        for host in self.hosts:
306
            info( host.name + ' ' )
307
            intf = host.defaultIntf()
308
            if intf:
309
                host.configDefault()
310
            else:
311
                # Don't configure nonexistent intf
312
                host.configDefault( ip=None, mac=None )
313
            # You're low priority, dude!
314
            # BL: do we want to do this here or not?
315
            # May not make sense if we have CPU lmiting...
316
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
317
            # This may not be the right place to do this, but
318
            # it needs to be done somewhere.
319
            host.cmd( 'ifconfig lo up' )
320
        info( '\n' )
321

    
322
    ''' TODO: remove this!
323
    def configGateway( self ):
324
        """Add gateway routes to all hosts if the networks has a gateway."""
325
        if self.gateway:
326
            gatewayIP = self.gateway.defaultIntf().IP()
327
            for host in self.hosts:
328
                if host.inNamespace and self.gateway:
329
                    host.cmd( 'ip route flush root 0/0' )
330
                    host.cmd( 'route add -net', self.ipBase, 'dev', host.defaultIntf() )
331
                    host.cmd( 'route add default gw', gatewayIP )
332
                else:
333
                    # Don't mess with hosts in the root namespace
334
                    pass
335
    '''
336

    
337
    def buildFromTopo( self, topo=None ):
338
        """Build mininet from a topology object
339
           At the end of this function, everything should be connected
340
           and up."""
341

    
342
        # Possibly we should clean up here and/or validate
343
        # the topo
344
        if self.cleanup:
345
            pass
346

    
347
        info( '*** Creating network\n' )
348

    
349
        if not self.controllers and self.controller:
350
            # Add a default controller
351
            info( '*** Adding controller\n' )
352
            classes = self.controller
353
            if type( classes ) is not list:
354
                classes = [ classes ]
355
            for i, cls in enumerate( classes ):
356
                self.addController( 'c%d' % i, cls )
357

    
358
        info( '*** Adding hosts:\n' )
359
        for hostName in topo.hosts():
360
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
361
            info( hostName + ' ' )
362

    
363
        info( '\n*** Adding switches:\n' )
364
        for switchName in topo.switches():
365
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
366
            info( switchName + ' ' )
367

    
368
        info( '\n*** Adding links:\n' )
369
        for srcName, dstName in topo.links(sort=True):
370
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
371
            params = topo.linkInfo( srcName, dstName )
372
            srcPort, dstPort = topo.port( srcName, dstName )
373
            self.addLink( src, dst, srcPort, dstPort, **params )
374
            info( '(%s, %s) ' % ( src.name, dst.name ) )
375

    
376
        info( '\n' )
377

    
378
    def configureControlNetwork( self ):
379
        "Control net config hook: override in subclass"
380
        raise Exception( 'configureControlNetwork: '
381
                         'should be overriden in subclass', self )
382

    
383
    def build( self ):
384
        "Build mininet."
385
        if self.topo:
386
            self.buildFromTopo( self.topo )
387
        if ( self.inNamespace ):
388
            self.configureControlNetwork()
389
        info( '*** Configuring hosts\n' )
390
        self.configHosts()
391
        if self.xterms:
392
            self.startTerms()
393
        if self.autoStaticArp:
394
            self.staticArp()
395
        # TODO: remove this
396
        #self.configGateway()
397
        self.built = True
398

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

    
410
    def stopXterms( self ):
411
        "Kill each xterm."
412
        for term in self.terms:
413
            os.kill( term.pid, signal.SIGKILL )
414
        cleanUpScreens()
415

    
416
    def staticArp( self ):
417
        "Add all-pairs ARP entries to remove the need to handle broadcast."
418
        for src in self.hosts:
419
            for dst in self.hosts:
420
                if src != dst:
421
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
422

    
423
    def start( self ):
424
        "Start controller and switches."
425
        if not self.built:
426
            self.build()
427
        info( '*** Starting controller\n' )
428
        for controller in self.controllers:
429
            controller.start()
430
        info( '*** Starting %s switches\n' % len( self.switches ) )
431
        for switch in self.switches:
432
            info( switch.name + ' ')
433
            switch.start( self.controllers )
434
        info( '\n' )
435

    
436
    def stop( self ):
437
        "Stop the controller(s), switches and hosts"
438
        if self.terms:
439
            info( '*** Stopping %i terms\n' % len( self.terms ) )
440
            self.stopXterms()
441
        info( '*** Stopping %i switches\n' % len( self.switches ) )
442
        for switch in self.switches:
443
            info( switch.name + ' ' )
444
            switch.stop()
445
        info( '\n' )
446
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
447
        for host in self.hosts:
448
            info( host.name + ' ' )
449
            host.terminate()
450
        info( '\n' )
451
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
452
        for controller in self.controllers:
453
            info( controller.name + ' ' )
454
            controller.stop()
455
        info( '\n*** Done\n' )
456

    
457
    def run( self, test, *args, **kwargs ):
458
        "Perform a complete start/test/stop cycle."
459
        self.start()
460
        info( '*** Running test\n' )
461
        result = test( *args, **kwargs )
462
        self.stop()
463
        return result
464

    
465
    def monitor( self, hosts=None, timeoutms=-1 ):
466
        """Monitor a set of hosts (or all hosts by default),
467
           and return their output, a line at a time.
468
           hosts: (optional) set of hosts to monitor
469
           timeoutms: (optional) timeout value in ms
470
           returns: iterator which returns host, line"""
471
        if hosts is None:
472
            hosts = self.hosts
473
        poller = select.poll()
474
        Node = hosts[ 0 ]  # so we can call class method fdToNode
475
        for host in hosts:
476
            poller.register( host.stdout )
477
        while True:
478
            ready = poller.poll( timeoutms )
479
            for fd, event in ready:
480
                host = Node.fdToNode( fd )
481
                if event & select.POLLIN:
482
                    line = host.readline()
483
                    if line is not None:
484
                        yield host, line
485
            # Return if non-blocking
486
            if not ready and timeoutms >= 0:
487
                yield None, None
488

    
489
    # XXX These test methods should be moved out of this class.
490
    # Probably we should create a tests.py for them
491

    
492
    @staticmethod
493
    def _parsePing( pingOutput ):
494
        "Parse ping output and return packets sent, received."
495
        # Check for downed link
496
        if 'connect: Network is unreachable' in pingOutput:
497
            return (1, 0)
498
        r = r'(\d+) packets transmitted, (\d+) received'
499
        m = re.search( r, pingOutput )
500
        if m is None:
501
            error( '*** Error: could not parse ping output: %s\n' %
502
                   pingOutput )
503
            return (1, 0)
504
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
505
        return sent, received
506

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

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

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

    
609
    def pingAll( self ):
610
        """Ping between all hosts.
611
           returns: ploss packet loss percentage"""
612
        return self.ping()
613

    
614
    def pingPair( self ):
615
        """Ping between first two hosts, useful for testing.
616
           returns: ploss packet loss percentage"""
617
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
618
        return self.ping( hosts=hosts )
619

    
620
    def pingAllFull( self ):
621
        """Ping between all hosts.
622
           returns: ploss packet loss percentage"""
623
        return self.pingFull()
624

    
625
    def pingPairFull( 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.pingFull( hosts=hosts )
630

    
631
    @staticmethod
632
    def _parseIperf( iperfOutput ):
633
        """Parse iperf output and return bandwidth.
634
           iperfOutput: string
635
           returns: result string"""
636
        r = r'([\d\.]+ \w+/sec)'
637
        m = re.findall( r, iperfOutput )
638
        if m:
639
            return m[-1]
640
        else:
641
            # was: raise Exception(...)
642
            error( 'could not parse iperf output: ' + iperfOutput )
643
            return ''
644

    
645
    # XXX This should be cleaned up
646

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

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

    
727
    # BL: I think this can be rewritten now that we have
728
    # a real link class.
729
    def configLinkStatus( self, src, dst, status ):
730
        """Change status of src <-> dst links.
731
           src: node name
732
           dst: node name
733
           status: string {up, down}"""
734
        if src not in self.nameToNode:
735
            error( 'src not in network: %s\n' % src )
736
        elif dst not in self.nameToNode:
737
            error( 'dst not in network: %s\n' % dst )
738
        else:
739
            if type( src ) is str:
740
                src = self.nameToNode[ src ]
741
            if type( dst ) is str:
742
                dst = self.nameToNode[ dst ]
743
            connections = src.connectionsTo( dst )
744
            if len( connections ) == 0:
745
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
746
            for srcIntf, dstIntf in connections:
747
                result = srcIntf.ifconfig( status )
748
                if result:
749
                    error( 'link src status change failed: %s\n' % result )
750
                result = dstIntf.ifconfig( status )
751
                if result:
752
                    error( 'link dst status change failed: %s\n' % result )
753

    
754
    def interact( self ):
755
        "Start network and run our simple CLI."
756
        self.start()
757
        result = CLI( self )
758
        self.stop()
759
        return result
760

    
761
    inited = False
762

    
763
    @classmethod
764
    def init( cls ):
765
        "Initialize Mininet"
766
        if cls.inited:
767
            return
768
        ensureRoot()
769
        fixLimits()
770
        cls.inited = True
771

    
772

    
773
class MininetWithControlNet( Mininet ):
774

    
775
    """Control network support:
776

777
       Create an explicit control network. Currently this is only
778
       used/usable with the user datapath.
779

780
       Notes:
781

782
       1. If the controller and switches are in the same (e.g. root)
783
          namespace, they can just use the loopback connection.
784

785
       2. If we can get unix domain sockets to work, we can use them
786
          instead of an explicit control network.
787

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

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

794
       5. Basically nobody ever used this code, so it has been moved
795
          into its own class.
796

797
       6. Ultimately we may wish to extend this to allow us to create a
798
          control network which every node's control interface is
799
          attached to."""
800

    
801
    def configureControlNetwork( self ):
802
        "Configure control network."
803
        self.configureRoutedControlNetwork()
804

    
805
    # We still need to figure out the right way to pass
806
    # in the control network location.
807

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