Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 083322a2

History | View | Annotate | Download (33.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 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.2b0"
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
    def addLink( self, node1, node2, port1=None, port2=None,
325
                 cls=None, **params ):
326
        """"Add a link from node1 to node2
327
            node1: source node
328
            node2: dest node
329
            port1: source port
330
            port2: dest port
331
            returns: link object"""
332
        mac1 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff  | 0x020000000000 )
333
        mac2 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff  | 0x020000000000 )
334
        defaults = { 'port1': port1,
335
                     'port2': port2,
336
                     'addr1': mac1,
337
                     'addr2': mac2,
338
                     'intf': self.intf }
339
        defaults.update( params )
340
        if not cls:
341
            cls = self.link
342
        link = cls( node1, node2, **defaults )
343
        self.links.append( link )
344
        return link
345

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

    
364
    def buildFromTopo( self, topo=None ):
365
        """Build mininet from a topology object
366
           At the end of this function, everything should be connected
367
           and up."""
368

    
369
        # Possibly we should clean up here and/or validate
370
        # the topo
371
        if self.cleanup:
372
            pass
373

    
374
        info( '*** Creating network\n' )
375

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

    
389
        info( '*** Adding hosts:\n' )
390
        for hostName in topo.hosts():
391
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
392
            info( hostName + ' ' )
393

    
394
        info( '\n*** Adding switches:\n' )
395
        for switchName in topo.switches():
396
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
397
            info( switchName + ' ' )
398

    
399
        info( '\n*** Adding links:\n' )
400
        for srcName, dstName in topo.links(sort=True):
401
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
402
            params = topo.linkInfo( srcName, dstName )
403
            srcPort, dstPort = topo.port( srcName, dstName )
404
            self.addLink( src, dst, srcPort, dstPort, **params )
405
            info( '(%s, %s) ' % ( src.name, dst.name ) )
406

    
407
        info( '\n' )
408

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
689
    # XXX This should be cleaned up
690

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

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

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

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

    
818
    inited = False
819

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

    
829

    
830
class MininetWithControlNet( Mininet ):
831

    
832
    """Control network support:
833

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

837
       Notes:
838

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

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

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

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

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

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

    
858
    def configureControlNetwork( self ):
859
        "Configure control network."
860
        self.configureRoutedControlNetwork()
861

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

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