Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 2e3258d2

History | View | Annotate | Download (36.3 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

    
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 ( Node, Host, OVSKernelSwitch, DefaultController,
102
                           Controller )
103
from mininet.nodelib import NAT
104
from mininet.link import Link, Intf
105
from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
106
                           macColonHex, ipStr, ipParse, netParse, ipAdd,
107
                           waitListening )
108
from mininet.term import cleanUpScreens, makeTerms
109

    
110
# Mininet version: should be consistent with README and LICENSE
111
VERSION = "2.2.1"
112

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

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

    
159
        self.hosts = []
160
        self.switches = []
161
        self.controllers = []
162
        self.links = []
163

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

    
166
        self.terms = []  # list of spawned xterm processes
167

    
168
        Mininet.init()  # Initialize Mininet if necessary
169

    
170
        self.built = False
171
        if topo and build:
172
            self.build()
173

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

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

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

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

    
268
    def addNAT( self, name='nat0', connect=True, inNamespace=False,
269
                **params):
270
        """Add a NAT to the Mininet network
271
           name: name of NAT node
272
           connect: switch to connect to | True (s1) | None
273
           inNamespace: create in a network namespace
274
           params: other NAT node params, notably:
275
               ip: used as default gateway address"""
276
        nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
277
                            subnet=self.ipBase, **params )
278
        # find first switch and create link
279
        if connect:
280
            if not isinstance( connect, Node ):
281
                # Use first switch if not specified
282
                connect = self.switches[ 0 ]
283
            # Connect the nat to the switch
284
            self.addLink( nat, connect )
285
            # Set the default route on hosts
286
            natIP = nat.params[ 'ip' ].split('/')[ 0 ]
287
            for host in self.hosts:
288
                if host.inNamespace:
289
                    host.setDefaultRoute( 'via %s' % natIP )
290
        return nat
291

    
292
    # BL: We now have four ways to look up nodes
293
    # This may (should?) be cleaned up in the future.
294
    def getNodeByName( self, *args ):
295
        "Return node(s) with given name(s)"
296
        if len( args ) == 1:
297
            return self.nameToNode[ args[ 0 ] ]
298
        return [ self.nameToNode[ n ] for n in args ]
299

    
300
    def get( self, *args ):
301
        "Convenience alias for getNodeByName"
302
        return self.getNodeByName( *args )
303

    
304
    # Even more convenient syntax for node lookup and iteration
305
    def __getitem__( self, key ):
306
        """net [ name ] operator: Return node(s) with given name(s)"""
307
        return self.nameToNode[ key ]
308

    
309
    def __iter__( self ):
310
        "return iterator over node names"
311
        for node in chain( self.hosts, self.switches, self.controllers ):
312
            yield node.name
313

    
314
    def __len__( self ):
315
        "returns number of nodes in net"
316
        return ( len( self.hosts ) + len( self.switches ) +
317
                 len( self.controllers ) )
318

    
319
    def __contains__( self, item ):
320
        "returns True if net contains named node"
321
        return item in self.nameToNode
322

    
323
    def keys( self ):
324
        "return a list of all node names or net's keys"
325
        return list( self )
326

    
327
    def values( self ):
328
        "return a list of all nodes or net's values"
329
        return [ self[name] for name in self ]
330

    
331
    def items( self ):
332
        "return (key,value) tuple list for every node in net"
333
        return zip( self.keys(), self.values() )
334

    
335
    @staticmethod
336
    def randMac():
337
        "Return a random, non-multicast MAC address"
338
        return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
339
                            0x020000000000 )
340

    
341
    def addLink( self, node1, node2, port1=None, port2=None,
342
                 cls=None, **params ):
343
        """"Add a link from node1 to node2
344
            node1: source node (or name)
345
            node2: dest node (or name)
346
            port1: source port (optional)
347
            port2: dest port (optional)
348
            cls: link class (optional)
349
            params: additional link params (optional)
350
            returns: link object"""
351
        # Accept node objects or names
352
        node1 = node1 if not isinstance( node1, basestring ) else self[ node1 ]
353
        node2 = node2 if not isinstance( node2, basestring ) else self[ node2 ]
354
        options = dict( params )
355
        # Port is optional
356
        if port1 is not None:
357
            options.setdefault( 'port1', port1 )
358
        if port2 is not None:
