Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 32507498

History | View | Annotate | Download (30 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
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
        if not controller:
213
            controller = self.controller
214
        controller_new = controller( name, **params )
215
        if controller_new:  # allow controller-less setups
216
            self.controllers.append( controller_new )
217
            self.nameToNode[ name ] = controller_new
218
        return controller_new
219

    
220
    # BL: We now have four ways to look up nodes
221
    # This may (should?) be cleaned up in the future.
222
    def getNodeByName( self, *args ):
223
        "Return node(s) with given name(s)"
224
        if len( args ) == 1:
225
            return self.nameToNode[ args[ 0 ] ]
226
        return [ self.nameToNode[ n ] for n in args ]
227

    
228
    def get( self, *args ):
229
        "Convenience alias for getNodeByName"
230
        return self.getNodeByName( *args )
231

    
232
    # Even more convenient syntax for node lookup and iteration
233
    def __getitem__( self, *args ):
234
        """net [ name ] operator: Return node(s) with given name(s)"""
235
        return self.getNodeByName( *args )
236

    
237
    def __iter__( self ):
238
        "return iterator over nodes"
239
        #or dow we want to iterate of the keys i.e. node.name like a dict
240
        for node in chain( self.hosts, self.switches, self.controllers ):
241
            yield node.name
242

    
243
    def __len__( self ):
244
        "returns number of nodes in net"
245
        return len( self.hosts ) + len( self.switches ) + len( self.controllers )
246

    
247
    def __contains__( self, item ):
248
        "returns True if net contains named node"
249
        return item in self.keys()
250

    
251
    def keys( self ):
252
        "return a list of all node names or net's keys"
253
        return list( self.__iter__() )
254

    
255
    def values( self ):
256
        "return a list of all nodes or net's values"
257
        return [ self[name] for name in self.__iter__() ]
258

    
259
    def items( self ):
260
        "return (key,value) tuple list for every node in net"
261
        return zip( self.keys(), self.values() )
262

    
263
    def addLink( self, node1, node2, port1=None, port2=None,
264
                 cls=None, **params ):
265
        """"Add a link from node1 to node2
266
            node1: source node
267
            node2: dest node
268
            port1: source port
269
            port2: dest port
270
            returns: link object"""
271
        defaults = { 'port1': port1,
272
                     'port2': port2,
273
                     'intf': self.intf }
274
        defaults.update( params )
275
        if not cls:
276
            cls = self.link
277
        return cls( node1, node2, **defaults )
278

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

    
298
    def buildFromTopo( self, topo=None ):
299
        """Build mininet from a topology object
300
           At the end of this function, everything should be connected
301
           and up."""
302

    
303
        # Possibly we should clean up here and/or validate
304
        # the topo
305
        if self.cleanup:
306
            pass
307

    
308
        info( '*** Creating network\n' )
309

    
310
        if not self.controllers:
311
            # Add a default controller
312
            info( '*** Adding controller\n' )
313
            classes = self.controller
314
            if type( classes ) is not list:
315
                classes = [ classes ]
316
            for i, cls in enumerate( classes ):
317
                self.addController( 'c%d' % i, cls )
318

    
319
        info( '*** Adding hosts:\n' )
320
        for hostName in topo.hosts():
321
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
322
            info( hostName + ' ' )
323

    
324
        info( '\n*** Adding switches:\n' )
325
        for switchName in topo.switches():
326
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
327
            info( switchName + ' ' )
328

    
329
        info( '\n*** Adding links:\n' )
330
        for srcName, dstName in topo.links(sort=True):
331
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
332
            params = topo.linkInfo( srcName, dstName )
333
            srcPort, dstPort = topo.port( srcName, dstName )
334
            self.addLink( src, dst, srcPort, dstPort, **params )
335
            info( '(%s, %s) ' % ( src.name, dst.name ) )
336

    
337
        info( '\n' )
338

    
339
    def configureControlNetwork( self ):
340
        "Control net config hook: override in subclass"
341
        raise Exception( 'configureControlNetwork: '
342
                         'should be overriden in subclass', self )
343

    
344
    def build( self ):
345
        "Build mininet."
346
        if self.topo:
347
            self.buildFromTopo( self.topo )
348
        if ( self.inNamespace ):
349
            self.configureControlNetwork()
350
        info( '*** Configuring hosts\n' )
351
        self.configHosts()
352
        if self.xterms:
353
            self.startTerms()
354
        if self.autoStaticArp:
355
            self.staticArp()
356
        self.built = True
357

    
358
    def startTerms( self ):
359
        "Start a terminal for each node."
360
        if 'DISPLAY' not in os.environ:
361
            error( "Error starting terms: Cannot connect to display\n" )
362
            return
363
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
364
        cleanUpScreens()
365
        self.terms += makeTerms( self.controllers, 'controller' )
366
        self.terms += makeTerms( self.switches, 'switch' )
367
        self.terms += makeTerms( self.hosts, 'host' )
368

    
369
    def stopXterms( self ):
370
        "Kill each xterm."
371
        for term in self.terms:
372
            os.kill( term.pid, signal.SIGKILL )
373
        cleanUpScreens()
374

    
375
    def staticArp( self ):
376
        "Add all-pairs ARP entries to remove the need to handle broadcast."
377
        for src in self.hosts:
378
            for dst in self.hosts:
379
                if src != dst:
380
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
381

    
382
    def start( self ):
383
        "Start controller and switches."
384
        if not self.built:
385
            self.build()
386
        info( '*** Starting controller\n' )
387
        for controller in self.controllers:
388
            controller.start()
389
        info( '*** Starting %s switches\n' % len( self.switches ) )
390
        for switch in self.switches:
391
            info( switch.name + ' ')
392
            switch.start( self.controllers )
393
        info( '\n' )
394

    
395
    def stop( self ):
396
        "Stop the controller(s), switches and hosts"
397
        if self.terms:
398
            info( '*** Stopping %i terms\n' % len( self.terms ) )
399
            self.stopXterms()
400
        info( '*** Stopping %i switches\n' % len( self.switches ) )
401
        for switch in self.switches:
402
            info( switch.name + ' ' )
403
            switch.stop()
404
        info( '\n' )
405
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
406
        for host in self.hosts:
407
            info( host.name + ' ' )
408
            host.terminate()
409
        info( '\n' )
410
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
411
        for controller in self.controllers:
412
            info( controller.name + ' ' )
413
            controller.stop()
414
        info( '\n*** Done\n' )
415

    
416
    def run( self, test, *args, **kwargs ):
417
        "Perform a complete start/test/stop cycle."
418
        self.start()
419
        info( '*** Running test\n' )
420
        result = test( *args, **kwargs )
421
        self.stop()
422
        return result
423

    
424
    def monitor( self, hosts=None, timeoutms=-1 ):
425
        """Monitor a set of hosts (or all hosts by default),
426
           and return their output, a line at a time.
427
           hosts: (optional) set of hosts to monitor
428
           timeoutms: (optional) timeout value in ms
429
           returns: iterator which returns host, line"""
430
        if hosts is None:
431
            hosts = self.hosts
432
        poller = select.poll()
433
        Node = hosts[ 0 ]  # so we can call class method fdToNode
434
        for host in hosts:
435
            poller.register( host.stdout )
436
        while True:
437
            ready = poller.poll( timeoutms )
438
            for fd, event in ready:
439
                host = Node.fdToNode( fd )
440
                if event & select.POLLIN:
441
                    line = host.readline()
442
                    if line is not None:
443
                        yield host, line
444
            # Return if non-blocking
445
            if not ready and timeoutms >= 0:
446
                yield None, None
447

    
448
    # XXX These test methods should be moved out of this class.
449
    # Probably we should create a tests.py for them
450

    
451
    @staticmethod
452
    def _parsePing( pingOutput ):
453
        "Parse ping output and return packets sent, received."
454
        # Check for downed link
455
        if 'connect: Network is unreachable' in pingOutput:
456
            return (1, 0)
457
        r = r'(\d+) packets transmitted, (\d+) received'
458
        m = re.search( r, pingOutput )
459
        if m is None:
460
            error( '*** Error: could not parse ping output: %s\n' %
461
                   pingOutput )
462
            return (1, 0)
463
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
464
        return sent, received
465

    
466
    def ping( self, hosts=None, timeout=None ):
467
        """Ping between all specified hosts.
468
           hosts: list of hosts
469
           timeout: time to wait for a response, as string
470
           returns: ploss packet loss percentage"""
471
        # should we check if running?
472
        packets = 0
473
        lost = 0
474
        ploss = None
475
        if not hosts:
476
            hosts = self.hosts
477
            output( '*** Ping: testing ping reachability\n' )
478
        for node in hosts:
479
            output( '%s -> ' % node.name )
480
            for dest in hosts:
481
                if node != dest:
482
                    opts = ''
483
                    if timeout:
484
                        opts = '-W %s' % timeout
485
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
486
                    sent, received = self._parsePing( result )
487
                    packets += sent
488
                    if received > sent:
489
                        error( '*** Error: received too many packets' )
490
                        error( '%s' % result )
491
                        node.cmdPrint( 'route' )
492
                        exit( 1 )
493
                    lost += sent - received
494
                    output( ( '%s ' % dest.name ) if received else 'X ' )
495
            output( '\n' )
496
        if packets > 0:
497
            ploss = 100 * lost / packets
498
            received = packets - lost
499
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
500
                    ( ploss, received, packets ) )
