diff --git a/examples/ripcordtest.py b/examples/ripcordtest.py index 3491851c313d4720167a44fd472f2976219791a6..7ac257819aa427fdaba5c8e931262e1563862e31 100755 --- a/examples/ripcordtest.py +++ b/examples/ripcordtest.py @@ -1,483 +1,16 @@ #!/usr/bin/python - -'''Run a FatTree network from the Ripcord project. - -For verbose printout, set LOG_LEVEL_DEFAULT in mininet.py to logging.INFO. -''' - -import os -import re -from subprocess import call -import sys -from time import sleep +'''Run a FatTree network from the Ripcord project.''' from ripcord.topo import FatTreeTopo -from mininet.net import init -from mininet.node import Switch, Controller, Host -from mininet.logging_mod import lg, set_loglevel -from mininet.util import make_veth_pair, move_intf, retry, quietRun -from mininet.util import MOVEINTF_DELAY - - -class Mininet(object): - '''Network emulation with hosts spawned in network namespaces.''' - - def __init__(self, topo, switch, host, controller, cparams, - build = True, xterms = False, cleanup = False, - in_namespace = False, switch_is_kernel = True): - '''Create Mininet object. - - @param topo Topo object - @param switch Switch class - @param host Host class - @param controller Controller class - @param cparams ControllerParams object - @param now build now? - @param xterms if build now, spawn xterms? - @param cleanup if build now, cleanup before creating? - @param in_namespace spawn switches and hosts in their own namespace? - ''' - self.topo = topo - self.switch = switch - self.host = host - self.controller = controller - self.cparams = cparams - self.nodes = {} # dpid to Node{Host, Switch} objects - self.controllers = {} # controller name to Controller objects - self.dps = 0 # number of created kernel datapaths - self.in_namespace = in_namespace - self.switch_is_kernel = switch_is_kernel - - self.kernel = True #temporary! - - if build: - self.build(xterms, cleanup) - - def _add_host(self, dpid): - '''Add host. - - @param dpid DPID of host to add - ''' - host = self.host('h_' + self.topo.name(dpid)) - # for now, assume one interface per host. - host.intfs.append('h_' + self.topo.name(dpid) + '-eth0') - self.nodes[dpid] = host - #lg.info('%s ' % host.name) - - def _add_switch(self, dpid): - ''' - @param dpid DPID of switch to add - ''' - sw = None - if self.switch_is_kernel: - sw = self.switch('s_' + self.topo.name(dpid), 'nl:' + str(self.dps)) - self.dps += 1 - else: - sw = self.switch('s_' + self.topo.name(dpid)) - self.nodes[dpid] = sw - - def _add_link(self, src, dst): - '''Add link. - - @param src source DPID - @param dst destination DPID - ''' - src_port, dst_port = self.topo.port(src, dst) - src_node = self.nodes[src] - dst_node = self.nodes[dst] - src_intf = src_node.intfName(src_port) - dst_intf = dst_node.intfName(dst_port) - make_veth_pair(src_intf, dst_intf) - src_node.intfs.append(src_intf) - dst_node.intfs.append(dst_intf) - #lg.info('\n') - #lg.info('added intf %s to src node %x\n' % (src_intf, src)) - #lg.info('added intf %s to dst node %x\n' % (dst_intf, dst)) - if src_node.inNamespace: - #lg.info('moving src w/inNamespace set\n') - retry(3, MOVEINTF_DELAY, move_intf, src_intf, src_node) - if dst_node.inNamespace: - #lg.info('moving dst w/inNamespace set\n') - retry(3, MOVEINTF_DELAY, move_intf, dst_intf, dst_node) - src_node.connection[src_intf] = (dst_node, dst_intf) - dst_node.connection[dst_intf] = (src_node, src_intf) - - def _add_controller(self, controller): - '''Add controller. - - @param controller Controller class - ''' - controller = self.controller('c0', kernel = self.kernel) - self.controllers['c0'] = controller - - # Control network support: - # - # Create an explicit control network. Currently this is only - # used by the user datapath configuration. - # - # Notes: - # - # 1. If the controller and switches are in the same (e.g. root) - # namespace, they can just use the loopback connection. - # We may wish to do this for the user datapath as well as the - # kernel datapath. - # - # 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!) - - def _configureControlNetwork(self): - '''Configure control network.''' - self._configureRoutedControlNetwork() - - def _configureRoutedControlNetwork(self): - '''Configure a routed control network on controller and switches. - - For use with the user datapath only right now. - - @todo(brandonh) Test this code and verify that user-space works! - ''' - # params were: controller, switches, ips - - controller = self.controllers['c0'] - lg.info('%s <-> ' % controller.name) - for switch_dpid in self.topo.switches(): - switch = self.nodes[switch_dpid] - lg.info('%s ' % switch.name) - sip = self.topo.ip(switch_dpid)#ips.next() - sintf = switch.intfs[0] - node, cintf = switch.connection[sintf] - if node != controller: - lg.error('*** Error: switch %s not connected to correct' - 'controller' % - switch.name) - exit(1) - controller.setIP(cintf, self.cparams.ip, '/' + - self.cparams.subnet_size) - switch.setIP(sintf, sip, '/' + self.cparams.subnet_size) - controller.setHostRoute(sip, cintf) - switch.setHostRoute(self.cparams.ip, sintf) - lg.info('\n') - lg.info('*** Testing control network\n') - while not controller.intfIsUp(controller.intfs[0]): - lg.info('*** Waiting for %s to come up\n', controller.intfs[0]) - sleep(1) - for switch_dpid in self.topo.switches(): - switch = self.nodes[switch_dpid] - while not switch.intfIsUp(switch.intfs[0]): - lg.info('*** Waiting for %s to come up\n' % switch.intfs[0]) - sleep(1) - if self.ping_test(hosts=[switch, controller]) != 0: - lg.error('*** Error: control network test failed\n') - exit(1) - lg.info('\n') - - def _config_hosts( self ): - '''Configure a set of hosts.''' - # params were: hosts, ips - for host_dpid in self.topo.hosts(): - host = self.nodes[host_dpid] - hintf = host.intfs[0] - host.setIP(hintf, self.topo.ip(host_dpid), - '/' + str(self.cparams.subnet_size)) - host.setDefaultRoute(hintf) - # You're low priority, dude! - quietRun('renice +18 -p ' + repr(host.pid)) - lg.info('%s ', host.name) - lg.info('\n') - - def build(self, xterms, cleanup): - '''Build mininet. - - At the end of this function, everything should be connected and up. - - @param xterms spawn xterms on build? - @param cleanup cleanup before creating? - ''' - if cleanup: - pass # cleanup - # validate topo? - kernel = self.kernel - if kernel: - lg.info('*** Using kernel datapath\n') - else: - lg.info('*** Using user datapath\n') - lg.info('*** Adding controller\n') - self._add_controller(self.controller) - lg.info('*** Creating network\n') - lg.info('*** Adding hosts:\n') - for host in sorted(self.topo.hosts()): - self._add_host(host) - lg.info('0x%x ' % host) - lg.info('\n*** Adding switches:\n') - for switch in sorted(self.topo.switches()): - self._add_switch(switch) - lg.info('0x%x ' % switch) - lg.info('\n*** Adding edges: ') - for src, dst in sorted(self.topo.edges()): - self._add_link(src, dst) - lg.info('(0x%x, 0x%x) ' % (src, dst)) - lg.info('\n') - - if not kernel: - lg.info('*** Configuring control network\n') - self._configureControlNetwork() - - lg.info('*** Configuring hosts\n') - self._config_hosts() - - if xterms: - pass # build xterms - - def start(self): - '''Start controller and switches\n''' - lg.info('*** Starting controller\n') - self.controllers['c0'].start() - #for controller in self.controllers: - # controller.start() - lg.info('*** Starting %s switches\n' % len(self.topo.switches())) - for switch_dpid in self.topo.switches(): - switch = self.nodes[switch_dpid] - #lg.info('switch = %s' % switch) - lg.info('0x%x ' % switch_dpid) - switch.start(self.controllers['c0']) - lg.info('\n') - - def stop(self): - '''Stop the controller(s), switches and hosts\n''' - lg.info('*** Stopping %i hosts\n' % len(self.topo.hosts())) - for host_dpid in self.topo.hosts(): - host = self.nodes[host_dpid] - lg.info('%s ' % host.name) - host.terminate() - lg.info('\n') - lg.info('*** Stopping %i switches\n' % len(self.topo.switches())) - for switch_dpid in self.topo.switches(): - switch = self.nodes[switch_dpid] - lg.info('%s' % switch.name) - switch.stop() - lg.info('\n') - lg.info('*** Stopping controller\n') - #for controller in self.controllers.iteriterms(): - self.controllers['c0'].stop() - lg.info('*** Test complete\n') - - def run(self, test, **params): - '''Perform a complete start/test/stop cycle.''' - self.start() - lg.info('*** Running test\n') - result = test(self, **params) - self.stop() - return result - - @staticmethod - def _parse_ping(pingOutput): - '''Parse ping output and return packets sent, received.''' - r = r'(\d+) packets transmitted, (\d+) received' - m = re.search( r, pingOutput ) - if m == None: - lg.error('*** Error: could not parse ping output: %s\n' % - pingOutput) - exit(1) - sent, received = int(m.group(1)), int(m.group(2)) - return sent, received - - def ping_test(self, hosts = None, verbose = False): - '''Ping between all specified hosts. - - @param hosts list of host DPIDs - @param verbose verbose printing - @return ploss packet loss percentage - ''' - #self.start() - # check if running - only then, start? - packets = 0 - lost = 0 - if not hosts: - hosts = self.topo.hosts() - for node_dpid in hosts: - node = self.nodes[node_dpid] - if verbose: - lg.info('%s -> ' % node.name) - for dest_dpid in hosts: - dest = self.nodes[dest_dpid] - if node != dest: - result = node.cmd('ping -c1 ' + dest.IP()) - sent, received = self._parse_ping(result) - packets += sent - if received > sent: - lg.error('*** Error: received too many packets') - lg.error('%s' % result) - node.cmdPrint('route') - exit( 1 ) - lost += sent - received - if verbose: - lg.info(('%s ' % dest.name) if received else 'X ') - if verbose: - lg.info('\n') - ploss = 100 * lost/packets - if verbose: - lg.info('%d%% packet loss (%d/%d lost)\n' % (ploss, lost, packets)) - #flush() - #self.stop() - return ploss - - def interact(self): - '''Start network and run our simple CLI.''' - self.run(Cli) - - -class Cli(object): - '''Simple command-line interface to talk to nodes.''' - cmds = ['?', 'help', 'nodes', 'net', 'sh', 'ping_all', 'exit', \ - 'ping_pair'] #'iperf' - - def __init__(self, mininet): - self.mn = mininet - self.nodemap = {} # map names to Node objects - for node in self.mn.nodes.values(): - self.nodemap[node.name] = node - self.nodemap['c0'] = self.mn.controllers['c0'] - self.nodelist = self.nodemap.values() - self.run() - - # Commands - def help(self, args): - '''Semi-useful help for CLI.''' - help_str = 'Available commands are:' + str(self.cmds) + '\n' + \ - 'You may also send a command to a node using:\n' + \ - ' <node> command {args}\n' + \ - 'For example:\n' + \ - ' mininet> h0 ifconfig\n' + \ - '\n' + \ - 'The interpreter automatically substitutes IP ' + \ - 'addresses\n' + \ - 'for node names, so commands like\n' + \ - ' mininet> h0 ping -c1 h1\n' + \ - 'should work.\n' + \ - '\n\n' + \ - 'Interactive commands are not really supported yet,\n' + \ - 'so please limit commands to ones that do not\n' + \ - 'require user interaction and will terminate\n' + \ - 'after a reasonable amount of time.\n' - print(help_str) - - def nodes(self, args): - '''List all nodes.''' - lg.info('available nodes are: \n%s\n', - ' '.join([node.name for node in sorted(self.nodelist)])) - - def net(self, args): - '''List network connection.''' - for switch_dpid in self.mn.topo.switches(): - switch = self.mn.nodes[switch_dpid] - lg.info('%s <->', switch.name) - for intf in switch.intfs: - node, remoteIntf = switch.connection[intf] - lg.info(' %s' % node.name) - lg.info('\n') - - def sh(self, args): - '''Run an external shell command''' - call( [ 'sh', '-c' ] + args ) - - def ping_all(self, args): - '''Ping between all hosts.''' - self.mn.ping_test(verbose = True) - - def ping_pair(self, args): - '''Ping between first two hosts, useful for testing.''' - hosts_unsorted = sorted(self.mn.topo.hosts()) - hosts = [hosts_unsorted[0], hosts_unsorted[1]] - self.mn.ping_test(hosts = hosts, verbose = True) - - def run(self): - '''Read and execute commands.''' - lg.warn('*** Starting CLI:\n') - while True: - lg.warn('mininet> ') - input = sys.stdin.readline() - if input == '': - break - if input[-1] == '\n': - input = input[:-1] - cmd = input.split(' ') - first = cmd[0] - rest = cmd[1:] - if first in self.cmds and hasattr(self, first): - getattr(self, first)(rest) - elif first in self.nodemap and rest != []: - node = self.nodemap[first] - # Substitute IP addresses for node names in command - rest = [self.nodemap[arg].IP() if arg in self.nodemap else arg - for arg in rest] - rest = ' '.join(rest) - # Interactive commands don't work yet, and - # there are still issues with control-c - lg.warn('*** %s: running %s\n' % (node.name, rest)) - node.sendCmd( rest ) - while True: - try: - done, data = node.monitor() - lg.info('%s\n' % data) - if done: - break - except KeyboardInterrupt: - node.sendInt() - elif first == '': - pass - elif first in ['exit', 'quit']: - break - elif first == '?': - self.help( rest ) - else: - lg.error('CLI: unknown node or command: < %s >\n' % first) - #lg.info('*** CLI: command complete\n') - return 'exited by user command' - - -class NOXController(Controller): - '''Controller to run a NOX application.''' - def __init__(self, name, nox_args = None, **kwargs): - '''Init. - - @param name name to give controller - @param nox_args list of args to use with NOX - ''' - if not nox_args: - nox_args = ['packetdump'] - nox_core_dir = os.environ['NOX_CORE_DIR'] - if not nox_core_dir: - raise Exception('please set NOX_CORE_DIR env var\n') - Controller.__init__(self, name, - controller = nox_core_dir + '/nox_core', - cargs = '--libdir=/usr/local/lib -v -i ptcp: ' + \ - ' '.join(nox_args), - cdir = nox_core_dir, **kwargs) - - -class ControllerParams(object): - '''Container for controller IP parameters.''' - def __init__(self, ip, subnet_size): - '''Init. - - @param ip integer, controller IP - @param subnet_size integer, ex 8 for slash-8, covering 17M - ''' - self.ip = ip - self.subnet_size = subnet_size - +from mininet.logging_mod import set_loglevel +from mininet.net import init, Mininet +from mininet.node import Switch, Host, NOXController, ControllerParams if __name__ == '__main__': set_loglevel('info') init() controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8 mn = Mininet(FatTreeTopo(4), Switch, Host, NOXController, - controller_params) + controller_params) mn.interact() \ No newline at end of file diff --git a/mininet/net.py b/mininet/net.py index 47f94562e8c4b7ab20a74784cd9ab600285064f4..0b25e88ab86bac5b3eaa5ebbf7a339eb5b5a725b 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -66,499 +66,479 @@ 12/12/09 Added subdivided network driver workflow 12/13/09 Added support for custom controller and switch classes """ - -from subprocess import call, Popen, PIPE, STDOUT +import os +import re +from subprocess import call +import sys from time import sleep -import os, re, signal, sys, select -flush = sys.stdout.flush +#flush = sys.stdout.flush +#import os, re, signal, sys, select from mininet.logging_mod import lg, set_loglevel -from mininet.node import Node, Host, Controller, Switch -from mininet.util import run, checkRun, quietRun, makeIntfPair, moveIntf -from mininet.util import createLink, setLimits - -DATAPATHS = ['user', 'kernel'] - -# Handy utilities - -def dumpNodes( nodes ): - "Dump ifconfig of each node." - for node in nodes: - lg.info("*** Dumping node %s\n" % node.name) - lg.info("%s\n" % node.cmd( 'ip link show' )) - lg.info("%s\n" % node.cmd( 'route' )) - -def ipGen( A, B, c, d ): - "Generate next IP class B IP address, starting at A.B.c.d" - while True: - yield '%d.%d.%d.%d' % ( A, B, c, d ) - d += 1 - if d > 254: - d = 1 - c += 1 - if c > 254: break - -def nameGen( prefix ): - "Generate names starting with prefix." - i = 0 - while True: yield prefix + `i`; i += 1 - -# Control network support: -# -# Create an explicit control network. Currently this is only -# used by the user datapath configuration. -# -# Notes: -# -# 1. If the controller and switches are in the same (e.g. root) -# namespace, they can just use the loopback connection. -# We may wish to do this for the user datapath as well as the -# kernel datapath. -# -# 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!) - -def configureRoutedControlNetwork( controller, switches, ips): - """Configure a routed control network on controller and switches, - for use with the user datapath.""" - cip = ips.next() - lg.info("%s <-> " % controller.name) - for switch in switches: - lg.info("%s " % switch.name) - sip = ips.next() - sintf = switch.intfs[ 0 ] - node, cintf = switch.connection[ sintf ] - if node != controller: - lg.error("*** Error: switch %s not connected to correct controller" % - switch.name) - exit( 1 ) - controller.setIP( cintf, cip, '/24' ) - switch.setIP( sintf, sip, '/24' ) - controller.setHostRoute( sip, cintf ) - switch.setHostRoute( cip, sintf ) - lg.info("\n") - lg.info("*** Testing control network\n") - while not controller.intfIsUp(): - lg.info("*** Waiting for %s to come up\n", controller.intfs[ 0 ]) - sleep( 1 ) - for switch in switches: - while not switch.intfIsUp(): - lg.info("*** Waiting for %s to come up\n" % switch.intfs[ 0 ]) - sleep( 1 ) - if pingTest( hosts=[ switch, controller ] ) != 0: - lg.error("*** Error: control network test failed\n") - exit( 1 ) - -def configHosts( hosts, ips ): - """Configure a set of hosts, starting at IP address a.b.c.d""" - for host in hosts: - hintf = host.intfs[ 0 ] - host.setIP( hintf, ips.next(), '/24' ) - host.setDefaultRoute( hintf ) - # You're low priority, dude! - quietRun( 'renice +18 -p ' + `host.pid` ) - lg.info("%s ", host.name) - lg.info("\n") - -# Test driver and topologies - -class Network( object ): - """Network topology (and test driver) base class.""" - def __init__( self, - kernel=True, - Controller=Controller, Switch=Switch, - hostIpGen=ipGen, hostIpStart=( 192, 168, 123, 1 ) ): - self.kernel = kernel - self.Controller = Controller - self.Switch = Switch - self.hostIps = apply( hostIpGen, hostIpStart ) - # Check for kernel modules - modules = quietRun( 'lsmod' ) - if not kernel and 'tun' not in modules: - lg.error("*** Error: kernel module tun not loaded:\n") - lg.error(" user datapath not supported\n") - exit( 1 ) - if kernel and 'ofdatapath' not in modules: - lg.error("*** Error: kernel module ofdatapath not loaded:\n") - lg.error(" kernel datapath not supported\n") - exit( 1 ) - # Create network, but don't start things up yet! - self.prepareNet() - def configureControlNetwork( self, - ipGen=ipGen, ipStart = (10, 0, 123, 1 ) ): - ips = apply( ipGen, ipStart ) - configureRoutedControlNetwork( self.controllers[ 0 ], - self.switches, ips = ips) - def configHosts( self ): - configHosts( self.hosts, self.hostIps ) - def prepareNet( self ): - """Create a network by calling makeNet as follows: - (switches, hosts ) = makeNet() - Create a controller here as well.""" - kernel = self.kernel - if kernel: - lg.info("*** Using kernel datapath\n") - else: - lg.info("*** Using user datapath\n") - lg.info("*** Creating controller\n") - self.controller = self.Controller( 'c0', kernel=kernel ) - self.controllers = [ self.controller ] - lg.info("*** Creating network\n") - self.switches, self.hosts = self.makeNet( self.controller ) - lg.info("\n") - if not kernel: - lg.info("*** Configuring control network\n") - self.configureControlNetwork() - lg.info("*** Configuring hosts\n") - self.configHosts() - def start( self ): - """Start controller and switches\n""" - lg.info("*** Starting controller\n") - for controller in self.controllers: - controller.start() - lg.info("*** Starting %s switches" % len(self.switches)) - for switch in self.switches: - switch.start( self.controllers[ 0 ] ) - lg.info("\n") - def stop( self ): - """Stop the controller(s), switches and hosts\n""" - lg.info("*** Stopping hosts\n") - for host in self.hosts: - host.terminate() - lg.info("*** Stopping switches\n") - for switch in self.switches: - lg.info("%s" % switch.name) - switch.stop() - lg.info("\n") - lg.info("*** Stopping controller\n") - for controller in self.controllers: - controller.stop(); - lg.info("*** Test complete\n") - def runTest( self, test ): - """Run a given test, called as test( controllers, switches, hosts)""" - return test( self.controllers, self.switches, self.hosts ) - def run( self, test ): - """Perform a complete start/test/stop cycle; test is of the form - test( controllers, switches, hosts )""" - self.start() - lg.info("*** Running test\n") - result = self.runTest( test ) - self.stop() - return result - def interact( self ): - "Create a network and run our simple CLI." - self.run( self, Cli ) - -def defaultNames( snames=None, hnames=None, dpnames=None ): - "Reinitialize default names from generators, if necessary." - if snames is None: snames = nameGen( 's' ) - if hnames is None: hnames = nameGen( 'h' ) - if dpnames is None: dpnames = nameGen( 'nl:' ) - return snames, hnames, dpnames - -# Tree network - -class TreeNet( Network ): - "A tree-structured network with the specified depth and fanout" - def __init__( self, depth, fanout, **kwargs): - self.depth, self.fanout = depth, fanout - Network.__init__( self, **kwargs ) - def treeNet( self, controller, depth, fanout, snames=None, - hnames=None, dpnames=None ): - """Return a tree network of the given depth and fanout as a triple: - ( root, switches, hosts ), using the given switch, host and - datapath name generators, with the switches connected to the given - controller. If kernel=True, use the kernel datapath; otherwise the - user datapath will be used.""" - # Ugly, but necessary (?) since defaults are only evaluated once - snames, hnames, dpnames = defaultNames( snames, hnames, dpnames ) - if ( depth == 0 ): - host = Host( hnames.next() ) - lg.info("%s " % host.name) - return host, [], [ host ] - dp = dpnames.next() if self.kernel else None - switch = Switch( snames.next(), dp ) - if not self.kernel: createLink( switch, controller ) - lg.info("%s " % switch.name) - switches, hosts = [ switch ], [] - for i in range( 0, fanout ): - child, slist, hlist = self.treeNet( controller, - depth - 1, fanout, snames, hnames, dpnames ) - createLink( switch, child ) - switches += slist - hosts += hlist - return switch, switches, hosts - def makeNet( self, controller ): - root, switches, hosts = self.treeNet( controller, - self.depth, self.fanout ) - return switches, hosts - -# Grid network - -class GridNet( Network ): - """An N x M grid/mesh network of switches, with hosts at the edges. - This class also demonstrates creating a somewhat complicated - topology.""" - def __init__( self, n, m, linear=False, **kwargs ): - self.n, self.m, self.linear = n, m, linear and m == 1 - Network.__init__( self, **kwargs ) - def makeNet( self, controller ): - snames, hnames, dpnames = defaultNames() - n, m = self.n, self.m - hosts = [] - switches = [] - kernel = self.kernel - rows = [] - if not self.linear: - lg.info("*** gridNet: creating", n, "x", m, "grid of switches") - for y in range( 0, m ): - row = [] - for x in range( 0, n ): - dp = dpnames.next() if kernel else None - switch = Switch( snames.next(), dp ) - if not kernel: createLink( switch, controller ) - row.append( switch ) - switches += [ switch ] - lg.info("%s " % switch.name) - rows += [ row ] - # Hook up rows - for row in rows: - previous = None - for switch in row: - if previous is not None: - createLink( switch, previous ) - previous = switch - h1, h2 = Host( hnames.next() ), Host( hnames.next() ) - createLink( h1, row[ 0 ] ) - createLink( h2, row[ -1 ] ) - hosts += [ h1, h2 ] - lg.info("%s %s" % (h1.name, h2.name)) - # Return here if we're using this to make a linear network - if self.linear: return switches, hosts - # Hook up columns - for x in range( 0, n ): - previous = None - for y in range( 0, m ): - switch = rows[ y ][ x ] - if previous is not None: - createLink( switch, previous ) - previous = switch - h1, h2 = Host( hnames.next() ), Host( hnames.next() ) - createLink( h1, rows[ 0 ][ x ] ) - createLink( h2, rows[ -1 ][ x ] ) - hosts += [ h1, h2 ] - lg.info("%s %s" % (h1.name, h2.name)) - return switches, hosts - -class LinearNet( GridNet ): - "A network consisting of two hosts connected by a string of switches." - def __init__( self, switchCount, **kwargs ): - self.switchCount = switchCount - GridNet.__init__( self, switchCount, 1, linear=True, **kwargs ) - -# Tests - -def parsePing( pingOutput ): - "Parse ping output and return packets sent, received." - r = r'(\d+) packets transmitted, (\d+) received' - m = re.search( r, pingOutput ) - if m == None: - lg.error("*** Error: could not parse ping output: %s\n" % pingOutput) - exit( 1 ) - sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) - return sent, received - -def pingTest( controllers=[], switches=[], hosts=[], verbose=False ): - "Test that each host can reach every other host." - packets = 0 ; lost = 0 - for node in hosts: - if verbose: - lg.info("%s -> " % node.name) - for dest in hosts: - if node != dest: - result = node.cmd( 'ping -c1 ' + dest.IP() ) - sent, received = parsePing( result ) - packets += sent - if received > sent: - lg.error("*** Error: received too many packets") - lg.error("%s" % result) - node.cmdPrint( 'route' ) - exit( 1 ) - lost += sent - received - if verbose: - lg.info(("%s " % dest.name) if received else "X ") - if verbose: - lg.info("\n") - ploss = 100 * lost/packets - if verbose: - lg.info("%d%% packet loss (%d/%d lost)\n" % ( ploss, lost, packets )) - flush() - return ploss - -def pingTestVerbose( controllers, switches, hosts ): - return "%d %% packet loss" % \ - pingTest( controllers, switches, hosts, verbose=True ) - -def parseIperf( iperfOutput ): - "Parse iperf output and return bandwidth." - r = r'([\d\.]+ \w+/sec)' - m = re.search( r, iperfOutput ) - return m.group( 1 ) if m is not None else "could not parse iperf output" - -def iperf( hosts, verbose=False ): - "Run iperf between two hosts." - assert len( hosts ) == 2 - host1, host2 = hosts[ 0 ], hosts[ 1 ] - host1.cmd( 'killall -9 iperf') # XXX shouldn't be global killall - server = host1.cmd( 'iperf -s &' ) - if verbose: - lg.info("%s" % server) - client = host2.cmd( 'iperf -t 5 -c ' + host1.IP() ) - if verbose: - lg.info("%s" % client) - server = host1.cmd( 'kill -9 %iperf' ) - if verbose: - lg.info("%s" % server) - return [ parseIperf( server ), parseIperf( client ) ] - -def iperfTest( controllers, switches, hosts, verbose=False ): - "Simple iperf test between two hosts." - if verbose: - lg.info("*** Starting ping test\n") - h0, hN = hosts[ 0 ], hosts[ -1 ] - lg.info("*** iperfTest: Testing bandwidth between") - lg.info("%s and %s\n" % (h0.name, hN.name)) - result = iperf( [ h0, hN], verbose ) - lg.info("*** result: %s\n" % result) - return result - -# Simple CLI - -class Cli( object ): - "Simple command-line interface to talk to nodes." - cmds = [ '?', 'help', 'nodes', 'sh', 'pingtest', 'iperf', 'net', 'exit' ] - def __init__( self, controllers, switches, hosts ): - self.controllers = controllers - self.switches = switches - self.hosts = hosts - self.nodemap = {} - self.nodelist = controllers + switches + hosts - for node in self.nodelist: - self.nodemap[ node.name ] = node - self.run() - # Commands - def help( self, args ): - "Semi-useful help for CLI" - help_str = "Available commands are:" + str(self.cmds) + "\n" + \ - "You may also send a command to a node using:" + \ - " <node> command {args}" + \ - "For example:" + \ - " mininet> h0 ifconfig" + \ - "\n" + \ - "The interpreter automatically substitutes IP addresses" + \ - "for node names, so commands like" + \ - " mininet> h0 ping -c1 h1" + \ - "should work." + \ - "\n" + \ - "Interactive commands are not really supported yet," + \ - "so please limit commands to ones that do not" + \ - "require user interaction and will terminate" + \ - "after a reasonable amount of time." - print(help_str) - - def nodes( self, args ): - "List available nodes" - lg.info("available nodes are:\n", [ node.name for node in self.nodelist]) - def sh( self, args ): - "Run an external shell command" - call( [ 'sh', '-c' ] + args ) - def pingtest( self, args ): - pingTest( self.controllers, self.switches, self.hosts, verbose=True ) - def net( self, args ): - for switch in self.switches: - lg.info("%s <-> ", switch.name) - for intf in switch.intfs: - node, remoteIntf = switch.connection[ intf ] - lg.info("%s" % node.name) - def iperf( self, args ): - if len( args ) != 2: - lg.error("usage: iperf <h1> <h2>") - return - for host in args: - if host not in self.nodemap: - lg.error("iperf: cannot find host: %s" % host) - return - iperf( [ self.nodemap[ h ] for h in args ], verbose=True ) - # Interpreter - def run( self ): - "Read and execute commands." - lg.info("*** cli: starting\n") - while True: - lg.info("mininet> ") - input = sys.stdin.readline() - if input == '': break - if input[ -1 ] == '\n': input = input[ : -1 ] - cmd = input.split( ' ' ) - first = cmd[ 0 ] - rest = cmd[ 1: ] - if first in self.cmds and hasattr( self, first ): - getattr( self, first )( rest ) - elif first in self.nodemap and rest != []: - node = self.nodemap[ first ] - # Substitute IP addresses for node names in command - rest = [ self.nodemap[ arg ].IP() if arg in self.nodemap else arg - for arg in rest ] - rest = ' '.join( rest ) - # Interactive commands don't work yet, and - # there are still issues with control-c - lg.error("*** %s: running %s\n" % (node.name, rest)) - node.sendCmd( rest ) - while True: - try: - done, data = node.monitor() - lg.info("%s\n" % data) - if done: break - except KeyboardInterrupt: node.sendInt() - elif first == '': pass - elif first in [ 'exit', 'quit' ]: break - elif first == '?': self.help( rest ) - else: - lg.error("cli: unknown node or command: < %s >\n" % first) - lg.info("*** cli: exiting\n") +from mininet.node import Host, Controller, Switch, ControllerParams +from mininet.topo import TreeTopo +from mininet.util import quietRun, fixLimits +from mininet.util import make_veth_pair, move_intf, retry, MOVEINTF_DELAY + +DATAPATHS = ['kernel'] #['user', 'kernel'] def init(): - "Initialize Mininet." - if os.getuid() != 0: - # Note: this script must be run as root - # Perhaps we should do so automatically! - print "*** Mininet must run as root."; exit( 1 ) - # If which produces no output, then netns is not in the path. - # May want to loosen this to handle netns in the current dir. - if not quietRun( [ 'which', 'netns' ] ): - raise Exception( "Could not find netns; see INSTALL" ) - fixLimits() + "Initialize Mininet." + if os.getuid() != 0: + # Note: this script must be run as root + # Perhaps we should do so automatically! + print "*** Mininet must run as root." + exit(1) + # If which produces no output, then netns is not in the path. + # May want to loosen this to handle netns in the current dir. + if not quietRun(['which', 'netns']): + raise Exception("Could not find netns; see INSTALL") + fixLimits() + + +class Mininet(object): + '''Network emulation with hosts spawned in network namespaces.''' + + def __init__(self, topo, switch, host, controller, cparams, + build = True, xterms = False, cleanup = False, + in_namespace = False, switch_is_kernel = True): + '''Create Mininet object. + + @param topo Topo object + @param switch Switch class + @param host Host class + @param controller Controller class + @param cparams ControllerParams object + @param now build now? + @param xterms if build now, spawn xterms? + @param cleanup if build now, cleanup before creating? + @param in_namespace spawn switches and hosts in their own namespace? + ''' + self.topo = topo + self.switch = switch + self.host = host + self.controller = controller + self.cparams = cparams + self.nodes = {} # dpid to Node{Host, Switch} objects + self.controllers = {} # controller name to Controller objects + self.dps = 0 # number of created kernel datapaths + self.in_namespace = in_namespace + self.switch_is_kernel = switch_is_kernel + + self.kernel = True #temporary! + + if build: + self.build(xterms, cleanup) + + def _add_host(self, dpid): + '''Add host. + + @param dpid DPID of host to add + ''' + host = self.host('h_' + self.topo.name(dpid)) + # for now, assume one interface per host. + host.intfs.append('h_' + self.topo.name(dpid) + '-eth0') + self.nodes[dpid] = host + #lg.info('%s ' % host.name) + + def _add_switch(self, dpid): + ''' + @param dpid DPID of switch to add + ''' + sw = None + if self.switch_is_kernel: + sw = self.switch('s_' + self.topo.name(dpid), 'nl:' + str(self.dps)) + self.dps += 1 + else: + sw = self.switch('s_' + self.topo.name(dpid)) + self.nodes[dpid] = sw + + def _add_link(self, src, dst): + '''Add link. + + @param src source DPID + @param dst destination DPID + ''' + src_port, dst_port = self.topo.port(src, dst) + src_node = self.nodes[src] + dst_node = self.nodes[dst] + src_intf = src_node.intfName(src_port) + dst_intf = dst_node.intfName(dst_port) + make_veth_pair(src_intf, dst_intf) + src_node.intfs.append(src_intf) + dst_node.intfs.append(dst_intf) + #lg.info('\n') + #lg.info('added intf %s to src node %x\n' % (src_intf, src)) + #lg.info('added intf %s to dst node %x\n' % (dst_intf, dst)) + if src_node.inNamespace: + #lg.info('moving src w/inNamespace set\n') + retry(3, MOVEINTF_DELAY, move_intf, src_intf, src_node) + if dst_node.inNamespace: + #lg.info('moving dst w/inNamespace set\n') + retry(3, MOVEINTF_DELAY, move_intf, dst_intf, dst_node) + src_node.connection[src_intf] = (dst_node, dst_intf) + dst_node.connection[dst_intf] = (src_node, src_intf) + + def _add_controller(self, controller): + '''Add controller. + + @param controller Controller class + ''' + controller = self.controller('c0', kernel = self.kernel) + self.controllers['c0'] = controller + + # Control network support: + # + # Create an explicit control network. Currently this is only + # used by the user datapath configuration. + # + # Notes: + # + # 1. If the controller and switches are in the same (e.g. root) + # namespace, they can just use the loopback connection. + # We may wish to do this for the user datapath as well as the + # kernel datapath. + # + # 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!) + + def _configureControlNetwork(self): + '''Configure control network.''' + self._configureRoutedControlNetwork() + + def _configureRoutedControlNetwork(self): + '''Configure a routed control network on controller and switches. + + For use with the user datapath only right now. + + @todo(brandonh) Test this code and verify that user-space works! + ''' + # params were: controller, switches, ips + + controller = self.controllers['c0'] + lg.info('%s <-> ' % controller.name) + for switch_dpid in self.topo.switches(): + switch = self.nodes[switch_dpid] + lg.info('%s ' % switch.name) + sip = self.topo.ip(switch_dpid)#ips.next() + sintf = switch.intfs[0] + node, cintf = switch.connection[sintf] + if node != controller: + lg.error('*** Error: switch %s not connected to correct' + 'controller' % + switch.name) + exit(1) + controller.setIP(cintf, self.cparams.ip, '/' + + self.cparams.subnet_size) + switch.setIP(sintf, sip, '/' + self.cparams.subnet_size) + controller.setHostRoute(sip, cintf) + switch.setHostRoute(self.cparams.ip, sintf) + lg.info('\n') + lg.info('*** Testing control network\n') + while not controller.intfIsUp(controller.intfs[0]): + lg.info('*** Waiting for %s to come up\n', controller.intfs[0]) + sleep(1) + for switch_dpid in self.topo.switches(): + switch = self.nodes[switch_dpid] + while not switch.intfIsUp(switch.intfs[0]): + lg.info('*** Waiting for %s to come up\n' % switch.intfs[0]) + sleep(1) + if self.ping_test(hosts=[switch, controller]) != 0: + lg.error('*** Error: control network test failed\n') + exit(1) + lg.info('\n') + + def _config_hosts( self ): + '''Configure a set of hosts.''' + # params were: hosts, ips + for host_dpid in self.topo.hosts(): + host = self.nodes[host_dpid] + hintf = host.intfs[0] + host.setIP(hintf, self.topo.ip(host_dpid), + '/' + str(self.cparams.subnet_size)) + host.setDefaultRoute(hintf) + # You're low priority, dude! + quietRun('renice +18 -p ' + repr(host.pid)) + lg.info('%s ', host.name) + lg.info('\n') + + def build(self, xterms, cleanup): + '''Build mininet. + + At the end of this function, everything should be connected and up. + + @param xterms spawn xterms on build? + @param cleanup cleanup before creating? + ''' + if cleanup: + pass # cleanup + # validate topo? + kernel = self.kernel + if kernel: + lg.info('*** Using kernel datapath\n') + else: + lg.info('*** Using user datapath\n') + lg.info('*** Adding controller\n') + self._add_controller(self.controller) + lg.info('*** Creating network\n') + lg.info('*** Adding hosts:\n') + for host in sorted(self.topo.hosts()): + self._add_host(host) + lg.info('0x%x ' % host) + lg.info('\n*** Adding switches:\n') + for switch in sorted(self.topo.switches()): + self._add_switch(switch) + lg.info('0x%x ' % switch) + lg.info('\n*** Adding edges: ') + for src, dst in sorted(self.topo.edges()): + self._add_link(src, dst) + lg.info('(0x%x, 0x%x) ' % (src, dst)) + lg.info('\n') + + if not kernel: + lg.info('*** Configuring control network\n') + self._configureControlNetwork() + + lg.info('*** Configuring hosts\n') + self._config_hosts() + + if xterms: + pass # build xterms + + def start(self): + '''Start controller and switches\n''' + lg.info('*** Starting controller\n') + self.controllers['c0'].start() + #for controller in self.controllers: + # controller.start() + lg.info('*** Starting %s switches\n' % len(self.topo.switches())) + for switch_dpid in self.topo.switches(): + switch = self.nodes[switch_dpid] + #lg.info('switch = %s' % switch) + lg.info('0x%x ' % switch_dpid) + switch.start(self.controllers['c0']) + lg.info('\n') + + def stop(self): + '''Stop the controller(s), switches and hosts\n''' + lg.info('*** Stopping %i hosts\n' % len(self.topo.hosts())) + for host_dpid in self.topo.hosts(): + host = self.nodes[host_dpid] + lg.info('%s ' % host.name) + host.terminate() + lg.info('\n') + lg.info('*** Stopping %i switches\n' % len(self.topo.switches())) + for switch_dpid in self.topo.switches(): + switch = self.nodes[switch_dpid] + lg.info('%s' % switch.name) + switch.stop() + lg.info('\n') + lg.info('*** Stopping controller\n') + #for controller in self.controllers.iteriterms(): + self.controllers['c0'].stop() + lg.info('*** Test complete\n') + + def run(self, test, **params): + '''Perform a complete start/test/stop cycle.''' + self.start() + lg.info('*** Running test\n') + result = test(self, **params) + self.stop() + return result + + @staticmethod + def _parse_ping(pingOutput): + '''Parse ping output and return packets sent, received.''' + r = r'(\d+) packets transmitted, (\d+) received' + m = re.search( r, pingOutput ) + if m == None: + lg.error('*** Error: could not parse ping output: %s\n' % + pingOutput) + exit(1) + sent, received = int(m.group(1)), int(m.group(2)) + return sent, received + + def ping_test(self, hosts = None, verbose = False): + '''Ping between all specified hosts. + + @param hosts list of host DPIDs + @param verbose verbose printing + @return ploss packet loss percentage + ''' + #self.start() + # check if running - only then, start? + packets = 0 + lost = 0 + ploss = None + if not hosts: + hosts = self.topo.hosts() + for node_dpid in hosts: + node = self.nodes[node_dpid] + if verbose: + lg.info('%s -> ' % node.name) + for dest_dpid in hosts: + dest = self.nodes[dest_dpid] + if node != dest: + result = node.cmd('ping -c1 ' + dest.IP()) + sent, received = self._parse_ping(result) + packets += sent + if received > sent: + lg.error('*** Error: received too many packets') + lg.error('%s' % result) + node.cmdPrint('route') + exit( 1 ) + lost += sent - received + if verbose: + lg.info(('%s ' % dest.name) if received else 'X ') + if verbose: + lg.info('\n') + ploss = 100 * lost / packets + if verbose: + lg.info('%d%% packet loss (%d/%d lost)\n' % (ploss, lost, packets)) + #flush() + #self.stop() + return ploss + + def interact(self): + '''Start network and run our simple CLI.''' + self.run(MininetCLI) + + +class MininetCLI(object): + '''Simple command-line interface to talk to nodes.''' + cmds = ['?', 'help', 'nodes', 'net', 'sh', 'ping_all', 'exit', \ + 'ping_pair'] #'iperf' + + def __init__(self, mininet): + self.mn = mininet + self.nodemap = {} # map names to Node objects + for node in self.mn.nodes.values(): + self.nodemap[node.name] = node + self.nodemap['c0'] = self.mn.controllers['c0'] + self.nodelist = self.nodemap.values() + self.run() + + # Commands + def help(self, args): + '''Semi-useful help for CLI.''' + help_str = 'Available commands are:' + str(self.cmds) + '\n' + \ + 'You may also send a command to a node using:\n' + \ + ' <node> command {args}\n' + \ + 'For example:\n' + \ + ' mininet> h0 ifconfig\n' + \ + '\n' + \ + 'The interpreter automatically substitutes IP ' + \ + 'addresses\n' + \ + 'for node names, so commands like\n' + \ + ' mininet> h0 ping -c1 h1\n' + \ + 'should work.\n' + \ + '\n\n' + \ + 'Interactive commands are not really supported yet,\n' + \ + 'so please limit commands to ones that do not\n' + \ + 'require user interaction and will terminate\n' + \ + 'after a reasonable amount of time.\n' + print(help_str) + + def nodes(self, args): + '''List all nodes.''' + lg.info('available nodes are: \n%s\n', + ' '.join([node.name for node in sorted(self.nodelist)])) + + def net(self, args): + '''List network connection.''' + for switch_dpid in self.mn.topo.switches(): + switch = self.mn.nodes[switch_dpid] + lg.info('%s <->', switch.name) + for intf in switch.intfs: + node, remoteIntf = switch.connection[intf] + lg.info(' %s' % node.name) + lg.info('\n') + + def sh(self, args): + '''Run an external shell command''' + call( [ 'sh', '-c' ] + args ) + + def ping_all(self, args): + '''Ping between all hosts.''' + self.mn.ping_test(verbose = True) + + def ping_pair(self, args): + '''Ping between first two hosts, useful for testing.''' + hosts_unsorted = sorted(self.mn.topo.hosts()) + hosts = [hosts_unsorted[0], hosts_unsorted[1]] + self.mn.ping_test(hosts = hosts, verbose = True) + + def run(self): + '''Read and execute commands.''' + lg.warn('*** Starting CLI:\n') + while True: + lg.warn('mininet> ') + input = sys.stdin.readline() + if input == '': + break + if input[-1] == '\n': + input = input[:-1] + cmd = input.split(' ') + first = cmd[0] + rest = cmd[1:] + if first in self.cmds and hasattr(self, first): + getattr(self, first)(rest) + elif first in self.nodemap and rest != []: + node = self.nodemap[first] + # Substitute IP addresses for node names in command + rest = [self.nodemap[arg].IP() if arg in self.nodemap else arg + for arg in rest] + rest = ' '.join(rest) + # Interactive commands don't work yet, and + # there are still issues with control-c + lg.warn('*** %s: running %s\n' % (node.name, rest)) + node.sendCmd(rest) + while True: + try: + done, data = node.monitor() + lg.info('%s\n' % data) + if done: + break + except KeyboardInterrupt: + node.sendInt() + elif first == '': + pass + elif first in ['exit', 'quit']: + break + elif first == '?': + self.help( rest ) + else: + lg.error('CLI: unknown node or command: < %s >\n' % first) + #lg.info('*** CLI: command complete\n') + return 'exited by user command' + if __name__ == '__main__': - if len(sys.argv) > 1: - set_loglevel(sys.argv[1]) - else: - set_loglevel('info') - - init() - results = {} - lg.info("*** Welcome to Mininet!\n") - lg.info("*** Look in examples/ for more examples\n\n") - lg.info("*** Testing Mininet with kernel and user datapath\n") - for datapath in [ 'kernel', 'user' ]: - k = datapath == 'kernel' - network = TreeNet( depth=2, fanout=4, kernel=k) - result = network.run( pingTestVerbose ) - results[ datapath ] = result - lg.info("*** Test results: %s\n", results) \ No newline at end of file + if len(sys.argv) > 1: + set_loglevel(sys.argv[1]) + else: + set_loglevel('info') + + init() + results = {} + lg.info("*** Welcome to Mininet!\n") + lg.info("*** Look in examples/ for more examples\n\n") + lg.info("*** Testing Mininet with kernel and user datapath\n") + for datapath in DATAPATHS: + k = datapath == 'kernel' + controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8 + mn = Mininet(TreeTopo(), Switch, Host, Controller, + controller_params) + mn.start() + dropped = mn.ping_test() + results[datapath] = "%i%% dropped" % dropped + mn.stop() + + lg.info("*** Test results: %s\n", results) \ No newline at end of file diff --git a/mininet/node.py b/mininet/node.py index 16f43bdb3cb7c3eb1c8105746fafee1c3196be23..d882f0613280f2fe7191a2a7e349dd0cd082ca17 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -335,4 +335,36 @@ def monitor(self): if not self.execed: return Node.monitor(self) else: - return True, '' \ No newline at end of file + return True, '' + + +class NOXController(Controller): + '''Controller to run a NOX application.''' + def __init__(self, name, nox_args = None, **kwargs): + '''Init. + + @param name name to give controller + @param nox_args list of args to use with NOX + ''' + if not nox_args: + nox_args = ['packetdump'] + nox_core_dir = os.environ['NOX_CORE_DIR'] + if not nox_core_dir: + raise Exception('please set NOX_CORE_DIR env var\n') + Controller.__init__(self, name, + controller = nox_core_dir + '/nox_core', + cargs = '--libdir=/usr/local/lib -v -i ptcp: ' + \ + ' '.join(nox_args), + cdir = nox_core_dir, **kwargs) + + +class ControllerParams(object): + '''Container for controller IP parameters.''' + def __init__(self, ip, subnet_size): + '''Init. + + @param ip integer, controller IP + @param subnet_size integer, ex 8 for slash-8, covering 17M + ''' + self.ip = ip + self.subnet_size = subnet_size \ No newline at end of file diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py index 1696106128de3fc994bea6327059433e0ec1dec7..ed6570df08c582d6ef148a1f562300f1318cc5d3 100755 --- a/mininet/test/test_nets.py +++ b/mininet/test/test_nets.py @@ -7,7 +7,13 @@ from time import sleep import unittest -from mininet.net import init, TreeNet, LinearNet, pingTest, DATAPATHS +from mininet.net import init, Mininet #, DATAPATHS +from mininet.node import Switch, Host, NOXController, ControllerParams +from mininet.node import Controller +from mininet.topo import TreeTopo + +# temporary, until user-space side is tested +DATAPATHS = ['kernel'] class testMinimal(unittest.TestCase): '''For each datapath type, test ping with a minimal topology. @@ -20,9 +26,13 @@ def testMinimal(self): init() for datapath in DATAPATHS: k = datapath == 'kernel' - network = TreeNet(depth = 1, fanout = 2, kernel = k) - dropped = network.run(pingTest) + controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8 + mn = Mininet(TreeTopo(), Switch, Host, Controller, + controller_params) + mn.start() + dropped = mn.ping_test() self.assertEqual(dropped, 0) + mn.stop() class testTree(unittest.TestCase): @@ -33,22 +43,26 @@ def testTree16(self): init() for datapath in DATAPATHS: k = datapath == 'kernel' - network = TreeNet(depth = 2, fanout = 4, kernel = k) - dropped = network.run(pingTest) + controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8 + tree_topo = TreeTopo(depth = 3, fanout = 4) + mn = Mininet(tree_topo, Switch, Host, Controller, + controller_params) + mn.start() + dropped = mn.ping_test() self.assertEqual(dropped, 0) + mn.stop() - -class testLinear(unittest.TestCase): - '''For each datapath type, test all-pairs ping with LinearNet.''' - - def testLinear10(self): - '''Ping test with both datapaths on 10-switch topology''' - init() - for datapath in DATAPATHS: - k = datapath == 'kernel' - network = network = LinearNet(10, kernel=k) - dropped = network.run(pingTest) - self.assertEqual(dropped, 0) +#class testLinear(unittest.TestCase): +# '''For each datapath type, test all-pairs ping with LinearNet.''' +# +# def testLinear10(self): +# '''Ping test with both datapaths on 10-switch topology''' +# init() +# for datapath in DATAPATHS: +# k = datapath == 'kernel' +# network = network = LinearNet(10, kernel=k) +# dropped = network.run(pingTest) +# self.assertEqual(dropped, 0) if __name__ == '__main__': diff --git a/mininet/topo.py b/mininet/topo.py new file mode 100644 index 0000000000000000000000000000000000000000..9417c6a261bb95b095c1a9bc13b76ca9f1e88543 --- /dev/null +++ b/mininet/topo.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +'''Starter topologies for Mininet.''' + +from ripcord.topo import Topo, StructuredNodeSpec, StructuredNode, Edge +from ripcord.topo import StructuredTopo, StructuredEdgeSpec, NodeID + +class TreeTopo(StructuredTopo): + '''Tree-structured network.''' + + class TreeNodeID(NodeID): + '''Tree-specific node.''' + + def __init__(self, layer = 0, index = 0, dpid = None): + '''Create TreeNodeID object from custom params. + + Either (layer, index) or dpid must be passed in. + + @param layer layer + @param index index within layer + @param dpid optional dpid + ''' + if dpid: + self.layer = (dpid & 0xff0000) >> 16 + self.index = (dpid & 0xffff) + self.dpid = dpid + else: + self.layer = layer + self.index = index + self.dpid = (layer << 16) + index + + def __str__(self): + return "(%i_%i)" % (self.layer, self.index) + + def name_str(self): + return "%i_%i" % (self.layer, self.index) + + def ip_str(self): + # add 1; can't have IP addr ending in 0 + index_hi = (self.index & 0xff00) >> 8 + index_lo = self.index & 0xff + return "10.%i.%i.%i" % (self.layer, index_hi, index_lo) + + def __init__(self, depth = 2, fanout = 2, speed = 1.0, enable_all = True): + '''Init. + + @param depth number of levels, including host level + @param fanout + ''' + node_specs = [] + core = StructuredNodeSpec(0, fanout, None, speed, type_str = 'core') + node_specs.append(core) + for i in range(1, depth - 1): + node = StructuredNodeSpec(1, fanout, speed, speed, + type_str = 'layer' + str(i)) + node_specs.append(node) + host = StructuredNodeSpec(1, 0, speed, None, type_str = 'host') + node_specs.append(host) + edge_specs = [StructuredEdgeSpec(speed)] * (depth - 1) + super(TreeTopo, self).__init__(node_specs, edge_specs) + + self.depth = depth + self.fanout = fanout + self.id_gen = TreeTopo.TreeNodeID + + # create root + root_id = self.id_gen(0, 0).dpid + self._add_node(root_id, StructuredNode(0)) + last_layer_ids = [root_id] + + # create lower layers + for i in range(1, depth): + current_layer_ids = [] + # start index at 1, as we can't have IP addresses ending in 0 + index = 1 + for last_id in last_layer_ids: + for j in range(fanout): + is_switch = (i < depth - 1) + node = StructuredNode(i, is_switch = is_switch) + node_id = self.id_gen(i, index).dpid + current_layer_ids.append(node_id) + self._add_node(node_id, node) + self._add_edge(last_id, node_id, Edge()) + index += 1 + last_layer_ids = current_layer_ids + + if enable_all: + self.enable_all() + + def port(self, src, dst): + '''Get port number (optional) + + Note that the topological significance of DPIDs in FatTreeTopo enables + this function to be implemented statelessly. + + @param src source switch DPID + @param dst destination switch DPID + @return tuple (src_port, dst_port): + src_port: port on source switch leading to the destination switch + dst_port: port on destination switch leading to the source switch + ''' + + src_layer = self.node_info[src].layer + dst_layer = self.node_info[dst].layer + + src_id = self.id_gen(dpid = src) + dst_id = self.id_gen(dpid = dst) + + lower = None + higher = None + if src_layer == dst_layer - 1: # src is higher + src_port = ((dst_id.index - 1) % self.fanout) + 1 + dst_port = 0 + elif dst_layer == src_layer - 1: + src_port = 0 + dst_port = ((src_id.index - 1) % self.fanout) + 1 + else: + raise Exception("Could not find port leading to given dst switch") + + return (src_port, dst_port) + + def name(self, dpid): + '''Get string name of node ID. + + @param dpid DPID of host or switch + @return name_str string name with no dashes + ''' + return self.id_gen(dpid = dpid).name_str() + + def ip(self, dpid): + '''Get IP dotted-decimal string of node ID. + + @param dpid DPID of host or switch + @return ip_str + ''' + return self.id_gen(dpid = dpid).ip_str() \ No newline at end of file