359
            options.setdefault( 'port2', port2 )
360
        if self.intf is not None:
361
            options.setdefault( 'intf', self.intf )
362
        # Set default MAC - this should probably be in Link
363
        options.setdefault( 'addr1', self.randMac() )
364
        options.setdefault( 'addr2', self.randMac() )
365
        cls = self.link if cls is None else cls
366
        link = cls( node1, node2, **options )
367
        self.links.append( link )
368
        return link
369

    
370
    def configHosts( self ):
371
        "Configure a set of hosts."
372
        for host in self.hosts:
373
            info( host.name + ' ' )
374
            intf = host.defaultIntf()
375
            if intf:
376
                if intf.prefixLen:
377
                    host.configDefault(ip=intf.ip+'/'+intf.prefixLen,mac=intf.mac)
378
                else:
379
                    if intf.ip :
380
                        host.configDefault(ip=intf.ip,mac=intf.mac)
381
                    else:
382
                        host.configDefault()
383
            else:
384
                # Don't configure nonexistent intf
385
                host.configDefault( ip=None, mac=None )
386
            # You're low priority, dude!
387
            # BL: do we want to do this here or not?
388
            # May not make sense if we have CPU lmiting...
389
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
390
            # This may not be the right place to do this, but
391
            # it needs to be done somewhere.
392
        info( '\n' )
393

    
394
    def buildFromTopo( self, topo=None ):
395
        """Build mininet from a topology object
396
           At the end of this function, everything should be connected
397
           and up."""
398

    
399
        # Possibly we should clean up here and/or validate
400
        # the topo
401
        if self.cleanup:
402
            pass
403

    
404
        info( '*** Creating network\n' )
405

    
406
        if not self.controllers and self.controller:
407
            # Add a default controller
408
            info( '*** Adding controller\n' )
409
            classes = self.controller
410
            if not isinstance( classes, list ):
411
                classes = [ classes ]
412
            for i, cls in enumerate( classes ):
413
                # Allow Controller objects because nobody understands partial()
414
                if isinstance( cls, Controller ):
415
                    self.addController( cls )
416
                else:
417
                    self.addController( 'c%d' % i, cls )
418

    
419
        info( '*** Adding hosts:\n' )
420
        for hostName in topo.hosts():
421
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
422
            info( hostName + ' ' )
423

    
424
        info( '\n*** Adding switches:\n' )
425
        for switchName in topo.switches():
426
            # A bit ugly: add batch parameter if appropriate
427
            params = topo.nodeInfo( switchName)
428
            cls = params.get( 'cls', self.switch )
429
            if hasattr( cls, 'batchStartup' ):
430
                params.setdefault( 'batch', True )
431
            self.addSwitch( switchName, **params )
432
            info( switchName + ' ' )
433

    
434
        info( '\n*** Adding links:\n' )
435
        for srcName, dstName, params in topo.links(
436
                sort=True, withInfo=True ):
437
            self.addLink( **params )
438
            info( '(%s, %s) ' % ( srcName, dstName ) )
439

    
440
        info( '\n' )
441

    
442
    def configureControlNetwork( self ):
443
        "Control net config hook: override in subclass"
444
        raise Exception( 'configureControlNetwork: '
445
                         'should be overriden in subclass', self )
446

    
447
    def build( self ):
448
        "Build mininet."
449
        if self.topo:
450
            self.buildFromTopo( self.topo )
451
        if self.inNamespace:
452
            self.configureControlNetwork()
453
        info( '*** Configuring hosts\n' )
454
        self.configHosts()
455
        if self.xterms:
456
            self.startTerms()
457
        if self.autoStaticArp:
458
            self.staticArp()
459
        self.built = True
460

    
461
    def startTerms( self ):
462
        "Start a terminal for each node."
463
        if 'DISPLAY' not in os.environ:
464
            error( "Error starting terms: Cannot connect to display\n" )
465
            return
466
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
467
        cleanUpScreens()
468
        self.terms += makeTerms( self.controllers, 'controller' )
469
        self.terms += makeTerms( self.switches, 'switch' )
470
        self.terms += makeTerms( self.hosts, 'host' )
471

    
472
    def stopXterms( self ):
473
        "Kill each xterm."
474
        for term in self.terms:
475
            os.kill( term.pid, signal.SIGKILL )
476
        cleanUpScreens()
