Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 4015e066

History | View | Annotate | Download (32.7 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 copy
94
from time import sleep
95
from itertools import chain, groupby
96

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

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

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

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

    
155
        self.hosts = []
156
        self.switches = []
157
        self.controllers = []
158

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

    
161
        self.terms = []  # list of spawned xterm processes
162

    
163
        Mininet.init()  # Initialize Mininet if necessary
164

    
165
        self.built = False
166
        if topo and build:
167
            self.build()
168

    
169

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

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

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

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

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

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

    
286
    def get( self, *args ):
287
        "Convenience alias for getNodeByName"
288
        return self.getNodeByName( *args )
289

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

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

    
300
    def __len__( self ):
301
        "returns number of nodes in net"
302
        return ( len( self.hosts ) + len( self.switches ) +
303
                 len( self.controllers ) )
304

    
305
    def __contains__( self, item ):
306
        "returns True if net contains named node"
307
        return item in self.nameToNode
308

    
309
    def keys( self ):
310
        "return a list of all node names or net's keys"
311
        return list( self )
312

    
313
    def values( self ):
314
        "return a list of all nodes or net's values"
315
        return [ self[name] for name in self ]
316

    
317
    def items( self ):
318
        "return (key,value) tuple list for every node in net"
319
        return zip( self.keys(), self.values() )
320

    
321
    def addLink( self, node1, node2, port1=None, port2=None,
322
                 cls=None, **params ):
323
        """"Add a link from node1 to node2
324
            node1: source node
325
            node2: dest node
326
            port1: source port
327
            port2: dest port
328
            returns: link object"""
329
        defaults = { 'port1': port1,
330
                     'port2': port2,
331
                     'intf': self.intf }
332
        defaults.update( params )
333
        if not cls:
334
            cls = self.link
335
        return cls( node1, node2, **defaults )
336

    
337
    def configHosts( self ):
338
        "Configure a set of hosts."
339
        for host in self.hosts:
340
            info( host.name + ' ' )
341
            intf = host.defaultIntf()
342
            if intf:
343
                host.configDefault()
344
            else:
345
                # Don't configure nonexistent intf
346
                host.configDefault( ip=None, mac=None )
347
            # You're low priority, dude!
348
            # BL: do we want to do this here or not?
349
            # May not make sense if we have CPU lmiting...
350
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
351
            # This may not be the right place to do this, but
352
            # it needs to be done somewhere.
353
            host.cmd( 'ifconfig lo up' )
354
        info( '\n' )
355

    
356
    def buildFromTopo( self, topo=None ):
357
        """Build mininet from a topology object
358
           At the end of this function, everything should be connected
359
           and up."""
360

    
361
        # Possibly we should clean up here and/or validate
362
        # the topo
363
        if self.cleanup:
364
            pass
365

    
366
        info( '*** Creating network\n' )
367

    
368
        if not self.controllers and self.controller:
369
            # Add a default controller
370
            info( '*** Adding controller\n' )
371
            classes = self.controller
372
            if type( classes ) is not list:
373
                classes = [ classes ]
374
            for i, cls in enumerate( classes ):
375
                # Allow Controller objects because nobody understands currying
376
                if isinstance( cls, Controller ):
377
                    self.addController( cls )
378
                else:
379
                    self.addController( 'c%d' % i, cls )
380

    
381
        info( '*** Adding hosts:\n' )
382
        for hostName in topo.hosts():
383
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
384
            info( hostName + ' ' )
385

    
386
        info( '\n*** Adding switches:\n' )
387
        for switchName in topo.switches():
388
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
389
            info( switchName + ' ' )
390

    
391
        info( '\n*** Adding links:\n' )
392
        for srcName, dstName in topo.links(sort=True):
393
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
394
            params = topo.linkInfo( srcName, dstName )
395
            srcPort, dstPort = topo.port( srcName, dstName )
396
            self.addLink( src, dst, srcPort, dstPort, **params )
397
            info( '(%s, %s) ' % ( src.name, dst.name ) )
398

    
399
        info( '\n' )
400

    
401
    def configureControlNetwork( self ):
402
        "Control net config hook: override in subclass"
403
        raise Exception( 'configureControlNetwork: '
404
                         'should be overriden in subclass', self )
405

    
406
    def build( self ):
407
        "Build mininet."
408
        if self.topo:
409
            self.buildFromTopo( self.topo )
410
        if self.inNamespace:
411
            self.configureControlNetwork()
412
        info( '*** Configuring hosts\n' )
413
        self.configHosts()
414
        if self.xterms:
415
            self.startTerms()
416
        if self.autoStaticArp:
417
            self.staticArp()
418
        self.built = True
419

    
420
    def startTerms( self ):
421
        "Start a terminal for each node."
422
        if 'DISPLAY' not in os.environ:
423
            error( "Error starting terms: Cannot connect to display\n" )
424
            return
425
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
426
        cleanUpScreens()
427
        self.terms += makeTerms( self.controllers, 'controller' )
428
        self.terms += makeTerms( self.switches, 'switch' )
429
        self.terms += makeTerms( self.hosts, 'host' )
430

    
431
    def stopXterms( self ):
432
        "Kill each xterm."
433
        for term in self.terms:
434
            os.kill( term.pid, signal.SIGKILL )
435
        cleanUpScreens()
436

    
437
    def staticArp( self ):
438
        "Add all-pairs ARP entries to remove the need to handle broadcast."
439
        for src in self.hosts:
440
            for dst in self.hosts:
441
                if src != dst:
442
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
443

    
444
    def start( self ):
445
        "Start controller and switches."
446
        if not self.built:
447
            self.build()
448
        info( '*** Starting controller\n' )
449
        for controller in self.controllers:
450
            controller.start()
451
        info( '*** Starting %s switches\n' % len( self.switches ) )
452
        for switch in self.switches:
453
            info( switch.name + ' ')
454
            switch.start( self.controllers )
455
        info( '\n' )
456
        if self.waitConn:
457
            self.waitConnected()
458

    
459
    def stop( self ):
460
        "Stop the controller(s), switches and hosts"
461
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
462
        for controller in self.controllers:
463
            info( controller.name + ' ' )
464
            controller.stop()
465
        info( '\n' )
466
        if self.terms:
467
            info( '*** Stopping %i terms\n' % len( self.terms ) )
468
            self.stopXterms()
469
        info( '*** Stopping %i switches\n' % len( self.switches ) )
470
        for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
471
            if hasattr( swclass, 'batchShutdown' ):
472
                swclass.batchShutdown( switches )
473
        for switch in self.switches:
474
            info( switch.name + ' ' )
475
            switch.stop()
476
        info( '\n' )
477
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
478
        for host in self.hosts:
479
            info( host.name + ' ' )
480
            host.terminate()
481
        info( '\n*** Done\n' )
482

    
483
    def run( self, test, *args, **kwargs ):
484
        "Perform a complete start/test/stop cycle."
485
        self.start()
486
        info( '*** Running test\n' )
487
        result = test( *args, **kwargs )
488
        self.stop()
489
        return result
490

    
491
    def monitor( self, hosts=None, timeoutms=-1 ):
492
        """Monitor a set of hosts (or all hosts by default),
493
           and return their output, a line at a time.
494
           hosts: (optional) set of hosts to monitor
495
           timeoutms: (optional) timeout value in ms
496
           returns: iterator which returns host, line"""
497
        if hosts is None:
498
            hosts = self.hosts
499
        poller = select.poll()
500
        Node = hosts[ 0 ]  # so we can call class method fdToNode
501
        for host in hosts:
502
            poller.register( host.stdout )
503
        while True:
504
            ready = poller.poll( timeoutms )
505
            for fd, event in ready:
506
                host = Node.fdToNode( fd )
507
                if event & select.POLLIN:
508
                    line = host.readline()
509
                    if line is not None:
510
                        yield host, line
511
            # Return if non-blocking
512
            if not ready and timeoutms >= 0:
513
                yield None, None
514

    
515
    # XXX These test methods should be moved out of this class.
516
    # Probably we should create a tests.py for them
517

    
518
    @staticmethod
519
    def _parsePing( pingOutput ):
520
        "Parse ping output and return packets sent, received."
521
        # Check for downed link
522
        if 'connect: Network is unreachable' in pingOutput:
523
            return 1, 0
524
        r = r'(\d+) packets transmitted, (\d+) received'
525
        m = re.search( r, pingOutput )
526
        if m is None:
527
            error( '*** Error: could not parse ping output: %s\n' %
528
                   pingOutput )
529
            return 1, 0
530
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
531
        return sent, received
532

    
533
    def ping( self, hosts=None, timeout=None ):
534
        """Ping between all specified hosts.
535
           hosts: list of hosts
536
           timeout: time to wait for a response, as string
537
           returns: ploss packet loss percentage"""
538
        # should we check if running?
539
        packets = 0
540
        lost = 0
541
        ploss = None
542
        if not hosts:
543
            hosts = self.hosts
544
            output( '*** Ping: testing ping reachability\n' )
545
        for node in hosts:
546
            output( '%s -> ' % node.name )
547
            for dest in hosts:
548
                if node != dest:
549
                    opts = ''
550
                    if timeout:
551
                        opts = '-W %s' % timeout
552
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
553
                    sent, received = self._parsePing( result )
554
                    packets += sent
555
                    if received > sent:
556
                        error( '*** Error: received too many packets' )
557
                        error( '%s' % result )
558
                        node.cmdPrint( 'route' )
559
                        exit( 1 )
560
                    lost += sent - received
561
                    output( ( '%s ' % dest.name ) if received else 'X ' )
562
            output( '\n' )
563
        if packets > 0:
564
            ploss = 100.0 * lost / packets
565
            received = packets - lost
566
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
567
                    ( ploss, received, packets ) )
