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( '\n*** Creating network...\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 + ' ' )
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 + ' ' )
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 ) )
494 def configureControlNetwork( self ):
495 "Control net config hook: override in subclass"
496 raise Exception( 'configureControlNetwork: '
497 'should be overriden in subclass', self )
502 self.buildFromTopo( self.topo )
504 self.configureControlNetwork()
505 info( '*** Configuring hosts\n' )
509 if self.autoStaticArp:
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" )
518 info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
520 self.terms += makeTerms( self.controllers, 'controller' )
521 self.terms += makeTerms( self.switches, 'switch' )
522 self.terms += makeTerms( self.hosts, 'host' )
524 def stopXterms( self ):
526 for term in self.terms:
527 os.kill( term.pid, signal.SIGKILL )
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:
535 src.setARP( ip=dst.IP(), mac=dst.MAC() )
538 "Start controller and switches."
541 info( '*** Starting controller\n' )
542 for controller in self.controllers:
543 info( controller.name + ' ')
546 info( '*** Starting %s switches\n' % len( self.switches ) )
547 for switch in self.switches:
548 info( switch.name + ' ')
549 switch.start( self.controllers )
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 } )
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 + ' ' )
570 info( '*** Stopping %i terms\n' % len( self.terms ) )
572 info( '*** Stopping %i links\n' % len( self.links ) )
573 for link in self.links:
577 info( '*** Stopping %i switches\n' % len( self.switches ) )
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:
592 info( '*** Stopping %i hosts\n' % len( self.hosts ) )
593 for host in self.hosts:
594 info( host.name + ' ' )
596 info( '\n*** Done\n' )
598 def run( self, test, *args, **kwargs ):
599 "Perform a complete start/test/stop cycle."
601 info( '*** Running test\n' )
602 result = test( *args, **kwargs )
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"""
614 poller = select.poll()
615 h1 = hosts[ 0 ] # so we can call class method fdToNode
617 poller.register( host.stdout )
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()
626 # Return if non-blocking
627 if not ready and timeoutms >= 0:
630 # XXX These test methods should be moved out of this class.
631 # Probably we should create a tests.py for them
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:
639 r = r'(\d+) packets transmitted, (\d+)( packets)? received'
640 m = re.search( r, pingOutput )
642 error( '*** Error: could not parse ping output: %s\n' %
645 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
646 return sent, received
648 def ping( self, hosts=None, timeout=None ):
649 """Ping between all specified hosts.
651 timeout: time to wait for a response, as string
652 returns: ploss packet loss percentage"""
653 # should we check if running?
659 output( '*** Ping: testing ping reachability\n' )
661 output( '%s -> ' % node.name )
666 opts = '-W %s' % timeout
668 result = node.cmd( 'ping -c1 %s %s' %
670 sent, received = self._parsePing( result )
672 sent, received = 0, 0
675 error( '*** Error: received too many packets' )
676 error( '%s' % result )
677 node.cmdPrint( 'route' )
679 lost += sent - received
680 output( ( '%s ' % dest.name ) if received else 'X ' )
683 ploss = 100.0 * lost / packets
684 received = packets - lost
685 output( "*** Results: %i%% dropped (%d/%d received)\n" %
686 ( ploss, received, packets ) )
689 output( "*** Warning: No packets sent\n" )
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 )
701 r = r'(\d+) packets transmitted, (\d+)( packets)? received'
702 m = re.search( r, pingOutput )
704 error( '*** Error: could not parse ping output: %s\n' %
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 )
714 error( '*** Error: could not parse ping output: %s\n' %
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
723 def pingFull( self, hosts=None, timeout=None ):
724 """Ping between all specified hosts and return all data.
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])
733 output( '*** Ping: testing ping reachability\n' )
735 output( '%s -> ' % node.name )
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 ' )
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) )
756 def pingAll( self, timeout=None ):
757 """Ping between all hosts.
758 returns: ploss packet loss percentage"""
759 return self.ping( timeout=timeout )
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 )
767 def pingAllFull( self ):
768 """Ping between all hosts.
769 returns: ploss packet loss percentage"""
770 return self.pingFull()
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 )
779 def _parseIperf( iperfOutput ):
780 """Parse iperf output and return bandwidth.
782 returns: result string"""
783 r = r'([\d\.]+ \w+/sec)'
784 m = re.findall( r, iperfOutput )
788 # was: raise Exception(...)
789 error( 'could not parse iperf output: ' + iperfOutput )
792 # XXX This should be cleaned up
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
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
817 bwArgs = '-b ' + udpBw + ' '
818 elif l4Type != 'TCP':
819 raise Exception( 'Unexpected l4 type: %s' % l4Type )
821 iperfArgs += '-f %s ' % fmt
822 server.sendCmd( iperfArgs + '-s' )
824 if not waitListening( client, server.IP(), port ):
825 raise Exception( 'Could not connect to iperf on port %d'
827 cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
828 server.IP() + ' ' + bwArgs )
829 debug( 'Client output: %s\n' % cliout )
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 )
837 servout += server.waitOutput()
838 debug( 'Server output: %s\n' % servout )
839 result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
841 result.insert( 0, udpBw )
842 output( '*** Results: %s\n' % result )
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.
852 info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
854 cores = int( quietRun( 'nproc' ) )
855 # number of processes to run a while loop on per host
856 num_procs = int( ceil( cores * cpu ) )
860 for _core in range( num_procs ):
861 h.cmd( 'while true; do a=1; done &' )
862 pids[ h ].append( h.cmd( 'echo $!' ).strip() )
865 # get the initial cpu time for each host
868 with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
870 time[ host ] = float( f.read() )
871 for _ in range( duration ):
874 with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
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():
882 h.cmd( 'kill -9 %s' % pid )
884 for _host, outputs in outputs.items():
886 cpu_fractions.append( pct )
887 output( '*** Results: %s\n' % cpu_fractions )
890 # BL: I think this can be rewritten now that we have
892 def configLinkStatus( self, src, dst, status ):
893 """Change status of src <-> dst links.
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 )
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 )
910 error( 'link src status change failed: %s\n' % result )
911 result = dstIntf.ifconfig( status )
913 error( 'link dst status change failed: %s\n' % result )
915 def interact( self ):
916 "Start network and run our simple CLI."
934 class MininetWithControlNet( Mininet ):
936 """Control network support:
938 Create an explicit control network. Currently this is only
939 used/usable with the user datapath.
943 1. If the controller and switches are in the same (e.g. root)
944 namespace, they can just use the loopback connection.
946 2. If we can get unix domain sockets to work, we can use them
947 instead of an explicit control network.
949 3. Instead of routing, we could bridge or use 'in-band' control.
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!)
955 5. Basically nobody ever used this code, so it has been moved
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
962 def configureControlNetwork( self ):
963 "Configure control network."
964 self.configureRoutedControlNetwork()
966 # We still need to figure out the right way to pass
967 # in the control network location.
969 def configureRoutedControlNetwork( self, ip='192.168.123.1',
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 + ' <->' )
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
983 while snum & 0xff in [ 0, 255 ]:
986 cintf.setIP( cip, prefixLen )
987 sintf.setIP( sip, prefixLen )
988 controller.setHostRoute( sip, cintf )
989 switch.setHostRoute( cip, sintf )
991 info( '*** Testing control network\n' )
992 while not cintf.isUp():
993 info( '*** Waiting for', cintf, 'to come up\n' )
995 for switch in self.switches:
996 while not sintf.isUp():
997 info( '*** Waiting for', sintf, 'to come up\n' )
999 if self.ping( hosts=[ switch, controller ] ) != 0:
1000 error( '*** Error: control network test failed\n' )