Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8137a9219 | |||
| 891ea4854d | |||
| 2e0e2d97ec | |||
| 71b3128122 | |||
| 5ca1fb7e0f | |||
| 18a0b7e4ac | |||
| e27673aeec | |||
| 035d4d20fe | |||
| 550ee2469e | |||
| d8d9035242 | |||
| f1aca0d9a6 | |||
| 669e420cc4 | |||
| 5507550c53 | |||
| 7cb340b7c9 | |||
| 92b601aba9 | |||
| 350fdbfe50 | |||
| 548580d817 | |||
| 149a1f5639 | |||
| 197b083fbc | |||
| a7648e78fb | |||
| 50202e1246 | |||
| a4338de38c | |||
| d08d101eba | |||
| 32f7847bca | |||
| 1bb990357f | |||
| a9c28885f3 | |||
| d776bd3a4f | |||
| 5d6fda932d | |||
| c1a6ae2b48 | |||
| 78606a35c9 | |||
| 1dd3de0d04 | |||
| ece14ff4b5 | |||
| e5653fb63b | |||
| 3f61ea7104 | |||
| 2ec866d2c5 | |||
| 26c61734da | |||
| e1246c3741 | |||
| 2d924f8a65 | |||
| 00d9b78035 | |||
| 612b21cbe7 | |||
| 8139695d46 | |||
| e8146dd130 | |||
| 44af37bc2b | |||
| beb05a71c8 | |||
| 74ea006d92 | |||
| 335ba99b60 | |||
| 8dcefd5ff9 | |||
| 28833d864c | |||
| a5af91d0d3 | |||
| 4deb735425 | |||
| f89d9a4d6b | |||
| d1b29d58df | |||
| ba8d4f9bd6 | |||
| 0b7c277ec2 | |||
| 1aec55d960 | |||
| 595427842c | |||
| 41245f5087 | |||
| b684ff7844 | |||
| ea7c326017 | |||
| f85c1cefb4 | |||
| 9005ce3255 | |||
| efc991547e | |||
| 8bebd37759 | |||
| e52d0ee1de | |||
| ff56881946 | |||
| 5a8bb48951 | |||
| 318ae55e35 | |||
| bf9c6ab7b4 | |||
| 14c1926081 | |||
| d7e5dfc5b6 | |||
| 8856d284c0 | |||
| 14ff3ad3d0 | |||
| 1d814c606d | |||
| 82f483f559 | |||
| a49c85a610 | |||
| 8e3699eca6 | |||
| e3c074b881 | |||
| 9addfc13ce | |||
| d27a3c52b9 | |||
| 2db4268ba8 | |||
| 0dbfd3a636 | |||
| a908fafad7 | |||
| 8688ca9249 | |||
| 8a622c3a9a | |||
| bf5becc7d5 | |||
| 216a4b7c9d | |||
| cbe20c7587 | |||
| edf46e9570 | |||
| b1f90976a3 | |||
| 4ac1148e9f | |||
| 84a91a14a4 | |||
| 94c02695fd | |||
| d8c88bedf3 | |||
| 7d557fd759 | |||
| 551a3666eb | |||
| 542fb6167e | |||
| ee222055f1 | |||
| 03dd914edc | |||
| a6bcad8f48 | |||
| 6f446f6e55 | |||
| 134a75ef38 | |||
| e6d8e974ce | |||
| 176856bc9b | |||
| 7a106d9b0d | |||
| de5d31184f | |||
| 65d46518c0 | |||
| 7a0ee56c80 | |||
| 1c0b54e52a | |||
| 148a3f5735 | |||
| 8996208084 | |||
| 7eb869af85 | |||
| 2b26161000 | |||
| e5b54a3143 | |||
| a24705d784 | |||
| 9d275262e2 | |||
| 8183cb6262 | |||
| 46cffb3bcf | |||
| 738ae1f3fa | |||
| fb25ee0200 | |||
| 2a4cbe2f57 | |||
| 0e3cb791b5 | |||
| ae5ac257dd | |||
| 3cd2e1a6aa | |||
| 08773f8fe9 | |||
| 8a7d42db0b | |||
| daa576c47a | |||
| b80f4aeb85 | |||
| 60b5864e1d |
@@ -1,3 +1,8 @@
|
||||
mnexec
|
||||
*.pyc
|
||||
*~
|
||||
\#*\#
|
||||
mininet.egg-info
|
||||
build/*
|
||||
dist/*
|
||||
|
||||
|
||||
@@ -25,9 +25,6 @@ ignore=CVS
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# Set the cache size for astng objects.
|
||||
cache-size=500
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
@@ -35,32 +32,23 @@ load-plugins=
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Enable only checker(s) with the given id(s). This option conflicts with the
|
||||
# disable-checker option
|
||||
#enable-checker=
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time.
|
||||
#enable=
|
||||
|
||||
# Enable all checker(s) except those with the given id(s). This option
|
||||
# conflicts with the enable-checker option
|
||||
#disable-checker=
|
||||
|
||||
# Enable all messages in the listed categories (IRCWEF).
|
||||
#enable-msg-cat=
|
||||
|
||||
# Disable all messages in the listed categories (IRCWEF).
|
||||
disable-msg-cat=IR
|
||||
|
||||
# Enable the message(s) with the given id(s).
|
||||
#enable-msg=
|
||||
|
||||
# Disable the message(s) with the given id(s).
|
||||
disable-msg=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once).
|
||||
disable=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801,I0011
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html
|
||||
output-format=text
|
||||
output-format=colorized
|
||||
|
||||
# Include message's id in outpu
|
||||
include-ids=yes
|
||||
@@ -276,7 +264,7 @@ int-import-graph=
|
||||
max-line-length=80
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
max-module-lines=1500
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
all: codecheck test
|
||||
|
||||
clean:
|
||||
rm -rf build dist *.egg-info *.pyc mnexec bin/mnexec
|
||||
|
||||
MININET = mininet/*.py
|
||||
TEST = mininet/test/*.py
|
||||
EXAMPLES = examples/*.py
|
||||
BIN = bin/mn
|
||||
PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
|
||||
|
||||
MNEXEC = mnexec
|
||||
P8IGN = E251,E201,E302,E202
|
||||
|
||||
all: codecheck test
|
||||
|
||||
clean:
|
||||
rm -rf build dist *.egg-info *.pyc $(MNEXEC)
|
||||
|
||||
codecheck: $(PYSRC)
|
||||
-echo "Running code check"
|
||||
pyflakes $(PYSRC)
|
||||
pylint --rcfile=.pylint $(PYSRC)
|
||||
pep8 --repeat --ignore=$(P8IGN) $(PYSRC)
|
||||
|
||||
errcheck: $(PYSRC)
|
||||
-echo "Running check for errors only"
|
||||
pyflakes $(PYSRC)
|
||||
pylint -E --rcfile=.pylint $(PYSRC)
|
||||
|
||||
test: $(MININET) $(TEST)
|
||||
-echo "Running tests"
|
||||
mininet/test/test_nets.py
|
||||
|
||||
install: mnexec
|
||||
cp mnexec bin/
|
||||
install: $(MNEXEC)
|
||||
install $(MNEXEC) /usr/local/bin/
|
||||
python setup.py install
|
||||
|
||||
develop: $(MNEXEC)
|
||||
install $(MNEXEC) /usr/local/bin/
|
||||
python setup.py develop
|
||||
|
||||
doc:
|
||||
doxygen doxygen.cfg
|
||||
|
||||
|
||||
@@ -18,13 +18,38 @@ import time
|
||||
|
||||
from mininet.clean import cleanup
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import lg, LEVELS, info
|
||||
from mininet.net import Mininet, init
|
||||
from mininet.node import KernelSwitch, Host, Controller, ControllerParams, NOX
|
||||
from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
|
||||
from mininet.log import lg, LEVELS, info, warn
|
||||
from mininet.net import Mininet, MininetWithControlNet
|
||||
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
||||
NOX, RemoteController, UserSwitch, OVSKernelSwitch,
|
||||
OVSLegacyKernelSwitch )
|
||||
from mininet.link import Link, TCLink
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||
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, newargs, kwargs = splitArgs( argStr )
|
||||
constructor = constructors.get( cname, None )
|
||||
|
||||
if not constructor:
|
||||
raise Exception( "error: %s is unknown - please specify one of %s" %
|
||||
( cname, constructors.keys() ) )
|
||||
|
||||
def customized( name, *args, **params ):
|
||||
"Customized Node constructor"
|
||||
params.update( kwargs )
|
||||
if not newargs:
|
||||
return constructor( name, *args, **params )
|
||||
if args:
|
||||
warn( 'warning: %s replacing %s with %s\n' % (
|
||||
constructor, args, newargs ) )
|
||||
return constructor( name, *newargs, **params )
|
||||
|
||||
return customized
|
||||
|
||||
|
||||
# built in topologies, created only when run
|
||||
TOPODEF = 'minimal'
|
||||
@@ -35,20 +60,26 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
||||
'tree': TreeTopo }
|
||||
|
||||
SWITCHDEF = 'ovsk'
|
||||
SWITCHES = { 'kernel': KernelSwitch,
|
||||
'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch }
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch,
|
||||
'ovsl': OVSLegacyKernelSwitch }
|
||||
|
||||
HOSTDEF = 'process'
|
||||
HOSTS = { 'process': Host }
|
||||
HOSTDEF = 'proc'
|
||||
HOSTS = { 'proc': Host,
|
||||
'rt': custom( CPULimitedHost, sched='rt' ),
|
||||
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
||||
|
||||
CONTROLLERDEF = 'ref'
|
||||
# a and b are the name and inNamespace params.
|
||||
CONTROLLERS = { 'ref': Controller,
|
||||
'nox_dump': lambda name: NOX( name, 'packetdump' ),
|
||||
'nox_pysw': lambda name: NOX( name, 'pyswitch' ),
|
||||
'remote': lambda name: None,
|
||||
'none': lambda name: None }
|
||||
'ovsc': OVSController,
|
||||
'nox': NOX,
|
||||
'remote': RemoteController,
|
||||
'none': lambda name: None }
|
||||
|
||||
LINKDEF = 'default'
|
||||
LINKS = { 'default': Link,
|
||||
'tc': TCLink }
|
||||
|
||||
|
||||
# optional tests to run
|
||||
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
||||
@@ -57,24 +88,30 @@ TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
||||
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
|
||||
'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
|
||||
# be flexible with input arg formats.
|
||||
topo_seq_params = [ s for s in topo_params if '=' not in s ]
|
||||
topo_seq_params = [ makeNumeric( s ) for s in topo_seq_params ]
|
||||
topo_kw_params = {}
|
||||
for s in [ p for p in topo_params if '=' in p ]:
|
||||
def splitArgs( argstr ):
|
||||
"""Split argument string into usable python arguments
|
||||
argstr: argument string with format fn,arg2,kw1=arg3...
|
||||
returns: fn, args, kwargs"""
|
||||
split = argstr.split( ',' )
|
||||
fn = split[ 0 ]
|
||||
params = split[ 1: ]
|
||||
# Convert int and float args; removes the need for function
|
||||
# to be flexible with input arg formats.
|
||||
args = [ makeNumeric( s ) for s in params if '=' not in s ]
|
||||
kwargs = {}
|
||||
for s in [ p for p in params if '=' in p ]:
|
||||
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 )
|
||||
return TOPOS[ topo_name ]( *topo_seq_params, **topo_kw_params )
|
||||
|
||||
def buildTopo( topoStr ):
|
||||
"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 ):
|
||||
@@ -88,10 +125,10 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
||||
raise Exception( 'Invalid default %s for choices dict: %s' %
|
||||
( default, name ) )
|
||||
if not helpStr:
|
||||
helpStr = '[' + ' '.join( choicesDict.keys() ) + ']'
|
||||
helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
|
||||
'[,param=value...]' )
|
||||
opts.add_option( '--' + name,
|
||||
type='choice',
|
||||
choices=choicesDict.keys(),
|
||||
type='string',
|
||||
default = default,
|
||||
help = helpStr )
|
||||
|
||||
@@ -124,11 +161,11 @@ class MininetRunner( object ):
|
||||
|
||||
def parseCustomFile( self, fileName ):
|
||||
"Parse custom file and add params before parsing cmd-line options."
|
||||
custom = {}
|
||||
customs = {}
|
||||
if os.path.isfile( fileName ):
|
||||
execfile( fileName, custom, custom )
|
||||
for name in custom:
|
||||
self.setCustom( name, custom[ name ] )
|
||||
execfile( fileName, customs, customs )
|
||||
for name, val in customs.iteritems():
|
||||
self.setCustom( name, val )
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
|
||||
@@ -136,11 +173,10 @@ class MininetRunner( object ):
|
||||
"""Parse command-line args and return options object.
|
||||
returns: opts parse options dict"""
|
||||
if '--custom' in sys.argv:
|
||||
print "custom in sys.argv"
|
||||
index = sys.argv.index( '--custom' )
|
||||
if len( sys.argv ) > index + 1:
|
||||
custom = sys.argv[ index + 1 ]
|
||||
self.parseCustomFile( custom )
|
||||
filename = sys.argv[ index + 1 ]
|
||||
self.parseCustomFile( filename )
|
||||
else:
|
||||
raise Exception( 'Custom file name not found' )
|
||||
|
||||
@@ -148,46 +184,46 @@ class MininetRunner( object ):
|
||||
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
|
||||
addDictOption( opts, HOSTS, HOSTDEF, 'host' )
|
||||
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
|
||||
addDictOption( opts, LINKS, LINKDEF, 'link' )
|
||||
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',
|
||||
default=False, help='clean and exit' )
|
||||
opts.add_option( '--custom', type='string', default=None,
|
||||
help='read custom topo and node params from .py file' )
|
||||
opts.add_option( '--test', type='choice', choices=TESTS,
|
||||
default=TESTS[ 0 ],
|
||||
help='[' + ' '.join( TESTS ) + ']' )
|
||||
help='|'.join( TESTS ) )
|
||||
opts.add_option( '--xterms', '-x', action='store_true',
|
||||
default=False, help='spawn xterms for each node' )
|
||||
opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8',
|
||||
help='base IP address for hosts' )
|
||||
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',
|
||||
default=False, help='set all-pairs ARP entries' )
|
||||
opts.add_option( '--verbosity', '-v', type='choice',
|
||||
choices=LEVELS.keys(), default = 'info',
|
||||
help = '[' + ' '.join( LEVELS.keys() ) + ']' )
|
||||
help = '|'.join( LEVELS.keys() ) )
|
||||
opts.add_option( '--ip', type='string', default='127.0.0.1',
|
||||
help='[ip address as a dotted decimal string for a'
|
||||
'remote controller]' )
|
||||
opts.add_option( '--port', type='int', default=6633,
|
||||
help='[port integer for a listening remote'
|
||||
' controller]' )
|
||||
help='ip address as a dotted decimal string for a'
|
||||
'remote controller' )
|
||||
opts.add_option( '--innamespace', action='store_true',
|
||||
default=False, help='sw and ctrl in namespace?' )
|
||||
opts.add_option( '--listenport', type='int', default=6634,
|
||||
help='[base port for passive switch listening'
|
||||
' controller]' )
|
||||
opts.add_option( '--listenport', type='int', default=6635,
|
||||
help='base port for passive switch listening' )
|
||||
opts.add_option( '--nolistenport', action='store_true',
|
||||
default=False, help="don't use passive listening port")
|
||||
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,
|
||||
help='[CLI script to run after tests]' )
|
||||
help='CLI script to run after tests' )
|
||||
opts.add_option( '--prefixlen', type='int', default=8,
|
||||
help='[prefix length (e.g. /8) for automatic '
|
||||
'network configuration]' )
|
||||
help='prefix length (e.g. /8) for automatic '
|
||||
'network configuration' )
|
||||
opts.add_option( '--pin', action='store_true',
|
||||
default=False, help="pin hosts to CPU cores "
|
||||
"(requires --host cfs or --host rt)" )
|
||||
|
||||
self.options, self.args = opts.parse_args()
|
||||
|
||||
@@ -202,9 +238,6 @@ class MininetRunner( object ):
|
||||
% self.options.verbosity )
|
||||
lg.setLogLevel( self.options.verbosity )
|
||||
|
||||
# validate environment setup
|
||||
init()
|
||||
|
||||
def begin( self ):
|
||||
"Create and run mininet."
|
||||
|
||||
@@ -215,34 +248,32 @@ class MininetRunner( object ):
|
||||
start = time.time()
|
||||
|
||||
topo = buildTopo( self.options.topo )
|
||||
switch = SWITCHES[ self.options.switch ]
|
||||
host = HOSTS[ self.options.host ]
|
||||
controller = CONTROLLERS[ self.options.controller ]
|
||||
if self.options.controller == 'remote':
|
||||
controller = lambda a: RemoteController( a,
|
||||
defaultIP=self.options.ip,
|
||||
port=self.options.port )
|
||||
switch = customNode( SWITCHES, self.options.switch )
|
||||
host = customNode( HOSTS, self.options.host )
|
||||
controller = customNode( CONTROLLERS, self.options.controller )
|
||||
link = customNode( LINKS, self.options.link )
|
||||
|
||||
if self.validate:
|
||||
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
|
||||
Net = MininetWithControlNet if inNamespace else Mininet
|
||||
ipBase = self.options.ipbase
|
||||
xterms = self.options.xterms
|
||||
mac = self.options.mac
|
||||
arp = self.options.arp
|
||||
pin = self.options.pin
|
||||
listenPort = None
|
||||
if not self.options.nolistenport:
|
||||
listenPort = self.options.listenport
|
||||
mn = Mininet( topo, switch, host, controller, controllerParams,
|
||||
inNamespace=inNamespace,
|
||||
xterms=xterms, autoSetMacs=mac,
|
||||
autoStaticArp=arp, listenPort=listenPort )
|
||||
mn = Net( topo=topo,
|
||||
switch=switch, host=host, controller=controller,
|
||||
link=link,
|
||||
ipBase=ipBase,
|
||||
inNamespace=inNamespace,
|
||||
xterms=xterms, autoSetMacs=mac,
|
||||
autoStaticArp=arp, autoPinCpus=pin,
|
||||
listenPort=listenPort )
|
||||
|
||||
if self.options.pre:
|
||||
CLI( mn, script=self.options.pre )
|
||||
|
||||
+24
-6
@@ -6,12 +6,21 @@ Mininet's Python API.
|
||||
|
||||
---
|
||||
|
||||
baresshd.py:
|
||||
|
||||
This example uses Mininet's medium-level API to create an sshd
|
||||
process running in a namespace. Doesn't use OpenFlow.
|
||||
|
||||
consoles.py:
|
||||
|
||||
This example creates a grid of console windows, one for each node,
|
||||
and allows interaction with and monitoring of each console, including
|
||||
graphical monitoring.
|
||||
|
||||
controllers.py:
|
||||
|
||||
This example creates a network and adds multiple controllers to it.
|
||||
|
||||
emptynet.py:
|
||||
|
||||
This example demonstrates creating an empty network (i.e. with no
|
||||
@@ -26,6 +35,15 @@ miniedit.py:
|
||||
|
||||
This example demonstrates creating a network via a graphical editor.
|
||||
|
||||
multiping.py:
|
||||
|
||||
This example demonstrates one method for
|
||||
monitoring output from multiple hosts, using node.monitor().
|
||||
|
||||
multipoll.py:
|
||||
|
||||
This example demonstrates monitoring output files from multiple hosts.
|
||||
|
||||
multitest.py:
|
||||
|
||||
This example creates a network and runs multiple tests on it.
|
||||
@@ -36,6 +54,10 @@ These two examples demonstrate how to create a network by using the lowest-
|
||||
level Mininet functions. Generally the higher-level API is easier to use,
|
||||
but scratchnet shows what is going on behind the scenes.
|
||||
|
||||
simpleperf.py:
|
||||
|
||||
A simple example of configuring network and CPU bandwidth limits.
|
||||
|
||||
sshd.py:
|
||||
|
||||
This example shows how to run an sshd process in each host, allowing
|
||||
@@ -44,10 +66,10 @@ to an interface in the root namespace (generaly the control network
|
||||
already lives in the root namespace, so it does not need to be explicitly
|
||||
connected.)
|
||||
|
||||
treeping64:
|
||||
treeping64.py:
|
||||
|
||||
This example creates a 64-host tree network, and attempts to check full
|
||||
connectivity using ping, for three different switch/datapath types.
|
||||
connectivity using ping, for different switch/datapath types.
|
||||
|
||||
tree1024.py:
|
||||
|
||||
@@ -55,7 +77,3 @@ This example attempts to create a 1024-host network, and then runs the
|
||||
CLI on it. It may run into scalability limits, depending on available
|
||||
memory and sysctl configuration (see INSTALL.)
|
||||
|
||||
udpbwtest.py:
|
||||
|
||||
This example shows how to run a test across an entire network, and monitor
|
||||
the output of a set of hosts in real time.
|
||||
|
||||
@@ -6,14 +6,17 @@ from mininet.node import Host
|
||||
|
||||
print "*** Creating nodes"
|
||||
h1 = Host( 'h1' )
|
||||
|
||||
root = Host( 'root', inNamespace=False )
|
||||
|
||||
print "*** Creating links"
|
||||
h1.linkTo( root )
|
||||
|
||||
print h1
|
||||
|
||||
print "*** Configuring nodes"
|
||||
h1.setIP( h1.intfs[ 0 ], '10.0.0.1', 8 )
|
||||
root.setIP( root.intfs[ 0 ], '10.0.0.2', 8 )
|
||||
h1.setIP( '10.0.0.1', 8 )
|
||||
root.setIP( '10.0.0.2', 8 )
|
||||
|
||||
print "*** Creating banner file"
|
||||
f = open( '/tmp/%s.banner' % h1.name, 'w' )
|
||||
|
||||
+6
-12
@@ -107,8 +107,10 @@ class Console( Frame ):
|
||||
self.text.insert( 'end', text )
|
||||
self.text.mark_set( 'insert', 'end' )
|
||||
self.text.see( 'insert' )
|
||||
outputHook = lambda x, y: True # make pylint happier
|
||||
if self.outputHook:
|
||||
self.outputHook( self, text )
|
||||
outputHook = self.outputHook
|
||||
outputHook( self, text )
|
||||
|
||||
def handleKey( self, event ):
|
||||
"If it's an interactive command, send it to the node."
|
||||
@@ -130,27 +132,22 @@ class Console( Frame ):
|
||||
self.sendCmd( cmd )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def handleInt( self, event=None ):
|
||||
def handleInt( self, _event=None ):
|
||||
"Handle control-c."
|
||||
self.node.sendInt()
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def sendCmd( self, cmd ):
|
||||
"Send a command to our node."
|
||||
if not self.node.waiting:
|
||||
self.node.sendCmd( cmd )
|
||||
|
||||
# Callback ignores fds
|
||||
# pylint: disable-msg=W0613
|
||||
def handleReadable( self, fds, timeoutms=None ):
|
||||
def handleReadable( self, _fds, timeoutms=None ):
|
||||
"Handle file readable event."
|
||||
data = self.node.monitor( timeoutms )
|
||||
self.append( data )
|
||||
if not self.node.waiting:
|
||||
# Print prompt
|
||||
self.append( self.prompt )
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def waiting( self ):
|
||||
"Are we waiting for output?"
|
||||
@@ -319,9 +316,7 @@ class ConsoleApp( Frame ):
|
||||
|
||||
self.pack( expand=True, fill='both' )
|
||||
|
||||
# Update callback doesn't use console arg
|
||||
# pylint: disable-msg=W0613
|
||||
def updateGraph( self, console, output ):
|
||||
def updateGraph( self, _console, output ):
|
||||
"Update our graph."
|
||||
m = re.search( r'(\d+) Mbits/sec', output )
|
||||
if not m:
|
||||
@@ -332,7 +327,6 @@ class ConsoleApp( Frame ):
|
||||
self.graph.addBar( self.bw )
|
||||
self.bw = 0
|
||||
self.updates = 0
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def setOutputHook( self, fn=None, consoles=None ):
|
||||
"Register fn as output hook [on specific consoles.]"
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
cpu.py: test iperf bandwidth for varying cpu limtis
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom
|
||||
from mininet.log import setLogLevel, output
|
||||
|
||||
from time import sleep
|
||||
|
||||
def waitListening(client, server, port):
|
||||
"Wait until server is listening on port"
|
||||
if not client.cmd('which telnet'):
|
||||
raise Exception('Could not find telnet')
|
||||
cmd = ('sh -c "echo A | telnet -e A %s %s"' %
|
||||
(server.IP(), port))
|
||||
while 'Connected' not in client.cmd(cmd):
|
||||
output('waiting for', server,
|
||||
'to listen on port', port, '\n')
|
||||
sleep(.5)
|
||||
|
||||
|
||||
def bwtest( cpuLimits, period_us=100000, seconds=5 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
cpu: cpu limit as fraction of overall CPU time"""
|
||||
|
||||
topo = TreeTopo( depth=1, fanout=2 )
|
||||
|
||||
results = {}
|
||||
|
||||
for sched in 'rt', 'cfs':
|
||||
print '*** Testing with', sched, 'bandwidth limiting'
|
||||
for cpu in cpuLimits:
|
||||
host = custom( CPULimitedHost, sched=sched,
|
||||
period_us=period_us,
|
||||
cpu=cpu )
|
||||
net = Mininet( topo=topo, host=host )
|
||||
net.start()
|
||||
net.pingAll()
|
||||
hosts = [ net.getNodeByName( h ) for h in topo.hosts() ]
|
||||
client, server = hosts[ 0 ], hosts[ -1 ]
|
||||
server.cmd( 'iperf -s -p 5001 &' )
|
||||
waitListening( client, server, 5001 )
|
||||
result = client.cmd( 'iperf -yc -t %s -c %s' % (
|
||||
seconds, server.IP() ) ).split( ',' )
|
||||
bps = float( result[ -1 ] )
|
||||
server.cmdPrint( 'kill %iperf' )
|
||||
net.stop()
|
||||
updated = results.get( sched, [] )
|
||||
updated += [ ( cpu, bps ) ]
|
||||
results[ sched ] = updated
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def dump( results ):
|
||||
"Dump results"
|
||||
|
||||
fmt = '%s\t%s\t%s'
|
||||
|
||||
print
|
||||
print fmt % ( 'sched', 'cpu', 'client MB/s' )
|
||||
print
|
||||
|
||||
for sched in sorted( results.keys() ):
|
||||
entries = results[ sched ]
|
||||
for cpu, bps in entries:
|
||||
pct = '%.2f%%' % ( cpu * 100 )
|
||||
mbps = bps / 1e6
|
||||
print fmt % ( sched, pct, mbps )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
limits = [ .45, .4, .3, .2, .1 ]
|
||||
out = bwtest( limits )
|
||||
dump( out )
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
limit.py: example of using link and CPU limits
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.link import TCIntf
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom, quietRun
|
||||
from mininet.log import setLogLevel
|
||||
from time import sleep
|
||||
|
||||
def testLinkLimit( net, bw ):
|
||||
"Run bandwidth limit test"
|
||||
print '*** Testing network %.2f Mbps bandwidth limit' % bw
|
||||
net.iperf( )
|
||||
|
||||
def testCpuLimit( net, cpu ):
|
||||
"run CPU limit test"
|
||||
pct = cpu * 100
|
||||
print '*** Testing CPU %.0f%% bandwidth limit' % pct
|
||||
h1, h2 = net.hosts
|
||||
h1.cmd( 'while true; do a=1; done &' )
|
||||
h2.cmd( 'while true; do a=1; done &' )
|
||||
pid1 = h1.cmd( 'echo $!' ).strip()
|
||||
pid2 = h2.cmd( 'echo $!' ).strip()
|
||||
cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 )
|
||||
# It's a shame that this is what pylint prefers
|
||||
for _ in range( 5 ):
|
||||
sleep( 1 )
|
||||
print quietRun( cmd ).strip()
|
||||
h1.cmd( 'kill %1')
|
||||
h2.cmd( 'kill %1')
|
||||
|
||||
def limit( bw=10, cpu=.4 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
bw: interface bandwidth limit in Mbps
|
||||
cpu: cpu limit as fraction of overall CPU time"""
|
||||
intf = custom( TCIntf, bw=bw )
|
||||
myTopo = TreeTopo( depth=1, fanout=2 )
|
||||
for sched in 'rt', 'cfs':
|
||||
print '*** Testing with', sched, 'bandwidth limiting'
|
||||
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
|
||||
net = Mininet( topo=myTopo, intf=intf, host=host )
|
||||
net.start()
|
||||
testLinkLimit( net, bw=bw )
|
||||
testCpuLimit( net, cpu=cpu )
|
||||
net.stop()
|
||||
|
||||
def verySimpleLimit( bw=150 ):
|
||||
"Absurdly simple limiting test"
|
||||
intf = custom( TCIntf, bw=bw )
|
||||
net = Mininet( intf=intf )
|
||||
h1, h2 = net.addHost( 'h1' ), net.addHost( 'h2' )
|
||||
net.addLink( h1, h2 )
|
||||
net.start()
|
||||
net.pingAll()
|
||||
net.iperf()
|
||||
h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() )
|
||||
h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() )
|
||||
h1.cmdPrint( 'tc -s qdisc ls dev', h1.defaultIntf() )
|
||||
h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
limit()
|
||||
+29
-31
@@ -6,9 +6,9 @@ using both kernel and user datapaths.
|
||||
|
||||
We construct a network of N hosts and N-1 switches, connected as follows:
|
||||
|
||||
h1 <-> sN+1 <-> sN+2 .. sN+N-1
|
||||
| | |
|
||||
h2 h3 hN
|
||||
h1 <-> s1 <-> s2 .. sN-1
|
||||
| | |
|
||||
h2 h3 hN
|
||||
|
||||
WARNING: by default, the reference controller only supports 16
|
||||
switches, so this test WILL NOT WORK unless you have recompiled
|
||||
@@ -23,42 +23,40 @@ of switches, this example demonstrates:
|
||||
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import lg
|
||||
from mininet.util import irange
|
||||
|
||||
import sys
|
||||
flush = sys.stdout.flush
|
||||
|
||||
from mininet.net import init, Mininet
|
||||
# from mininet.node import KernelSwitch
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.topo import Topo, Node
|
||||
from mininet.log import lg
|
||||
|
||||
class LinearTestTopo( Topo ):
|
||||
"Topology for a string of N hosts and N-1 switches."
|
||||
|
||||
def __init__( self, N ):
|
||||
def __init__( self, N, **params ):
|
||||
|
||||
# Add default members to class.
|
||||
super( LinearTestTopo, self ).__init__()
|
||||
# Initialize topology
|
||||
Topo.__init__( self, **params )
|
||||
|
||||
# Create switch and host nodes
|
||||
hosts = range( 1, N + 1 )
|
||||
switches = range( N + 1 , N + N )
|
||||
for h in hosts:
|
||||
self.add_node( h, Node( is_switch=False ) )
|
||||
for s in switches:
|
||||
self.add_node( s, Node( is_switch=True ) )
|
||||
# Create switches and hosts
|
||||
hosts = [ self.add_host( 'h%s' % h )
|
||||
for h in irange( 1, N ) ]
|
||||
switches = [ self.add_switch( 's%s' % s )
|
||||
for s in irange( 1, N - 1 ) ]
|
||||
|
||||
# Wire up switches
|
||||
for s in switches[ :-1 ]:
|
||||
self.add_edge( s, s + 1 )
|
||||
last = None
|
||||
for switch in switches:
|
||||
if last:
|
||||
self.add_link( last, switch )
|
||||
last = switch
|
||||
|
||||
# Wire up hosts
|
||||
self.add_edge( hosts[ 0 ], switches[ 0 ] )
|
||||
for h in hosts[ 1: ]:
|
||||
self.add_edge( h, h + N - 1 )
|
||||
|
||||
# Consider all switches and hosts 'on'
|
||||
self.enable_all()
|
||||
self.add_link( hosts[ 0 ], switches[ 0 ] )
|
||||
for host, switch in zip( hosts[ 1: ], switches ):
|
||||
self.add_link( host, switch )
|
||||
|
||||
|
||||
def linearBandwidthTest( lengths ):
|
||||
@@ -69,15 +67,16 @@ def linearBandwidthTest( lengths ):
|
||||
switchCount = max( lengths )
|
||||
hostCount = switchCount + 1
|
||||
|
||||
switches = { # 'reference kernel': KernelSwitch,
|
||||
'reference user': UserSwitch,
|
||||
switches = { 'reference user': UserSwitch,
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
|
||||
topo = LinearTestTopo( hostCount )
|
||||
|
||||
for datapath in switches.keys():
|
||||
print "*** testing", datapath, "datapath"
|
||||
Switch = switches[ datapath ]
|
||||
results[ datapath ] = []
|
||||
net = Mininet( topo=LinearTestTopo( hostCount ), switch=Switch )
|
||||
net = Mininet( topo=topo, switch=Switch )
|
||||
net.start()
|
||||
print "*** testing basic connectivity"
|
||||
for n in lengths:
|
||||
@@ -106,7 +105,6 @@ def linearBandwidthTest( lengths ):
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info' )
|
||||
init()
|
||||
sizes = [ 1, 10, 20, 40, 60, 80, 100 ]
|
||||
print "*** Running linearBandwidthTest", sizes
|
||||
linearBandwidthTest( sizes )
|
||||
|
||||
+7
-18
@@ -299,14 +299,11 @@ class MiniEdit( Frame ):
|
||||
# Delete from view
|
||||
self.canvas.delete( item )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def deleteSelection( self, event ):
|
||||
def deleteSelection( self, _event ):
|
||||
"Delete the selected item."
|
||||
if self.selection is not None:
|
||||
self.deleteItem( self.selection )
|
||||
self.selectItem( None )
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def nodeIcon( self, node, name ):
|
||||
"Create a new node icon."
|
||||
@@ -350,14 +347,11 @@ class MiniEdit( Frame ):
|
||||
c = self.canvas
|
||||
c.coords( self.link, self.linkx, self.linky, x, y )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def releaseLink( self, event ):
|
||||
def releaseLink( self, _event ):
|
||||
"Give up on the current link."
|
||||
if self.link is not None:
|
||||
self.canvas.delete( self.link )
|
||||
self.linkWidget = self.linkItem = self.link = None
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
# Generic node handlers
|
||||
|
||||
@@ -385,12 +379,9 @@ class MiniEdit( Frame ):
|
||||
"Select node on entry."
|
||||
self.selectNode( event )
|
||||
|
||||
# Callback ignores event
|
||||
# pylint: disable-msg=W0613
|
||||
def leaveNode( self, event ):
|
||||
def leaveNode( self, _event ):
|
||||
"Restore old selection on exit."
|
||||
self.selectItem( self.lastSelection )
|
||||
# pylint: enable-msg=W0613
|
||||
|
||||
def clickNode( self, event ):
|
||||
"Node click handler."
|
||||
@@ -454,23 +445,21 @@ class MiniEdit( Frame ):
|
||||
# Link bindings
|
||||
# Selection still needs a bit of work overall
|
||||
# Callbacks ignore event
|
||||
# pylint: disable-msg=W0613
|
||||
|
||||
def select( event, link=self.link ):
|
||||
def select( _event, link=self.link ):
|
||||
"Select item on mouse entry."
|
||||
self.selectItem( link )
|
||||
|
||||
def highlight( event, link=self.link ):
|
||||
def highlight( _event, link=self.link ):
|
||||
"Highlight item on mouse entry."
|
||||
# self.selectItem( link )
|
||||
self.canvas.itemconfig( link, fill='green' )
|
||||
|
||||
def unhighlight( event, link=self.link ):
|
||||
def unhighlight( _event, link=self.link ):
|
||||
"Unhighlight item on mouse exit."
|
||||
self.canvas.itemconfig( link, fill='blue' )
|
||||
# self.selectItem( None )
|
||||
|
||||
# pylint: disable-msg=W0613
|
||||
self.canvas.tag_bind( self.link, '<Enter>', highlight )
|
||||
self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
|
||||
self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
|
||||
@@ -602,7 +591,7 @@ class MiniEdit( Frame ):
|
||||
cleanUpScreens()
|
||||
self.net = None
|
||||
|
||||
def xterm( self, ignore=None ):
|
||||
def xterm( self, _=None ):
|
||||
"Make an xterm when a button is pressed."
|
||||
if ( self.selection is None or
|
||||
self.net is None or
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
multiping.py: monitor multiple sets of hosts using ping
|
||||
|
||||
This demonstrates how one may send a simple shell script to
|
||||
multiple hosts and monitor their output interactively for a period=
|
||||
of time.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
from select import poll, POLLIN
|
||||
from time import time
|
||||
|
||||
def chunks( l, n ):
|
||||
"Divide list l into chunks of size n - thanks Stackoverflow"
|
||||
return [ l[ i : i + n ] for i in range( 0, len( l ), n ) ]
|
||||
|
||||
def startpings( host, targetips ):
|
||||
"Tell host to repeatedly ping targets"
|
||||
|
||||
targetips.append( '10.0.0.200' )
|
||||
|
||||
targetips = ' '.join( targetips )
|
||||
|
||||
# BL: Not sure why loopback intf isn't up!
|
||||
host.cmd( 'ifconfig lo up' )
|
||||
|
||||
# Simple ping loop
|
||||
cmd = ( 'while true; do '
|
||||
' for ip in %s; do ' % targetips +
|
||||
' echo -n %s "->" $ip ' % host.IP() +
|
||||
' `ping -c1 -w 1 $ip | grep packets` ;'
|
||||
' sleep 1;'
|
||||
' done; '
|
||||
'done &' )
|
||||
|
||||
print ( '*** Host %s (%s) will be pinging ips: %s' %
|
||||
( host.name, host.IP(), targetips ) )
|
||||
|
||||
host.cmd( cmd )
|
||||
|
||||
def multiping( netsize, chunksize, seconds):
|
||||
"Ping subsets of size chunksize in net of size netsize"
|
||||
|
||||
# Create network and identify subnets
|
||||
topo = SingleSwitchTopo( netsize )
|
||||
net = Mininet( topo=topo )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
subnets = chunks( hosts, chunksize )
|
||||
|
||||
# Create polling object
|
||||
fds = [ host.stdout.fileno() for host in hosts ]
|
||||
poller = poll()
|
||||
for fd in fds:
|
||||
poller.register( fd, POLLIN )
|
||||
|
||||
# Start pings
|
||||
for subnet in subnets:
|
||||
ips = [ host.IP() for host in subnet ]
|
||||
for host in subnet:
|
||||
startpings( host, ips )
|
||||
|
||||
# Monitor output
|
||||
endTime = time() + seconds
|
||||
while time() < endTime:
|
||||
readable = poller.poll(1000)
|
||||
for fd, _mask in readable:
|
||||
node = Node.outToNode[ fd ]
|
||||
print '%s:' % node.name, node.monitor().strip()
|
||||
|
||||
# Stop pings
|
||||
for host in hosts:
|
||||
host.cmd( 'kill %while' )
|
||||
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
multiping( netsize=20, chunksize=4, seconds=10 )
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Simple example of sending output to multiple files and
|
||||
monitoring them
|
||||
"""
|
||||
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.net import Mininet
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
from time import time
|
||||
from select import poll, POLLIN
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
def monitorFiles( outfiles, seconds, timeoutms ):
|
||||
"Monitor set of files and return [(host, line)...]"
|
||||
devnull = open( '/dev/null', 'w' )
|
||||
tails, fdToFile, fdToHost = {}, {}, {}
|
||||
for h, outfile in outfiles.iteritems():
|
||||
tail = Popen( [ 'tail', '-f', outfile ],
|
||||
stdout=PIPE, stderr=devnull )
|
||||
fd = tail.stdout.fileno()
|
||||
tails[ h ] = tail
|
||||
fdToFile[ fd ] = tail.stdout
|
||||
fdToHost[ fd ] = h
|
||||
# Prepare to poll output files
|
||||
readable = poll()
|
||||
for t in tails.values():
|
||||
readable.register( t.stdout.fileno(), POLLIN )
|
||||
# Run until a set number of seconds have elapsed
|
||||
endTime = time() + seconds
|
||||
while time() < endTime:
|
||||
fdlist = readable.poll(timeoutms)
|
||||
if fdlist:
|
||||
for fd, _flags in fdlist:
|
||||
f = fdToFile[ fd ]
|
||||
host = fdToHost[ fd ]
|
||||
# Wait for a line of output
|
||||
line = f.readline().strip()
|
||||
yield host, line
|
||||
else:
|
||||
# If we timed out, return nothing
|
||||
yield None, ''
|
||||
for t in tails.values():
|
||||
t.terminate()
|
||||
devnull.close() # Not really necessary
|
||||
|
||||
|
||||
def monitorTest( N=3, seconds=3 ):
|
||||
"Run pings and monitor multiple hosts"
|
||||
topo = SingleSwitchTopo( N )
|
||||
net = Mininet( topo )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
print "Starting test..."
|
||||
server = hosts[ 0 ]
|
||||
outfiles, errfiles = {}, {}
|
||||
for h in hosts:
|
||||
# Create and/or erase output files
|
||||
outfiles[ h ] = '/tmp/%s.out' % h.name
|
||||
errfiles[ h ] = '/tmp/%s.err' % h.name
|
||||
h.cmd( 'echo >', outfiles[ h ] )
|
||||
h.cmd( 'echo >', errfiles[ h ] )
|
||||
# Start pings
|
||||
h.cmdPrint('ping', server.IP(),
|
||||
'>', outfiles[ h ],
|
||||
'2>', errfiles[ h ],
|
||||
'&' )
|
||||
print "Monitoring output for", seconds, "seconds"
|
||||
for h, line in monitorFiles( outfiles, seconds, timeoutms=500 ):
|
||||
if h:
|
||||
print '%s: %s' % ( h.name, line )
|
||||
for h in hosts:
|
||||
h.cmd('kill %ping')
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
monitorTest()
|
||||
+28
-18
@@ -8,13 +8,16 @@ but it exposes the configuration details and allows customization.
|
||||
For most tasks, the higher-level API will be preferable.
|
||||
"""
|
||||
|
||||
from mininet.net import init
|
||||
from mininet.node import Node, OVSKernelSwitch
|
||||
from mininet.util import createLink
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.link import Link
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.util import quietRun
|
||||
|
||||
def scratchNet( cname='controller', cargs='ptcp:' ):
|
||||
"Create network from scratch using kernel switch."
|
||||
from time import sleep
|
||||
|
||||
def scratchNet( cname='controller', cargs='-v ptcp:' ):
|
||||
"Create network from scratch using Open vSwitch."
|
||||
|
||||
info( "*** Creating nodes\n" )
|
||||
controller = Node( 'c0', inNamespace=False )
|
||||
@@ -23,36 +26,43 @@ def scratchNet( cname='controller', cargs='ptcp:' ):
|
||||
h1 = Node( 'h1' )
|
||||
|
||||
info( "*** Creating links\n" )
|
||||
createLink( node1=h0, node2=switch, port1=0, port2=0 )
|
||||
createLink( node1=h1, node2=switch, port1=0, port2=1 )
|
||||
Link( h0, switch )
|
||||
Link( h1, switch )
|
||||
|
||||
info( "*** Configuring hosts\n" )
|
||||
h0.setIP( h0.intfs[ 0 ], '192.168.123.1', 24 )
|
||||
h1.setIP( h1.intfs[ 0 ], '192.168.123.2', 24 )
|
||||
h0.setIP( '192.168.123.1/24' )
|
||||
h1.setIP( '192.168.123.2/24' )
|
||||
info( str( h0 ) + '\n' )
|
||||
info( str( h1 ) + '\n' )
|
||||
|
||||
info( "*** Starting network using Open vSwitch kernel datapath\n" )
|
||||
info( "*** Starting network using Open vSwitch\n" )
|
||||
controller.cmd( cname + ' ' + cargs + '&' )
|
||||
switch.cmd( 'ovs-dpctl del-dp dp0' )
|
||||
switch.cmd( 'ovs-dpctl add-dp dp0' )
|
||||
switch.cmd( 'ovs-vsctl del-br dp0' )
|
||||
switch.cmd( 'ovs-vsctl add-br dp0' )
|
||||
for intf in switch.intfs.values():
|
||||
print switch.cmd( 'ovs-dpctl add-if dp0 ' + intf )
|
||||
print switch.cmd( 'ovs-openflowd dp0 tcp:127.0.0.1 &' )
|
||||
print switch.cmd( 'ovs-vsctl add-port dp0 %s' % intf )
|
||||
|
||||
# Note: controller and switch are in root namespace, and we
|
||||
# can connect via loopback interface
|
||||
switch.cmd( 'ovs-vsctl set-controller dp0 tcp:127.0.0.1:6633' )
|
||||
|
||||
info( '*** Waiting for switch to connect to controller' )
|
||||
while 'is_connected' not in quietRun( 'ovs-vsctl show' ):
|
||||
sleep( 1 )
|
||||
info( '.' )
|
||||
info( '\n' )
|
||||
|
||||
info( "*** Running test\n" )
|
||||
h0.cmdPrint( 'ping -c1 ' + h1.IP() )
|
||||
|
||||
info( "*** Stopping network\n" )
|
||||
controller.cmd( 'kill %' + cname )
|
||||
switch.cmd( 'ovs-dpctl del-dp dp0' )
|
||||
switch.cmd( 'kill %ovs-openflowd' )
|
||||
switch.cmd( 'ovs-vsctl del-br dp0' )
|
||||
switch.deleteIntfs()
|
||||
info( '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (kernel datapath)\n' )
|
||||
OVSKernelSwitch.setup()
|
||||
init()
|
||||
Mininet.init()
|
||||
scratchNet()
|
||||
|
||||
+16
-11
@@ -10,11 +10,16 @@ For most tasks, the higher-level API will be preferable.
|
||||
This version uses the user datapath and an explicit control network.
|
||||
"""
|
||||
|
||||
from mininet.net import init
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.util import createLink
|
||||
from mininet.link import Link
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
def linkIntfs( node1, node2 ):
|
||||
"Create link from node1 to node2 and return intfs"
|
||||
link = Link( node1, node2 )
|
||||
return link.intf1, link.intf2
|
||||
|
||||
def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
"Create network from scratch using user switch."
|
||||
|
||||
@@ -28,17 +33,17 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
switch = Node( 's0')
|
||||
h0 = Node( 'h0' )
|
||||
h1 = Node( 'h1' )
|
||||
cintf, sintf = createLink( controller, switch )
|
||||
h0intf, sintf1 = createLink( h0, switch )
|
||||
h1intf, sintf2 = createLink( h1, switch )
|
||||
cintf, sintf = linkIntfs( controller, switch )
|
||||
h0intf, sintf1 = linkIntfs( h0, switch )
|
||||
h1intf, sintf2 = linkIntfs( h1, switch )
|
||||
|
||||
info( '*** Configuring control network\n' )
|
||||
controller.setIP( cintf, '10.0.123.1', 24 )
|
||||
switch.setIP( sintf, '10.0.123.2', 24 )
|
||||
controller.setIP( '10.0.123.1/24', cintf )
|
||||
switch.setIP( '10.0.123.2/24', sintf)
|
||||
|
||||
info( '*** Configuring hosts\n' )
|
||||
h0.setIP( h0intf, '192.168.123.1', 24 )
|
||||
h1.setIP( h1intf, '192.168.123.2', 24 )
|
||||
h0.setIP( '192.168.123.1/24', h0intf )
|
||||
h1.setIP( '192.168.123.2/24', h1intf )
|
||||
|
||||
info( '*** Network state:\n' )
|
||||
for node in controller, switch, h0, h1:
|
||||
@@ -47,7 +52,7 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
info( '*** Starting controller and user datapath\n' )
|
||||
controller.cmd( cname + ' ' + cargs + '&' )
|
||||
switch.cmd( 'ifconfig lo 127.0.0.1' )
|
||||
intfs = [ sintf1, sintf2 ]
|
||||
intfs = [ str( i ) for i in sintf1, sintf2 ]
|
||||
switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' )
|
||||
switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' )
|
||||
|
||||
@@ -64,5 +69,5 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (user datapath)\n' )
|
||||
init()
|
||||
Mininet.init()
|
||||
scratchNetUser()
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Simple example of setting network and CPU parameters
|
||||
"""
|
||||
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.link import TCLink
|
||||
from mininet.util import dumpNodeConnections
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
class SingleSwitchTopo(Topo):
|
||||
"Single switch connected to n hosts."
|
||||
def __init__(self, n=2, **opts):
|
||||
Topo.__init__(self, **opts)
|
||||
switch = self.add_switch('s1')
|
||||
for h in range(n):
|
||||
# Each host gets 50%/n of system CPU
|
||||
host = self.add_host('h%s' % (h + 1),
|
||||
cpu=.5 / n)
|
||||
# 10 Mbps, 5ms delay, 10% loss
|
||||
self.add_link(host, switch,
|
||||
bw=10, delay='5ms', loss=10, use_htb=True)
|
||||
|
||||
def perfTest():
|
||||
"Create network and run simple performance test"
|
||||
topo = SingleSwitchTopo(n=4)
|
||||
net = Mininet(topo=topo,
|
||||
host=CPULimitedHost, link=TCLink)
|
||||
net.start()
|
||||
print "Dumping host connections"
|
||||
dumpNodeConnections(net.hosts)
|
||||
print "Testing network connectivity"
|
||||
net.pingAll()
|
||||
print "Testing bandwidth between h1 and h4"
|
||||
h1, h4 = net.getNodeByName('h1', 'h4')
|
||||
net.iperf((h1, h4))
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
perfTest()
|
||||
+4
-4
@@ -21,7 +21,7 @@ from mininet.cli import CLI
|
||||
from mininet.log import lg
|
||||
from mininet.node import Node, OVSKernelSwitch
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import createLink
|
||||
from mininet.link import Link
|
||||
|
||||
def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
@@ -37,13 +37,13 @@ def connectToRootNS( network, switch, ip, prefixLen, routes ):
|
||||
routes: host networks to route to"""
|
||||
# Create a node in root namespace and link to switch 0
|
||||
root = Node( 'root', inNamespace=False )
|
||||
intf = createLink( root, switch )[ 0 ]
|
||||
root.setIP( intf, ip, prefixLen )
|
||||
intf = Link( root, switch ).intf1
|
||||
root.setIP( ip, prefixLen, intf )
|
||||
# Start network that now includes link to root namespace
|
||||
network.start()
|
||||
# Add routes from root ns to hosts
|
||||
for route in routes:
|
||||
root.cmd( 'route add -net ' + route + ' dev ' + intf )
|
||||
root.cmd( 'route add -net ' + route + ' dev ' + str( intf ) )
|
||||
|
||||
def sshd( network, cmd='/usr/sbin/sshd', opts='-D' ):
|
||||
"Start a network, connect it to root ns, and run sshd on all hosts."
|
||||
|
||||
@@ -45,6 +45,12 @@ def cleanup():
|
||||
if dp != '':
|
||||
sh( 'dpctl deldp ' + dp )
|
||||
|
||||
info( "*** Removing OVS datapaths" )
|
||||
dps = sh("ovs-vsctl list-br").split( '\n' )
|
||||
for dp in dps:
|
||||
if dp:
|
||||
sh( 'ovs-vsctl del-br ' + dp )
|
||||
|
||||
info( "*** Removing all links of the pattern foo-ethX\n" )
|
||||
links = sh( "ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' )
|
||||
for link in links:
|
||||
|
||||
+21
-27
@@ -33,7 +33,7 @@ import sys
|
||||
|
||||
from mininet.log import info, output, error
|
||||
from mininet.term import makeTerms
|
||||
from mininet.util import quietRun, isShellBuiltin
|
||||
from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
|
||||
|
||||
class CLI( Cmd ):
|
||||
"Simple command-line interface to talk to nodes."
|
||||
@@ -46,6 +46,9 @@ class CLI( Cmd ):
|
||||
self.nodemap = {} # map names to Node objects
|
||||
for node in self.nodelist:
|
||||
self.nodemap[ node.name ] = node
|
||||
# Local variable bindings for py command
|
||||
self.locals = { 'net': mininet }
|
||||
self.locals.update( self.nodemap )
|
||||
# Attempt to handle input
|
||||
self.stdin = stdin
|
||||
self.inPoller = poll()
|
||||
@@ -77,7 +80,7 @@ class CLI( Cmd ):
|
||||
# Disable pylint "Unused argument: 'arg's'" messages, as well as
|
||||
# "method could be a function" warning, since each CLI function
|
||||
# must have the same interface
|
||||
# pylint: disable-msg=W0613,R0201
|
||||
# pylint: disable-msg=R0201
|
||||
|
||||
helpStr = (
|
||||
'You may also send a command to a node using:\n'
|
||||
@@ -104,21 +107,14 @@ class CLI( Cmd ):
|
||||
if line is '':
|
||||
output( self.helpStr )
|
||||
|
||||
def do_nodes( self, line ):
|
||||
def do_nodes( self, _line ):
|
||||
"List all nodes."
|
||||
nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
|
||||
output( 'available nodes are: \n%s\n' % nodes )
|
||||
|
||||
def do_net( self, line ):
|
||||
def do_net( self, _line ):
|
||||
"List network connections."
|
||||
for switch in self.mn.switches:
|
||||
output( switch.name, '<->' )
|
||||
for intf in switch.intfs.values():
|
||||
# Ugly, but pylint wants it
|
||||
name = switch.connection.get( intf,
|
||||
( None, 'Unknown ' ) )[ 1 ]
|
||||
output( ' %s' % name )
|
||||
output( '\n' )
|
||||
dumpNodeConnections( self.nodelist )
|
||||
|
||||
def do_sh( self, line ):
|
||||
"Run an external shell command"
|
||||
@@ -131,7 +127,7 @@ class CLI( Cmd ):
|
||||
"""Evaluate a Python expression.
|
||||
Node names may be used, e.g.: h1.cmd('ls')"""
|
||||
try:
|
||||
result = eval( line, globals(), self.nodemap )
|
||||
result = eval( line, globals(), self.locals )
|
||||
if not result:
|
||||
return
|
||||
elif isinstance( result, str ):
|
||||
@@ -143,11 +139,11 @@ class CLI( Cmd ):
|
||||
|
||||
# pylint: enable-msg=W0703
|
||||
|
||||
def do_pingall( self, line ):
|
||||
def do_pingall( self, _line ):
|
||||
"Ping between all hosts."
|
||||
self.mn.pingAll()
|
||||
|
||||
def do_pingpair( self, line ):
|
||||
def do_pingpair( self, _line ):
|
||||
"Ping between first two hosts, useful for testing."
|
||||
self.mn.pingPair()
|
||||
|
||||
@@ -191,16 +187,16 @@ class CLI( Cmd ):
|
||||
error( 'invalid number of args: iperfudp bw src dst\n' +
|
||||
'bw examples: 10M\n' )
|
||||
|
||||
def do_intfs( self, line ):
|
||||
def do_intfs( self, _line ):
|
||||
"List interfaces."
|
||||
for node in self.nodelist:
|
||||
output( '%s: %s\n' %
|
||||
( node.name, ' '.join( sorted( node.intfs.values() ) ) ) )
|
||||
( node.name, ','.join( node.intfNames() ) ) )
|
||||
|
||||
def do_dump( self, line ):
|
||||
def do_dump( self, _line ):
|
||||
"Dump node info."
|
||||
for node in self.nodelist:
|
||||
output( '%s\n' % node )
|
||||
output( '%s\n' % repr( node ) )
|
||||
|
||||
def do_link( self, line ):
|
||||
"Bring link(s) between two nodes up or down."
|
||||
@@ -229,7 +225,7 @@ class CLI( Cmd ):
|
||||
"Spawn gnome-terminal(s) for the given node(s)."
|
||||
self.do_xterm( line, term='gterm' )
|
||||
|
||||
def do_exit( self, line ):
|
||||
def do_exit( self, _line ):
|
||||
"Exit"
|
||||
return 'exited by user command'
|
||||
|
||||
@@ -275,16 +271,12 @@ class CLI( Cmd ):
|
||||
def do_dpctl( self, line ):
|
||||
"Run dpctl command on all switches."
|
||||
args = line.split()
|
||||
if len(args) == 0:
|
||||
if len(args) < 1:
|
||||
error( 'usage: dpctl command [arg1] [arg2] ...\n' )
|
||||
return
|
||||
if not self.mn.listenPort:
|
||||
error( "can't run dpctl w/no passive listening port\n")
|
||||
return
|
||||
for sw in self.mn.switches:
|
||||
output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
|
||||
output( sw.cmd( 'dpctl ' + ' '.join(args) +
|
||||
' tcp:127.0.0.1:%i' % sw.listenPort ) )
|
||||
output( sw.dpctl( *args ) )
|
||||
|
||||
def default( self, line ):
|
||||
"""Called on an input line when the command prefix is not recognized.
|
||||
@@ -293,6 +285,8 @@ class CLI( Cmd ):
|
||||
corresponding IP addrs."""
|
||||
|
||||
first, args, line = self.parseline( line )
|
||||
if not args:
|
||||
return
|
||||
if args and len(args) > 0 and args[ -1 ] == '\n':
|
||||
args = args[ :-1 ]
|
||||
rest = args.split( ' ' )
|
||||
@@ -311,7 +305,7 @@ class CLI( Cmd ):
|
||||
else:
|
||||
error( '*** Unknown command: %s\n' % first )
|
||||
|
||||
# pylint: enable-msg=W0613,R0201
|
||||
# pylint: enable-msg=R0201
|
||||
|
||||
def waitForNode( self, node ):
|
||||
"Wait for a node to finish, and print its output."
|
||||
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
"""
|
||||
link.py: interface and link abstractions for mininet
|
||||
|
||||
It seems useful to bundle functionality for interfaces into a single
|
||||
class.
|
||||
|
||||
Also it seems useful to enable the possibility of multiple flavors of
|
||||
links, including:
|
||||
|
||||
- simple veth pairs
|
||||
- tunneled links
|
||||
- patchable links (which can be disconnected and reconnected via a patchbay)
|
||||
- link simulators (e.g. wireless)
|
||||
|
||||
Basic division of labor:
|
||||
|
||||
Nodes: know how to execute commands
|
||||
Intfs: know how to configure themselves
|
||||
Links: know how to connect nodes together
|
||||
|
||||
Intf: basic interface object that can configure itself
|
||||
TCIntf: interface with bandwidth limiting and delay via tc
|
||||
|
||||
Link: basic link class for creating veth pairs
|
||||
"""
|
||||
|
||||
from mininet.log import info, error, debug
|
||||
from mininet.util import makeIntfPair
|
||||
from time import sleep
|
||||
import re
|
||||
|
||||
class Intf( object ):
|
||||
|
||||
"Basic interface object that can configure itself."
|
||||
|
||||
def __init__( self, name, node=None, port=None, link=None, **params ):
|
||||
"""name: interface name (e.g. h1-eth0)
|
||||
node: owning node (where this intf most likely lives)
|
||||
link: parent link if we're part of a link
|
||||
other arguments are passed to config()"""
|
||||
self.node = node
|
||||
self.name = name
|
||||
self.link = link
|
||||
self.mac, self.ip, self.prefixLen = None, None, None
|
||||
# Add to node (and move ourselves if necessary )
|
||||
node.addIntf( self, port=port )
|
||||
# Save params for future reference
|
||||
self.params = params
|
||||
self.config( **params )
|
||||
|
||||
def cmd( self, *args, **kwargs ):
|
||||
"Run a command in our owning node"
|
||||
return self.node.cmd( *args, **kwargs )
|
||||
|
||||
def ifconfig( self, *args ):
|
||||
"Configure ourselves using ifconfig"
|
||||
return self.cmd( 'ifconfig', self.name, *args )
|
||||
|
||||
def setIP( self, ipstr, prefixLen=None ):
|
||||
"""Set our IP address"""
|
||||
# This is a sign that we should perhaps rethink our prefix
|
||||
# mechanism and/or the way we specify IP addresses
|
||||
if '/' in ipstr:
|
||||
self.ip, self.prefixLen = ipstr.split( '/' )
|
||||
return self.ifconfig( ipstr, 'up' )
|
||||
else:
|
||||
self.ip, self.prefixLen = ipstr, prefixLen
|
||||
return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
|
||||
|
||||
def setMAC( self, macstr ):
|
||||
"""Set the MAC address for an interface.
|
||||
macstr: MAC address as string"""
|
||||
self.mac = macstr
|
||||
return ( self.ifconfig( 'down' ) +
|
||||
self.ifconfig( 'hw', 'ether', macstr ) +
|
||||
self.ifconfig( 'up' ) )
|
||||
|
||||
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
|
||||
_macMatchRegex = re.compile( r'..:..:..:..:..:..' )
|
||||
|
||||
def updateIP( self ):
|
||||
"Return updated IP address based on ifconfig"
|
||||
ifconfig = self.ifconfig()
|
||||
ips = self._ipMatchRegex.findall( ifconfig )
|
||||
self.ip = ips[ 0 ] if ips else None
|
||||
return self.ip
|
||||
|
||||
def updateMAC( self ):
|
||||
"Return updated MAC address based on ifconfig"
|
||||
ifconfig = self.ifconfig()
|
||||
macs = self._macMatchRegex.findall( ifconfig )
|
||||
self.mac = macs[ 0 ] if macs else None
|
||||
return self.mac
|
||||
|
||||
def IP( self ):
|
||||
"Return IP address"
|
||||
return self.ip
|
||||
|
||||
def MAC( self ):
|
||||
"Return MAC address"
|
||||
return self.mac
|
||||
|
||||
def isUp( self, setUp=False ):
|
||||
"Return whether interface is up"
|
||||
if setUp:
|
||||
self.ifconfig( 'up' )
|
||||
return "UP" in self.ifconfig()
|
||||
|
||||
def rename( self, newname ):
|
||||
"Rename interface"
|
||||
self.ifconfig( 'down' )
|
||||
result = self.cmd( 'ip link set', self.name, 'name', newname )
|
||||
self.name = newname
|
||||
self.ifconfig( 'up' )
|
||||
return result
|
||||
|
||||
# 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 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 ]
|
||||
f = getattr( self, method, None )
|
||||
if not f or value is None:
|
||||
return
|
||||
if type( value ) is list:
|
||||
result = f( *value )
|
||||
elif type( value ) is dict:
|
||||
result = f( **value )
|
||||
else:
|
||||
result = f( value )
|
||||
results[ name ] = result
|
||||
return result
|
||||
|
||||
def config( self, mac=None, ip=None, ifconfig=None,
|
||||
up=True, **_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, 'isUp', up=up )
|
||||
self.setParam( r, 'ifconfig', ifconfig=ifconfig )
|
||||
self.updateIP()
|
||||
self.updateMAC()
|
||||
return r
|
||||
|
||||
def delete( self ):
|
||||
"Delete interface"
|
||||
self.cmd( 'ip link del ' + self.name )
|
||||
# Does it help to sleep to let things run?
|
||||
sleep( 0.001 )
|
||||
|
||||
def __repr__( self ):
|
||||
return '<%s %s>' % ( self.__class__.__name__, self.name )
|
||||
|
||||
def __str__( self ):
|
||||
return self.name
|
||||
|
||||
|
||||
class TCIntf( Intf ):
|
||||
"""Interface customized by tc (traffic control) utility
|
||||
Allows specification of bandwidth limits (various methods)
|
||||
as well as delay, loss and max queue length"""
|
||||
|
||||
def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
|
||||
enable_ecn=False, enable_red=False ):
|
||||
"Return tc commands to set bandwidth"
|
||||
|
||||
cmds, parent = [], ' root '
|
||||
|
||||
if bw and ( bw < 0 or bw > 1000 ):
|
||||
error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
|
||||
|
||||
elif bw is not None:
|
||||
# BL: this seems a bit brittle...
|
||||
if ( speedup > 0 and
|
||||
self.node.name[0:1] == 's' ):
|
||||
bw = speedup
|
||||
# This may not be correct - we should look more closely
|
||||
# at the semantics of burst (and cburst) to make sure we
|
||||
# are specifying the correct sizes. For now I have used
|
||||
# the same settings we had in the mininet-hifi code.
|
||||
if use_hfsc:
|
||||
cmds = [ '%s qdisc add dev %s root handle 1:0 hfsc default 1',
|
||||
'%s class add dev %s parent 1:0 classid 1:1 hfsc sc '
|
||||
+ 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
|
||||
elif use_tbf:
|
||||
latency_us = 10 * 1500 * 8 / bw
|
||||
cmds = ['%s qdisc add dev %s root handle 1: tbf ' +
|
||||
'rate %fMbit burst 15000 latency %fus' %
|
||||
( bw, latency_us ) ]
|
||||
else:
|
||||
cmds = [ '%s qdisc add dev %s root handle 1:0 htb default 1',
|
||||
'%s class add dev %s parent 1:0 classid 1:1 htb ' +
|
||||
'rate %fMbit burst 15k' % bw ]
|
||||
parent = ' parent 1:1 '
|
||||
|
||||
# ECN or RED
|
||||
if enable_ecn:
|
||||
cmds = [ '%s qdisc add dev %s' + parent +
|
||||
'handle 10: red limit 1000000 ' +
|
||||
'min 20000 max 25000 avpkt 1000 ' +
|
||||
'burst 20 ' +
|
||||
'bandwidth %fmbit probability 1 ecn' % bw ]
|
||||
parent = ' parent 10: '
|
||||
elif enable_red:
|
||||
cmds = [ '%s qdisc add dev %s' + parent +
|
||||
'handle 10: red limit 1000000 ' +
|
||||
'min 20000 max 25000 avpkt 1000 ' +
|
||||
'burst 20 ' +
|
||||
'bandwidth %fmbit probability 1' % bw ]
|
||||
parent = ' parent 10: '
|
||||
|
||||
return cmds, parent
|
||||
|
||||
@staticmethod
|
||||
def delayCmds( parent, delay=None, loss=None,
|
||||
max_queue_size=None ):
|
||||
"Internal method: return tc commands for delay and loss"
|
||||
cmds = []
|
||||
if delay and delay < 0:
|
||||
error( 'Negative delay', delay, '\n' )
|
||||
elif loss and ( loss < 0 or loss > 100 ):
|
||||
error( 'Bad loss percentage', loss, '%%\n' )
|
||||
else:
|
||||
# Delay/loss/max queue size
|
||||
netemargs = '%s%s%s' % (
|
||||
'delay %s ' % delay if delay is not None else '',
|
||||
'loss %d ' % loss if loss is not None else '',
|
||||
'limit %d' % max_queue_size if max_queue_size is not None
|
||||
else '' )
|
||||
if netemargs:
|
||||
cmds = [ '%s qdisc add dev %s ' + parent +
|
||||
' handle 10: netem ' +
|
||||
netemargs ]
|
||||
return cmds
|
||||
|
||||
def tc( self, cmd, tc='tc' ):
|
||||
"Execute tc command for our interface"
|
||||
c = cmd % (tc, self) # Add in tc command and our name
|
||||
debug(" *** executing command: %s\n" % c)
|
||||
return self.cmd( c )
|
||||
|
||||
def config( self, bw=None, delay=None, loss=None, disable_gro=True,
|
||||
speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False,
|
||||
enable_red=False, max_queue_size=None, **params ):
|
||||
"Configure the port and set its properties."
|
||||
|
||||
result = Intf.config( self, **params)
|
||||
|
||||
# Disable GRO
|
||||
if disable_gro:
|
||||
self.cmd( 'ethtool -K %s gro off' % self )
|
||||
|
||||
# Optimization: return if nothing else to configure
|
||||
# Question: what happens if we want to reset things?
|
||||
if ( bw is None and not delay and not loss
|
||||
and max_queue_size is None ):
|
||||
return
|
||||
|
||||
# Clear existing configuration
|
||||
cmds = [ '%s qdisc del dev %s root' ]
|
||||
|
||||
# Bandwidth limits via various methods
|
||||
bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
|
||||
use_hfsc=use_hfsc, use_tbf=use_tbf,
|
||||
enable_ecn=enable_ecn,
|
||||
enable_red=enable_red )
|
||||
cmds += bwcmds
|
||||
|
||||
# Delay/loss/max_queue_size using netem
|
||||
cmds += self.delayCmds( delay=delay, loss=loss,
|
||||
max_queue_size=max_queue_size,
|
||||
parent=parent )
|
||||
|
||||
# Ugly but functional: display configuration info
|
||||
stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
|
||||
( [ '%s delay' % delay ] if delay is not None else [] ) +
|
||||
( ['%d%% loss' % loss ] if loss is not None else [] ) +
|
||||
( [ 'ECN' ] if enable_ecn else [ 'RED' ]
|
||||
if enable_red else [] ) )
|
||||
info( '(' + ' '.join( stuff ) + ') ' )
|
||||
|
||||
# Execute all the commands in our node
|
||||
debug("at map stage w/cmds: %s\n" % cmds)
|
||||
tcoutputs = [ self.tc(cmd) for cmd in cmds ]
|
||||
debug( "cmds:", cmds, '\n' )
|
||||
debug( "outputs:", tcoutputs, '\n' )
|
||||
result[ 'tcoutputs'] = tcoutputs
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Link( object ):
|
||||
|
||||
"""A basic link is just a veth pair.
|
||||
Other types of links could be tunnels, link emulators, etc.."""
|
||||
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None,
|
||||
intf=Intf, cls1=None, cls2=None, params1=None,
|
||||
params2=None ):
|
||||
"""Create veth link to another node, making two new interfaces.
|
||||
node1: first node
|
||||
node2: second node
|
||||
port1: node1 port number (optional)
|
||||
port2: node2 port number (optional)
|
||||
intf: default interface class/constructor
|
||||
cls1, cls2: optional interface-specific constructors
|
||||
intfName1: node1 interface name (optional)
|
||||
intfName2: node2 interface name (optional)
|
||||
params1: parameters for interface 1
|
||||
params2: parameters for interface 2"""
|
||||
# This is a bit awkward; it seems that having everything in
|
||||
# params would be more orthogonal, but being able to specify
|
||||
# in-line arguments is more convenient!
|
||||
if port1 is None:
|
||||
port1 = node1.newPort()
|
||||
if port2 is None:
|
||||
port2 = node2.newPort()
|
||||
if not intfName1:
|
||||
intfName1 = self.intfName( node1, port1 )
|
||||
if not intfName2:
|
||||
intfName2 = self.intfName( node2, port2 )
|
||||
|
||||
self.makeIntfPair( intfName1, intfName2 )
|
||||
|
||||
if not cls1:
|
||||
cls1 = intf
|
||||
if not cls2:
|
||||
cls2 = intf
|
||||
if not params1:
|
||||
params1 = {}
|
||||
if not params2:
|
||||
params2 = {}
|
||||
|
||||
intf1 = cls1( name=intfName1, node=node1, port=port1,
|
||||
link=self, **params1 )
|
||||
intf2 = cls2( name=intfName2, node=node2, port=port2,
|
||||
link=self, **params2 )
|
||||
|
||||
# All we are is dust in the wind, and our two interfaces
|
||||
self.intf1, self.intf2 = intf1, intf2
|
||||
|
||||
@classmethod
|
||||
def intfName( cls, node, n ):
|
||||
"Construct a canonical interface name node-ethN for interface n."
|
||||
return node.name + '-eth' + repr( n )
|
||||
|
||||
@classmethod
|
||||
def makeIntfPair( cls, intf1, intf2 ):
|
||||
"""Create pair of interfaces
|
||||
intf1: name of interface 1
|
||||
intf2: name of interface 2
|
||||
(override this class method [and possibly delete()]
|
||||
to change link type)"""
|
||||
makeIntfPair( intf1, intf2 )
|
||||
|
||||
def delete( self ):
|
||||
"Delete this link"
|
||||
self.intf1.delete()
|
||||
self.intf2.delete()
|
||||
|
||||
def __str__( self ):
|
||||
return '%s<->%s' % ( self.intf1, self.intf2 )
|
||||
|
||||
class TCLink( Link ):
|
||||
"Link with symmetric TC interfaces configured via opts"
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None, **params ):
|
||||
Link.__init__( self, node1, node2, port1=port1, port2=port2,
|
||||
intfName1=intfName1, intfName2=intfName2,
|
||||
cls1=TCIntf,
|
||||
cls2=TCIntf,
|
||||
params1=params,
|
||||
params2=params)
|
||||
@@ -0,0 +1,145 @@
|
||||
from time import sleep, time
|
||||
from subprocess import Popen, PIPE
|
||||
from multiprocessing import Process
|
||||
import re
|
||||
import os
|
||||
|
||||
from mininet.log import info, error, debug, output
|
||||
from mininet.util import quietRun
|
||||
|
||||
class Monitor(object):
|
||||
|
||||
def __init__(self, output_dir='/tmp'):
|
||||
self.monitors = []
|
||||
self.output_dir = output_dir
|
||||
|
||||
# Add general process monitors
|
||||
# Bandwidth monitor
|
||||
self.monitors.append(Process(target=self.monitor_devs_ng,
|
||||
args=('%s/bwm.txt' % self.output_dir, 1.0)))
|
||||
|
||||
# CPU monitor
|
||||
self.monitors.append(Process(target=self.monitor_cpu,
|
||||
args=('%s/cpu.txt' % self.output_dir, )))
|
||||
|
||||
# cwnd monitor: tcp_probe
|
||||
self.monitors.append(Process(target=self.monitor_cwnd,
|
||||
args=('%s/tcp_probe.txt' % self.output_dir, )))
|
||||
|
||||
def start(self):
|
||||
'''Start all the system monitors'''
|
||||
|
||||
# Set output directory
|
||||
self.set_output_dir(self.output_dir)
|
||||
|
||||
# Start the monitors
|
||||
for m in self.monitors:
|
||||
m.start()
|
||||
|
||||
def stop(self):
|
||||
'''Terminate all the system monitors'''
|
||||
# Stop the monitors
|
||||
for m in self.monitors:
|
||||
m.terminate()
|
||||
self.monitors = []
|
||||
Popen("killall -9 bwm-ng top", shell=True).wait()
|
||||
Popen("killall -9 cat; rmmod tcp_probe > /dev/null 2>&1;", shell=True).wait()
|
||||
|
||||
def set_output_dir(self, output_dir):
|
||||
# Create output directory if it doesn't exist already
|
||||
self.output_dir = output_dir
|
||||
debug('Monitoring output dir: %s' % self.output_dir)
|
||||
if not os.path.isdir(self.output_dir):
|
||||
os.makedirs(self.output_dir)
|
||||
|
||||
def monitor_qlen(self, iface, interval_sec = 0.01, fname='%s/qlen.txt' % '.'):
|
||||
pat_queued = re.compile(r'backlog\s[^\s]+\s([\d]+)p')
|
||||
cmd = "tc -s qdisc show dev %s" % (iface)
|
||||
ret = []
|
||||
open(fname, 'w').write('')
|
||||
while 1:
|
||||
p = Popen(cmd, shell=True, stdout=PIPE)
|
||||
output = p.stdout.read()
|
||||
# Not quite right, but will do for now
|
||||
matches = pat_queued.findall(output)
|
||||
if matches and len(matches) > 1:
|
||||
ret.append(matches[1])
|
||||
t = "%f" % time()
|
||||
open(fname, 'a').write(t + ',' + matches[1] + '\n')
|
||||
sleep(interval_sec)
|
||||
return
|
||||
|
||||
def monitor_cwnd(self, fname='%s/tcp_probe.txt' % '.'):
|
||||
Popen("rmmod tcp_probe > /dev/null 2>&1; modprobe tcp_probe;", shell=True).wait()
|
||||
Popen("cat /proc/net/tcpprobe > %s" % fname, shell=True).wait()
|
||||
|
||||
def monitor_devs_ng(self, fname="%s/txrate.txt" % '.', interval_sec=0.01):
|
||||
"""Uses bwm-ng tool to collect iface tx rate stats. Very reliable."""
|
||||
cmd = "sleep 1; bwm-ng -t %s -o csv -u bits -T rate -C ',' > %s" % (interval_sec * 1000, fname)
|
||||
Popen(cmd, shell=True).wait()
|
||||
|
||||
def monitor_cpu(self, fname="%s/cpu.txt" % '.'):
|
||||
cmd = "(top -b -p 1 -d 1 | grep --line-buffered \"^Cpu\") > %s" % fname
|
||||
Popen(cmd, shell=True).wait()
|
||||
|
||||
def monitor_cpuacct(self, hosts, fname="%s/cpuacct.txt" % '.', interval_sec=1.0):
|
||||
prereqs = ['cgget']
|
||||
for p in prereqs:
|
||||
if not quietRun('which ' + p):
|
||||
error('Could not find %s... not monitoring cpuacct' % p)
|
||||
return
|
||||
hnames = ' '.join([h.name for h in hosts])
|
||||
cpuacct_cmd = 'cgget -g cpuacct %s >> %s' % (hnames, fname)
|
||||
prev_time = time()
|
||||
while 1:
|
||||
sleep(interval_sec - (time() - prev_time))
|
||||
prev_time = time()
|
||||
cpu_usage = Popen(cpuacct_cmd, shell=True).wait()
|
||||
return
|
||||
|
||||
# Obsolete: Use bwm-ng instead (monitor_devs_ng), for reliable stats
|
||||
def monitor_count(self, ipt_args="--src 10.0.0.0/8", interval_sec=0.01, fname='%s/bytes_sent.txt' % '.', chain="OUTPUT"):
|
||||
cmd = "iptables -I %(chain)s 1 %(filter)s -j RETURN" % {
|
||||
"filter": ipt_args,
|
||||
"chain": chain,
|
||||
}
|
||||
# We always erase the first rule; will fix this later
|
||||
Popen("iptables -D %s 1" % chain, shell=True).wait()
|
||||
# Add our rule
|
||||
Popen(cmd, shell=True).wait()
|
||||
open(fname, 'w').write('')
|
||||
cmd = "iptables -vnL %s 1 -Z" % (chain)
|
||||
while 1:
|
||||
p = Popen(cmd, shell=True, stdout=PIPE)
|
||||
output = p.stdout.read().strip()
|
||||
values = output.split(' ')
|
||||
if len(values) > 2:
|
||||
t = "%f" % time()
|
||||
pkts, bytes = values[0], values[1]
|
||||
open(fname, 'a').write(','.join([t, pkts, bytes]) + '\n')
|
||||
sleep(interval_sec)
|
||||
return
|
||||
|
||||
# Obsolete: Use bwm-ng instead (monitor_devs_ng), for reliable stats
|
||||
def monitor_devs(self, dev_pattern='^sw', fname="%s/bytes_sent.txt" % '.', interval_sec=0.01):
|
||||
"""Aggregates (sums) all txed bytes and rate (in Mbps) from devices whose name
|
||||
matches @dev_pattern and writes to @fname"""
|
||||
pat = re.compile(dev_pattern)
|
||||
spaces = re.compile('\s+')
|
||||
open(fname, 'w').write('')
|
||||
prev_tx = {}
|
||||
while 1:
|
||||
lines = open('/proc/net/dev').read().split('\n')
|
||||
t = str(time())
|
||||
total = 0
|
||||
for line in lines:
|
||||
line = spaces.split(line.strip())
|
||||
iface = line[0]
|
||||
if pat.match(iface) and len(line) > 9:
|
||||
tx_bytes = int(line[9])
|
||||
total += tx_bytes - prev_tx.get(iface, tx_bytes)
|
||||
prev_tx[iface] = tx_bytes
|
||||
open(fname, 'a').write(','.join([t, str(total * 8 / interval_sec / 1e6), str(total)]) + "\n")
|
||||
sleep(interval_sec)
|
||||
return
|
||||
|
||||
+244
-165
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
|
||||
Mininet: A simple networking testbed for OpenFlow!
|
||||
Mininet: A simple networking testbed for OpenFlow/SDN!
|
||||
|
||||
author: Bob Lantz (rlantz@cs.stanford.edu)
|
||||
author: Brandon Heller (brandonh@stanford.edu)
|
||||
@@ -91,228 +91,228 @@ import re
|
||||
import select
|
||||
import signal
|
||||
from time import sleep
|
||||
from datetime import datetime
|
||||
from multiprocessing import Process
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import info, error, debug, output
|
||||
from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller
|
||||
from mininet.node import ControllerParams
|
||||
from mininet.util import quietRun, fixLimits
|
||||
from mininet.util import createLink, macColonHex, ipStr, ipParse
|
||||
from mininet.node import Host, OVSKernelSwitch, Controller
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.util import quietRun, fixLimits, numCores
|
||||
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||
from mininet.term import cleanUpScreens, makeTerms
|
||||
from mininet.monitor import Monitor
|
||||
|
||||
class Mininet( object ):
|
||||
"Network emulation with hosts spawned in network namespaces."
|
||||
|
||||
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
|
||||
controller=Controller,
|
||||
cparams=ControllerParams( '10.0.0.0', 8 ),
|
||||
build=True, xterms=False, cleanup=False,
|
||||
controller=Controller, link=Link, intf=Intf,
|
||||
build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
|
||||
inNamespace=False,
|
||||
autoSetMacs=False, autoStaticArp=False, listenPort=None ):
|
||||
autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
|
||||
listenPort=None ):
|
||||
"""Create Mininet object.
|
||||
topo: Topo (topology) object or None
|
||||
switch: Switch class
|
||||
host: Host class
|
||||
controller: Controller class
|
||||
cparams: ControllerParams object
|
||||
switch: default Switch class
|
||||
host: default Host class/constructor
|
||||
controller: default Controller class/constructor
|
||||
link: default Link class/constructor
|
||||
intf: default Intf class/constructor
|
||||
ipBase: base IP address for hosts,
|
||||
build: build now from topo?
|
||||
xterms: if build now, spawn xterms?
|
||||
cleanup: if build now, cleanup before creating?
|
||||
inNamespace: spawn switches and controller in net namespaces?
|
||||
autoSetMacs: set MAC addrs from topo?
|
||||
autoSetMacs: set MAC addrs automatically like IP addresses?
|
||||
autoStaticArp: set all-pairs static MAC addrs?
|
||||
autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
|
||||
listenPort: base listening port to open; will be incremented for
|
||||
each additional switch in the net if inNamespace=False"""
|
||||
self.topo = topo
|
||||
self.switch = switch
|
||||
self.host = host
|
||||
self.controller = controller
|
||||
self.cparams = cparams
|
||||
self.topo = topo
|
||||
self.link = link
|
||||
self.intf = intf
|
||||
self.ipBase = ipBase
|
||||
self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
|
||||
self.nextIP = 1 # start for address allocation
|
||||
self.inNamespace = inNamespace
|
||||
self.xterms = xterms
|
||||
self.cleanup = cleanup
|
||||
self.autoSetMacs = autoSetMacs
|
||||
self.autoStaticArp = autoStaticArp
|
||||
self.autoPinCpus = autoPinCpus
|
||||
self.numCores = numCores()
|
||||
self.nextCore = 0 # next core for pinning hosts to CPUs
|
||||
self.listenPort = listenPort
|
||||
self.monitoring = None
|
||||
|
||||
self.hosts = []
|
||||
self.switches = []
|
||||
self.controllers = []
|
||||
|
||||
self.nameToNode = {} # name to Node (Host/Switch) objects
|
||||
self.idToNode = {} # dpid to Node (Host/Switch) objects
|
||||
self.dps = 0 # number of created kernel datapaths
|
||||
|
||||
self.terms = [] # list of spawned xterm processes
|
||||
|
||||
init()
|
||||
switch.setup()
|
||||
Mininet.init() # Initialize Mininet if necessary
|
||||
|
||||
self.built = False
|
||||
if topo and build:
|
||||
self.build()
|
||||
|
||||
def addHost( self, name, mac=None, ip=None ):
|
||||
def set_debug(self, output_dir=None):
|
||||
'''Enable debugging, with output to the specified directory'''
|
||||
dt = datetime.now()
|
||||
if not output_dir:
|
||||
output_dir = '/tmp/mininet-%s-%s' % (str(dt.date()), str(dt.time()))
|
||||
self.monitoring = Monitor(output_dir=output_dir)
|
||||
|
||||
def addHost( self, name, cls=None, **params ):
|
||||
"""Add host.
|
||||
name: name of host to add
|
||||
mac: default MAC address for intf 0
|
||||
ip: default IP address for intf 0
|
||||
cls: custom host class/constructor (optional)
|
||||
params: parameters for host
|
||||
returns: added host"""
|
||||
host = self.host( name, defaultMAC=mac, defaultIP=ip )
|
||||
self.hosts.append( host )
|
||||
self.nameToNode[ name ] = host
|
||||
return host
|
||||
# Default IP and MAC addresses
|
||||
defaults = { 'ip': ipAdd( self.nextIP,
|
||||
ipBaseNum=self.ipBaseNum,
|
||||
prefixLen=self.prefixLen ) }
|
||||
if self.autoSetMacs:
|
||||
defaults[ 'mac'] = macColonHex( self.nextIP )
|
||||
if self.autoPinCpus:
|
||||
defaults[ 'cores' ] = self.nextCore
|
||||
self.nextCore = ( self.nextCore + 1 ) % self.numCores
|
||||
self.nextIP += 1
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.host
|
||||
h = cls( name, **defaults )
|
||||
self.hosts.append( h )
|
||||
self.nameToNode[ name ] = h
|
||||
return h
|
||||
|
||||
def addSwitch( self, name, mac=None, ip=None ):
|
||||
def addSwitch( self, name, cls=None, **params ):
|
||||
"""Add switch.
|
||||
name: name of switch to add
|
||||
mac: default MAC address for kernel/OVS switch intf 0
|
||||
cls: custom switch class/constructor (optional)
|
||||
returns: added switch
|
||||
side effect: increments the listenPort member variable."""
|
||||
if self.switch == UserSwitch:
|
||||
sw = self.switch( name, listenPort=self.listenPort,
|
||||
defaultMAC=mac, defaultIP=ip, inNamespace=self.inNamespace )
|
||||
else:
|
||||
sw = self.switch( name, listenPort=self.listenPort,
|
||||
defaultMAC=mac, defaultIP=ip, dp=self.dps,
|
||||
inNamespace=self.inNamespace )
|
||||
side effect: increments listenPort ivar ."""
|
||||
defaults = { 'listenPort': self.listenPort,
|
||||
'inNamespace': self.inNamespace }
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.switch
|
||||
sw = cls( name, **defaults )
|
||||
if not self.inNamespace and self.listenPort:
|
||||
self.listenPort += 1
|
||||
self.dps += 1
|
||||
self.switches.append( sw )
|
||||
self.nameToNode[ name ] = sw
|
||||
return sw
|
||||
|
||||
def addController( self, name='c0', controller=None, **kwargs ):
|
||||
def addController( self, name='c0', controller=None, **params ):
|
||||
"""Add controller.
|
||||
controller: Controller class"""
|
||||
if not controller:
|
||||
controller = self.controller
|
||||
controller_new = controller( name, **kwargs )
|
||||
controller_new = controller( name, **params )
|
||||
if controller_new: # allow controller-less setups
|
||||
self.controllers.append( controller_new )
|
||||
self.nameToNode[ name ] = controller_new
|
||||
return controller_new
|
||||
|
||||
# Control network support:
|
||||
#
|
||||
# Create an explicit control network. Currently this is only
|
||||
# used by the user datapath configuration.
|
||||
#
|
||||
# Notes:
|
||||
#
|
||||
# 1. If the controller and switches are in the same (e.g. root)
|
||||
# namespace, they can just use the loopback connection.
|
||||
#
|
||||
# 2. If we can get unix domain sockets to work, we can use them
|
||||
# instead of an explicit control network.
|
||||
#
|
||||
# 3. Instead of routing, we could bridge or use 'in-band' control.
|
||||
#
|
||||
# 4. Even if we dispense with this in general, it could still be
|
||||
# useful for people who wish to simulate a separate control
|
||||
# network (since real networks may need one!)
|
||||
# BL: is this better than just using nameToNode[] ?
|
||||
# Should it have a better name?
|
||||
def getNodeByName( self, *args ):
|
||||
"Return node(s) with given name(s)"
|
||||
if len( args ) == 1:
|
||||
return self.nameToNode[ args[ 0 ] ]
|
||||
return [ self.nameToNode[ n ] for n in args ]
|
||||
|
||||
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 addLink( self, node1, node2, port1=None, port2=None,
|
||||
cls=None, **params ):
|
||||
""""Add a link from node1 to node2
|
||||
node1: source node
|
||||
node2: dest node
|
||||
port1: source port
|
||||
port2: dest port
|
||||
returns: link object"""
|
||||
defaults = { 'port1': port1,
|
||||
'port2': port2,
|
||||
'intf': self.intf }
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.link
|
||||
return cls( node1, node2, **defaults )
|
||||
|
||||
def configHosts( self ):
|
||||
"Configure a set of hosts."
|
||||
# params were: hosts, ips
|
||||
for host in self.hosts:
|
||||
hintf = host.intfs[ 0 ]
|
||||
host.setIP( hintf, host.defaultIP, self.cparams.prefixLen )
|
||||
host.setDefaultRoute( hintf )
|
||||
# You're low priority, dude!
|
||||
quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||||
info( host.name + ' ' )
|
||||
host.configDefault( defaultRoute=host.defaultIntf() )
|
||||
# You're low priority, dude!
|
||||
# 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 ) )
|
||||
# This may not be the right place to do this, but
|
||||
# it needs to be done somewhere.
|
||||
host.cmd( 'ifconfig lo up' )
|
||||
info( '\n' )
|
||||
|
||||
def buildFromTopo( self, topo ):
|
||||
def buildFromTopo( self, topo=None ):
|
||||
"""Build mininet from a topology object
|
||||
At the end of this function, everything should be connected
|
||||
and up."""
|
||||
|
||||
def addNode( prefix, addMethod, nodeId ):
|
||||
"Add a host or a switch."
|
||||
name = prefix + topo.name( nodeId )
|
||||
mac = macColonHex( nodeId ) if self.setMacs else None
|
||||
ip = topo.ip( nodeId )
|
||||
node = addMethod( name, mac=mac, ip=ip )
|
||||
self.idToNode[ nodeId ] = node
|
||||
info( name + ' ' )
|
||||
|
||||
# Possibly we should clean up here and/or validate
|
||||
# the topo
|
||||
if self.cleanup:
|
||||
pass
|
||||
|
||||
info( '*** Adding controller\n' )
|
||||
self.addController( 'c0' )
|
||||
info( '*** Creating network\n' )
|
||||
|
||||
if not self.controllers:
|
||||
# Add a default controller
|
||||
info( '*** Adding controller\n' )
|
||||
self.addController( 'c0' )
|
||||
|
||||
info( '*** Adding hosts:\n' )
|
||||
for hostId in sorted( topo.hosts() ):
|
||||
addNode( 'h', self.addHost, hostId )
|
||||
for hostName in topo.hosts():
|
||||
self.addHost( hostName, **topo.nodeInfo( hostName ) )
|
||||
info( hostName + ' ' )
|
||||
|
||||
info( '\n*** Adding switches:\n' )
|
||||
for switchId in sorted( topo.switches() ):
|
||||
addNode( 's', self.addSwitch, switchId )
|
||||
for switchName in topo.switches():
|
||||
self.addSwitch( switchName, **topo.nodeInfo( switchName) )
|
||||
info( switchName + ' ' )
|
||||
|
||||
info( '\n*** Adding links:\n' )
|
||||
for srcId, dstId in sorted( topo.edges() ):
|
||||
src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
|
||||
srcPort, dstPort = topo.port( srcId, dstId )
|
||||
createLink( src, dst, srcPort, dstPort )
|
||||
for srcName, dstName in topo.links(sort=True):
|
||||
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
|
||||
params = topo.linkInfo( srcName, dstName )
|
||||
srcPort, dstPort = topo.port( srcName, dstName )
|
||||
self.addLink( src, dst, srcPort, dstPort, **params )
|
||||
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
||||
|
||||
info( '\n' )
|
||||
|
||||
def configureControlNetwork( self ):
|
||||
"Control net config hook: override in subclass"
|
||||
raise Exception( 'configureControlNetwork: '
|
||||
'should be overriden in subclass', self )
|
||||
|
||||
def build( self ):
|
||||
"Build mininet."
|
||||
if self.topo:
|
||||
self.buildFromTopo( self.topo )
|
||||
if self.inNamespace:
|
||||
info( '*** Configuring control network\n' )
|
||||
if ( self.inNamespace ):
|
||||
self.configureControlNetwork()
|
||||
info( '*** Configuring hosts\n' )
|
||||
self.configHosts()
|
||||
if self.xterms:
|
||||
self.startTerms()
|
||||
if self.autoSetMacs:
|
||||
self.setMacs()
|
||||
if self.autoStaticArp:
|
||||
self.staticArp()
|
||||
self.built = True
|
||||
@@ -327,17 +327,10 @@ class Mininet( object ):
|
||||
|
||||
def stopXterms( self ):
|
||||
"Kill each xterm."
|
||||
# Kill xterms
|
||||
for term in self.terms:
|
||||
os.kill( term.pid, signal.SIGKILL )
|
||||
cleanUpScreens()
|
||||
|
||||
def setMacs( self ):
|
||||
"""Set MAC addrs to correspond to default MACs on hosts.
|
||||
Assume that the host only has one interface."""
|
||||
for host in self.hosts:
|
||||
host.setMAC( host.intfs[ 0 ], host.defaultMAC )
|
||||
|
||||
def staticArp( self ):
|
||||
"Add all-pairs ARP entries to remove the need to handle broadcast."
|
||||
for src in self.hosts:
|
||||
@@ -357,26 +350,38 @@ class Mininet( object ):
|
||||
info( switch.name + ' ')
|
||||
switch.start( self.controllers )
|
||||
info( '\n' )
|
||||
info( '*** Starting system monitor\n' )
|
||||
if self.monitoring:
|
||||
m = self.monitoring
|
||||
m.monitors.append(Process(target=m.monitor_cpuacct,
|
||||
args=(self.hosts, '%s/cpuacct.txt' % m.output_dir)))
|
||||
m.start()
|
||||
info( 'Logging monitoring info in: %s\n' % m.output_dir )
|
||||
|
||||
def stop( self ):
|
||||
"Stop the controller(s), switches and hosts"
|
||||
info( '*** Stopping system monitor\n' )
|
||||
if self.monitoring:
|
||||
self.monitoring.stop()
|
||||
if self.terms:
|
||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||
self.stopXterms()
|
||||
info( '*** Stopping %i hosts\n' % len( self.hosts ) )
|
||||
for host in self.hosts:
|
||||
info( '%s ' % host.name )
|
||||
info( host.name + ' ' )
|
||||
host.terminate()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
||||
for switch in self.switches:
|
||||
info( switch.name )
|
||||
info( switch.name + ' ' )
|
||||
switch.stop()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i controllers\n' % len( self.controllers ) )
|
||||
for controller in self.controllers:
|
||||
info( controller.name + ' ' )
|
||||
controller.stop()
|
||||
info( '*** Done\n' )
|
||||
info( '\n' )
|
||||
info( '\n*** Done\n' )
|
||||
|
||||
def run( self, test, *args, **kwargs ):
|
||||
"Perform a complete start/test/stop cycle."
|
||||
@@ -410,6 +415,9 @@ class Mininet( object ):
|
||||
if not ready and timeoutms >= 0:
|
||||
yield None, None
|
||||
|
||||
# XXX These test methods should be moved out of this class.
|
||||
# Probably we should create a tests.py for them
|
||||
|
||||
@staticmethod
|
||||
def _parsePing( pingOutput ):
|
||||
"Parse ping output and return packets sent, received."
|
||||
@@ -481,6 +489,8 @@ class Mininet( object ):
|
||||
error( 'could not parse iperf output: ' + iperfOutput )
|
||||
return ''
|
||||
|
||||
# XXX This should be cleaned up
|
||||
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
|
||||
"""Run iperf between two hosts.
|
||||
hosts: list of hosts; if None, uses opposite hosts
|
||||
@@ -508,10 +518,11 @@ class Mininet( object ):
|
||||
servout = ''
|
||||
while server.lastPid is None:
|
||||
servout += server.monitor()
|
||||
while 'Connected' not in client.cmd(
|
||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
||||
output('waiting for iperf to start up')
|
||||
sleep(.5)
|
||||
if l4Type == 'TCP':
|
||||
while 'Connected' not in client.cmd(
|
||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
||||
output('waiting for iperf to start up...')
|
||||
sleep(.5)
|
||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||
bwArgs )
|
||||
debug( 'Client output: %s\n' % cliout )
|
||||
@@ -524,6 +535,8 @@ class Mininet( object ):
|
||||
output( '*** Results: %s\n' % result )
|
||||
return result
|
||||
|
||||
# BL: I think this can be rewritten now that we have
|
||||
# a real link class.
|
||||
def configLinkStatus( self, src, dst, status ):
|
||||
"""Change status of src <-> dst links.
|
||||
src: node name
|
||||
@@ -534,15 +547,18 @@ class Mininet( object ):
|
||||
elif dst not in self.nameToNode:
|
||||
error( 'dst not in network: %s\n' % dst )
|
||||
else:
|
||||
srcNode, dstNode = self.nameToNode[ src ], self.nameToNode[ dst ]
|
||||
connections = srcNode.connectionsTo( dstNode )
|
||||
if type( src ) is str:
|
||||
src = self.nameToNode[ src ]
|
||||
if type( dst ) is str:
|
||||
dst = self.nameToNode[ dst ]
|
||||
connections = src.connectionsTo( dst )
|
||||
if len( connections ) == 0:
|
||||
error( 'src and dst not connected: %s %s\n' % ( src, dst) )
|
||||
for srcIntf, dstIntf in connections:
|
||||
result = srcNode.cmd( 'ifconfig', srcIntf, status )
|
||||
result = srcIntf.ifconfig( status )
|
||||
if result:
|
||||
error( 'link src status change failed: %s\n' % result )
|
||||
result = dstNode.cmd( 'ifconfig', dstIntf, status )
|
||||
result = dstIntf.ifconfig( status )
|
||||
if result:
|
||||
error( 'link dst status change failed: %s\n' % result )
|
||||
|
||||
@@ -553,26 +569,89 @@ class Mininet( object ):
|
||||
self.stop()
|
||||
return result
|
||||
|
||||
inited = False
|
||||
|
||||
# pylint thinks inited is unused
|
||||
# pylint: disable-msg=W0612
|
||||
@classmethod
|
||||
def init( cls ):
|
||||
"Initialize Mininet"
|
||||
if cls.inited:
|
||||
return
|
||||
if os.getuid() != 0:
|
||||
# Note: this script must be run as root
|
||||
# Probably we should only sudo when we need
|
||||
# to as per Big Switch's patch
|
||||
print "*** Mininet must run as root."
|
||||
exit( 1 )
|
||||
fixLimits()
|
||||
cls.inited = True
|
||||
|
||||
def init():
|
||||
"Initialize Mininet."
|
||||
if init.inited:
|
||||
return
|
||||
if os.getuid() != 0:
|
||||
# Note: this script must be run as root
|
||||
# Perhaps we should do so automatically!
|
||||
print "*** Mininet must run as root."
|
||||
exit( 1 )
|
||||
# If which produces no output, then mnexec is not in the path.
|
||||
# May want to loosen this to handle mnexec in the current dir.
|
||||
if not quietRun( 'which mnexec' ):
|
||||
raise Exception( "Could not find mnexec - check $PATH" )
|
||||
fixLimits()
|
||||
init.inited = True
|
||||
|
||||
init.inited = False
|
||||
class MininetWithControlNet( Mininet ):
|
||||
|
||||
# pylint: enable-msg=W0612
|
||||
"""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.
|
||||
|
||||
6. Ultimately we may wish to extend this to allow us to create a
|
||||
control network which every node's control interface is
|
||||
attached to."""
|
||||
|
||||
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 )
|
||||
link = self.link( switch, controller, port1=0 )
|
||||
sintf, cintf = link.intf1, link.intf2
|
||||
switch.controlIntf = sintf
|
||||
snum += 1
|
||||
while snum & 0xff in [ 0, 255 ]:
|
||||
snum += 1
|
||||
sip = ipStr( snum )
|
||||
cintf.setIP( cip, prefixLen )
|
||||
sintf.setIP( sip, prefixLen )
|
||||
controller.setHostRoute( sip, cintf )
|
||||
switch.setHostRoute( cip, sintf )
|
||||
info( '\n' )
|
||||
info( '*** Testing control network\n' )
|
||||
while not cintf.isUp():
|
||||
info( '*** Waiting for', cintf, 'to come up\n' )
|
||||
sleep( 1 )
|
||||
for switch in self.switches:
|
||||
while not sintf.isUp():
|
||||
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' )
|
||||
|
||||
+557
-266
File diff suppressed because it is too large
Load Diff
@@ -5,16 +5,14 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from mininet.net import init, Mininet
|
||||
from mininet.node import Host, Controller, ControllerParams
|
||||
# from mininet.node import KernelSwitch
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host, Controller
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch,
|
||||
# 'kernel': KernelSwitch
|
||||
}
|
||||
|
||||
|
||||
@@ -23,21 +21,15 @@ class testSingleSwitch( unittest.TestCase ):
|
||||
|
||||
def testMinimal( self ):
|
||||
"Ping test with both datapaths on minimal topology"
|
||||
init()
|
||||
for switch in SWITCHES.values():
|
||||
controllerParams = ControllerParams( '10.0.0.0', 8 )
|
||||
mn = Mininet( SingleSwitchTopo(), switch, Host, Controller,
|
||||
controllerParams )
|
||||
mn = Mininet( SingleSwitchTopo(), switch, Host, Controller )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
def testSingle5( self ):
|
||||
"Ping test with both datapaths on 5-host single-switch topology"
|
||||
init()
|
||||
for switch in SWITCHES.values():
|
||||
controllerParams = ControllerParams( '10.0.0.0', 8 )
|
||||
mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller,
|
||||
controllerParams )
|
||||
mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
@@ -47,15 +39,12 @@ class testLinear( unittest.TestCase ):
|
||||
|
||||
def testLinear5( self ):
|
||||
"Ping test with both datapaths on a 5-switch topology"
|
||||
init()
|
||||
for switch in SWITCHES.values():
|
||||
controllerParams = ControllerParams( '10.0.0.0', 8 )
|
||||
mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller,
|
||||
controllerParams )
|
||||
mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('warning')
|
||||
setLogLevel( 'warning' )
|
||||
unittest.main()
|
||||
|
||||
+137
-317
@@ -16,258 +16,125 @@ setup for testing, and can even be emulated with the Mininet package.
|
||||
# from networkx.classes.graph import Graph
|
||||
|
||||
from networkx import Graph
|
||||
from mininet.node import SWITCH_PORT_BASE
|
||||
|
||||
class NodeID(object):
|
||||
'''Topo node identifier.'''
|
||||
|
||||
def __init__(self, dpid = None):
|
||||
'''Init.
|
||||
|
||||
@param dpid dpid
|
||||
'''
|
||||
# DPID-compatible hashable identifier: opaque 64-bit unsigned int
|
||||
self.dpid = dpid
|
||||
|
||||
def __str__(self):
|
||||
'''String conversion.
|
||||
|
||||
@return str dpid as string
|
||||
'''
|
||||
return str(self.dpid)
|
||||
|
||||
def name_str(self):
|
||||
'''Name conversion.
|
||||
|
||||
@return name name as string
|
||||
'''
|
||||
return str(self.dpid)
|
||||
|
||||
def ip_str(self):
|
||||
'''Name conversion.
|
||||
|
||||
@return ip ip as string
|
||||
'''
|
||||
hi = (self.dpid & 0xff0000) >> 16
|
||||
mid = (self.dpid & 0xff00) >> 8
|
||||
lo = self.dpid & 0xff
|
||||
return "10.%i.%i.%i" % (hi, mid, lo)
|
||||
|
||||
|
||||
class Node(object):
|
||||
'''Node-specific vertex metadata for a Topo object.'''
|
||||
|
||||
def __init__(self, connected = False, admin_on = True,
|
||||
power_on = True, fault = False, is_switch = True):
|
||||
'''Init.
|
||||
|
||||
@param connected actively connected to controller
|
||||
@param admin_on administratively on or off
|
||||
@param power_on powered on or off
|
||||
@param fault fault seen on node
|
||||
@param is_switch switch or host
|
||||
'''
|
||||
self.connected = connected
|
||||
self.admin_on = admin_on
|
||||
self.power_on = power_on
|
||||
self.fault = fault
|
||||
self.is_switch = is_switch
|
||||
|
||||
|
||||
class Edge(object):
|
||||
'''Edge-specific metadata for a StructuredTopo graph.'''
|
||||
|
||||
def __init__(self, admin_on = True, power_on = True, fault = False):
|
||||
'''Init.
|
||||
|
||||
@param admin_on administratively on or off; defaults to True
|
||||
@param power_on powered on or off; defaults to True
|
||||
@param fault fault seen on edge; defaults to False
|
||||
'''
|
||||
self.admin_on = admin_on
|
||||
self.power_on = power_on
|
||||
self.fault = fault
|
||||
|
||||
from mininet.util import irange, natural, naturalSeq
|
||||
|
||||
class Topo(object):
|
||||
'''Data center network representation for structured multi-trees.'''
|
||||
"Data center network representation for structured multi-trees."
|
||||
|
||||
def __init__(self):
|
||||
'''Create Topo object.
|
||||
|
||||
'''
|
||||
def __init__(self, hopts=None, sopts=None, lopts=None):
|
||||
"""Topo object:
|
||||
hinfo: default host options
|
||||
sopts: default switch options
|
||||
lopts: default link options"""
|
||||
self.g = Graph()
|
||||
self.node_info = {} # dpids hash to Node objects
|
||||
self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects
|
||||
self.node_info = {}
|
||||
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
|
||||
self.hopts = {} if hopts is None else hopts
|
||||
self.sopts = {} if sopts is None else sopts
|
||||
self.lopts = {} if lopts is None else lopts
|
||||
self.ports = {} # ports[src][dst] is port on src that connects to dst
|
||||
self.id_gen = NodeID # class used to generate dpid
|
||||
|
||||
def add_node(self, dpid, node):
|
||||
'''Add Node to graph.
|
||||
def add_node(self, name, **opts):
|
||||
"""Add Node to graph.
|
||||
name: name
|
||||
opts: node options
|
||||
returns: node name"""
|
||||
self.g.add_node(name)
|
||||
self.node_info[name] = opts
|
||||
return name
|
||||
|
||||
@param dpid dpid
|
||||
@param node Node object
|
||||
'''
|
||||
self.g.add_node(dpid)
|
||||
self.node_info[dpid] = node
|
||||
def add_host(self, name, **opts):
|
||||
"""Convenience method: Add host to graph.
|
||||
name: host name
|
||||
opts: host options
|
||||
returns: host name"""
|
||||
if not opts and self.hopts:
|
||||
opts = self.hopts
|
||||
return self.add_node(name, **opts)
|
||||
|
||||
def add_edge(self, src, dst, edge = None):
|
||||
'''Add edge (Node, Node) to graph.
|
||||
def add_switch(self, name, **opts):
|
||||
"""Convenience method: Add switch to graph.
|
||||
name: switch name
|
||||
opts: switch options
|
||||
returns: switch name"""
|
||||
if not opts and self.sopts:
|
||||
opts = self.sopts
|
||||
result = self.add_node(name, is_switch=True, **opts)
|
||||
return result
|
||||
|
||||
@param src src dpid
|
||||
@param dst dst dpid
|
||||
@param edge Edge object
|
||||
'''
|
||||
src, dst = tuple(sorted([src, dst]))
|
||||
self.g.add_edge(src, dst)
|
||||
if not edge:
|
||||
edge = Edge()
|
||||
self.edge_info[(src, dst)] = edge
|
||||
self.add_port(src, dst)
|
||||
def add_link(self, node1, node2, port1=None, port2=None,
|
||||
**opts):
|
||||
"""node1, node2: nodes to link together
|
||||
port1, port2: ports (optional)
|
||||
opts: link options (optional)
|
||||
returns: link info key"""
|
||||
if not opts and self.lopts:
|
||||
opts = self.lopts
|
||||
self.add_port(node1, node2, port1, port2)
|
||||
key = tuple(self.sorted([node1, node2]))
|
||||
self.link_info[key] = opts
|
||||
self.g.add_edge(*key)
|
||||
return key
|
||||
|
||||
def add_port(self, src, dst):
|
||||
def add_port(self, src, dst, sport=None, dport=None):
|
||||
'''Generate port mapping for new edge.
|
||||
|
||||
@param src source switch DPID
|
||||
@param dst destination switch DPID
|
||||
@param src source switch name
|
||||
@param dst destination switch name
|
||||
'''
|
||||
src_base = SWITCH_PORT_BASE if self.is_switch(src) else 0
|
||||
dst_base = SWITCH_PORT_BASE if self.is_switch(dst) else 0
|
||||
if src not in self.ports:
|
||||
self.ports[src] = {}
|
||||
if dst not in self.ports[src]:
|
||||
# num outlinks
|
||||
self.ports[src][dst] = len(self.ports[src]) + src_base
|
||||
if dst not in self.ports:
|
||||
self.ports[dst] = {}
|
||||
if src not in self.ports[dst]:
|
||||
# num outlinks
|
||||
self.ports[dst][src] = len(self.ports[dst]) + dst_base
|
||||
self.ports.setdefault(src, {})
|
||||
self.ports.setdefault(dst, {})
|
||||
# New port: number of outlinks + base
|
||||
src_base = 1 if self.is_switch(src) else 0
|
||||
dst_base = 1 if self.is_switch(dst) else 0
|
||||
if sport is None:
|
||||
sport = len(self.ports[src]) + src_base
|
||||
if dport is None:
|
||||
dport = len(self.ports[dst]) + dst_base
|
||||
self.ports[src][dst] = sport
|
||||
self.ports[dst][src] = dport
|
||||
|
||||
def node_enabled(self, dpid):
|
||||
'''Is node connected, admin on, powered on, and fault-free?
|
||||
|
||||
@param dpid dpid
|
||||
|
||||
@return bool node is enabled
|
||||
'''
|
||||
ni = self.node_info[dpid]
|
||||
return ni.connected and ni.admin_on and ni.power_on and not ni.fault
|
||||
|
||||
def nodes_enabled(self, dpids, enabled = True):
|
||||
'''Return subset of enabled nodes
|
||||
|
||||
@param dpids list of dpids
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
@return dpids filtered list of dpids
|
||||
'''
|
||||
if enabled:
|
||||
return [n for n in dpids if self.node_enabled(n)]
|
||||
def nodes(self, sort=True):
|
||||
"Return nodes in graph"
|
||||
if sort:
|
||||
return self.sorted( self.g.nodes() )
|
||||
else:
|
||||
return dpids
|
||||
|
||||
def nodes(self, enabled = True):
|
||||
'''Return graph nodes.
|
||||
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
return self.nodes_enabled(self.g.nodes(), enabled)
|
||||
|
||||
def nodes_str(self, dpids):
|
||||
'''Return string of custom-encoded nodes.
|
||||
|
||||
@param dpids list of dpids
|
||||
|
||||
@return str string
|
||||
'''
|
||||
return [str(self.id_gen(dpid = dpid)) for dpid in dpids]
|
||||
return self.g.nodes()
|
||||
|
||||
def is_switch(self, n):
|
||||
'''Returns true if node is a switch.'''
|
||||
return self.node_info[n].is_switch
|
||||
info = self.node_info[n]
|
||||
return info and info.get('is_switch', False)
|
||||
|
||||
def switches(self, enabled = True):
|
||||
def switches(self, sort=True):
|
||||
'''Return switches.
|
||||
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
sort: sort switches alphabetically
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
nodes = [n for n in self.g.nodes() if self.is_switch(n)]
|
||||
return self.nodes_enabled(nodes, enabled)
|
||||
return [n for n in self.nodes(sort) if self.is_switch(n)]
|
||||
|
||||
def hosts(self, enabled = True):
|
||||
def hosts(self, sort=True):
|
||||
'''Return hosts.
|
||||
|
||||
@param enabled only return enabled nodes?
|
||||
|
||||
sort: sort hosts alphabetically
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
return [n for n in self.nodes(sort) if not self.is_switch(n)]
|
||||
|
||||
def is_host(n):
|
||||
'''Returns true if node is a host.'''
|
||||
return not self.node_info[n].is_switch
|
||||
|
||||
nodes = [n for n in self.g.nodes() if is_host(n)]
|
||||
return self.nodes_enabled(nodes, enabled)
|
||||
|
||||
def edge_enabled(self, edge):
|
||||
'''Is edge admin on, powered on, and fault-free?
|
||||
|
||||
@param edge (src, dst) dpid tuple
|
||||
|
||||
@return bool edge is enabled
|
||||
def links(self, sort=True):
|
||||
'''Return links.
|
||||
sort: sort links alphabetically
|
||||
@return links list of name pairs
|
||||
'''
|
||||
src, dst = edge
|
||||
src, dst = tuple(sorted([src, dst]))
|
||||
ei = self.edge_info[tuple(sorted([src, dst]))]
|
||||
return ei.admin_on and ei.power_on and not ei.fault
|
||||
|
||||
def edges_enabled(self, edges, enabled = True):
|
||||
'''Return subset of enabled edges
|
||||
|
||||
@param edges list of edges
|
||||
@param enabled only return enabled edges?
|
||||
|
||||
@return edges filtered list of edges
|
||||
'''
|
||||
if enabled:
|
||||
return [e for e in edges if self.edge_enabled(e)]
|
||||
if not sort:
|
||||
return self.g.edges()
|
||||
else:
|
||||
return edges
|
||||
|
||||
def edges(self, enabled = True):
|
||||
'''Return edges.
|
||||
|
||||
@param enabled only return enabled edges?
|
||||
|
||||
@return edges list of dpid pairs
|
||||
'''
|
||||
return self.edges_enabled(self.g.edges(), enabled)
|
||||
|
||||
def edges_str(self, dpid_pairs):
|
||||
'''Return string of custom-encoded node pairs.
|
||||
|
||||
@param dpid_pairs list of dpid pairs (src, dst)
|
||||
|
||||
@return str string
|
||||
'''
|
||||
edges = []
|
||||
for pair in dpid_pairs:
|
||||
src, dst = pair
|
||||
src = str(self.id_gen(dpid = src))
|
||||
dst = str(self.id_gen(dpid = dst))
|
||||
edges.append((src, dst))
|
||||
return edges
|
||||
links = [tuple(self.sorted(e)) for e in self.g.edges()]
|
||||
return sorted( links, key=naturalSeq )
|
||||
|
||||
def port(self, src, dst):
|
||||
'''Get port number.
|
||||
|
||||
@param src source switch DPID
|
||||
@param dst destination switch DPID
|
||||
@param src source switch name
|
||||
@param dst destination switch name
|
||||
@return tuple (src_port, dst_port):
|
||||
src_port: port on source switch leading to the destination switch
|
||||
dst_port: port on destination switch leading to the source switch
|
||||
@@ -276,130 +143,83 @@ class Topo(object):
|
||||
assert dst in self.ports and src in self.ports[dst]
|
||||
return (self.ports[src][dst], self.ports[dst][src])
|
||||
|
||||
def enable_edges(self):
|
||||
'''Enable all edges in the network graph.
|
||||
def linkInfo( self, src, dst ):
|
||||
"Return link metadata"
|
||||
src, dst = self.sorted([src, dst])
|
||||
return self.link_info[(src, dst)]
|
||||
|
||||
Set admin on, power on, and fault off.
|
||||
'''
|
||||
for e in self.g.edges():
|
||||
src, dst = e
|
||||
ei = self.edge_info[tuple(sorted([src, dst]))]
|
||||
ei.admin_on = True
|
||||
ei.power_on = True
|
||||
ei.fault = False
|
||||
def nodeInfo( self, name ):
|
||||
"Return metadata (dict) for node"
|
||||
info = self.node_info[ name ]
|
||||
return info if info is not None else {}
|
||||
|
||||
def enable_nodes(self):
|
||||
'''Enable all nodes in the network graph.
|
||||
|
||||
Set connected on, admin on, power on, and fault off.
|
||||
'''
|
||||
for node in self.g.nodes():
|
||||
ni = self.node_info[node]
|
||||
ni.connected = True
|
||||
ni.admin_on = True
|
||||
ni.power_on = True
|
||||
ni.fault = False
|
||||
|
||||
def enable_all(self):
|
||||
'''Enable all nodes and edges in the network graph.'''
|
||||
self.enable_nodes()
|
||||
self.enable_edges()
|
||||
|
||||
def name(self, dpid):
|
||||
'''Get string name of node ID.
|
||||
|
||||
@param dpid DPID of host or switch
|
||||
@return name_str string name with no dashes
|
||||
'''
|
||||
return self.id_gen(dpid = dpid).name_str()
|
||||
|
||||
def ip(self, dpid):
|
||||
'''Get IP dotted-decimal string of node ID.
|
||||
|
||||
@param dpid DPID of host or switch
|
||||
@return ip_str
|
||||
'''
|
||||
return self.id_gen(dpid = dpid).ip_str()
|
||||
def setNodeInfo( self, name, info ):
|
||||
"Set metadata (dict) for node"
|
||||
self.node_info[ name ] = info
|
||||
|
||||
@staticmethod
|
||||
def sorted( items ):
|
||||
"Items sorted in natural (i.e. alphabetical) order"
|
||||
return sorted(items, key=natural)
|
||||
|
||||
class SingleSwitchTopo(Topo):
|
||||
'''Single switch connected to k hosts.'''
|
||||
|
||||
def __init__(self, k = 2, enable_all = True):
|
||||
def __init__(self, k=2, **opts):
|
||||
'''Init.
|
||||
|
||||
@param k number of hosts
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(SingleSwitchTopo, self).__init__()
|
||||
super(SingleSwitchTopo, self).__init__(**opts)
|
||||
|
||||
self.k = k
|
||||
|
||||
self.add_node(1, Node())
|
||||
hosts = range(2, k + 2)
|
||||
for h in hosts:
|
||||
self.add_node(h, Node(is_switch = False))
|
||||
self.add_edge(h, 1, Edge())
|
||||
|
||||
if enable_all:
|
||||
self.enable_all()
|
||||
switch = self.add_switch('s1')
|
||||
for h in irange(1, k):
|
||||
host = self.add_host('h%s' % h)
|
||||
self.add_link(host, switch)
|
||||
|
||||
|
||||
class SingleSwitchReversedTopo(SingleSwitchTopo):
|
||||
class SingleSwitchReversedTopo(Topo):
|
||||
'''Single switch connected to k hosts, with reversed ports.
|
||||
|
||||
The lowest-numbered host is connected to the highest-numbered port.
|
||||
|
||||
Useful to verify that Mininet properly handles custom port numberings.
|
||||
'''
|
||||
|
||||
def port(self, src, dst):
|
||||
'''Get port number.
|
||||
|
||||
@param src source switch DPID
|
||||
@param dst destination switch DPID
|
||||
@return tuple (src_port, dst_port):
|
||||
src_port: port on source switch leading to the destination switch
|
||||
dst_port: port on destination switch leading to the source switch
|
||||
'''
|
||||
if src == 1:
|
||||
if dst in range(2, self.k + 2):
|
||||
dst_index = dst - 2
|
||||
highest = self.k - 1
|
||||
return (highest - dst_index, 0)
|
||||
else:
|
||||
raise Exception('unexpected dst: %i' % dst)
|
||||
elif src in range(2, self.k + 2):
|
||||
if dst == 1:
|
||||
raise Exception('unexpected dst: %i' % dst)
|
||||
else:
|
||||
src_index = src - 2
|
||||
highest = self.k - 1
|
||||
return (0, highest - src_index)
|
||||
|
||||
|
||||
class LinearTopo(Topo):
|
||||
'''Linear topology of k switches, with one host per switch.'''
|
||||
|
||||
def __init__(self, k = 2, enable_all = True):
|
||||
def __init__(self, k=2, **opts):
|
||||
'''Init.
|
||||
|
||||
@param k number of switches (and hosts too)
|
||||
@param k number of hosts
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(LinearTopo, self).__init__()
|
||||
super(SingleSwitchReversedTopo, self).__init__(**opts)
|
||||
self.k = k
|
||||
switch = self.add_switch('s1')
|
||||
for h in irange(1, k):
|
||||
host = self.add_host('h%s' % h)
|
||||
self.add_link(host, switch,
|
||||
port1=0, port2=(k - h + 1))
|
||||
|
||||
class LinearTopo(Topo):
|
||||
"Linear topology of k switches, with one host per switch."
|
||||
|
||||
def __init__(self, k=2, **opts):
|
||||
"""Init.
|
||||
k: number of switches (and hosts)
|
||||
hconf: host configuration options
|
||||
lconf: link configuration options"""
|
||||
|
||||
super(LinearTopo, self).__init__(**opts)
|
||||
|
||||
self.k = k
|
||||
|
||||
switches = range(1, k + 1)
|
||||
for s in switches:
|
||||
h = s + k
|
||||
self.add_node(s, Node())
|
||||
self.add_node(h, Node(is_switch = False))
|
||||
self.add_edge(s, h, Edge())
|
||||
for s in switches:
|
||||
if s != k:
|
||||
self.add_edge(s, s + 1, Edge())
|
||||
|
||||
if enable_all:
|
||||
self.enable_all()
|
||||
lastSwitch = None
|
||||
for i in irange(1, k):
|
||||
host = self.add_host('h%s' % i)
|
||||
switch = self.add_switch('s%s' % i)
|
||||
self.add_link( host, switch)
|
||||
if lastSwitch:
|
||||
self.add_link( switch, lastSwitch)
|
||||
lastSwitch = switch
|
||||
|
||||
+10
-19
@@ -1,6 +1,6 @@
|
||||
"Library of potentially useful topologies for Mininet"
|
||||
|
||||
from mininet.topo import Topo, Node
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
|
||||
class TreeTopo( Topo ):
|
||||
@@ -8,36 +8,27 @@ class TreeTopo( Topo ):
|
||||
|
||||
def __init__( self, depth=1, fanout=2 ):
|
||||
super( TreeTopo, self ).__init__()
|
||||
# Numbering: h1..N, sN+1..M
|
||||
hostCount = fanout ** depth
|
||||
# Numbering: h1..N, s1..M
|
||||
self.hostNum = 1
|
||||
self.switchNum = hostCount + 1
|
||||
self.switchNum = 1
|
||||
# Build topology
|
||||
self.addTree( depth, fanout )
|
||||
# Consider all switches and hosts 'on'
|
||||
self.enable_all()
|
||||
|
||||
# It is OK that i is "unused" in the for loop.
|
||||
# pylint: disable-msg=W0612
|
||||
|
||||
def addTree( self, depth, fanout ):
|
||||
"""Add a subtree starting with node n.
|
||||
returns: last node added"""
|
||||
isSwitch = depth > 0
|
||||
if isSwitch:
|
||||
num = self.switchNum
|
||||
node = self.add_switch( 's%s' % self.switchNum )
|
||||
self.switchNum += 1
|
||||
else:
|
||||
num = self.hostNum
|
||||
self.hostNum += 1
|
||||
self.add_node( num, Node( is_switch=isSwitch ) )
|
||||
if isSwitch:
|
||||
for i in range( 0, fanout ):
|
||||
for _ in range( fanout ):
|
||||
child = self.addTree( depth - 1, fanout )
|
||||
self.add_edge( num, child )
|
||||
return num
|
||||
self.add_link( node, child )
|
||||
else:
|
||||
node = self.add_host( 'h%s' % self.hostNum )
|
||||
self.hostNum += 1
|
||||
return node
|
||||
|
||||
# pylint: enable-msg=W0612
|
||||
|
||||
def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
|
||||
+170
-25
@@ -2,10 +2,10 @@
|
||||
|
||||
from time import sleep
|
||||
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
|
||||
import select
|
||||
from select import poll, POLLIN
|
||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||
|
||||
from mininet.log import error
|
||||
from mininet.log import output, info, error
|
||||
import re
|
||||
|
||||
# Command execution support
|
||||
|
||||
@@ -22,7 +22,7 @@ def checkRun( cmd ):
|
||||
# pylint doesn't understand explicit type checking
|
||||
# pylint: disable-msg=E1103
|
||||
|
||||
def quietRun( *cmd ):
|
||||
def oldQuietRun( *cmd ):
|
||||
"""Run a command, routing stderr to stdout, and return the output.
|
||||
cmd: list of command params"""
|
||||
if len( cmd ) == 1:
|
||||
@@ -33,19 +33,82 @@ def quietRun( *cmd ):
|
||||
# We can't use Popen.communicate() because it uses
|
||||
# select(), which can't handle
|
||||
# high file descriptor numbers! poll() can, however.
|
||||
output = ''
|
||||
readable = select.poll()
|
||||
out = ''
|
||||
readable = poll()
|
||||
readable.register( popen.stdout )
|
||||
while True:
|
||||
while readable.poll():
|
||||
data = popen.stdout.read( 1024 )
|
||||
if len( data ) == 0:
|
||||
break
|
||||
output += data
|
||||
out += data
|
||||
popen.poll()
|
||||
if popen.returncode != None:
|
||||
break
|
||||
return output
|
||||
return out
|
||||
|
||||
|
||||
# This is a bit complicated, but it enables us to
|
||||
# monitor commount output as it is happening
|
||||
|
||||
def errRun( *cmd, **kwargs ):
|
||||
"""Run a command and return stdout, stderr and return code
|
||||
cmd: string or list of command and args
|
||||
stderr: STDOUT to merge stderr with stdout
|
||||
shell: run command using shell
|
||||
echo: monitor output to console"""
|
||||
# Allow passing in a list or a string
|
||||
if len( cmd ) == 1:
|
||||
cmd = cmd[ 0 ]
|
||||
if isinstance( cmd, str ):
|
||||
cmd = cmd.split( ' ' )
|
||||
cmd = [ str( arg ) for arg in cmd ]
|
||||
# By default we separate stderr, don't run in a shell, and don't echo
|
||||
stderr = kwargs.get( 'stderr', PIPE )
|
||||
shell = kwargs.get( 'shell', False )
|
||||
echo = kwargs.get( 'echo', False )
|
||||
if echo:
|
||||
# cmd goes to stderr, output goes to stdout
|
||||
info( cmd, '\n' )
|
||||
popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
|
||||
# We use poll() because select() doesn't work with large fd numbers
|
||||
out, err = '', ''
|
||||
poller = poll()
|
||||
poller.register( popen.stdout, POLLIN )
|
||||
fdtofile = { popen.stdout.fileno(): popen.stdout }
|
||||
if popen.stderr:
|
||||
fdtofile[ popen.stderr.fileno() ] = popen.stderr
|
||||
poller.register( popen.stderr, POLLIN )
|
||||
while True:
|
||||
readable = poller.poll()
|
||||
# Tell pylint to ignore unused variable event
|
||||
# pylint: disable-msg=W0612
|
||||
for fd, event in readable:
|
||||
# pylint: enable-msg=W0612
|
||||
f = fdtofile[ fd ]
|
||||
data = f.read( 1024 )
|
||||
if echo:
|
||||
output( data )
|
||||
if f == popen.stdout:
|
||||
out += data
|
||||
elif f == popen.stderr:
|
||||
err += data
|
||||
returncode = popen.poll()
|
||||
if returncode is not None:
|
||||
break
|
||||
return out, err, returncode
|
||||
|
||||
def errFail( *cmd, **kwargs ):
|
||||
"Run a command using errRun and raise exception on nonzero exit"
|
||||
out, err, ret = errRun( *cmd, **kwargs )
|
||||
if ret:
|
||||
raise Exception( "errFail: %s failed with return code %s: %s"
|
||||
% ( cmd, ret, err ) )
|
||||
return out, err, ret
|
||||
|
||||
def quietRun( cmd, **kwargs ):
|
||||
"Run a command and return merged stdout and stderr"
|
||||
return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
|
||||
|
||||
# pylint: enable-msg=E1103
|
||||
# pylint: disable-msg=E1101,W0612
|
||||
@@ -124,25 +187,41 @@ def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ):
|
||||
printError: if true, print error"""
|
||||
retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError )
|
||||
|
||||
def createLink( node1, node2, port1=None, port2=None ):
|
||||
"""Create a link between nodes, making an interface for each.
|
||||
node1: Node object
|
||||
node2: Node object
|
||||
port1: node1 port number (optional)
|
||||
port2: node2 port number (optional)
|
||||
returns: intf1 name, intf2 name"""
|
||||
return node1.linkTo( node2, port1, port2 )
|
||||
# Support for dumping network
|
||||
|
||||
def dumpNodeConnections( nodes ):
|
||||
"Dump connections to/from nodes."
|
||||
|
||||
def dumpConnections( node ):
|
||||
"Helper function: dump connections to node"
|
||||
for intf in node.intfList():
|
||||
output( ' %s:' % intf )
|
||||
if intf.link:
|
||||
intfs = [ intf.link.intf1, intf.link.intf2 ]
|
||||
intfs.remove( intf )
|
||||
output( intfs[ 0 ] )
|
||||
else:
|
||||
output( ' ' )
|
||||
|
||||
for node in nodes:
|
||||
output( node.name )
|
||||
dumpConnections( node )
|
||||
output( '\n' )
|
||||
|
||||
def dumpNetConnections( net ):
|
||||
"Dump connections in network"
|
||||
nodes = net.controllers + net.switches + net.hosts
|
||||
dumpNodeConnections( nodes )
|
||||
|
||||
# IP and Mac address formatting and parsing
|
||||
|
||||
def _colonHex( val, bytes ):
|
||||
def _colonHex( val, bytecount ):
|
||||
"""Generate colon-hex string.
|
||||
val: input as unsigned int
|
||||
bytes: number of bytes to convert
|
||||
bytescount: number of bytes to convert
|
||||
returns: chStr colon-hex string"""
|
||||
pieces = []
|
||||
for i in range( bytes - 1, -1, -1 ):
|
||||
for i in range( bytecount - 1, -1, -1 ):
|
||||
piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
|
||||
pieces.append( '%02x' % piece )
|
||||
chStr = ':'.join( pieces )
|
||||
@@ -158,23 +237,44 @@ def ipStr( ip ):
|
||||
"""Generate IP address string from an unsigned int.
|
||||
ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
|
||||
returns: ip address string w.x.y.z, or 10.x.y.z if w==0"""
|
||||
w = ( ip & 0xff000000 ) >> 24
|
||||
w = ( ip >> 24 ) & 0xff
|
||||
w = 10 if w == 0 else w
|
||||
x = ( ip & 0xff0000 ) >> 16
|
||||
y = ( ip & 0xff00 ) >> 8
|
||||
x = ( ip >> 16 ) & 0xff
|
||||
y = ( ip >> 8 ) & 0xff
|
||||
z = ip & 0xff
|
||||
return "%i.%i.%i.%i" % ( w, x, y, z )
|
||||
|
||||
def ipNum( w, x, y, z ):
|
||||
"""Generate unsigned int from components ofIP address
|
||||
"""Generate unsigned int from components of IP address
|
||||
returns: w << 24 | x << 16 | y << 8 | z"""
|
||||
return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
|
||||
|
||||
def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
|
||||
"""Return IP address string from ints
|
||||
i: int to be added to ipbase
|
||||
prefixLen: optional IP prefix length
|
||||
ipBaseNum: option base IP address as int
|
||||
returns IP address as string"""
|
||||
# Ugly but functional
|
||||
assert i < ( 1 << ( 32 - prefixLen ) )
|
||||
mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 )
|
||||
ipnum = i + ( ipBaseNum & mask )
|
||||
return ipStr( ipnum )
|
||||
|
||||
def ipParse( ip ):
|
||||
"Parse an IP address and return an unsigned int."
|
||||
args = [ int( arg ) for arg in ip.split( '.' ) ]
|
||||
return ipNum( *args )
|
||||
|
||||
def netParse( ipstr ):
|
||||
"""Parse an IP network specification, returning
|
||||
address and prefix len as unsigned ints"""
|
||||
prefixLen = 0
|
||||
if '/' in ipstr:
|
||||
ip, pf = ipstr.split( '/' )
|
||||
prefixLen = int( pf )
|
||||
return ipParse( ip ), prefixLen
|
||||
|
||||
def checkInt( s ):
|
||||
"Check if input string is an int"
|
||||
try:
|
||||
@@ -205,5 +305,50 @@ def makeNumeric( s ):
|
||||
|
||||
def fixLimits():
|
||||
"Fix ridiculously small resource limits."
|
||||
setrlimit( RLIMIT_NPROC, ( 4096, 8192 ) )
|
||||
setrlimit( RLIMIT_NOFILE, ( 16384, 32768 ) )
|
||||
setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) )
|
||||
setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) )
|
||||
|
||||
def mountCgroups():
|
||||
"Make sure cgroups file system is mounted"
|
||||
mounts = quietRun( 'mount' )
|
||||
cgdir = '/sys/fs/cgroup'
|
||||
csdir = cgdir + '/cpuset'
|
||||
if 'cgroups on %s' % cgdir not in mounts:
|
||||
raise Exception( "cgroups not mounted on " + cgdir )
|
||||
if 'cpuset on %s' % csdir not in mounts:
|
||||
errRun( 'mkdir -p ' + csdir )
|
||||
errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
|
||||
|
||||
def natural( text ):
|
||||
"To sort sanely/alphabetically: sorted( l, key=natural )"
|
||||
def num( s ):
|
||||
"Convert text segment to int if necessary"
|
||||
return int( s ) if s.isdigit() else s
|
||||
return [ num( s ) for s in re.split( r'(\d+)', text ) ]
|
||||
|
||||
def naturalSeq( t ):
|
||||
"Natural sort key function for sequences"
|
||||
return [ natural( x ) for x in t ]
|
||||
|
||||
def numCores():
|
||||
"Returns number of CPU cores based on /proc/cpuinfo"
|
||||
if hasattr( numCores, 'ncores' ):
|
||||
return numCores.ncores
|
||||
try:
|
||||
numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
|
||||
except ValueError:
|
||||
return 0
|
||||
return numCores.ncores
|
||||
|
||||
def irange(start, end):
|
||||
"""Inclusive range from start to end (vs. Python insanity.)
|
||||
irange(1,5) -> 1, 2, 3, 4, 5"""
|
||||
return range( start, end + 1 )
|
||||
|
||||
def custom( cls, **params ):
|
||||
"Returns customized constructor for class cls."
|
||||
def customized( *args, **kwargs):
|
||||
"Customized constructor"
|
||||
kwargs.update( params )
|
||||
return cls( *args, **kwargs )
|
||||
return customized
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
from setuptools import setup, find_packages
|
||||
from os.path import join
|
||||
|
||||
scripts = [ join( 'bin', filename ) for filename in [
|
||||
'mn', 'mnexec' ] ]
|
||||
scripts = [ join( 'bin', filename ) for filename in [ 'mn' ] ]
|
||||
|
||||
modname = distname = 'mininet'
|
||||
|
||||
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Attempt to build debian packages for OVS
|
||||
|
||||
set -e # exit on error
|
||||
set -u # exit on undefined variable
|
||||
|
||||
kvers=`uname -r`
|
||||
ksrc=/lib/modules/$kvers/build
|
||||
dist=`lsb_release -is | tr [A-Z] [a-z]`
|
||||
release=`lsb_release -rs`
|
||||
arch=`uname -m`
|
||||
buildsuffix=-2
|
||||
if [ "$arch" = "i686" ]; then arch=i386; fi
|
||||
if [ "$arch" = "x86_64" ]; then arch=amd64; fi
|
||||
|
||||
overs=1.4.0
|
||||
ovs=openvswitch-$overs
|
||||
ovstgz=$ovs.tar.gz
|
||||
ovsurl=http://openvswitch.org/releases/$ovstgz
|
||||
|
||||
install='sudo apt-get install -y'
|
||||
|
||||
echo "*** Installing debian/ubuntu build system"
|
||||
$install build-essential devscripts ubuntu-dev-tools debhelper dh-make
|
||||
$install diff patch cdbs quilt gnupg fakeroot lintian pbuilder piuparts
|
||||
$install module-assistant
|
||||
|
||||
echo "*** Installing OVS dependencies"
|
||||
$install pkg-config gcc make python-dev libssl-dev libtool
|
||||
$install dkms ipsec-tools
|
||||
|
||||
echo "*** Installing headers for $kvers"
|
||||
$install linux-headers-$kvers
|
||||
|
||||
echo "*** Retrieving OVS source"
|
||||
wget -c $ovsurl
|
||||
tar xzf $ovstgz
|
||||
cd $ovs
|
||||
|
||||
echo "*** Patching OVS source"
|
||||
# Not sure why this fails, but off it goes!
|
||||
sed -i -e 's/dh_strip/# dh_strip/' debian/rules
|
||||
if [ "$release" = "10.04" ]; then
|
||||
# Lucid doesn't seem to have all the packages for ovsdbmonitor
|
||||
echo "*** Patching debian/rules to remove dh_python2"
|
||||
sed -i -e 's/dh_python2/dh_pysupport/' debian/rules
|
||||
echo "*** Not building ovsdbmonitor since it's too hard on 10.04"
|
||||
mv debian/ovsdbmonitor.install debian/ovsdbmonitor.install.backup
|
||||
sed -i -e 's/ovsdbmonitor.install/ovsdbmonitor.install.backup/' Makefile.in
|
||||
else
|
||||
# Install a bag of hurt for ovsdbmonitor
|
||||
$install python-pyside.qtcore pyqt4-dev-tools python-twisted python-twisted-bin \
|
||||
python-twisted-core python-twisted-conch python-anyjson python-zope.interface
|
||||
fi
|
||||
# init script was written to assume that commands complete
|
||||
sed -i -e 's/^set -e/#set -e/' debian/openvswitch-controller.init
|
||||
|
||||
echo "*** Building OVS user packages"
|
||||
opts=--with-linux=/lib/modules/`uname -r`/build
|
||||
fakeroot make -f debian/rules DATAPATH_CONFIGURE_OPTS=$opts binary
|
||||
|
||||
echo "*** Building OVS datapath kernel module package"
|
||||
# Still looking for the "right" way to do this...
|
||||
sudo mkdir -p /usr/src/linux
|
||||
ln -sf _debian/openvswitch.tar.gz .
|
||||
sudo make -f debian/rules.modules KSRC=$ksrc KVERS=$kvers binary-modules
|
||||
|
||||
echo "*** Built the following packages:"
|
||||
cd ~
|
||||
ls -l *deb
|
||||
|
||||
archive=ovs-$overs-core-$dist-$release-$arch$buildsuffix.tar
|
||||
ovsbase='common pki switch brcompat controller datapath-dkms'
|
||||
echo "*** Packing up $ovsbase .debs into:"
|
||||
echo " $archive"
|
||||
pkgs=""
|
||||
for component in $ovsbase; do
|
||||
if echo $component | egrep 'dkms|pki'; then
|
||||
# Architecture-independent packages
|
||||
deb=(openvswitch-${component}_$overs*all.deb)
|
||||
else
|
||||
deb=(openvswitch-${component}_$overs*$arch.deb)
|
||||
fi
|
||||
pkgs="$pkgs $deb"
|
||||
done
|
||||
rm -rf $archive
|
||||
tar cf $archive $pkgs
|
||||
|
||||
echo "*** Contents of archive $archive:"
|
||||
tar tf $archive
|
||||
|
||||
echo "*** Done (hopefully)"
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ if __name__ == '__main__':
|
||||
fixLines( infile.readlines(), outfid )
|
||||
infile.close()
|
||||
os.close( outfid )
|
||||
call( [ 'doxypy.py', outname ] )
|
||||
call( [ 'doxypy', outname ] )
|
||||
|
||||
|
||||
|
||||
|
||||
+179
-66
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Mininet install script for Ubuntu (and Debian Lenny)
|
||||
# Brandon Heller (brandonh@stanford.edu)
|
||||
|
||||
@@ -16,57 +17,78 @@ KERNEL_LOC=http://www.openflow.org/downloads/mininet
|
||||
DIST=Unknown
|
||||
RELEASE=Unknown
|
||||
CODENAME=Unknown
|
||||
ARCH=`uname -m`
|
||||
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi
|
||||
if [ "$ARCH" = "i686" ]; then ARCH="i386"; fi
|
||||
|
||||
test -e /etc/debian_version && DIST="Debian"
|
||||
grep Ubuntu /etc/lsb-release &> /dev/null && DIST="Ubuntu"
|
||||
if [ "$DIST" = "Ubuntu" ] || [ "$DIST" = "Debian" ]; then
|
||||
sudo apt-get install -y lsb-release
|
||||
install='sudo apt-get -y install'
|
||||
remove='sudo apt-get -y remove'
|
||||
pkginst='sudo dpkg -i'
|
||||
# Prereqs for this script
|
||||
if ! which lsb_release &> /dev/null; then
|
||||
$install lsb-release
|
||||
fi
|
||||
if ! which bc &> /dev/null; then
|
||||
$install bc
|
||||
fi
|
||||
fi
|
||||
if which lsb_release &> /dev/null; then
|
||||
DIST=`lsb_release -is`
|
||||
RELEASE=`lsb_release -rs`
|
||||
CODENAME=`lsb_release -cs`
|
||||
fi
|
||||
echo "Detected Linux distribution: $DIST $RELEASE $CODENAME"
|
||||
echo "Detected Linux distribution: $DIST $RELEASE $CODENAME $ARCH"
|
||||
|
||||
# Kernel params
|
||||
|
||||
if [ "$DIST" = "Debian" ]; then
|
||||
KERNEL_NAME=2.6.33.1-mininet
|
||||
KERNEL_HEADERS=linux-headers-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
|
||||
KERNEL_IMAGE=linux-image-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
|
||||
elif [ "$DIST" = "Ubuntu" ]; then
|
||||
if [ "$DIST" = "Ubuntu" ]; then
|
||||
if [ "$RELEASE" = "10.04" ]; then
|
||||
KERNEL_NAME='3.0.0-15-generic'
|
||||
else
|
||||
KERNEL_NAME=`uname -r`
|
||||
fi
|
||||
KERNEL_HEADERS=linux-headers-${KERNEL_NAME}
|
||||
elif [ "$DIST" = "Debian" ] && [ "$ARCH" = "i386" ] && [ "$CODENAME" = "lenny" ]; then
|
||||
KERNEL_NAME=2.6.33.1-mininet
|
||||
KERNEL_HEADERS=linux-headers-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
|
||||
KERNEL_IMAGE=linux-image-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
|
||||
else
|
||||
echo "Install.sh currently only supports Ubuntu and Debian."
|
||||
echo "Install.sh currently only supports Ubuntu and Debian Lenny i386."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# More distribution info
|
||||
DIST_LC=`echo $DIST | tr [A-Z] [a-z]` # as lower case
|
||||
|
||||
# Kernel Deb pkg to be removed:
|
||||
KERNEL_IMAGE_OLD=linux-image-2.6.26-2-686
|
||||
KERNEL_IMAGE_OLD=linux-image-2.6.26-33-generic
|
||||
|
||||
DRIVERS_DIR=/lib/modules/${KERNEL_NAME}/kernel/drivers/net
|
||||
|
||||
OVS_RELEASE=v1.2.2
|
||||
OVS_RELEASE=1.4.0
|
||||
OVS_PACKAGE_LOC=https://github.com/downloads/mininet/mininet
|
||||
OVS_BUILDSUFFIX=-ignore # was -2
|
||||
OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar
|
||||
OVS_SRC=~/openvswitch
|
||||
OVS_TAG=v$OVS_RELEASE
|
||||
OVS_BUILD=$OVS_SRC/build-$KERNEL_NAME
|
||||
OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko})
|
||||
|
||||
function kernel {
|
||||
echo "Install Mininet-compatible kernel if necessary"
|
||||
sudo apt-get update
|
||||
if [ "$DIST" = "Debian" ]; then
|
||||
if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then
|
||||
$install linux-image-$KERNEL_NAME
|
||||
elif [ "$DIST" = "Debian" ]; then
|
||||
# The easy approach: download pre-built linux-image and linux-headers packages:
|
||||
wget -c $KERNEL_LOC/$KERNEL_HEADERS
|
||||
wget -c $KERNEL_LOC/$KERNEL_IMAGE
|
||||
|
||||
# Install custom linux headers and image:
|
||||
sudo dpkg -i $KERNEL_IMAGE $KERNEL_HEADERS
|
||||
$pkginst $KERNEL_IMAGE $KERNEL_HEADERS
|
||||
|
||||
# The next two steps are to work around a bug in newer versions of
|
||||
# kernel-package, which fails to add initrd images with the latest kernels.
|
||||
@@ -83,16 +105,15 @@ function kernel {
|
||||
# /boot/grub/menu.lst to set the default to the entry corresponding to the
|
||||
# kernel you just installed.
|
||||
fi
|
||||
if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then
|
||||
sudo apt-get -y install linux-image-$KERNEL_NAME
|
||||
fi
|
||||
}
|
||||
|
||||
function kernel_clean {
|
||||
echo "Cleaning kernel..."
|
||||
|
||||
# To save disk space, remove previous kernel
|
||||
sudo apt-get -y remove $KERNEL_IMAGE_OLD
|
||||
if ! $remove $KERNEL_IMAGE_OLD; then
|
||||
echo $KERNEL_IMAGE_OLD not installed.
|
||||
fi
|
||||
|
||||
# Also remove downloaded packages:
|
||||
rm -f ~/linux-headers-* ~/linux-image-*
|
||||
@@ -101,8 +122,8 @@ function kernel_clean {
|
||||
# Install Mininet deps
|
||||
function mn_deps {
|
||||
echo "Installing Mininet dependencies"
|
||||
sudo aptitude install -y gcc make screen psmisc xterm ssh iperf iproute \
|
||||
python-setuptools python-networkx
|
||||
$install gcc make screen psmisc xterm ssh iperf iproute \
|
||||
python-setuptools python-networkx cgroup-bin ethtool
|
||||
|
||||
if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then
|
||||
echo "Upgrading networkx to avoid deprecation warning"
|
||||
@@ -124,16 +145,14 @@ function mn_deps {
|
||||
|
||||
# The following will cause a full OF install, covering:
|
||||
# -user switch
|
||||
# -dissector
|
||||
# The instructions below are an abbreviated version from
|
||||
# http://www.openflowswitch.org/wk/index.php/Debian_Install
|
||||
# ... modified to use Debian Lenny rather than unstable.
|
||||
function of {
|
||||
echo "Installing OpenFlow and its tools..."
|
||||
|
||||
echo "Installing OpenFlow reference implementation..."
|
||||
cd ~/
|
||||
sudo apt-get install -y git-core automake m4 pkg-config libtool \
|
||||
make libc6-dev autoconf autotools-dev gcc
|
||||
$install git-core autoconf automake autotools-dev pkg-config \
|
||||
make gcc libtool libc6-dev
|
||||
git clone git://openflowswitch.org/openflow.git
|
||||
cd ~/openflow
|
||||
|
||||
@@ -146,19 +165,9 @@ function of {
|
||||
make
|
||||
sudo make install
|
||||
|
||||
# Install dissector:
|
||||
sudo apt-get install -y wireshark libgtk2.0-dev
|
||||
cd ~/openflow/utilities/wireshark_dissectors/openflow
|
||||
make
|
||||
sudo make install
|
||||
|
||||
# Copy coloring rules: OF is white-on-blue:
|
||||
mkdir -p ~/.wireshark
|
||||
cp ~/mininet/util/colorfilters ~/.wireshark
|
||||
|
||||
# Remove avahi-daemon, which may cause unwanted discovery packets to be
|
||||
# sent during tests, near link status changes:
|
||||
sudo apt-get remove -y avahi-daemon
|
||||
# sent during tests, near link status changes:
|
||||
$remove avahi-daemon
|
||||
|
||||
# Disable IPv6. Add to /etc/modprobe.d/blacklist:
|
||||
if [ "$DIST" = "Ubuntu" ]; then
|
||||
@@ -167,46 +176,146 @@ function of {
|
||||
BLACKLIST=/etc/modprobe.d/blacklist
|
||||
fi
|
||||
sudo sh -c "echo 'blacklist net-pf-10\nblacklist ipv6' >> $BLACKLIST"
|
||||
cd ~
|
||||
}
|
||||
|
||||
function wireshark {
|
||||
echo "Installing Wireshark dissector..."
|
||||
|
||||
sudo apt-get install -y wireshark libgtk2.0-dev
|
||||
|
||||
if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" != "10.04" ]; then
|
||||
# Install newer version
|
||||
sudo apt-get install -y scons mercurial libglib2.0-dev
|
||||
sudo apt-get install -y libwiretap-dev libwireshark-dev
|
||||
cd ~
|
||||
hg clone https://bitbucket.org/onlab/of-dissector
|
||||
cd of-dissector/src
|
||||
export WIRESHARK=/usr/include/wireshark
|
||||
scons
|
||||
# libwireshark0/ on 11.04; libwireshark1/ on later
|
||||
WSDIR=`ls -d /usr/lib/wireshark/libwireshark* | head -1`
|
||||
WSPLUGDIR=$WSDIR/plugins/
|
||||
sudo cp openflow.so $WSPLUGDIR
|
||||
echo "Copied openflow plugin to $WSPLUGDIR"
|
||||
else
|
||||
# Install older version from reference source
|
||||
cd ~/openflow/utilities/wireshark_dissectors/openflow
|
||||
make
|
||||
sudo make install
|
||||
fi
|
||||
|
||||
# Copy coloring rules: OF is white-on-blue:
|
||||
mkdir -p ~/.wireshark
|
||||
cp ~/mininet/util/colorfilters ~/.wireshark
|
||||
}
|
||||
|
||||
|
||||
# Install Open vSwitch
|
||||
# Instructions derived from OVS INSTALL, INSTALL.OpenFlow and README files.
|
||||
function ovs {
|
||||
echo "Installing Open vSwitch..."
|
||||
|
||||
if [ "$DIST" = "Debian" ] && [ "$CODENAME" == "lenny" ]; then
|
||||
sudo aptitude -y install pkg-config gcc make git-core python-dev libssl-dev
|
||||
# Install Autoconf 2.63+ backport from Debian Backports repo:
|
||||
# Instructions from http://backports.org/dokuwiki/doku.php?id=instructions
|
||||
sudo su -c "echo 'deb http://www.backports.org/debian lenny-backports main contrib non-free' >> /etc/apt/sources.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get -y --force-yes install debian-backports-keyring
|
||||
sudo apt-get -y --force-yes -t lenny-backports install autoconf
|
||||
# Required for module build/dkms install
|
||||
$install $KERNEL_HEADERS
|
||||
|
||||
# First see if we have packages
|
||||
# XXX wget -c seems to fail from github/amazon s3
|
||||
cd /tmp
|
||||
if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME; then
|
||||
$install patch dkms fakeroot python-argparse
|
||||
tar xf $OVS_PACKAGE_NAME
|
||||
orig=`tar tf $OVS_PACKAGE_NAME`
|
||||
# Now install packages in reasonable dependency order
|
||||
order='dkms common pki openvswitch-switch brcompat controller'
|
||||
pkgs=""
|
||||
for p in $order; do
|
||||
pkg=`echo "$orig" | grep $p`
|
||||
# Annoyingly, things seem to be missing without this flag
|
||||
$pkginst --force-confmiss $pkg
|
||||
done
|
||||
# Switch can run on its own, but
|
||||
# Mininet should control the controller
|
||||
if [ -e /etc/init.d/openvswitch-controller ]; then
|
||||
if sudo service openvswitch-controller stop; then
|
||||
echo "Stopped running controller"
|
||||
fi
|
||||
sudo update-rc.d openvswitch-controller disable
|
||||
fi
|
||||
echo "Done (hopefully) installing packages"
|
||||
cd ~
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$DIST" = "Ubuntu" ]; then
|
||||
sudo apt-get -y install $KERNEL_HEADERS
|
||||
# Otherwise try distribution's OVS packages
|
||||
if [ "$DIST" = "Ubuntu" ] && [ `echo "$RELEASE >= 11.10" | bc` = 1 ]; then
|
||||
if ! dpkg --get-selections | grep openvswitch-datapath; then
|
||||
# If you've already installed a datapath, assume you
|
||||
# know what you're doing and don't need dkms datapath.
|
||||
# Otherwise, install it.
|
||||
$install openvswitch-datapath-dkms
|
||||
fi
|
||||
if $install openvswitch-switch openvswitch-controller; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
$install pkg-config gcc make python-dev libssl-dev libtool
|
||||
|
||||
if [ "$DIST" = "Debian" ]; then
|
||||
if [ "$CODENAME" = "lenny" ]; then
|
||||
$install git-core
|
||||
# Install Autoconf 2.63+ backport from Debian Backports repo:
|
||||
# Instructions from http://backports.org/dokuwiki/doku.php?id=instructions
|
||||
sudo su -c "echo 'deb http://www.backports.org/debian lenny-backports main contrib non-free' >> /etc/apt/sources.list"
|
||||
sudo apt-get update
|
||||
sudo apt-get -y --force-yes install debian-backports-keyring
|
||||
sudo apt-get -y --force-yes -t lenny-backports install autoconf
|
||||
fi
|
||||
else
|
||||
$install git
|
||||
fi
|
||||
|
||||
# Install OVS from release
|
||||
cd ~/
|
||||
git clone git://openvswitch.org/openvswitch
|
||||
git clone git://openvswitch.org/openvswitch $OVS_SRC
|
||||
cd $OVS_SRC
|
||||
git checkout $OVS_RELEASE
|
||||
git checkout $OVS_TAG
|
||||
./boot.sh
|
||||
BUILDDIR=/lib/modules/${KERNEL_NAME}/build
|
||||
if [ ! -e $BUILDDIR ]; then
|
||||
echo "Creating build sdirectory $BUILDDIR"
|
||||
sudo mkdir -p $BUILDDIR
|
||||
fi
|
||||
opts="--with-linux=$BUILDDIR"
|
||||
mkdir -p $OVS_BUILD
|
||||
cd $OVS_BUILD
|
||||
opts="--with-linux=$BUILDDIR"
|
||||
mkdir -p $OVS_BUILD
|
||||
cd $OVS_BUILD
|
||||
../configure $opts
|
||||
make
|
||||
sudo make install
|
||||
# openflowd is deprecated, but for now copy it in
|
||||
sudo cp tests/test-openflowd /usr/local/bin/ovs-openflowd
|
||||
|
||||
modprobe
|
||||
}
|
||||
|
||||
function remove_ovs {
|
||||
pkgs=`dpkg --get-selections | grep openvswitch | awk '{ print $1;}'`
|
||||
echo "Removing existing Open vSwitch packages:"
|
||||
echo $pkgs
|
||||
if ! $remove $pkgs; then
|
||||
echo "Not all packages removed correctly"
|
||||
fi
|
||||
# For some reason this doesn't happen
|
||||
if scripts=`ls /etc/init.d/*openvswitch* 2>/dev/null`; then
|
||||
echo $scripts
|
||||
for s in $scripts; do
|
||||
s=$(basename $s)
|
||||
echo SCRIPT $s
|
||||
sudo service $s stop
|
||||
sudo rm -f /etc/init.d/$s
|
||||
sudo update-rc.d -f $s remove
|
||||
done
|
||||
fi
|
||||
echo "Done removing OVS"
|
||||
}
|
||||
|
||||
# Install NOX with tutorial files
|
||||
@@ -214,17 +323,17 @@ function nox {
|
||||
echo "Installing NOX w/tutorial files..."
|
||||
|
||||
# Install NOX deps:
|
||||
sudo apt-get -y install autoconf automake g++ libtool python python-twisted \
|
||||
$install autoconf automake g++ libtool python python-twisted \
|
||||
swig libssl-dev make
|
||||
if [ "$DIST" = "Debian" ]; then
|
||||
sudo apt-get -y install libboost1.35-dev
|
||||
$install libboost1.35-dev
|
||||
elif [ "$DIST" = "Ubuntu" ]; then
|
||||
sudo apt-get -y install python-dev libboost-dev
|
||||
sudo apt-get -y install libboost-filesystem-dev
|
||||
sudo apt-get -y install libboost-test-dev
|
||||
$install python-dev libboost-dev
|
||||
$install libboost-filesystem-dev
|
||||
$install libboost-test-dev
|
||||
fi
|
||||
# Install NOX optional deps:
|
||||
sudo apt-get install -y libsqlite3-dev python-simplejson
|
||||
$install libsqlite3-dev python-simplejson
|
||||
|
||||
# Fetch NOX destiny
|
||||
cd ~/
|
||||
@@ -257,7 +366,7 @@ function oftest {
|
||||
echo "Installing oftest..."
|
||||
|
||||
# Install deps:
|
||||
sudo apt-get install -y tcpdump python-scapy
|
||||
$install tcpdump python-scapy
|
||||
|
||||
# Install oftest:
|
||||
cd ~/
|
||||
@@ -271,7 +380,7 @@ function oftest {
|
||||
function cbench {
|
||||
echo "Installing cbench..."
|
||||
|
||||
sudo apt-get install -y libsnmp-dev libpcap-dev
|
||||
$install libsnmp-dev libpcap-dev
|
||||
cd ~/
|
||||
git clone git://openflow.org/oflops.git
|
||||
cd oflops
|
||||
@@ -290,13 +399,13 @@ function other {
|
||||
|
||||
# Install tcpdump and tshark, cmd-line packet dump tools. Also install gitk,
|
||||
# a graphical git history viewer.
|
||||
sudo apt-get install -y tcpdump tshark gitk
|
||||
$install tcpdump tshark gitk
|
||||
|
||||
# Install common text editors
|
||||
sudo apt-get install -y vim nano emacs
|
||||
$install vim nano emacs
|
||||
|
||||
# Install NTP
|
||||
sudo apt-get install -y ntp
|
||||
$install ntp
|
||||
|
||||
# Set git to colorize everything.
|
||||
git config --global color.diff auto
|
||||
@@ -331,8 +440,8 @@ function all {
|
||||
kernel
|
||||
mn_deps
|
||||
of
|
||||
wireshark
|
||||
ovs
|
||||
modprobe
|
||||
nox
|
||||
oftest
|
||||
cbench
|
||||
@@ -386,10 +495,12 @@ function usage {
|
||||
printf -- ' -f: install open(F)low\n' >&2
|
||||
printf -- ' -h: print this (H)elp message\n' >&2
|
||||
printf -- ' -k: install new (K)ernel\n' >&2
|
||||
printf -- ' -m: install Open vSwitch kernel (M)odule\n' >&2
|
||||
printf -- ' -m: install Open vSwitch kernel (M)odule from source dir\n' >&2
|
||||
printf -- ' -n: install mini(N)et dependencies + core files\n' >&2
|
||||
printf -- ' -r: remove existing Open vSwitch packages\n' >&2
|
||||
printf -- ' -t: install o(T)her stuff\n' >&2
|
||||
printf -- ' -v: install open (V)switch\n' >&2
|
||||
printf -- ' -w: install OpenFlow (w)ireshark dissector\n' >&2
|
||||
printf -- ' -x: install NO(X) OpenFlow controller\n' >&2
|
||||
printf -- ' -y: install (A)ll packages\n' >&2
|
||||
|
||||
@@ -400,7 +511,7 @@ if [ $# -eq 0 ]
|
||||
then
|
||||
all
|
||||
else
|
||||
while getopts 'abcdfhkmntvx' OPTION
|
||||
while getopts 'abcdfhkmnrtvwx' OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
a) all;;
|
||||
@@ -412,8 +523,10 @@ else
|
||||
k) kernel;;
|
||||
m) modprobe;;
|
||||
n) mn_deps;;
|
||||
r) remove_ovs;;
|
||||
t) other;;
|
||||
v) ovs;;
|
||||
w) wireshark;;
|
||||
x) nox;;
|
||||
?) usage;;
|
||||
esac
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is intended to install Mininet into
|
||||
# a brand-new Ubuntu (10.04 or 11.10) virtual machine,
|
||||
# to create a fully usable "tutorial" VM.
|
||||
|
||||
set -e
|
||||
sudo sh -c 'cat >> /etc/sudoers' <<EOF
|
||||
openflow ALL=NOPASSWD: ALL
|
||||
EOF
|
||||
sudo sed -i -e 's/Default/#Default/' /etc/sudoers
|
||||
sudo sed -i -e 's/ubuntu/mininet-vm/' /etc/hostname
|
||||
sudo sed -i -e 's/ubuntu/mininet-vm/g' /etc/hosts
|
||||
sudo hostname `cat /etc/hostname`
|
||||
sudo sed -i -e 's/quiet splash/text/' /etc/default/grub
|
||||
sudo update-grub
|
||||
sudo sed -i -e 's/us.archive.ubuntu.com/mirrors.kernel.org/' \
|
||||
/etc/apt/sources.list
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install git-core openssh-server
|
||||
git clone git://github.com/mininet/mininet
|
||||
cd mininet
|
||||
# Check out branch for cs244
|
||||
git checkout -b cs244 origin/class/cs244
|
||||
cd
|
||||
time mininet/util/install.sh
|
||||
if ! grep NOX_CORE_DIR .bashrc; then
|
||||
echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc
|
||||
fi
|
||||
echo <<EOF
|
||||
You may need to reboot and then:
|
||||
sudo dpkg-reconfigure openvswitch-datapath-dkms
|
||||
sudo service openvswitch-switch start
|
||||
EOF
|
||||
|
||||
|
||||
Reference in New Issue
Block a user