Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ a802d8b1

History | View | Annotate | Download (30.7 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
        if not cls:
185
            cls = self.host
186
        h = cls( name, **defaults )
187
        self.hosts.append( h )
188
        self.nameToNode[ name ] = h
189
        return h
190

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

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

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

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

    
248
    def get( self, *args ):
249
        "Convenience alias for getNodeByName"
250
        return self.getNodeByName( *args )
251

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

    
257
    def __iter__( self ):
258
        "return iterator over node names"
259
        for node in chain( self.hosts, self.switches, self.controllers ):
260
            yield node.name
261

    
262
    def __len__( self ):
263
        "returns number of nodes in net"
264
        return ( len( self.hosts ) + len( self.switches ) +
265
                 len( self.controllers ) )
266

    
267
    def __contains__( self, item ):
268
        "returns True if net contains named node"
269
        return item in self.nameToNode
270

    
271
    def keys( self ):
272
        "return a list of all node names or net's keys"
273
        return list( self )
274

    
275
    def values( self ):
276
        "return a list of all nodes or net's values"
277
        return [ self[name] for name in self ]
278

    
279
    def items( self ):
280
        "return (key,value) tuple list for every node in net"
281
        return zip( self.keys(), self.values() )
282

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

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

    
318
    def buildFromTopo( self, topo=None ):
319
        """Build mininet from a topology object
320
           At the end of this function, everything should be connected
321
           and up."""
322

    
323
        # Possibly we should clean up here and/or validate
324
        # the topo
325
        if self.cleanup:
326
            pass
327

    
328
        info( '*** Creating network\n' )
329

    
330
        if not self.controllers and self.controller:
331
            # Add a default controller
332
            info( '*** Adding controller\n' )
333
            classes = self.controller
334
            if type( classes ) is not list:
335
                classes = [ classes ]
336
            for i, cls in enumerate( classes ):
337
                self.addController( 'c%d' % i, cls )
338

    
339
        info( '*** Adding hosts:\n' )
340
        for hostName in topo.hosts():
341
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
342
            info( hostName + ' ' )
343

    
344
        info( '\n*** Adding switches:\n' )
345
        for switchName in topo.switches():
346
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
347
            info( switchName + ' ' )
348

    
349
        info( '\n*** Adding links:\n' )
350
        for srcName, dstName in topo.links(sort=True):
351
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
352
            params = topo.linkInfo( srcName, dstName )
353
            srcPort, dstPort = topo.port( srcName, dstName )
354
            self.addLink( src, dst, srcPort, dstPort, **params )
355
            info( '(%s, %s) ' % ( src.name, dst.name ) )
356

    
357
        info( '\n' )
358

    
359
    def configureControlNetwork( self ):
360
        "Control net config hook: override in subclass"
361
        raise Exception( 'configureControlNetwork: '
362
                         'should be overriden in subclass', self )
363

    
364
    def build( self ):
365
        "Build mininet."
366
        if self.topo:
367
            self.buildFromTopo( self.topo )
368
        if ( self.inNamespace ):
369
            self.configureControlNetwork()
370
        info( '*** Configuring hosts\n' )
371
        self.configHosts()
372
        if self.xterms:
373
            self.startTerms()
374
        if self.autoStaticArp:
375
            self.staticArp()
376
        self.built = True
377

    
378
    def startTerms( self ):
379
        "Start a terminal for each node."
380
        if 'DISPLAY' not in os.environ:
381
            error( "Error starting terms: Cannot connect to display\n" )
382
            return
383
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
384
        cleanUpScreens()
385
        self.terms += makeTerms( self.controllers, 'controller' )
386
        self.terms += makeTerms( self.switches, 'switch' )
387
        self.terms += makeTerms( self.hosts, 'host' )
388

    
389
    def stopXterms( self ):
390
        "Kill each xterm."
391
        for term in self.terms:
392
            os.kill( term.pid, signal.SIGKILL )
393
        cleanUpScreens()
394

    
395
    def staticArp( self ):
396
        "Add all-pairs ARP entries to remove the need to handle broadcast."
397
        for src in self.hosts:
398
            for dst in self.hosts:
399
                if src != dst:
400
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
401

    
402
    def start( self ):
403
        "Start controller and switches."
404
        if not self.built:
405
            self.build()
406
        info( '*** Starting controller\n' )
407
        for controller in self.controllers:
408
            controller.start()
409
        info( '*** Starting %s switches\n' % len( self.switches ) )
410
        for switch in self.switches:
411
            info( switch.name + ' ')
412
            switch.start( self.controllers )
413
        info( '\n' )
414

    
415
    def stop( self ):
416
        "Stop the controller(s), switches and hosts"
417
        if self.terms:
418
            info( '*** Stopping %i terms\n' % len( self.terms ) )
419
            self.stopXterms()
420
        info( '*** Stopping %i switches\n' % len( self.switches ) )
421
        for switch in self.switches:
422
            info( switch.name + ' ' )
423
            switch.stop()
424
        info( '\n' )
425
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
426
        for host in self.hosts:
427
            info( host.name + ' ' )
428
            host.terminate()
429
        info( '\n' )
430
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
431
        for controller in self.controllers:
432
            info( controller.name + ' ' )
433
            controller.stop()
434
        info( '\n*** Done\n' )
435

    
436
    def run( self, test, *args, **kwargs ):
437
        "Perform a complete start/test/stop cycle."
438
        self.start()
439
        info( '*** Running test\n' )
440
        result = test( *args, **kwargs )
441
        self.stop()
442
        return result
443

    
444
    def monitor( self, hosts=None, timeoutms=-1 ):
445
        """Monitor a set of hosts (or all hosts by default),
446
           and return their output, a line at a time.
447
           hosts: (optional) set of hosts to monitor
448
           timeoutms: (optional) timeout value in ms
449
           returns: iterator which returns host, line"""
450
        if hosts is None:
451
            hosts = self.hosts
452
        poller = select.poll()
453
        Node = hosts[ 0 ]  # so we can call class method fdToNode
454
        for host in hosts:
455
            poller.register( host.stdout )
456
        while True:
457
            ready = poller.poll( timeoutms )
458
            for fd, event in ready:
459
                host = Node.fdToNode( fd )
460
                if event & select.POLLIN:
461
                    line = host.readline()
462
                    if line is not None:
463
                        yield host, line
464
            # Return if non-blocking
465
            if not ready and timeoutms >= 0:
466
                yield None, None
467

    
468
    # XXX These test methods should be moved out of this class.
469
    # Probably we should create a tests.py for them
470

    
471
    @staticmethod
472
    def _parsePing( pingOutput ):
473
        "Parse ping output and return packets sent, received."
474
        # Check for downed link
475
        if 'connect: Network is unreachable' in pingOutput:
476
            return (1, 0)
477
        r = r'(\d+) packets transmitted, (\d+) received'
478
        m = re.search( r, pingOutput )
479
        if m is None:
480
            error( '*** Error: could not parse ping output: %s\n' %
481
                   pingOutput )
482
            return (1, 0)
483
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
484
        return sent, received
485

    
486
    def ping( self, hosts=None, timeout=None ):
487
        """Ping between all specified hosts.
488
           hosts: list of hosts
489
           timeout: time to wait for a response, as string
490
           returns: ploss packet loss percentage"""
491
        # should we check if running?
492
        packets = 0
493
        lost = 0
494
        ploss = None
495
        if not hosts:
496
            hosts = self.hosts
497
            output( '*** Ping: testing ping reachability\n' )
498
        for node in hosts:
499
            output( '%s -> ' % node.name )
500
            for dest in hosts:
501
                if node != dest:
502
                    opts = ''
503
                    if timeout:
504
                        opts = '-W %s' % timeout
505
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
506
                    sent, received = self._parsePing( result )
507
                    packets += sent
508
                    if received > sent:
509
                        error( '*** Error: received too many packets' )
510
                        error( '%s' % result )
511
                        node.cmdPrint( 'route' )
512
                        exit( 1 )
513
                    lost += sent - received
514
                    output( ( '%s ' % dest.name ) if received else 'X ' )
515
            output( '\n' )
516
        if packets > 0:
517
            ploss = 100 * lost / packets
518
            received = packets - lost
519
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
520
                    ( ploss, received, packets ) )
