Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 708b1843

History | View | Annotate | Download (31.9 KB)

1
"""
2

3
    Mininet: A simple networking testbed for OpenFlow/SDN!
4

5
author: Bob Lantz (rlantz@cs.stanford.edu)
6
author: Brandon Heller (brandonh@stanford.edu)
7

8
Mininet creates scalable OpenFlow test networks by using
9
process-based virtualization and network namespaces.
10

11
Simulated hosts are created as processes in separate network
12
namespaces. This allows a complete OpenFlow network to be simulated on
13
top of a single Linux kernel.
14

15
Each host has:
16

17
A virtual console (pipes to a shell)
18
A virtual interfaces (half of a veth pair)
19
A parent shell (and possibly some child processes) in a namespace
20

21
Hosts have a network interface which is configured via ifconfig/ip
22
link/etc.
23

24
This version supports both the kernel and user space datapaths
25
from the OpenFlow reference implementation (openflowswitch.org)
26
as well as OpenVSwitch (openvswitch.org.)
27

28
In kernel datapath mode, the controller and switches are simply
29
processes in the root namespace.
30

31
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32
attached to the one side of a veth pair; the other side resides in the
33
host namespace. In this mode, switch processes can simply connect to the
34
controller via the loopback interface.
35

36
In user datapath mode, the controller and switches can be full-service
37
nodes that live in their own network namespaces and have management
38
interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39
currently routed although it could be bridged.)
40

41
In addition to a management interface, user mode switches also have
42
several switch interfaces, halves of veth pairs whose other halves
43
reside in the host nodes that the switches are connected to.
44

45
Consistent, straightforward naming is important in order to easily
46
identify hosts, switches and controllers, both from the CLI and
47
from program code. Interfaces are named to make it easy to identify
48
which interfaces belong to which node.
49

50
The basic naming scheme is as follows:
51

52
    Host nodes are named h1-hN
53
    Switch nodes are named s1-sN
54
    Controller nodes are named c0-cN
55
    Interfaces are named {nodename}-eth0 .. {nodename}-ethN
56

57
Note: If the network topology is created using mininet.topo, then
58
node numbers are unique among hosts and switches (e.g. we have
59
h1..hN and SN..SN+M) and also correspond to their default IP addresses
60
of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61
hN. This mapping allows easy determination of a node's IP
62
address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63

64
Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
65
"ping 10.1" is equivalent to "ping 10.0.0.1".
66

67
Currently we wrap the entire network in a 'mininet' object, which
68
constructs a simulated network based on a network topology created
69
using a topology object (e.g. LinearTopo) from mininet.topo or
70
mininet.topolib, and a Controller which the switches will connect
71
to. Several configuration options are provided for functions such as
72
automatically setting MAC addresses, populating the ARP table, or
73
even running a set of terminals to allow direct interaction with nodes.
74

75
After the network is created, it can be started using start(), and a
76
variety of useful tasks maybe performed, including basic connectivity
77
and bandwidth tests and running the mininet CLI.
78

79
Once the network is up and running, test code can easily get access
80
to host and switch objects which can then be used for arbitrary
81
experiments, typically involving running a series of commands on the
82
hosts.
83

84
After all desired tests or activities have been completed, the stop()
85
method may be called to shut down the network.
86

87
"""
88

    
89
import os
90
import re
91
import select
92
import signal
93
import copy
94
from time import sleep
95
from itertools import chain, groupby
96

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

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

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

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

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

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

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

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

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

    
168

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
362
        info( '*** Adding hosts:\n' )
363
        for hostName in topo.hosts():
364
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
365
            info( hostName + ' ' )
366

    
367
        info( '\n*** Adding switches:\n' )
368
        for switchName in topo.switches():
369
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
370
            info( switchName + ' ' )
371

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

    
380
        info( '\n' )
381

    
382
    def configureControlNetwork( self ):
383
        "Control net config hook: override in subclass"
384
        raise Exception( 'configureControlNetwork: '
385
                         'should be overriden in subclass', self )
386

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

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

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

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

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

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

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

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

    
496
    # XXX These test methods should be moved out of this class.
497
    # Probably we should create a tests.py for them
498

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

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

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

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

    
616
    def pingAll( self, timeout=None ):
617
        """Ping between all hosts.
618
           returns: ploss packet loss percentage"""
619
        return self.ping( timeout=timeout )
620

    
621
    def pingPair( self ):
622
        """Ping between first two hosts, useful for testing.
623
           returns: ploss packet loss percentage"""
624
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
625
        return self.ping( hosts=hosts )
626

    
627
    def pingAllFull( self ):
628
        """Ping between all hosts.
629
           returns: ploss packet loss percentage"""
630
        return self.pingFull()
631

    
632
    def pingPairFull( self ):
633
        """Ping between first two hosts, useful for testing.
634
           returns: ploss packet loss percentage"""
635
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
636
        return self.pingFull( hosts=hosts )
637

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

    
652
    # XXX This should be cleaned up
653

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

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

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

    
763
    def interact( self ):
764
        "Start network and run our simple CLI."
765
        self.start()
766
        result = CLI( self )
767
        self.stop()
768
        return result
769

    
770
    inited = False
771

    
772
    @classmethod
773
    def init( cls ):
774
        "Initialize Mininet"
775
        if cls.inited:
776
            return
777
        ensureRoot()
778
        fixLimits()
779
        cls.inited = True
780

    
781

    
782
class MininetWithControlNet( Mininet ):
783

    
784
    """Control network support:
785

786
       Create an explicit control network. Currently this is only
787
       used/usable with the user datapath.
788

789
       Notes:
790

791
       1. If the controller and switches are in the same (e.g. root)
792
          namespace, they can just use the loopback connection.
793

794
       2. If we can get unix domain sockets to work, we can use them
795
          instead of an explicit control network.
796

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

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

803
       5. Basically nobody ever used this code, so it has been moved
804
          into its own class.
805

806
       6. Ultimately we may wish to extend this to allow us to create a
807
          control network which every node's control interface is
808
          attached to."""
809

    
810
    def configureControlNetwork( self ):
811
        "Configure control network."
812
        self.configureRoutedControlNetwork()
813

    
814
    # We still need to figure out the right way to pass
815
    # in the control network location.
816

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