Statistics
| Branch: | Tag: | Revision:

mininet / mininet / net.py @ 824afb84

History | View | Annotate | Download (30.3 KB)

1
"""
2

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

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

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

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

15
Each host has:
16

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

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

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

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

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

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

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

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

50
The basic naming scheme is as follows:
51

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

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

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

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

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

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

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

87
"""
88

    
89
import os
90
import re
91
import select
92
import signal
93
from time import sleep
94
from itertools import chain
95

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

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

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

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

    
152
        self.hosts = []
153
        self.switches = []
154
        self.controllers = []
155

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

    
158
        self.terms = []  # list of spawned xterm processes
159

    
160
        Mininet.init()  # Initialize Mininet if necessary
161

    
162
        self.built = False
163
        if topo and build:
164
            self.build()
165

    
166
    def addHost( self, name, cls=None, **params ):
167
        """Add host.
168
           name: name of host to add
169
           cls: custom host class/constructor (optional)
170
           params: parameters for host
171
           returns: added host"""
172
        # Default IP and MAC addresses
173
        defaults = { 'ip': ipAdd( self.nextIP,
174
                                  ipBaseNum=self.ipBaseNum,
175
                                  prefixLen=self.prefixLen ) +
176
                                  '/%s' % self.prefixLen }
177
        if self.autoSetMacs:
178
            defaults[ 'mac'] = macColonHex( self.nextIP )
179
        if self.autoPinCpus:
180
            defaults[ 'cores' ] = self.nextCore
181
            self.nextCore = ( self.nextCore + 1 ) % self.numCores
182
        self.nextIP += 1
183
        defaults.update( params )
184
        if not cls:
185
            cls = self.host
186
        h = cls( name, **defaults )
187
        self.hosts.append( h )
188
        self.nameToNode[ name ] = h
189
        return h
190

    
191
    def addSwitch( self, name, cls=None, **params ):
192
        """Add switch.
193
           name: name of switch to add
194
           cls: custom switch class/constructor (optional)
195
           returns: added switch
196
           side effect: increments listenPort ivar ."""
197
        defaults = { 'listenPort': self.listenPort,
198
                     'inNamespace': self.inNamespace }
199
        defaults.update( params )
200
        if not cls:
201
            cls = self.switch
202
        sw = cls( name, **defaults )
203
        if not self.inNamespace and self.listenPort:
204
            self.listenPort += 1
205
        self.switches.append( sw )
206
        self.nameToNode[ name ] = sw
207
        return sw
208

    
209
    def addController( self, name='c0', controller=None, **params ):
210
        """Add controller.
211
           controller: Controller class"""
212
        # Get controller class
213
        if not controller:
214
            controller = self.controller
215
        # Construct new controller if one is not given
216
        if isinstance(name, Controller):
217
            controller_new = name
218
            # Pylint thinks controller is a str()
219
            # pylint: disable=E1103
220
            name = controller_new.name
221
            # pylint: enable=E1103
222
        else:
223
            controller_new = controller( name, **params )
224
        # Add new controller to net
225
        if controller_new:  # allow controller-less setups
226
            self.controllers.append( controller_new )
227
            self.nameToNode[ name ] = controller_new
228
        return controller_new
229

    
230
    # BL: We now have four ways to look up nodes
231
    # This may (should?) be cleaned up in the future.
232
    def getNodeByName( self, *args ):
233
        "Return node(s) with given name(s)"
234
        if len( args ) == 1:
235
            return self.nameToNode[ args[ 0 ] ]
236
        return [ self.nameToNode[ n ] for n in args ]
237

    
238
    def get( self, *args ):
239
        "Convenience alias for getNodeByName"
240
        return self.getNodeByName( *args )
241

    
242
    # Even more convenient syntax for node lookup and iteration
243
    def __getitem__( self, key ):
244
        """net [ name ] operator: Return node(s) with given name(s)"""
245
        return self.nameToNode[ key ]
246

    
247
    def __iter__( self ):
248
        "return iterator over node names"
249
        for node in chain( self.hosts, self.switches, self.controllers ):
