def run(self, test, *args, **kwargs): "Perform a complete start/test/stop cycle." self.start() info('*** Running test\n') result = test(*args, **kwargs) self.stop() return result
def moduleDeps(subtract=None, add=None): """Handle module dependencies. subtract: string or list of module names to remove, if already loaded add: string or list of module names to add, if not already loaded""" subtract = subtract if subtract is not None else [] add = add if add is not None else [] if isinstance(subtract, BaseString): subtract = [subtract] if isinstance(add, BaseString): add = [add] for mod in subtract: if mod in lsmod(): info('*** Removing ' + mod + '\n') rmmodOutput = rmmod(mod) if rmmodOutput: error('Error removing ' + mod + ': "%s">\n' % rmmodOutput) exit(1) if mod in lsmod(): error('Failed to remove ' + mod + '; still there!\n') exit(1) for mod in add: if mod not in lsmod(): info('*** Loading ' + mod + '\n') modprobeOutput = modprobe(mod) if modprobeOutput: error('Error inserting ' + mod + ' - is it installed and available via modprobe?\n' + 'Error was: "%s"\n' % modprobeOutput) if mod not in lsmod(): error('Failed to insert ' + mod + ' - quitting.\n') exit(1) else: debug('*** ' + mod + ' already loaded\n')
def startTerms(self): "Start a terminal for each node." if 'DISPLAY' not in os.environ: error("Error starting terms: Cannot connect to display\n") return info("*** Running terms on %s\n" % os.environ['DISPLAY']) cleanUpScreens() self.terms += makeTerms(self.controllers, 'controller') self.terms += makeTerms(self.switches, 'switch') self.terms += makeTerms(self.hosts, 'host')
def build(self): "Build mininet." if self.topo: self.buildFromTopo(self.topo) if self.inNamespace: self.configureControlNetwork() info('*** Configuring hosts\n') self.configHosts() if self.xterms: self.startTerms() if self.autoStaticArp: self.staticArp() self.built = True
def configHosts(self): "Configure a set of hosts." for host in self.hosts: info(host.name + ' ') intf = host.defaultIntf() if intf: host.configDefault() else: # Don't configure nonexistent intf host.configDefault(ip=None, mac=None) # You're low priority, dude! # BL: do we want to do this here or not? # May not make sense if we have CPU lmiting... # quietRun( 'renice +18 -p ' + repr( host.pid ) ) # This may not be the right place to do this, but # it needs to be done somewhere. info('\n')
def runCpuLimitTest(self, cpu, duration=5): """run CPU limit test with 'while true' processes. cpu: desired CPU fraction of each host duration: test duration in seconds (integer) returns a single list of measured CPU fractions as floats. """ pct = cpu * 100 info('*** Testing CPU %.0f%% bandwidth limit\n' % pct) hosts = self.hosts cores = int(quietRun('nproc')) # number of processes to run a while loop on per host num_procs = int(ceil(cores * cpu)) pids = {} for h in hosts: pids[h] = [] for _core in range(num_procs): h.cmd('while true; do a=1; done &') pids[h].append(h.cmd('echo $!').strip()) outputs = {} time = {} # get the initial cpu time for each host for host in hosts: outputs[host] = [] with open('/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r') as f: time[host] = float(f.read()) for _ in range(duration): sleep(1) for host in hosts: with open('/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r') as f: readTime = float(f.read()) outputs[host].append( ((readTime - time[host]) / 1000000000) / cores * 100) time[host] = readTime for h, pids in pids.items(): for pid in pids: h.cmd('kill -9 %s' % pid) cpu_fractions = [] for _host, outputs in outputs.items(): for pct in outputs: cpu_fractions.append(pct) output('*** Results: %s\n' % cpu_fractions) return cpu_fractions
def config( self, **params ): """Configure the NAT and iptables""" super( NAT, self).config( **params ) if not self.localIntf: self.localIntf = self.defaultIntf() if self.flush: self.cmd( 'sysctl net.ipv4.ip_forward=0' ) self.cmd( 'iptables -F' ) self.cmd( 'iptables -t nat -F' ) # Create default entries for unmatched traffic self.cmd( 'iptables -P INPUT ACCEPT' ) self.cmd( 'iptables -P OUTPUT ACCEPT' ) self.cmd( 'iptables -P FORWARD DROP' ) # Install NAT rules self.cmd( 'iptables -I FORWARD', '-i', self.localIntf, '-d', self.subnet, '-j DROP' ) self.cmd( 'iptables -A FORWARD', '-i', self.localIntf, '-s', self.subnet, '-j ACCEPT' ) self.cmd( 'iptables -A FORWARD', '-o', self.localIntf, '-d', self.subnet, '-j ACCEPT' ) self.cmd( 'iptables -t nat -A POSTROUTING', '-s', self.subnet, "'!'", '-d', self.subnet, '-j MASQUERADE' ) # Instruct the kernel to perform forwarding self.cmd( 'sysctl net.ipv4.ip_forward=1' ) # Prevent network-manager from messing with our interface # by specifying manual configuration in /etc/network/interfaces intf = self.localIntf cfile = '/etc/network/interfaces' line = '\niface %s inet manual\n' % intf config = open( cfile ).read() if ( line ) not in config: info( '*** Adding "' + line.strip() + '" to ' + cfile + '\n' ) with open( cfile, 'a' ) as f: f.write( line ) # Probably need to restart network-manager to be safe - # hopefully this won't disconnect you self.cmd( 'service network-manager restart' )
def run(self): "Run our cmdloop(), catching KeyboardInterrupt" while True: try: # Make sure no nodes are still waiting for node in self.mn.values(): while node.waiting: info('stopping', node, '\n') node.sendInt() node.waitOutput() if self.isatty(): quietRun('stty echo sane intr ^C') self.cmdloop() break except KeyboardInterrupt: # Output a message - unless it's also interrupted # pylint: disable=broad-except try: output('\nInterrupt\n') except Exception: pass
def waitConnected(self, timeout=None, delay=.5): """wait for each switch to connect to a controller, up to 5 seconds timeout: time to wait, or None to wait indefinitely delay: seconds to sleep per iteration returns: True if all switches are connected""" info('*** Waiting for switches to connect\n') time = 0 remaining = list(self.switches) while True: for switch in tuple(remaining): if switch.connected(): info('%s ' % switch) remaining.remove(switch) if not remaining: info('\n') return True if timeout is not None and time > timeout: break sleep(delay) time += delay warn('Timed out after %d seconds\n' % time) for switch in remaining: if not switch.connected(): warn('Warning: %s is not connected to a controller\n' % switch.name) else: remaining.remove(switch) return not remaining
def __init__(self, mininet, stdin=sys.stdin, script=None): """Start and run interactive or batch mode CLI mininet: Mininet network object stdin: standard input for CLI script: script to run in batch mode""" self.mn = mininet # Local variable bindings for py command self.locals = {'net': mininet} # Attempt to handle input self.stdin = stdin self.inPoller = poll() self.inPoller.register(stdin) self.inputFile = script Cmd.__init__(self) info('*** Starting CLI:\n') if self.inputFile: self.do_source(self.inputFile) return self.initReadline() self.run()
def configureRoutedControlNetwork(self, ip='192.168.123.1', prefixLen=16): """Configure a routed control network on controller and switches. For use with the user datapath only right now.""" controller = self.controllers[0] info(controller.name + ' <->') cip = ip snum = ipParse(ip) for switch in self.switches: info(' ' + switch.name) link = self.link(switch, controller, port1=0) sintf, cintf = link.intf1, link.intf2 switch.controlIntf = sintf snum += 1 while snum & 0xff in [0, 255]: snum += 1 sip = ipStr(snum) cintf.setIP(cip, prefixLen) sintf.setIP(sip, prefixLen) controller.setHostRoute(sip, cintf) switch.setHostRoute(cip, sintf) info('\n') info('*** Testing control network\n') while not cintf.isUp(): info('*** Waiting for', cintf, 'to come up\n') sleep(1) for switch in self.switches: while not sintf.isUp(): info('*** Waiting for', sintf, 'to come up\n') sleep(1) if self.ping(hosts=[switch, controller]) != 0: error('*** Error: control network test failed\n') exit(1) info('\n')
def config( self, bw=None, delay=None, jitter=None, loss=None, gro=False, txo=True, rxo=True, speedup=0, use_hfsc=False, use_tbf=False, latency_ms=None, enable_ecn=False, enable_red=False, max_queue_size=None, **params ): """Configure the port and set its properties. bw: bandwidth in b/s (e.g. '10m') delay: transmit delay (e.g. '1ms' ) jitter: jitter (e.g. '1ms') loss: loss (e.g. '1%' ) gro: enable GRO (False) txo: enable transmit checksum offload (True) rxo: enable receive checksum offload (True) speedup: experimental switch-side bw option use_hfsc: use HFSC scheduling use_tbf: use TBF scheduling latency_ms: TBF latency parameter enable_ecn: enable ECN (False) enable_red: enable RED (False) max_queue_size: queue limit parameter for netem""" # Support old names for parameters gro = not params.pop( 'disable_gro', not gro ) result = Intf.config( self, **params) def on( isOn ): "Helper method: bool -> 'on'/'off'" return 'on' if isOn else 'off' # Set offload parameters with ethool self.cmd( 'ethtool -K', self, 'gro', on( gro ), 'tx', on( txo ), 'rx', on( rxo ) ) # Optimization: return if nothing else to configure # Question: what happens if we want to reset things? if ( bw is None and not delay and not loss and max_queue_size is None ): return # Clear existing configuration tcoutput = self.tc( '%s qdisc show dev %s' ) if "priomap" not in tcoutput and "noqueue" not in tcoutput: cmds = [ '%s qdisc del dev %s root' ] else: cmds = [] # Bandwidth limits via various methods bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup, use_hfsc=use_hfsc, use_tbf=use_tbf, latency_ms=latency_ms, enable_ecn=enable_ecn, enable_red=enable_red ) cmds += bwcmds # Delay/jitter/loss/max_queue_size using netem delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter, loss=loss, max_queue_size=max_queue_size, parent=parent ) cmds += delaycmds # Ugly but functional: display configuration info stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + ( [ '%s delay' % delay ] if delay is not None else [] ) + ( [ '%s jitter' % jitter ] if jitter is not None else [] ) + ( ['%.5f%% loss' % loss ] if loss is not None else [] ) + ( [ 'ECN' ] if enable_ecn else [ 'RED' ] if enable_red else [] ) ) info( '(' + ' '.join( stuff ) + ') ' ) # Execute all the commands in our node debug("at map stage w/cmds: %s\n" % cmds) tcoutputs = [ self.tc(cmd) for cmd in cmds ] for output in tcoutputs: if output != '': error( "*** Error: %s" % output ) debug( "cmds:", cmds, '\n' ) debug( "outputs:", tcoutputs, '\n' ) result[ 'tcoutputs'] = tcoutputs result[ 'parent' ] = parent return result
def start(self): "Start controller and switches." if not self.built: self.build() info('*** Starting controller\n') for controller in self.controllers: info(controller.name + ' ') controller.start() info('\n') info('*** Starting %s switches\n' % len(self.switches)) for switch in self.switches: info(switch.name + ' ') switch.start(self.controllers) started = {} for swclass, switches in groupby( sorted(self.switches, key=lambda s: str(type(s))), type): switches = tuple(switches) if hasattr(swclass, 'batchStartup'): success = swclass.batchStartup(switches) started.update({s: s for s in success}) info('\n') if self.waitConn: self.waitConnected()
def buildFromTopo(self, topo=None): """Build mininet from a topology object At the end of this function, everything should be connected and up.""" # Possibly we should clean up here and/or validate # the topo if self.cleanup: pass info('*** Creating network\n') if not self.controllers and self.controller: # Add a default controller info('*** Adding controller\n') classes = self.controller if not isinstance(classes, list): classes = [classes] for i, cls in enumerate(classes): # Allow Controller objects because nobody understands partial() if isinstance(cls, Controller): self.addController(cls) else: self.addController('c%d' % i, cls) info('*** Adding hosts:\n') for hostName in topo.hosts(): self.addHost(hostName, **topo.nodeInfo(hostName)) info(hostName + ' ') info('\n*** Adding switches:\n') for switchName in topo.switches(): # A bit ugly: add batch parameter if appropriate params = topo.nodeInfo(switchName) cls = params.get('cls', self.switch) if hasattr(cls, 'batchStartup'): params.setdefault('batch', True) self.addSwitch(switchName, **params) info(switchName + ' ') info('\n*** Adding links:\n') for srcName, dstName, params in topo.links(sort=True, withInfo=True): self.addLink(**params) info('(%s, %s) ' % (srcName, dstName)) info('\n')
def sh( cmd ): "Print a command and send it to the shell" info( cmd + '\n' ) result = Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ] return decode( result )
def cleanup( cls): """Clean up junk which might be left over from old runs; do fast stuff before slow dp and link removal!""" info( "*** Removing excess controllers/ofprotocols/ofdatapaths/" "pings/noxes\n" ) zombies = ( 'controller ofprotocol ofdatapath ping nox_core' 'lt-nox_core ovs-openflowd ovs-controller' 'ovs-testcontroller udpbwtest vdlocalmnexec ivs ryu-manager' ) # Note: real zombie processes can't actually be killed, since they # are already (un)dead. Then again, # you can't connect to them either, so they're mostly harmless. # Send SIGTERM first to give processes a chance to shutdown cleanly. sh( 'killall ' + zombies + ' 2> /dev/null' ) time.sleep( 1 ) sh( 'killall -9 ' + zombies + ' 2> /dev/null' ) # And kill off sudo vdlocalmnexec sh( 'pkill -9 -f "sudo vdlocalmnexec"') info( "*** Removing junk from /tmp\n" ) sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' ) info( "*** Removing old X11 tunnels\n" ) cleanUpScreens() info( "*** Removing excess kernel datapaths\n" ) dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines() for dp in dps: if dp: sh( 'dpctl deldp ' + dp ) info( "*** Removing OVS datapaths\n" ) dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines() if dps: sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp for dp in dps if dp ) ) # And in case the above didn't work... dps = sh( "ovs-vsctl --timeout=1 list-br" ).strip().splitlines() for dp in dps: sh( 'ovs-vsctl del-br ' + dp ) info( "*** Removing all links of the pattern foo-ethX\n" ) links = sh( "ip link show | " "egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines() # Delete blocks of links n = 1000 # chunk size for i in range( 0, len( links ), n ): cmd = ';'.join( 'ip link del %s' % link for link in links[ i : i + n ] ) sh( '( %s ) 2> /dev/null' % cmd ) if 'tap9' in sh( 'ip link show' ): info( "*** Removing tap9 - assuming it's from cluster edition\n" ) sh( 'ip link del tap9' ) info( "*** Killing stale mininet node processes\n" ) killprocs( 'mininet:' ) info( "*** Shutting down stale tunnels\n" ) killprocs( 'Tunnel=Ethernet' ) killprocs( '.ssh/mn') sh( 'rm -f ~/.ssh/mn/*' ) # Call any additional cleanup code if necessary for callback in cls.callbacks: callback() info( "*** Cleanup complete.\n" )
def stop(self): "Stop the controller(s), switches and hosts" info('*** Stopping %i controllers\n' % len(self.controllers)) for controller in self.controllers: info(controller.name + ' ') controller.stop() info('\n') if self.terms: info('*** Stopping %i terms\n' % len(self.terms)) self.stopXterms() info('*** Stopping %i links\n' % len(self.links)) for link in self.links: info('.') link.stop() info('\n') info('*** Stopping %i switches\n' % len(self.switches)) stopped = {} for swclass, switches in groupby( sorted(self.switches, key=lambda s: str(type(s))), type): switches = tuple(switches) if hasattr(swclass, 'batchShutdown'): success = swclass.batchShutdown(switches) stopped.update({s: s for s in success}) for switch in self.switches: info(switch.name + ' ') if switch not in stopped: switch.stop() switch.terminate() info('\n') info('*** Stopping %i hosts\n' % len(self.hosts)) for host in self.hosts: info(host.name + ' ') host.terminate() info('\n*** Done\n')