521
        else:
522
            ploss = 0
523
            output( "*** Warning: No packets sent\n" )
524
        return ploss
525

    
526
    @staticmethod
527
    def _parsePingFull( pingOutput ):
528
        "Parse ping output and return all data."
529
        errorTuple = (1, 0, 0, 0, 0, 0)
530
        # Check for downed link
531
        r = r'[uU]nreachable'
532
        m = re.search( r, pingOutput )
533
        if m is not None:
534
            return errorTuple
535
        r = r'(\d+) packets transmitted, (\d+) received'
536
        m = re.search( r, pingOutput )
537
        if m is None:
538
            error( '*** Error: could not parse ping output: %s\n' %
539
                   pingOutput )
540
            return errorTuple
541
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
542
        r = r'rtt min/avg/max/mdev = '
543
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
544
        m = re.search( r, pingOutput )
545
        if m is None:
546
            error( '*** Error: could not parse ping output: %s\n' %
547
                   pingOutput )
548
            return errorTuple
549
        rttmin = float( m.group( 1 ) )
550
        rttavg = float( m.group( 2 ) )
551
        rttmax = float( m.group( 3 ) )
552
        rttdev = float( m.group( 4 ) )
553
        return sent, received, rttmin, rttavg, rttmax, rttdev
554

    
555
    def pingFull( self, hosts=None, timeout=None ):