250
            yield node.name
251

    
252
    def __len__( self ):
253
        "returns number of nodes in net"
254
        return ( len( self.hosts ) + len( self.switches ) +
255
                 len( self.controllers ) )
256

    
257
    def __contains__( self, item ):
258
        "returns True if net contains named node"
259
        return item in self.nameToNode
260

    
261
    def keys( self ):
262
        "return a list of all node names or net's keys"
263
        return list( self )
264

    
265
    def values( self ):
266
        "return a list of all nodes or net's values"
267
        return [ self[name] for name in self ]
268

    
269
    def items( self ):
270
        "return (key,value) tuple list for every node in net"
271
        return zip( self.keys(), self.values() )
272

    
273
    def addLink( self, node1, node2, port1=None, port2=None,
274
                 cls=None, **params ):
275
        """"Add a link from node1 to node2
276
            node1: source node
277
            node2: dest node
278
            port1: source port
279
            port2: dest port
280
            returns: link object"""
281
        defaults = { 'port1': port1,
282
                     'port2': port2,
283
                     'intf': self.intf }
284
        defaults.update( params )
285
        if not cls:
286
            cls = self.link
287
        return cls( node1, node2, **defaults )
288

    
289
    def configHosts( self ):
290
        "Configure a set of hosts."
291
        for host in self.hosts:
292
            info( host.name + ' ' )
293
            intf = host.defaultIntf()
294
            if intf:
295
                host.configDefault()
296
            else:
297
                # Don't configure nonexistent intf
298
                host.configDefault( ip=None, mac=None )
299
            # You're low priority, dude!
300
            # BL: do we want to do this here or not?
301
            # May not make sense if we have CPU lmiting...
302
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
303
            # This may not be the right place to do this, but
304
            # it needs to be done somewhere.
305
            host.cmd( 'ifconfig lo up' )
306
        info( '\n' )
307

    
308
    def buildFromTopo( self, topo=None ):
309
        """Build mininet from a topology object
310
           At the end of this function, everything should be connected
311
           and up."""
312

    
313
        # Possibly we should clean up here and/or validate
314
        # the topo
315
        if self.cleanup:
316
            pass
317

    
318
        info( '*** Creating network\n' )
319

    
320
        if not self.controllers and self.controller:
321
            # Add a default controller
322
            info( '*** Adding controller\n' )
323
            classes = self.controller
324
            if type( classes ) is not list:
325
                classes = [ classes ]
326
            for i, cls in enumerate( classes ):
327
                self.addController( 'c%d' % i, cls )
328

    
329
        info( '*** Adding hosts:\n' )
330
        for hostName in topo.hosts():
331
            self.addHost( hostName, **topo.nodeInfo( hostName ) )
332
            info( hostName + ' ' )
333

    
334
        info( '\n*** Adding switches:\n' )
335
        for switchName in topo.switches():
336
            self.addSwitch( switchName, **topo.nodeInfo( switchName) )
337
            info( switchName + ' ' )
338

    
339
        info( '\n*** Adding links:\n' )
340
        for srcName, dstName in topo.links(sort=True):
341
            src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
342
            params = topo.linkInfo( srcName, dstName )
343
            srcPort, dstPort = topo.port( srcName, dstName )
344
            self.addLink( src, dst, srcPort, dstPort, **params )
345
            info( '(%s, %s) ' % ( src.name, dst.name ) )
346

    
347
        info( '\n' )
348

    
349
    def configureControlNetwork( self ):
350
        "Control net config hook: override in subclass"
351
        raise Exception( 'configureControlNetwork: '
352
                         'should be overriden in subclass', self )
353

    
354
    def build( self ):
355
        "Build mininet."
356
        if self.topo:
357
            self.buildFromTopo( self.topo )
358
        if self.inNamespace:
359
            self.configureControlNetwork()
360
        info( '*** Configuring hosts\n' )
361
        self.configHosts()
362
        if self.xterms:
363
            self.startTerms()
364
        if self.autoStaticArp:
365
            self.staticArp()
366
        self.built = True
