Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 72fd120d

History | View | Annotate | Download (30.6 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, groupby
95

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

    
104
# Mininet version: should be consistent with README and LICENSE
105
VERSION = "2.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
            # bookmark
224
            controller_new = controller( name, **params )
225
        # Add new controller to net
226
        if controller_new: # allow controller-less setups
227
            self.controllers.append( controller_new )
228
            self.nameToNode[ name ] = controller_new
229
        return controller_new
230

    
231
    # BL: We now have four ways to look up nodes
232
    # This may (should?) be cleaned up in the future.
233
    def getNodeByName( self, *args ):
234
        "Return node(s) with given name(s)"
235
        if len( args ) == 1:
236
            return self.nameToNode[ args[ 0 ] ]
237
        return [ self.nameToNode[ n ] for n in args ]
238

    
239
    def get( self, *args ):
240
        "Convenience alias for getNodeByName"
241
        return self.getNodeByName( *args )
242

    
243
    # Even more convenient syntax for node lookup and iteration
244
    def __getitem__( self, key ):
245
        """net [ name ] operator: Return node(s) with given name(s)"""
246
        return self.nameToNode[ key ]
247

    
248
    def __iter__( self ):
249
        "return iterator over node names"
250
        for node in chain( self.hosts, self.switches, self.controllers ):
251
            yield node.name
252

    
253
    def __len__( self ):
254
        "returns number of nodes in net"
255
        return ( len( self.hosts ) + len( self.switches ) +
256
                 len( self.controllers ) )
257

    
258
    def __contains__( self, item ):
259
        "returns True if net contains named node"
260
        return item in self.nameToNode
261

    
262
    def keys( self ):
263
        "return a list of all node names or net's keys"
264
        return list( self )
265

    
266
    def values( self ):
267
        "return a list of all nodes or net's values"
268
        return [ self[name] for name in self ]
269

    
270
    def items( self ):
271
        "return (key,value) tuple list for every node in net"
272
        return zip( self.keys(), self.values() )
273

    
274
    def addLink( self, node1, node2, port1=None, port2=None,
275
                 cls=None, **params ):
276
        """"Add a link from node1 to node2
277
            node1: source node
278
            node2: dest node
279
            port1: source port
280
            port2: dest port
281
            returns: link object"""
282
        defaults = { 'port1': port1,
283
                     'port2': port2,
284
                     'intf': self.intf }
285
        defaults.update( params )
286
        if not cls:
287
            cls = self.link
288
        return cls( node1, node2, **defaults )
289

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

    
309
    def buildFromTopo( self, topo=None ):
310
        """Build mininet from a topology object
311
           At the end of this function, everything should be connected
312
           and up."""
313

    
314
        # Possibly we should clean up here and/or validate
315
        # the topo
316
        if self.cleanup:
317
            pass
318

    
319
        info( '*** Creating network\n' )
320

    
321
        if not self.controllers and self.controller:
322
            # Add a default controller
323
            info( '*** Adding controller\n' )
324
            classes = self.controller
325
            if type( classes ) is not list:
326
                classes = [ classes ]
327
            for i, cls in enumerate( classes ):
328
                self.addController( 'c%d' % i, cls )
329

    
330
        info( '*** Adding hosts:\n' )
331
        for hostName in topo.hosts():
332
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
333
            info( hostName + ' ' )
334

    
335
        info( '\n*** Adding switches:\n' )
336
        for switchName in topo.switches():
337
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
338
            info( switchName + ' ' )
339

    
340
        info( '\n*** Adding links:\n' )
341
        for srcName, dstName in topo.links(sort=True):
342
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
343
            params = topo.linkInfo( srcName, dstName )
344
            srcPort, dstPort = topo.port( srcName, dstName )
345
            self.addLink( src, dst, srcPort, dstPort, **params )
346
            info( '(%s, %s) ' % ( src.name, dst.name ) )
347

    
348
        info( '\n' )
349

    
350
    def configureControlNetwork( self ):
351
        "Control net config hook: override in subclass"
352
        raise Exception( 'configureControlNetwork: '
353
                         'should be overriden in subclass', self )
354

    
355
    def build( self ):
356
        "Build mininet."
357
        if self.topo:
358
            self.buildFromTopo( self.topo )
359
        if self.inNamespace:
360
            self.configureControlNetwork()
361
        info( '*** Configuring hosts\n' )
362
        self.configHosts()
363
        if self.xterms:
364
            self.startTerms()
365
        if self.autoStaticArp:
366
            self.staticArp()
367
        self.built = True
368

    
369
    def startTerms( self ):
370
        "Start a terminal for each node."
371
        if 'DISPLAY' not in os.environ:
372
            error( "Error starting terms: Cannot connect to display\n" )
373
            return
374
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
375
        cleanUpScreens()
376
        self.terms += makeTerms( self.controllers, 'controller' )
377
        self.terms += makeTerms( self.switches, 'switch' )
378
        self.terms += makeTerms( self.hosts, 'host' )
379

    
380
    def stopXterms( self ):
381
        "Kill each xterm."
382
        for term in self.terms:
383
            os.kill( term.pid, signal.SIGKILL )
384
        cleanUpScreens()
385

    
386
    def staticArp( self ):
387
        "Add all-pairs ARP entries to remove the need to handle broadcast."
388
        for src in self.hosts:
389
            for dst in self.hosts:
390
                if src != dst:
391
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
392

    
393
    def start( self ):
394
        "Start controller and switches."
395
        if not self.built:
396
            self.build()
397
        info( '*** Starting controller\n' )
398
        for controller in self.controllers:
399
            controller.start()
400
        info( '*** Starting %s switches\n' % len( self.switches ) )
401
        for switch in self.switches:
402
            info( switch.name + ' ')
403
            switch.start( self.controllers )
404
        info( '\n' )
405

    
406
    def stop( self ):
407
        "Stop the controller(s), switches and hosts"
408
        if self.terms:
409
            info( '*** Stopping %i terms\n' % len( self.terms ) )
410
            self.stopXterms()
411
        info( '*** Stopping %i switches\n' % len( self.switches ) )
412
        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
413
            if hasattr( swclass, 'batchShutdown' ):
414
                swclass.batchShutdown( switches )
415
        for switch in self.switches:
416
            info( switch.name + ' ' )
417
            switch.stop()
418
        info( '\n' )
419
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
420
        for host in self.hosts:
421
            info( host.name + ' ' )
422
            host.terminate()
423
        info( '\n' )
424
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
425
        for controller in self.controllers:
426
            info( controller.name + ' ' )
427
            controller.stop()
428
        info( '\n*** Done\n' )
429

    
430
    def run( self, test, *args, **kwargs ):
431
        "Perform a complete start/test/stop cycle."
432
        self.start()
433
        info( '*** Running test\n' )
434
        result = test( *args, **kwargs )
435
        self.stop()
436
        return result
437

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

    
462
    # XXX These test methods should be moved out of this class.
463
    # Probably we should create a tests.py for them
464

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

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

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

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

    
582
    def pingAll( self, timeout=None ):
583
        """Ping between all hosts.
584
           returns: ploss packet loss percentage"""
585
        return self.ping( timeout=timeout )
586

    
587
    def pingPair( self ):
588
        """Ping between first two hosts, useful for testing.
589
           returns: ploss packet loss percentage"""
590
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
591
        return self.ping( hosts=hosts )
592

    
593
    def pingAllFull( self ):
594
        """Ping between all hosts.
595
           returns: ploss packet loss percentage"""
596
        return self.pingFull()
597

    
598
    def pingPairFull( self ):
599
        """Ping between first two hosts, useful for testing.
600
           returns: ploss packet loss percentage"""
601
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
602
        return self.pingFull( hosts=hosts )
603

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

    
618
    # XXX This should be cleaned up
619

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

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

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

    
727
    def interact( self ):
728
        "Start network and run our simple CLI."
729
        self.start()
730
        result = CLI( self )
731
        self.stop()
732
        return result
733

    
734
    inited = False
735

    
736
    @classmethod
737
    def init( cls ):
738
        "Initialize Mininet"
739
        if cls.inited:
740
            return
741
        ensureRoot()
742
        fixLimits()
743
        cls.inited = True
744

    
745

    
746
class MininetWithControlNet( Mininet ):
747

    
748
    """Control network support:
749

750
       Create an explicit control network. Currently this is only
751
       used/usable with the user datapath.
752

753
       Notes:
754

755
       1. If the controller and switches are in the same (e.g. root)
756
          namespace, they can just use the loopback connection.
757

758
       2. If we can get unix domain sockets to work, we can use them
759
          instead of an explicit control network.
760

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

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

767
       5. Basically nobody ever used this code, so it has been moved
768
          into its own class.
769

770
       6. Ultimately we may wish to extend this to allow us to create a
771
          control network which every node's control interface is
772
          attached to."""
773

    
774
    def configureControlNetwork( self ):
775
        "Configure control network."
776
        self.configureRoutedControlNetwork()
777

    
778
    # We still need to figure out the right way to pass
779
    # in the control network location.
780

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