477

    
478
    def staticArp( self ):
479
        "Add all-pairs ARP entries to remove the need to handle broadcast."
480
        for src in self.hosts:
481
            for dst in self.hosts:
482
                if src != dst:
483
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
484

    
485
    def start( self ):
486
        "Start controller and switches."
487
        if not self.built:
488
            self.build()
489
        info( '*** Starting controller\n' )
490
        for controller in self.controllers:
491
            info( controller.name + ' ')
492
            controller.start()
493
        info( '\n' )
494
        info( '*** Starting %s switches\n' % len( self.switches ) )
495
        for switch in self.switches:
496
            info( switch.name + ' ')
497
            switch.start( self.controllers )
498
        started = {}
499
        for swclass, switches in groupby(
500
                sorted( self.switches, key=type ), type ):
501
            switches = tuple( switches )
502
            if hasattr( swclass, 'batchStartup' ):
503
                success = swclass.batchStartup( switches )
504
                started.update( { s: s for s in success } )
505
        info( '\n' )
506
        if self.waitConn:
507
            self.waitConnected()
508

    
509
    def stop( self ):
510
        "Stop the controller(s), switches and hosts"
511
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
512
        for controller in self.controllers:
513
            info( controller.name + ' ' )
514
            controller.stop()
515
        info( '\n' )
516
        if self.terms:
517
            info( '*** Stopping %i terms\n' % len( self.terms ) )
518
            self.stopXterms()
519
        info( '*** Stopping %i links\n' % len( self.links ) )
520
        for link in self.links:
521
            info( '.' )
522
            link.stop()
523
        info( '\n' )
524
        info( '*** Stopping %i switches\n' % len( self.switches ) )
525
        stopped = {}
526
        for swclass, switches in groupby(
527
                sorted( self.switches, key=type ), type ):
528
            switches = tuple( switches )
529
            if hasattr( swclass, 'batchShutdown' ):
530
                success = swclass.batchShutdown( switches )
531
                stopped.update( { s: s for s in success } )
532
        for switch in self.switches:
533
            info( switch.name + ' ' )
534
            if switch not in stopped:
535
                switch.stop()
536
            switch.terminate()
537
        info( '\n' )
538
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
539
        for host in self.hosts:
540
            info( host.name + ' ' )
541
            host.terminate()
542
        info( '\n*** Done\n' )
543

    
544
    def run( self, test, *args, **kwargs ):
545
        "Perform a complete start/test/stop cycle."
546
        self.start()
547
        info( '*** Running test\n' )
548
        result = test( *args, **kwargs )
549
        self.stop()
550
        return result
551

    
552
    def monitor( self, hosts=None, timeoutms=-1 ):
553
        """Monitor a set of hosts (or all hosts by default),
554
           and return their output, a line at a time.
555
           hosts: (optional) set of hosts to monitor
556
           timeoutms: (optional) timeout value in ms
557
           returns: iterator which returns host, line"""
558
        if hosts is None:
559
            hosts = self.hosts
560
        poller = select.poll()
561
        h1 = hosts[ 0 ]  # so we can call class method fdToNode
562
        for host in hosts:
563
            poller.register( host.stdout )
564
        while True:
565
            ready = poller.poll( timeoutms )
566
            for fd, event in ready:
567
                host = h1.fdToNode( fd )
568
                if event & select.POLLIN:
569
                    line = host.readline()
570
                    if line is not None:
571
                        yield host, line
572
            # Return if non-blocking
573
            if not ready and timeoutms >= 0:
574
                yield None, None
575

    
576
    # XXX These test methods should be moved out of this class.
577
    # Probably we should create a tests.py for them
578

    
579
    @staticmethod
580
    def _parsePing( pingOutput ):
581
        "Parse ping output and return packets sent, received."
582
        # Check for downed link
583
        if 'connect: Network is unreachable' in pingOutput:
584
            return 1, 0
585
        r = r'(\d+) packets transmitted, (\d+) received'
586
        m = re.search( r, pingOutput )
587
        if m is None:
588
            error( '*** Error: could not parse ping output: %s\n' %
589
                   pingOutput )
590
            return 1, 0
591
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
592
        return sent, received
593

    
594
    def ping( self, hosts=None, timeout=None ):
