New configuration scheme and support for CPU limits (RT).
This commit is contained in:
+60
-34
@@ -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
@@ -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
@@ -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."
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user