568
        else:
569
            ploss = 0
570
            output( "*** Warning: No packets sent\n" )
571
        return ploss
572

    
573
    @staticmethod
574
    def _parsePingFull( pingOutput ):
575
        "Parse ping output and return all data."
576
        errorTuple = (1, 0, 0, 0, 0, 0)
577
        # Check for downed link
578
        r = r'[uU]nreachable'
579
        m = re.search( r, pingOutput )
580
        if m is not None:
581
            return errorTuple
582
        r = r'(\d+) packets transmitted, (\d+) received'
583
        m = re.search( r, pingOutput )
584
        if m is None:
585
            error( '*** Error: could not parse ping output: %s\n' %
586
                   pingOutput )
587
            return errorTuple
588
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
589
        r = r'rtt min/avg/max/mdev = '
590
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
591
        m = re.search( r, pingOutput )
592
        if m is None:
593
            error( '*** Error: could not parse ping output: %s\n' %
594
                   pingOutput )
595
            return errorTuple
596
        rttmin = float( m.group( 1 ) )
597
        rttavg = float( m.group( 2 ) )
598
        rttmax = float( m.group( 3 ) )
599
        rttdev = float( m.group( 4 ) )
600
        return sent, received, rttmin, rttavg, rttmax, rttdev
601

    
602
    def pingFull( self, hosts=None, timeout=None ):
