Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ ab8c4e91

History | View | Annotate | Download (35.6 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, self.switches[ 0 ] )
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
        # Set default MAC - this should probably be in Link
361
        options.setdefault( 'addr1', self.randMac() )
362
        options.setdefault( 'addr2', self.randMac() )
363
        cls = self.link if cls is None else cls
364
        link = cls( node1, node2, **options )
365
        self.links.append( link )
366
        return link
367

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

    
386
    def buildFromTopo( self, topo=None ):
387
        """Build mininet from a topology object
388
           At the end of this function, everything should be connected
389
           and up."""
390

    
391
        # Possibly we should clean up here and/or validate
392
        # the topo
393
        if self.cleanup:
394
            pass
395

    
396
        info( '*** Creating network\n' )
397

    
398
        if not self.controllers and self.controller:
399
            # Add a default controller
400
            info( '*** Adding controller\n' )
401
            classes = self.controller
402
            if not isinstance( classes, list ):
403
                classes = [ classes ]
404
            for i, cls in enumerate( classes ):
405
                # Allow Controller objects because nobody understands partial()
406
                if isinstance( cls, Controller ):
407
                    self.addController( cls )
408
                else:
409
                    self.addController( 'c%d' % i, cls )
410

    
411
        info( '*** Adding hosts:\n' )
412
        for hostName in topo.hosts():
413
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
414
            info( hostName + ' ' )
415

    
416
        info( '\n*** Adding switches:\n' )
417
        for switchName in topo.switches():
418
            # A bit ugly: add batch parameter if appropriate
419
            params = topo.nodeInfo( switchName)
420
            cls = params.get( 'cls', self.switch )
421
            if hasattr( cls, 'batchStartup' ):
422
                params.setdefault( 'batch', True )
423
            self.addSwitch( switchName, **params )
424
            info( switchName + ' ' )
425

    
426
        info( '\n*** Adding links:\n' )
427
        for srcName, dstName, params in topo.links(
428
                sort=True, withInfo=True ):
429
            self.addLink( **params )
430
            info( '(%s, %s) ' % ( srcName, dstName ) )
431

    
432
        info( '\n' )
433

    
434
    def configureControlNetwork( self ):
435
        "Control net config hook: override in subclass"
436
        raise Exception( 'configureControlNetwork: '
437
                         'should be overriden in subclass', self )
438

    
439
    def build( self ):
440
        "Build mininet."
441
        if self.topo:
442
            self.buildFromTopo( self.topo )
443
        if self.inNamespace:
444
            self.configureControlNetwork()
445
        info( '*** Configuring hosts\n' )
446
        self.configHosts()
447
        if self.xterms:
448
            self.startTerms()
449
        if self.autoStaticArp:
450
            self.staticArp()
451
        self.built = True
452

    
453
    def startTerms( self ):
454
        "Start a terminal for each node."
455
        if 'DISPLAY' not in os.environ:
456
            error( "Error starting terms: Cannot connect to display\n" )
457
            return
458
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
459
        cleanUpScreens()
460
        self.terms += makeTerms( self.controllers, 'controller' )
461
        self.terms += makeTerms( self.switches, 'switch' )
462
        self.terms += makeTerms( self.hosts, 'host' )
463

    
464
    def stopXterms( self ):
465
        "Kill each xterm."
466
        for term in self.terms:
467
            os.kill( term.pid, signal.SIGKILL )
468
        cleanUpScreens()
469

    
470
    def staticArp( self ):
471
        "Add all-pairs ARP entries to remove the need to handle broadcast."
472
        for src in self.hosts:
473
            for dst in self.hosts:
474
                if src != dst:
475
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
476

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

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

    
536
    def run( self, test, *args, **kwargs ):
537
        "Perform a complete start/test/stop cycle."
538
        self.start()
539
        info( '*** Running test\n' )
540
        result = test( *args, **kwargs )
541
        self.stop()
542
        return result
543

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

    
568
    # XXX These test methods should be moved out of this class.
569
    # Probably we should create a tests.py for them
570

    
571
    @staticmethod
572
    def _parsePing( pingOutput ):
573
        "Parse ping output and return packets sent, received."
574
        # Check for downed link
575
        if 'connect: Network is unreachable' in pingOutput:
576
            return 1, 0
577
        r = r'(\d+) packets transmitted, (\d+) received'
578
        m = re.search( r, pingOutput )
579
        if m is None:
580
            error( '*** Error: could not parse ping output: %s\n' %
581
                   pingOutput )
582
            return 1, 0
583
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
584
        return sent, received
585

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

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

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

    
694
    def pingAll( self, timeout=None ):
695
        """Ping between all hosts.
696
           returns: ploss packet loss percentage"""
697
        return self.ping( timeout=timeout )
698

    
699
    def pingPair( self ):
700
        """Ping between first two hosts, useful for testing.
701
           returns: ploss packet loss percentage"""
702
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
703
        return self.ping( hosts=hosts )
704

    
705
    def pingAllFull( self ):
706
        """Ping between all hosts.
707
           returns: ploss packet loss percentage"""
708
        return self.pingFull()
709

    
710
    def pingPairFull( self ):
711
        """Ping between first two hosts, useful for testing.
712
           returns: ploss packet loss percentage"""
713
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
714
        return self.pingFull( hosts=hosts )
715

    
716
    @staticmethod
717
    def _parseIperf( iperfOutput ):
718
        """Parse iperf output and return bandwidth.
719
           iperfOutput: string
720
           returns: result string"""
721
        r = r'([\d\.]+ \w+/sec)'
722
        m = re.findall( r, iperfOutput )
723
        if m:
724
            return m[-1]
725
        else:
726
            # was: raise Exception(...)
727
            error( 'could not parse iperf output: ' + iperfOutput )
