Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 2a2d6050

History | View | Annotate | Download (34 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 random
94
import copy
95
from time import sleep
96
from itertools import chain, groupby
97
from math import ceil
98

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

    
108
# Mininet version: should be consistent with README and LICENSE
109
VERSION = "2.1.0+"
110

    
111
class Mininet( object ):
112
    "Network emulation with hosts spawned in network namespaces."
113

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

    
157
        self.hosts = []
158
        self.switches = []
159
        self.controllers = []
160
        self.links = []
161

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

    
164
        self.terms = []  # list of spawned xterm processes
165

    
166
        Mininet.init()  # Initialize Mininet if necessary
167

    
168
        self.built = False
169
        if topo and build:
170
            self.build()
171

    
172

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

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

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

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

    
267
    def addNAT( self, name='nat0', connect=True, inNamespace=False, **params ):
268
        nat = self.addHost( name, cls=NAT, inNamespace=inNamespace, 
269
                            subnet=self.ipBase, **params )
270
        # find first switch and create link
271
        if connect:
272
            # connect the nat to the first switch
273
            self.addLink( nat, self.switches[ 0 ] )
274
            # set the default route on hosts
275
            natIP = nat.params[ 'ip' ].split('/')[ 0 ]
276
            for host in self.hosts:
277
                if host.inNamespace:
278
                    host.setDefaultRoute( 'via %s' % natIP )
279
        return nat
280

    
281
    # BL: We now have four ways to look up nodes
282
    # This may (should?) be cleaned up in the future.
283
    def getNodeByName( self, *args ):
284
        "Return node(s) with given name(s)"
285
        if len( args ) == 1:
286
            return self.nameToNode[ args[ 0 ] ]
287
        return [ self.nameToNode[ n ] for n in args ]
288

    
289
    def get( self, *args ):
290
        "Convenience alias for getNodeByName"
291
        return self.getNodeByName( *args )
292

    
293
    # Even more convenient syntax for node lookup and iteration
294
    def __getitem__( self, key ):
295
        """net [ name ] operator: Return node(s) with given name(s)"""
296
        return self.nameToNode[ key ]
297

    
298
    def __iter__( self ):
299
        "return iterator over node names"
300
        for node in chain( self.hosts, self.switches, self.controllers ):
301
            yield node.name
302

    
303
    def __len__( self ):
304
        "returns number of nodes in net"
305
        return ( len( self.hosts ) + len( self.switches ) +
306
                 len( self.controllers ) )
307

    
308
    def __contains__( self, item ):
309
        "returns True if net contains named node"
310
        return item in self.nameToNode
311

    
312
    def keys( self ):
313
        "return a list of all node names or net's keys"
314
        return list( self )
315

    
316
    def values( self ):
317
        "return a list of all nodes or net's values"
318
        return [ self[name] for name in self ]
319

    
320
    def items( self ):
321
        "return (key,value) tuple list for every node in net"
322
        return zip( self.keys(), self.values() )
323

    
324
    @staticmethod
325
    def randMac():
326
        "Return a random, non-multicast MAC address"
327
        return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff  |
328
                            0x020000000000 )
329
    
330
    def addLink( self, node1, node2, port1=None, port2=None,
331
                 cls=None, **params ):
332
        """"Add a link from node1 to node2
333
            node1: source node (or name)
334
            node2: dest node (or name)
335
            port1: source port (optional)
336
            port2: dest port (optional)
337
            cls: link class (optional)
338
            params: additional link params (optional)
339
            returns: link object"""
340
        node1 = node1 if type( node1 ) != str else self[ node1 ]
341
        node2 = node2 if type( node2 ) != str else self[ node2 ]
342
        options = dict( params )
343
        options.setdefault( 'addr1', self.randMac() )
344
        options.setdefault( 'addr2', self.randMac() )
345
        cls = self.link if cls is None else cls
346
        link = cls( node1, node2, **options )