603
        """Ping between all specified hosts and return all data.
604
           hosts: list of hosts
605
           timeout: time to wait for a response, as string
606
           returns: all ping data; see function body."""
607
        # should we check if running?
608
        # Each value is a tuple: (src, dsd, [all ping outputs])
609
        all_outputs = []
610
        if not hosts:
611
            hosts = self.hosts
612
            output( '*** Ping: testing ping reachability\n' )
613
        for node in hosts:
614
            output( '%s -> ' % node.name )
615
            for dest in hosts:
616
                if node != dest:
617
                    opts = ''
618
                    if timeout:
619
                        opts = '-W %s' % timeout
620
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
621
                    outputs = self._parsePingFull( result )
622
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
623
                    all_outputs.append( (node, dest, outputs) )
624
                    output( ( '%s ' % dest.name ) if received else 'X ' )
625
            output( '\n' )
626
        output( "*** Results: \n" )
627
        for outputs in all_outputs:
628
            src, dest, ping_outputs = outputs
629
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
630
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
631
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
632
                    (rttmin, rttavg, rttmax, rttdev) )
633
        return all_outputs
634

    
635
    def pingAll( self, timeout=None ):
636
        """Ping between all hosts.
637
           returns: ploss packet loss percentage"""
638
        return self.ping( timeout=timeout )