556
        """Ping between all specified hosts and return all data.
557
           hosts: list of hosts
558
           timeout: time to wait for a response, as string
559
           returns: all ping data; see function body."""
560
        # should we check if running?
561
        # Each value is a tuple: (src, dsd, [all ping outputs])
562
        all_outputs = []
563
        if not hosts:
564
            hosts = self.hosts
565
            output( '*** Ping: testing ping reachability\n' )
566
        for node in hosts:
567
            output( '%s -> ' % node.name )
568
            for dest in hosts:
569
                if node != dest:
570
                    opts = ''
571
                    if timeout:
572
                        opts = '-W %s' % timeout
573
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
574
                    outputs = self._parsePingFull( result )
575
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
576
                    all_outputs.append( (node, dest, outputs) )
577
                    output( ( '%s ' % dest.name ) if received else 'X ' )
578
            output( '\n' )
579
        output( "*** Results: \n" )
580
        for outputs in all_outputs:
581
            src, dest, ping_outputs = outputs
582
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
583
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
584
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
585
                    (rttmin, rttavg, rttmax, rttdev) )
586
        return all_outputs
587

    
588
    def pingAll( self ):
589
        """Ping between all hosts.
590
           returns: ploss packet loss percentage"""
591
        return self.ping()
592

    
593
    def pingPair( self ):
594
        """Ping between first two hosts, useful for testing.
595
           returns: ploss packet loss percentage"""
596
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
597
        return self.ping( hosts=hosts )
598

    
599
    def pingAllFull( self ):
600
        """Ping between all hosts.
601
           returns: ploss packet loss percentage"""
602
        return self.pingFull()
603

    
604
    def pingPairFull( self ):
605
        """Ping between first two hosts, useful for testing.
606
           returns: ploss packet loss percentage"""
607
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
608
        return self.pingFull( hosts=hosts )
609

    
610
    @staticmethod
611
    def _parseIperf( iperfOutput ):
612
        """Parse iperf output and return bandwidth.
613
           iperfOutput: string
614
           returns: result string"""
615
        r = r'([\d\.]+ \w+/sec)'
616
        m = re.findall( r, iperfOutput )
617
        if m:
618
            return m[-1]
619
        else:
620
            # was: raise Exception(...)
621
            error( 'could not parse iperf output: ' + iperfOutput )
622
            return ''
623

    
624
    # XXX This should be cleaned up
625

    
626
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
627
        """Run iperf between two hosts.
628
           hosts: list of hosts; if None, uses opposite hosts
629
           l4Type: string, one of [ TCP, UDP ]
630
           returns: results two-element array of server and client speeds"""
631
        if not quietRun( 'which telnet' ):
632
            error( 'Cannot find telnet in $PATH - required for iperf test' )
633
            return
634
        if not hosts:
635
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
636
        else:
637
            assert len( hosts ) == 2
638
        client, server = hosts
639
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
640
        output( "%s and %s\n" % ( client.name, server.name ) )
641
        server.cmd( 'killall -9 iperf' )
642
        iperfArgs = 'iperf '
643
        bwArgs = ''
644
        if l4Type == 'UDP':
645
            iperfArgs += '-u '
646
            bwArgs = '-b ' + udpBw + ' '
647
        elif l4Type != 'TCP':
648
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
649
        server.sendCmd( iperfArgs + '-s', printPid=True )
650
        servout = ''
651
        while server.lastPid is None:
652
            servout += server.monitor()
653
        if l4Type == 'TCP':
654
            while 'Connected' not in client.cmd(
655
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
656
                output('waiting for iperf to start up...')
657
                sleep(.5)
658
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
659
                             bwArgs )
660
        debug( 'Client output: %s\n' % cliout )
661
        server.sendInt()
662
        servout += server.waitOutput()
663
        debug( 'Server output: %s\n' % servout )
664
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
665
        if l4Type == 'UDP':
666
            result.insert( 0, udpBw )
667
        output( '*** Results: %s\n' % result )
668
        return result
669

    
670
    def runCpuLimitTest( self, cpu, duration=5 ):
671
        """run CPU limit test with 'while true' processes.
672
        cpu: desired CPU fraction of each host
673
        duration: test duration in seconds
674
        returns a single list of measured CPU fractions as floats.
675
        """
676
        pct = cpu * 100
677
        info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
678
        hosts = self.hosts
679
        for h in hosts:
680
            h.cmd( 'while true; do a=1; done &' )
681
        pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