347
        self.links.append( link )
348
        return link
349

    
350
    def configHosts( self ):
351
        "Configure a set of hosts."
352
        for host in self.hosts:
353
            info( host.name + ' ' )
354
            intf = host.defaultIntf()
355
            if intf:
356
                host.configDefault()
357
            else:
358
                # Don't configure nonexistent intf
359
                host.configDefault( ip=None, mac=None )
360
            # You're low priority, dude!
361
            # BL: do we want to do this here or not?
362
            # May not make sense if we have CPU lmiting...
363
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
364
            # This may not be the right place to do this, but
365
            # it needs to be done somewhere.
366
        info( '\n' )
367

    
368
    def buildFromTopo( self, topo=None ):
369
        """Build mininet from a topology object
370
           At the end of this function, everything should be connected
371
           and up."""
372

    
373
        # Possibly we should clean up here and/or validate
374
        # the topo
375
        if self.cleanup:
376
            pass
377

    
378
        info( '*** Creating network\n' )
379

    
380
        if not self.controllers and self.controller:
381
            # Add a default controller
382
            info( '*** Adding controller\n' )
383
            classes = self.controller
384
            if type( classes ) is not list:
385
                classes = [ classes ]
386
            for i, cls in enumerate( classes ):
387
                # Allow Controller objects because nobody understands currying
388
                if isinstance( cls, Controller ):
389
                    self.addController( cls )
390
                else:
391
                    self.addController( 'c%d' % i, cls )
392

    
393
        info( '*** Adding hosts:\n' )
394
        for hostName in topo.hosts():
395
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
396
            info( hostName + ' ' )
397

    
398
        info( '\n*** Adding switches:\n' )
399
        for switchName in topo.switches():
400
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
401
            info( switchName + ' ' )
402

    
403
        info( '\n*** Adding links:\n' )
404
        for srcName, dstName, params in topo.links(
405
                sort=True, withInfo=True ):
406
            self.addLink( **params )
407
            info( '(%s, %s) ' % ( srcName, dstName ) )
408

    
409
        info( '\n' )
410

    
411
    def configureControlNetwork( self ):
412
        "Control net config hook: override in subclass"
413
        raise Exception( 'configureControlNetwork: '
414
                         'should be overriden in subclass', self )
415

    
416
    def build( self ):
417
        "Build mininet."
418
        if self.topo:
419
            self.buildFromTopo( self.topo )
420
        if self.inNamespace:
421
            self.configureControlNetwork()
422
        info( '*** Configuring hosts\n' )
423
        self.configHosts()
424
        if self.xterms:
425
            self.startTerms()
426
        if self.autoStaticArp:
427
            self.staticArp()
428
        self.built = True
429

    
430
    def startTerms( self ):
431
        "Start a terminal for each node."
432
        if 'DISPLAY' not in os.environ:
433
            error( "Error starting terms: Cannot connect to display\n" )
434
            return
435
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
436
        cleanUpScreens()
437
        self.terms += makeTerms( self.controllers, 'controller' )
438
        self.terms += makeTerms( self.switches, 'switch' )
439
        self.terms += makeTerms( self.hosts, 'host' )
440

    
441
    def stopXterms( self ):
442
        "Kill each xterm."
443
        for term in self.terms:
444
            os.kill( term.pid, signal.SIGKILL )
445
        cleanUpScreens()
446

    
447
    def staticArp( self ):
448
        "Add all-pairs ARP entries to remove the need to handle broadcast."
449
        for src in self.hosts:
450
            for dst in self.hosts:
451
                if src != dst:
452
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
453

    
454
    def start( self ):
455
        "Start controller and switches."
456
        if not self.built:
457
            self.build()
458
        info( '*** Starting controller\n' )
459
        for controller in self.controllers:
460
            controller.start()
461
        info( '*** Starting %s switches\n' % len( self.switches ) )
462
        for switch in self.switches:
