Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 18aab5b7

History | View | Annotate | Download (35.1 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
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
107
from mininet.term import cleanUpScreens, makeTerms
108

    
109
# Mininet version: should be consistent with README and LICENSE
110
VERSION = "2.2.0b2"
111

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

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

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

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

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

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

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

    
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=E1103
258
            name = controller_new.name
259
            # pylint: enable=E1103
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 currying
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
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
419
            info( switchName + ' ' )
420

    
421
        info( '\n*** Adding links:\n' )
422
        for srcName, dstName, params in topo.links(
423
                sort=True, withInfo=True ):
424
            self.addLink( **params )
425
            info( '(%s, %s) ' % ( srcName, dstName ) )
426

    
427
        info( '\n' )
428

    
429
    def configureControlNetwork( self ):
430
        "Control net config hook: override in subclass"
431
        raise Exception( 'configureControlNetwork: '
432
                         'should be overriden in subclass', self )
433

    
434
    def build( self ):
435
        "Build mininet."
436
        if self.topo:
437
            self.buildFromTopo( self.topo )
438
        if self.inNamespace:
439
            self.configureControlNetwork()
440
        info( '*** Configuring hosts\n' )
441
        self.configHosts()
442
        if self.xterms:
443
            self.startTerms()
444
        if self.autoStaticArp:
445
            self.staticArp()
446
        self.built = True
447

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

    
459
    def stopXterms( self ):
460
        "Kill each xterm."
461
        for term in self.terms:
462
            os.kill( term.pid, signal.SIGKILL )
463
        cleanUpScreens()
464

    
465
    def staticArp( self ):
466
        "Add all-pairs ARP entries to remove the need to handle broadcast."
467
        for src in self.hosts:
468
            for dst in self.hosts:
469
                if src != dst:
470
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
471

    
472
    def start( self ):
473
        "Start controller and switches."
474
        if not self.built:
475
            self.build()
476
        info( '*** Starting controller\n' )
477
        for controller in self.controllers:
478
            info( controller.name + ' ')
479
            controller.start()
480
        info( '\n' )
481
        info( '*** Starting %s switches\n' % len( self.switches ) )
482
        for switch in self.switches:
483
            info( switch.name + ' ')
484
            switch.start( self.controllers )
485
        info( '\n' )
486
        if self.waitConn:
487
            self.waitConnected()
488

    
489
    def stop( self ):
490
        "Stop the controller(s), switches and hosts"
491
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
492
        for controller in self.controllers:
493
            info( controller.name + ' ' )
494
            controller.stop()
495
        info( '\n' )
496
        if self.terms:
497
            info( '*** Stopping %i terms\n' % len( self.terms ) )
498
            self.stopXterms()
499
        info( '*** Stopping %i switches\n' % len( self.switches ) )
500
        for swclass, switches in groupby(
501
                sorted( self.switches, key=type ), type ):
502
            if hasattr( swclass, 'batchShutdown' ):
503
                swclass.batchShutdown( switches )
504
        for switch in self.switches:
505
            info( switch.name + ' ' )
506
            switch.stop()
507
            switch.terminate()
508
        info( '\n' )
509
        info( '*** Stopping %i links\n' % len( self.links ) )
510
        for link in self.links:
511
            link.stop()
512
        info( '\n' )
513
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
514
        for host in self.hosts:
515
            info( host.name + ' ' )
516
            host.terminate()
517
        info( '\n*** Done\n' )
518

    
519
    def run( self, test, *args, **kwargs ):
520
        "Perform a complete start/test/stop cycle."
521
        self.start()
522
        info( '*** Running test\n' )
523
        result = test( *args, **kwargs )
524
        self.stop()
525
        return result
526

    
527
    def monitor( self, hosts=None, timeoutms=-1 ):
528
        """Monitor a set of hosts (or all hosts by default),
529
           and return their output, a line at a time.
530
           hosts: (optional) set of hosts to monitor
531
           timeoutms: (optional) timeout value in ms
532
           returns: iterator which returns host, line"""
533
        if hosts is None:
534
            hosts = self.hosts
535
        poller = select.poll()
536
        h1 = hosts[ 0 ]  # so we can call class method fdToNode
537
        for host in hosts:
538
            poller.register( host.stdout )
539
        while True:
540
            ready = poller.poll( timeoutms )
541
            for fd, event in ready:
542
                host = h1.fdToNode( fd )
543
                if event & select.POLLIN:
544
                    line = host.readline()
545
                    if line is not None:
546
                        yield host, line
547
            # Return if non-blocking
548
            if not ready and timeoutms >= 0:
549
                yield None, None
550

    
551
    # XXX These test methods should be moved out of this class.
552
    # Probably we should create a tests.py for them
553

    
554
    @staticmethod
555
    def _parsePing( pingOutput ):
556
        "Parse ping output and return packets sent, received."
557
        # Check for downed link
558
        if 'connect: Network is unreachable' in pingOutput:
559
            return 1, 0
560
        r = r'(\d+) packets transmitted, (\d+) received'
561
        m = re.search( r, pingOutput )
562
        if m is None:
563
            error( '*** Error: could not parse ping output: %s\n' %
564
                   pingOutput )
565
            return 1, 0
566
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
567
        return sent, received
568

    
569
    def ping( self, hosts=None, timeout=None ):
570
        """Ping between all specified hosts.
571
           hosts: list of hosts
572
           timeout: time to wait for a response, as string
573
           returns: ploss packet loss percentage"""
574
        # should we check if running?
575
        packets = 0
576
        lost = 0
577
        ploss = None
578
        if not hosts:
579
            hosts = self.hosts
580
            output( '*** Ping: testing ping reachability\n' )
581
        for node in hosts:
582
            output( '%s -> ' % node.name )
583
            for dest in hosts:
584
                if node != dest:
585
                    opts = ''
586
                    if timeout:
587
                        opts = '-W %s' % timeout
588
                    if dest.intfs:
589
                        result = node.cmd( 'ping -c1 %s %s' %
590
                                           (opts, dest.IP()) )
591
                        sent, received = self._parsePing( result )
592
                    else:
593
                        sent, received = 0, 0
594
                    packets += sent
595
                    if received > sent:
596
                        error( '*** Error: received too many packets' )
597
                        error( '%s' % result )
598
                        node.cmdPrint( 'route' )
599
                        exit( 1 )
600
                    lost += sent - received
601
                    output( ( '%s ' % dest.name ) if received else 'X ' )
602
            output( '\n' )
603
        if packets > 0:
604
            ploss = 100.0 * lost / packets
605
            received = packets - lost
606
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
607
                    ( ploss, received, packets ) )