595
        """Ping between all specified hosts.
596
           hosts: list of hosts
597
           timeout: time to wait for a response, as string
598
           returns: ploss packet loss percentage"""
599
        # should we check if running?
600
        packets = 0
601
        lost = 0
602
        ploss = None
603
        if not hosts:
604
            hosts = self.hosts
605
            output( '*** Ping: testing ping reachability\n' )
606
        for node in hosts:
607
            output( '%s -> ' % node.name )
608
            for dest in hosts:
609
                if node != dest:
610
                    opts = ''
611
                    if timeout:
612
                        opts = '-W %s' % timeout
613
                    if dest.intfs:
614
                        result = node.cmd( 'ping -c1 %s %s' %
615
                                           (opts, dest.IP()) )
616
                        sent, received = self._parsePing( result )
617
                    else:
618
                        sent, received = 0, 0
619
                    packets += sent
620
                    if received > sent:
621
                        error( '*** Error: received too many packets' )
622
                        error( '%s' % result )
623
                        node.cmdPrint( 'route' )
624
                        exit( 1 )
625
                    lost += sent - received
626
                    output( ( '%s ' % dest.name ) if received else 'X ' )
627
            output( '\n' )
628
        if packets > 0:
629
            ploss = 100.0 * lost / packets
630
            received = packets - lost
631
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
632
                    ( ploss, received, packets ) )
633
        else:
634
            ploss = 0
635
            output( "*** Warning: No packets sent\n" )
636
        return ploss
637

    
638
    @staticmethod
639
    def _parsePingFull( pingOutput ):
640
        "Parse ping output and return all data."
641
        errorTuple = (1, 0, 0, 0, 0, 0)
642
        # Check for downed link
643
        r = r'[uU]nreachable'
644
        m = re.search( r, pingOutput )
645
        if m is not None:
646
            return errorTuple
647
        r = r'(\d+) packets transmitted, (\d+) received'
648
        m = re.search( r, pingOutput )
649
        if m is None:
650
            error( '*** Error: could not parse ping output: %s\n' %
651
                   pingOutput )
652
            return errorTuple
653
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
654
        r = r'rtt min/avg/max/mdev = '
655
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
656
        m = re.search( r, pingOutput )
657
        if m is None:
658
            if received == 0:
659
                return errorTuple
660
            error( '*** Error: could not parse ping output: %s\n' %
661
                   pingOutput )
662
            return errorTuple
663
        rttmin = float( m.group( 1 ) )
664
        rttavg = float( m.group( 2 ) )
665
        rttmax = float( m.group( 3 ) )
666
        rttdev = float( m.group( 4 ) )
667
        return sent, received, rttmin, rttavg, rttmax, rttdev
668

    
669
    def pingFull( self, hosts=None, timeout=None ):
670
        """Ping between all specified hosts and return all data.
671
           hosts: list of hosts
672
           timeout: time to wait for a response, as string
673
           returns: all ping data; see function body."""
674
        # should we check if running?
675
        # Each value is a tuple: (src, dsd, [all ping outputs])
676
        all_outputs = []
677
        if not hosts:
678
            hosts = self.hosts
679
            output( '*** Ping: testing ping reachability\n' )
680
        for node in hosts:
681
            output( '%s -> ' % node.name )
682
            for dest in hosts:
683
                if node != dest:
684
                    opts = ''
685
                    if timeout:
686
                        opts = '-W %s' % timeout
687
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
688
                    outputs = self._parsePingFull( result )
689
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
690
                    all_outputs.append( (node, dest, outputs) )
691
                    output( ( '%s ' % dest.name ) if received else 'X ' )
692
            output( '\n' )
693
        output( "*** Results: \n" )
694
        for outputs in all_outputs:
695
            src, dest, ping_outputs = outputs
696
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
697
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
698
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
699
                    (rttmin, rttavg, rttmax, rttdev) )
700
        return all_outputs
701

    
702
    def pingAll( self, timeout=None ):
703
        """Ping between all hosts.
704
           returns: ploss packet loss percentage"""
705
        return self.ping( timeout=timeout )
706

    
707
    def pingPair( self ):
708
        """Ping between first two hosts, useful for testing.
709
           returns: ploss packet loss percentage"""
710
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
711
        return self.ping( hosts=hosts )
712

    
713
    def pingAllFull( self ):
714
        """Ping between all hosts.
715
           returns: ploss packet loss percentage"""
