Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 89fb0819

History | View | Annotate | Download (34.5 KB)

1
"""
2

3
    Mininet: A simple networking testbed for OpenFlow/SDN!
4

5
author: Bob Lantz (rlantz@cs.stanford.edu)
6
author: Brandon Heller (brandonh@stanford.edu)
7

8
Mininet creates scalable OpenFlow test networks by using
9
process-based virtualization and network namespaces.
10

11
Simulated hosts are created as processes in separate network
12
namespaces. This allows a complete OpenFlow network to be simulated on
13
top of a single Linux kernel.
14

15
Each host has:
16

17
A virtual console (pipes to a shell)
18
A virtual interfaces (half of a veth pair)
19
A parent shell (and possibly some child processes) in a namespace
20

21
Hosts have a network interface which is configured via ifconfig/ip
22
link/etc.
23

24
This version supports both the kernel and user space datapaths
25
from the OpenFlow reference implementation (openflowswitch.org)
26
as well as OpenVSwitch (openvswitch.org.)
27

28
In kernel datapath mode, the controller and switches are simply
29
processes in the root namespace.
30

31
Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32
attached to the one side of a veth pair; the other side resides in the
33
host namespace. In this mode, switch processes can simply connect to the
34
controller via the loopback interface.
35

36
In user datapath mode, the controller and switches can be full-service
37
nodes that live in their own network namespaces and have management
38
interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39
currently routed although it could be bridged.)
40

41
In addition to a management interface, user mode switches also have
42
several switch interfaces, halves of veth pairs whose other halves
43
reside in the host nodes that the switches are connected to.
44

45
Consistent, straightforward naming is important in order to easily
46
identify hosts, switches and controllers, both from the CLI and
47
from program code. Interfaces are named to make it easy to identify
48
which interfaces belong to which node.
49

50
The basic naming scheme is as follows:
51

52
    Host nodes are named h1-hN
53
    Switch nodes are named s1-sN
54
    Controller nodes are named c0-cN
55
    Interfaces are named {nodename}-eth0 .. {nodename}-ethN
56

57
Note: If the network topology is created using mininet.topo, then
58
node numbers are unique among hosts and switches (e.g. we have
59
h1..hN and SN..SN+M) and also correspond to their default IP addresses
60
of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61
hN. This mapping allows easy determination of a node's IP
62
address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63

64
Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
65
"ping 10.1" is equivalent to "ping 10.0.0.1".
66

67
Currently we wrap the entire network in a 'mininet' object, which
68
constructs a simulated network based on a network topology created
69
using a topology object (e.g. LinearTopo) from mininet.topo or
70
mininet.topolib, and a Controller which the switches will connect
71
to. Several configuration options are provided for functions such as
72
automatically setting MAC addresses, populating the ARP table, or
73
even running a set of terminals to allow direct interaction with nodes.
74

75
After the network is created, it can be started using start(), and a
76
variety of useful tasks maybe performed, including basic connectivity
77
and bandwidth tests and running the mininet CLI.
78

79
Once the network is up and running, test code can easily get access
80
to host and switch objects which can then be used for arbitrary
81
experiments, typically involving running a series of commands on the
82
hosts.
83

84
After all desired tests or activities have been completed, the stop()
85
method may be called to shut down the network.
86

87
"""
88

    
89
import os
90
import re
91
import select
92
import signal
93
import random
94
import copy
95
from time import sleep
96
from itertools import chain, groupby
97
from math import ceil
98

    
99
from mininet.cli import CLI
100
from mininet.log import info, error, debug, output, warn
101
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller
102
from mininet.nodelib import NAT
103
from mininet.link import Link, Intf
104
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
105
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
106
from mininet.term import cleanUpScreens, makeTerms
107

    
108
# Mininet version: should be consistent with README and LICENSE
109
VERSION = "2.1.0+"
110

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

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

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

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

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

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

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

    
172

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

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

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

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

    
267
    def addNAT( self, name='nat0', connect=True, inNamespace=False, **params ):
268
        nat = self.addHost( name, cls=NAT, inNamespace=inNamespace, 
269
                            subnet=self.ipBase, **params )
270
        # find first switch and create link
271
        if connect:
272
            # connect the nat to the first switch