608
        else:
609
            ploss = 0
610
            output( "*** Warning: No packets sent\n" )
611
        return ploss
612

    
613
    @staticmethod
614
    def _parsePingFull( pingOutput ):
615
        "Parse ping output and return all data."
616
        errorTuple = (1, 0, 0, 0, 0, 0)
617
        # Check for downed link
618
        r = r'[uU]nreachable'
619
        m = re.search( r, pingOutput )
620
        if m is not None:
621
            return errorTuple
622
        r = r'(\d+) packets transmitted, (\d+) received'
623
        m = re.search( r, pingOutput )
624
        if m is None:
625
            error( '*** Error: could not parse ping output: %s\n' %
626
                   pingOutput )
627
            return errorTuple
628
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
629
        r = r'rtt min/avg/max/mdev = '
630
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
631
        m = re.search( r, pingOutput )
632
        if m is None:
633
            if received == 0:
634
                return errorTuple
635
            error( '*** Error: could not parse ping output: %s\n' %
636
                   pingOutput )
637
            return errorTuple
638
        rttmin = float( m.group( 1 ) )
639
        rttavg = float( m.group( 2 ) )
640
        rttmax = float( m.group( 3 ) )
641
        rttdev = float( m.group( 4 ) )
642
        return sent, received, rttmin, rttavg, rttmax, rttdev
643

    
644
    def pingFull( self, hosts=None, timeout=None ):
645
        """Ping between all specified hosts and return all data.
646
           hosts: list of hosts
647
           timeout: time to wait for a response, as string
648
           returns: all ping data; see function body."""
649
        # should we check if running?
650
        # Each value is a tuple: (src, dsd, [all ping outputs])
651
        all_outputs = []
652
        if not hosts:
653
            hosts = self.hosts
654
            output( '*** Ping: testing ping reachability\n' )
655
        for node in hosts:
656
            output( '%s -> ' % node.name )
657
            for dest in hosts:
658
                if node != dest:
659
                    opts = ''
660
                    if timeout:
661
                        opts = '-W %s' % timeout
662
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
663
                    outputs = self._parsePingFull( result )
664
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
665
                    all_outputs.append( (node, dest, outputs) )
666
                    output( ( '%s ' % dest.name ) if received else 'X ' )
667
            output( '\n' )
668
        output( "*** Results: \n" )
669
        for outputs in all_outputs:
670
            src, dest, ping_outputs = outputs
671
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
672
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
673
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
674
                    (rttmin, rttavg, rttmax, rttdev) )
675
        return all_outputs