367

    
368
    def startTerms( self ):
369
        "Start a terminal for each node."
370
        if 'DISPLAY' not in os.environ:
371
            error( "Error starting terms: Cannot connect to display\n" )
372
            return
373
        info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
374
        cleanUpScreens()
375
        self.terms += makeTerms( self.controllers, 'controller' )
376
        self.terms += makeTerms( self.switches, 'switch' )
377
        self.terms += makeTerms( self.hosts, 'host' )
378

    
379
    def stopXterms( self ):
380
        "Kill each xterm."
381
        for term in self.terms:
382
            os.kill( term.pid, signal.SIGKILL )
383
        cleanUpScreens()
384

    
385
    def staticArp( self ):
386
        "Add all-pairs ARP entries to remove the need to handle broadcast."
387
        for src in self.hosts:
388
            for dst in self.hosts:
389
                if src != dst:
390
                    src.setARP( ip=dst.IP(), mac=dst.MAC() )
391

    
392
    def start( self ):
393
        "Start controller and switches."
394
        if not self.built:
395
            self.build()
396
        info( '*** Starting controller\n' )
397
        for controller in self.controllers:
398
            controller.start()
399
        info( '*** Starting %s switches\n' % len( self.switches ) )
400
        for switch in self.switches:
401
            info( switch.name + ' ')
402
            switch.start( self.controllers )
403
        info( '\n' )
404

    
405
    def stop( self ):
406
        "Stop the controller(s), switches and hosts"
407
        if self.terms:
408
            info( '*** Stopping %i terms\n' % len( self.terms ) )
409
            self.stopXterms()
410
        info( '*** Stopping %i switches\n' % len( self.switches ) )
411
        for switch in self.switches:
412
            info( switch.name + ' ' )
413
            switch.stop()
414
        info( '\n' )
415
        info( '*** Stopping %i hosts\n' % len( self.hosts ) )
416
        for host in self.hosts:
417
            info( host.name + ' ' )
418
            host.terminate()
419
        info( '\n' )
420
        info( '*** Stopping %i controllers\n' % len( self.controllers ) )
421
        for controller in self.controllers:
422
            info( controller.name + ' ' )
423
            controller.stop()
424
        info( '\n*** Done\n' )
425

    
426
    def run( self, test, *args, **kwargs ):
427
        "Perform a complete start/test/stop cycle."
428
        self.start()
429
        info( '*** Running test\n' )
430
        result = test( *args, **kwargs )
431
        self.stop()
432
        return result
433

    
434
    def monitor( self, hosts=None, timeoutms=-1 ):
435
        """Monitor a set of hosts (or all hosts by default),
436
           and return their output, a line at a time.
437
           hosts: (optional) set of hosts to monitor
438
           timeoutms: (optional) timeout value in ms
439
           returns: iterator which returns host, line"""
440
        if hosts is None:
441
            hosts = self.hosts
442
        poller = select.poll()
443
        Node = hosts[ 0 ]  # so we can call class method fdToNode
444
        for host in hosts:
445
            poller.register( host.stdout )
446
        while True:
447
            ready = poller.poll( timeoutms )
448
            for fd, event in ready:
449
                host = Node.fdToNode( fd )
450
                if event & select.POLLIN:
451
                    line = host.readline()
452
                    if line is not None:
453
                        yield host, line
454
            # Return if non-blocking
455
            if not ready and timeoutms >= 0:
456
                yield None, None
457

    
458
    # XXX These test methods should be moved out of this class.
459
    # Probably we should create a tests.py for them
460

    
461
    @staticmethod
462
    def _parsePing( pingOutput ):
463
        "Parse ping output and return packets sent, received."
464
        # Check for downed link
465
        if 'connect: Network is unreachable' in pingOutput:
466
            return 1, 0
467
        r = r'(\d+) packets transmitted, (\d+) received'
468
        m = re.search( r, pingOutput )
469
        if m is None:
470
            error( '*** Error: could not parse ping output: %s\n' %
471
                   pingOutput )
472
            return 1, 0
473
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
474
        return sent, received