463
            info( switch.name + ' ')
464
            switch.start( self.controllers )
465
        info( '\n' )
466
        if self.waitConn:
467
            self.waitConnected()
468

    
469
    def stop( self ):
470
        "Stop the controller(s), switches and hosts"
471
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
472
        for controller in self.controllers:
473
            info( controller.name + ' ' )
474
            controller.stop()
475
        info( '\n' )
476
        if self.terms:
477
            info( '*** Stopping %i terms\n' % len( self.terms ) )
478
            self.stopXterms()
479
        info( '*** Stopping %i switches\n' % len( self.switches ) )
480
        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
481
            if hasattr( swclass, 'batchShutdown' ):
482
                swclass.batchShutdown( switches )
483
        for switch in self.switches:
484
            info( switch.name + ' ' )
485
            switch.stop()
486
            switch.terminate()
487
        info( '\n' )
488
        info( '*** Stopping %i links\n' % len( self.links ) )
489
        for link in self.links:
490
            link.stop()
491
        info( '\n' )
492
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
493
        for host in self.hosts:
494
            info( host.name + ' ' )
495
            host.terminate()
496
        info( '\n*** Done\n' )
497

    
498
    def run( self, test, *args, **kwargs ):
499
        "Perform a complete start/test/stop cycle."
500
        self.start()
501
        info( '*** Running test\n' )
502
        result = test( *args, **kwargs )
503
        self.stop()
504
        return result
505

    
506
    def monitor( self, hosts=None, timeoutms=-1 ):
507
        """Monitor a set of hosts (or all hosts by default),
508
           and return their output, a line at a time.
509
           hosts: (optional) set of hosts to monitor
510
           timeoutms: (optional) timeout value in ms
511
           returns: iterator which returns host, line"""
512
        if hosts is None:
513
            hosts = self.hosts
514
        poller = select.poll()
515
        Node = hosts[ 0 ]  # so we can call class method fdToNode
516
        for host in hosts:
517
            poller.register( host.stdout )
518
        while True:
519
            ready = poller.poll( timeoutms )
520
            for fd, event in ready:
521
                host = Node.fdToNode( fd )
522
                if event & select.POLLIN:
523
                    line = host.readline()
524
                    if line is not None:
525
                        yield host, line
526
            # Return if non-blocking
527
            if not ready and timeoutms >= 0:
528
                yield None, None
529

    
530
    # XXX These test methods should be moved out of this class.
531
    # Probably we should create a tests.py for them
532

    
533
    @staticmethod
534
    def _parsePing( pingOutput ):
535
        "Parse ping output and return packets sent, received."
536
        # Check for downed link
537
        if 'connect: Network is unreachable' in pingOutput:
538
            return 1, 0
539
        r = r'(\d+) packets transmitted, (\d+) received'
540
        m = re.search( r, pingOutput )
541
        if m is None:
542
            error( '*** Error: could not parse ping output: %s\n' %
543
                   pingOutput )
544
            return 1, 0
545
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
546
        return sent, received
547

    
548
    def ping( self, hosts=None, timeout=None ):
549
        """Ping between all specified hosts.
550
           hosts: list of hosts
551
           timeout: time to wait for a response, as string
552
           returns: ploss packet loss percentage"""
553
        # should we check if running?
554
        packets = 0
555
        lost = 0
556
        ploss = None
557
        if not hosts:
558
            hosts = self.hosts
559
            output( '*** Ping: testing ping reachability\n' )
560
        for node in hosts:
561
            output( '%s -> ' % node.name )
562
            for dest in hosts:
563
                if node != dest:
564
                    opts = ''
565
                    if timeout:
566
                        opts = '-W %s' % timeout
567
                    if dest.intfs:
568
                        result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
569
                        sent, received = self._parsePing( result )
570
                    else:
571
                        sent, received = 0, 0
572
                    packets += sent
573
                    if received > sent:
