New configuration scheme and support for CPU limits (RT).

This commit is contained in:
Bob Lantz
2012-03-06 23:52:00 -08:00
parent 94c02695fd
commit 84a91a14a4
3 changed files with 434 additions and 275 deletions
+60 -34
View File
@@ -39,10 +39,12 @@ class BasicIntf( object ):
self.name = name
self.link = link
self.mac, self.ip = None, None
# Add to node (and move ourselves if necessary )
node.addIntf( self )
self.config( **kwargs )
def cmd( self, *args, **kwargs ):
self.node.cmd( *args, **kwargs )
return self.node.cmd( *args, **kwargs )
def ifconfig( self, *args ):
"Configure ourselves using ifconfig"
@@ -93,22 +95,43 @@ class BasicIntf( object ):
return "UP" in self.ifconfig()
# Map of config params to config methods
# Perhaps this could be more graceful, but it
# is flexible
configMap = { 'mac': 'setMAC',
'ip': 'setIP',
'ifconfig': 'ifconfig' }
# The reason why we configure things in this way is so
# That the parameters can be listed and documented in
# the config method.
# Dealing with subclasses and superclasses is slightly
# annoying, but at least the information is there!
def config( self, **params ):
"Configure interface based on parameters"
self.__dict__.update(**params)
for name, value in params.iteritems():
method = self.configMap.get( name, None )
if method:
if type( value ) is str:
value = value.split( ',' )
method( value )
def setParam( self, result, method, **param ):
"""Internal method: configure single parameter
result: dict of results to update
method: config method
param: foo=bar (ignore if bar=None)"""
name, value = param.items()[ 0 ]
if value is None:
return
if type( value ) is list:
result[ name ] = getattr( self, method )( *value )
elif type( value ) is dict:
result[ name ] = getattr( self, method )( **value )
else:
result[ name ] = getattr( self, method )( value )
def config( self, mac=None, ip=None, ifconfig=None,
defaultRoute=None, **params):
"""Configure Node according to (optional) parameters:
mac: MAC address
ip: IP address
ifconfig: arbitrary interface configuration
Subclasses should override this method and call
the parent class's config(**params)"""
# If we were overriding this method, we would call
# the superclass config method here as follows:
# r = Parent.config( **params )
r = {}
self.setParam( r, 'setMAC', mac=mac )
self.setParam( r, 'setIP', ip=ip )
self.setParam( r, 'ifconfig', ifconfig=ifconfig )
return r
def delete( self ):
"Delete interface"
@@ -125,10 +148,10 @@ class TCIntf( BasicIntf ):
def config( self, bw=None, delay=None, loss=0, disable_gro=True,
speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False,
enable_red=False, max_queue_size=1000, **kwargs ):
enable_red=False, max_queue_size=1000, **params ):
"Configure the port and set its properties."
BasicIntf.config( self, **kwargs)
result = BasicIntf.config( self, **params)
# disable GRO
if disable_gro:
@@ -153,7 +176,7 @@ class TCIntf( BasicIntf ):
delay = '0ms'
if bw is not None and delay is not None:
info( self, '(bw %.2fMbit, delay %s, loss %d%%)\n' %
info( self, '(bw %.2fMbit, delay %s, loss %d%%) ' %
( bw, delay, loss ) )
# BL: hmm... what exactly is this???
@@ -209,8 +232,11 @@ class TCIntf( BasicIntf ):
debug(" *** executing command: %s\n" % c)
return self.cmd(c)
outputs = [ doConfigPort(cmd) for cmd in cmds ]
debug( "outputs: %s\n" % outputs )
tcoutputs = [ doConfigPort(cmd) for cmd in cmds ]
debug( "cmds:", cmds, '\n' )
debug( "outputs:", tcoutputs, '\n' )
result[ 'tcoutputs'] = tcoutputs
return result
Intf = TCIntf
@@ -220,14 +246,18 @@ class Link( object ):
Other types of links could be tunnels, link emulators, etc.."""
def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None,
intf=Intf, params1={}, params2={} ):
intf=Intf, cls1=None, cls2=None, params1={}, params2={} ):
"""Create veth link to another node, making two new interfaces.
node1: first node
node2: second node
port1: node1 port number (optional)
port2: node2 port number (optional)
intf: default interface class/constructor
cls1, cls2: optional interface-specific constructors
intfName1: node1 interface name (optional)
intfName2: node2 interface name (optional)"""
intfName2: node2 interface name (optional)
params1: parameters for interface 1
params2: parameters for interface 2"""
# This is a bit awkward; it seems that having everything in
# params would be more orthogonal, but being able to specify
# in-line arguments is more convenient!
@@ -240,11 +270,13 @@ class Link( object ):
if not intfName2:
intfName2 = self.intfName( node2, port2 )
self.makeIntfPair( intfName1, intfName2 )
intf1 = intf( name=intfName1, node=node1, link=self, **params1 )
intf2 = intf( name=intfName2, node=node2, link=self, **params2 )
# Add to nodes
node1.addIntf( intf1 )
node2.addIntf( intf2 )
if not cls1:
cls1 = intf
if not cls2:
cls2 = intf
intf1 = cls1( name=intfName1, node=node1, link=self, **params1 )
intf2 = cls2( name=intfName2, node=node2, link=self, **params2 )
# All we are is dust in the wind, and our two interfaces
self.intf1, self.intf2 = intf1, intf2
@classmethod
@@ -267,9 +299,3 @@ class Link( object ):
def __str__( self ):
return '%s<->%s' % ( self.intf1, self.intf2 )
+129 -120
View File
@@ -94,8 +94,7 @@ from time import sleep
from mininet.cli import CLI
from mininet.log import info, error, debug, output
from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller
from mininet.node import ControllerParams
from mininet.node import Host, OVSKernelSwitch, Controller
from mininet.link import Link
from mininet.util import quietRun, fixLimits
from mininet.util import createLink, macColonHex, ipStr, ipParse
@@ -106,7 +105,6 @@ class Mininet( object ):
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
controller=Controller, link=Link,
cparams=ControllerParams( '10.0.0.0', 8 ),
build=True, xterms=False, cleanup=False,
inNamespace=False,
autoSetMacs=False, autoStaticArp=False, listenPort=None ):
@@ -116,12 +114,12 @@ class Mininet( object ):
host: default Host class/constructor
controller: default Controller class/constructor
link: default Link class/constructor
cparams: ControllerParams object
ipBase: base IP address for hosts,
build: build now from topo?
xterms: if build now, spawn xterms?
cleanup: if build now, cleanup before creating?
inNamespace: spawn switches and controller in net namespaces?
autoSetMacs: set MAC addrs from topo?
autoSetMacs: set MAC addrs from topo dpid?
autoStaticArp: set all-pairs static MAC addrs?
listenPort: base listening port to open; will be incremented for
each additional switch in the net if inNamespace=False"""
@@ -129,7 +127,6 @@ class Mininet( object ):
self.host = host
self.controller = controller
self.link = link
self.cparams = cparams
self.topo = topo
self.inNamespace = inNamespace
self.xterms = xterms
@@ -141,13 +138,13 @@ class Mininet( object ):
self.hosts = []
self.switches = []
self.controllers = []
self.nameToNode = {} # name to Node (Host/Switch) objects
self.idToNode = {} # dpid to Node (Host/Switch) objects
self.dps = 0 # number of created kernel datapaths
self.terms = [] # list of spawned xterm processes
init()
switch.setup()
init() # Initialize Mininet if necessary
self.built = False
if topo and build:
@@ -157,17 +154,15 @@ class Mininet( object ):
# The specific items for host/switch/etc. should probably be
# handled in the node classes rather than here!!
def addHost( self, name, mac=None, ip=None, host=None, **params ):
def addHost( self, name, host=None, **params ):
"""Add host.
name: name of host to add
mac: default MAC address for intf 0
ip: default IP address for intf 0
host: custom host constructor (optional)
params: parameters for host
returns: added host"""
if not host:
host = self.host
defaults = { 'defaultMAC': mac, 'defaultIP': ip }
defaults.update( params )
h = host( name, **defaults)
h = host( name, **params)
self.hosts.append( h )
self.nameToNode[ name ] = h
return h
@@ -175,19 +170,17 @@ class Mininet( object ):
def addSwitch( self, name, switch=None, **params ):
"""Add switch.
name: name of switch to add
switch: custom switch constructor (optional)
returns: added switch
side effect: increments listenPort and dps ivars."""
side effect: increments listenPort ivar ."""
defaults = { 'listenPort': self.listenPort,
'inNamespace': self.inNamespace }
defaults.update( params )
if not switch:
switch = self.switch
if switch != UserSwitch:
defaults[ 'dps' ] = self.dps
defaults.update( params )
sw = self.switch( name, **defaults )
if not self.inNamespace and self.listenPort:
self.listenPort += 1
self.dps += 1
self.switches.append( sw )
self.nameToNode[ name ] = sw
return sw
@@ -203,122 +196,81 @@ class Mininet( object ):
self.nameToNode[ name ] = controller_new
return controller_new
# 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.
#
# 2. If we can get unix domain sockets to work, we can use them
# instead of an explicit control network.
#
# 3. Instead of routing, we could bridge or use 'in-band' control.
#
# 4. Even if we dispense with this in general, it could still be
# useful for people who wish to simulate a separate control
# network (since real networks may need one!)
#
# 5. Basically nobody ever uses this method, so perhaps it should be moved
# out of this core class.
def configureControlNetwork( self ):
"Configure control network."
self.configureRoutedControlNetwork()
# We still need to figure out the right way to pass
# in the control network location.
def configureRoutedControlNetwork( self, ip='192.168.123.1',
prefixLen=16 ):
"""Configure a routed control network on controller and switches.
For use with the user datapath only right now."""
controller = self.controllers[ 0 ]
info( controller.name + ' <->' )
cip = ip
snum = ipParse( ip )
for switch in self.switches:
info( ' ' + switch.name )
sintf, cintf = createLink( switch, controller )
snum += 1
while snum & 0xff in [ 0, 255 ]:
snum += 1
sip = ipStr( snum )
controller.setIP( cintf, cip, prefixLen )
switch.setIP( sintf, sip, prefixLen )
controller.setHostRoute( sip, cintf )
switch.setHostRoute( cip, sintf )
info( '\n' )
info( '*** Testing control network\n' )
while not controller.intfIsUp( cintf ):
info( '*** Waiting for', cintf, 'to come up\n' )
sleep( 1 )
for switch in self.switches:
while not switch.intfIsUp( sintf ):
info( '*** Waiting for', sintf, 'to come up\n' )
sleep( 1 )
if self.ping( hosts=[ switch, controller ] ) != 0:
error( '*** Error: control network test failed\n' )
exit( 1 )
info( '\n' )
def configHosts( self ):
"Configure a set of hosts."
# params were: hosts, ips
for host in self.hosts:
hintf = host.defaultIntf()
host.setIP( host.defaultIP, self.cparams.prefixLen, hintf )
host.setDefaultRoute( hintf )
host.configDefault( defaultRoute=host.defaultIntf )
# You're low priority, dude!
quietRun( 'renice +18 -p ' + repr( host.pid ) )
# BL: do we want to do this here or not?
# May not make sense if we have CPU lmiting...
# quietRun( 'renice +18 -p ' + repr( host.pid ) )
info( host.name + ' ' )
info( '\n' )
def buildFromTopo( self, topo ):
def buildFromTopo( self, topo=None ):
"""Build mininet from a topology object
At the end of this function, everything should be connected
and up."""
if not topo:
topo = self.topo()
def addNode( prefix, addMethod, nodeId ):
"Add a host or a switch."
"Add a host or a switch from topo"
name = prefix + topo.name( nodeId )
# MAC and IP should probably be from nodeInfo...
mac = macColonHex( nodeId ) if self.setMacs else None
ip = topo.ip( nodeId )
ni = topo.nodeInfo( nodeId )
node = addMethod( name, cls=ni.cls, mac=mac, ip=ip, **ni.params )
# Default IP and MAC addresses
defaults = { 'ip': topo.ip( nodeId ) }
if self.autoSetMacs:
defaults[ 'mac'] = macColonHex( nodeId )
defaults.update( ni.params )
node = addMethod( name, cls=ni.cls, **defaults )
self.idToNode[ nodeId ] = node
info( name + ' ' )
def addLink( srcId, dstId, link=None ):
"Add a link from topo"
src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
srcPort, dstPort = topo.port( srcId, dstId )
ei = topo.edgeInfo( srcId, dstId )
link = getattr( ei, 'cls', link )
params = ei.params
if not link:
link = self.link
info( '(%s, %s) ' % ( src.name, dst.name ) )
link( src, dst, srcPort, dstPort, **params )
# Possibly we should clean up here and/or validate
# the topo
if self.cleanup:
pass
info( '*** Adding controller\n' )
self.addController( 'c0' )
info( '*** Creating network\n' )
if not self.controllers:
# Add a default controller
info( '*** Adding controller\n' )
self.addController( 'c0' )
info( '*** Adding hosts:\n' )
for hostId in sorted( topo.hosts() ):
addNode( 'h', self.addHost, hostId )
info( '\n*** Adding switches:\n' )
for switchId in sorted( topo.switches() ):
addNode( 's', self.addSwitch, switchId)
addNode( 's', self.addSwitch, switchId )
info( '\n*** Adding links:\n' )
for srcId, dstId in sorted( topo.edges() ):
src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
srcPort, dstPort = topo.port( srcId, dstId )
ei = topo.edgeInfo( srcId, dstId )
link, params = ei.cls, ei.params
if not link:
link = self.link
link( src, dst, srcPort, dstPort, **params )
info( '(%s, %s) ' % ( src.name, dst.name ) )
addLink( srcId, dstId )
info( '\n' )
def configureControlNetwork( self ):
error( "configureControlNetwork: override in subclass, or use"
"MininetWithControlNet class" )
def build( self ):
"Build mininet."
if self.topo:
@@ -330,8 +282,6 @@ class Mininet( object ):
self.configHosts()
if self.xterms:
self.startTerms()
if self.autoSetMacs:
self.setMacs()
if self.autoStaticArp:
self.staticArp()
self.built = True
@@ -346,17 +296,10 @@ class Mininet( object ):
def stopXterms( self ):
"Kill each xterm."
# Kill xterms
for term in self.terms:
os.kill( term.pid, signal.SIGKILL )
cleanUpScreens()
def setMacs( self ):
"""Set MAC addrs to correspond to default MACs on hosts.
Assume that the host only has one interface."""
for host in self.hosts:
host.setMAC( host.intfs[ 0 ], host.defaultMAC )
def staticArp( self ):
"Add all-pairs ARP entries to remove the need to handle broadcast."
for src in self.hosts:
@@ -384,18 +327,19 @@ class Mininet( object ):
self.stopXterms()
info( '*** Stopping %i hosts\n' % len( self.hosts ) )
for host in self.hosts:
info( '%s ' % host.name )
info( host.name + ' ' )
host.terminate()
info( '\n' )
info( '*** Stopping %i switches\n' % len( self.switches ) )
for switch in self.switches:
info( switch.name )
info( switch.name + ' ' )
switch.stop()
info( '\n' )
info( '*** Stopping %i controllers\n' % len( self.controllers ) )
for controller in self.controllers:
info( controller.name + ' ' )
controller.stop()
info( '*** Done\n' )
info( '\n*** Done\n' )
def run( self, test, *args, **kwargs ):
"Perform a complete start/test/stop cycle."
@@ -429,6 +373,9 @@ class Mininet( object ):
if not ready and timeoutms >= 0:
yield None, None
# XXX These test methods should be moved out of this class.
# Probably we should create a tests.py for them
@staticmethod
def _parsePing( pingOutput ):
"Parse ping output and return packets sent, received."
@@ -543,6 +490,8 @@ class Mininet( object ):
output( '*** Results: %s\n' % result )
return result
# BL: I think this can be rewritten now that we have
# a real link class.
def configLinkStatus( self, src, dst, status ):
"""Change status of src <-> dst links.
src: node name
@@ -573,6 +522,70 @@ class Mininet( object ):
return result
class MininetWithControlNet( Mininet ):
"""Control network support:
Create an explicit control network. Currently this is only
used/usable with the user datapath.
Notes:
1. If the controller and switches are in the same (e.g. root)
namespace, they can just use the loopback connection.
2. If we can get unix domain sockets to work, we can use them
instead of an explicit control network.
3. Instead of routing, we could bridge or use 'in-band' control.
4. Even if we dispense with this in general, it could still be
useful for people who wish to simulate a separate control
network (since real networks may need one!)
5. Basically nobody ever used this code, so it has been moved
into its own class."""
def configureControlNetwork( self ):
"Configure control network."
self.configureRoutedControlNetwork()
# We still need to figure out the right way to pass
# in the control network location.
def configureRoutedControlNetwork( self, ip='192.168.123.1',
prefixLen=16 ):
"""Configure a routed control network on controller and switches.
For use with the user datapath only right now."""
controller = self.controllers[ 0 ]
info( controller.name + ' <->' )
cip = ip
snum = ipParse( ip )
for switch in self.switches:
info( ' ' + switch.name )
sintf, cintf = createLink( switch, controller )
snum += 1
while snum & 0xff in [ 0, 255 ]:
snum += 1
sip = ipStr( snum )
controller.setIP( cintf, cip, prefixLen )
switch.setIP( sintf, sip, prefixLen )
controller.setHostRoute( sip, cintf )
switch.setHostRoute( cip, sintf )
info( '\n' )
info( '*** Testing control network\n' )
while not controller.intfIsUp( cintf ):
info( '*** Waiting for', cintf, 'to come up\n' )
sleep( 1 )
for switch in self.switches:
while not switch.intfIsUp( sintf ):
info( '*** Waiting for', sintf, 'to come up\n' )
sleep( 1 )
if self.ping( hosts=[ switch, controller ] ) != 0:
error( '*** Error: control network test failed\n' )
exit( 1 )
info( '\n' )
# pylint thinks inited is unused
# pylint: disable-msg=W0612
@@ -585,10 +598,6 @@ def init():
# Perhaps we should do so automatically!
print "*** Mininet must run as root."
exit( 1 )
# If which produces no output, then mnexec is not in the path.
# May want to loosen this to handle mnexec in the current dir.
if not quietRun( 'which mnexec' ):
raise Exception( "Could not find mnexec - check $PATH" )
fixLimits()
init.inited = True
+245 -121
View File
@@ -48,7 +48,8 @@ import select
from subprocess import Popen, PIPE, STDOUT
from mininet.log import info, error, debug
from mininet.util import quietRun, errRun, moveIntf, isShellBuiltin
from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin
from mininet.util import numCores
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
from mininet.link import Link
@@ -58,21 +59,74 @@ class Node( object ):
"""A virtual network node is simply a shell in a network namespace.
We communicate with it using pipes."""
portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
def __init__( self, name, inNamespace=True, **params ):
"""name: name of node
inNamespace: in network namespace?
params: Node parameters (see config() for details)"""
# Make sure class actually works
self.checkSetup()
self.name = name
self.inNamespace = inNamespace
# Stash configuration parameters for future reference
self.params = params
self.intfs = {} # dict of port numbers to interfaces
self.ports = {} # dict of interfaces to port numbers
# replace with Port objects, eventually ?
self.nameToIntf = {} # dict of interface names to Intfs
# Start command interpreter shell
self.shell = None
self.startShell()
# File descriptor to node mapping support
# Class variables and methods
inToNode = {} # mapping of input fds to nodes
outToNode = {} # mapping of output fds to nodes
portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
@classmethod
def fdToNode( cls, fd ):
"""Return node corresponding to given file descriptor.
fd: file descriptor
returns: node"""
node = cls.outToNode.get( fd )
return node or cls.inToNode.get( fd )
def __init__( self, name, inNamespace=True,
defaultMAC=None, defaultIP=None, **kwargs ):
"""name: name of node
inNamespace: in network namespace?
defaultMAC: default MAC address for intf 0
defaultIP: default IP address for intf 0"""
self.name = name
self.inNamespace = inNamespace
self.defaultIP = defaultIP
self.defaultMAC = defaultMAC
# Automatic class setup support
isSetup = False;
@classmethod
def checkSetup( cls ):
"Make sure our class and superclasses are set up"
while cls and not getattr( cls, 'isSetup', True ):
cls.setup()
cls.isSetup = True
# Make pylint happy
cls = getattr( type( cls ), '__base__', None )
@classmethod
def setup( cls ):
"Make sure our class dependencies are available"
pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
def cleanup( self ):
"Help python collect its garbage."
self.shell = None
# Command support via shell process in namespace
def startShell( self ):
"Start a shell process for running commands"
if self.shell:
error( "%s: shell is already running" )
return
opts = '-cdp'
if self.inNamespace:
opts += 'n'
@@ -89,31 +143,12 @@ class Node( object ):
# using select.poll()
self.outToNode[ self.stdout.fileno() ] = self
self.inToNode[ self.stdin.fileno() ] = self
self.intfs = {} # dict of port numbers to interfaces
self.ports = {} # dict of interfaces to port numbers
# replace with Port objects, eventually ?
self.nameToIntf = {} # dict of interface names to Intfs
self.execed = False
self.lastCmd = None
self.lastPid = None
self.readbuf = ''
self.waiting = False
# Stash additional information as desired
self.args = kwargs
@classmethod
def fdToNode( cls, fd ):
"""Return node corresponding to given file descriptor.
fd: file descriptor
returns: node"""
node = Node.outToNode.get( fd )
return node or Node.inToNode.get( fd )
def cleanup( self ):
"Help python collect its garbage."
self.shell = None
# Subshell I/O, commands and control
def read( self, bytes=1024 ):
"""Buffered read from node, non-blocking.
bytes: maximum number of bytes to return"""
@@ -267,10 +302,10 @@ class Node( object ):
self.intfs[ port ] = intf
self.ports[ intf ] = port
self.nameToIntf[ intf.name ] = intf
info( '\n' )
info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
debug( '\n' )
debug( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
if self.inNamespace:
info( 'moving', intf, 'into namespace for', self.name, '\n' )
debug( 'moving', intf, 'into namespace for', self.name, '\n' )
moveIntf( intf.name, self )
def defaultIntf( self ):
@@ -326,13 +361,15 @@ class Node( object ):
intf: string, interface name"""
return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
def setDefaultRoute( self, intf ):
def setDefaultRoute( self, intf=None ):
"""Set the default route to go through intf.
intf: string, interface name"""
if not intf:
intf = self.defaultIntf()
self.cmd( 'ip route flush root 0/0' )
return self.cmd( 'route add default %s' % intf )
# Convenience methods
# Convenience and configuration methods
def setMAC( self, mac, intf=''):
"""Set the MAC address for an interface.
@@ -361,6 +398,49 @@ class Node( object ):
"Check if an interface is up."
return self.intf( intf ).isUp()
# The reason why we configure things in this way is so
# That the parameters can be listed and documented in
# the config method.
# Dealing with subclasses and superclasses is slightly
# annoying, but at least the information is there!
def setParam( self, results, method, **param ):
"""Internal method: configure single parameter"""
name, value = param.items()[ 0 ]
f = getattr( self, method, None )
if not value or not f:
return
if type( value ) is list:
result = f( *value )
elif type( value ) is dict:
result = f( **value )
else:
result = f( value )
results[ name ] = result
def config( self, mac=None, ip=None, ifconfig=None,
defaultRoute=None, **params):
"""Configure Node according to (optional) parameters:
mac: MAC address for default interface
ip: IP address for default interface
ifconfig: arbitrary interface configuration
Subclasses should override this method and call
the parent class's config(**params)"""
# If we were overriding this method, we would call
# the superclass config method here as follows:
# r = Parent.config( **params )
r = {}
self.setParam( r, 'setMAC', mac=mac )
self.setParam( r, 'setIP', ip=ip )
self.setParam( r, 'ifconfig', ifconfig=ifconfig )
self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute )
return r
def configDefault( self, **moreParams ):
"Configure with default parameters"
self.params.update( moreParams )
self.config( **self.params )
# This is here for backward compatibility
def linkTo( self, node, link=Link ):
"""(Deprecated) Link to another node
@@ -382,9 +462,94 @@ class Node( object ):
self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
class Host( Node ):
"A host is simply a Node."
class CPULimitedHost( Node ):
"CPU limited host"
def __init__( self, *args, **kwargs ):
Node.__init__( self, *args, **kwargs )
# Create a cgroup and move shell into it
cgroup = 'cpu,cpuacct:/' + self.name
errFail( 'cgcreate -g ' + cgroup )
errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) )
self.sched = 'rt'
self.period_us = 10000
self.rtset = False
def cgroupSet( self, param, value, resource='cpu' ):
"Set a cgroup parameter and return its value"
cmd = 'cgset -r %s.%s=%s /%s' % (
resource, param, value, self.name )
return quietRun( cmd )
def cgroupGet( self, param, resource='cpu' ):
cmd = 'cgget -r %s.%s /%s' % (
resource, param, self.name )
return quietRun( cmd ).split()[ -1 ]
def chrt( self, prio=20 ):
"Set RT scheduling priority"
quietRun( 'chrt -p %s %s' % ( prio, self.pid ) )
result = quietRun( 'chrt -p %s' % self.pid )
firstline = result.split( '\n' )[ 0 ]
lastword = firstline.split( ' ' )[ -1 ]
return lastword
def setCPUFrac( self, f=-1 ):
"Set overall CPU fraction for this host"
if ( f < 0 or f is None):
# Reset to unlimited
f = -1
# Set new period and quota
pstr, qstr = 'rt_period_us', 'rt_runtime_us'
quota = int( self.period_us * f * numCores() )
self.cgroupSet( pstr, self.period_us )
nquota = int ( self.cgroupGet( qstr ) )
self.cgroupSet( qstr, quota )
nperiod = int( self.cgroupGet( pstr ) )
# Set RT priority
nchrt = self.chrt( prio=20 )
# Check to make sure it worked
if 'SCHED_RR' not in nchrt:
error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
if nperiod != self.period_us:
error( '*** error: period is %s rather than %s\n' % (
nperiod, self.period_us ) )
if nquota != quota:
error( '*** error: quota is %s rather than %s\n' % (
nquota, quota ) )
def config( self, cpu=None, **params ):
"""cpu: desired overall system CPU fraction
params: parameters for Node.config()"""
r = Node.config( self, **params )
self.setParam( r, 'setCPUFrac', cpu=cpu )
return r
Host = CPULimitedHost
# Some important things to note:
#
# The "IP" address which we assign to the switch is not
# an "IP address for the switch" in the sense of IP routing.
# Rather, it is the IP address for a control interface if
# (and only if) you happen to be running the switch in a
# namespace, which is something we currently don't support
# for OVS!
#
# In general, you NEVER want to attempt to use Linux's
# network stack (i.e. ifconfig) to "assign" an IP address or
# MAC address to a switch data port. Instead, you "assign"
# the IP and MAC addresses in the controller by specifying
# packets that you want to receive or send. The "MAC" address
# reported by ifconfig for a switch data port is essentially
# meaningless.
#
# So, I'm tyring changing the API to make it
# impossible to try this, since it will not work, since nobody
# ever makes separate control networks in Mininet, and indeed
# we don't even support running OVS in a namespace.
class Switch( Node ):
"""A Switch is a Node that is running (or has execed?)
@@ -392,19 +557,31 @@ class Switch( Node ):
portBase = SWITCH_PORT_BASE # 0 for OF < 1.0, 1 for OF >= 1.0
def __init__( self, name, opts='', listenPort=None, **kwargs):
Node.__init__( self, name, **kwargs )
def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
"""dpid: dpid for switch (or None for default)
opts: additional switch options
listenPort: port to listen on for dpctl connections"""
Node.__init__( self, name, **params )
self.dpid = dpid if dpid else self.defaultDpid()
self.opts = opts
self.listenPort = listenPort
if self.listenPort:
self.opts += ' --listen=ptcp:%i ' % self.listenPort
self.controlIntf = None
def defaultDpid( self ):
"Derive dpid from switch name, s1 -> 1"
dpid = int( re.findall( '\d+', self.name )[ 0 ] )
dpid = hex( dpid )[ 2: ]
dpid = '0' * ( 12 - len( dpid ) ) + dpid
return dpid
def defaultIntf( self ):
"Return interface for HIGHEST port"
ports = self.intfs.keys()
if ports:
intf = self.intfs[ max( ports ) ]
return intf
"Return control interface, if any"
if not self.inNamespace:
error( "error: tried to access control interface of "
" switch %s in root namespace" % self.name )
return self.controlIntf
def sendCmd( self, *cmd, **kwargs ):
"""Send command to Node.
@@ -440,15 +617,13 @@ class UserSwitch( Switch ):
ofdlog = '/tmp/' + self.name + '-ofd.log'
ofplog = '/tmp/' + self.name + '-ofp.log'
self.cmd( 'ifconfig lo up' )
mac_str = ''
if self.defaultMAC:
# ofdatapath expects a string of hex digits with no colons.
mac_str = ' -d ' + ''.join( self.defaultMAC.split( ':' ) )
intfs = sorted( self.intfs.values() )
ports = sorted( self.ports.values() )
intfs = [ str( self.intfs[ p ] ) for p in ports ]
if self.inNamespace:
intfs = intfs[ :-1 ]
self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
' punix:/tmp/' + self.name + mac_str + ' --no-slicing ' +
' punix:/tmp/' + self.name + ' -d ' + self.dpid +
' --no-slicing ' +
' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
self.cmd( 'ofprotocol unix:/tmp/' + self.name +
' tcp:%s:%d' % ( controller.IP(), controller.port ) +
@@ -461,61 +636,6 @@ class UserSwitch( Switch ):
self.cmd( 'kill %ofprotocol' )
self.deleteIntfs()
class KernelSwitch( Switch ):
"""Kernel-space switch.
Currently only works in root namespace."""
def __init__( self, name, dp=None, **kwargs ):
"""Init.
name: name for switch
dp: netlink id (0, 1, 2, ...)
defaultMAC: default MAC as string; random value if None"""
Switch.__init__( self, name, **kwargs )
self.dp = 'nl:%i' % dp
self.intf = 'of%i' % dp
if self.inNamespace:
error( "KernelSwitch currently only works"
" in the root namespace." )
exit( 1 )
@staticmethod
def setup():
"Ensure any dependencies are loaded; if not, try to load them."
pathCheck( 'ofprotocol',
moduleName='the OpenFlow reference kernel switch'
' (openflow.org) (NOTE: not available in OpenFlow 1.0!)' )
moduleDeps( subtract=OVS_KMOD, add=OF_KMOD )
def start( self, controllers ):
"Start up 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.cmd( 'dpctl adddp ' + self.dp )
if self.defaultMAC:
self.cmd( 'ifconfig', self.intf, 'hw', 'ether', self.defaultMAC )
ports = sorted( self.ports.values() )
if len( ports ) != ports[ -1 ] + 1 - self.portBase:
raise Exception( 'only contiguous, zero-indexed port ranges'
'supported: %s' % ports )
intfs = [ self.intfs[ port ] for port in ports ]
self.cmd( 'dpctl', 'addif', self.dp, ' '.join( intfs ) )
# Run protocol daemon
controller = controllers[ 0 ]
self.cmd( 'ofprotocol ' + self.dp +
' tcp:%s:%d' % ( controller.IP(), controller.port ) +
' --fail=closed ' + self.opts +
' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
self.execed = False
def stop( self ):
"Terminate kernel datapath."
quietRun( 'dpctl deldp ' + self.dp )
self.cmd( 'kill %ofprotocol' )
self.deleteIntfs()
class OVSLegacyKernelSwitch( Switch ):
"""Open VSwitch legacy kernel-space switch using ovs-openflowd.
@@ -549,12 +669,6 @@ class OVSLegacyKernelSwitch( Switch ):
# then create a new one monitoring the given interfaces
quietRun( 'ovs-dpctl del-dp ' + self.dp )
self.cmd( 'ovs-dpctl add-dp ' + self.dp )
mac_str = ''
if self.defaultMAC:
# ovs-openflowd expects a string of exactly 16 hex digits with no
# colons.
mac_str = ' --datapath-id=0000' + \
''.join( self.defaultMAC.split( ':' ) ) + ' '
ports = sorted( self.ports.values() )
if len( ports ) != ports[ -1 ] + 1 - self.portBase:
raise Exception( 'only contiguous, one-indexed port ranges '
@@ -565,7 +679,8 @@ class OVSLegacyKernelSwitch( Switch ):
controller = controllers[ 0 ]
self.cmd( 'ovs-openflowd ' + self.dp +
' tcp:%s:%d' % ( controller.IP(), controller.port ) +
' --fail=secure ' + self.opts + mac_str +
' --fail=secure ' + self.opts +
' --datapath-id=' + self.dpid +
' 1>' + ofplog + ' 2>' + ofplog + '&' )
self.execed = False
@@ -579,13 +694,17 @@ class OVSLegacyKernelSwitch( Switch ):
class OVSSwitch( Switch ):
"Open vSwitch switch. Depends on ovs-vsctl."
def __init__( self, name, dp=None, **kwargs ):
def __init__( self, name, **params ):
"""Init.
name: name for switch
defaultMAC: default MAC as unsigned int; random value if None"""
Switch.__init__( self, name, **kwargs )
Switch.__init__( self, name, **params )
# self.dp is the text name for the datapath that
# we use for ovs-vsctl. This is different from the
# dpid, which is a 64-bit numerical value used by
# the openflow protocol.
self.dp = name
@staticmethod
def setup():
"Make sure Open vSwitch is installed and working"
@@ -609,7 +728,6 @@ class OVSSwitch( Switch ):
self.cmd( 'ovs-vsctl del-br ', self.dp )
self.cmd( 'ovs-vsctl add-br', self.dp )
self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' )
# Add ports
ports = sorted( self.ports.values() )
intfs = [ self.intfs[ port ] for port in ports ]
# XXX: Ugly check - we should probably fix this!
@@ -629,19 +747,21 @@ class OVSSwitch( Switch ):
OVSKernelSwitch = OVSSwitch
class Controller( Node ):
"""A Controller is a Node that is running (or has execed?) an
OpenFlow controller."""
def __init__( self, name, inNamespace=False, command='controller',
cargs='-v ptcp:%d', cdir=None, defaultIP="127.0.0.1",
port=6633 ):
cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
port=6633, **params ):
self.command = command
self.cargs = cargs
self.cdir = cdir
self.ip = ip
self.port = port
Node.__init__( self, name, inNamespace=inNamespace,
defaultIP=defaultIP )
ip=ip, **params )
def start( self ):
"""Start <controller> <args> on controller.
@@ -664,9 +784,13 @@ class Controller( Node ):
if self.intfs:
ip = Node.IP( self, intf )
else:
ip = self.defaultIP
ip = self.ip
return ip
# BL: This really seems to be poorly specified,
# so it's going to go away!
class ControllerParams( object ):
"Container for controller IP parameters."