273
            self.addLink( nat, self.switches[ 0 ] )
274
            # set the default route on hosts
275
            natIP = nat.params[ 'ip' ].split('/')[ 0 ]
276
            for host in self.hosts:
277
                if host.inNamespace:
278
                    host.setDefaultRoute( 'via %s' % natIP )
279
        return nat
280

    
281
    # BL: We now have four ways to look up nodes
282
    # This may (should?) be cleaned up in the future.
283
    def getNodeByName( self, *args ):
284
        "Return node(s) with given name(s)"
285
        if len( args ) == 1:
286
            return self.nameToNode[ args[ 0 ] ]
287
        return [ self.nameToNode[ n ] for n in args ]
288

    
289
    def get( self, *args ):
290
        "Convenience alias for getNodeByName"
291
        return self.getNodeByName( *args )
292

    
293
    # Even more convenient syntax for node lookup and iteration
294
    def __getitem__( self, key ):
295
        """net [ name ] operator: Return node(s) with given name(s)"""
296
        return self.nameToNode[ key ]
297

    
298
    def __iter__( self ):
299
        "return iterator over node names"
300
        for node in chain( self.hosts, self.switches, self.controllers ):
301
            yield node.name
302

    
303
    def __len__( self ):
304
        "returns number of nodes in net"
305
        return ( len( self.hosts ) + len( self.switches ) +
306
                 len( self.controllers ) )
307

    
308
    def __contains__( self, item ):
309
        "returns True if net contains named node"
310
        return item in self.nameToNode
311

    
312
    def keys( self ):
313
        "return a list of all node names or net's keys"
314
        return list( self )
315

    
316
    def values( self ):
317
        "return a list of all nodes or net's values"
318
        return [ self[name] for name in self ]
319

    
320
    def items( self ):
321
        "return (key,value) tuple list for every node in net"
322
        return zip( self.keys(), self.values() )
323

    
324
    @staticmethod
325
    def randMac():
326
        "Return a random, non-multicast MAC address"
327
        return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff  | 0x020000000000 )
328
    
329
    def addLink( self, node1, node2, port1=None, port2=None,
330
                 cls=None, paramDict=None, **params ):
331
        """"Add a link from node1 to node2
332
            node1: source node
333
            node2: dest node
334
            port1: source port (optional)
335
            port2: dest port (optional)
336
            cls: link class (optional)
337
            paramDict: dictionary of additional link params (optional)
338
            params: additional link params (optional)
339
            returns: link object"""
340
        mac1 = self.randMac()
341
        mac2 = self.randMac()
342
        paramDict = {} if paramDict is None else paramDict
343
        paramDict.update( params )
344
        # Ugly: try to ensure that node1 and node2 line up correctly with
345
        # other link parameters
346
        node1 = self[ paramDict.pop( 'node1', node1.name ) ]
347
        node2 = self[ paramDict.pop( 'node2', node2.name ) ]
348
        paramDict.setdefault( 'port1', port1 )
349
        paramDict.setdefault( 'port2', port2 )
350
        paramDict.setdefault( 'addr1', mac1 )
351
        paramDict.setdefault( 'addr2', mac2 )
352
        cls = self.link if cls is None else cls
353
        link = cls( node1, node2, **paramDict )
354
        self.links.append( link )
355
        return link
356

    
357
    def configHosts( self ):
358
        "Configure a set of hosts."
359
        for host in self.hosts:
360
            info( host.name + ' ' )
361
            intf = host.defaultIntf()
362
            if intf:
363
                host.configDefault()
364
            else:
365
                # Don't configure nonexistent intf
366
                host.configDefault( ip=None, mac=None )
367
            # You're low priority, dude!
368
            # BL: do we want to do this here or not?
369
            # May not make sense if we have CPU lmiting...
370
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
371
            # This may not be the right place to do this, but
372
            # it needs to be done somewhere.
373
        info( '\n' )
374

    
375
    def buildFromTopo( self, topo=None ):
376
        """Build mininet from a topology object
377
           At the end of this function, everything should be connected
378
           and up."""
379

    
380
        # Possibly we should clean up here and/or validate
381
        # the topo
382
        if self.cleanup:
383
            pass
384

    
385
        info( '*** Creating network\n' )