728
            return ''
729

    
730
    # XXX This should be cleaned up
731

    
732
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
733
               seconds=5, port=5001):
734
        """Run iperf between two hosts.
735
           hosts: list of hosts; if None, uses first and last hosts
736
           l4Type: string, one of [ TCP, UDP ]
737
           udpBw: bandwidth target for UDP test
738
           fmt: iperf format argument if any
739
           seconds: iperf time to transmit
740
           port: iperf port
741
           returns: two-element array of [ server, client ] speeds
742
           note: send() is buffered, so client rate can be much higher than
743
           the actual transmission rate; on an unloaded system, server
744
           rate should be much closer to the actual receive rate"""
745
        hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
746
        assert len( hosts ) == 2
747
        client, server = hosts
748
        output( '*** Iperf: testing', l4Type, 'bandwidth between',
749
                client, 'and', server, '\n' )
750
        server.cmd( 'killall -9 iperf' )
751
        iperfArgs = 'iperf -p %d ' % port
752
        bwArgs = ''
753
        if l4Type == 'UDP':
754
            iperfArgs += '-u '
755
            bwArgs = '-b ' + udpBw + ' '
756
        elif l4Type != 'TCP':
757
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
758
        if fmt:
759
            iperfArgs += '-f %s ' % fmt
760
        server.sendCmd( iperfArgs + '-s' )
761
        if l4Type == 'TCP':
762
            if not waitListening( client, server.IP(), port ):
763
                raise Exception( 'Could not connect to iperf on port %d'
764
                                 % port )
765
        cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
766
                             server.IP() + ' ' + bwArgs )
767
        debug( 'Client output: %s\n' % cliout )
768
        server.sendInt()
769
        servout = server.waitOutput()
770
        debug( 'Server output: %s\n' % servout )
771
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
772
        if l4Type == 'UDP':
773
            result.insert( 0, udpBw )
774
        output( '*** Results: %s\n' % result )
775
        return result
776

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

    
823
    # BL: I think this can be rewritten now that we have
824
    # a real link class.
825
    def configLinkStatus( self, src, dst, status ):
826
        """Change status of src <-> dst links.
827
           src: node name
828
           dst: node name
829
           status: string {up, down}"""
830
        if src not in self.nameToNode:
831
            error( 'src not in network: %s\n' % src )
832
        elif dst not in self.nameToNode:
833
            error( 'dst not in network: %s\n' % dst )
834
        else:
835
            if isinstance( src, basestring ):
836
                src = self.nameToNode[ src ]
837
            if isinstance( dst, basestring ):
838
                dst = self.nameToNode[ dst ]
839
            connections = src.connectionsTo( dst )
840
            if len( connections ) == 0:
841
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
842
            for srcIntf, dstIntf in connections:
843
                result = srcIntf.ifconfig( status )
844
                if result:
845
                    error( 'link src status change failed: %s\n' % result )
846
                result = dstIntf.ifconfig( status )
847
                if result:
848
                    error( 'link dst status change failed: %s\n' % result )
849

    
850
    def interact( self ):
851
        "Start network and run our simple CLI."
852
        self.start()
853
        result = CLI( self )
854
        self.stop()
855
        return result
856

    
857
    inited = False
858

    
859
    @classmethod
860
    def init( cls ):
861
        "Initialize Mininet"
862
        if cls.inited:
863
            return
864
        ensureRoot()
865
        fixLimits()
866
        cls.inited = True
867

    
868

    
869
class MininetWithControlNet( Mininet ):
870

    
871
    """Control network support:
872

873
       Create an explicit control network. Currently this is only
874
       used/usable with the user datapath.
875

876
       Notes:
877

878
       1. If the controller and switches are in the same (e.g. root)
879
          namespace, they can just use the loopback connection.
880

881
       2. If we can get unix domain sockets to work, we can use them
882
          instead of an explicit control network.
883

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

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

890
       5. Basically nobody ever used this code, so it has been moved
891
          into its own class.
892

893
       6. Ultimately we may wish to extend this to allow us to create a
894
          control network which every node's control interface is
895
          attached to."""
896

    
897
    def configureControlNetwork( self ):
898
        "Configure control network."
899
        self.configureRoutedControlNetwork()
900

    
901
    # We still need to figure out the right way to pass
902
    # in the control network location.
903

    
904
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
905
                                       prefixLen=16 ):
906
        """Configure a routed control network on controller and switches.
907
           For use with the user datapath only right now."""
908
        controller = self.controllers[ 0 ]
909
        info( controller.name + ' <->' )
910
        cip = ip
911
        snum = ipParse( ip )
912
        for switch in self.switches:
913
            info( ' ' + switch.name )
914
            link = self.link( switch, controller, port1=0 )
915
            sintf, cintf = link.intf1, link.intf2
916
            switch.controlIntf = sintf
917
            snum += 1
918
            while snum & 0xff in [ 0, 255 ]:
919
                snum += 1
920
            sip = ipStr( snum )
921
            cintf.setIP( cip, prefixLen )
922
            sintf.setIP( sip, prefixLen )
923
            controller.setHostRoute( sip, cintf )
924
            switch.setHostRoute( cip, sintf )
925
        info( '\n' )
926
        info( '*** Testing control network\n' )
927
        while not cintf.isUp():
928
            info( '*** Waiting for', cintf, 'to come up\n' )
929
            sleep( 1 )
930
        for switch in self.switches:
931
            while not sintf.isUp():
932
                info( '*** Waiting for', sintf, 'to come up\n' )
933
                sleep( 1 )
934
            if self.ping( hosts=[ switch, controller ] ) != 0:
935
                error( '*** Error: control network test failed\n' )
936
                exit( 1 )
937
        info( '\n' )