574
                        error( '*** Error: received too many packets' )
575
                        error( '%s' % result )
576
                        node.cmdPrint( 'route' )
577
                        exit( 1 )
578
                    lost += sent - received
579
                    output( ( '%s ' % dest.name ) if received else 'X ' )
580
            output( '\n' )
581
        if packets > 0:
582
            ploss = 100.0 * lost / packets
583
            received = packets - lost
584
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
585
                    ( ploss, received, packets ) )
586
        else:
587
            ploss = 0
588
            output( "*** Warning: No packets sent\n" )
589
        return ploss
590

    
591
    @staticmethod
592
    def _parsePingFull( pingOutput ):
593
        "Parse ping output and return all data."
594
        errorTuple = (1, 0, 0, 0, 0, 0)
595
        # Check for downed link
596
        r = r'[uU]nreachable'
597
        m = re.search( r, pingOutput )
598
        if m is not None:
599
            return errorTuple
600
        r = r'(\d+) packets transmitted, (\d+) received'
601
        m = re.search( r, pingOutput )
602
        if m is None:
603
            error( '*** Error: could not parse ping output: %s\n' %
604
                   pingOutput )
605
            return errorTuple
606
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
607
        r = r'rtt min/avg/max/mdev = '
608
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
609
        m = re.search( r, pingOutput )
610
        if m is None:
611
            if received == 0:
612
                return errorTuple
613
            error( '*** Error: could not parse ping output: %s\n' %
614
                   pingOutput )
615
            return errorTuple
616
        rttmin = float( m.group( 1 ) )
617
        rttavg = float( m.group( 2 ) )
618
        rttmax = float( m.group( 3 ) )
619
        rttdev = float( m.group( 4 ) )
620
        return sent, received, rttmin, rttavg, rttmax, rttdev
621

    
622
    def pingFull( self, hosts=None, timeout=None ):
623
        """Ping between all specified hosts and return all data.
624
           hosts: list of hosts
625
           timeout: time to wait for a response, as string
626
           returns: all ping data; see function body."""
627
        # should we check if running?
628
        # Each value is a tuple: (src, dsd, [all ping outputs])
629
        all_outputs = []
630
        if not hosts:
631
            hosts = self.hosts
632
            output( '*** Ping: testing ping reachability\n' )
633
        for node in hosts:
634
            output( '%s -> ' % node.name )
635
            for dest in hosts:
636
                if node != dest:
637
                    opts = ''
638
                    if timeout:
639
                        opts = '-W %s' % timeout
640
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
641
                    outputs = self._parsePingFull( result )
642
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
643
                    all_outputs.append( (node, dest, outputs) )
644
                    output( ( '%s ' % dest.name ) if received else 'X ' )
645
            output( '\n' )
646
        output( "*** Results: \n" )
647
        for outputs in all_outputs:
648
            src, dest, ping_outputs = outputs
649
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
650
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
651
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
652
                    (rttmin, rttavg, rttmax, rttdev) )
653
        return all_outputs
654

    
655
    def pingAll( self, timeout=None ):
656
        """Ping between all hosts.
657
           returns: ploss packet loss percentage"""
658
        return self.ping( timeout=timeout )
659

    
660
    def pingPair( self ):
661
        """Ping between first two hosts, useful for testing.
662
           returns: ploss packet loss percentage"""
663
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
664
        return self.ping( hosts=hosts )
665

    
666
    def pingAllFull( self ):
667
        """Ping between all hosts.
668
           returns: ploss packet loss percentage"""
669
        return self.pingFull()
670

    
671
    def pingPairFull( self ):
672
        """Ping between first two hosts, useful for testing.
673
           returns: ploss packet loss percentage"""
674
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
675
        return self.pingFull( hosts=hosts )
676

    
677
    @staticmethod
678
    def _parseIperf( iperfOutput ):
679
        """Parse iperf output and return bandwidth.
680
           iperfOutput: string
681
           returns: result string"""