716
        return self.pingFull()
717

    
718
    def pingPairFull( self ):
719
        """Ping between first two hosts, useful for testing.
720
           returns: ploss packet loss percentage"""
721
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
722
        return self.pingFull( hosts=hosts )
723

    
724
    @staticmethod
725
    def _parseIperf( iperfOutput ):
726
        """Parse iperf output and return bandwidth.
727
           iperfOutput: string
728
           returns: result string"""
729
        r = r'([\d\.]+ \w+/sec)'
730
        m = re.findall( r, iperfOutput )
731
        if m:
732
            return m[-1]
733
        else:
734
            # was: raise Exception(...)
735
            error( 'could not parse iperf output: ' + iperfOutput )
736
            return ''
737

    
738
    # XXX This should be cleaned up
739

    
740
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
741
               seconds=5, port=5001):
742
        """Run iperf between two hosts.
743
           hosts: list of hosts; if None, uses first and last hosts
744
           l4Type: string, one of [ TCP, UDP ]
745
           udpBw: bandwidth target for UDP test
746
           fmt: iperf format argument if any
747
           seconds: iperf time to transmit
748
           port: iperf port
749
           returns: two-element array of [ server, client ] speeds
750
           note: send() is buffered, so client rate can be much higher than
751
           the actual transmission rate; on an unloaded system, server
752
           rate should be much closer to the actual receive rate"""
753
        hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
754
        assert len( hosts ) == 2
755
        client, server = hosts
756
        output( '*** Iperf: testing', l4Type, 'bandwidth between',
757
                client, 'and', server, '\n' )
758
        server.cmd( 'killall -9 iperf' )
759
        iperfArgs = 'iperf -p %d ' % port
760
        bwArgs = ''
761
        if l4Type == 'UDP':
762
            iperfArgs += '-u '
763
            bwArgs = '-b ' + udpBw + ' '
764
        elif l4Type != 'TCP':
765
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
766
        if fmt:
767
            iperfArgs += '-f %s ' % fmt
768
        server.sendCmd( iperfArgs + '-s' )
769
        if l4Type == 'TCP':
770
            if not waitListening( client, server.IP(), port ):
771
                raise Exception( 'Could not connect to iperf on port %d'
772
                                 % port )
773
        cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
774
                             server.IP() + ' ' + bwArgs )
775
        debug( 'Client output: %s\n' % cliout )
776
        servout = ''
777
        # We want the last *b/sec from the iperf server output
778
        # for TCP, there are two fo them because of waitListening
779
        count = 2 if l4Type == 'TCP' else 1
780
        while len( re.findall( '/sec', servout ) ) < count:
781
            servout += server.monitor( timeoutms=5000 )
782
        server.sendInt()
783
        servout += server.waitOutput()
784
        debug( 'Server output: %s\n' % servout )
785
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
786
        if l4Type == 'UDP':
787
            result.insert( 0, udpBw )
788
        output( '*** Results: %s\n' % result )
789
        return result
790

    
791
    def runCpuLimitTest( self, cpu, duration=5 ):
792
        """run CPU limit test with 'while true' processes.
793
        cpu: desired CPU fraction of each host
794
        duration: test duration in seconds (integer)
795
        returns a single list of measured CPU fractions as floats.
796
        """
797
        cores = int( quietRun( 'nproc' ) )
798
        pct = cpu * 100
799
        info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
800
        hosts = self.hosts
801
        cores = int( quietRun( 'nproc' ) )
802
        # number of processes to run a while loop on per host
803
        num_procs = int( ceil( cores * cpu ) )
804
        pids = {}
805
        for h in hosts:
806
            pids[ h ] = []
807
            for _core in range( num_procs ):
808
                h.cmd( 'while true; do a=1; done &' )
809
                pids[ h ].append( h.cmd( 'echo $!' ).strip() )
810
        outputs = {}
811
        time = {}
812
        # get the initial cpu time for each host
813
        for host in hosts:
814
            outputs[ host ] = []
815
            with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
816
                       host, 'r' ) as f:
817
                time[ host ] = float( f.read() )
818
        for _ in range( duration ):
819
            sleep( 1 )
820
            for host in hosts:
821
                with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
822
                           host, 'r' ) as f:
823
                    readTime = float( f.read() )