386

    
387
        if not self.controllers and self.controller:
388
            # Add a default controller
389
            info( '*** Adding controller\n' )
390
            classes = self.controller
391
            if type( classes ) is not list:
392
                classes = [ classes ]
393
            for i, cls in enumerate( classes ):
394
                # Allow Controller objects because nobody understands currying
395
                if isinstance( cls, Controller ):
396
                    self.addController( cls )
397
                else:
398
                    self.addController( 'c%d' % i, cls )
399

    
400
        info( '*** Adding hosts:\n' )
401
        for hostName in topo.hosts():
402
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
403
            info( hostName + ' ' )
404

    
405
        info( '\n*** Adding switches:\n' )
406
        for switchName in topo.switches():
407
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
408
            info( switchName + ' ' )
409

    
410
        info( '\n*** Adding links:\n' )
411
        for srcName, dstName in topo.links( sort=True ):
412
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
413
            params = topo.linkInfo( srcName, dstName )
414
            srcPort, dstPort = topo.port( srcName, dstName )
415
            self.addLink( src, dst, srcPort, dstPort, paramDict=params )
416
            info( '(%s, %s) ' % ( src.name, dst.name ) )
417

    
418
        info( '\n' )
419

    
420
    def configureControlNetwork( self ):
421
        "Control net config hook: override in subclass"
422
        raise Exception( 'configureControlNetwork: '
423
                         'should be overriden in subclass', self )
424

    
425
    def build( self ):
426
        "Build mininet."
427
        if self.topo:
428
            self.buildFromTopo( self.topo )
429
        if self.inNamespace:
430
            self.configureControlNetwork()
431
        info( '*** Configuring hosts\n' )
432
        self.configHosts()
433
        if self.xterms:
434
            self.startTerms()
435
        if self.autoStaticArp:
436
            self.staticArp()
437
        self.built = True
438

    
439
    def startTerms( self ):
440
        "Start a terminal for each node."
441
        if 'DISPLAY' not in os.environ:
442
            error( "Error starting terms: Cannot connect to display\n" )
443
            return
444
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
445
        cleanUpScreens()
446
        self.terms += makeTerms( self.controllers, 'controller' )
447
        self.terms += makeTerms( self.switches, 'switch' )
448
        self.terms += makeTerms( self.hosts, 'host' )
449

    
450
    def stopXterms( self ):
451
        "Kill each xterm."
452
        for term in self.terms:
453
            os.kill( term.pid, signal.SIGKILL )
454
        cleanUpScreens()
455

    
456
    def staticArp( self ):
457
        "Add all-pairs ARP entries to remove the need to handle broadcast."
458
        for src in self.hosts:
459
            for dst in self.hosts:
460
                if src != dst:
461
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
462

    
463
    def start( self ):
464
        "Start controller and switches."
465
        if not self.built:
466
            self.build()
467
        info( '*** Starting controller\n' )
468
        for controller in self.controllers:
469
            controller.start()
470
        info( '*** Starting %s switches\n' % len( self.switches ) )
471
        for switch in self.switches:
472
            info( switch.name + ' ')
473
            switch.start( self.controllers )
474
        info( '\n' )
475
        if self.waitConn:
476
            self.waitConnected()
477

    
478
    def stop( self ):
479
        "Stop the controller(s), switches and hosts"
480
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
481
        for controller in self.controllers:
482
            info( controller.name + ' ' )
483
            controller.stop()
484
        info( '\n' )
485
        if self.terms:
486
            info( '*** Stopping %i terms\n' % len( self.terms ) )
487
            self.stopXterms()
488
        info( '*** Stopping %i switches\n' % len( self.switches ) )
489
        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
490
            if hasattr( swclass, 'batchShutdown' ):
491
                swclass.batchShutdown( switches )
492
        for switch in self.switches:
493
            info( switch.name + ' ' )
494
            switch.stop()
495
            switch.terminate()
496
        info( '\n' )
497
        info( '*** Stopping %i links\n' % len( self.links ) )
498
        for link in self.links:
499
            link.stop()
500
        info( '\n' )
501
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
502
        for host in self.hosts:
503
            info( host.name + ' ' )
504
            host.terminate()
505
        info( '\n*** Done\n' )