501
        else:
502
            ploss = 0
503
            output( "*** Warning: No packets sent\n" )
504
        return ploss
505

    
506
    @staticmethod
507
    def _parsePingFull( pingOutput ):
508
        "Parse ping output and return all data."
509
        errorTuple = (1, 0, 0, 0, 0, 0)
510
        # Check for downed link
511
        r = r'[uU]nreachable'
512
        m = re.search( r, pingOutput )
513
        if m is not None:
514
            return errorTuple
515
        r = r'(\d+) packets transmitted, (\d+) received'
516
        m = re.search( r, pingOutput )
517
        if m is None:
518
            error( '*** Error: could not parse ping output: %s\n' %
519
                   pingOutput )
520
            return errorTuple
521
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
522
        r = r'rtt min/avg/max/mdev = '
523
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
524
        m = re.search( r, pingOutput )
525
        if m is None:
526
            error( '*** Error: could not parse ping output: %s\n' %
527
                   pingOutput )
528
            return errorTuple
529
        rttmin = float( m.group( 1 ) )
530
        rttavg = float( m.group( 2 ) )
531
        rttmax = float( m.group( 3 ) )
532
        rttdev = float( m.group( 4 ) )
533
        return sent, received, rttmin, rttavg, rttmax, rttdev
534

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

    
568
    def pingAll( self ):
