Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ e1711f35

History | View | Annotate | Download (33.8 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
    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
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
566
                    sent, received = self._parsePing( result )
567
                    packets += sent
568
                    if received > sent:
569
                        error( '*** Error: received too many packets' )
570
                        error( '%s' % result )
571
                        node.cmdPrint( 'route' )
572
                        exit( 1 )
573
                    lost += sent - received
574
                    output( ( '%s ' % dest.name ) if received else 'X ' )
575
            output( '\n' )
576
        if packets > 0:
577
            ploss = 100.0 * lost / packets
578
            received = packets - lost
579
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
580
                    ( ploss, received, packets ) )
581
        else:
582
            ploss = 0
583
            output( "*** Warning: No packets sent\n" )
584
        return ploss
585

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

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

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

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

    
661
    def pingAllFull( self ):
662
        """Ping between all hosts.
663
           returns: ploss packet loss percentage"""
664
        return self.pingFull()
665

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

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

    
686
    # XXX This should be cleaned up
687

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

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

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

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

    
815
    inited = False
816

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

    
826

    
827
class MininetWithControlNet( Mininet ):
828

    
829
    """Control network support:
830

831
       Create an explicit control network. Currently this is only
832
       used/usable with the user datapath.
833

834
       Notes:
835

836
       1. If the controller and switches are in the same (e.g. root)
837
          namespace, they can just use the loopback connection.
838

839
       2. If we can get unix domain sockets to work, we can use them
840
          instead of an explicit control network.
841

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

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

848
       5. Basically nobody ever used this code, so it has been moved
849
          into its own class.
850

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

    
855
    def configureControlNetwork( self ):
856
        "Configure control network."
857
        self.configureRoutedControlNetwork()
858

    
859
    # We still need to figure out the right way to pass
860
    # in the control network location.
861

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