475

    
476
    def ping( self, hosts=None, timeout=None ):
477
        """Ping between all specified hosts.
478
           hosts: list of hosts
479
           timeout: time to wait for a response, as string
480
           returns: ploss packet loss percentage"""
481
        # should we check if running?
482
        packets = 0
483
        lost = 0
484
        ploss = None
485
        if not hosts:
486
            hosts = self.hosts
487
            output( '*** Ping: testing ping reachability\n' )
488
        for node in hosts:
489
            output( '%s -> ' % node.name )
490
            for dest in hosts:
491
                if node != dest:
492
                    opts = ''
493
                    if timeout:
494
                        opts = '-W %s' % timeout
495
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
496
                    sent, received = self._parsePing( result )
497
                    packets += sent
498
                    if received > sent:
499
                        error( '*** Error: received too many packets' )
500
                        error( '%s' % result )
501
                        node.cmdPrint( 'route' )
502
                        exit( 1 )
503
                    lost += sent - received
504
                    output( ( '%s ' % dest.name ) if received else 'X ' )
505
            output( '\n' )
506
        if packets > 0:
507
            ploss = 100 * lost / packets
508
            received = packets - lost
509
            output( "*** Results: %i%% dropped (%d/%d received)\n" %
510
                    ( ploss, received, packets ) )
511
        else:
512
            ploss = 0
513
            output( "*** Warning: No packets sent\n" )
514
        return ploss
515

    
516
    @staticmethod
517
    def _parsePingFull( pingOutput ):
518
        "Parse ping output and return all data."
519
        errorTuple = (1, 0, 0, 0, 0, 0)
520
        # Check for downed link
521
        r = r'[uU]nreachable'
522
        m = re.search( r, pingOutput )
523
        if m is not None:
524
            return errorTuple
525
        r = r'(\d+) packets transmitted, (\d+) received'
526
        m = re.search( r, pingOutput )
527
        if m is None:
528
            error( '*** Error: could not parse ping output: %s\n' %
529
                   pingOutput )
530
            return errorTuple
531
        sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
532
        r = r'rtt min/avg/max/mdev = '
533
        r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
534
        m = re.search( r, pingOutput )
535
        if m is None:
536
            error( '*** Error: could not parse ping output: %s\n' %
537
                   pingOutput )
538
            return errorTuple
539
        rttmin = float( m.group( 1 ) )
540
        rttavg = float( m.group( 2 ) )
541
        rttmax = float( m.group( 3 ) )
542
        rttdev = float( m.group( 4 ) )
543
        return sent, received, rttmin, rttavg, rttmax, rttdev
544

    
545
    def pingFull( self, hosts=None, timeout=None ):
546
        """Ping between all specified hosts and return all data.
547
           hosts: list of hosts
548
           timeout: time to wait for a response, as string
549
           returns: all ping data; see function body."""
550
        # should we check if running?
551
        # Each value is a tuple: (src, dsd, [all ping outputs])
552
        all_outputs = []
553
        if not hosts:
554
            hosts = self.hosts
555
            output( '*** Ping: testing ping reachability\n' )
556
        for node in hosts:
557
            output( '%s -> ' % node.name )
558
            for dest in hosts:
559
                if node != dest:
560
                    opts = ''
561
                    if timeout:
562
                        opts = '-W %s' % timeout
563
                    result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
564
                    outputs = self._parsePingFull( result )
565
                    sent, received, rttmin, rttavg, rttmax, rttdev = outputs
566
                    all_outputs.append( (node, dest, outputs) )
567
                    output( ( '%s ' % dest.name ) if received else 'X ' )
568
            output( '\n' )
569
        output( "*** Results: \n" )
570
        for outputs in all_outputs:
571
            src, dest, ping_outputs = outputs
572
            sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
573
            output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
574
            output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
575
                    (rttmin, rttavg, rttmax, rttdev) )
576
        return all_outputs
577

    
578
    def pingAll( self ):
579
        """Ping between all hosts.
580
           returns: ploss packet loss percentage"""
581
        return self.ping()
