Move TreeNet to new Mininet API
Also remove all non-object-oriented legacy Mininet code and update tests. User-space compatibility is untested, but most of the code for it is still in.
This commit is contained in:
+5
-472
@@ -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()
|
||||
+466
-486
@@ -66,499 +66,479 @@ History:
|
||||
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
|
||||
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 = ['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")
|
||||
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')
|
||||
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)
|
||||
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)
|
||||
+33
-1
@@ -335,4 +335,36 @@ class Switch(Node):
|
||||
if not self.execed:
|
||||
return Node.monitor(self)
|
||||
else:
|
||||
return True, ''
|
||||
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
|
||||
+31
-17
@@ -7,7 +7,13 @@ Test creation and all-pairs ping for each included mininet topo type.
|
||||
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 @@ class testMinimal(unittest.TestCase):
|
||||
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 @@ class testTree(unittest.TestCase):
|
||||
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__':
|
||||
|
||||
+135
@@ -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()
|
||||
Reference in New Issue
Block a user