639

    
640
    def pingPair( self ):
641
        """Ping between first two hosts, useful for testing.
642
           returns: ploss packet loss percentage"""
643
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
644
        return self.ping( hosts=hosts )
645

    
646
    def pingAllFull( self ):
647
        """Ping between all hosts.
648
           returns: ploss packet loss percentage"""
649
        return self.pingFull()
650

    
651
    def pingPairFull( self ):
652
        """Ping between first two hosts, useful for testing.
653
           returns: ploss packet loss percentage"""
654
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
655
        return self.pingFull( hosts=hosts )
656

    
657
    @staticmethod
658
    def _parseIperf( iperfOutput ):
659
        """Parse iperf output and return bandwidth.
660
           iperfOutput: string
661
           returns: result string"""
662
        r = r'([\d\.]+ \w+/sec)'
663
        m = re.findall( r, iperfOutput )
664
        if m:
665
            return m[-1]
666
        else:
667
            # was: raise Exception(...)
668
            error( 'could not parse iperf output: ' + iperfOutput )
669
            return ''
670

    
671
    # XXX This should be cleaned up
672

    
673
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
674
        """Run iperf between two hosts.
675
           hosts: list of hosts; if None, uses opposite hosts
676
           l4Type: string, one of [ TCP, UDP ]
677
           returns: results two-element array of server and client speeds"""
678
        if not quietRun( 'which telnet' ):
679
            error( 'Cannot find telnet in $PATH - required for iperf test' )
680
            return
681
        if not hosts:
682
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
683
        else:
684
            assert len( hosts ) == 2
685
        client, server = hosts
686
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
687
        output( "%s and %s\n" % ( client.name, server.name ) )
688
        server.cmd( 'killall -9 iperf' )
689
        iperfArgs = 'iperf '
690
        bwArgs = ''
691
        if l4Type == 'UDP':
692
            iperfArgs += '-u '
693
            bwArgs = '-b ' + udpBw + ' '
694
        elif l4Type != 'TCP':
695
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
696
        if format:
697
          iperfArgs += '-f %s ' %format
698
        server.sendCmd( iperfArgs + '-s', printPid=True )
699
        servout = ''
700
        while server.lastPid is None:
701
            servout += server.monitor()
702
        if l4Type == 'TCP':
703
            while 'Connected' not in client.cmd(
704
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
705
                info( 'Waiting for iperf to start up...' )
706
                sleep(.5)
707
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
708
                             bwArgs )
709
        debug( 'Client output: %s\n' % cliout )
710
        server.sendInt()
711
        servout += server.waitOutput()
712
        debug( 'Server output: %s\n' % servout )
713
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
714
        if l4Type == 'UDP':
715
            result.insert( 0, udpBw )
716
        output( '*** Results: %s\n' % result )
717
        return result
718

    
719
    def runCpuLimitTest( self, cpu, duration=5 ):
720
        """run CPU limit test with 'while true' processes.
721
        cpu: desired CPU fraction of each host
722
        duration: test duration in seconds
723
        returns a single list of measured CPU fractions as floats.
724
        """
725
        pct = cpu * 100
726
        info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
727
        hosts = self.hosts
728
        for h in hosts:
729
            h.cmd( 'while true; do a=1; done &' )
730
        pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
731
        pids_str = ",".join(["%s" % pid for pid in pids])
732
        cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
733
        # It's a shame that this is what pylint prefers
734
        outputs = []
735
        for _ in range( duration ):
736
            sleep( 1 )
737
            outputs.append( quietRun( cmd ).strip() )
738
        for h in hosts:
739
            h.cmd( 'kill %1' )
740
        cpu_fractions = []
741
        for test_output in outputs:
742
            # Split by line.  Ignore first line, which looks like this:
743
            # PID %CPU COMMAND\n
744
            for line in test_output.split('\n')[1:]:
745
                r = r'\d+\s*(\d+\.\d+)'