582

    
583
    def pingPair( self ):
584
        """Ping between first two hosts, useful for testing.
585
           returns: ploss packet loss percentage"""
586
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
587
        return self.ping( hosts=hosts )
588

    
589
    def pingAllFull( self ):
590
        """Ping between all hosts.
591
           returns: ploss packet loss percentage"""
592
        return self.pingFull()
593

    
594
    def pingPairFull( self ):
595
        """Ping between first two hosts, useful for testing.
596
           returns: ploss packet loss percentage"""
597
        hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
598
        return self.pingFull( hosts=hosts )
599

    
600
    @staticmethod
601
    def _parseIperf( iperfOutput ):
602
        """Parse iperf output and return bandwidth.
603
           iperfOutput: string
604
           returns: result string"""
605
        r = r'([\d\.]+ \w+/sec)'
606
        m = re.findall( r, iperfOutput )
607
        if m:
608
            return m[-1]
609
        else:
610
            # was: raise Exception(...)
611
            error( 'could not parse iperf output: ' + iperfOutput )
612
            return ''
613

    
614
    # XXX This should be cleaned up
615

    
616
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
617
        """Run iperf between two hosts.
618
           hosts: list of hosts; if None, uses opposite hosts
619
           l4Type: string, one of [ TCP, UDP ]
620
           returns: results two-element array of server and client speeds"""
621
        if not quietRun( 'which telnet' ):
622
            error( 'Cannot find telnet in $PATH - required for iperf test' )
623
            return
624
        if not hosts:
625
            hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
626
        else:
627
            assert len( hosts ) == 2
628
        client, server = hosts
629
        output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
630
        output( "%s and %s\n" % ( client.name, server.name ) )
631
        server.cmd( 'killall -9 iperf' )
632
        iperfArgs = 'iperf '
633
        bwArgs = ''
634
        if l4Type == 'UDP':
635
            iperfArgs += '-u '
636
            bwArgs = '-b ' + udpBw + ' '
637
        elif l4Type != 'TCP':
638
            raise Exception( 'Unexpected l4 type: %s' % l4Type )
639
        server.sendCmd( iperfArgs + '-s', printPid=True )
640
        servout = ''
641
        while server.lastPid is None:
642
            servout += server.monitor()
643
        if l4Type == 'TCP':
644
            while 'Connected' not in client.cmd(
645
                    'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
646
                output('waiting for iperf to start up...')
647
                sleep(.5)
648
        cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
649
                             bwArgs )
650
        debug( 'Client output: %s\n' % cliout )
651
        server.sendInt()
652
        servout += server.waitOutput()
653
        debug( 'Server output: %s\n' % servout )
654
        result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
655
        if l4Type == 'UDP':
656
            result.insert( 0, udpBw )
657
        output( '*** Results: %s\n' % result )
658
        return result
659

    
660
    def runCpuLimitTest( self, cpu, duration=5 ):
661
        """run CPU limit test with 'while true' processes.
662
        cpu: desired CPU fraction of each host
663
        duration: test duration in seconds
664
        returns a single list of measured CPU fractions as floats.
665
        """
666
        pct = cpu * 100
667
        info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
668
        hosts = self.hosts
669
        for h in hosts:
670
            h.cmd( 'while true; do a=1; done &' )
671
        pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
672
        pids_str = ",".join(["%s" % pid for pid in pids])
673
        cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
674
        # It's a shame that this is what pylint prefers
675
        outputs = []
676
        for _ in range( duration ):
677
            sleep( 1 )
678
            outputs.append( quietRun( cmd ).strip() )
679
        for h in hosts:
680
            h.cmd( 'kill %1' )
681
        cpu_fractions = []
682
        for test_output in outputs:
683
            # Split by line.  Ignore first line, which looks like this:
684
            # PID %CPU COMMAND\n
685
            for line in test_output.split('\n')[1:]:
686
                r = r'\d+\s*(\d+\.\d+)'
687
                m = re.search( r, line )
688
                if m is None:
689
                    error( '*** Error: could not extract CPU fraction: %s\n' %
690
                           line )