682
        pids_str = ",".join(["%s" % pid for pid in pids])
683
        cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
684
        # It's a shame that this is what pylint prefers
685
        outputs = []
686
        for _ in range( duration ):
687
            sleep( 1 )
688
            outputs.append( quietRun( cmd ).strip() )
689
        for h in hosts:
690
            h.cmd( 'kill %1' )
691
        cpu_fractions = []
692
        for test_output in outputs:
693
            # Split by line.  Ignore first line, which looks like this:
694
            # PID %CPU COMMAND\n
695
            for line in test_output.split('\n')[1:]:
696
                r = r'\d+\s*(\d+\.\d+)'
697
                m = re.search( r, line )
698
                if m is None:
699
                    error( '*** Error: could not extract CPU fraction: %s\n' %
700
                           line )
701
                    return None
702
                cpu_fractions.append( float( m.group( 1 ) ) )
703
        output( '*** Results: %s\n' % cpu_fractions )
704
        return cpu_fractions
705

    
706
    # BL: I think this can be rewritten now that we have
707
    # a real link class.
708
    def configLinkStatus( self, src, dst, status ):
709
        """Change status of src <-> dst links.
710
           src: node name
711
           dst: node name
712
           status: string {up, down}"""
713
        if src not in self.nameToNode:
714
            error( 'src not in network: %s\n' % src )
715
        elif dst not in self.nameToNode:
716
            error( 'dst not in network: %s\n' % dst )
717
        else:
718
            if type( src ) is str:
719
                src = self.nameToNode[ src ]
720
            if type( dst ) is str:
721
                dst = self.nameToNode[ dst ]
722
            connections = src.connectionsTo( dst )
723
            if len( connections ) == 0:
724
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
725
            for srcIntf, dstIntf in connections:
726
                result = srcIntf.ifconfig( status )
727
                if result:
728
                    error( 'link src status change failed: %s\n' % result )
729
                result = dstIntf.ifconfig( status )
730
                if result:
731
                    error( 'link dst status change failed: %s\n' % result )
732

    
733
    def interact( self ):
734
        "Start network and run our simple CLI."
735
        self.start()
736
        result = CLI( self )
737
        self.stop()
738
        return result
739

    
740
    inited = False
741

    
742
    @classmethod
743
    def init( cls ):
744
        "Initialize Mininet"
745
        if cls.inited:
746
            return
747
        ensureRoot()
748
        fixLimits()
749
        cls.inited = True
750

    
751

    
752
class MininetWithControlNet( Mininet ):
753

    
754
    """Control network support:
755

756
       Create an explicit control network. Currently this is only
757
       used/usable with the user datapath.
758

759
       Notes:
760

761
       1. If the controller and switches are in the same (e.g. root)
762
          namespace, they can just use the loopback connection.
763

764
       2. If we can get unix domain sockets to work, we can use them
765
          instead of an explicit control network.
766

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

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

773
       5. Basically nobody ever used this code, so it has been moved
774
          into its own class.
775

776
       6. Ultimately we may wish to extend this to allow us to create a
777
          control network which every node's control interface is
778
          attached to."""
779

    
780
    def configureControlNetwork( self ):
781
        "Configure control network."
782
        self.configureRoutedControlNetwork()
783

    
784
    # We still need to figure out the right way to pass
785
    # in the control network location.
786

    
787
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
788
                                       prefixLen=16 ):
789
        """Configure a routed control network on controller and switches.
790
           For use with the user datapath only right now."""
791
        controller = self.controllers[ 0 ]
792
        info( controller.name + ' <->' )
793
        cip = ip
794
        snum = ipParse( ip )
795
        for switch in self.switches:
796
            info( ' ' + switch.name )
797
            link = self.link( switch, controller, port1=0 )
798
            sintf, cintf = link.intf1, link.intf2
799
            switch.controlIntf = sintf
800
            snum += 1
801
            while snum & 0xff in [ 0, 255 ]:
802
                snum += 1
803
            sip = ipStr( snum )
804
            cintf.setIP( cip, prefixLen )
805
            sintf.setIP( sip, prefixLen )
806
            controller.setHostRoute( sip, cintf )
807
            switch.setHostRoute( cip, sintf )
808
        info( '\n' )
809
        info( '*** Testing control network\n' )
810
        while not cintf.isUp():
811
            info( '*** Waiting for', cintf, 'to come up\n' )
812
            sleep( 1 )
813
        for switch in self.switches:
814
            while not sintf.isUp():
815
                info( '*** Waiting for', sintf, 'to come up\n' )
816
                sleep( 1 )
817
            if self.ping( hosts=[ switch, controller ] ) != 0:
818
                error( '*** Error: control network test failed\n' )
819
                exit( 1 )
820
        info( '\n' )