Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 4797b420

History | View | Annotate | Download (31.7 KB)

1
"""
2

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

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

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

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

15
Each host has:
16

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

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

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

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

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

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

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

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

50
The basic naming scheme is as follows:
51

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

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

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

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

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

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

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

87
"""
88

    
89
import os
90
import re
91
import select
92
import signal
93
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

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

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

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

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

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

    
167
        if waitConnected:
168
            self.waitConnected()
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
349
        info( '*** Creating network\n' )
350

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

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

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

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

    
378
        info( '\n' )
379

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

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

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

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

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

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

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

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

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

    
492
    # XXX These test methods should be moved out of this class.
493
    # Probably we should create a tests.py for them
494

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

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

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

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

    
612
    def pingAll( self, timeout=None ):
613
        """Ping between all hosts.
614
           returns: ploss packet loss percentage"""
615
        return self.ping( timeout=timeout )
616

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

    
623
    def pingAllFull( self ):
624
        """Ping between all hosts.
625
           returns: ploss packet loss percentage"""
626
        return self.pingFull()
627

    
628
    def pingPairFull( self ):
629
        """Ping between first two hosts, useful for testing.
630
           returns: ploss packet loss percentage"""
631
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
632
        return self.pingFull( hosts=hosts )
633

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

    
648
    # XXX This should be cleaned up
649

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

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

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

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

    
764
    inited = False
765

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

    
775

    
776
class MininetWithControlNet( Mininet ):
777

    
778
    """Control network support:
779

780
       Create an explicit control network. Currently this is only
781
       used/usable with the user datapath.
782

783
       Notes:
784

785
       1. If the controller and switches are in the same (e.g. root)
786
          namespace, they can just use the loopback connection.
787

788
       2. If we can get unix domain sockets to work, we can use them
789
          instead of an explicit control network.
790

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

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

797
       5. Basically nobody ever used this code, so it has been moved
798
          into its own class.
799

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

    
804
    def configureControlNetwork( self ):
805
        "Configure control network."
806
        self.configureRoutedControlNetwork()
807

    
808
    # We still need to figure out the right way to pass
809
    # in the control network location.
810

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