682
        r = r'([\d\.]+ \w+/sec)'
683
        m = re.findall( r, iperfOutput )
684
        if m:
685
            return m[-1]
686
        else:
687
            # was: raise Exception(...)
688
            error( 'could not parse iperf output: ' + iperfOutput )
689
            return ''
690

    
691
    # XXX This should be cleaned up
692

    
693
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
694
        """Run iperf between two hosts.
695
           hosts: list of hosts; if None, uses opposite hosts
696
           l4Type: string, one of [ TCP, UDP ]
697
           returns: results two-element array of [ server, client ] speeds
698
           note: send() is buffered, so client rate can be much higher than
699
           the actual transmission rate; on an unloaded system, server
700
           rate should be much closer to the actual receive rate"""
701
        if not quietRun( 'which telnet' ):
702
            error( 'Cannot find telnet in $PATH - required for iperf test' )
703
            return
704
        if not hosts:
705
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
706
        else:
707
            assert len( hosts ) == 2
708
        client, server = hosts
709
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
710
        output( "%s and %s\n" % ( client.name, server.name ) )
711
        server.cmd( 'killall -9 iperf' )
712
        iperfArgs = 'iperf '
713
        bwArgs = ''
714
        if l4Type == 'UDP':
715
            iperfArgs += '-u '
716
            bwArgs = '-b ' + udpBw + ' '
717
        elif l4Type != 'TCP':
718
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
719
        if format:
720
          iperfArgs += '-f %s ' %format
721
        server.sendCmd( iperfArgs + '-s', printPid=True )
722
        servout = ''
723
        while server.lastPid is None:
724
            servout += server.monitor()
725
        if l4Type == 'TCP':
726
            while 'Connected' not in client.cmd(
727
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
728
                info( 'Waiting for iperf to start up...' )
729
                sleep(.5)
730
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
731
                             bwArgs )
732
        debug( 'Client output: %s\n' % cliout )
733
        server.sendInt()
734
        servout += server.waitOutput()
735
        debug( 'Server output: %s\n' % servout )
736
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
737
        if l4Type == 'UDP':
738
            result.insert( 0, udpBw )
739
        output( '*** Results: %s\n' % result )
740
        return result
741

    
742
    def runCpuLimitTest( self, cpu, duration=5 ):
743
        """run CPU limit test with 'while true' processes.
744
        cpu: desired CPU fraction of each host
745
        duration: test duration in seconds
746
        returns a single list of measured CPU fractions as floats.
747
        """
748
        cores = int( quietRun( 'nproc' ) )
749
        pct = cpu * 100
750
        info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
751
        hosts = self.hosts
752
        cores = int( quietRun( 'nproc' ) )
753
        # number of processes to run a while loop on per host
754
        num_procs = int( ceil( cores * cpu ) )
755
        pids = {}
756
        for h in hosts:
757
            pids[ h ] = []
758
            for _core in range( num_procs ):
759
                h.cmd( 'while true; do a=1; done &' )
760
                pids[ h ].append( h.cmd( 'echo $!' ).strip() )
761
        outputs = {}
762
        time = {}
763
        # get the initial cpu time for each host
764
        for host in hosts:
765
            outputs[ host ] = []
766
            with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
767
                time[ host ] = float( f.read() )
768
        for _ in range( 5 ):
769
            sleep( 1 )
770
            for host in hosts:
771
                with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
772
                    readTime = float( f.read() )
773
                outputs[ host ].append( ( ( readTime - time[ host ] )
774
                                        / 1000000000 ) / cores * 100 )
775
                time[ host ] = readTime
776
        for h, pids in pids.items():
777
            for pid in pids:
778
                h.cmd( 'kill -9 %s' % pid )
779
        cpu_fractions = []
780
        for _host, outputs in outputs.items():
781
            for pct in outputs:
782
                cpu_fractions.append( pct )