676

    
677
    def pingAll( self, timeout=None ):
678
        """Ping between all hosts.
679
           returns: ploss packet loss percentage"""
680
        return self.ping( timeout=timeout )
681

    
682
    def pingPair( self ):
683
        """Ping between first two hosts, useful for testing.
684
           returns: ploss packet loss percentage"""
685
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
686
        return self.ping( hosts=hosts )
687

    
688
    def pingAllFull( self ):
689
        """Ping between all hosts.
690
           returns: ploss packet loss percentage"""
691
        return self.pingFull()
692

    
693
    def pingPairFull( self ):
694
        """Ping between first two hosts, useful for testing.
695
           returns: ploss packet loss percentage"""
696
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
697
        return self.pingFull( hosts=hosts )
698

    
699
    @staticmethod
700
    def _parseIperf( iperfOutput ):
701
        """Parse iperf output and return bandwidth.
702
           iperfOutput: string
703
           returns: result string"""
704
        r = r'([\d\.]+ \w+/sec)'
705
        m = re.findall( r, iperfOutput )
706
        if m:
707
            return m[-1]
708
        else:
709
            # was: raise Exception(...)
710
            error( 'could not parse iperf output: ' + iperfOutput )
711
            return ''
712

    
713
    # XXX This should be cleaned up
714

    
715
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
716
               seconds=5):
717
        """Run iperf between two hosts.
718
           hosts: list of hosts; if None, uses first and last hosts
719
           l4Type: string, one of [ TCP, UDP ]
720
           udpBw: bandwidth target for UDP test
721
           fmt: iperf format argument if any
722
           seconds: iperf time to transmit
723
           returns: two-element array of [ server, client ] speeds
724
           note: send() is buffered, so client rate can be much higher than
725
           the actual transmission rate; on an unloaded system, server
726
           rate should be much closer to the actual receive rate"""
727
        if not quietRun( 'which telnet' ):
728
            error( 'Cannot find telnet in $PATH - required for iperf test' )
729
            return
730
        hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
731
        assert len( hosts ) == 2
732
        client, server = hosts
733
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
734
        output( "%s and %s\n" % ( client.name, server.name ) )
735
        server.cmd( 'killall -9 iperf' )
736
        iperfArgs = 'iperf '
737
        bwArgs = ''
738
        if l4Type == 'UDP':
739
            iperfArgs += '-u '
740
            bwArgs = '-b ' + udpBw + ' '
741
        elif l4Type != 'TCP':
742
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
743
        if fmt:
744
            iperfArgs += '-f %s ' % fmt
745
        server.sendCmd( iperfArgs + '-s', printPid=True )
746
        servout = ''
747
        while server.lastPid is None:
748
            servout += server.monitor()
749
        if l4Type == 'TCP':
750
            while 'Connected' not in client.cmd(
751
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
752
                info( 'Waiting for iperf to start up...' )
753
                sleep(.5)
754
        cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
755
                             server.IP() + ' ' + bwArgs )
756
        debug( 'Client output: %s\n' % cliout )
757
        server.sendInt()
758
        servout += server.waitOutput()
759
        debug( 'Server output: %s\n' % servout )
760
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
761
        if l4Type == 'UDP':
762
            result.insert( 0, udpBw )
763
        output( '*** Results: %s\n' % result )
764
        return result
765

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

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

    
839
    def interact( self ):
840
        "Start network and run our simple CLI."
841
        self.start()
842
        result = CLI( self )
843
        self.stop()
844
        return result
845

    
846
    inited = False
847

    
848
    @classmethod
849
    def init( cls ):
850
        "Initialize Mininet"
851
        if cls.inited:
852
            return
853
        ensureRoot()
854
        fixLimits()
855
        cls.inited = True
856

    
857

    
858
class MininetWithControlNet( Mininet ):
859

    
860
    """Control network support:
861

862
       Create an explicit control network. Currently this is only
863
       used/usable with the user datapath.
864

865
       Notes:
866

867
       1. If the controller and switches are in the same (e.g. root)
868
          namespace, they can just use the loopback connection.
869

870
       2. If we can get unix domain sockets to work, we can use them
871
          instead of an explicit control network.
872

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

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

879
       5. Basically nobody ever used this code, so it has been moved
880
          into its own class.
881

882
       6. Ultimately we may wish to extend this to allow us to create a
883
          control network which every node's control interface is
884
          attached to."""
885

    
886
    def configureControlNetwork( self ):
887
        "Configure control network."
888
        self.configureRoutedControlNetwork()
889

    
890
    # We still need to figure out the right way to pass
891
    # in the control network location.
892

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