506

    
507
    def run( self, test, *args, **kwargs ):
508
        "Perform a complete start/test/stop cycle."
509
        self.start()
510
        info( '*** Running test\n' )
511
        result = test( *args, **kwargs )
512
        self.stop()
513
        return result
514

    
515
    def monitor( self, hosts=None, timeoutms=-1 ):
516
        """Monitor a set of hosts (or all hosts by default),
517
           and return their output, a line at a time.
518
           hosts: (optional) set of hosts to monitor
519
           timeoutms: (optional) timeout value in ms
520
           returns: iterator which returns host, line"""
521
        if hosts is None:
522
            hosts = self.hosts
523
        poller = select.poll()
524
        Node = hosts[ 0 ]  # so we can call class method fdToNode
525
        for host in hosts:
526
            poller.register( host.stdout )
527
        while True:
528
            ready = poller.poll( timeoutms )
529
            for fd, event in ready:
530
                host = Node.fdToNode( fd )
531
                if event & select.POLLIN:
532
                    line = host.readline()
533
                    if line is not None:
534
                        yield host, line
535
            # Return if non-blocking
536
            if not ready and timeoutms >= 0:
537
                yield None, None
538

    
539
    # XXX These test methods should be moved out of this class.
540
    # Probably we should create a tests.py for them
541

    
542
    @staticmethod
543
    def _parsePing( pingOutput ):
544
        "Parse ping output and return packets sent, received."
545
        # Check for downed link
546
        if 'connect: Network is unreachable' in pingOutput:
547
            return 1, 0
548
        r = r'(\d+) packets transmitted, (\d+) received'
549
        m = re.search( r, pingOutput )
550
        if m is None:
551
            error( '*** Error: could not parse ping output: %s\n' %
552
                   pingOutput )
553
            return 1, 0
554
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
555
        return sent, received
556

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

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

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

    
664
    def pingAll( self, timeout=None ):
665
        """Ping between all hosts.
666
           returns: ploss packet loss percentage"""
667
        return self.ping( timeout=timeout )
668

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

    
675
    def pingAllFull( self ):
676
        """Ping between all hosts.
677
           returns: ploss packet loss percentage"""
678
        return self.pingFull()
679

    
680
    def pingPairFull( self ):
681
        """Ping between first two hosts, useful for testing.
682
           returns: ploss packet loss percentage"""
683
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
684
        return self.pingFull( hosts=hosts )
685

    
686
    @staticmethod
687
    def _parseIperf( iperfOutput ):
688
        """Parse iperf output and return bandwidth.
689
           iperfOutput: string
690
           returns: result string"""
691
        r = r'([\d\.]+ \w+/sec)'
692
        m = re.findall( r, iperfOutput )
693
        if m:
694
            return m[-1]
695
        else:
696
            # was: raise Exception(...)
697
            error( 'could not parse iperf output: ' + iperfOutput )
698
            return ''
699

    
700
    # XXX This should be cleaned up
701

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

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

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

    
822
    def interact( self ):
823
        "Start network and run our simple CLI."
824
        self.start()
825
        result = CLI( self )
826
        self.stop()
827
        return result
828

    
829
    inited = False
830

    
831
    @classmethod
832
    def init( cls ):
833
        "Initialize Mininet"
834
        if cls.inited:
835
            return
836
        ensureRoot()
837
        fixLimits()
838
        cls.inited = True
839

    
840

    
841
class MininetWithControlNet( Mininet ):
842

    
843
    """Control network support:
844

845
       Create an explicit control network. Currently this is only
846
       used/usable with the user datapath.
847

848
       Notes:
849

850
       1. If the controller and switches are in the same (e.g. root)
851
          namespace, they can just use the loopback connection.
852

853
       2. If we can get unix domain sockets to work, we can use them
854
          instead of an explicit control network.
855

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

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

862
       5. Basically nobody ever used this code, so it has been moved
863
          into its own class.
864

865
       6. Ultimately we may wish to extend this to allow us to create a
866
          control network which every node's control interface is
867
          attached to."""
868

    
869
    def configureControlNetwork( self ):
870
        "Configure control network."
871
        self.configureRoutedControlNetwork()
872

    
873
    # We still need to figure out the right way to pass
874
    # in the control network location.
875

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