Organized routines into classes:

Node -> { Host, Switch, Controller }
Network -> { TreeNet, GridNet -> LinearNet }

Modified cleanup to clean up kernel datapaths.
This commit is contained in:
Bob Lantz
2009-12-09 21:13:17 -08:00
parent 98d4f1891c
commit eddef947a4
2 changed files with 450 additions and 334 deletions
+4
View File
@@ -11,6 +11,10 @@ done
echo "Removing excess controllers/ofprotocols/ofdatapaths/pings" echo "Removing excess controllers/ofprotocols/ofdatapaths/pings"
killall -9 controller ofprotocol ofdatapath ping 2> /dev/null killall -9 controller ofprotocol ofdatapath ping 2> /dev/null
echo "Removing excess kernel datapath processes"
ps ax | grep 'dp[0-9]' | awk '{print $1;}' | xargs kill
echo "Removing vconn junk in /tmp" echo "Removing vconn junk in /tmp"
rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log
+446 -334
View File
@@ -1,10 +1,9 @@
#!/usr/bin/python #!/usr/bin/python
""" """
Mininet: A simple networking testbed for OpenFlow! Mininet: A simple networking testbed for OpenFlow!
Mininet creates a simple test network for OpenFlow by using Mininet creates simple OpenFlow test networks by using
process-based virtualization and network namespaces. process-based virtualization and network namespaces.
This file supports use of either the kernel or user space datapath This file supports use of either the kernel or user space datapath
@@ -12,35 +11,34 @@ from the OpenFlow reference implementation. Up to 32 switches are
supported using the kernel datapath, and 512 (or more) switches are supported using the kernel datapath, and 512 (or more) switches are
supported via the user datapath. supported via the user datapath.
Simulated hosts are created as processes in Simulated hosts are created as processes in separate network
separate network namespaces. This allows a complete OpenFlow namespaces. This allows a complete OpenFlow network to be simulated on
network to be simulated on top of a single Linux kernel. top of a single Linux kernel.
Each host has: Each host has:
A virtual console (pipes to a shell) A virtual console (pipes to a shell)
A virtual interfaces (half of a veth pair) A virtual interfaces (half of a veth pair)
A namespaced parent shell (and possibly some child processes) A parent shell (and possibly some child processes) in a namespace
Hosts have a network interface which is Hosts have a network interface which is configured via ifconfig/ip
configured via ifconfig/ip link/etc. with data network IP link/etc. with data network IP addresses (e.g. 192.168.123.2 )
addresses (e.g. 192.168.123.2 )
In kernel datapath mode, the controller and switches are simply In kernel datapath mode, the controller and switches are simply
processes in the root namespace. processes in the root namespace.
Kernel OpenFlow datapaths are instantiated using dpctl, and are Kernel OpenFlow datapaths are instantiated using dpctl(8), and are attached
attached to the one side of a veth pair; the other side resides in to the one side of a veth pair; the other side resides in the host
the host namespace. In this mode, switch processes can simply namespace. In this mode, switch processes can simply connect to the
connect to the controller via the loopback interface. controller via the loopback interface.
In user datapath mode, the controller and switch are full-service In user datapath mode, the controller and switches are full-service
nodes that live in their own network namespace and have management nodes that live in their own network namespaces and have management
interfaces and IP addresses on a control network (e.g. 10.0.123.1, interfaces and IP addresses on a control network (e.g. 10.0.123.1,
currently routed although it could be bridged.) currently routed although it could be bridged.)
In addition to a management interface, user mode switches also have In addition to a management interface, user mode switches also have
several switch interfaces, halves of veth pairs whose other halves several switch interfaces, halves of veth pairs whose other halves reside
reside in the host nodes that the switches are connected to. in the host nodes that the switches are connected to.
Naming: Naming:
Host nodes are named h1-hN Host nodes are named h1-hN
@@ -65,16 +63,13 @@ rlantz@cs.stanford.edu
History: History:
11/19/09 Initial revision (user datapath only) 11/19/09 Initial revision (user datapath only)
12/8/08 Kernel datapath support complete 12/08/09 Kernel datapath support complete
12/09/09 Moved controller and switch routines into classes
""" """
# Note: this script must be run as root
# Perhaps we should do so automatically!
from subprocess import call, check_call, Popen, PIPE, STDOUT from subprocess import call, check_call, Popen, PIPE, STDOUT
from time import sleep from time import sleep
import re, os, signal, sys, select import os, re, signal, sys, select
flush = sys.stdout.flush flush = sys.stdout.flush
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
@@ -106,11 +101,6 @@ def quietRun( cmd ):
if popen.returncode != None: break if popen.returncode != None: break
return output return output
# Command paths
netns = "/usr/local/bin/netns"
bash = "/bin/bash"
ifconfig = "/sbin/ifconfig"
class Node( object ): class Node( object ):
"""A virtual network node is simply a shell in a network namespace. """A virtual network node is simply a shell in a network namespace.
We communicate with it using pipes.""" We communicate with it using pipes."""
@@ -118,9 +108,9 @@ class Node( object ):
self.name = name self.name = name
closeFds = False # speed vs. memory use closeFds = False # speed vs. memory use
# xpg_echo is needed so we can echo our sentinel in sendCmd # xpg_echo is needed so we can echo our sentinel in sendCmd
cmd = [ bash, '-O', 'xpg_echo' ] cmd = [ '/bin/bash', '-O', 'xpg_echo' ]
self.inNamespace = inNamespace self.inNamespace = inNamespace
if self.inNamespace: cmd = [ netns ] + cmd if self.inNamespace: cmd = [ 'netns' ] + cmd
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
close_fds=closeFds ) close_fds=closeFds )
self.stdin = self.shell.stdin self.stdin = self.shell.stdin
@@ -135,8 +125,7 @@ class Node( object ):
self.ips = {} self.ips = {}
self.connection = {} self.connection = {}
self.waiting = False self.waiting = False
# For sanity check, try bringing up loopback interface # Subshell I/O, commands and control
# self.cmd( "ifconfig lo 127.0.0.1 up" )
def read( self, max ): return os.read( self.stdout.fileno(), max ) def read( self, max ): return os.read( self.stdout.fileno(), max )
def write( self, data ): os.write( self.stdin.fileno(), data ) def write( self, data ): os.write( self.stdin.fileno(), data )
def terminate( self ): os.kill( self.pid, signal.SIGKILL ) def terminate( self ): os.kill( self.pid, signal.SIGKILL )
@@ -190,6 +179,7 @@ class Node( object ):
result = self.cmd( cmd ) result = self.cmd( cmd )
print result, print result,
return result return result
# Interface management, configuration, and routing
def intfName( self, n): def intfName( self, n):
"Construct a canonical interface name node-intf for interface N." "Construct a canonical interface name node-intf for interface N."
return self.name + '-eth' + `n` return self.name + '-eth' + `n`
@@ -201,7 +191,7 @@ class Node( object ):
return intfName return intfName
def setIP( self, intf, ip, bits ): def setIP( self, intf, ip, bits ):
"Set an interface's IP address." "Set an interface's IP address."
result = self.cmd( [ ifconfig, intf, ip + bits, 'up' ] ) result = self.cmd( [ 'ifconfig', intf, ip + bits, 'up' ] )
self.ips[ intf ] = ip self.ips[ intf ] = ip
return result return result
def setHostRoute( self, ip, intf ): def setHostRoute( self, ip, intf ):
@@ -213,13 +203,17 @@ class Node( object ):
return self.cmd( 'route add default ' + intf ) return self.cmd( 'route add default ' + intf )
def IP( self ): def IP( self ):
"Return IP address of first interface" "Return IP address of first interface"
return self.ips[ self.intfs[ 0 ] ] return self.ips[ self.intfs[ 0 ] ]
def intfIsUp( self, intf ):
"Check if one of our interfaces is up."
return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
# Other methods
def __str__( self ): def __str__( self ):
result = self.name result = self.name
result += ": IP=" + self.IP() + " intfs=" + self.intfs result += ": IP=" + self.IP() + " intfs=" + self.intfs
result += " waiting=", self.waiting result += " waiting=", self.waiting
return result return result
# Maintain mapping between i/o pipes and nodes # Maintain mapping between i/o pipes and nodes
# This could be useful for monitoring multiple nodes # This could be useful for monitoring multiple nodes
# using select.poll() # using select.poll()
@@ -233,20 +227,93 @@ def nodeFromFile( f ):
node = outToNode.get( f ) node = outToNode.get( f )
return node or inToNode.get( f ) return node or inToNode.get( f )
def createNodes( name, count ): class Host( Node ):
"Create and return a list of nodes." """A host is simply a Node."""
nodes = [ Node( name + `i` ) for i in range( 0, count ) ] pass
# print "*** CreateNodes: created:", nodes
return nodes class Controller( Node ):
"""A Controller is a Node that is running (or has execed) an
OpenFlow controller."""
def __init__( self, name, kernel=True ):
Node.__init__( self, name, inNamespace=( not kernel ) )
def start( self, cprog='controller', cargs='ptcp:' ):
"Start <cprog cargs> on controller, logging to /tmp/cN.log"
cout = '/tmp/' + self.name + '.log'
self.cmdPrint( cprog + ' ' + cargs +
' 1> ' + cout + ' 2> ' + cout + ' &' )
def stop( self, cprog='controller' ):
"Stop controller cprog on controller"
self.cmd( "kill %" + cprog )
class Switch( Node ):
"""A Switch is a Node that is running (or has execed)
an OpenFlow switch."""
def __init__( self, name, datapath=None ):
self.dp = datapath
self.execed = False
Node.__init__( self, name, inNamespace=( datapath == None ) )
def startUserDatapath( self, controller ):
"""Start OpenFlow reference user datapath,
logging to /tmp/sN-{ofd,ofp}.log"""
ofdlog = '/tmp/' + self.name + '-ofd.log'
ofplog = '/tmp/' + self.name + '-ofp.log'
self.cmd( 'ifconfig lo up' )
intfs = self.intfs[ 1 : ] # 0 is mgmt interface
self.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
self.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
' tcp:localhost 1> ' + ofplog + ' 2>' + ofplog + ' &' )
def stopUserDatapath( self ):
"Stop OpenFlow reference user datapath."
self.cmd( "kill %ofdatapath" )
self.cmd( "kill %ofprotocol" )
def startKernelDatapath( self, controller):
"Start up switch using OpenFlow reference kernel datapath."
ofplog = '/tmp/' + self.name + '-ofp.log'
quietRun( 'ifconfig lo up' )
# Delete local datapath if it exists;
# then create a new one monitoring the given interfaces
quietRun( 'dpctl deldp ' + self.dp )
self.cmdPrint( 'dpctl adddp ' + self.dp )
self.cmdPrint( 'dpctl addif ' + self.dp + ' ' + ' '.join( self.intfs ) )
# Become protocol daemon
self.cmdPrint( 'exec ofprotocol' +
' ' + self.dp + ' tcp:127.0.0.1 1> ' + ofplog + ' 2>' + ofplog + ' &' )
self.execed = True
def stopKernelDatapath( self ):
"Terminate a switch using OpenFlow reference kernel datapath."
quietRun( 'dpctl deldp ' + self.dp )
for intf in self.intfs: quietRun( 'ip link del ' + intf )
self.terminate()
def start( self, controller ):
if self.dp is None: self.startUserDatapath( controller )
else: self.startKernelDatapath( controller )
def stop( self ):
if self.dp is None: self.stopUserDatapath()
else: self.stopKernelDatapath()
# Handle non-interaction if we've execed
def sendCmd( self, cmd ):
if not self.execed: return Node.sendCmd( self, cmd )
else: print "*** Error:", self.name, "has execed and cannot accept commands"
def monitor( self ):
if not self.execed: return Node.monitor( self )
else: return True, ''
# Interface management # Interface management
# #
# We connect nodes by creating a pair of veth interfaces, # Interfaces are managed as strings which are simply the
# and then placing them in the pair of nodes that we want # interface names, of the form "nodeN-ethM".
# to communicate. Interfaces are named nodeN-ethM #
# To connect nodes, we create a pair of veth interfaces, and then place them
# in the pair of nodes that we want to communicate. We then update the node's
# list of interfaces and connectivity map.
#
# For the kernel datapath, switch interfaces
# live in the root namespace and thus do not have to be
# explicitly moved.
def makeIntfPair( intf1, intf2 ): def makeIntfPair( intf1, intf2 ):
"Make a veth pair of intf1 and intf2" "Make a veth pair of intf1 and intf2."
# Delete any old interfaces with the same names # Delete any old interfaces with the same names
quietRun( 'ip link del ' + intf1 ) quietRun( 'ip link del ' + intf1 )
quietRun( 'ip link del ' + intf2 ) quietRun( 'ip link del ' + intf2 )
@@ -255,7 +322,7 @@ def makeIntfPair( intf1, intf2 ):
return checkRun( cmd ) return checkRun( cmd )
def moveIntf( intf, node ): def moveIntf( intf, node ):
"Move intf to node" "Move intf to node."
cmd = 'ip link set ' + intf + ' netns ' + `node.pid` cmd = 'ip link set ' + intf + ' netns ' + `node.pid`
checkRun( cmd ) checkRun( cmd )
links = node.cmd( 'ip link show' ) links = node.cmd( 'ip link show' )
@@ -277,9 +344,240 @@ def createLink( node1, node2 ):
return intf1, intf2 return intf1, intf2
# Handy utilities # Handy utilities
def createNodes( name, count ):
"Create and return a list of nodes."
nodes = [ Node( name + `i` ) for i in range( 0, count ) ]
# print "*** CreateNodes: created:", nodes
return nodes
def dumpNodes( nodes ):
"Dump ifconfig of each node."
for node in nodes:
print "*** Dumping node", node.name
print node.cmd( 'ip link show' )
print 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
# For the user datapath, we create an explicit control network.
# Note: Instead of routing, we could bridge or use "in-band" control
def configRoutedControlNetwork( controller, switches,
startAddr=( 10, 123, 0, 1 ) ):
"""Configure a routed control network on controller and switches,
for use with the user datapath."""
ips = apply( ipGen, startAddr )
cip = ips.next()
print controller.name, '<->',
for switch in switches:
print switch.name, ; flush()
sip = ips.next()
sintf = switch.intfs[ 0 ]
node, cintf = switch.connection[ sintf ]
if node != controller:
print "*** Error: switch", switch.name,
print "not connected to correct controller"
exit( 1 )
controller.setIP( cintf, cip, '/24' )
switch.setIP( sintf, sip, '/24' )
controller.setHostRoute( sip, cintf )
switch.setHostRoute( cip, sintf )
print
print "*** Testing control network"
while not controller.intfIsUp( controller.intfs[ 0 ] ):
print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
sleep( 1 )
for switch in switches:
while not switch.intfIsUp( switch.intfs[ 0 ] ):
print "*** Waiting for ", switch.intfs[ 0 ], "to come up"
sleep( 1 )
if pingTest( [ switch, controller ] ) != 0:
print "*** Error: control network test failed"
exit( 1 )
def configHosts( hosts, ( a, b, c, d ) ):
"Configure a set of hosts, starting at IP address a.b.c.d"
ips = ipGen( 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` )
print host.name, ; flush()
print
# Test driver and topologies
class Network( object ):
"Network topology (and test driver) base class."
def __init__( self, kernel=True, startAddr=( 192, 168, 123, 1) ):
self.kernel, self.startAddr = kernel, startAddr
# In progress: we probably want to decouple creating/starting/stopping
# the network and running tests, since we might wish to run
# multiple tests on the same network. It's not clear if the network
# should always be started/stopped for each test or not. Probably
# not...
def run( self, test ):
"""Create a network by calling makeNet as follows:
(switches, hosts ) = makeNet()
and then run test( controller, switches, hosts ) on it."""
kernel = self.kernel
if kernel: print "*** Using kernel datapath"
else: print "*** Using user datapath"
print "*** Creating controller"
controller = Controller( 'c0', kernel )
print "*** Creating network"
switches, hosts = self.makeNet()
print
if not kernel:
print "*** Configuring control network"
configRoutedControlNetwork( controller, switches )
print "*** Configuring hosts"
configHosts( hosts, self.startAddr )
print "*** Starting reference controller"
controller.start()
print "*** Starting switches"
for switch in switches:
switch.start( controller )
print "*** Running test"
test( [ controller ], switches, hosts )
print "*** Stopping controller"
controller.stop()
print "*** Stopping switches"
for switch in switches:
switch.stop()
print "*** Test complete"
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 of the given depth and fanout"
def __init__( self, depth, fanout, kernel=True):
self.depth, self.fanout = depth, fanout
Network.__init__( self, kernel )
def treeNet( self, depth, fanout, kernel=True, 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() )
print host.name, ; flush()
return host, [], [ host ]
dp = dpnames.next() if kernel else None
switch = Switch( snames.next(), dp )
if not kernel: createLink( switch, controller )
print switch.name, ; flush()
switches, hosts = [ switch ], []
for i in range( 0, fanout ):
child, slist, hlist = self.treeNet(
depth - 1, fanout, kernel, snames, hnames, dpnames )
createLink( switch, child )
switches += slist
hosts += hlist
return switch, switches, hosts
def makeNet( self ):
root, switches, hosts = self.treeNet(
self.depth, self.fanout, self.kernel)
return switches, hosts
# Grid network
class GridNet( Network ):
"An n x m grid/mesh network of switches, with hosts at the edges."
def __init__( self, n, m, kernel=True, linear=False ):
self.n, self.m, self.linear = n, m, linear and m == 1
print "m=",m
Network.__init__( self, kernel )
def makeNet( self ):
snames, hnames, dpnames = defaultNames()
n, m = self.n, self.m
hosts = []
switches = []
kernel = self.kernel
rows = []
if not self.linear:
print "*** gridNet: creating", n, "x", m, "grid of switches" ; flush()
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 ]
print switch.name, ; flush()
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 ]
print h1.name, h2.name, ; flush()
# Return here if we're using this to make a linear network
if self.linear:
print "returning linear network"
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 ]
print h1.name, h2.name, ; flush()
return switches, hosts
class LinearNet( GridNet ):
def __init__( self, switchCount, kernel=True ):
self.switchCount = switchCount
GridNet.__init__( self, switchCount, 1, kernel, linear=True )
# Tests
def parsePing( pingOutput ): def parsePing( pingOutput ):
"Parse ping output and return packets sent, received" "Parse ping output and return packets sent, received."
r = r'(\d+) packets transmitted, (\d+) received' r = r'(\d+) packets transmitted, (\d+) received'
m = re.search( r, pingOutput ) m = re.search( r, pingOutput )
if m == None: if m == None:
@@ -288,7 +586,7 @@ def parsePing( pingOutput ):
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) ) sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
return sent, received return sent, received
def pingTest( hosts, verbose=False ): def pingTest( controllers, switches, hosts, verbose=False ):
"Test that each host can reach every other host." "Test that each host can reach every other host."
packets = 0 ; lost = 0 packets = 0 ; lost = 0
for node in hosts: for node in hosts:
@@ -313,309 +611,123 @@ def pingTest( hosts, verbose=False ):
print "%d%% packet loss (%d/%d lost)" % ( ploss, lost, packets ) print "%d%% packet loss (%d/%d lost)" % ( ploss, lost, packets )
flush() flush()
return ploss return ploss
def dumpNodes( nodes ):
"Dump ifconfig of each node."
for node in nodes:
print "*** Dumping node", node.name
print node.cmd( 'ip link show' )
print node.cmd( 'route' )
def ipGen( A, B, c, d ): def pingTestVerbose( controllers, switches, hosts ):
"Generate next IP class B IP address, starting at A.B.c.d" return pingTest( controllers, switches, hosts, verbose=True )
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
# Instead of routing, we could bridge or use "in-band" control
def checkUp( node ):
"Make sure node's first interface is up."
return 'UP' in node.cmd( 'ifconfig ' + node.intfs[ 0 ] )
def configRoutedControlNetwork( controller, switches ):
"Configure a routed control network on controller and switches."
cip = '10.0.0.1'
sips = ipGen( 10, 123, 0, 1)
print controller.name, '<->',
for switch in switches:
print switch.name, ; flush()
sip = sips.next()
sintf = switch.intfs[ 0 ]
node, cintf = switch.connection[ sintf ]
assert node == controller
controller.setIP( cintf, cip, '/24')
switch.setIP( sintf, sip, '/24' )
controller.setHostRoute( sip, cintf )
switch.setHostRoute( cip, sintf )
print
print "*** Testing control network"
while not checkUp( controller ):
print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
sleep( 1 )
for switch in switches:
while not checkUp( switch ):
print "*** Waiting for ", controller.intfs[ 0 ], "to come up"
sleep( 1 )
if pingTest( [ switch, controller ] ) != 0:
print "*** Error: control network test failed"
else:
return
exit( 1 )
def configHosts( hosts, ( a, b, c, d ) ):
"Configure a set of hosts, starting at IP address a.b.c.d"
ips = ipGen( 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` )
print host.name, ; flush()
print
def startController( controller, cprog='controller', cargs='ptcp:' ):
"Start <cprog cargs> on controller, logging to /tmp/cN.log"
cout = '/tmp/' + controller.name + '.log'
controller.cmdPrint( cprog + ' ' + cargs +
' 1> ' + cout + ' 2> ' + cout + ' &' )
def stopController( controller, cprog='controller' ):
"Stop controller cprog on controller"
controller.cmd( "kill %" + cprog )
def startOpenFlowU( switch, controller ):
"""Start OpenFlow reference user datapath on a switch,
logging to /tmp/sN-{ofd,ofp}.log"""
ofdlog = '/tmp/' + switch.name + '-ofd.log'
ofplog = '/tmp/' + switch.name + '-ofp.log'
switch.cmd( 'ifconfig lo up' )
intfs = switch.intfs[ 1 : ] # 0 is mgmt interface
switch.cmdPrint( 'ofdatapath -i ' + ','.join( intfs ) +
' ptcp: 1> ' + ofdlog + ' 2> '+ ofdlog + ' &' )
switch.cmdPrint( 'ofprotocol tcp:' + controller.IP() +
' tcp:localhost 1> ' + ofplog + ' 2>' + ofplog + '&' )
def stopOpenFlowU( switch ):
"Stop OpenFlow reference user datapath on a switch."
switch.cmd( "kill %ofdatapath" )
switch.cmd( "kill %ofprotocol" )
def dpgen():
"Generator for OpenFlow kernel datapath names."
dpCount = 0
while True:
yield 'nl:' + `dpCount`
dpCount += 1
def startOpenFlowK( switch, dp, controller):
"Start up a switch connected to an OpenFlow reference kernel datapath."
ofplog = '/tmp/' + switch.name + '-ofp.log'
switch.cmd( 'ifconfig lo up' )
# Delete local datapath if it exists;
# then create a new one monitoring the given interfaces
quietRun( 'dpctl deldp ' + dp )
switch.cmdPrint( 'dpctl adddp ' + dp )
switch.cmdPrint( 'dpctl addif ' + dp + ' ' + ' '.join( switch.intfs ) )
switch.dp = dp
# Become protocol daemon
switch.cmdPrint( 'exec ofprotocol' +
' ' + dp + ' tcp:127.0.0.1 1> ' + ofplog + ' 2>' + ofplog + '&' )
def stopOpenFlowK( switch ):
"Terminate a switch using OpenFlow reference kernel datapath."
quietRun( 'dpctl deldp ' + switch.dp )
for intf in switch.intfs: quietRun( 'ip link del ' + intf )
switch.terminate()
def stopOpenFlow( switch ):
if hasattr(switch, 'dp' ): stopOpenFlowK( switch )
else: stopOpenFlowU( switch )
# Test scenarios and topologies
def treeNet( controller, depth, fanout, snames=nameGen( 's' ),
hnames=nameGen( 'h' ), kernel=True ):
"""Return a tree network of the given depth and fanout as a triple:
( root, switches, hosts ), using the given switch and host
name generators, with the switches connected to the given
controller."""
if ( depth == 0 ):
host = Node( hnames.next() )
print host.name, ; flush()
return host, [], [ host ]
switch = Node( snames.next(), inNamespace=(not kernel) )
if not kernel: createLink( switch, controller )
print switch.name, ; flush()
switches, hosts = [ switch ], []
for i in range( 0, fanout ):
child, slist, hlist = treeNet(
controller, depth - 1, fanout, snames, hnames, kernel )
createLink( switch, child )
switches += slist
hosts += hlist
return switch, switches, hosts
def treeNetTest( depth, fanout, test, kernel=True ):
"""Create a tree network of the given depth and fanout, and
run test( controller, root, switches, hosts ) on it."""
if kernel: print "*** Using kernel datapath"
else: print "*** Using user datapath"
print "*** Creating controller"
controller = Node( 'c0', inNamespace=( not kernel ) )
print "*** Creating tree network depth:", depth, "fanout:", fanout
root, switches, hosts = treeNet( controller, depth, fanout, kernel=kernel )
print
if not kernel:
print "*** Configuring control network"
configRoutedControlNetwork( controller, switches )
else: dp = dpgen()
print "*** Configuring hosts"
configHosts( hosts, ( 192, 168, 123, 1 ) )
print "*** Starting reference controller"
startController( controller )
print "*** Starting switches"
for switch in switches:
if kernel: startOpenFlowK( switch, dp.next(), controller )
else: startOpenFlowU( switch, controller )
print "*** Running test"
test( controller, root, switches, hosts )
print "*** Stopping controller"
stopController( controller )
print "*** Stopping switches"
for switch in switches:
stopOpenFlow( switch )
def treePingTest( depth, fanout, kernel=True ):
"Run a ping test on a tree network with the given depth and fanout."
test = lambda c, r, s, hosts : pingTest( hosts, verbose=True )
treeNetTest( depth, fanout, test, kernel)
def iperf( hosts ): def iperf( hosts ):
"Run iperf between two hosts." "Run iperf between two hosts."
assert len( hosts ) == 2 assert len( hosts ) == 2
host1, host2 = hosts[ 0 ], hosts[ 1 ] host1, host2 = hosts[ 0 ], hosts[ 1 ]
dumpNodes( [ host1, host2 ] ) # dumpNodes( [ host1, host2 ] )
host1.cmdPrint( 'killall -9 iperf') host1.cmdPrint( 'killall -9 iperf') # XXX shouldn't be global killall
host1.cmdPrint( 'iperf -s &' ) host1.cmdPrint( 'iperf -s &' )
host2.cmdPrint( 'iperf -t 5 -c ' + host1.IP() ) host2.cmdPrint( 'iperf -t 5 -c ' + host1.IP() )
host1.cmdPrint( 'kill -9 %iperf' ) host1.cmdPrint( 'kill -9 %iperf' )
def iperfTest( depth=1, fanout=2, kernel=True ): def iperfTest( controllers, switches, hosts ):
"Simple iperf test between two hosts." "Simple iperf test between two hosts."
def test( c, r, s, hosts ): h0, hN = hosts[ 0 ], hosts[ -1 ]
h0, hN = hosts[ 0 ], hosts[ -1 ] print "*** iperfTest: Testing bandwidth between",
print "*** iperfTest: Testing bandwidth between", print h0.name, "and", hN.name
print h0.name, "and", hN.name return iperf( [ h0, hN] )
return iperf( [ h0, hN] )
treeNetTest( depth, fanout, test, kernel ) # 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"
print "available commands are:", self.cmds
def nodes( self, args ):
"List available nodes"
print "available nodes are:", [ 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:
print switch.name, "<->",
for intf in switch.intfs:
node, rintf = switch.connection[ intf ]
print node.name,
print
def iperf( self, args ):
print "iperf: got args", args
if len( args ) != 2:
print "usage: iperf <h1> <h2>"
return
for host in args:
if host not in self.nodemap:
print "iperf: cannot find host:", host
return
iperf( [ self.nodemap[ h ] for h in args ] )
# Interpreter
def run( self ):
"Read and execute commands."
print "*** cli: starting"
while True:
print "mininet> ", ; flush()
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
print "***", node.name, ": running", rest
node.sendCmd( rest )
while True:
try:
done, data = node.monitor()
print data,
if done: break
except KeyboardInterrupt: node.sendInt()
print
elif first == '': pass
elif first in [ 'exit', 'quit' ]: break
elif first == '?': self.help( rest )
else: print "cli: unknown node or command: <", first, ">"
print "*** cli: exiting"
def fixLimits(): def fixLimits():
"Fix ridiculously small resource limits." "Fix ridiculously small resource limits."
setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) ) setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) ) setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
# Simple CLI def init():
# Note: this script must be run as root
def cliHelp( nodemap, c, s, h, args ): # Perhaps we should do so automatically!
"Semi-useful help for CLI" if os.getuid() != 0:
print "available commands are:", cliCmds.keys() print "*** Mininet must run as root."; exit( 1 )
fixLimits()
def cliNodes( nodemap, c, s, h, args ):
"List available nodes"
print "available nodes are:", nodemap.keys()
def cliSh( nodemap, c, s, h, args ):
"Run an external shell command"
call( [ bash, '-c', args ] )
def cliPingTest( map, c, s, hosts, args ):
pingTest( hosts, verbose=True )
def cliNet( map, c, switches, h, args ):
for switch in switches:
print switch.name, "<->",
for intf in switch.intfs:
node, rintf = switch.connection[ intf ]
print node.name,
print
def cliIperf( map, c, switches, h, args ):
print "iperf: got args", args
if len( args ) != 2:
print "usage: iperf <h1> <h2>"
return
for host in args:
if host not in map:
print "iperf: cannot find host:", host
return
iperf( [ map[ h ] for h in args ] )
cliCmds = { '?': cliHelp, 'help': cliHelp, 'net': cliNet, 'nodes': cliNodes,
'pingtest': cliPingTest, 'iperf': cliIperf, 'sh': cliSh,
'exit': None }
def cli( controllers, switches, hosts ):
"Simple command-line interface to talk to nodes."
print "*** cli: starting"
nodemap = {}
nodes = controllers + switches + hosts
for node in nodes:
nodemap[ node.name ] = node
while True:
print "mininet> ", ; flush()
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 cliCmds: cliCmds[ first ](
nodemap, controllers, switches, hosts, rest )
elif first in nodemap and rest != []:
node = nodemap[ first ]
# Substitute IP addresses for node names in command
rest = [ nodemap[ arg ].IP() if arg in nodemap else arg
for arg in rest ]
rest = ' '.join( rest )
# Interactive commands don't work yet, and
# there are still issues with control-c
print "***", node.name, ": running", rest
node.sendCmd( rest )
while True:
try:
done, data = node.monitor()
print data,
if done: break
except KeyboardInterrupt: node.sendInt()
print
elif first == '': pass
elif first in [ 'exit', 'quit' ]: break
else: print "cli: unknown node or command: <", first, ">"
print "*** cli: exiting"
def treeInteract( depth, fanout, kernel=True ):
"Create a tree network and start the CLI."
interact = lambda c, r, s, h : cli( [ c ], s, h )
treeNetTest( depth, fanout, interact, kernel )
if __name__ == '__main__': if __name__ == '__main__':
fixLimits() init()
# for kernel in [ False, True ]: # for kernel in [ False, True ]:
# treePingTest( depth=3, fanout=4, kernel=kernel ) # TreeNet( depth=3, fanout=4, kernel=kernel).run( pingTest )
# treeInteract( depth=1, fanout=2, kernel=False ) TreeNet( depth=2, fanout=32).run( Cli )
# iperfTest( depth=1, fanout=2, kernel=kernel ) # LinearNet( switchCount=100 ).run( iperfTest)
treeInteract( depth=1, fanout=2, kernel=False ) # GridNet( 2, 2 ).run( Cli )