| """ |
| |
| Mininet: A simple networking testbed for OpenFlow/SDN! |
| |
| author: Bob Lantz (rlantz@cs.stanford.edu) |
| author: Brandon Heller (brandonh@stanford.edu) |
| |
| Mininet creates scalable OpenFlow test networks by using |
| process-based virtualization and network namespaces. |
| |
| Simulated hosts are created as processes in separate network |
| namespaces. This allows a complete OpenFlow network to be simulated on |
| top of a single Linux kernel. |
| |
| Each host has: |
| |
| A virtual console (pipes to a shell) |
| A virtual interfaces (half of a veth pair) |
| A parent shell (and possibly some child processes) in a namespace |
| |
| Hosts have a network interface which is configured via ifconfig/ip |
| link/etc. |
| |
| This version supports both the kernel and user space datapaths |
| from the OpenFlow reference implementation (openflowswitch.org) |
| as well as OpenVSwitch (openvswitch.org.) |
| |
| In kernel datapath mode, the controller and switches are simply |
| processes in the root namespace. |
| |
| Kernel OpenFlow datapaths are instantiated using dpctl(8), and are |
| attached to the one side of a veth pair; the other side resides in the |
| host namespace. In this mode, switch processes can simply connect to the |
| controller via the loopback interface. |
| |
| In user datapath mode, the controller and switches can be full-service |
| nodes that live in their own network namespaces and have management |
| interfaces and IP addresses on a control network (e.g. 192.168.123.1, |
| currently routed although it could be bridged.) |
| |
| In addition to a management interface, user mode switches also have |
| several switch interfaces, halves of veth pairs whose other halves |
| reside in the host nodes that the switches are connected to. |
| |
| Consistent, straightforward naming is important in order to easily |
| identify hosts, switches and controllers, both from the CLI and |
| from program code. Interfaces are named to make it easy to identify |
| which interfaces belong to which node. |
| |
| The basic naming scheme is as follows: |
| |
| Host nodes are named h1-hN |
| Switch nodes are named s1-sN |
| Controller nodes are named c0-cN |
| Interfaces are named {nodename}-eth0 .. {nodename}-ethN |
| |
| Note: If the network topology is created using mininet.topo, then |
| node numbers are unique among hosts and switches (e.g. we have |
| h1..hN and SN..SN+M) and also correspond to their default IP addresses |
| of 10.x.y.z/8 where x.y.z is the base-256 representation of N for |
| hN. This mapping allows easy determination of a node's IP |
| address from its name, e.g. h1 -> 10.0.0.1, h257 -> 10.0.1.1. |
| |
| Note also that 10.0.0.1 can often be written as 10.1 for short, e.g. |
| "ping 10.1" is equivalent to "ping 10.0.0.1". |
| |
| Currently we wrap the entire network in a 'mininet' object, which |
| constructs a simulated network based on a network topology created |
| using a topology object (e.g. LinearTopo) from mininet.topo or |
| mininet.topolib, and a Controller which the switches will connect |
| to. Several configuration options are provided for functions such as |
| automatically setting MAC addresses, populating the ARP table, or |
| even running a set of terminals to allow direct interaction with nodes. |
| |
| After the network is created, it can be started using start(), and a |
| variety of useful tasks maybe performed, including basic connectivity |
| and bandwidth tests and running the mininet CLI. |
| |
| Once the network is up and running, test code can easily get access |
| to host and switch objects which can then be used for arbitrary |
| experiments, typically involving running a series of commands on the |
| hosts. |
| |
| After all desired tests or activities have been completed, the stop() |
| method may be called to shut down the network. |
| |
| """ |
| |
| import os |
| import re |
| import select |
| import signal |
| from time import sleep |
| |
| from mininet.cli import CLI |
| from mininet.log import info, error, debug, output |
| from mininet.node import Host, OVSKernelSwitch, Controller |
| from mininet.link import Link, Intf |
| from mininet.util import quietRun, fixLimits, numCores, ensureRoot |
| from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd, nextCCNnet |
| from mininet.term import cleanUpScreens, makeTerms |
| import pdb |
| |
| # Mininet version: should be consistent with README and LICENSE |
| VERSION = "2.0.0" |
| |
| class Mininet( object ): |
| "Network emulation with hosts spawned in network namespaces." |
| |
| def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, |
| controller=Controller, link=Link, intf=Intf, |
| build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8', |
| inNamespace=False, |
| autoSetMacs=False, autoStaticArp=False, autoPinCpus=False, |
| listenPort=None ): |
| """Create Mininet object. |
| topo: Topo (topology) object or None |
| switch: default Switch class |
| host: default Host class/constructor |
| controller: default Controller class/constructor |
| link: default Link class/constructor |
| intf: default Intf class/constructor |
| ipBase: base IP address for hosts, |
| build: build now from topo? |
| xterms: if build now, spawn xterms? |
| cleanup: if build now, cleanup before creating? |
| inNamespace: spawn switches and controller in net namespaces? |
| autoSetMacs: set MAC addrs automatically like IP addresses? |
| autoStaticArp: set all-pairs static MAC addrs? |
| autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)? |
| listenPort: base listening port to open; will be incremented for |
| each additional switch in the net if inNamespace=False""" |
| self.topo = topo |
| self.switch = switch |
| self.host = host |
| self.controller = controller |
| self.link = link |
| self.intf = intf |
| self.ipBase = ipBase |
| self.ipBaseNum, self.prefixLen = netParse( self.ipBase ) |
| self.nextIP = 1 # start for address allocation |
| self.ccnNetBase = '1.0.0.0' |
| self.inNamespace = inNamespace |
| self.xterms = xterms |
| self.cleanup = cleanup |
| self.autoSetMacs = autoSetMacs |
| self.autoStaticArp = autoStaticArp |
| self.autoPinCpus = autoPinCpus |
| self.numCores = numCores() |
| self.nextCore = 0 # next core for pinning hosts to CPUs |
| self.listenPort = listenPort |
| |
| self.hosts = [] |
| self.switches = [] |
| self.controllers = [] |
| |
| self.nameToNode = {} # name to Node (Host/Switch) objects |
| |
| self.terms = [] # list of spawned xterm processes |
| |
| Mininet.init() # Initialize Mininet if necessary |
| |
| self.built = False |
| if topo and build: |
| self.build() |
| |
| def isNdnhost(self, node): |
| if 'fib' in node.params: |
| return True |
| else: |
| return False |
| |
| def addHost( self, name, cls=None, **params ): |
| """Add host. |
| name: name of host to add |
| cls: custom host class/constructor (optional) |
| params: parameters for host |
| returns: added host""" |
| # Default IP and MAC addresses |
| #pdb.set_trace() |
| #defaults = { 'ip': ipAdd( self.nextIP, |
| #ipBaseNum=self.ipBaseNum, |
| #prefixLen=self.prefixLen ) + |
| #'/%s' % self.prefixLen } |
| #if self.autoSetMacs: |
| #defaults[ 'mac'] = macColonHex( self.nextIP ) |
| #if self.autoPinCpus: |
| #defaults[ 'cores' ] = self.nextCore |
| #self.nextCore = ( self.nextCore + 1 ) % self.numCores |
| #self.nextIP += 1 |
| defaults = {} |
| defaults.update( params ) |
| |
| if not cls: |
| cls = self.host |
| h = cls( name, **defaults ) |
| self.hosts.append( h ) |
| self.nameToNode[ name ] = h |
| return h |
| |
| def addSwitch( self, name, cls=None, **params ): |
| """Add switch. |
| name: name of switch to add |
| cls: custom switch class/constructor (optional) |
| returns: added switch |
| side effect: increments listenPort ivar .""" |
| defaults = { 'listenPort': self.listenPort, |
| 'inNamespace': self.inNamespace } |
| defaults.update( params ) |
| if not cls: |
| cls = self.switch |
| sw = cls( name, **defaults ) |
| if not self.inNamespace and self.listenPort: |
| self.listenPort += 1 |
| self.switches.append( sw ) |
| self.nameToNode[ name ] = sw |
| return sw |
| |
| def addController( self, name='c0', controller=None, **params ): |
| """Add controller. |
| controller: Controller class""" |
| if not controller: |
| controller = self.controller |
| controller_new = controller( name, **params ) |
| if controller_new: # allow controller-less setups |
| self.controllers.append( controller_new ) |
| self.nameToNode[ name ] = controller_new |
| return controller_new |
| |
| # BL: is this better than just using nameToNode[] ? |
| # Should it have a better name? |
| def getNodeByName( self, *args ): |
| "Return node(s) with given name(s)" |
| if len( args ) == 1: |
| return self.nameToNode[ args[ 0 ] ] |
| return [ self.nameToNode[ n ] for n in args ] |
| |
| def get( self, *args ): |
| "Convenience alias for getNodeByName" |
| return self.getNodeByName( *args ) |
| |
| def addLink( self, node1, node2, port1=None, port2=None, |
| cls=None, **params ): |
| """"Add a link from node1 to node2 |
| node1: source node |
| node2: dest node |
| port1: source port |
| port2: dest port |
| returns: link object""" |
| defaults = { 'port1': port1, |
| 'port2': port2, |
| 'intf': self.intf } |
| defaults.update( params ) |
| if not cls: |
| cls = self.link |
| return cls( node1, node2, **defaults ) |
| |
| def configHosts( self ): |
| "Configure a set of hosts." |
| for host in self.hosts: |
| info( host.name + ' ' ) |
| intf = host.defaultIntf() |
| if self.isNdnhost(host): |
| host.configNdn() |
| host.configDefault(ip=None,mac=None) |
| elif intf: |
| host.configDefault( defaultRoute=intf ) |
| 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. |
| host.cmd( 'ifconfig lo up' ) |
| info( '\n' ) |
| |
| 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: |
| # Add a default controller |
| #info( '*** Adding controller\n' ) |
| #classes = self.controller |
| #if type( classes ) is not list: |
| # classes = [ classes ] |
| #for i, cls in enumerate( classes ): |
| # self.addController( 'c%d' % i, cls ) |
| |
| info( '*** Adding hosts:\n' ) |
| for hostName in topo.hosts(): |
| #pdb.set_trace() |
| self.addHost( hostName, **topo.nodeInfo( hostName ) ) |
| info( hostName + ' ' ) |
| |
| info( '\n*** Adding switches:\n' ) |
| for switchName in topo.switches(): |
| self.addSwitch( switchName, **topo.nodeInfo( switchName) ) |
| info( switchName + ' ' ) |
| |
| info( '\n*** Adding links:\n' ) |
| for srcName, dstName in topo.links(sort=True): |
| src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ] |
| params = topo.linkInfo( srcName, dstName ) |
| srcPort, dstPort = topo.port( srcName, dstName ) |
| self.addLink( src, dst, srcPort, dstPort, **params ) |
| if self.isNdnhost(src): |
| src.setIP(ipStr(ipParse(self.ccnNetBase) + 1) + '/30', intf=src.name + '-eth' + str(srcPort)) |
| dst.setIP(ipStr(ipParse(self.ccnNetBase) + 2) + '/30', intf=dst.name + '-eth' + str(dstPort)) |
| self.ccnNetBase=nextCCNnet(self.ccnNetBase) |
| |
| info( '(%s, %s) ' % ( src.name, dst.name ) ) |
| |
| info( '\n' ) |
| |
| |
| def configureControlNetwork( self ): |
| "Control net config hook: override in subclass" |
| raise Exception( 'configureControlNetwork: ' |
| 'should be overriden in subclass', self ) |
| |
| 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 startTerms( self ): |
| "Start a terminal for each node." |
| 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 stopXterms( self ): |
| "Kill each xterm." |
| for term in self.terms: |
| os.kill( term.pid, signal.SIGKILL ) |
| cleanUpScreens() |
| |
| def staticArp( self ): |
| "Add all-pairs ARP entries to remove the need to handle broadcast." |
| for src in self.hosts: |
| for dst in self.hosts: |
| if src != dst: |
| src.setARP( ip=dst.IP(), mac=dst.MAC() ) |
| |
| def start( self ): |
| "Start controller and switches." |
| if not self.built: |
| self.build() |
| info( '*** Starting controller\n' ) |
| for controller in self.controllers: |
| controller.start() |
| info( '*** Starting %s switches\n' % len( self.switches ) ) |
| for switch in self.switches: |
| info( switch.name + ' ') |
| switch.start( self.controllers ) |
| info( '\n' ) |
| |
| def stop( self ): |
| "Stop the controller(s), switches and hosts" |
| if self.terms: |
| info( '*** Stopping %i terms\n' % len( self.terms ) ) |
| self.stopXterms() |
| info( '*** Stopping %i hosts\n' % len( self.hosts ) ) |
| for host in self.hosts: |
| info( host.name + ' ' ) |
| host.terminate() |
| info( '\n' ) |
| info( '*** Stopping %i switches\n' % len( self.switches ) ) |
| for switch in self.switches: |
| info( switch.name + ' ' ) |
| switch.stop() |
| info( '\n' ) |
| info( '*** Stopping %i controllers\n' % len( self.controllers ) ) |
| for controller in self.controllers: |
| info( controller.name + ' ' ) |
| controller.stop() |
| info( '\n*** Done\n' ) |
| |
| 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 monitor( self, hosts=None, timeoutms=-1 ): |
| """Monitor a set of hosts (or all hosts by default), |
| and return their output, a line at a time. |
| hosts: (optional) set of hosts to monitor |
| timeoutms: (optional) timeout value in ms |
| returns: iterator which returns host, line""" |
| if hosts is None: |
| hosts = self.hosts |
| poller = select.poll() |
| Node = hosts[ 0 ] # so we can call class method fdToNode |
| for host in hosts: |
| poller.register( host.stdout ) |
| while True: |
| ready = poller.poll( timeoutms ) |
| for fd, event in ready: |
| host = Node.fdToNode( fd ) |
| if event & select.POLLIN: |
| line = host.readline() |
| if line is not None: |
| yield host, line |
| # Return if non-blocking |
| if not ready and timeoutms >= 0: |
| yield None, None |
| |
| # XXX These test methods should be moved out of this class. |
| # Probably we should create a tests.py for them |
| |
| @staticmethod |
| def _parsePing( pingOutput ): |
| "Parse ping output and return packets sent, received." |
| # Check for downed link |
| if 'connect: Network is unreachable' in pingOutput: |
| return (1, 0) |
| r = r'(\d+) packets transmitted, (\d+) received' |
| m = re.search( r, pingOutput ) |
| if m is None: |
| error( '*** Error: could not parse ping output: %s\n' % |
| pingOutput ) |
| return (1, 0) |
| sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) |
| return sent, received |
| |
| def ping( self, hosts=None, timeout=None ): |
| """Ping between all specified hosts. |
| hosts: list of hosts |
| timeout: time to wait for a response, as string |
| returns: ploss packet loss percentage""" |
| # should we check if running? |
| packets = 0 |
| lost = 0 |
| ploss = None |
| if not hosts: |
| hosts = self.hosts |
| output( '*** Ping: testing ping reachability\n' ) |
| for node in hosts: |
| output( '%s -> ' % node.name ) |
| for dest in hosts: |
| if node != dest: |
| opts = '' |
| if timeout: |
| opts = '-W %s' % timeout |
| result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) ) |
| sent, received = self._parsePing( result ) |
| packets += sent |
| if received > sent: |
| error( '*** Error: received too many packets' ) |
| error( '%s' % result ) |
| node.cmdPrint( 'route' ) |
| exit( 1 ) |
| lost += sent - received |
| output( ( '%s ' % dest.name ) if received else 'X ' ) |
| output( '\n' ) |
| ploss = 100 * lost / packets |
| output( "*** Results: %i%% dropped (%d/%d lost)\n" % |
| ( ploss, lost, packets ) ) |
| return ploss |
| |
| @staticmethod |
| def _parsePingFull( pingOutput ): |
| "Parse ping output and return all data." |
| # Check for downed link |
| if 'connect: Network is unreachable' in pingOutput: |
| return (1, 0) |
| r = r'(\d+) packets transmitted, (\d+) received' |
| m = re.search( r, pingOutput ) |
| if m is None: |
| error( '*** Error: could not parse ping output: %s\n' % |
| pingOutput ) |
| return (1, 0, 0, 0, 0, 0) |
| sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) |
| r = r'rtt min/avg/max/mdev = ' |
| r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms' |
| m = re.search( r, pingOutput ) |
| rttmin = float( m.group( 1 ) ) |
| rttavg = float( m.group( 2 ) ) |
| rttmax = float( m.group( 3 ) ) |
| rttdev = float( m.group( 4 ) ) |
| return sent, received, rttmin, rttavg, rttmax, rttdev |
| |
| def pingFull( self, hosts=None, timeout=None ): |
| """Ping between all specified hosts and return all data. |
| hosts: list of hosts |
| timeout: time to wait for a response, as string |
| returns: all ping data; see function body.""" |
| # should we check if running? |
| # Each value is a tuple: (src, dsd, [all ping outputs]) |
| all_outputs = [] |
| if not hosts: |
| hosts = self.hosts |
| output( '*** Ping: testing ping reachability\n' ) |
| for node in hosts: |
| output( '%s -> ' % node.name ) |
| for dest in hosts: |
| if node != dest: |
| opts = '' |
| if timeout: |
| opts = '-W %s' % timeout |
| result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) ) |
| outputs = self._parsePingFull( result ) |
| sent, received, rttmin, rttavg, rttmax, rttdev = outputs |
| all_outputs.append( (node, dest, outputs) ) |
| output( ( '%s ' % dest.name ) if received else 'X ' ) |
| output( '\n' ) |
| output( "*** Results: \n" ) |
| for outputs in all_outputs: |
| src, dest, ping_outputs = outputs |
| sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs |
| output( " %s->%s: %s/%s, " % (src, dest, sent, received ) ) |
| output( "rtt min/avg/max/mdev %0.3f/%0.3f/%0.3f/%0.3f ms\n" % |
| (rttmin, rttavg, rttmax, rttdev) ) |
| return all_outputs |
| |
| def pingAll( self ): |
| """Ping between all hosts. |
| returns: ploss packet loss percentage""" |
| return self.ping() |
| |
| def pingPair( self ): |
| """Ping between first two hosts, useful for testing. |
| returns: ploss packet loss percentage""" |
| hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] |
| return self.ping( hosts=hosts ) |
| |
| def pingAllFull( self ): |
| """Ping between all hosts. |
| returns: ploss packet loss percentage""" |
| return self.pingFull() |
| |
| def pingPairFull( self ): |
| """Ping between first two hosts, useful for testing. |
| returns: ploss packet loss percentage""" |
| hosts = [ self.hosts[ 0 ], self.hosts[ 1 ] ] |
| return self.pingFull( hosts=hosts ) |
| |
| @staticmethod |
| def _parseIperf( iperfOutput ): |
| """Parse iperf output and return bandwidth. |
| iperfOutput: string |
| returns: result string""" |
| r = r'([\d\.]+ \w+/sec)' |
| m = re.findall( r, iperfOutput ) |
| if m: |
| return m[-1] |
| else: |
| # was: raise Exception(...) |
| error( 'could not parse iperf output: ' + iperfOutput ) |
| return '' |
| |
| # XXX This should be cleaned up |
| |
| def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ): |
| """Run iperf between two hosts. |
| hosts: list of hosts; if None, uses opposite hosts |
| l4Type: string, one of [ TCP, UDP ] |
| returns: results two-element array of server and client speeds""" |
| if not quietRun( 'which telnet' ): |
| error( 'Cannot find telnet in $PATH - required for iperf test' ) |
| return |
| if not hosts: |
| hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ] |
| else: |
| assert len( hosts ) == 2 |
| client, server = hosts |
| output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' ) |
| output( "%s and %s\n" % ( client.name, server.name ) ) |
| server.cmd( 'killall -9 iperf' ) |
| iperfArgs = 'iperf ' |
| bwArgs = '' |
| if l4Type == 'UDP': |
| iperfArgs += '-u ' |
| bwArgs = '-b ' + udpBw + ' ' |
| elif l4Type != 'TCP': |
| raise Exception( 'Unexpected l4 type: %s' % l4Type ) |
| server.sendCmd( iperfArgs + '-s', printPid=True ) |
| servout = '' |
| while server.lastPid is None: |
| servout += server.monitor() |
| if l4Type == 'TCP': |
| while 'Connected' not in client.cmd( |
| 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): |
| output('waiting for iperf to start up...') |
| sleep(.5) |
| cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + |
| bwArgs ) |
| debug( 'Client output: %s\n' % cliout ) |
| server.sendInt() |
| servout += server.waitOutput() |
| debug( 'Server output: %s\n' % servout ) |
| result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ] |
| if l4Type == 'UDP': |
| result.insert( 0, udpBw ) |
| output( '*** Results: %s\n' % result ) |
| return result |
| |
| 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 |
| returns a single list of measured CPU fractions as floats. |
| """ |
| pct = cpu * 100 |
| info('*** Testing CPU %.0f%% bandwidth limit\n' % pct) |
| hosts = self.hosts |
| for h in hosts: |
| h.cmd( 'while true; do a=1; done &' ) |
| pids = [h.cmd( 'echo $!' ).strip() for h in hosts] |
| pids_str = ",".join(["%s" % pid for pid in pids]) |
| cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str |
| # It's a shame that this is what pylint prefers |
| outputs = [] |
| for _ in range( duration ): |
| sleep( 1 ) |
| outputs.append( quietRun( cmd ).strip() ) |
| for h in hosts: |
| h.cmd( 'kill %1' ) |
| cpu_fractions = [] |
| for test_output in outputs: |
| # Split by line. Ignore first line, which looks like this: |
| # PID %CPU COMMAND\n |
| for line in test_output.split('\n')[1:]: |
| r = r'\d+\s*(\d+\.\d+)' |
| m = re.search( r, line ) |
| if m is None: |
| error( '*** Error: could not extract CPU fraction: %s\n' % |
| line ) |
| return None |
| cpu_fractions.append( float( m.group( 1 ) ) ) |
| output( '*** Results: %s\n' % cpu_fractions ) |
| return cpu_fractions |
| |
| # BL: I think this can be rewritten now that we have |
| # a real link class. |
| def configLinkStatus( self, src, dst, status ): |
| """Change status of src <-> dst links. |
| src: node name |
| dst: node name |
| status: string {up, down}""" |
| if src not in self.nameToNode: |
| error( 'src not in network: %s\n' % src ) |
| elif dst not in self.nameToNode: |
| error( 'dst not in network: %s\n' % dst ) |
| else: |
| if type( src ) is str: |
| src = self.nameToNode[ src ] |
| if type( dst ) is str: |
| dst = self.nameToNode[ dst ] |
| connections = src.connectionsTo( dst ) |
| if len( connections ) == 0: |
| error( 'src and dst not connected: %s %s\n' % ( src, dst) ) |
| for srcIntf, dstIntf in connections: |
| result = srcIntf.ifconfig( status ) |
| if result: |
| error( 'link src status change failed: %s\n' % result ) |
| result = dstIntf.ifconfig( status ) |
| if result: |
| error( 'link dst status change failed: %s\n' % result ) |
| |
| def interact( self ): |
| "Start network and run our simple CLI." |
| self.start() |
| result = CLI( self ) |
| self.stop() |
| return result |
| |
| inited = False |
| |
| @classmethod |
| def init( cls ): |
| "Initialize Mininet" |
| if cls.inited: |
| return |
| ensureRoot() |
| fixLimits() |
| cls.inited = True |
| |
| |
| class MininetWithControlNet( Mininet ): |
| |
| """Control network support: |
| |
| Create an explicit control network. Currently this is only |
| used/usable with the user datapath. |
| |
| Notes: |
| |
| 1. If the controller and switches are in the same (e.g. root) |
| namespace, they can just use the loopback connection. |
| |
| 2. If we can get unix domain sockets to work, we can use them |
| instead of an explicit control network. |
| |
| 3. Instead of routing, we could bridge or use 'in-band' control. |
| |
| 4. Even if we dispense with this in general, it could still be |
| useful for people who wish to simulate a separate control |
| network (since real networks may need one!) |
| |
| 5. Basically nobody ever used this code, so it has been moved |
| into its own class. |
| |
| 6. Ultimately we may wish to extend this to allow us to create a |
| control network which every node's control interface is |
| attached to.""" |
| |
| def configureControlNetwork( self ): |
| "Configure control network." |
| self.configureRoutedControlNetwork() |
| |
| # We still need to figure out the right way to pass |
| # in the control network location. |
| |
| 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' ) |