824
                outputs[ host ].append( ( ( readTime - time[ host ] )
825
                                        / 1000000000 ) / cores * 100 )
826
                time[ host ] = readTime
827
        for h, pids in pids.items():
828
            for pid in pids:
829
                h.cmd( 'kill -9 %s' % pid )
830
        cpu_fractions = []
831
        for _host, outputs in outputs.items():
832
            for pct in outputs:
833
                cpu_fractions.append( pct )
834
        output( '*** Results: %s\n' % cpu_fractions )
835
        return cpu_fractions
836

    
837
    # BL: I think this can be rewritten now that we have
838
    # a real link class.
839
    def configLinkStatus( self, src, dst, status ):
840
        """Change status of src <-> dst links.
841
           src: node name
842
           dst: node name
843
           status: string {up, down}"""
844
        if src not in self.nameToNode:
845
            error( 'src not in network: %s\n' % src )
846
        elif dst not in self.nameToNode:
847
            error( 'dst not in network: %s\n' % dst )
848
        else:
849
            if isinstance( src, basestring ):
850
                src = self.nameToNode[ src ]
851
            if isinstance( dst, basestring ):
852
                dst = self.nameToNode[ dst ]
853
            connections = src.connectionsTo( dst )
854
            if len( connections ) == 0:
855
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
856
            for srcIntf, dstIntf in connections:
857
                result = srcIntf.ifconfig( status )
858
                if result:
859
                    error( 'link src status change failed: %s\n' % result )
860
                result = dstIntf.ifconfig( status )
861
                if result:
862
                    error( 'link dst status change failed: %s\n' % result )
863

    
864
    def interact( self ):
865
        "Start network and run our simple CLI."
866
        self.start()
867
        result = CLI( self )
868
        self.stop()
869
        return result
870

    
871
    inited = False
872

    
873
    @classmethod
874
    def init( cls ):
875
        "Initialize Mininet"
876
        if cls.inited:
877
            return
878
        ensureRoot()
879
        fixLimits()
880
        cls.inited = True
881

    
882

    
883
class MininetWithControlNet( Mininet ):
884

    
885
    """Control network support:
886

887
       Create an explicit control network. Currently this is only
888
       used/usable with the user datapath.
889

890
       Notes:
891

892
       1. If the controller and switches are in the same (e.g. root)
893
          namespace, they can just use the loopback connection.
894

895
       2. If we can get unix domain sockets to work, we can use them
896
          instead of an explicit control network.
897

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

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

904
       5. Basically nobody ever used this code, so it has been moved
905
          into its own class.
906

907
       6. Ultimately we may wish to extend this to allow us to create a
908
          control network which every node's control interface is
909
          attached to."""
910

    
911
    def configureControlNetwork( self ):
912
        "Configure control network."
913
        self.configureRoutedControlNetwork()
914

    
915
    # We still need to figure out the right way to pass
916
    # in the control network location.
917

    
918
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
919
                                       prefixLen=16 ):
920
        """Configure a routed control network on controller and switches.
921
           For use with the user datapath only right now."""
922
        controller = self.controllers[ 0 ]
923
        info( controller.name + ' <->' )
924
        cip = ip
925
        snum = ipParse( ip )
926
        for switch in self.switches:
927
            info( ' ' + switch.name )
928
            link = self.link( switch, controller, port1=0 )
929
            sintf, cintf = link.intf1, link.intf2
930
            switch.controlIntf = sintf
931
            snum += 1
932
            while snum & 0xff in [ 0, 255 ]:
933
                snum += 1
934
            sip = ipStr( snum )
935
            cintf.setIP( cip, prefixLen )
936
            sintf.setIP( sip, prefixLen )
937
            controller.setHostRoute( sip, cintf )
938
            switch.setHostRoute( cip, sintf )
939
        info( '\n' )
940
        info( '*** Testing control network\n' )
941
        while not cintf.isUp():
942
            info( '*** Waiting for', cintf, 'to come up\n' )
943
            sleep( 1 )
944
        for switch in self.switches:
945
            while not sintf.isUp():
946
                info( '*** Waiting for', sintf, 'to come up\n' )
947
                sleep( 1 )
948
            if self.ping( hosts=[ switch, controller ] ) != 0:
949
                error( '*** Error: control network test failed\n' )
950
                exit( 1 )
951
        info( '\n' )