Support for CFS bandwidth limiting.
Also trying to fix NOX cmdline opt, but broken at the moment.
This commit is contained in:
@@ -20,11 +20,27 @@ from mininet.clean import cleanup
|
|||||||
from mininet.cli import CLI
|
from mininet.cli import CLI
|
||||||
from mininet.log import lg, LEVELS, info
|
from mininet.log import lg, LEVELS, info
|
||||||
from mininet.net import Mininet, init
|
from mininet.net import Mininet, init
|
||||||
from mininet.node import Host, Controller, ControllerParams, NOX
|
from mininet.node import Host, CPULimitedHost, Controller, NOX
|
||||||
from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
|
from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
|
||||||
|
from mininet.link import Intf, TCIntf
|
||||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||||
from mininet.topolib import TreeTopo
|
from mininet.topolib import TreeTopo
|
||||||
from mininet.util import makeNumeric
|
from mininet.util import makeNumeric, custom
|
||||||
|
|
||||||
|
def customNode( constructors, argStr ):
|
||||||
|
"Return custom Node constructor based on argStr"
|
||||||
|
cname, noargs, kwargs = splitArgs( argStr )
|
||||||
|
constructor = constructors.get( cname, None )
|
||||||
|
if noargs:
|
||||||
|
raise Exception( "please specify keyword arguments for " + cname )
|
||||||
|
if not constructor:
|
||||||
|
raise Exception( "error: %s is unknown - please specify one of %s" %
|
||||||
|
( cname, constructors.keys() ) )
|
||||||
|
def custom( *args, **params ):
|
||||||
|
params.update( kwargs )
|
||||||
|
print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params
|
||||||
|
return constructor( *args, **params )
|
||||||
|
return custom
|
||||||
|
|
||||||
# built in topologies, created only when run
|
# built in topologies, created only when run
|
||||||
TOPODEF = 'minimal'
|
TOPODEF = 'minimal'
|
||||||
@@ -38,17 +54,22 @@ SWITCHDEF = 'ovsk'
|
|||||||
SWITCHES = { 'user': UserSwitch,
|
SWITCHES = { 'user': UserSwitch,
|
||||||
'ovsk': OVSKernelSwitch }
|
'ovsk': OVSKernelSwitch }
|
||||||
|
|
||||||
HOSTDEF = 'process'
|
HOSTDEF = 'proc'
|
||||||
HOSTS = { 'process': Host }
|
HOSTS = { 'proc': Host,
|
||||||
|
'rt': custom( CPULimitedHost, sched='rt' ),
|
||||||
|
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
||||||
|
|
||||||
CONTROLLERDEF = 'ref'
|
CONTROLLERDEF = 'ref'
|
||||||
# a and b are the name and inNamespace params.
|
|
||||||
CONTROLLERS = { 'ref': Controller,
|
CONTROLLERS = { 'ref': Controller,
|
||||||
'nox_dump': lambda name: NOX( name, 'packetdump' ),
|
'nox': NOX,
|
||||||
'nox_pysw': lambda name: NOX( name, 'pyswitch' ),
|
'remote': RemoteController,
|
||||||
'remote': lambda name: None,
|
|
||||||
'none': lambda name: None }
|
'none': lambda name: None }
|
||||||
|
|
||||||
|
INTFDEF = 'default'
|
||||||
|
INTFS = { 'default': Intf,
|
||||||
|
'tc': TCIntf }
|
||||||
|
|
||||||
|
|
||||||
# optional tests to run
|
# optional tests to run
|
||||||
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
||||||
'none' ]
|
'none' ]
|
||||||
@@ -56,24 +77,31 @@ TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
|||||||
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
|
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
|
||||||
'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' }
|
'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' }
|
||||||
|
|
||||||
def buildTopo( topo ):
|
|
||||||
"Create topology from string with format (object, arg1, arg2,...)."
|
|
||||||
topo_split = topo.split( ',' )
|
|
||||||
topo_name = topo_split[ 0 ]
|
|
||||||
topo_params = topo_split[ 1: ]
|
|
||||||
|
|
||||||
# Convert int and float args; removes the need for every topology to
|
def splitArgs( argstr ):
|
||||||
# be flexible with input arg formats.
|
"""Split argument string into usable python arguments
|
||||||
topo_seq_params = [ s for s in topo_params if '=' not in s ]
|
argstr: argument string with format fn,arg2,kw1=arg3...
|
||||||
topo_seq_params = [ makeNumeric( s ) for s in topo_seq_params ]
|
returns: fn, args, kwargs"""
|
||||||
topo_kw_params = {}
|
split = argstr.split( ',' )
|
||||||
for s in [ p for p in topo_params if '=' in p ]:
|
fn = split[ 0 ]
|
||||||
|
params = split[ 1: ]
|
||||||
|
# Convert int and float args; removes the need for function
|
||||||
|
# to be flexible with input arg formats.
|
||||||
|
args = [ s for s in params if '=' not in s ]
|
||||||
|
args = map( makeNumeric, args )
|
||||||
|
kwargs = {}
|
||||||
|
for s in [ p for p in params if '=' in p ]:
|
||||||
key, val = s.split( '=' )
|
key, val = s.split( '=' )
|
||||||
topo_kw_params[ key ] = makeNumeric( val )
|
kwargs[ key ] = makeNumeric( val )
|
||||||
|
return fn, args, kwargs
|
||||||
|
|
||||||
if topo_name not in TOPOS.keys():
|
|
||||||
raise Exception( 'Invalid topo_name %s' % topo_name )
|
def buildTopo( topoStr ):
|
||||||
return TOPOS[ topo_name ]( *topo_seq_params, **topo_kw_params )
|
"Create topology from string with format (object, arg1, arg2,...)."
|
||||||
|
topo, args, kwargs = splitArgs( topoStr )
|
||||||
|
if topo not in TOPOS:
|
||||||
|
raise Exception( 'Invalid topo name %s' % topo )
|
||||||
|
return TOPOS[ topo ]( *args, **kwargs )
|
||||||
|
|
||||||
|
|
||||||
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
||||||
@@ -87,10 +115,9 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
|||||||
raise Exception( 'Invalid default %s for choices dict: %s' %
|
raise Exception( 'Invalid default %s for choices dict: %s' %
|
||||||
( default, name ) )
|
( default, name ) )
|
||||||
if not helpStr:
|
if not helpStr:
|
||||||
helpStr = '[' + ' '.join( choicesDict.keys() ) + ']'
|
helpStr = '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]'
|
||||||
opts.add_option( '--' + name,
|
opts.add_option( '--' + name,
|
||||||
type='choice',
|
type='string',
|
||||||
choices=choicesDict.keys(),
|
|
||||||
default = default,
|
default = default,
|
||||||
help = helpStr )
|
help = helpStr )
|
||||||
|
|
||||||
@@ -135,7 +162,6 @@ class MininetRunner( object ):
|
|||||||
"""Parse command-line args and return options object.
|
"""Parse command-line args and return options object.
|
||||||
returns: opts parse options dict"""
|
returns: opts parse options dict"""
|
||||||
if '--custom' in sys.argv:
|
if '--custom' in sys.argv:
|
||||||
print "custom in sys.argv"
|
|
||||||
index = sys.argv.index( '--custom' )
|
index = sys.argv.index( '--custom' )
|
||||||
if len( sys.argv ) > index + 1:
|
if len( sys.argv ) > index + 1:
|
||||||
custom = sys.argv[ index + 1 ]
|
custom = sys.argv[ index + 1 ]
|
||||||
@@ -147,46 +173,41 @@ class MininetRunner( object ):
|
|||||||
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
|
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
|
||||||
addDictOption( opts, HOSTS, HOSTDEF, 'host' )
|
addDictOption( opts, HOSTS, HOSTDEF, 'host' )
|
||||||
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
|
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
|
||||||
|
addDictOption( opts, INTFS, INTFDEF, 'intf' )
|
||||||
|
addDictOption( opts, TOPOS, TOPODEF, 'topo' )
|
||||||
|
|
||||||
opts.add_option( '--topo', type='string', default=TOPODEF,
|
|
||||||
help='[' + ' '.join( TOPOS.keys() ) + '],arg1,arg2,'
|
|
||||||
'...argN')
|
|
||||||
opts.add_option( '--clean', '-c', action='store_true',
|
opts.add_option( '--clean', '-c', action='store_true',
|
||||||
default=False, help='clean and exit' )
|
default=False, help='clean and exit' )
|
||||||
opts.add_option( '--custom', type='string', default=None,
|
opts.add_option( '--custom', type='string', default=None,
|
||||||
help='read custom topo and node params from .py file' )
|
help='read custom topo and node params from .py file' )
|
||||||
opts.add_option( '--test', type='choice', choices=TESTS,
|
opts.add_option( '--test', type='choice', choices=TESTS,
|
||||||
default=TESTS[ 0 ],
|
default=TESTS[ 0 ],
|
||||||
help='[' + ' '.join( TESTS ) + ']' )
|
help='|'.join( TESTS ) )
|
||||||
opts.add_option( '--xterms', '-x', action='store_true',
|
opts.add_option( '--xterms', '-x', action='store_true',
|
||||||
default=False, help='spawn xterms for each node' )
|
default=False, help='spawn xterms for each node' )
|
||||||
opts.add_option( '--mac', action='store_true',
|
opts.add_option( '--mac', action='store_true',
|
||||||
default=False, help='set MACs equal to DPIDs' )
|
default=False, help='automatically set host MACs' )
|
||||||
opts.add_option( '--arp', action='store_true',
|
opts.add_option( '--arp', action='store_true',
|
||||||
default=False, help='set all-pairs ARP entries' )
|
default=False, help='set all-pairs ARP entries' )
|
||||||
opts.add_option( '--verbosity', '-v', type='choice',
|
opts.add_option( '--verbosity', '-v', type='choice',
|
||||||
choices=LEVELS.keys(), default = 'info',
|
choices=LEVELS.keys(), default = 'info',
|
||||||
help = '[' + ' '.join( LEVELS.keys() ) + ']' )
|
help = '|'.join( LEVELS.keys() ) )
|
||||||
opts.add_option( '--ip', type='string', default='127.0.0.1',
|
opts.add_option( '--ip', type='string', default='127.0.0.1',
|
||||||
help='[ip address as a dotted decimal string for a'
|
help='ip address as a dotted decimal string for a'
|
||||||
'remote controller]' )
|
'remote controller' )
|
||||||
opts.add_option( '--port', type='int', default=6633,
|
|
||||||
help='[port integer for a listening remote'
|
|
||||||
' controller]' )
|
|
||||||
opts.add_option( '--innamespace', action='store_true',
|
opts.add_option( '--innamespace', action='store_true',
|
||||||
default=False, help='sw and ctrl in namespace?' )
|
default=False, help='sw and ctrl in namespace?' )
|
||||||
opts.add_option( '--listenport', type='int', default=6634,
|
opts.add_option( '--listenport', type='int', default=6634,
|
||||||
help='[base port for passive switch listening'
|
help='base port for passive switch listening' )
|
||||||
' controller]' )
|
|
||||||
opts.add_option( '--nolistenport', action='store_true',
|
opts.add_option( '--nolistenport', action='store_true',
|
||||||
default=False, help="don't use passive listening port")
|
default=False, help="don't use passive listening port")
|
||||||
opts.add_option( '--pre', type='string', default=None,
|
opts.add_option( '--pre', type='string', default=None,
|
||||||
help='[CLI script to run before tests]' )
|
help='CLI script to run before tests' )
|
||||||
opts.add_option( '--post', type='string', default=None,
|
opts.add_option( '--post', type='string', default=None,
|
||||||
help='[CLI script to run after tests]' )
|
help='CLI script to run after tests' )
|
||||||
opts.add_option( '--prefixlen', type='int', default=8,
|
opts.add_option( '--prefixlen', type='int', default=8,
|
||||||
help='[prefix length (e.g. /8) for automatic '
|
help='prefix length (e.g. /8) for automatic '
|
||||||
'network configuration]' )
|
'network configuration' )
|
||||||
|
|
||||||
self.options, self.args = opts.parse_args()
|
self.options, self.args = opts.parse_args()
|
||||||
|
|
||||||
@@ -214,23 +235,14 @@ class MininetRunner( object ):
|
|||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
topo = buildTopo( self.options.topo )
|
topo = buildTopo( self.options.topo )
|
||||||
switch = SWITCHES[ self.options.switch ]
|
switch = customNode( SWITCHES, self.options.switch )
|
||||||
host = HOSTS[ self.options.host ]
|
host = customNode( HOSTS, self.options.host )
|
||||||
controller = CONTROLLERS[ self.options.controller ]
|
controller = customNode( CONTROLLERS, self.options.controller )
|
||||||
if self.options.controller == 'remote':
|
intf = customNode( INTFS, self.options.intf )
|
||||||
controller = lambda a: RemoteController( a,
|
|
||||||
defaultIP=self.options.ip,
|
|
||||||
port=self.options.port )
|
|
||||||
|
|
||||||
if self.validate:
|
if self.validate:
|
||||||
self.validate( self.options )
|
self.validate( self.options )
|
||||||
|
|
||||||
# We should clarify what this is actually for...
|
|
||||||
# It seems like it should be default values for the
|
|
||||||
# *data* network, so it may be misnamed.
|
|
||||||
controllerParams = ControllerParams( '10.0.0.0',
|
|
||||||
self.options.prefixlen)
|
|
||||||
|
|
||||||
inNamespace = self.options.innamespace
|
inNamespace = self.options.innamespace
|
||||||
xterms = self.options.xterms
|
xterms = self.options.xterms
|
||||||
mac = self.options.mac
|
mac = self.options.mac
|
||||||
@@ -238,7 +250,9 @@ class MininetRunner( object ):
|
|||||||
listenPort = None
|
listenPort = None
|
||||||
if not self.options.nolistenport:
|
if not self.options.nolistenport:
|
||||||
listenPort = self.options.listenport
|
listenPort = self.options.listenport
|
||||||
mn = Mininet( topo, switch, host, controller, controllerParams,
|
mn = Mininet( topo=topo,
|
||||||
|
switch=switch, host=host, controller=controller,
|
||||||
|
intf=intf,
|
||||||
inNamespace=inNamespace,
|
inNamespace=inNamespace,
|
||||||
xterms=xterms, autoSetMacs=mac,
|
xterms=xterms, autoSetMacs=mac,
|
||||||
autoStaticArp=arp, listenPort=listenPort )
|
autoStaticArp=arp, listenPort=listenPort )
|
||||||
|
|||||||
+19
-20
@@ -5,19 +5,20 @@ limit.py: example of using link and CPU limits
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from mininet.net import Mininet
|
from mininet.net import Mininet
|
||||||
from mininet.link import TCIntf, Link
|
from mininet.link import TCIntf
|
||||||
from mininet.node import CPULimitedHost
|
from mininet.node import CPULimitedHost
|
||||||
from mininet.topolib import TreeTopo
|
from mininet.topolib import TreeTopo
|
||||||
from mininet.util import custom, quietRun
|
from mininet.util import custom, quietRun
|
||||||
from mininet.log import setLogLevel
|
from mininet.log import setLogLevel
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
def testLinkLimit( net ):
|
def testLinkLimit( net, bw ):
|
||||||
print '*** Testing network bandwidth limit'
|
print '*** Testing network %.2f Mbps bandwidth limit' % bw
|
||||||
net.iperf()
|
net.iperf( )
|
||||||
|
|
||||||
def testCpuLimit( net ):
|
def testCpuLimit( net, cpu ):
|
||||||
print '*** Testing CPU bandwidth limit'
|
pct = cpu * 100
|
||||||
|
print '*** Testing CPU %.0f%% bandwidth limit' % pct
|
||||||
h1, h2 = net.hosts
|
h1, h2 = net.hosts
|
||||||
h1.cmd( 'while true; do a=1; done &' )
|
h1.cmd( 'while true; do a=1; done &' )
|
||||||
h2.cmd( 'while true; do a=1; done &' )
|
h2.cmd( 'while true; do a=1; done &' )
|
||||||
@@ -26,25 +27,23 @@ def testCpuLimit( net ):
|
|||||||
cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 )
|
cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 )
|
||||||
for i in range( 0, 5):
|
for i in range( 0, 5):
|
||||||
sleep( 1 )
|
sleep( 1 )
|
||||||
print quietRun( cmd )
|
print quietRun( cmd ).strip()
|
||||||
h1.cmd( 'kill %1')
|
h1.cmd( 'kill %1')
|
||||||
h2.cmd( 'kill %1')
|
h2.cmd( 'kill %1')
|
||||||
|
|
||||||
def limit():
|
def limit( bw=1, cpu=.3 ):
|
||||||
"Example/test of link and CPU bandwidth limits"
|
"""Example/test of link and CPU bandwidth limits
|
||||||
# 1 Mbps interfaces limited using tc
|
bw: interface bandwidth limit in Mbps
|
||||||
intf1Mbps = custom( TCIntf, bw=1 )
|
cpu: cpu limit as fraction of overall CPU time"""
|
||||||
# Links consisting of two 10 Mbps interfaces
|
intf = custom( TCIntf, bw=1 )
|
||||||
link1Mbps = custom( Link, intf=intf1Mbps, cls2=TCIntf )
|
|
||||||
# Hosts with 30% of system bandwidth
|
|
||||||
host30pct = custom( CPULimitedHost, cpu=.3 )
|
|
||||||
myTopo = TreeTopo( depth=1, fanout=2 )
|
myTopo = TreeTopo( depth=1, fanout=2 )
|
||||||
net = Mininet( topo=myTopo,
|
for sched in 'rt', 'cfs':
|
||||||
link=link1Mbps,
|
print '*** Testing with', sched, 'bandwidth limiting'
|
||||||
host=host30pct )
|
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
|
||||||
|
net = Mininet( topo=myTopo, intf=intf, host=host )
|
||||||
net.start()
|
net.start()
|
||||||
testLinkLimit( net )
|
testLinkLimit( net, bw=bw )
|
||||||
testCpuLimit( net )
|
testCpuLimit( net, cpu=cpu )
|
||||||
net.stop()
|
net.stop()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
+9
-3
@@ -104,7 +104,7 @@ class Mininet( object ):
|
|||||||
"Network emulation with hosts spawned in network namespaces."
|
"Network emulation with hosts spawned in network namespaces."
|
||||||
|
|
||||||
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, intf=None,
|
||||||
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 ):
|
||||||
@@ -114,6 +114,7 @@ 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
|
||||||
|
intf: default Intf class/constructor
|
||||||
ipBase: base IP address for hosts,
|
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?
|
||||||
@@ -123,11 +124,12 @@ class Mininet( object ):
|
|||||||
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"""
|
||||||
|
self.topo = topo
|
||||||
self.switch = switch
|
self.switch = switch
|
||||||
self.host = host
|
self.host = host
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.link = link
|
self.link = link
|
||||||
self.topo = topo
|
self.intf = intf
|
||||||
self.inNamespace = inNamespace
|
self.inNamespace = inNamespace
|
||||||
self.xterms = xterms
|
self.xterms = xterms
|
||||||
self.cleanup = cleanup
|
self.cleanup = cleanup
|
||||||
@@ -199,12 +201,12 @@ class Mininet( object ):
|
|||||||
def configHosts( self ):
|
def configHosts( self ):
|
||||||
"Configure a set of hosts."
|
"Configure a set of hosts."
|
||||||
for host in self.hosts:
|
for host in self.hosts:
|
||||||
|
info( host.name + ' ' )
|
||||||
host.configDefault( defaultRoute=host.defaultIntf )
|
host.configDefault( defaultRoute=host.defaultIntf )
|
||||||
# You're low priority, dude!
|
# You're low priority, dude!
|
||||||
# BL: do we want to do this here or not?
|
# BL: do we want to do this here or not?
|
||||||
# May not make sense if we have CPU lmiting...
|
# May not make sense if we have CPU lmiting...
|
||||||
# quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
# quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||||||
info( host.name + ' ' )
|
|
||||||
info( '\n' )
|
info( '\n' )
|
||||||
|
|
||||||
def buildFromTopo( self, topo=None ):
|
def buildFromTopo( self, topo=None ):
|
||||||
@@ -235,6 +237,8 @@ class Mininet( object ):
|
|||||||
ei = topo.edgeInfo( srcId, dstId )
|
ei = topo.edgeInfo( srcId, dstId )
|
||||||
link = getattr( ei, 'cls', link )
|
link = getattr( ei, 'cls', link )
|
||||||
params = ei.params
|
params = ei.params
|
||||||
|
if self.intf and not 'intf' in params:
|
||||||
|
params[ 'intf' ] = self.intf
|
||||||
if not link:
|
if not link:
|
||||||
link = self.link
|
link = self.link
|
||||||
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
||||||
@@ -447,6 +451,8 @@ class Mininet( object ):
|
|||||||
error( 'could not parse iperf output: ' + iperfOutput )
|
error( 'could not parse iperf output: ' + iperfOutput )
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
# XXX This should be cleaned up
|
||||||
|
|
||||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
|
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
|
||||||
"""Run iperf between two hosts.
|
"""Run iperf between two hosts.
|
||||||
hosts: list of hosts; if None, uses opposite hosts
|
hosts: list of hosts; if None, uses opposite hosts
|
||||||
|
|||||||
+68
-23
@@ -127,9 +127,12 @@ class Node( object ):
|
|||||||
if self.shell:
|
if self.shell:
|
||||||
error( "%s: shell is already running" )
|
error( "%s: shell is already running" )
|
||||||
return
|
return
|
||||||
|
# mnexec: (c)lose descriptors, (d)etach from tty,
|
||||||
|
# (p)rint pid, and run in (n)amespace
|
||||||
opts = '-cdp'
|
opts = '-cdp'
|
||||||
if self.inNamespace:
|
if self.inNamespace:
|
||||||
opts += 'n'
|
opts += 'n'
|
||||||
|
# bash -m: enable job control
|
||||||
cmd = [ 'mnexec', opts, 'bash', '-m' ]
|
cmd = [ 'mnexec', opts, 'bash', '-m' ]
|
||||||
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
|
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
|
||||||
close_fds=True )
|
close_fds=True )
|
||||||
@@ -405,10 +408,14 @@ class Node( object ):
|
|||||||
# annoying, but at least the information is there!
|
# annoying, but at least the information is there!
|
||||||
|
|
||||||
def setParam( self, results, method, **param ):
|
def setParam( self, results, method, **param ):
|
||||||
"""Internal method: configure single parameter"""
|
"""Internal method: configure a *single* parameter
|
||||||
|
results: dict of results to update
|
||||||
|
method: config method name
|
||||||
|
param: arg=value (ignore if value=None)
|
||||||
|
value may also be list or dict"""
|
||||||
name, value = param.items()[ 0 ]
|
name, value = param.items()[ 0 ]
|
||||||
f = getattr( self, method, None )
|
f = getattr( self, method, None )
|
||||||
if not value or not f:
|
if not f or value is None:
|
||||||
return
|
return
|
||||||
if type( value ) is list:
|
if type( value ) is list:
|
||||||
result = f( *value )
|
result = f( *value )
|
||||||
@@ -417,6 +424,7 @@ class Node( object ):
|
|||||||
else:
|
else:
|
||||||
result = f( value )
|
result = f( value )
|
||||||
results[ name ] = result
|
results[ name ] = result
|
||||||
|
return result
|
||||||
|
|
||||||
def config( self, mac=None, ip=None, ifconfig=None,
|
def config( self, mac=None, ip=None, ifconfig=None,
|
||||||
defaultRoute=None, **params):
|
defaultRoute=None, **params):
|
||||||
@@ -462,7 +470,12 @@ class Node( object ):
|
|||||||
self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
|
self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
|
||||||
|
|
||||||
|
|
||||||
class CPULimitedHost( Node ):
|
class Host( Node ):
|
||||||
|
"A host is simply a Node"
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CPULimitedHost( Host ):
|
||||||
|
|
||||||
"CPU limited host"
|
"CPU limited host"
|
||||||
|
|
||||||
@@ -472,9 +485,8 @@ class CPULimitedHost( Node ):
|
|||||||
cgroup = 'cpu,cpuacct:/' + self.name
|
cgroup = 'cpu,cpuacct:/' + self.name
|
||||||
errFail( 'cgcreate -g ' + cgroup )
|
errFail( 'cgcreate -g ' + cgroup )
|
||||||
errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) )
|
errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) )
|
||||||
self.sched = 'rt'
|
self.period_us = kwargs.get( 'period_us', 10000 )
|
||||||
self.period_us = 10000
|
self.sched = kwargs.get( 'sched', 'rt' )
|
||||||
self.rtset = False
|
|
||||||
|
|
||||||
def cgroupSet( self, param, value, resource='cpu' ):
|
def cgroupSet( self, param, value, resource='cpu' ):
|
||||||
"Set a cgroup parameter and return its value"
|
"Set a cgroup parameter and return its value"
|
||||||
@@ -495,40 +507,73 @@ class CPULimitedHost( Node ):
|
|||||||
lastword = firstline.split( ' ' )[ -1 ]
|
lastword = firstline.split( ' ' )[ -1 ]
|
||||||
return lastword
|
return lastword
|
||||||
|
|
||||||
def setCPUFrac( self, f=-1 ):
|
# BL comment:
|
||||||
"Set overall CPU fraction for this host"
|
# This may not be the right API,
|
||||||
if ( f < 0 or f is None):
|
# since it doesn't specify CPU bandwidth in "absolute"
|
||||||
# Reset to unlimited
|
# units the way link bandwidth is specified.
|
||||||
f = -1
|
# We should use MIPS or SPECINT or something instead.
|
||||||
# Set new period and quota
|
# Alternatively, we should change from system fraction
|
||||||
|
# to CPU seconds per second, essentially assuming that
|
||||||
|
# all CPUs are the same.
|
||||||
|
|
||||||
|
def setCPUFrac( self, f=-1, sched=None):
|
||||||
|
"""Set overall CPU fraction for this host
|
||||||
|
f: CPU bandwidth limit (fraction)
|
||||||
|
sched: 'rt' or 'cfs'
|
||||||
|
Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
|
||||||
|
if not f:
|
||||||
|
return
|
||||||
|
if not sched:
|
||||||
|
sched = self.sched
|
||||||
|
period = self.period_us
|
||||||
|
if sched == 'rt':
|
||||||
pstr, qstr = 'rt_period_us', 'rt_runtime_us'
|
pstr, qstr = 'rt_period_us', 'rt_runtime_us'
|
||||||
|
# RT uses system time for period and quota
|
||||||
|
quota = int( period * f * numCores() )
|
||||||
|
elif sched == 'cfs':
|
||||||
|
pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
|
||||||
|
# CFS uses wall clock time for period and CPU time for quota.
|
||||||
quota = int( self.period_us * f * numCores() )
|
quota = int( self.period_us * f * numCores() )
|
||||||
self.cgroupSet( pstr, self.period_us )
|
if f > 0 and quota < 1000:
|
||||||
|
info( '*** setCPUFrac: quota too small - adjusting period\n' )
|
||||||
|
quota = 1000
|
||||||
|
period = int( quota / f / numCores() )
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
if quota < 0:
|
||||||
|
# Reset to unlimited
|
||||||
|
quota = -1
|
||||||
|
# Set cgroup's period and quota
|
||||||
|
self.cgroupSet( pstr, period )
|
||||||
nquota = int ( self.cgroupGet( qstr ) )
|
nquota = int ( self.cgroupGet( qstr ) )
|
||||||
self.cgroupSet( qstr, quota )
|
self.cgroupSet( qstr, quota )
|
||||||
nperiod = int( self.cgroupGet( pstr ) )
|
nperiod = int( self.cgroupGet( pstr ) )
|
||||||
# Set RT priority
|
# Make sure it worked
|
||||||
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:
|
if nperiod != self.period_us:
|
||||||
error( '*** error: period is %s rather than %s\n' % (
|
error( '*** error: period is %s rather than %s\n' % (
|
||||||
nperiod, self.period_us ) )
|
nperiod, self.period_us ) )
|
||||||
if nquota != quota:
|
if nquota != quota:
|
||||||
error( '*** error: quota is %s rather than %s\n' % (
|
error( '*** error: quota is %s rather than %s\n' % (
|
||||||
nquota, quota ) )
|
nquota, quota ) )
|
||||||
|
if sched == 'rt':
|
||||||
|
# Set RT priority if necessary
|
||||||
|
nchrt = self.chrt( prio=20 )
|
||||||
|
# Nake sure it worked
|
||||||
|
if sched == 'SCHED_RR' not in nchrt:
|
||||||
|
error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
|
||||||
|
info( '( period', nperiod, 'quota', nquota, nchrt, ') ' )
|
||||||
|
else:
|
||||||
|
info( '( period', nperiod, 'quota', nquota, ') ' )
|
||||||
|
|
||||||
def config( self, cpu=None, **params ):
|
def config( self, cpu=None, sched=None, **params ):
|
||||||
"""cpu: desired overall system CPU fraction
|
"""cpu: desired overall system CPU fraction
|
||||||
params: parameters for Node.config()"""
|
params: parameters for Node.config()"""
|
||||||
r = Node.config( self, **params )
|
r = Node.config( self, **params )
|
||||||
|
# Was considering cpu={'cpu': cpu , 'sched': sched}, but
|
||||||
|
# that seems redundant
|
||||||
self.setParam( r, 'setCPUFrac', cpu=cpu )
|
self.setParam( r, 'setCPUFrac', cpu=cpu )
|
||||||
return r
|
return r
|
||||||
|
|
||||||
Host = CPULimitedHost
|
|
||||||
|
|
||||||
|
|
||||||
# Some important things to note:
|
# Some important things to note:
|
||||||
#
|
#
|
||||||
# The "IP" address which we assign to the switch is not
|
# The "IP" address which we assign to the switch is not
|
||||||
@@ -805,7 +850,7 @@ class ControllerParams( object ):
|
|||||||
class NOX( Controller ):
|
class NOX( Controller ):
|
||||||
"Controller to run a NOX application."
|
"Controller to run a NOX application."
|
||||||
|
|
||||||
def __init__( self, name, noxArgs=None, **kwargs ):
|
def __init__( self, name, noxArgs=[], **kwargs ):
|
||||||
"""Init.
|
"""Init.
|
||||||
name: name to give controller
|
name: name to give controller
|
||||||
noxArgs: list of args, or single arg, to pass to NOX"""
|
noxArgs: list of args, or single arg, to pass to NOX"""
|
||||||
|
|||||||
Reference in New Issue
Block a user