691
                    return None
692
                cpu_fractions.append( float( m.group( 1 ) ) )
693
        output( '*** Results: %s\n' % cpu_fractions )
694
        return cpu_fractions
695

    
696
    # BL: I think this can be rewritten now that we have
697
    # a real link class.
698
    def configLinkStatus( self, src, dst, status ):
699
        """Change status of src <-> dst links.
700
           src: node name
701
           dst: node name
702
           status: string {up, down}"""
703
        if src not in self.nameToNode:
704
            error( 'src not in network: %s\n' % src )
705
        elif dst not in self.nameToNode:
706
            error( 'dst not in network: %s\n' % dst )
707
        else:
708
            if type( src ) is str:
709
                src = self.nameToNode[ src ]
710
            if type( dst ) is str:
711
                dst = self.nameToNode[ dst ]
712
            connections = src.connectionsTo( dst )
713
            if len( connections ) == 0:
714
                error( 'src and dst not connected: %s %s\n' % ( src, dst) )
715
            for srcIntf, dstIntf in connections:
716
                result = srcIntf.ifconfig( status )
717
                if result:
718
                    error( 'link src status change failed: %s\n' % result )
719
                result = dstIntf.ifconfig( status )
720
                if result:
721
                    error( 'link dst status change failed: %s\n' % result )
722

    
723
    def interact( self ):
724
        "Start network and run our simple CLI."
725
        self.start()
726
        result = CLI( self )
727
        self.stop()
728
        return result
729

    
730
    inited = False
731

    
732
    @classmethod
733
    def init( cls ):
734
        "Initialize Mininet"
735
        if cls.inited:
736
            return
737
        ensureRoot()
738
        fixLimits()
739
        cls.inited = True
740

    
741

    
742
class MininetWithControlNet( Mininet ):
743

    
744
    """Control network support:
745

746
       Create an explicit control network. Currently this is only
747
       used/usable with the user datapath.
748

749
       Notes:
750

751
       1. If the controller and switches are in the same (e.g. root)
752
          namespace, they can just use the loopback connection.
753

754
       2. If we can get unix domain sockets to work, we can use them
755
          instead of an explicit control network.
756

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

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

763
       5. Basically nobody ever used this code, so it has been moved
764
          into its own class.
765

766
       6. Ultimately we may wish to extend this to allow us to create a
767
          control network which every node's control interface is
768
          attached to."""
769

    
770
    def configureControlNetwork( self ):
771
        "Configure control network."
772
        self.configureRoutedControlNetwork()
773

    
774
    # We still need to figure out the right way to pass
775
    # in the control network location.
776

    
777
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
778
                                       prefixLen=16 ):
779
        """Configure a routed control network on controller and switches.
780
           For use with the user datapath only right now."""
781
        controller = self.controllers[ 0 ]
782
        info( controller.name + ' <->' )
783
        cip = ip
784
        snum = ipParse( ip )
785
        for switch in self.switches:
786
            info( ' ' + switch.name )
787
            link = self.link( switch, controller, port1=0 )
788
            sintf, cintf = link.intf1, link.intf2
789
            switch.controlIntf = sintf
790
            snum += 1
791
            while snum & 0xff in [ 0, 255 ]:
792
                snum += 1
793
            sip = ipStr( snum )
794
            cintf.setIP( cip, prefixLen )
795
            sintf.setIP( sip, prefixLen )
796
            controller.setHostRoute( sip, cintf )
797
            switch.setHostRoute( cip, sintf )
798
        info( '\n' )
799
        info( '*** Testing control network\n' )
800
        while not cintf.isUp():
801
            info( '*** Waiting for', cintf, 'to come up\n' )
802
            sleep( 1 )
803
        for switch in self.switches:
804
            while not sintf.isUp():
805
                info( '*** Waiting for', sintf, 'to come up\n' )
806
                sleep( 1 )
807
            if self.ping( hosts=[ switch, controller ] ) != 0:
808
                error( '*** Error: control network test failed\n' )
809
                exit( 1 )
810
        info( '\n' )