746
                m = re.search( r, line )
747
                if m is None:
748
                    error( '*** Error: could not extract CPU fraction: %s\n' %
749
                           line )
750
                    return None
751
                cpu_fractions.append( float( m.group( 1 ) ) )
752
        output( '*** Results: %s\n' % cpu_fractions )
753
        return cpu_fractions
754

    
755
    # BL: I think this can be rewritten now that we have
756
    # a real link class.
757
    def configLinkStatus( self, src, dst, status ):
758
        """Change status of src <-> dst links.
759
           src: node name
760
           dst: node name
761
           status: string {up, down}"""
762
        if src not in self.nameToNode:
763
            error( 'src not in network: %s\n' % src )
764
        elif dst not in self.nameToNode:
765
            error( 'dst not in network: %s\n' % dst )
766
        else:
767
            if type( src ) is str:
768
                src = self.nameToNode[ src ]
769
            if type( dst ) is str:
770
                dst = self.nameToNode[ dst ]
771
            connections = src.connectionsTo( dst )
772
            if len( connections ) == 0:
773
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
774
            for srcIntf, dstIntf in connections:
775
                result = srcIntf.ifconfig( status )
776
                if result:
777
                    error( 'link src status change failed: %s\n' % result )
778
                result = dstIntf.ifconfig( status )
779
                if result:
780
                    error( 'link dst status change failed: %s\n' % result )
781

    
782
    def interact( self ):
783
        "Start network and run our simple CLI."
784
        self.start()
785
        result = CLI( self )
786
        self.stop()
787
        return result
788

    
789
    inited = False
790

    
791
    @classmethod
792
    def init( cls ):
793
        "Initialize Mininet"
794
        if cls.inited:
795
            return
796
        ensureRoot()
797
        fixLimits()
798
        cls.inited = True
799

    
800

    
801
class MininetWithControlNet( Mininet ):
802

    
803
    """Control network support:
804

805
       Create an explicit control network. Currently this is only
806
       used/usable with the user datapath.
807

808
       Notes:
809

810
       1. If the controller and switches are in the same (e.g. root)
811
          namespace, they can just use the loopback connection.
812

813
       2. If we can get unix domain sockets to work, we can use them
814
          instead of an explicit control network.
815

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

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

822
       5. Basically nobody ever used this code, so it has been moved
823
          into its own class.
824

825
       6. Ultimately we may wish to extend this to allow us to create a
826
          control network which every node's control interface is
827
          attached to."""
828

    
829
    def configureControlNetwork( self ):
830
        "Configure control network."
831
        self.configureRoutedControlNetwork()
832

    
833
    # We still need to figure out the right way to pass
834
    # in the control network location.
835

    
836
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
837
                                       prefixLen=16 ):
838
        """Configure a routed control network on controller and switches.
839
           For use with the user datapath only right now."""
840
        controller = self.controllers[ 0 ]
841
        info( controller.name + ' <->' )
842
        cip = ip
843
        snum = ipParse( ip )
844
        for switch in self.switches:
845
            info( ' ' + switch.name )
846
            link = self.link( switch, controller, port1=0 )
847
            sintf, cintf = link.intf1, link.intf2
848
            switch.controlIntf = sintf
849
            snum += 1
850
            while snum & 0xff in [ 0, 255 ]:
851
                snum += 1
852
            sip = ipStr( snum )
853
            cintf.setIP( cip, prefixLen )
854
            sintf.setIP( sip, prefixLen )
855
            controller.setHostRoute( sip, cintf )
856
            switch.setHostRoute( cip, sintf )
857
        info( '\n' )
858
        info( '*** Testing control network\n' )
859
        while not cintf.isUp():
860
            info( '*** Waiting for', cintf, 'to come up\n' )
861
            sleep( 1 )
862
        for switch in self.switches:
863
            while not sintf.isUp():
864
                info( '*** Waiting for', sintf, 'to come up\n' )
865
                sleep( 1 )
866
            if self.ping( hosts=[ switch, controller ] ) != 0:
867
                error( '*** Error: control network test failed\n' )
868
                exit( 1 )
869
        info( '\n' )