569
        """Ping between all hosts.
570
           returns: ploss packet loss percentage"""
571
        return self.ping()
572

    
573
    def pingPair( self ):
574
        """Ping between first two hosts, useful for testing.
575
           returns: ploss packet loss percentage"""
576
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
577
        return self.ping( hosts=hosts )
578

    
579
    def pingAllFull( self ):
580
        """Ping between all hosts.
581
           returns: ploss packet loss percentage"""
582
        return self.pingFull()
583

    
584
    def pingPairFull( self ):
585
        """Ping between first two hosts, useful for testing.
586
           returns: ploss packet loss percentage"""
587
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
588
        return self.pingFull( hosts=hosts )
589

    
590
    @staticmethod
591
    def _parseIperf( iperfOutput ):
592
        """Parse iperf output and return bandwidth.
593
           iperfOutput: string
594
           returns: result string"""
595
        r = r'([\d\.]+ \w+/sec)'
596
        m = re.findall( r, iperfOutput )
597
        if m:
598
            return m[-1]
599
        else:
600
            # was: raise Exception(...)
601
            error( 'could not parse iperf output: ' + iperfOutput )
602
            return ''
603

    
604
    # XXX This should be cleaned up
605

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

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

    
686
    # BL: I think this can be rewritten now that we have
687
    # a real link class.
