3 Mininet: A simple networking testbed for OpenFlow/SDN!
5 author: Bob Lantz (rlantz@cs.stanford.edu)
6 author: Brandon Heller (brandonh@stanford.edu)
8 Mininet creates scalable OpenFlow test networks by using
9 process-based virtualization and network namespaces.
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.
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
21 Hosts have a network interface which is configured via ifconfig/ip
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.)
28 In kernel datapath mode, the controller and switches are simply
29 processes in the root namespace.
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.
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.)
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.
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.
50 The basic naming scheme is as follows:
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
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.
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".
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.
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.
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
84 After all desired tests or activities have been completed, the stop()
85 method may be called to shut down the network.
95 from time import sleep
96 from itertools import chain, groupby
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,
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
110 # Mininet version: should be consistent with README and LICENSE
113 class Mininet( object ):
114 "Network emulation with hosts spawned in network namespaces."
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',
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"""
142 self.controller = controller
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
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
163 self.controllers = []
166 self.nameToNode = {} # name to Node (Host/Switch) objects
168 self.terms = [] # list of spawned xterm processes
170 Mininet.init() # Initialize Mininet if necessary
176 def waitConnected( self, timeout=None, delay=.5 ):
177 """wait for each switch to connect to a controller,
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' )
184 remaining = list( self.switches )
186 for switch in tuple( remaining ):
187 if switch.connected():
188 info( '%s ' % switch )
189 remaining.remove( switch )
193 if timeout is not None and time > timeout:
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'
203 remaining.remove( switch )
206 def addHost( self, name, cls=None, **params ):
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 }
218 defaults[ 'mac' ] = macColonHex( self.nextIP )
220 defaults[ 'cores' ] = self.nextCore
221 self.nextCore = ( self.nextCore + 1 ) % self.numCores
223 defaults.update( params )
226 h = cls( name, **defaults )
227 self.hosts.append( h )
228 self.nameToNode[ name ] = h
231 def delNode( self, node, nodes=None):
234 nodes: optional list to delete from (e.g. self.hosts)"""
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
240 node.stop( deleteIntfs=True )
243 del self.nameToNode[ node.name ]
245 def delHost( self, host ):
247 self.delNode( host, nodes=self.hosts )
249 def addSwitch( self, name, cls=None, **params ):
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 )
260 sw = cls( name, **defaults )
261 if not self.inNamespace and self.listenPort:
263 self.switches.append( sw )
264 self.nameToNode[ name ] = sw
267 def delSwitch( self, switch ):
269 self.delNode( switch, nodes=self.switches )
271 def addController( self, name='c0', controller=None, **params ):
273 controller: Controller class"""
274 # Get controller class
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
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
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 )
298 def addNAT( self, name='nat0', connect=True, inNamespace=False,
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
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:
319 host.setDefaultRoute( 'via %s' % natIP )
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)"
327 return self.nameToNode[ args[ 0 ] ]
328 return [ self.nameToNode[ n ] for n in args ]
330 def get( self, *args ):
331 "Convenience alias for getNodeByName"
332 return self.getNodeByName( *args )
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 ]
339 def __delitem__( self, key ):
340 "del net[ name ] operator - delete node with given name"
341 self.delNode( self.nameToNode[ key ] )
343 def __iter__( self ):
344 "return iterator over node names"
345 for node in chain( self.hosts, self.switches, self.controllers ):
349 "returns number of nodes in net"
350 return ( len( self.hosts ) + len( self.switches ) +
351 len( self.controllers ) )
353 def __contains__( self, item ):
354 "returns True if net contains named node"
355 return item in self.nameToNode
358 "return a list of all node names or net's keys"
362 "return a list of all nodes or net's values"
363 return [ self[name] for name in self ]
366 "return (key,value) tuple list for every node in net"
367 return zip( self.keys(), self.values() )
371 "Return a random, non-multicast MAC address"
372 return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
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 )
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 )
404 def delLink( self, link ):
405 "Remove a link from this network"
407 self.links.remove( link )
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 ) ) ]
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 )
423 links = [ links[ index ] ]
428 def configHosts( self ):
429 "Configure a set of hosts."
430 for host in self.hosts:
431 info( host.name + ' ' )
432 intf = host.defaultIntf()
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.
446 def buildFromTopo( self, topo=None ):
447 """Build mininet from a topology object
448 At the end of this function, everything should be connected
451 # Possibly we should clean up here and/or validate
456 info( '*** Creating network...(toy en mininet.net buildfromtopo)\n' )
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 )
469 self.addController( 'c%d' % i, cls )
471 info( '*** Adding hosts:\n' )
472 for hostName in topo.hosts():
473 self.addHost( hostName, **topo.nodeInfo( hostName ) )
474 info( hostName + ' ' )
475 info('dentro de adding host\n')
476 info('sali de adding host')
478 info( '\n*** Adding switches:\n' )
479 for switchName in topo.switches():
480 # A bit ugly: add batch parameter if appropriate
481 params = topo.nodeInfo( switchName)
482 cls = params.get( 'cls', self.switch )
483 if hasattr( cls, 'batchStartup' ):
484 params.setdefault( 'batch', True )
485 self.addSwitch( switchName, **params )
486 info( switchName + ' ' )
488 info( '\n*** Adding links:\n' )
489 for srcName, dstName, params in topo.links(
490 sort=True, withInfo=True ):
491 self.addLink( **params )
492 info( '(%s, %s) ' % ( srcName, dstName ) )
496 def configureControlNetwork( self ):
497 "Control net config hook: override in subclass"
498 raise Exception( 'configureControlNetwork: '
499 'should be overriden in subclass', self )
504 self.buildFromTopo( self.topo )
506 self.configureControlNetwork()
507 info( '*** Configuring hosts\n' )
511 if self.autoStaticArp:
515 def startTerms( self ):
516 "Start a terminal for each node."
517 if 'DISPLAY' not in os.environ:
518 error( "Error starting terms: Cannot connect to display\n" )
520 info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
522 self.terms += makeTerms( self.controllers, 'controller' )
523 self.terms += makeTerms( self.switches, 'switch' )
524 self.terms += makeTerms( self.hosts, 'host' )
526 def stopXterms( self ):
528 for term in self.terms:
529 os.kill( term.pid, signal.SIGKILL )
532 def staticArp( self ):
533 "Add all-pairs ARP entries to remove the need to handle broadcast."
534 for src in self.hosts:
535 for dst in self.hosts:
537 src.setARP( ip=dst.IP(), mac=dst.MAC() )
540 "Start controller and switches."
543 info( '*** Starting controller\n' )
544 for controller in self.controllers:
545 info( controller.name + ' ')
548 info( '*** Starting %s switches\n' % len( self.switches ) )
549 for switch in self.switches:
550 info( switch.name + ' ')
551 switch.start( self.controllers )
553 for swclass, switches in groupby(
554 sorted( self.switches,
555 key=lambda s: str( type( s ) ) ), type ):
556 switches = tuple( switches )
557 if hasattr( swclass, 'batchStartup' ):
558 success = swclass.batchStartup( switches )
559 started.update( { s: s for s in success } )
565 "Stop the controller(s), switches and hosts"
566 info( '*** Stopping %i controllers\n' % len( self.controllers ) )
567 for controller in self.controllers:
568 info( controller.name + ' ' )
572 info( '*** Stopping %i terms\n' % len( self.terms ) )
574 info( '*** Stopping %i links\n' % len( self.links ) )
575 for link in self.links:
579 info( '*** Stopping %i switches\n' % len( self.switches ) )
581 for swclass, switches in groupby(
582 sorted( self.switches,
583 key=lambda s: str( type( s ) ) ), type ):
584 switches = tuple( switches )
585 if hasattr( swclass, 'batchShutdown' ):
586 success = swclass.batchShutdown( switches )
587 stopped.update( { s: s for s in success } )
588 for switch in self.switches:
589 info( switch.name + ' ' )
590 if switch not in stopped:
594 info( '*** Stopping %i hosts\n' % len( self.hosts ) )
595 for host in self.hosts:
596 info( host.name + ' ' )
598 info( '\n*** Done\n' )
600 def run( self, test, *args, **kwargs ):
601 "Perform a complete start/test/stop cycle."
603 info( '*** Running test\n' )
604 result = test( *args, **kwargs )
608 def monitor( self, hosts=None, timeoutms=-1 ):
609 """Monitor a set of hosts (or all hosts by default),
610 and return their output, a line at a time.
611 hosts: (optional) set of hosts to monitor
612 timeoutms: (optional) timeout value in ms
613 returns: iterator which returns host, line"""
616 poller = select.poll()
617 h1 = hosts[ 0 ] # so we can call class method fdToNode
619 poller.register( host.stdout )
621 ready = poller.poll( timeoutms )
622 for fd, event in ready:
623 host = h1.fdToNode( fd )
624 if event & select.POLLIN:
625 line = host.readline()
628 # Return if non-blocking
629 if not ready and timeoutms >= 0:
632 # XXX These test methods should be moved out of this class.
633 # Probably we should create a tests.py for them
636 def _parsePing( pingOutput ):
637 "Parse ping output and return packets sent, received."
638 # Check for downed link
639 if 'connect: Network is unreachable' in pingOutput:
641 r = r'(\d+) packets transmitted, (\d+)( packets)? received'
642 m = re.search( r, pingOutput )
644 error( '*** Error: could not parse ping output: %s\n' %
647 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
648 return sent, received
650 def ping( self, hosts=None, timeout=None ):
651 """Ping between all specified hosts.
653 timeout: time to wait for a response, as string
654 returns: ploss packet loss percentage"""
655 # should we check if running?
661 output( '*** Ping: testing ping reachability\n' )
663 output( '%s -> ' % node.name )
668 opts = '-W %s' % timeout
670 result = node.cmd( 'ping -c1 %s %s' %
672 sent, received = self._parsePing( result )
674 sent, received = 0, 0
677 error( '*** Error: received too many packets' )
678 error( '%s' % result )
679 node.cmdPrint( 'route' )
681 lost += sent - received
682 output( ( '%s ' % dest.name ) if received else 'X ' )
685 ploss = 100.0 * lost / packets
686 received = packets - lost
687 output( "*** Results: %i%% dropped (%d/%d received)\n" %
688 ( ploss, received, packets ) )
691 output( "*** Warning: No packets sent\n" )
695 def _parsePingFull( pingOutput ):
696 "Parse ping output and return all data."
697 errorTuple = (1, 0, 0, 0, 0, 0)
698 # Check for downed link
699 r = r'[uU]nreachable'
700 m = re.search( r, pingOutput )
703 r = r'(\d+) packets transmitted, (\d+)( packets)? received'
704 m = re.search( r, pingOutput )
706 error( '*** Error: could not parse ping output: %s\n' %
709 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
710 r = r'rtt min/avg/max/mdev = '
711 r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
712 m = re.search( r, pingOutput )
716 error( '*** Error: could not parse ping output: %s\n' %
719 rttmin = float( m.group( 1 ) )
720 rttavg = float( m.group( 2 ) )
721 rttmax = float( m.group( 3 ) )
722 rttdev = float( m.group( 4 ) )
723 return sent, received, rttmin, rttavg, rttmax, rttdev
725 def pingFull( self, hosts=None, timeout=None ):
726 """Ping between all specified hosts and return all data.
728 timeout: time to wait for a response, as string
729 returns: all ping data; see function body."""
730 # should we check if running?
731 # Each value is a tuple: (src, dsd, [all ping outputs])
735 output( '*** Ping: testing ping reachability\n' )
737 output( '%s -> ' % node.name )
742 opts = '-W %s' % timeout
743 result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
744 outputs = self._parsePingFull( result )
745 sent, received, rttmin, rttavg, rttmax, rttdev = outputs
746 all_outputs.append( (node, dest, outputs) )
747 output( ( '%s ' % dest.name ) if received else 'X ' )
749 output( "*** Results: \n" )
750 for outputs in all_outputs:
751 src, dest, ping_outputs = outputs
752 sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
753 output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
754 output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
755 (rttmin, rttavg, rttmax, rttdev) )
758 def pingAll( self, timeout=None ):
759 """Ping between all hosts.
760 returns: ploss packet loss percentage"""
761 return self.ping( timeout=timeout )
763 def pingPair( self ):
764 """Ping between first two hosts, useful for testing.
765 returns: ploss packet loss percentage"""
766 hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
767 return self.ping( hosts=hosts )
769 def pingAllFull( self ):
770 """Ping between all hosts.
771 returns: ploss packet loss percentage"""
772 return self.pingFull()
774 def pingPairFull( self ):
775 """Ping between first two hosts, useful for testing.
776 returns: ploss packet loss percentage"""
777 hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
778 return self.pingFull( hosts=hosts )
781 def _parseIperf( iperfOutput ):
782 """Parse iperf output and return bandwidth.
784 returns: result string"""
785 r = r'([\d\.]+ \w+/sec)'
786 m = re.findall( r, iperfOutput )
790 # was: raise Exception(...)
791 error( 'could not parse iperf output: ' + iperfOutput )
794 # XXX This should be cleaned up
796 def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
797 seconds=5, port=5001):
798 """Run iperf between two hosts.
799 hosts: list of hosts; if None, uses first and last hosts
800 l4Type: string, one of [ TCP, UDP ]
801 udpBw: bandwidth target for UDP test
802 fmt: iperf format argument if any
803 seconds: iperf time to transmit
805 returns: two-element array of [ server, client ] speeds
806 note: send() is buffered, so client rate can be much higher than
807 the actual transmission rate; on an unloaded system, server
808 rate should be much closer to the actual receive rate"""
809 hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
810 assert len( hosts ) == 2
811 client, server = hosts
812 output( '*** Iperf: testing', l4Type, 'bandwidth between',
813 client, 'and', server, '\n' )
814 server.cmd( 'killall -9 iperf' )
815 iperfArgs = 'iperf -p %d ' % port
819 bwArgs = '-b ' + udpBw + ' '
820 elif l4Type != 'TCP':
821 raise Exception( 'Unexpected l4 type: %s' % l4Type )
823 iperfArgs += '-f %s ' % fmt
824 server.sendCmd( iperfArgs + '-s' )
826 if not waitListening( client, server.IP(), port ):
827 raise Exception( 'Could not connect to iperf on port %d'
829 cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
830 server.IP() + ' ' + bwArgs )
831 debug( 'Client output: %s\n' % cliout )
833 # We want the last *b/sec from the iperf server output
834 # for TCP, there are two of them because of waitListening
835 count = 2 if l4Type == 'TCP' else 1
836 while len( re.findall( '/sec', servout ) ) < count:
837 servout += server.monitor( timeoutms=5000 )
839 servout += server.waitOutput()
840 debug( 'Server output: %s\n' % servout )
841 result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
843 result.insert( 0, udpBw )
844 output( '*** Results: %s\n' % result )
847 def runCpuLimitTest( self, cpu, duration=5 ):
848 """run CPU limit test with 'while true' processes.
849 cpu: desired CPU fraction of each host
850 duration: test duration in seconds (integer)
851 returns a single list of measured CPU fractions as floats.
854 info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
856 cores = int( quietRun( 'nproc' ) )
857 # number of processes to run a while loop on per host
858 num_procs = int( ceil( cores * cpu ) )
862 for _core in range( num_procs ):
863 h.cmd( 'while true; do a=1; done &' )
864 pids[ h ].append( h.cmd( 'echo $!' ).strip() )
867 # get the initial cpu time for each host
870 with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
872 time[ host ] = float( f.read() )
873 for _ in range( duration ):
876 with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
878 readTime = float( f.read() )
879 outputs[ host ].append( ( ( readTime - time[ host ] )
880 / 1000000000 ) / cores * 100 )
881 time[ host ] = readTime
882 for h, pids in pids.items():
884 h.cmd( 'kill -9 %s' % pid )
886 for _host, outputs in outputs.items():
888 cpu_fractions.append( pct )
889 output( '*** Results: %s\n' % cpu_fractions )
892 # BL: I think this can be rewritten now that we have
894 def configLinkStatus( self, src, dst, status ):
895 """Change status of src <-> dst links.
898 status: string {up, down}"""
899 if src not in self.nameToNode:
900 error( 'src not in network: %s\n' % src )
901 elif dst not in self.nameToNode:
902 error( 'dst not in network: %s\n' % dst )
904 src = self.nameToNode[ src ]
905 dst = self.nameToNode[ dst ]
906 connections = src.connectionsTo( dst )
907 if len( connections ) == 0:
908 error( 'src and dst not connected: %s %s\n' % ( src, dst) )
909 for srcIntf, dstIntf in connections:
910 result = srcIntf.ifconfig( status )
912 error( 'link src status change failed: %s\n' % result )
913 result = dstIntf.ifconfig( status )
915 error( 'link dst status change failed: %s\n' % result )
917 def interact( self ):
918 "Start network and run our simple CLI."
936 class MininetWithControlNet( Mininet ):
938 """Control network support:
940 Create an explicit control network. Currently this is only
941 used/usable with the user datapath.
945 1. If the controller and switches are in the same (e.g. root)
946 namespace, they can just use the loopback connection.
948 2. If we can get unix domain sockets to work, we can use them
949 instead of an explicit control network.
951 3. Instead of routing, we could bridge or use 'in-band' control.
953 4. Even if we dispense with this in general, it could still be
954 useful for people who wish to simulate a separate control
955 network (since real networks may need one!)
957 5. Basically nobody ever used this code, so it has been moved
960 6. Ultimately we may wish to extend this to allow us to create a
961 control network which every node's control interface is
964 def configureControlNetwork( self ):
965 "Configure control network."
966 self.configureRoutedControlNetwork()
968 # We still need to figure out the right way to pass
969 # in the control network location.
971 def configureRoutedControlNetwork( self, ip='192.168.123.1',
973 """Configure a routed control network on controller and switches.
974 For use with the user datapath only right now."""
975 controller = self.controllers[ 0 ]
976 info( controller.name + ' <->' )
979 for switch in self.switches:
980 info( ' ' + switch.name )
981 link = self.link( switch, controller, port1=0 )
982 sintf, cintf = link.intf1, link.intf2
983 switch.controlIntf = sintf
985 while snum & 0xff in [ 0, 255 ]:
988 cintf.setIP( cip, prefixLen )
989 sintf.setIP( sip, prefixLen )
990 controller.setHostRoute( sip, cintf )
991 switch.setHostRoute( cip, sintf )
993 info( '*** Testing control network\n' )
994 while not cintf.isUp():
995 info( '*** Waiting for', cintf, 'to come up\n' )
997 for switch in self.switches:
998 while not sintf.isUp():
999 info( '*** Waiting for', sintf, 'to come up\n' )
1001 if self.ping( hosts=[ switch, controller ] ) != 0:
1002 error( '*** Error: control network test failed\n' )