blob: 847850b568e915840fbcbd85707cdd03f58625c1 [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"""
2
3 Mininet: A simple networking testbed for OpenFlow/SDN!
4
5author: Bob Lantz (rlantz@cs.stanford.edu)
6author: Brandon Heller (brandonh@stanford.edu)
7
8Mininet creates scalable OpenFlow test networks by using
9process-based virtualization and network namespaces.
10
11Simulated hosts are created as processes in separate network
12namespaces. This allows a complete OpenFlow network to be simulated on
13top of a single Linux kernel.
14
15Each host has:
16
17A virtual console (pipes to a shell)
18A virtual interfaces (half of a veth pair)
19A parent shell (and possibly some child processes) in a namespace
20
21Hosts have a network interface which is configured via ifconfig/ip
22link/etc.
23
24This version supports both the kernel and user space datapaths
25from the OpenFlow reference implementation (openflowswitch.org)
26as well as OpenVSwitch (openvswitch.org.)
27
28In kernel datapath mode, the controller and switches are simply
29processes in the root namespace.
30
31Kernel OpenFlow datapaths are instantiated using dpctl(8), and are
32attached to the one side of a veth pair; the other side resides in the
33host namespace. In this mode, switch processes can simply connect to the
34controller via the loopback interface.
35
36In user datapath mode, the controller and switches can be full-service
37nodes that live in their own network namespaces and have management
38interfaces and IP addresses on a control network (e.g. 192.168.123.1,
39currently routed although it could be bridged.)
40
41In addition to a management interface, user mode switches also have
42several switch interfaces, halves of veth pairs whose other halves
43reside in the host nodes that the switches are connected to.
44
45Consistent, straightforward naming is important in order to easily
46identify hosts, switches and controllers, both from the CLI and
47from program code. Interfaces are named to make it easy to identify
48which interfaces belong to which node.
49
50The 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
57Note: If the network topology is created using mininet.topo, then
58node numbers are unique among hosts and switches (e.g. we have
59h1..hN and SN..SN+M) and also correspond to their default IP addresses
60of 10.x.y.z/8 where x.y.z is the base-256 representation of N for
61hN. This mapping allows easy determination of a node's IP
62address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1.
63
64Note 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
67Currently we wrap the entire network in a 'mininet' object, which
68constructs a simulated network based on a network topology created
69using a topology object (e.g. LinearTopo) from mininet.topo or
70mininet.topolib, and a Controller which the switches will connect
71to. Several configuration options are provided for functions such as
72automatically setting MAC addresses, populating the ARP table, or
73even running a set of terminals to allow direct interaction with nodes.
74
75After the network is created, it can be started using start(), and a
76variety of useful tasks maybe performed, including basic connectivity
77and bandwidth tests and running the mininet CLI.
78
79Once the network is up and running, test code can easily get access
80to host and switch objects which can then be used for arbitrary
81experiments, typically involving running a series of commands on the
82hosts.
83
84After all desired tests or activities have been completed, the stop()
85method may be called to shut down the network.
86
87"""
88
89import os
90import re
91import select
92import signal
93from time import sleep
94
95from mininet.cli import CLI
96from mininet.log import info, error, debug, output
97from mininet.node import Host, OVSKernelSwitch, Controller
98from mininet.link import Link, Intf
99from mininet.util import quietRun, fixLimits, numCores, ensureRoot
100from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd, nextCCNnet
101from mininet.term import cleanUpScreens, makeTerms
Caio188d2f32015-01-22 23:35:08 -0200102import pdb
carlosmscabralf40ecd12013-02-01 18:15:58 -0200103
104# Mininet version: should be consistent with README and LICENSE
105VERSION = "2.0.0"
106
107class Mininet( object ):
108 "Network emulation with hosts spawned in network namespaces."
109
110 def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
111 controller=Controller, link=Link, intf=Intf,
112 build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
113 inNamespace=False,
114 autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
115 listenPort=None ):
116 """Create Mininet object.
117 topo: Topo (topology) object or None
118 switch: default Switch class
119 host: default Host class/constructor
120 controller: default Controller class/constructor
121 link: default Link class/constructor
122 intf: default Intf class/constructor
123 ipBase: base IP address for hosts,
124 build: build now from topo?
125 xterms: if build now, spawn xterms?
126 cleanup: if build now, cleanup before creating?
127 inNamespace: spawn switches and controller in net namespaces?
128 autoSetMacs: set MAC addrs automatically like IP addresses?
129 autoStaticArp: set all-pairs static MAC addrs?
130 autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
131 listenPort: base listening port to open; will be incremented for
132 each additional switch in the net if inNamespace=False"""
133 self.topo = topo
134 self.switch = switch
135 self.host = host
136 self.controller = controller
137 self.link = link
138 self.intf = intf
139 self.ipBase = ipBase
140 self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
141 self.nextIP = 1 # start for address allocation
142 self.ccnNetBase = '1.0.0.0'
143 self.inNamespace = inNamespace
144 self.xterms = xterms
145 self.cleanup = cleanup
146 self.autoSetMacs = autoSetMacs
147 self.autoStaticArp = autoStaticArp
148 self.autoPinCpus = autoPinCpus
149 self.numCores = numCores()
150 self.nextCore = 0 # next core for pinning hosts to CPUs
151 self.listenPort = listenPort
152
153 self.hosts = []
154 self.switches = []
155 self.controllers = []
156
157 self.nameToNode = {} # name to Node (Host/Switch) objects
158
159 self.terms = [] # list of spawned xterm processes
160
161 Mininet.init() # Initialize Mininet if necessary
162
163 self.built = False
164 if topo and build:
165 self.build()
166
ashuef3490b2015-02-17 11:01:04 -0600167 def isNdnhost(self, node):
carlosmscabralf40ecd12013-02-01 18:15:58 -0200168 if 'fib' in node.params:
169 return True
170 else:
171 return False
172
173 def addHost( self, name, cls=None, **params ):
174 """Add host.
175 name: name of host to add
176 cls: custom host class/constructor (optional)
177 params: parameters for host
178 returns: added host"""
179 # Default IP and MAC addresses
Caio188d2f32015-01-22 23:35:08 -0200180 #pdb.set_trace()
181 #defaults = { 'ip': ipAdd( self.nextIP,
182 #ipBaseNum=self.ipBaseNum,
183 #prefixLen=self.prefixLen ) +
184 #'/%s' % self.prefixLen }
185 #if self.autoSetMacs:
186 #defaults[ 'mac'] = macColonHex( self.nextIP )
187 #if self.autoPinCpus:
188 #defaults[ 'cores' ] = self.nextCore
189 #self.nextCore = ( self.nextCore + 1 ) % self.numCores
190 #self.nextIP += 1
191 defaults = {}
carlosmscabralf40ecd12013-02-01 18:15:58 -0200192 defaults.update( params )
Caio188d2f32015-01-22 23:35:08 -0200193
carlosmscabralf40ecd12013-02-01 18:15:58 -0200194 if not cls:
195 cls = self.host
196 h = cls( name, **defaults )
197 self.hosts.append( h )
198 self.nameToNode[ name ] = h
199 return h
200
201 def addSwitch( self, name, cls=None, **params ):
202 """Add switch.
203 name: name of switch to add
204 cls: custom switch class/constructor (optional)
205 returns: added switch
206 side effect: increments listenPort ivar ."""
207 defaults = { 'listenPort': self.listenPort,
208 'inNamespace': self.inNamespace }
209 defaults.update( params )
210 if not cls:
211 cls = self.switch
212 sw = cls( name, **defaults )
213 if not self.inNamespace and self.listenPort:
214 self.listenPort += 1
215 self.switches.append( sw )
216 self.nameToNode[ name ] = sw
217 return sw
218
219 def addController( self, name='c0', controller=None, **params ):
220 """Add controller.
221 controller: Controller class"""
222 if not controller:
223 controller = self.controller
224 controller_new = controller( name, **params )
225 if controller_new: # allow controller-less setups
226 self.controllers.append( controller_new )
227 self.nameToNode[ name ] = controller_new
228 return controller_new
229
230 # BL: is this better than just using nameToNode[] ?
231 # Should it have a better name?
232 def getNodeByName( self, *args ):
233 "Return node(s) with given name(s)"
234 if len( args ) == 1:
235 return self.nameToNode[ args[ 0 ] ]
236 return [ self.nameToNode[ n ] for n in args ]
237
238 def get( self, *args ):
239 "Convenience alias for getNodeByName"
240 return self.getNodeByName( *args )
241
242 def addLink( self, node1, node2, port1=None, port2=None,
243 cls=None, **params ):
244 """"Add a link from node1 to node2
245 node1: source node
246 node2: dest node
247 port1: source port
248 port2: dest port
249 returns: link object"""
250 defaults = { 'port1': port1,
251 'port2': port2,
252 'intf': self.intf }
253 defaults.update( params )
254 if not cls:
255 cls = self.link
256 return cls( node1, node2, **defaults )
257
258 def configHosts( self ):
259 "Configure a set of hosts."
260 for host in self.hosts:
261 info( host.name + ' ' )
262 intf = host.defaultIntf()
ashuef3490b2015-02-17 11:01:04 -0600263 if self.isNdnhost(host):
264 host.configNdn()
carlosmscabralf40ecd12013-02-01 18:15:58 -0200265 host.configDefault(ip=None,mac=None)
266 elif intf:
267 host.configDefault( defaultRoute=intf )
268 else:
269 # Don't configure nonexistent intf
270 host.configDefault( ip=None, mac=None )
271 # You're low priority, dude!
272 # BL: do we want to do this here or not?
273 # May not make sense if we have CPU lmiting...
274 # quietRun( 'renice +18 -p ' + repr( host.pid ) )
275 # This may not be the right place to do this, but
276 # it needs to be done somewhere.
277 host.cmd( 'ifconfig lo up' )
278 info( '\n' )
279
280 def buildFromTopo( self, topo=None ):
281 """Build mininet from a topology object
282 At the end of this function, everything should be connected
283 and up."""
284
285 # Possibly we should clean up here and/or validate
286 # the topo
287 if self.cleanup:
288 pass
289
290 info( '*** Creating network\n' )
291
Caio188d2f32015-01-22 23:35:08 -0200292 #if not self.controllers:
carlosmscabralf40ecd12013-02-01 18:15:58 -0200293 # Add a default controller
Caio188d2f32015-01-22 23:35:08 -0200294 #info( '*** Adding controller\n' )
295 #classes = self.controller
296 #if type( classes ) is not list:
297 # classes = [ classes ]
298 #for i, cls in enumerate( classes ):
299 # self.addController( 'c%d' % i, cls )
carlosmscabralf40ecd12013-02-01 18:15:58 -0200300
301 info( '*** Adding hosts:\n' )
302 for hostName in topo.hosts():
Caio188d2f32015-01-22 23:35:08 -0200303 #pdb.set_trace()
carlosmscabralf40ecd12013-02-01 18:15:58 -0200304 self.addHost( hostName, **topo.nodeInfo( hostName ) )
305 info( hostName + ' ' )
306
307 info( '\n*** Adding switches:\n' )
308 for switchName in topo.switches():
309 self.addSwitch( switchName, **topo.nodeInfo( switchName) )
310 info( switchName + ' ' )
311
312 info( '\n*** Adding links:\n' )
313 for srcName, dstName in topo.links(sort=True):
314 src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
315 params = topo.linkInfo( srcName, dstName )
316 srcPort, dstPort = topo.port( srcName, dstName )
317 self.addLink( src, dst, srcPort, dstPort, **params )
ashuef3490b2015-02-17 11:01:04 -0600318 if self.isNdnhost(src):
carlosmscabralf40ecd12013-02-01 18:15:58 -0200319 src.setIP(ipStr(ipParse(self.ccnNetBase) + 1) + '/30', intf=src.name + '-eth' + str(srcPort))
320 dst.setIP(ipStr(ipParse(self.ccnNetBase) + 2) + '/30', intf=dst.name + '-eth' + str(dstPort))
321 self.ccnNetBase=nextCCNnet(self.ccnNetBase)
322
323 info( '(%s, %s) ' % ( src.name, dst.name ) )
324
325 info( '\n' )
326
327
328 def configureControlNetwork( self ):
329 "Control net config hook: override in subclass"
330 raise Exception( 'configureControlNetwork: '
331 'should be overriden in subclass', self )
332
333 def build( self ):
334 "Build mininet."
Caio188d2f32015-01-22 23:35:08 -0200335
carlosmscabralf40ecd12013-02-01 18:15:58 -0200336 if self.topo:
337 self.buildFromTopo( self.topo )
338 if ( self.inNamespace ):
339 self.configureControlNetwork()
340 info( '*** Configuring hosts\n' )
341 self.configHosts()
342 if self.xterms:
343 self.startTerms()
344 if self.autoStaticArp:
345 self.staticArp()
346 self.built = True
347
348 def startTerms( self ):
349 "Start a terminal for each node."
350 info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
351 cleanUpScreens()
352 self.terms += makeTerms( self.controllers, 'controller' )
353 self.terms += makeTerms( self.switches, 'switch' )
354 self.terms += makeTerms( self.hosts, 'host' )
355
356 def stopXterms( self ):
357 "Kill each xterm."
358 for term in self.terms:
359 os.kill( term.pid, signal.SIGKILL )
360 cleanUpScreens()
361
362 def staticArp( self ):
363 "Add all-pairs ARP entries to remove the need to handle broadcast."
364 for src in self.hosts:
365 for dst in self.hosts:
366 if src != dst:
367 src.setARP( ip=dst.IP(), mac=dst.MAC() )
368
369 def start( self ):
370 "Start controller and switches."
371 if not self.built:
372 self.build()
373 info( '*** Starting controller\n' )
374 for controller in self.controllers:
375 controller.start()
376 info( '*** Starting %s switches\n' % len( self.switches ) )
377 for switch in self.switches:
378 info( switch.name + ' ')
379 switch.start( self.controllers )
380 info( '\n' )
381
382 def stop( self ):
383 "Stop the controller(s), switches and hosts"
384 if self.terms:
385 info( '*** Stopping %i terms\n' % len( self.terms ) )
386 self.stopXterms()
387 info( '*** Stopping %i hosts\n' % len( self.hosts ) )
388 for host in self.hosts:
389 info( host.name + ' ' )
390 host.terminate()
391 info( '\n' )
392 info( '*** Stopping %i switches\n' % len( self.switches ) )
393 for switch in self.switches:
394 info( switch.name + ' ' )
395 switch.stop()
396 info( '\n' )
397 info( '*** Stopping %i controllers\n' % len( self.controllers ) )
398 for controller in self.controllers:
399 info( controller.name + ' ' )
400 controller.stop()
401 info( '\n*** Done\n' )
402
403 def run( self, test, *args, **kwargs ):
404 "Perform a complete start/test/stop cycle."
405 self.start()
406 info( '*** Running test\n' )
407 result = test( *args, **kwargs )
408 self.stop()
409 return result
410
411 def monitor( self, hosts=None, timeoutms=-1 ):
412 """Monitor a set of hosts (or all hosts by default),
413 and return their output, a line at a time.
414 hosts: (optional) set of hosts to monitor
415 timeoutms: (optional) timeout value in ms
416 returns: iterator which returns host, line"""
417 if hosts is None:
418 hosts = self.hosts
419 poller = select.poll()
420 Node = hosts[ 0 ] # so we can call class method fdToNode
421 for host in hosts:
422 poller.register( host.stdout )
423 while True:
424 ready = poller.poll( timeoutms )
425 for fd, event in ready:
426 host = Node.fdToNode( fd )
427 if event & select.POLLIN:
428 line = host.readline()
429 if line is not None:
430 yield host, line
431 # Return if non-blocking
432 if not ready and timeoutms >= 0:
433 yield None, None
434
435 # XXX These test methods should be moved out of this class.
436 # Probably we should create a tests.py for them
437
438 @staticmethod
439 def _parsePing( pingOutput ):
440 "Parse ping output and return packets sent, received."
441 # Check for downed link
442 if 'connect: Network is unreachable' in pingOutput:
443 return (1, 0)
444 r = r'(\d+) packets transmitted, (\d+) received'
445 m = re.search( r, pingOutput )
446 if m is None:
447 error( '*** Error: could not parse ping output: %s\n' %
448 pingOutput )
449 return (1, 0)
450 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
451 return sent, received
452
453 def ping( self, hosts=None, timeout=None ):
454 """Ping between all specified hosts.
455 hosts: list of hosts
456 timeout: time to wait for a response, as string
457 returns: ploss packet loss percentage"""
458 # should we check if running?
459 packets = 0
460 lost = 0
461 ploss = None
462 if not hosts:
463 hosts = self.hosts
464 output( '*** Ping: testing ping reachability\n' )
465 for node in hosts:
466 output( '%s -> ' % node.name )
467 for dest in hosts:
468 if node != dest:
469 opts = ''
470 if timeout:
471 opts = '-W %s' % timeout
472 result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
473 sent, received = self._parsePing( result )
474 packets += sent
475 if received > sent:
476 error( '*** Error: received too many packets' )
477 error( '%s' % result )
478 node.cmdPrint( 'route' )
479 exit( 1 )
480 lost += sent - received
481 output( ( '%s ' % dest.name ) if received else 'X ' )
482 output( '\n' )
483 ploss = 100 * lost / packets
484 output( "*** Results: %i%% dropped (%d/%d lost)\n" %
485 ( ploss, lost, packets ) )
486 return ploss
487
488 @staticmethod
489 def _parsePingFull( pingOutput ):
490 "Parse ping output and return all data."
491 # Check for downed link
492 if 'connect: Network is unreachable' in pingOutput:
493 return (1, 0)
494 r = r'(\d+) packets transmitted, (\d+) received'
495 m = re.search( r, pingOutput )
496 if m is None:
497 error( '*** Error: could not parse ping output: %s\n' %
498 pingOutput )
499 return (1, 0, 0, 0, 0, 0)
500 sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
501 r = r'rtt min/avg/max/mdev = '
502 r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
503 m = re.search( r, pingOutput )
504 rttmin = float( m.group( 1 ) )
505 rttavg = float( m.group( 2 ) )
506 rttmax = float( m.group( 3 ) )
507 rttdev = float( m.group( 4 ) )
508 return sent, received, rttmin, rttavg, rttmax, rttdev
509
510 def pingFull( self, hosts=None, timeout=None ):
511 """Ping between all specified hosts and return all data.
512 hosts: list of hosts
513 timeout: time to wait for a response, as string
514 returns: all ping data; see function body."""
515 # should we check if running?
516 # Each value is a tuple: (src, dsd, [all ping outputs])
517 all_outputs = []
518 if not hosts:
519 hosts = self.hosts
520 output( '*** Ping: testing ping reachability\n' )
521 for node in hosts:
522 output( '%s -> ' % node.name )
523 for dest in hosts:
524 if node != dest:
525 opts = ''
526 if timeout:
527 opts = '-W %s' % timeout
528 result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
529 outputs = self._parsePingFull( result )
530 sent, received, rttmin, rttavg, rttmax, rttdev = outputs
531 all_outputs.append( (node, dest, outputs) )
532 output( ( '%s ' % dest.name ) if received else 'X ' )
533 output( '\n' )
534 output( "*** Results: \n" )
535 for outputs in all_outputs:
536 src, dest, ping_outputs = outputs
537 sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
538 output( " %s->%s: %s/%s, " % (src, dest, sent, received ) )
539 output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" %
540 (rttmin, rttavg, rttmax, rttdev) )
541 return all_outputs
542
543 def pingAll( self ):
544 """Ping between all hosts.
545 returns: ploss packet loss percentage"""
546 return self.ping()
547
548 def pingPair( self ):
549 """Ping between first two hosts, useful for testing.
550 returns: ploss packet loss percentage"""
551 hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
552 return self.ping( hosts=hosts )
553
554 def pingAllFull( self ):
555 """Ping between all hosts.
556 returns: ploss packet loss percentage"""
557 return self.pingFull()
558
559 def pingPairFull( self ):
560 """Ping between first two hosts, useful for testing.
561 returns: ploss packet loss percentage"""
562 hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ]
563 return self.pingFull( hosts=hosts )
564
565 @staticmethod
566 def _parseIperf( iperfOutput ):
567 """Parse iperf output and return bandwidth.
568 iperfOutput: string
569 returns: result string"""
570 r = r'([\d\.]+ \w+/sec)'
571 m = re.findall( r, iperfOutput )
572 if m:
573 return m[-1]
574 else:
575 # was: raise Exception(...)
576 error( 'could not parse iperf output: ' + iperfOutput )
577 return ''
578
579 # XXX This should be cleaned up
580
581 def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
582 """Run iperf between two hosts.
583 hosts: list of hosts; if None, uses opposite hosts
584 l4Type: string, one of [ TCP, UDP ]
585 returns: results two-element array of server and client speeds"""
586 if not quietRun( 'which telnet' ):
587 error( 'Cannot find telnet in $PATH - required for iperf test' )
588 return
589 if not hosts:
590 hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
591 else:
592 assert len( hosts ) == 2
593 client, server = hosts
594 output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
595 output( "%s and %s\n" % ( client.name, server.name ) )
596 server.cmd( 'killall -9 iperf' )
597 iperfArgs = 'iperf '
598 bwArgs = ''
599 if l4Type == 'UDP':
600 iperfArgs += '-u '
601 bwArgs = '-b ' + udpBw + ' '
602 elif l4Type != 'TCP':
603 raise Exception( 'Unexpected l4 type: %s' % l4Type )
604 server.sendCmd( iperfArgs + '-s', printPid=True )
605 servout = ''
606 while server.lastPid is None:
607 servout += server.monitor()
608 if l4Type == 'TCP':
609 while 'Connected' not in client.cmd(
610 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
611 output('waiting for iperf to start up...')
612 sleep(.5)
613 cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
614 bwArgs )
615 debug( 'Client output: %s\n' % cliout )
616 server.sendInt()
617 servout += server.waitOutput()
618 debug( 'Server output: %s\n' % servout )
619 result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
620 if l4Type == 'UDP':
621 result.insert( 0, udpBw )
622 output( '*** Results: %s\n' % result )
623 return result
624
625 def runCpuLimitTest( self, cpu, duration=5 ):
626 """run CPU limit test with 'while true' processes.
627 cpu: desired CPU fraction of each host
628 duration: test duration in seconds
629 returns a single list of measured CPU fractions as floats.
630 """
631 pct = cpu * 100
632 info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
633 hosts = self.hosts
634 for h in hosts:
635 h.cmd( 'while true; do a=1; done &' )
636 pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
637 pids_str = ",".join(["%s" % pid for pid in pids])
638 cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
639 # It's a shame that this is what pylint prefers
640 outputs = []
641 for _ in range( duration ):
642 sleep( 1 )
643 outputs.append( quietRun( cmd ).strip() )
644 for h in hosts:
645 h.cmd( 'kill %1' )
646 cpu_fractions = []
647 for test_output in outputs:
648 # Split by line. Ignore first line, which looks like this:
649 # PID %CPU COMMAND\n
650 for line in test_output.split('\n')[1:]:
651 r = r'\d+\s*(\d+\.\d+)'
652 m = re.search( r, line )
653 if m is None:
654 error( '*** Error: could not extract CPU fraction: %s\n' %
655 line )
656 return None
657 cpu_fractions.append( float( m.group( 1 ) ) )
658 output( '*** Results: %s\n' % cpu_fractions )
659 return cpu_fractions
660
661 # BL: I think this can be rewritten now that we have
662 # a real link class.
663 def configLinkStatus( self, src, dst, status ):
664 """Change status of src <-> dst links.
665 src: node name
666 dst: node name
667 status: string {up, down}"""
668 if src not in self.nameToNode:
669 error( 'src not in network: %s\n' % src )
670 elif dst not in self.nameToNode:
671 error( 'dst not in network: %s\n' % dst )
672 else:
673 if type( src ) is str:
674 src = self.nameToNode[ src ]
675 if type( dst ) is str:
676 dst = self.nameToNode[ dst ]
677 connections = src.connectionsTo( dst )
678 if len( connections ) == 0:
679 error( 'src and dst not connected: %s %s\n' % ( src, dst) )
680 for srcIntf, dstIntf in connections:
681 result = srcIntf.ifconfig( status )
682 if result:
683 error( 'link src status change failed: %s\n' % result )
684 result = dstIntf.ifconfig( status )
685 if result:
686 error( 'link dst status change failed: %s\n' % result )
687
688 def interact( self ):
689 "Start network and run our simple CLI."
690 self.start()
691 result = CLI( self )
692 self.stop()
693 return result
694
695 inited = False
696
697 @classmethod
698 def init( cls ):
699 "Initialize Mininet"
700 if cls.inited:
701 return
702 ensureRoot()
703 fixLimits()
704 cls.inited = True
705
706
707class MininetWithControlNet( Mininet ):
708
709 """Control network support:
710
711 Create an explicit control network. Currently this is only
712 used/usable with the user datapath.
713
714 Notes:
715
716 1. If the controller and switches are in the same (e.g. root)
717 namespace, they can just use the loopback connection.
718
719 2. If we can get unix domain sockets to work, we can use them
720 instead of an explicit control network.
721
722 3. Instead of routing, we could bridge or use 'in-band' control.
723
724 4. Even if we dispense with this in general, it could still be
725 useful for people who wish to simulate a separate control
726 network (since real networks may need one!)
727
728 5. Basically nobody ever used this code, so it has been moved
729 into its own class.
730
731 6. Ultimately we may wish to extend this to allow us to create a
732 control network which every node's control interface is
733 attached to."""
734
735 def configureControlNetwork( self ):
736 "Configure control network."
737 self.configureRoutedControlNetwork()
738
739 # We still need to figure out the right way to pass
740 # in the control network location.
741
742 def configureRoutedControlNetwork( self, ip='192.168.123.1',
743 prefixLen=16 ):
744 """Configure a routed control network on controller and switches.
745 For use with the user datapath only right now."""
746 controller = self.controllers[ 0 ]
747 info( controller.name + ' <->' )
748 cip = ip
749 snum = ipParse( ip )
750 for switch in self.switches:
751 info( ' ' + switch.name )
752 link = self.link( switch, controller, port1=0 )
753 sintf, cintf = link.intf1, link.intf2
754 switch.controlIntf = sintf
755 snum += 1
756 while snum & 0xff in [ 0, 255 ]:
757 snum += 1
758 sip = ipStr( snum )
759 cintf.setIP( cip, prefixLen )
760 sintf.setIP( sip, prefixLen )
761 controller.setHostRoute( sip, cintf )
762 switch.setHostRoute( cip, sintf )
763 info( '\n' )
764 info( '*** Testing control network\n' )
765 while not cintf.isUp():
766 info( '*** Waiting for', cintf, 'to come up\n' )
767 sleep( 1 )
768 for switch in self.switches:
769 while not sintf.isUp():
770 info( '*** Waiting for', sintf, 'to come up\n' )
771 sleep( 1 )
772 if self.ping( hosts=[ switch, controller ] ) != 0:
773 error( '*** Error: control network test failed\n' )
774 exit( 1 )
775 info( '\n' )