688
    def configLinkStatus( self, src, dst, status ):
689
        """Change status of src <-> dst links.
690
           src: node name
691
           dst: node name
692
           status: string {up, down}"""
693
        if src not in self.nameToNode:
694
            error( 'src not in network: %s\n' % src )
695
        elif dst not in self.nameToNode:
696
            error( 'dst not in network: %s\n' % dst )
697
        else:
698
            if type( src ) is str:
699
                src = self.nameToNode[ src ]
700
            if type( dst ) is str:
701
                dst = self.nameToNode[ dst ]
702
            connections = src.connectionsTo( dst )
703
            if len( connections ) == 0:
704
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
705
            for srcIntf, dstIntf in connections:
706
                result = srcIntf.ifconfig( status )
707
                if result:
708
                    error( 'link src status change failed: %s\n' % result )
709
                result = dstIntf.ifconfig( status )
710
                if result:
711
                    error( 'link dst status change failed: %s\n' % result )
712

    
713
    def interact( self ):
714
        "Start network and run our simple CLI."
715
        self.start()
716
        result = CLI( self )
717
        self.stop()
718
        return result
719

    
720
    inited = False
721

    
722
    @classmethod
723
    def init( cls ):
724
        "Initialize Mininet"
725
        if cls.inited:
726
            return
727
        ensureRoot()
728
        fixLimits()
729
        cls.inited = True
730

    
731

    
732
class MininetWithControlNet( Mininet ):
733

    
734
    """Control network support:
735

736
       Create an explicit control network. Currently this is only
737
       used/usable with the user datapath.
738

739
       Notes:
740

741
       1. If the controller and switches are in the same (e.g. root)
742
          namespace, they can just use the loopback connection.
743

744
       2. If we can get unix domain sockets to work, we can use them
745
          instead of an explicit control network.
746

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

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

753
       5. Basically nobody ever used this code, so it has been moved
754
          into its own class.
755

756
       6. Ultimately we may wish to extend this to allow us to create a
757
          control network which every node's control interface is
758
          attached to."""
759

    
760
    def configureControlNetwork( self ):
761
        "Configure control network."
762
        self.configureRoutedControlNetwork()
763

    
764
    # We still need to figure out the right way to pass
765
    # in the control network location.
766

    
767
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
768
                                       prefixLen=16 ):
769
        """Configure a routed control network on controller and switches.
770
           For use with the user datapath only right now."""
771
        controller = self.controllers[ 0 ]
772
        info( controller.name + ' <->' )
773
        cip = ip
774
        snum = ipParse( ip )
775
        for switch in self.switches:
776
            info( ' ' + switch.name )
777
            link = self.link( switch, controller, port1=0 )
778
            sintf, cintf = link.intf1, link.intf2
779
            switch.controlIntf = sintf
780
            snum += 1
781
            while snum & 0xff in [ 0, 255 ]:
782
                snum += 1
783
            sip = ipStr( snum )
784
            cintf.setIP( cip, prefixLen )
785
            sintf.setIP( sip, prefixLen )
786
            controller.setHostRoute( sip, cintf )
787
            switch.setHostRoute( cip, sintf )
788
        info( '\n' )
789
        info( '*** Testing control network\n' )
790
        while not cintf.isUp():
791
            info( '*** Waiting for', cintf, 'to come up\n' )
792
            sleep( 1 )
793
        for switch in self.switches:
794
            while not sintf.isUp():
795
                info( '*** Waiting for', sintf, 'to come up\n' )
796
                sleep( 1 )
797
            if self.ping( hosts=[ switch, controller ] ) != 0:
798
                error( '*** Error: control network test failed\n' )
799
                exit( 1 )
800
        info( '\n' )