783
        output( '*** Results: %s\n' % cpu_fractions )
784
        return cpu_fractions
785

    
786
    # BL: I think this can be rewritten now that we have
787
    # a real link class.
788
    def configLinkStatus( self, src, dst, status ):
789
        """Change status of src <-> dst links.
790
           src: node name
791
           dst: node name
792
           status: string {up, down}"""
793
        if src not in self.nameToNode:
794
            error( 'src not in network: %s\n' % src )
795
        elif dst not in self.nameToNode:
796
            error( 'dst not in network: %s\n' % dst )
797
        else:
798
            if type( src ) is str:
799
                src = self.nameToNode[ src ]
800
            if type( dst ) is str:
801
                dst = self.nameToNode[ dst ]
802
            connections = src.connectionsTo( dst )
803
            if len( connections ) == 0:
804
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
805
            for srcIntf, dstIntf in connections:
806
                result = srcIntf.ifconfig( status )
807
                if result:
808
                    error( 'link src status change failed: %s\n' % result )
809
                result = dstIntf.ifconfig( status )
810
                if result:
811
                    error( 'link dst status change failed: %s\n' % result )
812

    
813
    def interact( self ):
814
        "Start network and run our simple CLI."
815
        self.start()
816
        result = CLI( self )
817
        self.stop()
818
        return result
819

    
820
    inited = False
821

    
822
    @classmethod
823
    def init( cls ):
824
        "Initialize Mininet"
825
        if cls.inited:
826
            return
827
        ensureRoot()
828
        fixLimits()
829
        cls.inited = True
830

    
831

    
832
class MininetWithControlNet( Mininet ):
833

    
834
    """Control network support:
835

836
       Create an explicit control network. Currently this is only
837
       used/usable with the user datapath.
838

839
       Notes:
840

841
       1. If the controller and switches are in the same (e.g. root)
842
          namespace, they can just use the loopback connection.
843

844
       2. If we can get unix domain sockets to work, we can use them
845
          instead of an explicit control network.
846

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

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

853
       5. Basically nobody ever used this code, so it has been moved
854
          into its own class.
855

856
       6. Ultimately we may wish to extend this to allow us to create a
857
          control network which every node's control interface is
858
          attached to."""
859

    
860
    def configureControlNetwork( self ):
861
        "Configure control network."
862
        self.configureRoutedControlNetwork()
863

    
864
    # We still need to figure out the right way to pass
865
    # in the control network location.
866

    
867
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
868
                                       prefixLen=16 ):
869
        """Configure a routed control network on controller and switches.
870
           For use with the user datapath only right now."""
871
        controller = self.controllers[ 0 ]
872
        info( controller.name + ' <->' )
873
        cip = ip
874
        snum = ipParse( ip )
875
        for switch in self.switches:
876
            info( ' ' + switch.name )
877
            link = self.link( switch, controller, port1=0 )
878
            sintf, cintf = link.intf1, link.intf2
879
            switch.controlIntf = sintf
880
            snum += 1
881
            while snum & 0xff in [ 0, 255 ]:
882
                snum += 1
883
            sip = ipStr( snum )
884
            cintf.setIP( cip, prefixLen )
885
            sintf.setIP( sip, prefixLen )
886
            controller.setHostRoute( sip, cintf )
887
            switch.setHostRoute( cip, sintf )
888
        info( '\n' )
889
        info( '*** Testing control network\n' )
890
        while not cintf.isUp():
891
            info( '*** Waiting for', cintf, 'to come up\n' )
892
            sleep( 1 )
893
        for switch in self.switches:
894
            while not sintf.isUp():
895
                info( '*** Waiting for', sintf, 'to come up\n' )
896
                sleep( 1 )
897
            if self.ping( hosts=[ switch, controller ] ) != 0:
898
                error( '*** Error: control network test failed\n' )
899
                exit( 1 )
900
        info( '\n' )