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.name = name
self.link = link self.link = link
self.mac, self.ip = None, None self.mac, self.ip = None, None
# Add to node (and move ourselves if necessary )
node.addIntf( self )
self.config( **kwargs ) self.config( **kwargs )
def cmd( self, *args, **kwargs ): def cmd( self, *args, **kwargs ):
self.node.cmd( *args, **kwargs ) return self.node.cmd( *args, **kwargs )
def ifconfig( self, *args ): def ifconfig( self, *args ):
"Configure ourselves using ifconfig" "Configure ourselves using ifconfig"
@@ -93,22 +95,43 @@ class BasicIntf( object ):
return "UP" in self.ifconfig() return "UP" in self.ifconfig()
# Map of config params to config methods # The reason why we configure things in this way is so
# Perhaps this could be more graceful, but it # That the parameters can be listed and documented in
# is flexible # the config method.
configMap = { 'mac': 'setMAC', # Dealing with subclasses and superclasses is slightly
'ip': 'setIP', # annoying, but at least the information is there!
'ifconfig': 'ifconfig' }
def config( self, **params ): def setParam( self, result, method, **param ):
"Configure interface based on parameters" """Internal method: configure single parameter
self.__dict__.update(**params) result: dict of results to update
for name, value in params.iteritems(): method: config method
method = self.configMap.get( name, None ) param: foo=bar (ignore if bar=None)"""
if method: name, value = param.items()[ 0 ]
if type( value ) is str: if value is None:
value = value.split( ',' ) return
method( value ) 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 ): def delete( self ):
"Delete interface" "Delete interface"
@@ -125,10 +148,10 @@ class TCIntf( BasicIntf ):
def config( self, bw=None, delay=None, loss=0, disable_gro=True, def config( self, bw=None, delay=None, loss=0, disable_gro=True,
speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, 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." "Configure the port and set its properties."
BasicIntf.config( self, **kwargs) result = BasicIntf.config( self, **params)
# disable GRO # disable GRO
if disable_gro: if disable_gro:
@@ -153,7 +176,7 @@ class TCIntf( BasicIntf ):
delay = '0ms' delay = '0ms'
if bw is not None and delay is not None: 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 ) ) ( bw, delay, loss ) )
# BL: hmm... what exactly is this??? # BL: hmm... what exactly is this???
@@ -209,8 +232,11 @@ class TCIntf( BasicIntf ):
debug(" *** executing command: %s\n" % c) debug(" *** executing command: %s\n" % c)
return self.cmd(c) return self.cmd(c)
outputs = [ doConfigPort(cmd) for cmd in cmds ] tcoutputs = [ doConfigPort(cmd) for cmd in cmds ]
debug( "outputs: %s\n" % outputs ) debug( "cmds:", cmds, '\n' )
debug( "outputs:", tcoutputs, '\n' )
result[ 'tcoutputs'] = tcoutputs
return result
Intf = TCIntf Intf = TCIntf
@@ -220,14 +246,18 @@ class Link( object ):
Other types of links could be tunnels, link emulators, etc..""" Other types of links could be tunnels, link emulators, etc.."""
def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None, 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. """Create veth link to another node, making two new interfaces.
node1: first node node1: first node
node2: second node node2: second node
port1: node1 port number (optional) port1: node1 port number (optional)
port2: node2 port number (optional) port2: node2 port number (optional)
intf: default interface class/constructor
cls1, cls2: optional interface-specific constructors
intfName1: node1 interface name (optional) 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 # This is a bit awkward; it seems that having everything in
# params would be more orthogonal, but being able to specify # params would be more orthogonal, but being able to specify
# in-line arguments is more convenient! # in-line arguments is more convenient!
@@ -240,11 +270,13 @@ class Link( object ):
if not intfName2: if not intfName2:
intfName2 = self.intfName( node2, port2 ) intfName2 = self.intfName( node2, port2 )
self.makeIntfPair( intfName1, intfName2 ) self.makeIntfPair( intfName1, intfName2 )
intf1 = intf( name=intfName1, node=node1, link=self, **params1 ) if not cls1:
intf2 = intf( name=intfName2, node=node2, link=self, **params2 ) cls1 = intf
# Add to nodes if not cls2:
node1.addIntf( intf1 ) cls2 = intf
node2.addIntf( intf2 ) 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 self.intf1, self.intf2 = intf1, intf2
@classmethod @classmethod
@@ -267,9 +299,3 @@ class Link( object ):
def __str__( self ): def __str__( self ):
return '%s<->%s' % ( self.intf1, self.intf2 ) return '%s<->%s' % ( self.intf1, self.intf2 )
+127 -118
View File
@@ -94,8 +94,7 @@ from time import sleep
from mininet.cli import CLI from mininet.cli import CLI
from mininet.log import info, error, debug, output from mininet.log import info, error, debug, output
from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller from mininet.node import Host, OVSKernelSwitch, Controller
from mininet.node import ControllerParams
from mininet.link import Link from mininet.link import Link
from mininet.util import quietRun, fixLimits from mininet.util import quietRun, fixLimits
from mininet.util import createLink, macColonHex, ipStr, ipParse from mininet.util import createLink, macColonHex, ipStr, ipParse
@@ -106,7 +105,6 @@ class Mininet( object ):
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
controller=Controller, link=Link, controller=Controller, link=Link,
cparams=ControllerParams( '10.0.0.0', 8 ),
build=True, xterms=False, cleanup=False, build=True, xterms=False, cleanup=False,
inNamespace=False, inNamespace=False,
autoSetMacs=False, autoStaticArp=False, listenPort=None ): autoSetMacs=False, autoStaticArp=False, listenPort=None ):
@@ -116,12 +114,12 @@ class Mininet( object ):
host: default Host class/constructor host: default Host class/constructor
controller: default Controller class/constructor controller: default Controller class/constructor
link: default Link class/constructor link: default Link class/constructor
cparams: ControllerParams object ipBase: base IP address for hosts,
build: build now from topo? build: build now from topo?
xterms: if build now, spawn xterms? xterms: if build now, spawn xterms?
cleanup: if build now, cleanup before creating? cleanup: if build now, cleanup before creating?
inNamespace: spawn switches and controller in net namespaces? 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? autoStaticArp: set all-pairs static MAC addrs?
listenPort: base listening port to open; will be incremented for listenPort: base listening port to open; will be incremented for
each additional switch in the net if inNamespace=False""" each additional switch in the net if inNamespace=False"""
@@ -129,7 +127,6 @@ class Mininet( object ):
self.host = host self.host = host
self.controller = controller self.controller = controller
self.link = link self.link = link
self.cparams = cparams
self.topo = topo self.topo = topo
self.inNamespace = inNamespace self.inNamespace = inNamespace
self.xterms = xterms self.xterms = xterms
@@ -141,13 +138,13 @@ class Mininet( object ):
self.hosts = [] self.hosts = []
self.switches = [] self.switches = []
self.controllers = [] self.controllers = []
self.nameToNode = {} # name to Node (Host/Switch) objects self.nameToNode = {} # name to Node (Host/Switch) objects
self.idToNode = {} # dpid 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 self.terms = [] # list of spawned xterm processes
init() init() # Initialize Mininet if necessary
switch.setup()
self.built = False self.built = False
if topo and build: if topo and build:
@@ -157,17 +154,15 @@ class Mininet( object ):
# The specific items for host/switch/etc. should probably be # The specific items for host/switch/etc. should probably be
# handled in the node classes rather than here!! # 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. """Add host.
name: name of host to add name: name of host to add
mac: default MAC address for intf 0 host: custom host constructor (optional)
ip: default IP address for intf 0 params: parameters for host
returns: added host""" returns: added host"""
if not host: if not host:
host = self.host host = self.host
defaults = { 'defaultMAC': mac, 'defaultIP': ip } h = host( name, **params)
defaults.update( params )
h = host( name, **defaults)
self.hosts.append( h ) self.hosts.append( h )
self.nameToNode[ name ] = h self.nameToNode[ name ] = h
return h return h
@@ -175,19 +170,17 @@ class Mininet( object ):
def addSwitch( self, name, switch=None, **params ): def addSwitch( self, name, switch=None, **params ):
"""Add switch. """Add switch.
name: name of switch to add name: name of switch to add
switch: custom switch constructor (optional)
returns: added switch returns: added switch
side effect: increments listenPort and dps ivars.""" side effect: increments listenPort ivar ."""
defaults = { 'listenPort': self.listenPort, defaults = { 'listenPort': self.listenPort,
'inNamespace': self.inNamespace } 'inNamespace': self.inNamespace }
defaults.update( params )
if not switch: if not switch:
switch = self.switch switch = self.switch
if switch != UserSwitch:
defaults[ 'dps' ] = self.dps
defaults.update( params )
sw = self.switch( name, **defaults ) sw = self.switch( name, **defaults )
if not self.inNamespace and self.listenPort: if not self.inNamespace and self.listenPort:
self.listenPort += 1 self.listenPort += 1
self.dps += 1
self.switches.append( sw ) self.switches.append( sw )
self.nameToNode[ name ] = sw self.nameToNode[ name ] = sw
return sw return sw
@@ -203,122 +196,81 @@ class Mininet( object ):
self.nameToNode[ name ] = controller_new self.nameToNode[ name ] = controller_new
return 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 ): def configHosts( self ):
"Configure a set of hosts." "Configure a set of hosts."
# params were: hosts, ips
for host in self.hosts: for host in self.hosts:
hintf = host.defaultIntf() host.configDefault( defaultRoute=host.defaultIntf )
host.setIP( host.defaultIP, self.cparams.prefixLen, hintf )
host.setDefaultRoute( hintf )
# You're low priority, dude! # 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( host.name + ' ' )
info( '\n' ) info( '\n' )
def buildFromTopo( self, topo ): def buildFromTopo( self, topo=None ):
"""Build mininet from a topology object """Build mininet from a topology object
At the end of this function, everything should be connected At the end of this function, everything should be connected
and up.""" and up."""
if not topo:
topo = self.topo()
def addNode( prefix, addMethod, nodeId ): def addNode( prefix, addMethod, nodeId ):
"Add a host or a switch." "Add a host or a switch from topo"
name = prefix + topo.name( nodeId ) 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 ) 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 self.idToNode[ nodeId ] = node
info( name + ' ' ) 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 # Possibly we should clean up here and/or validate
# the topo # the topo
if self.cleanup: if self.cleanup:
pass pass
info( '*** Creating network\n' )
if not self.controllers:
# Add a default controller
info( '*** Adding controller\n' ) info( '*** Adding controller\n' )
self.addController( 'c0' ) self.addController( 'c0' )
info( '*** Creating network\n' )
info( '*** Adding hosts:\n' ) info( '*** Adding hosts:\n' )
for hostId in sorted( topo.hosts() ): for hostId in sorted( topo.hosts() ):
addNode( 'h', self.addHost, hostId ) addNode( 'h', self.addHost, hostId )
info( '\n*** Adding switches:\n' ) info( '\n*** Adding switches:\n' )
for switchId in sorted( topo.switches() ): for switchId in sorted( topo.switches() ):
addNode( 's', self.addSwitch, switchId ) addNode( 's', self.addSwitch, switchId )
info( '\n*** Adding links:\n' ) info( '\n*** Adding links:\n' )
for srcId, dstId in sorted( topo.edges() ): for srcId, dstId in sorted( topo.edges() ):
src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] addLink( srcId, 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 ) )
info( '\n' ) info( '\n' )
def configureControlNetwork( self ):
error( "configureControlNetwork: override in subclass, or use"
"MininetWithControlNet class" )
def build( self ): def build( self ):
"Build mininet." "Build mininet."
if self.topo: if self.topo:
@@ -330,8 +282,6 @@ class Mininet( object ):
self.configHosts() self.configHosts()
if self.xterms: if self.xterms:
self.startTerms() self.startTerms()
if self.autoSetMacs:
self.setMacs()
if self.autoStaticArp: if self.autoStaticArp:
self.staticArp() self.staticArp()
self.built = True self.built = True
@@ -346,17 +296,10 @@ class Mininet( object ):
def stopXterms( self ): def stopXterms( self ):
"Kill each xterm." "Kill each xterm."
# Kill xterms
for term in self.terms: for term in self.terms:
os.kill( term.pid, signal.SIGKILL ) os.kill( term.pid, signal.SIGKILL )
cleanUpScreens() 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 ): def staticArp( self ):
"Add all-pairs ARP entries to remove the need to handle broadcast." "Add all-pairs ARP entries to remove the need to handle broadcast."
for src in self.hosts: for src in self.hosts:
@@ -384,18 +327,19 @@ class Mininet( object ):
self.stopXterms() self.stopXterms()
info( '*** Stopping %i hosts\n' % len( self.hosts ) ) info( '*** Stopping %i hosts\n' % len( self.hosts ) )
for host in self.hosts: for host in self.hosts:
info( '%s ' % host.name ) info( host.name + ' ' )
host.terminate() host.terminate()
info( '\n' ) info( '\n' )
info( '*** Stopping %i switches\n' % len( self.switches ) ) info( '*** Stopping %i switches\n' % len( self.switches ) )
for switch in self.switches: for switch in self.switches:
info( switch.name ) info( switch.name + ' ' )
switch.stop() switch.stop()
info( '\n' ) info( '\n' )
info( '*** Stopping %i controllers\n' % len( self.controllers ) ) info( '*** Stopping %i controllers\n' % len( self.controllers ) )
for controller in self.controllers: for controller in self.controllers:
info( controller.name + ' ' )
controller.stop() controller.stop()
info( '*** Done\n' ) info( '\n*** Done\n' )
def run( self, test, *args, **kwargs ): def run( self, test, *args, **kwargs ):
"Perform a complete start/test/stop cycle." "Perform a complete start/test/stop cycle."
@@ -429,6 +373,9 @@ class Mininet( object ):
if not ready and timeoutms >= 0: if not ready and timeoutms >= 0:
yield None, None yield None, None
# XXX These test methods should be moved out of this class.
# Probably we should create a tests.py for them
@staticmethod @staticmethod
def _parsePing( pingOutput ): def _parsePing( pingOutput ):
"Parse ping output and return packets sent, received." "Parse ping output and return packets sent, received."
@@ -543,6 +490,8 @@ class Mininet( object ):
output( '*** Results: %s\n' % result ) output( '*** Results: %s\n' % result )
return result return result
# BL: I think this can be rewritten now that we have
# a real link class.
def configLinkStatus( self, src, dst, status ): def configLinkStatus( self, src, dst, status ):
"""Change status of src <-> dst links. """Change status of src <-> dst links.
src: node name src: node name
@@ -573,6 +522,70 @@ class Mininet( object ):
return result 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 thinks inited is unused
# pylint: disable-msg=W0612 # pylint: disable-msg=W0612
@@ -585,10 +598,6 @@ def init():
# Perhaps we should do so automatically! # Perhaps we should do so automatically!
print "*** Mininet must run as root." print "*** Mininet must run as root."
exit( 1 ) 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() fixLimits()
init.inited = True init.inited = True
+244 -120
View File
@@ -48,7 +48,8 @@ import select
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
from mininet.log import info, error, debug 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.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
from mininet.link import Link from mininet.link import Link
@@ -58,21 +59,74 @@ 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."""
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 inToNode = {} # mapping of input fds to nodes
outToNode = {} # mapping of output 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, # Automatic class setup support
defaultMAC=None, defaultIP=None, **kwargs ):
"""name: name of node isSetup = False;
inNamespace: in network namespace?
defaultMAC: default MAC address for intf 0 @classmethod
defaultIP: default IP address for intf 0""" def checkSetup( cls ):
self.name = name "Make sure our class and superclasses are set up"
self.inNamespace = inNamespace while cls and not getattr( cls, 'isSetup', True ):
self.defaultIP = defaultIP cls.setup()
self.defaultMAC = defaultMAC 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' opts = '-cdp'
if self.inNamespace: if self.inNamespace:
opts += 'n' opts += 'n'
@@ -89,31 +143,12 @@ class Node( object ):
# using select.poll() # using select.poll()
self.outToNode[ self.stdout.fileno() ] = self self.outToNode[ self.stdout.fileno() ] = self
self.inToNode[ self.stdin.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.execed = False
self.lastCmd = None self.lastCmd = None
self.lastPid = None self.lastPid = None
self.readbuf = '' self.readbuf = ''
self.waiting = False 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 ): def read( self, bytes=1024 ):
"""Buffered read from node, non-blocking. """Buffered read from node, non-blocking.
bytes: maximum number of bytes to return""" bytes: maximum number of bytes to return"""
@@ -267,10 +302,10 @@ class Node( object ):
self.intfs[ port ] = intf self.intfs[ port ] = intf
self.ports[ intf ] = port self.ports[ intf ] = port
self.nameToIntf[ intf.name ] = intf self.nameToIntf[ intf.name ] = intf
info( '\n' ) debug( '\n' )
info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) debug( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
if self.inNamespace: 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 ) moveIntf( intf.name, self )
def defaultIntf( self ): def defaultIntf( self ):
@@ -326,13 +361,15 @@ class Node( object ):
intf: string, interface name""" intf: string, interface name"""
return self.cmd( 'route add -host ' + ip + ' dev ' + intf ) 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. """Set the default route to go through intf.
intf: string, interface name""" intf: string, interface name"""
if not intf:
intf = self.defaultIntf()
self.cmd( 'ip route flush root 0/0' ) self.cmd( 'ip route flush root 0/0' )
return self.cmd( 'route add default %s' % intf ) return self.cmd( 'route add default %s' % intf )
# Convenience methods # Convenience and configuration methods
def setMAC( self, mac, intf=''): def setMAC( self, mac, intf=''):
"""Set the MAC address for an interface. """Set the MAC address for an interface.
@@ -361,6 +398,49 @@ class Node( object ):
"Check if an interface is up." "Check if an interface is up."
return self.intf( intf ).isUp() 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 # This is here for backward compatibility
def linkTo( self, node, link=Link ): def linkTo( self, node, link=Link ):
"""(Deprecated) Link to another node """(Deprecated) Link to another node
@@ -382,9 +462,94 @@ class Node( object ):
self.name, self.IP(), ','.join( self.intfNames() ), self.pid ) self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
class Host( Node ): class CPULimitedHost( Node ):
"A host is simply a 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 ): class Switch( Node ):
"""A Switch is a Node that is running (or has execed?) """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 portBase = SWITCH_PORT_BASE # 0 for OF < 1.0, 1 for OF >= 1.0
def __init__( self, name, opts='', listenPort=None, **kwargs): def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
Node.__init__( self, name, **kwargs ) """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.opts = opts
self.listenPort = listenPort self.listenPort = listenPort
if self.listenPort: if self.listenPort:
self.opts += ' --listen=ptcp:%i ' % 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 ): def defaultIntf( self ):
"Return interface for HIGHEST port" "Return control interface, if any"
ports = self.intfs.keys() if not self.inNamespace:
if ports: error( "error: tried to access control interface of "
intf = self.intfs[ max( ports ) ] " switch %s in root namespace" % self.name )
return intf return self.controlIntf
def sendCmd( self, *cmd, **kwargs ): def sendCmd( self, *cmd, **kwargs ):
"""Send command to Node. """Send command to Node.
@@ -440,15 +617,13 @@ class UserSwitch( Switch ):
ofdlog = '/tmp/' + self.name + '-ofd.log' ofdlog = '/tmp/' + self.name + '-ofd.log'
ofplog = '/tmp/' + self.name + '-ofp.log' ofplog = '/tmp/' + self.name + '-ofp.log'
self.cmd( 'ifconfig lo up' ) self.cmd( 'ifconfig lo up' )
mac_str = '' ports = sorted( self.ports.values() )
if self.defaultMAC: intfs = [ str( self.intfs[ p ] ) for p in ports ]
# ofdatapath expects a string of hex digits with no colons.
mac_str = ' -d ' + ''.join( self.defaultMAC.split( ':' ) )
intfs = sorted( self.intfs.values() )
if self.inNamespace: if self.inNamespace:
intfs = intfs[ :-1 ] intfs = intfs[ :-1 ]
self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + 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 + ' &' ) ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
self.cmd( 'ofprotocol unix:/tmp/' + self.name + self.cmd( 'ofprotocol unix:/tmp/' + self.name +
' tcp:%s:%d' % ( controller.IP(), controller.port ) + ' tcp:%s:%d' % ( controller.IP(), controller.port ) +
@@ -461,61 +636,6 @@ class UserSwitch( Switch ):
self.cmd( 'kill %ofprotocol' ) self.cmd( 'kill %ofprotocol' )
self.deleteIntfs() 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 ): class OVSLegacyKernelSwitch( Switch ):
"""Open VSwitch legacy kernel-space switch using ovs-openflowd. """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 # then create a new one monitoring the given interfaces
quietRun( 'ovs-dpctl del-dp ' + self.dp ) quietRun( 'ovs-dpctl del-dp ' + self.dp )
self.cmd( 'ovs-dpctl add-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() ) ports = sorted( self.ports.values() )
if len( ports ) != ports[ -1 ] + 1 - self.portBase: if len( ports ) != ports[ -1 ] + 1 - self.portBase:
raise Exception( 'only contiguous, one-indexed port ranges ' raise Exception( 'only contiguous, one-indexed port ranges '
@@ -565,7 +679,8 @@ class OVSLegacyKernelSwitch( Switch ):
controller = controllers[ 0 ] controller = controllers[ 0 ]
self.cmd( 'ovs-openflowd ' + self.dp + self.cmd( 'ovs-openflowd ' + self.dp +
' tcp:%s:%d' % ( controller.IP(), controller.port ) + ' 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 + '&' ) ' 1>' + ofplog + ' 2>' + ofplog + '&' )
self.execed = False self.execed = False
@@ -579,11 +694,15 @@ class OVSLegacyKernelSwitch( Switch ):
class OVSSwitch( Switch ): class OVSSwitch( Switch ):
"Open vSwitch switch. Depends on ovs-vsctl." "Open vSwitch switch. Depends on ovs-vsctl."
def __init__( self, name, dp=None, **kwargs ): def __init__( self, name, **params ):
"""Init. """Init.
name: name for switch name: name for switch
defaultMAC: default MAC as unsigned int; random value if None""" 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 self.dp = name
@staticmethod @staticmethod
@@ -609,7 +728,6 @@ class OVSSwitch( Switch ):
self.cmd( 'ovs-vsctl del-br ', self.dp ) self.cmd( 'ovs-vsctl del-br ', self.dp )
self.cmd( 'ovs-vsctl add-br', self.dp ) self.cmd( 'ovs-vsctl add-br', self.dp )
self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' ) self.cmd( 'ovs-vsctl set-fail-mode', self.dp, 'secure' )
# Add ports
ports = sorted( self.ports.values() ) ports = sorted( self.ports.values() )
intfs = [ self.intfs[ port ] for port in ports ] intfs = [ self.intfs[ port ] for port in ports ]
# XXX: Ugly check - we should probably fix this! # XXX: Ugly check - we should probably fix this!
@@ -629,19 +747,21 @@ class OVSSwitch( Switch ):
OVSKernelSwitch = OVSSwitch OVSKernelSwitch = OVSSwitch
class Controller( Node ): class Controller( Node ):
"""A Controller is a Node that is running (or has execed?) an """A Controller is a Node that is running (or has execed?) an
OpenFlow controller.""" OpenFlow controller."""
def __init__( self, name, inNamespace=False, command='controller', def __init__( self, name, inNamespace=False, command='controller',
cargs='-v ptcp:%d', cdir=None, defaultIP="127.0.0.1", cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
port=6633 ): port=6633, **params ):
self.command = command self.command = command
self.cargs = cargs self.cargs = cargs
self.cdir = cdir self.cdir = cdir
self.ip = ip
self.port = port self.port = port
Node.__init__( self, name, inNamespace=inNamespace, Node.__init__( self, name, inNamespace=inNamespace,
defaultIP=defaultIP ) ip=ip, **params )
def start( self ): def start( self ):
"""Start <controller> <args> on controller. """Start <controller> <args> on controller.
@@ -664,9 +784,13 @@ class Controller( Node ):
if self.intfs: if self.intfs:
ip = Node.IP( self, intf ) ip = Node.IP( self, intf )
else: else:
ip = self.defaultIP ip = self.ip
return ip return ip
# BL: This really seems to be poorly specified,
# so it's going to go away!
class ControllerParams( object ): class ControllerParams( object ):
"Container for controller IP parameters." "Container for controller IP parameters."