pre final lap
[vsorcdistro/.git] / mininet / mininet / net.py
1 """
2
3     Mininet: A simple networking testbed for OpenFlow/SDN!
4
5 author: Bob Lantz (rlantz@cs.stanford.edu)
6 author: Brandon Heller (brandonh@stanford.edu)
7
8 Mininet creates scalable OpenFlow test networks by using
9 process-based virtualization and network namespaces.
10
11 Simulated hosts are created as processes in separate network
12 namespaces. This allows a complete OpenFlow network to be simulated on
13 top of a single Linux kernel.
14
15 Each host has:
16
17 A virtual console (pipes to a shell)
18 A virtual interfaces (half of a veth pair)
19 A parent shell (and possibly some child processes) in a namespace
20
21 Hosts have a network interface which is configured via ifconfig/ip
22 link/etc.
23
24 This version supports both the kernel and user space datapaths
25 from the OpenFlow reference implementation (openflowswitch.org)
26 as well as OpenVSwitch (openvswitch.org.)
27
28 In kernel datapath mode, the controller and switches are simply
29 processes in the root namespace.
30
31 Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32 attached to the one side of a veth pair; the other side resides in the
33 host namespace. In this mode, switch processes can simply connect to the
34 controller via the loopback interface.
35
36 In user datapath mode, the controller and switches can be full-service
37 nodes that live in their own network namespaces and have management
38 interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39 currently routed although it could be bridged.)
40
41 In addition to a management interface, user mode switches also have
42 several switch interfaces, halves of veth pairs whose other halves
43 reside in the host nodes that the switches are connected to.
44
45 Consistent, straightforward naming is important in order to easily
46 identify hosts, switches and controllers, both from the CLI and
47 from program code. Interfaces are named to make it easy to identify
48 which interfaces belong to which node.
49
50 The basic naming scheme is as follows:
51
52     Host nodes are named h1-hN
53     Switch nodes are named s1-sN
54     Controller nodes are named c0-cN
55     Interfaces are named {nodename}-eth0 .. {nodename}-ethN
56
57 Note: If the network topology is created using mininet.topo, then
58 node numbers are unique among hosts and switches (e.g. we have
59 h1..hN and SN..SN+M) and also correspond to their default IP addresses
60 of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61 hN. This mapping allows easy determination of a node's IP
62 address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63
64 Note also that 10.0.0.1 can often be written as 10.1 for short, e.g.
65 "ping 10.1" is equivalent to "ping 10.0.0.1".
66
67 Currently we wrap the entire network in a 'mininet' object, which
68 constructs a simulated network based on a network topology created
69 using a topology object (e.g. LinearTopo) from mininet.topo or
70 mininet.topolib, and a Controller which the switches will connect
71 to. Several configuration options are provided for functions such as
72 automatically setting MAC addresses, populating the ARP table, or
73 even running a set of terminals to allow direct interaction with nodes.
74
75 After the network is created, it can be started using start(), and a
76 variety of useful tasks maybe performed, including basic connectivity
77 and bandwidth tests and running the mininet CLI.
78
79 Once the network is up and running, test code can easily get access
80 to host and switch objects which can then be used for arbitrary
81 experiments, typically involving running a series of commands on the
82 hosts.
83
84 After all desired tests or activities have been completed, the stop()
85 method may be called to shut down the network.
86
87 """
88
89 import os
90 import re
91 import select
92 import signal
93 import random
94
95 from time import sleep
96 from itertools import chain, groupby
97 from math import ceil
98
99 from mininet.cli import CLI
100 from mininet.log import info, error, debug, output, warn
101 from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
102                            Controller )
103 from mininet.nodelib import NAT
104 from mininet.link import Link, Intf
105 from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
106                            macColonHex, ipStr, ipParse, netParse, ipAdd,
107                            waitListening, BaseString )
108 from mininet.term import cleanUpScreens, makeTerms
109
110 # Mininet version: should be consistent with README and LICENSE
111 VERSION = "2.3.0d5"
112
113 class Mininet( object ):
114     "Network emulation with hosts spawned in network namespaces."
115
116     def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
117                   controller=DefaultController, link=Link, intf=Intf,
118                   build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
119                   inNamespace=False,
120                   autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
121                   listenPort=None, waitConnected=False ):
122         """Create Mininet object.
123            topo: Topo (topology) object or None
124            switch: default Switch class
125            host: default Host class/constructor
126            controller: default Controller class/constructor
127            link: default Link class/constructor
128            intf: default Intf class/constructor
129            ipBase: base IP address for hosts,
130            build: build now from topo?
131            xterms: if build now, spawn xterms?
132            cleanup: if build now, cleanup before creating?
133            inNamespace: spawn switches and controller in net namespaces?
134            autoSetMacs: set MAC addrs automatically like IP addresses?
135            autoStaticArp: set all-pairs static MAC addrs?
136            autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
137            listenPort: base listening port to open; will be incremented for
138                each additional switch in the net if inNamespace=False"""
139         self.topo = topo
140         self.switch = switch
141         self.host = host
142         self.controller = controller
143         self.link = link
144         self.intf = intf
145         self.ipBase = ipBase
146         self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
147         hostIP = ( 0xffffffff >> self.prefixLen ) & self.ipBaseNum
148         # Start for address allocation
149         self.nextIP = hostIP if hostIP > 0 else 1
150         self.inNamespace = inNamespace
151         self.xterms = xterms
152         self.cleanup = cleanup
153         self.autoSetMacs = autoSetMacs
154         self.autoStaticArp = autoStaticArp
155         self.autoPinCpus = autoPinCpus
156         self.numCores = numCores()
157         self.nextCore = 0  # next core for pinning hosts to CPUs
158         self.listenPort = listenPort
159         self.waitConn = waitConnected
160
161         self.hosts = []
162         self.switches = []
163         self.controllers = []
164         self.links = []
165
166         self.nameToNode = {}  # name to Node (Host/Switch) objects
167
168         self.terms = []  # list of spawned xterm processes
169
170         Mininet.init()  # Initialize Mininet if necessary
171
172         self.built = False
173         if topo and build:
174             self.build()
175
176     def waitConnected( self, timeout=None, delay=.5 ):
177         """wait for each switch to connect to a controller,
178            up to 5 seconds
179            timeout: time to wait, or None to wait indefinitely
180            delay: seconds to sleep per iteration
181            returns: True if all switches are connected"""
182         info( '*** Waiting for switches to connect\n' )
183         time = 0
184         remaining = list( self.switches )
185         while True:
186             for switch in tuple( remaining ):
187                 if switch.connected():
188                     info( '%s ' % switch )
189                     remaining.remove( switch )
190             if not remaining:
191                 info( '\n' )
192                 return True
193             if timeout is not None and time > timeout:
194                 break
195             sleep( delay )
196             time += delay
197         warn( 'Timed out after %d seconds\n' % time )
198         for switch in remaining:
199             if not switch.connected():
200                 warn( 'Warning: %s is not connected to a controller\n'
201                       % switch.name )
202             else:
203                 remaining.remove( switch )
204         return not remaining
205
206     def addHost( self, name, cls=None, **params ):
207         """Add host.
208            name: name of host to add
209            cls: custom host class/constructor (optional)
210            params: parameters for host
211            returns: added host"""
212         # Default IP and MAC addresses
213         defaults = { 'ip': ipAdd( self.nextIP,
214                                   ipBaseNum=self.ipBaseNum,
215                                   prefixLen=self.prefixLen ) +
216                                   '/%s' % self.prefixLen }
217         if self.autoSetMacs:
218             defaults[ 'mac' ] = macColonHex( self.nextIP )
219         if self.autoPinCpus:
220             defaults[ 'cores' ] = self.nextCore
221             self.nextCore = ( self.nextCore + 1 ) % self.numCores
222         self.nextIP += 1
223         defaults.update( params )
224         if not cls:
225             cls = self.host
226         h = cls( name, **defaults )
227         self.hosts.append( h )
228         self.nameToNode[ name ] = h
229         return h
230
231     def delNode( self, node, nodes=None):
232         """Delete node
233            node: node to delete
234            nodes: optional list to delete from (e.g. self.hosts)"""
235         if nodes is None:
236             nodes = ( self.hosts if node in self.hosts else
237                       ( self.switches if node in self.switches else
238                         ( self.controllers if node in self.controllers else
239                           [] ) ) )
240         node.stop( deleteIntfs=True )
241         node.terminate()
242         nodes.remove( node )
243         del self.nameToNode[ node.name ]
244
245     def delHost( self, host ):
246         "Delete a host"
247         self.delNode( host, nodes=self.hosts )
248
249     def addSwitch( self, name, cls=None, **params ):
250         """Add switch.
251            name: name of switch to add
252            cls: custom switch class/constructor (optional)
253            returns: added switch
254            side effect: increments listenPort ivar ."""
255         defaults = { 'listenPort': self.listenPort,
256                      'inNamespace': self.inNamespace }
257         defaults.update( params )
258         if not cls:
259             cls = self.switch
260         sw = cls( name, **defaults )
261         if not self.inNamespace and self.listenPort:
262             self.listenPort += 1
263         self.switches.append( sw )
264         self.nameToNode[ name ] = sw
265         return sw
266
267     def delSwitch( self, switch ):
268         "Delete a switch"
269         self.delNode( switch, nodes=self.switches )
270
271     def addController( self, name='c0', controller=None, **params ):
272         """Add controller.
273            controller: Controller class"""
274         # Get controller class
275         if not controller:
276             controller = self.controller
277         # Construct new controller if one is not given
278         if isinstance( name, Controller ):
279             controller_new = name
280             # Pylint thinks controller is a str()
281             # pylint: disable=maybe-no-member
282             name = controller_new.name
283             # pylint: enable=maybe-no-member
284         else:
285             controller_new = controller( name, **params )
286         # Add new controller to net
287         if controller_new:  # allow controller-less setups
288             self.controllers.append( controller_new )
289             self.nameToNode[ name ] = controller_new
290         return controller_new
291
292     def delController( self, controller ):
293         """Delete a controller
294            Warning - does not reconfigure switches, so they
295            may still attempt to connect to it!"""
296         self.delNode( controller )
297
298     def addNAT( self, name='nat0', connect=True, inNamespace=False,
299                 **params):
300         """Add a NAT to the Mininet network
301            name: name of NAT node
302            connect: switch to connect to | True (s1) | None
303            inNamespace: create in a network namespace
304            params: other NAT node params, notably:
305                ip: used as default gateway address"""
306         nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
307                             subnet=self.ipBase, **params )
308         # find first switch and create link
309         if connect:
310             if not isinstance( connect, Node ):
311                 # Use first switch if not specified
312                 connect = self.switches[ 0 ]
313             # Connect the nat to the switch
314             self.addLink( nat, connect )
315             # Set the default route on hosts
316             natIP = nat.params[ 'ip' ].split('/')[ 0 ]
317             for host in self.hosts:
318                 if host.inNamespace:
319                     host.setDefaultRoute( 'via %s' % natIP )
320         return nat
321
322     # BL: We now have four ways to look up nodes
323     # This may (should?) be cleaned up in the future.
324     def getNodeByName( self, *args ):
325         "Return node(s) with given name(s)"
326         if len( args ) == 1:
327             return self.nameToNode[ args[ 0 ] ]
328         return [ self.nameToNode[ n ] for n in args ]
329
330     def get( self, *args ):
331         "Convenience alias for getNodeByName"
332         return self.getNodeByName( *args )
333
334     # Even more convenient syntax for node lookup and iteration
335     def __getitem__( self, key ):
336         "net[ name ] operator: Return node with given name"
337         return self.nameToNode[ key ]
338
339     def __delitem__( self, key ):
340         "del net[ name ] operator - delete node with given name"
341         self.delNode( self.nameToNode[ key ] )
342
343     def __iter__( self ):
344         "return iterator over node names"
345         for node in chain( self.hosts, self.switches, self.controllers ):
346             yield node.name
347
348     def __len__( self ):
349         "returns number of nodes in net"
350         return ( len( self.hosts ) + len( self.switches ) +
351                  len( self.controllers ) )
352
353     def __contains__( self, item ):
354         "returns True if net contains named node"
355         return item in self.nameToNode
356
357     def keys( self ):
358         "return a list of all node names or net's keys"
359         return list( self )
360
361     def values( self ):
362         "return a list of all nodes or net's values"
363         return [ self[name] for name in self ]
364
365     def items( self ):
366         "return (key,value) tuple list for every node in net"
367         return zip( self.keys(), self.values() )
368
369     @staticmethod
370     def randMac():
371         "Return a random, non-multicast MAC address"
372         return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
373                             0x020000000000 )
374
375     def addLink( self, node1, node2, port1=None, port2=None,
376                  cls=None, **params ):
377         """"Add a link from node1 to node2
378             node1: source node (or name)
379             node2: dest node (or name)
380             port1: source port (optional)
381             port2: dest port (optional)
382             cls: link class (optional)
383             params: additional link params (optional)
384             returns: link object"""
385         # Accept node objects or names
386         node1 = node1 if not isinstance( node1, BaseString ) else self[ node1 ]
387         node2 = node2 if not isinstance( node2, BaseString ) else self[ node2 ]
388         options = dict( params )
389         # Port is optional
390         if port1 is not None:
391             options.setdefault( 'port1', port1 )
392         if port2 is not None:
393             options.setdefault( 'port2', port2 )
394         if self.intf is not None:
395             options.setdefault( 'intf', self.intf )
396         # Set default MAC - this should probably be in Link
397         options.setdefault( 'addr1', self.randMac() )
398         options.setdefault( 'addr2', self.randMac() )
399         cls = self.link if cls is None else cls
400         link = cls( node1, node2, **options )
401         self.links.append( link )
402         return link
403
404     def delLink( self, link ):
405         "Remove a link from this network"
406         link.delete()
407         self.links.remove( link )
408
409     def linksBetween( self, node1, node2 ):
410         "Return Links between node1 and node2"
411         return [ link for link in self.links
412                  if ( node1, node2 ) in (
413                     ( link.intf1.node, link.intf2.node ),
414                     ( link.intf2.node, link.intf1.node ) ) ]
415
416     def delLinkBetween( self, node1, node2, index=0, allLinks=False ):
417         """Delete link(s) between node1 and node2
418            index: index of link to delete if multiple links (0)
419            allLinks: ignore index and delete all such links (False)
420            returns: deleted link(s)"""
421         links = self.linksBetween( node1, node2 )
422         if not allLinks:
423             links = [ links[ index ] ]
424         for link in links:
425             self.delLink( link )
426         return links
427
428     def configHosts( self ):
429         "Configure a set of hosts."
430         for host in self.hosts:
431             info( host.name + ' ' )
432             intf = host.defaultIntf()
433             if intf:
434                 host.configDefault()
435             else:
436                 # Don't configure nonexistent intf
437                 host.configDefault( ip=None, mac=None )
438             # You're low priority, dude!
439             # BL: do we want to do this here or not?
440             # May not make sense if we have CPU lmiting...
441             # quietRun( 'renice +18 -p ' + repr( host.pid ) )
442             # This may not be the right place to do this, but
443             # it needs to be done somewhere.
444         info( '\n' )
445
446     def buildFromTopo( self, topo=None ):
447         """Build mininet from a topology object
448            At the end of this function, everything should be connected
449            and up."""
450
451         # Possibly we should clean up here and/or validate
452         # the topo
453         if self.cleanup:
454             pass
455
456         info( '\n*** Creating network...\n' )
457
458         if not self.controllers and self.controller:
459             # Add a default controller
460             info( '*** Adding controller\n' )
461             classes = self.controller
462             if not isinstance( classes, list ):
463                 classes = [ classes ]
464             for i, cls in enumerate( classes ):
465                 # Allow Controller objects because nobody understands partial()
466                 if isinstance( cls, Controller ):
467                     self.addController( cls )
468                 else:
469                     self.addController( 'c%d' % i, cls )
470
471         info( '*** Adding hosts:\n' )
472         for hostName in topo.hosts():
473             self.addHost( hostName, **topo.nodeInfo( hostName ) )
474             info( hostName + ' ' )
475
476         info( '\n*** Adding switches:\n' )
477         for switchName in topo.switches():
478             # A bit ugly: add batch parameter if appropriate
479             params = topo.nodeInfo( switchName)
480             cls = params.get( 'cls', self.switch )
481             if hasattr( cls, 'batchStartup' ):
482                 params.setdefault( 'batch', True )
483             self.addSwitch( switchName, **params )
484             info( switchName + ' ' )
485
486         info( '\n*** Adding links:\n' )
487         for srcName, dstName, params in topo.links(
488                 sort=True, withInfo=True ):
489             self.addLink( **params )
490             info( '(%s, %s) ' % ( srcName, dstName ) )
491
492         info( '\n' )
493
494     def configureControlNetwork( self ):
495         "Control net config hook: override in subclass"
496         raise Exception( 'configureControlNetwork: '
497                          'should be overriden in subclass', self )
498
499     def build( self ):
500         "Build mininet."
501         if self.topo:
502             self.buildFromTopo( self.topo )
503         if self.inNamespace:
504             self.configureControlNetwork()
505         info( '*** Configuring hosts\n' )
506         self.configHosts()
507         if self.xterms:
508             self.startTerms()
509         if self.autoStaticArp:
510             self.staticArp()
511         self.built = True
512
513     def startTerms( self ):
514         "Start a terminal for each node."
515         if 'DISPLAY' not in os.environ:
516             error( "Error starting terms: Cannot connect to display\n" )
517             return
518         info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
519         cleanUpScreens()
520         self.terms += makeTerms( self.controllers, 'controller' )
521         self.terms += makeTerms( self.switches, 'switch' )
522         self.terms += makeTerms( self.hosts, 'host' )
523
524     def stopXterms( self ):
525         "Kill each xterm."
526         for term in self.terms:
527             os.kill( term.pid, signal.SIGKILL )
528         cleanUpScreens()
529
530     def staticArp( self ):
531         "Add all-pairs ARP entries to remove the need to handle broadcast."
532         for src in self.hosts:
533             for dst in self.hosts:
534                 if src != dst:
535                     src.setARP( ip=dst.IP(), mac=dst.MAC() )
536
537     def start( self ):
538         "Start controller and switches."
539         if not self.built:
540             self.build()
541         info( '*** Starting controller\n' )
542         for controller in self.controllers:
543             info( controller.name + ' ')
544             controller.start()
545         info( '\n' )
546         info( '*** Starting %s switches\n' % len( self.switches ) )
547         for switch in self.switches:
548             info( switch.name + ' ')
549             switch.start( self.controllers )
550         started = {}
551         for swclass, switches in groupby(
552                 sorted( self.switches,
553                         key=lambda s: str( type( s ) ) ), type ):
554             switches = tuple( switches )
555             if hasattr( swclass, 'batchStartup' ):
556                 success = swclass.batchStartup( switches )
557                 started.update( { s: s for s in success } )
558         info( '\n' )
559         if self.waitConn:
560             self.waitConnected()
561
562     def stop( self ):
563         "Stop the controller(s), switches and hosts"
564         info( '*** Stopping %i controllers\n' % len( self.controllers ) )
565         for controller in self.controllers:
566             info( controller.name + ' ' )
567             controller.stop()
568         info( '\n' )
569         if self.terms:
570             info( '*** Stopping %i terms\n' % len( self.terms ) )
571             self.stopXterms()
572         info( '*** Stopping %i links\n' % len( self.links ) )
573         for link in self.links:
574             info( '.' )
575             link.stop()
576         info( '\n' )
577         info( '*** Stopping %i switches\n' % len( self.switches ) )
578         stopped = {}
579         for swclass, switches in groupby(
580                 sorted( self.switches,
581                         key=lambda s: str( type( s ) ) ), type ):
582             switches = tuple( switches )
583             if hasattr( swclass, 'batchShutdown' ):
584                 success = swclass.batchShutdown( switches )
585                 stopped.update( { s: s for s in success } )
586         for switch in self.switches:
587             info( switch.name + ' ' )
588             if switch not in stopped:
589                 switch.stop()
590             switch.terminate()
591         info( '\n' )
592         info( '*** Stopping %i hosts\n' % len( self.hosts ) )
593         for host in self.hosts:
594             info( host.name + ' ' )
595             host.terminate()
596         info( '\n*** Done\n' )
597
598     def run( self, test, *args, **kwargs ):
599         "Perform a complete start/test/stop cycle."
600         self.start()
601         info( '*** Running test\n' )
602         result = test( *args, **kwargs )
603         self.stop()
604         return result
605
606     def monitor( self, hosts=None, timeoutms=-1 ):
607         """Monitor a set of hosts (or all hosts by default),
608            and return their output, a line at a time.
609            hosts: (optional) set of hosts to monitor
610            timeoutms: (optional) timeout value in ms
611            returns: iterator which returns host, line"""
612         if hosts is None:
613             hosts = self.hosts
614         poller = select.poll()
615         h1 = hosts[ 0 ]  # so we can call class method fdToNode
616         for host in hosts:
617             poller.register( host.stdout )
618         while True:
619             ready = poller.poll( timeoutms )
620             for fd, event in ready:
621                 host = h1.fdToNode( fd )
622                 if event & select.POLLIN:
623                     line = host.readline()
624                     if line is not None:
625                         yield host, line
626             # Return if non-blocking
627             if not ready and timeoutms >= 0:
628                 yield None, None
629
630     # XXX These test methods should be moved out of this class.
631     # Probably we should create a tests.py for them
632
633     @staticmethod
634     def _parsePing( pingOutput ):
635         "Parse ping output and return packets sent, received."
636         # Check for downed link
637         if 'connect: Network is unreachable' in pingOutput:
638             return 1, 0
639         r = r'(\d+) packets transmitted, (\d+)( packets)? received'
640         m = re.search( r, pingOutput )
641         if m is None:
642             error( '*** Error: could not parse ping output: %s\n' %
643                    pingOutput )
644             return 1, 0
645         sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
646         return sent, received
647
648     def ping( self, hosts=None, timeout=None ):
649         """Ping between all specified hosts.
650            hosts: list of hosts
651            timeout: time to wait for a response, as string
652            returns: ploss packet loss percentage"""
653         # should we check if running?
654         packets = 0
655         lost = 0
656         ploss = None
657         if not hosts:
658             hosts = self.hosts
659             output( '*** Ping: testing ping reachability\n' )
660         for node in hosts:
661             output( '%s -> ' % node.name )
662             for dest in hosts:
663                 if node != dest:
664                     opts = ''
665                     if timeout:
666                         opts = '-W %s' % timeout
667                     if dest.intfs:
668                         result = node.cmd( 'ping -c1 %s %s' %
669                                            (opts, dest.IP()) )
670                         sent, received = self._parsePing( result )
671                     else:
672                         sent, received = 0, 0
673                     packets += sent
674                     if received > sent:
675                         error( '*** Error: received too many packets' )
676                         error( '%s' % result )
677                         node.cmdPrint( 'route' )
678                         exit( 1 )
679                     lost += sent - received
680                     output( ( '%s ' % dest.name ) if received else 'X ' )
681             output( '\n' )
682         if packets > 0:
683             ploss = 100.0 * lost / packets
684             received = packets - lost
685             output( "*** Results: %i%% dropped (%d/%d received)\n" %
686                     ( ploss, received, packets ) )
687         else:
688             ploss = 0
689             output( "*** Warning: No packets sent\n" )
690         return ploss
691
692     @staticmethod
693     def _parsePingFull( pingOutput ):
694         "Parse ping output and return all data."
695         errorTuple = (1, 0, 0, 0, 0, 0)
696         # Check for downed link
697         r = r'[uU]nreachable'
698         m = re.search( r, pingOutput )
699         if m is not None:
700             return errorTuple
701         r = r'(\d+) packets transmitted, (\d+)( packets)? received'
702         m = re.search( r, pingOutput )
703         if m is None:
704             error( '*** Error: could not parse ping output: %s\n' %
705                    pingOutput )
706             return errorTuple
707         sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
708         r = r'rtt min/avg/max/mdev = '
709         r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
710         m = re.search( r, pingOutput )
711         if m is None:
712             if received == 0:
713                 return errorTuple
714             error( '*** Error: could not parse ping output: %s\n' %
715                    pingOutput )
716             return errorTuple
717         rttmin = float( m.group( 1 ) )
718         rttavg = float( m.group( 2 ) )
719         rttmax = float( m.group( 3 ) )
720         rttdev = float( m.group( 4 ) )
721         return sent, received, rttmin, rttavg, rttmax, rttdev
722
723     def pingFull( self, hosts=None, timeout=None ):
724         """Ping between all specified hosts and return all data.
725            hosts: list of hosts
726            timeout: time to wait for a response, as string
727            returns: all ping data; see function body."""
728         # should we check if running?
729         # Each value is a tuple: (src, dsd, [all ping outputs])
730         all_outputs = []
731         if not hosts:
732             hosts = self.hosts
733             output( '*** Ping: testing ping reachability\n' )
734         for node in hosts:
735             output( '%s -> ' % node.name )
736             for dest in hosts:
737                 if node != dest:
738                     opts = ''
739                     if timeout:
740                         opts = '-W %s' % timeout
741                     result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
742                     outputs = self._parsePingFull( result )
743                     sent, received, rttmin, rttavg, rttmax, rttdev = outputs
744                     all_outputs.append( (node, dest, outputs) )
745                     output( ( '%s ' % dest.name ) if received else 'X ' )
746             output( '\n' )
747         output( "*** Results: \n" )
748         for outputs in all_outputs:
749             src, dest, ping_outputs = outputs
750             sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
751             output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
752             output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
753                     (rttmin, rttavg, rttmax, rttdev) )
754         return all_outputs
755
756     def pingAll( self, timeout=None ):
757         """Ping between all hosts.
758            returns: ploss packet loss percentage"""
759         return self.ping( timeout=timeout )
760
761     def pingPair( self ):
762         """Ping between first two hosts, useful for testing.
763            returns: ploss packet loss percentage"""
764         hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
765         return self.ping( hosts=hosts )
766
767     def pingAllFull( self ):
768         """Ping between all hosts.
769            returns: ploss packet loss percentage"""
770         return self.pingFull()
771
772     def pingPairFull( self ):
773         """Ping between first two hosts, useful for testing.
774            returns: ploss packet loss percentage"""
775         hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
776         return self.pingFull( hosts=hosts )
777
778     @staticmethod
779     def _parseIperf( iperfOutput ):
780         """Parse iperf output and return bandwidth.
781            iperfOutput: string
782            returns: result string"""
783         r = r'([\d\.]+ \w+/sec)'
784         m = re.findall( r, iperfOutput )
785         if m:
786             return m[-1]
787         else:
788             # was: raise Exception(...)
789             error( 'could not parse iperf output: ' + iperfOutput )
790             return ''
791
792     # XXX This should be cleaned up
793
794     def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
795                seconds=5, port=5001):
796         """Run iperf between two hosts.
797            hosts: list of hosts; if None, uses first and last hosts
798            l4Type: string, one of [ TCP, UDP ]
799            udpBw: bandwidth target for UDP test
800            fmt: iperf format argument if any
801            seconds: iperf time to transmit
802            port: iperf port
803            returns: two-element array of [ server, client ] speeds
804            note: send() is buffered, so client rate can be much higher than
805            the actual transmission rate; on an unloaded system, server
806            rate should be much closer to the actual receive rate"""
807         hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
808         assert len( hosts ) == 2
809         client, server = hosts
810         output( '*** Iperf: testing', l4Type, 'bandwidth between',
811                 client, 'and', server, '\n' )
812         server.cmd( 'killall -9 iperf' )
813         iperfArgs = 'iperf -p %d ' % port
814         bwArgs = ''
815         if l4Type == 'UDP':
816             iperfArgs += '-u '
817             bwArgs = '-b ' + udpBw + ' '
818         elif l4Type != 'TCP':
819             raise Exception( 'Unexpected l4 type: %s' % l4Type )
820         if fmt:
821             iperfArgs += '-f %s ' % fmt
822         server.sendCmd( iperfArgs + '-s' )
823         if l4Type == 'TCP':
824             if not waitListening( client, server.IP(), port ):
825                 raise Exception( 'Could not connect to iperf on port %d'
826                                  % port )
827         cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
828                              server.IP() + ' ' + bwArgs )
829         debug( 'Client output: %s\n' % cliout )
830         servout = ''
831         # We want the last *b/sec from the iperf server output
832         # for TCP, there are two of them because of waitListening
833         count = 2 if l4Type == 'TCP' else 1
834         while len( re.findall( '/sec', servout ) ) < count:
835             servout += server.monitor( timeoutms=5000 )
836         server.sendInt()
837         servout += server.waitOutput()
838         debug( 'Server output: %s\n' % servout )
839         result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
840         if l4Type == 'UDP':
841             result.insert( 0, udpBw )
842         output( '*** Results: %s\n' % result )
843         return result
844
845     def runCpuLimitTest( self, cpu, duration=5 ):
846         """run CPU limit test with 'while true' processes.
847         cpu: desired CPU fraction of each host
848         duration: test duration in seconds (integer)
849         returns a single list of measured CPU fractions as floats.
850         """
851         pct = cpu * 100
852         info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
853         hosts = self.hosts
854         cores = int( quietRun( 'nproc' ) )
855         # number of processes to run a while loop on per host
856         num_procs = int( ceil( cores * cpu ) )
857         pids = {}
858         for h in hosts:
859             pids[ h ] = []
860             for _core in range( num_procs ):
861                 h.cmd( 'while true; do a=1; done &' )
862                 pids[ h ].append( h.cmd( 'echo $!' ).strip() )
863         outputs = {}
864         time = {}
865         # get the initial cpu time for each host
866         for host in hosts:
867             outputs[ host ] = []
868             with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
869                        host, 'r' ) as f:
870                 time[ host ] = float( f.read() )
871         for _ in range( duration ):
872             sleep( 1 )
873             for host in hosts:
874                 with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
875                            host, 'r' ) as f:
876                     readTime = float( f.read() )
877                 outputs[ host ].append( ( ( readTime - time[ host ] )
878                                         / 1000000000 ) / cores * 100 )
879                 time[ host ] = readTime
880         for h, pids in pids.items():
881             for pid in pids:
882                 h.cmd( 'kill -9 %s' % pid )
883         cpu_fractions = []
884         for _host, outputs in outputs.items():
885             for pct in outputs:
886                 cpu_fractions.append( pct )
887         output( '*** Results: %s\n' % cpu_fractions )
888         return cpu_fractions
889
890     # BL: I think this can be rewritten now that we have
891     # a real link class.
892     def configLinkStatus( self, src, dst, status ):
893         """Change status of src <-> dst links.
894            src: node name
895            dst: node name
896            status: string {up, down}"""
897         if src not in self.nameToNode:
898             error( 'src not in network: %s\n' % src )
899         elif dst not in self.nameToNode:
900             error( 'dst not in network: %s\n' % dst )
901         else:
902             src = self.nameToNode[ src ]
903             dst = self.nameToNode[ dst ]
904             connections = src.connectionsTo( dst )
905             if len( connections ) == 0:
906                 error( 'src and dst not connected: %s %s\n' % ( src, dst) )
907             for srcIntf, dstIntf in connections:
908                 result = srcIntf.ifconfig( status )
909                 if result:
910                     error( 'link src status change failed: %s\n' % result )
911                 result = dstIntf.ifconfig( status )
912                 if result:
913                     error( 'link dst status change failed: %s\n' % result )
914
915     def interact( self ):
916         "Start network and run our simple CLI."
917         self.start()
918         result = CLI( self )
919         self.stop()
920         return result
921
922     inited = False
923
924     @classmethod
925     def init( cls ):
926         "Initialize Mininet"
927         if cls.inited:
928             return
929         ensureRoot()
930         fixLimits()
931         cls.inited = True
932
933
934 class MininetWithControlNet( Mininet ):
935
936     """Control network support:
937
938        Create an explicit control network. Currently this is only
939        used/usable with the user datapath.
940
941        Notes:
942
943        1. If the controller and switches are in the same (e.g. root)
944           namespace, they can just use the loopback connection.
945
946        2. If we can get unix domain sockets to work, we can use them
947           instead of an explicit control network.
948
949        3. Instead of routing, we could bridge or use 'in-band' control.
950
951        4. Even if we dispense with this in general, it could still be
952           useful for people who wish to simulate a separate control
953           network (since real networks may need one!)
954
955        5. Basically nobody ever used this code, so it has been moved
956           into its own class.
957
958        6. Ultimately we may wish to extend this to allow us to create a
959           control network which every node's control interface is
960           attached to."""
961
962     def configureControlNetwork( self ):
963         "Configure control network."
964         self.configureRoutedControlNetwork()
965
966     # We still need to figure out the right way to pass
967     # in the control network location.
968
969     def configureRoutedControlNetwork( self, ip='192.168.123.1',
970                                        prefixLen=16 ):
971         """Configure a routed control network on controller and switches.
972            For use with the user datapath only right now."""
973         controller = self.controllers[ 0 ]
974         info( controller.name + ' <->' )
975         cip = ip
976         snum = ipParse( ip )
977         for switch in self.switches:
978             info( ' ' + switch.name )
979             link = self.link( switch, controller, port1=0 )
980             sintf, cintf = link.intf1, link.intf2
981             switch.controlIntf = sintf
982             snum += 1
983             while snum & 0xff in [ 0, 255 ]:
984                 snum += 1
985             sip = ipStr( snum )
986             cintf.setIP( cip, prefixLen )
987             sintf.setIP( sip, prefixLen )
988             controller.setHostRoute( sip, cintf )
989             switch.setHostRoute( cip, sintf )
990         info( '\n' )
991         info( '*** Testing control network\n' )
992         while not cintf.isUp():
993             info( '*** Waiting for', cintf, 'to come up\n' )
994             sleep( 1 )
995         for switch in self.switches:
996             while not sintf.isUp():
997                 info( '*** Waiting for', sintf, 'to come up\n' )
998                 sleep( 1 )
999             if self.ping( hosts=[ switch, controller ] ) != 0:
1000                 error( '*** Error: control network test failed\n' )
1001                 exit( 1 )
1002         info( '\n' )