Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 993b73c1be |
@@ -41,19 +41,16 @@ load-plugins=
|
||||
# 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=pointless-except, invalid-name, super-init-not-called, fixme, star-args,
|
||||
too-many-instance-attributes, too-few-public-methods, too-many-arguments,
|
||||
too-many-locals, too-many-public-methods, duplicate-code, bad-whitespace,
|
||||
locally-disabled
|
||||
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=colorized
|
||||
msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
|
||||
|
||||
# Include message's id in output
|
||||
# Include message's id in outpu
|
||||
include-ids=yes
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
@@ -194,7 +191,7 @@ additional-builtins=
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp,build
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
|
||||
# checks for sign of poor/misdesign:
|
||||
@@ -267,8 +264,7 @@ int-import-graph=
|
||||
max-line-length=80
|
||||
|
||||
# Maximum number of lines in a module
|
||||
# XXX 1500 -> 4000 for miniedit.py
|
||||
max-module-lines=4000
|
||||
max-module-lines=1500
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
|
||||
+1
-12
@@ -7,43 +7,32 @@ or send a pull request.
|
||||
|
||||
Contributors include:
|
||||
|
||||
Mininet Core Team (and alumni)
|
||||
Mininet Core Team
|
||||
|
||||
Bob Lantz
|
||||
Brandon Heller
|
||||
Nikhil Handigol
|
||||
Vimal Jeyakumar
|
||||
Brian O'Connor
|
||||
Cody Burkard
|
||||
|
||||
Additional Mininet Contributors
|
||||
|
||||
Tomasz Buchert
|
||||
Gustavo Pantuza Coelho Pinto
|
||||
Fernando Cappi
|
||||
Ryan Cox
|
||||
Shaun Crampton
|
||||
David Erickson
|
||||
Glen Gibb
|
||||
Andrew Ferguson
|
||||
Eder Leao Fernandes
|
||||
Gregory Gee
|
||||
Jon Hall
|
||||
Vitaly Ivanov
|
||||
Rich Lane
|
||||
Zi Shen Lim
|
||||
Murphy McCauley
|
||||
José Pedro Oliveira
|
||||
James Page
|
||||
Rich Lane
|
||||
Rémy Léone
|
||||
Angad Singh
|
||||
Piyush Srivastava
|
||||
Ed Swierk
|
||||
Darshan Thaker
|
||||
Andreas Wundsam
|
||||
Isaku Yamahata
|
||||
Baohua Yang
|
||||
|
||||
Thanks also to everyone who has submitted issues and pull
|
||||
requests on github, and to our friendly mininet-discuss
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Mininet Installation/Configuration Notes
|
||||
----------------------------------------
|
||||
|
||||
Mininet 2.2.0
|
||||
Mininet 2.1.0+
|
||||
---
|
||||
|
||||
The supported installation methods for Mininet are 1) using a
|
||||
@@ -53,27 +53,19 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
Note that the above git command will check out the latest and greatest
|
||||
Mininet (which we recommend!) If you want to run the last tagged/released
|
||||
version of Mininet, you can look at the release tags using
|
||||
version of Mininet, use:
|
||||
|
||||
cd mininet
|
||||
git tag
|
||||
git clone git://github.com/mininet/mininet
|
||||
git checkout -b 2.1.0 2.1.0
|
||||
|
||||
and then
|
||||
|
||||
git checkout <release tag>
|
||||
|
||||
where <release tag> is the release you want to check out.
|
||||
|
||||
If you are running Ubuntu, Debian, or Fedora, you may be able to use
|
||||
our handy `install.sh` script, which is in `mininet/util`.
|
||||
If you are running Ubuntu, you may be able to use our handy
|
||||
`install.sh` script, which is in `mininet/util`.
|
||||
|
||||
*WARNING: USE AT YOUR OWN RISK!*
|
||||
|
||||
`install.sh` is a bit intrusive and may possibly damage your OS
|
||||
and/or home directory, by creating/modifying several directories
|
||||
such as `mininet`, `openflow`, `oftest`, `pox`, etc.. We recommend
|
||||
trying it in a VM before trying it on a system you use from day to day.
|
||||
|
||||
such as `mininet`, `openflow`, `oftest`, `pox`, etc..
|
||||
Although we hope it won't do anything completely terrible, you may
|
||||
want to look at the script before you run it, and you should make
|
||||
sure your system and home directory are backed up just in case!
|
||||
@@ -161,8 +153,10 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
* A Linux kernel compiled with network namespace support enabled
|
||||
|
||||
* An compatible software switch such as Open vSwitch or
|
||||
the Linux bridge.
|
||||
* An OpenFlow implementation (either the reference user or kernel
|
||||
space implementations, or Open vSwitch.) Appropriate kernel
|
||||
modules (e.g. tun and ofdatapath for the reference kernel
|
||||
implementation) must be loaded.
|
||||
|
||||
* Python, `bash`, `ping`, `iperf`, etc.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Mininet 2.2.0 License
|
||||
Mininet 2.1.0+ License
|
||||
|
||||
Copyright (c) 2013-2014 Open Networking Laboratory
|
||||
Copyright (c) 2013 Open Networking Laboratory
|
||||
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
|
||||
The Leland Stanford Junior University
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ BIN = $(MN)
|
||||
PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
|
||||
MNEXEC = mnexec
|
||||
MANPAGES = mn.1 mnexec.1
|
||||
P8IGN = E251,E201,E302,E202,E126,E127,E203,E226
|
||||
P8IGN = E251,E201,E302,E202
|
||||
BINDIR = /usr/bin
|
||||
MANDIR = /usr/share/man/man1
|
||||
DOCDIRS = doc/html doc/latex
|
||||
@@ -24,8 +24,7 @@ codecheck: $(PYSRC)
|
||||
util/versioncheck.py
|
||||
pyflakes $(PYSRC)
|
||||
pylint --rcfile=.pylint $(PYSRC)
|
||||
# Exclude miniedit from pep8 checking for now
|
||||
pep8 --repeat --ignore=$(P8IGN) `ls $(PYSRC) | grep -v miniedit.py`
|
||||
pep8 --repeat --ignore=$(P8IGN) $(PYSRC)
|
||||
|
||||
errcheck: $(PYSRC)
|
||||
-echo "Running check for errors only"
|
||||
@@ -37,11 +36,6 @@ test: $(MININET) $(TEST)
|
||||
mininet/test/test_nets.py
|
||||
mininet/test/test_hifi.py
|
||||
|
||||
slowtest: $(MININET)
|
||||
-echo "Running slower tests (walkthrough, examples)"
|
||||
mininet/test/test_walkthrough.py -v
|
||||
mininet/examples/test/runner.py -v
|
||||
|
||||
mnexec: mnexec.c $(MN) mininet/net.py
|
||||
cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@
|
||||
|
||||
@@ -51,7 +45,7 @@ install: $(MNEXEC) $(MANPAGES)
|
||||
python setup.py install
|
||||
|
||||
develop: $(MNEXEC) $(MANPAGES)
|
||||
# Perhaps we should link these as well
|
||||
# Perhaps we should link these as well
|
||||
install $(MNEXEC) $(BINDIR)
|
||||
install $(MANPAGES) $(MANDIR)
|
||||
python setup.py develop
|
||||
@@ -64,7 +58,7 @@ mn.1: $(MN)
|
||||
|
||||
mnexec.1: mnexec
|
||||
help2man -N -n "execution utility for Mininet." \
|
||||
-h "-h" -v "-v" --no-discard-stderr ./$< -o $@
|
||||
-h "-h" -v "-v" --no-discard-stderr ./$< -o $@
|
||||
|
||||
.PHONY: doc
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Mininet: Rapid Prototyping for Software Defined Networks
|
||||
|
||||
*The best way to emulate almost any network on your laptop!*
|
||||
|
||||
Mininet 2.2.0
|
||||
Version 2.1.0+
|
||||
|
||||
### What is Mininet?
|
||||
|
||||
@@ -66,47 +66,33 @@ Mininet includes:
|
||||
|
||||
`mn -c`
|
||||
|
||||
### New features in this release
|
||||
### New features in 2.1.0+
|
||||
|
||||
This release provides a number of bug fixes as well as
|
||||
Mininet 2.1.0+ provides a number of bug fixes as well as
|
||||
several new features, including:
|
||||
|
||||
* Improved OpenFlow 1.3 support
|
||||
* Convenient access to `Mininet()` as a dict of nodes
|
||||
* X11 tunneling (wireshark in Mininet hosts, finally!)
|
||||
* Accurate reflection of the `Mininet()` object in the CLI
|
||||
* Automatically detecting and adjusting resource limits
|
||||
* Automatic cleanup on failure of the `mn` command
|
||||
* Preliminary support for running OVS in user space mode
|
||||
* Preliminary support (`IVSSwitch()`) for the Indigo Virtual Switch
|
||||
* support for installing the OpenFlow 1.3 versions of the reference
|
||||
user switch and NOX from CPqD
|
||||
* The ability to import modules from `mininet.examples`
|
||||
|
||||
- `mn --switch ovs,protocols=openflow13` starts OVS in 1.3 mode
|
||||
- `install.sh -w` installs a 1.3-compatible Wireshark dissector using
|
||||
Loxigen
|
||||
- `install.sh -y` installs the Ryu 1.3-compatible controller
|
||||
We have provided several new examples (which can easily be
|
||||
imported to provide useful functionality) including:
|
||||
|
||||
* A new `nodelib.py` node library, and new `Node` types including
|
||||
`LinuxBridge`, `OVSBridge`, `LinuxRouter` (see `examples/`)
|
||||
and `NAT`
|
||||
|
||||
* A `--nat` option which connects a Mininet network to your LAN using NAT
|
||||
(For this to work correctly, Mininet's `--ipbase` subnet should not
|
||||
overlap with any external or internet IP addresses you wish to use)
|
||||
|
||||
* An improved MiniEdit GUI (`examples/miniedit.py`) - thanks to
|
||||
Gregory Gee
|
||||
|
||||
* Support for multiple `--custom` arguments to `mn`
|
||||
|
||||
* Experimental cluster support - consult the
|
||||
[documentation](http://docs.mininet.org) for details -
|
||||
as well as `examples/cluster.py` and an experimental `--cluster`
|
||||
option for topologies built with the default `Host` and `OVSSwitch`
|
||||
classes:
|
||||
|
||||
`mn --cluster localhost,server1,server2`
|
||||
* Modeling separate control and data networks: `mininet.examples.controlnet`
|
||||
* Connecting Mininet hosts the internet (or a LAN) using NAT: `mininet.examples.nat`
|
||||
* Creating per-host custom directories using bind mounts: `mininet.examples.bind`
|
||||
|
||||
Note that examples contain experimental features which might
|
||||
"graduate" into mainline Mininet in the future, but they should
|
||||
not be considered a stable part of the Mininet API!
|
||||
|
||||
A number of bugs have also been fixed, most notably multiple link
|
||||
support in `Topo()`. See github issues and the release notes on
|
||||
the Mininet wiki for additional information.
|
||||
|
||||
### Installation
|
||||
|
||||
See `INSTALL` for installation instructions and details.
|
||||
@@ -127,27 +113,21 @@ Mininet mailing list, `mininet-discuss` at:
|
||||
|
||||
<https://mailman.stanford.edu/mailman/listinfo/mininet-discuss>
|
||||
|
||||
### Join Us
|
||||
### Contributing
|
||||
|
||||
Mininet is an open source project and is currently hosted
|
||||
at <https://github.com/mininet>. You are encouraged to download
|
||||
the code, examine it, modify it, and submit bug reports, bug fixes,
|
||||
feature requests, new features and other issues and pull requests.
|
||||
Thanks to everyone who has contributed code to the Mininet project
|
||||
(see CONTRIBUTORS for more info!) It is because of everyone's
|
||||
hard work that Mininet continues to grow and improve.
|
||||
|
||||
### Enjoy Mininet
|
||||
Thanks to everyone who has contributed to the project
|
||||
(see CONTRIBUTORS for more info!)
|
||||
|
||||
Best wishes, and we look forward to seeing what you can do with
|
||||
Mininet to change the networking world!
|
||||
|
||||
The Mininet Core Team:
|
||||
### Credits
|
||||
|
||||
The Mininet 2.1.0+ Team:
|
||||
|
||||
* Bob Lantz
|
||||
* Brian O'Connor
|
||||
* Cody Burkard
|
||||
|
||||
Thanks again to all of the Mininet contributors, particularly Gregory
|
||||
Gee for his work on MiniEdit.
|
||||
|
||||
|
||||
@@ -25,16 +25,16 @@ from mininet.cli import CLI
|
||||
from mininet.log import lg, LEVELS, info, debug, warn, error
|
||||
from mininet.net import Mininet, MininetWithControlNet, VERSION
|
||||
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
||||
RYU, NOX, RemoteController, findController,
|
||||
DefaultController,
|
||||
UserSwitch, OVSSwitch, OVSBridge,
|
||||
RYU, NOX, RemoteController, DefaultController,
|
||||
UserSwitch, OVSSwitch,
|
||||
OVSLegacyKernelSwitch, IVSSwitch )
|
||||
from mininet.nodelib import LinuxBridge
|
||||
from mininet.link import Link, TCLink
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||
from mininet.topolib import TreeTopo, TorusTopo
|
||||
from mininet.util import customConstructor, splitArgs
|
||||
from mininet.util import custom, customConstructor
|
||||
from mininet.util import buildTopo
|
||||
from mininet.util import updateMininet
|
||||
|
||||
from functools import partial
|
||||
|
||||
@@ -42,7 +42,7 @@ from functools import partial
|
||||
from mininet.examples.cluster import ( MininetCluster, RemoteHost,
|
||||
RemoteOVSSwitch, RemoteLink,
|
||||
SwitchBinPlacer, RandomPlacer )
|
||||
from mininet.examples.clustercli import ClusterCLI
|
||||
from mininet.examples.clustercli import DemoCLI as ClusterCLI
|
||||
|
||||
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
|
||||
|
||||
@@ -55,29 +55,27 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
||||
'tree': TreeTopo,
|
||||
'torus': TorusTopo }
|
||||
|
||||
SWITCHDEF = 'default'
|
||||
SWITCHDEF = 'ovsk'
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovs': OVSSwitch,
|
||||
'ovsbr' : OVSBridge,
|
||||
'ovs': OVSSwitch,
|
||||
# Keep ovsk for compatibility with 2.0
|
||||
'ovsk': OVSSwitch,
|
||||
'ovsl': OVSLegacyKernelSwitch,
|
||||
'ivs': IVSSwitch,
|
||||
'lxbr': LinuxBridge,
|
||||
'default': OVSSwitch }
|
||||
'lxbr': LinuxBridge }
|
||||
|
||||
HOSTDEF = 'proc'
|
||||
HOSTS = { 'proc': Host,
|
||||
'rt': partial( CPULimitedHost, sched='rt' ),
|
||||
'cfs': partial( CPULimitedHost, sched='cfs' ) }
|
||||
'rt': custom( CPULimitedHost, sched='rt' ),
|
||||
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
||||
|
||||
CONTROLLERDEF = 'default'
|
||||
CONTROLLERS = { 'ref': Controller,
|
||||
'ovsc': OVSController,
|
||||
'nox': NOX,
|
||||
'remote': RemoteController,
|
||||
'default': DefaultController,
|
||||
'ryu': RYU,
|
||||
'default': DefaultController, # Note: replaced below
|
||||
'none': lambda name: None }
|
||||
|
||||
LINKDEF = 'default'
|
||||
@@ -119,7 +117,6 @@ def version( *_args ):
|
||||
print "%s" % VERSION
|
||||
exit()
|
||||
|
||||
|
||||
class MininetRunner( object ):
|
||||
"Build, setup, and run Mininet."
|
||||
|
||||
@@ -133,29 +130,6 @@ class MininetRunner( object ):
|
||||
self.setup()
|
||||
self.begin()
|
||||
|
||||
def custom( self, _option, _opt_str, value, _parser ):
|
||||
"""Parse custom file and add params.
|
||||
option: option e.g. --custom
|
||||
opt_str: option string e.g. --custom
|
||||
value: the value the follows the option
|
||||
parser: option parser instance"""
|
||||
files = []
|
||||
if os.path.isfile( value ):
|
||||
# Accept any single file (including those with commas)
|
||||
files.append( value )
|
||||
else:
|
||||
# Accept a comma-separated list of filenames
|
||||
files += value.split(',')
|
||||
|
||||
for fileName in files:
|
||||
customs = {}
|
||||
if os.path.isfile( fileName ):
|
||||
execfile( fileName, customs, customs )
|
||||
for name, val in customs.iteritems():
|
||||
self.setCustom( name, val )
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
|
||||
def setCustom( self, name, value ):
|
||||
"Set custom parameters for MininetRunner."
|
||||
if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
|
||||
@@ -169,23 +143,26 @@ class MininetRunner( object ):
|
||||
# Add or modify global variable or class
|
||||
globals()[ name ] = value
|
||||
|
||||
def setNat( self, _option, opt_str, value, parser ):
|
||||
"Set NAT option(s)"
|
||||
assert self # satisfy pylint
|
||||
parser.values.nat = True
|
||||
# first arg, first char != '-'
|
||||
if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-':
|
||||
value = parser.rargs.pop( 0 )
|
||||
_, args, kwargs = splitArgs( opt_str + ',' + value )
|
||||
parser.values.nat_args = args
|
||||
parser.values.nat_kwargs = kwargs
|
||||
def parseCustomFile( self, fileName ):
|
||||
"Parse custom file and add params before parsing cmd-line options."
|
||||
customs = {}
|
||||
if os.path.isfile( fileName ):
|
||||
execfile( fileName, customs, customs )
|
||||
for name, val in customs.iteritems():
|
||||
self.setCustom( name, val )
|
||||
else:
|
||||
parser.values.nat_args = []
|
||||
parser.values.nat_kwargs = {}
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
|
||||
def parseArgs( self ):
|
||||
"""Parse command-line args and return options object.
|
||||
returns: opts parse options dict"""
|
||||
if '--custom' in sys.argv:
|
||||
index = sys.argv.index( '--custom' )
|
||||
if len( sys.argv ) > index + 1:
|
||||
filename = sys.argv[ index + 1 ]
|
||||
self.parseCustomFile( filename )
|
||||
else:
|
||||
raise Exception( 'Custom file name not found' )
|
||||
|
||||
desc = ( "The %prog utility creates Mininet network from the\n"
|
||||
"command line. It can create parametrized topologies,\n"
|
||||
@@ -203,12 +180,9 @@ class MininetRunner( object ):
|
||||
|
||||
opts.add_option( '--clean', '-c', action='store_true',
|
||||
default=False, help='clean and exit' )
|
||||
opts.add_option( '--custom', action='callback',
|
||||
callback=self.custom,
|
||||
type='string',
|
||||
help='read custom classes or params from .py file(s)'
|
||||
)
|
||||
|
||||
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 ) )
|
||||
@@ -237,16 +211,13 @@ class MininetRunner( object ):
|
||||
opts.add_option( '--pin', action='store_true',
|
||||
default=False, help="pin hosts to CPU cores "
|
||||
"(requires --host cfs or --host rt)" )
|
||||
opts.add_option( '--nat', action='callback', callback=self.setNat,
|
||||
help="adds a NAT to the topology that"
|
||||
" connects Mininet hosts to the physical network."
|
||||
" Warning: This may route any traffic on the machine"
|
||||
" that uses Mininet's"
|
||||
" IP subnet into the Mininet network."
|
||||
" If you need to change"
|
||||
" Mininet's IP subnet, see the --ipbase option." )
|
||||
opts.add_option( '--version', action='callback', callback=version,
|
||||
help='prints the version and exits' )
|
||||
opts.add_option( '--nat', action='store_true',
|
||||
default=False, help="adds a NAT to the topology "
|
||||
"that connects Mininet to the physical network" )
|
||||
opts.add_option( '--version', action='callback', callback=version )
|
||||
opts.add_option( '--upgrade', action='callback', callback=updateMininet,
|
||||
callback_kwargs={ 'version':VERSION },
|
||||
help='check for Mininet updates' )
|
||||
opts.add_option( '--cluster', type='string', default=None,
|
||||
metavar='server1,server2...',
|
||||
help=( 'run on multiple servers (experimental!)' ) )
|
||||
@@ -274,35 +245,17 @@ class MininetRunner( object ):
|
||||
% self.options.verbosity )
|
||||
lg.setLogLevel( self.options.verbosity )
|
||||
|
||||
# Maybe we'll reorganize this someday...
|
||||
# pylint: disable=too-many-branches,too-many-statements
|
||||
|
||||
def begin( self ):
|
||||
"Create and run mininet."
|
||||
|
||||
global CLI
|
||||
|
||||
if self.options.clean:
|
||||
cleanup()
|
||||
exit()
|
||||
|
||||
start = time.time()
|
||||
|
||||
if self.options.controller == 'default':
|
||||
# Update default based on available controllers
|
||||
CONTROLLERS[ 'default' ] = findController()
|
||||
if CONTROLLERS[ 'default' ] is None:
|
||||
if self.options.switch == 'default':
|
||||
info( '*** No default OpenFlow controller found '
|
||||
'for default switch!\n' )
|
||||
info( '*** Falling back to OVS Bridge\n' )
|
||||
self.options.switch = 'ovsbr'
|
||||
self.options.controller = 'none'
|
||||
elif self.options.switch in ( 'ovsbr', 'lxbr' ):
|
||||
self.options.controller = 'none'
|
||||
else:
|
||||
raise Exception( "Could not find a default controller "
|
||||
"for switch %s" %
|
||||
self.options.switch )
|
||||
|
||||
topo = buildTopo( TOPOS, self.options.topo )
|
||||
switch = customConstructor( SWITCHES, self.options.switch )
|
||||
host = customConstructor( HOSTS, self.options.host )
|
||||
@@ -328,11 +281,11 @@ class MininetRunner( object ):
|
||||
print "Please specify --innamespace OR --cluster"
|
||||
exit()
|
||||
Net = MininetWithControlNet if inNamespace else Mininet
|
||||
cli = ClusterCLI if cluster else CLI
|
||||
if cluster:
|
||||
warn( '*** WARNING: Experimental cluster mode!\n'
|
||||
'*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
|
||||
'*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
|
||||
host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
|
||||
CLI = ClusterCLI
|
||||
Net = partial( MininetCluster, servers=cluster.split( ',' ),
|
||||
placement=PLACEMENT[ self.options.placement ] )
|
||||
|
||||
@@ -345,13 +298,12 @@ class MininetRunner( object ):
|
||||
autoStaticArp=arp, autoPinCpus=pin,
|
||||
listenPort=listenPort )
|
||||
|
||||
if self.options.ensure_value( 'nat', False ):
|
||||
nat = mn.addNAT( *self.options.nat_args,
|
||||
**self.options.nat_kwargs )
|
||||
if self.options.nat:
|
||||
nat = mn.addNAT()
|
||||
nat.configDefault()
|
||||
|
||||
if self.options.pre:
|
||||
cli( mn, script=self.options.pre )
|
||||
CLI( mn, script=self.options.pre )
|
||||
|
||||
test = self.options.test
|
||||
test = ALTSPELLING.get( test, test )
|
||||
@@ -366,13 +318,13 @@ class MininetRunner( object ):
|
||||
mn.ping()
|
||||
mn.iperf()
|
||||
elif test == 'cli':
|
||||
cli( mn )
|
||||
CLI( mn )
|
||||
elif test != 'build':
|
||||
mn.waitConnected()
|
||||
getattr( mn, test )()
|
||||
|
||||
if self.options.post:
|
||||
cli( mn, script=self.options.post )
|
||||
CLI( mn, script=self.options.post )
|
||||
|
||||
mn.stop()
|
||||
|
||||
|
||||
+3
-43
@@ -11,31 +11,6 @@ Mininet's Python API.
|
||||
This example uses Mininet's medium-level API to create an sshd
|
||||
process running in a namespace. Doesn't use OpenFlow.
|
||||
|
||||
#### bind.py:
|
||||
|
||||
This example shows how you can create private directories for each
|
||||
node in a Mininet topology.
|
||||
|
||||
#### cluster.py:
|
||||
|
||||
This example contains all of the code for experimental cluster
|
||||
edition. Remote classes and MininetCluster can be imported from
|
||||
here to create a topology with nodes on remote machines.
|
||||
|
||||
#### clusterSanity.py:
|
||||
|
||||
This example runs cluster edition locally as a sanity check to test
|
||||
basic functionality.
|
||||
|
||||
#### clustercli.py:
|
||||
|
||||
This example contains a CLI for experimental cluster edition.
|
||||
|
||||
#### clusterdemo.py:
|
||||
|
||||
This example is a basic demo of cluster edition on 3 servers with
|
||||
a tree topology of depth 3 and fanout 3.
|
||||
|
||||
#### consoles.py:
|
||||
|
||||
This example creates a grid of console windows, one for each node,
|
||||
@@ -72,11 +47,6 @@ topology object) and adding nodes to it.
|
||||
This example shows how to add an interface (for example a real
|
||||
hardware interface) to a network after the network is created.
|
||||
|
||||
#### intfoptions.py:
|
||||
|
||||
This example reconfigures a TCIntf during runtime with different
|
||||
traffic control commands to test bandwidth, loss, and delay.
|
||||
|
||||
#### limit.py:
|
||||
|
||||
This example shows how to use link and CPU limits.
|
||||
@@ -86,7 +56,7 @@ This example shows how to use link and CPU limits.
|
||||
This example shows how to create a custom topology programatically
|
||||
by subclassing Topo, and how to run a series of tests on it.
|
||||
|
||||
#### linuxrouter.py:
|
||||
### linuxrouter.py:
|
||||
|
||||
This example shows how to create and configure a router in Mininet
|
||||
that uses Linux IP forwarding.
|
||||
@@ -95,16 +65,11 @@ that uses Linux IP forwarding.
|
||||
|
||||
This example demonstrates creating a network via a graphical editor.
|
||||
|
||||
#### mobility.py:
|
||||
#### mobility.py
|
||||
|
||||
This example demonstrates detaching an interface from one switch and
|
||||
attaching it another as a basic way to move a host around a network.
|
||||
|
||||
#### multiLink.py:
|
||||
|
||||
This example demonstrates the creation of multiple links between
|
||||
nodes using a custom Topology class.
|
||||
|
||||
#### multiping.py:
|
||||
|
||||
This example demonstrates one method for
|
||||
@@ -124,12 +89,7 @@ This example shows how to connect a Mininet network to the Internet
|
||||
using NAT. It also answers the eternal question "why can't I ping
|
||||
`google.com`?"
|
||||
|
||||
#### natnet.py:
|
||||
|
||||
This example demonstrates how to create a network using a NAT node
|
||||
to connect hosts to the internet.
|
||||
|
||||
#### numberedports.py:
|
||||
#### numberedports.py
|
||||
|
||||
This example verifies the mininet ofport numbers match up to the ovs port numbers.
|
||||
It also verifies that the port numbers match up to the interface numbers
|
||||
|
||||
+14
-9
@@ -22,7 +22,7 @@ to temporary private directories. To do this, simply create a list of
|
||||
directories to be made private. A tmpfs will then be mounted on them.
|
||||
|
||||
You may use both temporary and persistent directories at the same
|
||||
time. In the following privateDirs string, each host will have a
|
||||
time. In the following privateDirs string, each host will have a
|
||||
persistent directory in the root filesystem at
|
||||
"/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also
|
||||
have a temporary private directory mounted on "/var/log".
|
||||
@@ -35,10 +35,10 @@ on '/var/mn'
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host
|
||||
from mininet.node import Host, HostWithPrivateDirs
|
||||
from mininet.cli import CLI
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.log import setLogLevel, info, debug
|
||||
|
||||
from functools import partial
|
||||
|
||||
@@ -48,16 +48,19 @@ from functools import partial
|
||||
def testHostWithPrivateDirs():
|
||||
"Test bind mounts"
|
||||
topo = SingleSwitchTopo( 10 )
|
||||
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
|
||||
( '/var/run', '/tmp/%(name)s/var/run' ),
|
||||
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
|
||||
( '/var/run', '/tmp/%(name)s/var/run' ),
|
||||
'/var/mn' ]
|
||||
host = partial( Host,
|
||||
host = partial( HostWithPrivateDirs,
|
||||
privateDirs=privateDirs )
|
||||
net = Mininet( topo=topo, host=host )
|
||||
net.start()
|
||||
directories = [ directory[ 0 ] if isinstance( directory, tuple )
|
||||
else directory for directory in privateDirs ]
|
||||
info( 'Private Directories:', directories, '\n' )
|
||||
directories = []
|
||||
for directory in privateDirs:
|
||||
directories.append( directory[ 0 ]
|
||||
if isinstance( directory, tuple )
|
||||
else directory )
|
||||
info( 'Private Directories:', directories, '\n' )
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
@@ -65,3 +68,5 @@ if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
testHostWithPrivateDirs()
|
||||
info( 'Done.\n')
|
||||
|
||||
|
||||
|
||||
+81
-107
@@ -83,11 +83,11 @@ from mininet.util import quietRun, makeIntfPair, errRun, retry
|
||||
from mininet.examples.clustercli import CLI
|
||||
from mininet.log import setLogLevel, debug, info, error
|
||||
|
||||
from signal import signal, SIGINT, SIG_IGN
|
||||
from signal import signal, SIGINT, SIGHUP, SIG_IGN
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
import os
|
||||
from random import randrange
|
||||
import sys
|
||||
from sys import exit
|
||||
import re
|
||||
|
||||
from distutils.version import StrictVersion
|
||||
@@ -112,65 +112,68 @@ class RemoteMixin( object ):
|
||||
'-o', 'BatchMode=yes',
|
||||
'-o', 'ForwardAgent=yes', '-tt' ]
|
||||
|
||||
def __init__( self, name, server='localhost', user=None, serverIP=None,
|
||||
controlPath=False, splitInit=False, **kwargs):
|
||||
def __init__( self, name, server=None, user=None, serverIP=None,
|
||||
controlPath='/tmp/mn-%r@%h:%p', splitInit=False, **kwargs):
|
||||
"""Instantiate a remote node
|
||||
name: name of remote node
|
||||
server: remote server (optional)
|
||||
user: user on remote server (optional)
|
||||
controlPath: specify shared ssh control path (optional)
|
||||
controlPath: ssh control path template (optional)
|
||||
splitInit: split initialization?
|
||||
**kwargs: see Node()"""
|
||||
# We connect to servers by IP address
|
||||
self.server = server if server else 'localhost'
|
||||
self.serverIP = ( serverIP if serverIP
|
||||
else self.findServerIP( self.server ) )
|
||||
if server == 'localhost':
|
||||
server = None
|
||||
self.server = server
|
||||
self.serverIP = serverIP if serverIP else self.findServerIP( server )
|
||||
self.user = user if user else self.findUser()
|
||||
if controlPath is True:
|
||||
# Set a default control path for shared SSH connections
|
||||
controlPath = '/tmp/mn-%r@%h:%p'
|
||||
self.controlPath = controlPath
|
||||
self.splitInit = splitInit
|
||||
if self.user and self.server != 'localhost':
|
||||
if self.user and self.server:
|
||||
self.dest = '%s@%s' % ( self.user, self.serverIP )
|
||||
else:
|
||||
self.dest = None
|
||||
self.controlPath = controlPath
|
||||
self.sshcmd = []
|
||||
if self.dest:
|
||||
self.sshcmd = [ 'sudo', '-E', '-u', self.user ] + self.sshbase
|
||||
if self.controlPath:
|
||||
self.sshcmd += [ '-o', 'ControlPath=' + self.controlPath,
|
||||
'-o', 'ControlMaster=auto',
|
||||
'-o', 'ControlPersist=' + '1' ]
|
||||
'-o', 'ControlMaster=auto' ]
|
||||
self.sshcmd = self.sshcmd + [ self.dest ]
|
||||
self.isRemote = True
|
||||
else:
|
||||
self.dest = None
|
||||
self.sshcmd = []
|
||||
self.isRemote = False
|
||||
# Satisfy pylint
|
||||
self.shell, self.pid = None, None
|
||||
self.splitInit = splitInit
|
||||
super( RemoteMixin, self ).__init__( name, **kwargs )
|
||||
|
||||
@staticmethod
|
||||
def findUser():
|
||||
"Try to return logged-in (usually non-root) user"
|
||||
return (
|
||||
try:
|
||||
# If we're running sudo
|
||||
os.environ.get( 'SUDO_USER', False ) or
|
||||
# Logged-in user (if we have a tty)
|
||||
( quietRun( 'who am i' ).split() or [ False ] )[ 0 ] or
|
||||
# Give up and return effective user
|
||||
quietRun( 'whoami' ) )
|
||||
return os.environ[ 'SUDO_USER' ]
|
||||
except:
|
||||
try:
|
||||
# Logged-in user (if we have a tty)
|
||||
return quietRun( 'who am i' ).split()[ 0 ]
|
||||
except:
|
||||
# Give up and return effective user
|
||||
return quietRun( 'whoami' )
|
||||
|
||||
# Determine IP address of local host
|
||||
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
|
||||
|
||||
@classmethod
|
||||
def findServerIP( cls, server ):
|
||||
def findServerIP( cls, server, intf='eth0' ):
|
||||
"Return our server's IP address"
|
||||
# First, check for an IP address
|
||||
ipmatch = cls._ipMatchRegex.findall( server )
|
||||
if ipmatch:
|
||||
return ipmatch[ 0 ]
|
||||
# Otherwise, look up remote server
|
||||
output = quietRun( 'getent ahostsv4 %s' % server )
|
||||
# Check for this server
|
||||
if not server:
|
||||
output = quietRun( 'ifconfig %s' % intf )
|
||||
# Otherwise, handle remote server
|
||||
else:
|
||||
# First, check for an IP address
|
||||
if server:
|
||||
ipmatch = cls._ipMatchRegex.findall( server )
|
||||
if ipmatch:
|
||||
return ipmatch[ 0 ]
|
||||
# Otherwise, look up remote server
|
||||
output = quietRun( 'getent ahostsv4 %s' % server )
|
||||
ips = cls._ipMatchRegex.findall( output )
|
||||
ip = ips[ 0 ] if ips else None
|
||||
return ip
|
||||
@@ -178,16 +181,15 @@ class RemoteMixin( object ):
|
||||
# Command support via shell process in namespace
|
||||
def startShell( self, *args, **kwargs ):
|
||||
"Start a shell process for running commands"
|
||||
if self.isRemote:
|
||||
if self.dest:
|
||||
kwargs.update( mnopts='-c' )
|
||||
super( RemoteMixin, self ).startShell( *args, **kwargs )
|
||||
# Optional split initialization
|
||||
self.sendCmd( 'echo $$' )
|
||||
if not self.splitInit:
|
||||
self.finishInit()
|
||||
if self.splitInit:
|
||||
self.sendCmd( 'echo $$' )
|
||||
else:
|
||||
self.pid = int( self.cmd( 'echo $$' ) )
|
||||
|
||||
def finishInit( self ):
|
||||
"Wait for split initialization to complete"
|
||||
self.pid = int( self.waitOutput() )
|
||||
|
||||
def rpopen( self, *cmd, **opts ):
|
||||
@@ -229,7 +231,7 @@ class RemoteMixin( object ):
|
||||
returns: Popen() object"""
|
||||
if type( cmd ) is str:
|
||||
cmd = cmd.split()
|
||||
if self.isRemote:
|
||||
if self.dest:
|
||||
if sudo:
|
||||
cmd = [ 'sudo', '-E' ] + cmd
|
||||
if tt:
|
||||
@@ -254,18 +256,9 @@ class RemoteMixin( object ):
|
||||
|
||||
def addIntf( self, *args, **kwargs ):
|
||||
"Override: use RemoteLink.moveIntf"
|
||||
return super( RemoteMixin,
|
||||
self).addIntf( *args,
|
||||
moveIntfFn=RemoteLink.moveIntf,
|
||||
**kwargs )
|
||||
return super( RemoteMixin, self).addIntf( *args,
|
||||
moveIntfFn=RemoteLink.moveIntf, **kwargs )
|
||||
|
||||
def cleanup( self ):
|
||||
"Help python collect its garbage."
|
||||
# Intfs may end up in root NS
|
||||
for intfName in self.intfNames():
|
||||
if self.name in intfName:
|
||||
self.rcmd( 'ip link del ' + intfName )
|
||||
self.shell = None
|
||||
|
||||
class RemoteNode( RemoteMixin, Node ):
|
||||
"A node on a remote server"
|
||||
@@ -279,23 +272,20 @@ class RemoteHost( RemoteNode ):
|
||||
|
||||
class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
|
||||
"Remote instance of Open vSwitch"
|
||||
|
||||
OVSVersions = {}
|
||||
|
||||
def isOldOVS( self ):
|
||||
"Is remote switch using an old OVS version?"
|
||||
cls = type( self )
|
||||
if self.server not in cls.OVSVersions:
|
||||
# pylint: disable=not-callable
|
||||
vers = self.cmd( 'ovs-vsctl --version' )
|
||||
# pylint: enable=not-callable
|
||||
cls.OVSVersions[ self.server ] = re.findall(
|
||||
r'\d+\.\d+', vers )[ 0 ]
|
||||
cls.OVSVersions[ self.server ] = re.findall( '\d+\.\d+', vers )[ 0 ]
|
||||
return ( StrictVersion( cls.OVSVersions[ self.server ] ) <
|
||||
StrictVersion( '1.10' ) )
|
||||
StrictVersion( '1.10' ) )
|
||||
|
||||
|
||||
|
||||
class RemoteLink( Link ):
|
||||
|
||||
"A RemoteLink is a link between nodes which may be on different servers"
|
||||
|
||||
def __init__( self, node1, node2, **kwargs ):
|
||||
@@ -307,7 +297,6 @@ class RemoteLink( Link ):
|
||||
self.tunnel = None
|
||||
kwargs.setdefault( 'params1', {} )
|
||||
kwargs.setdefault( 'params2', {} )
|
||||
self.cmd = None # satisfy pylint
|
||||
Link.__init__( self, node1, node2, **kwargs )
|
||||
|
||||
def stop( self ):
|
||||
@@ -323,18 +312,17 @@ class RemoteLink( Link ):
|
||||
(override this method [and possibly delete()]
|
||||
to change link type)"""
|
||||
node1, node2 = self.node1, self.node2
|
||||
server1 = getattr( node1, 'server', 'localhost' )
|
||||
server2 = getattr( node2, 'server', 'localhost' )
|
||||
if server1 == 'localhost' and server2 == 'localhost':
|
||||
server1 = getattr( node1, 'server', None )
|
||||
server2 = getattr( node2, 'server', None )
|
||||
if not server1 and not server2:
|
||||
# Local link
|
||||
return makeIntfPair( intfname1, intfname2, addr1, addr2 )
|
||||
elif server1 == server2:
|
||||
# Remote link on same remote server
|
||||
return makeIntfPair( intfname1, intfname2, addr1, addr2,
|
||||
runCmd=node1.rcmd )
|
||||
run=node1.rcmd )
|
||||
# Otherwise, make a tunnel
|
||||
self.tunnel = self.makeTunnel( node1, node2, intfname1, intfname2,
|
||||
addr1, addr2 )
|
||||
self.tunnel = self.makeTunnel( node1, node2, intfname1, intfname2, addr1, addr2 )
|
||||
return self.tunnel
|
||||
|
||||
@staticmethod
|
||||
@@ -348,23 +336,16 @@ class RemoteLink( Link ):
|
||||
cmd = 'ip link set %s netns %s' % ( intf, node.pid )
|
||||
node.rcmd( cmd )
|
||||
links = node.cmd( 'ip link show' )
|
||||
if not ' %s:' % intf in links:
|
||||
if not ( ' %s:' % intf ) in links:
|
||||
if printError:
|
||||
error( '*** Error: RemoteLink.moveIntf: ' + intf +
|
||||
' not successfully moved to ' + node.name + '\n' )
|
||||
' not successfully moved to ' + node.name + '\n' )
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def makeTunnel( self, node1, node2, intfname1, intfname2,
|
||||
addr1=None, addr2=None ):
|
||||
"Make a tunnel across switches on different servers"
|
||||
# We should never try to create a tunnel to ourselves!
|
||||
assert node1.server != 'localhost' or node2.server != 'localhost'
|
||||
# And we can't ssh into this server remotely as 'localhost',
|
||||
# so try again swappping node1 and node2
|
||||
if node2.server == 'localhost':
|
||||
return self.makeTunnel( node2, node1, intfname2, intfname1,
|
||||
addr2, addr1 )
|
||||
# 1. Create tap interfaces
|
||||
for node in node1, node2:
|
||||
# For now we are hard-wiring tap9, which we will rename
|
||||
@@ -388,21 +369,21 @@ class RemoteLink( Link ):
|
||||
if ch != '@':
|
||||
error( 'makeTunnel:\n',
|
||||
'Tunnel setup failed for',
|
||||
'%s:%s' % ( node1, node1.dest ), 'to',
|
||||
'%s:%s' % ( node1, node1.dest ), 'to',
|
||||
'%s:%s\n' % ( node2, node2.dest ),
|
||||
'command was:', cmd, '\n' )
|
||||
'command was:', cmd, '\n' )
|
||||
tunnel.terminate()
|
||||
tunnel.wait()
|
||||
error( ch + tunnel.stdout.read() )
|
||||
error( tunnel.stderr.read() )
|
||||
sys.exit( 1 )
|
||||
exit( 1 )
|
||||
# 3. Move interfaces if necessary
|
||||
for node in node1, node2:
|
||||
if node.inNamespace:
|
||||
retry( 3, .01, RemoteLink.moveIntf, 'tap9', node )
|
||||
# 4. Rename tap interfaces to desired names
|
||||
for node, intf, addr in ( ( node1, intfname1, addr1 ),
|
||||
( node2, intfname2, addr2 ) ):
|
||||
( node2, intfname2, addr2 ) ):
|
||||
if not addr:
|
||||
node.cmd( 'ip link set tap9 name', intf )
|
||||
else:
|
||||
@@ -429,9 +410,9 @@ class RemoteLink( Link ):
|
||||
|
||||
class Placer( object ):
|
||||
"Node placement algorithm for MininetCluster"
|
||||
|
||||
|
||||
def __init__( self, servers=None, nodes=None, hosts=None,
|
||||
switches=None, controllers=None, links=None ):
|
||||
switches=None, controllers=None, links=None ):
|
||||
"""Initialize placement object
|
||||
servers: list of servers
|
||||
nodes: list of all nodes
|
||||
@@ -450,9 +431,8 @@ class Placer( object ):
|
||||
|
||||
def place( self, node ):
|
||||
"Return server for a given node"
|
||||
assert self, node # satisfy pylint
|
||||
# Default placement: run locally
|
||||
return 'localhost'
|
||||
return None
|
||||
|
||||
|
||||
class RandomPlacer( Placer ):
|
||||
@@ -460,7 +440,6 @@ class RandomPlacer( Placer ):
|
||||
def place( self, nodename ):
|
||||
"""Random placement function
|
||||
nodename: node name"""
|
||||
assert nodename # please pylint
|
||||
# This may be slow with lots of servers
|
||||
return self.servers[ randrange( 0, len( self.servers ) ) ]
|
||||
|
||||
@@ -469,7 +448,7 @@ class RoundRobinPlacer( Placer ):
|
||||
"""Round-robin placement
|
||||
Note this will usually result in cross-server links between
|
||||
hosts and switches"""
|
||||
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
Placer.__init__( self, *args, **kwargs )
|
||||
self.next = 0
|
||||
@@ -477,7 +456,6 @@ class RoundRobinPlacer( Placer ):
|
||||
def place( self, nodename ):
|
||||
"""Round-robin placement function
|
||||
nodename: node name"""
|
||||
assert nodename # please pylint
|
||||
# This may be slow with lots of servers
|
||||
server = self.servers[ self.next ]
|
||||
self.next = ( self.next + 1 ) % len( self.servers )
|
||||
@@ -496,7 +474,7 @@ class SwitchBinPlacer( Placer ):
|
||||
self.sset = frozenset( self.switches )
|
||||
self.cset = frozenset( self.controllers )
|
||||
# Server and switch placement indices
|
||||
self.placement = self.calculatePlacement()
|
||||
self.placement = self.calculatePlacement()
|
||||
|
||||
@staticmethod
|
||||
def bin( nodes, servers ):
|
||||
@@ -563,7 +541,7 @@ class HostSwitchBinPlacer( Placer ):
|
||||
scount = len( self.servers )
|
||||
self.hbin = max( int( len( self.hosts ) / scount ), 1 )
|
||||
self.sbin = max( int( len( self.switches ) / scount ), 1 )
|
||||
self.cbin = max( int( len( self.controllers ) / scount ), 1 )
|
||||
self.cbin = max( int( len( self.controllers ) / scount ) , 1 )
|
||||
info( 'scount:', scount )
|
||||
info( 'bins:', self.hbin, self.sbin, self.cbin, '\n' )
|
||||
self.servdict = dict( enumerate( self.servers ) )
|
||||
@@ -571,7 +549,7 @@ class HostSwitchBinPlacer( Placer ):
|
||||
self.sset = frozenset( self.switches )
|
||||
self.cset = frozenset( self.controllers )
|
||||
self.hind, self.sind, self.cind = 0, 0, 0
|
||||
|
||||
|
||||
def place( self, nodename ):
|
||||
"""Simple placement algorithm:
|
||||
place nodes into evenly sized bins"""
|
||||
@@ -591,6 +569,7 @@ class HostSwitchBinPlacer( Placer ):
|
||||
return server
|
||||
|
||||
|
||||
|
||||
# The MininetCluster class is not strictly necessary.
|
||||
# However, it has several purposes:
|
||||
# 1. To set up ssh connection sharing/multiplexing
|
||||
@@ -617,8 +596,8 @@ class MininetCluster( Mininet ):
|
||||
'link': RemoteLink,
|
||||
'precheck': True }
|
||||
params.update( kwargs )
|
||||
servers = params.pop( 'servers', [ 'localhost' ] )
|
||||
servers = [ s if s else 'localhost' for s in servers ]
|
||||
servers = params.pop( 'servers', [ None ] )
|
||||
servers = [ s if s != 'localhost' else None for s in servers ]
|
||||
self.servers = servers
|
||||
self.serverIP = params.pop( 'serverIP', {} )
|
||||
if not self.serverIP:
|
||||
@@ -636,7 +615,6 @@ class MininetCluster( Mininet ):
|
||||
|
||||
def popen( self, cmd ):
|
||||
"Popen() for server connections"
|
||||
assert self # please pylint
|
||||
old = signal( SIGINT, SIG_IGN )
|
||||
conn = Popen( cmd, stdin=PIPE, stdout=PIPE, close_fds=True )
|
||||
signal( SIGINT, old )
|
||||
@@ -654,13 +632,13 @@ class MininetCluster( Mininet ):
|
||||
for server in self.servers:
|
||||
ip = self.serverIP[ server ]
|
||||
if not server or server == 'localhost':
|
||||
continue
|
||||
continue
|
||||
info( server, '' )
|
||||
dest = '%s@%s' % ( self.user, ip )
|
||||
cmd = [ 'sudo', '-E', '-u', self.user ]
|
||||
cmd += self.sshcmd + [ '-n', dest, 'sudo true' ]
|
||||
debug( ' '.join( cmd ), '\n' )
|
||||
_out, _err, code = errRun( cmd )
|
||||
out, err, code = errRun( cmd )
|
||||
if code != 0:
|
||||
error( '\nstartConnection: server connection check failed '
|
||||
'to %s using command:\n%s\n'
|
||||
@@ -668,19 +646,18 @@ class MininetCluster( Mininet ):
|
||||
result |= code
|
||||
if result:
|
||||
error( '*** Server precheck failed.\n'
|
||||
'*** Make sure that the above ssh command works'
|
||||
' correctly.\n'
|
||||
'*** Make sure that the above ssh command works correctly.\n'
|
||||
'*** You may also need to run mn -c on all nodes, and/or\n'
|
||||
'*** use sudo -E.\n' )
|
||||
sys.exit( 1 )
|
||||
exit( 1 )
|
||||
info( '\n' )
|
||||
|
||||
def modifiedaddHost( self, *args, **kwargs ):
|
||||
"Slightly modify addHost"
|
||||
assert self # please pylint
|
||||
kwargs[ 'splitInit' ] = True
|
||||
return Mininet.addHost( *args, **kwargs )
|
||||
|
||||
|
||||
def placeNodes( self ):
|
||||
"""Place nodes on servers (if they don't have a server), and
|
||||
start shell processes"""
|
||||
@@ -694,10 +671,7 @@ class MininetCluster( Mininet ):
|
||||
switches=self.topo.switches(),
|
||||
links=self.topo.links() )
|
||||
for node in nodes:
|
||||
config = self.topo.nodeInfo( node )
|
||||
# keep local server name consistent accross nodes
|
||||
if 'server' in config.keys() and config[ 'server' ] is None:
|
||||
config[ 'server' ] = 'localhost'
|
||||
config = self.topo.node_info[ node ]
|
||||
server = config.setdefault( 'server', placer.place( node ) )
|
||||
if server:
|
||||
config.setdefault( 'serverIP', self.serverIP[ server ] )
|
||||
@@ -715,7 +689,7 @@ class MininetCluster( Mininet ):
|
||||
if ( isinstance( controller, Controller)
|
||||
and controller.IP() == '127.0.0.1'
|
||||
and ' eth0:' in controller.cmd( 'ip link show' ) ):
|
||||
Intf( 'eth0', node=controller ).updateIP()
|
||||
Intf( 'eth0', node=controller ).updateIP()
|
||||
return controller
|
||||
|
||||
def buildFromTopo( self, *args, **kwargs ):
|
||||
@@ -806,7 +780,7 @@ def testRemoteTopo():
|
||||
"Test remote Node classes using Mininet()/Topo() API"
|
||||
topo = LinearTopo( 2 )
|
||||
net = Mininet( topo=topo, host=HostPlacer, switch=SwitchPlacer,
|
||||
link=RemoteLink, controller=ClusterController )
|
||||
link=RemoteLink, controller=ClusterController )
|
||||
net.start()
|
||||
net.pingAll()
|
||||
net.stop()
|
||||
|
||||
@@ -5,18 +5,18 @@ A sanity check for cluster edition
|
||||
'''
|
||||
|
||||
from mininet.examples.cluster import MininetCluster
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.examples.clustercli import ClusterCLI as CLI
|
||||
from mininet.log import info, setLogLevel
|
||||
from mininet.examples.clustercli import DemoCLI as CLI
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
|
||||
def clusterSanity():
|
||||
"Sanity check for cluster mode"
|
||||
topo = SingleSwitchTopo()
|
||||
net = MininetCluster( topo=topo )
|
||||
net = MininetCluster( topo=topo )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
clusterSanity()
|
||||
|
||||
+17
-22
@@ -5,38 +5,34 @@
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import output, error
|
||||
|
||||
# pylint: disable=global-statement
|
||||
nx, graphviz_layout, plt = None, None, None # Will be imported on demand
|
||||
|
||||
|
||||
class ClusterCLI( CLI ):
|
||||
class DemoCLI( CLI ):
|
||||
"CLI with additional commands for Cluster Edition demo"
|
||||
|
||||
@staticmethod
|
||||
def colorsFor( seq ):
|
||||
"Return a list of background colors for a sequence"
|
||||
colors = [ 'red', 'lightgreen', 'cyan', 'yellow', 'orange',
|
||||
'magenta', 'pink', 'grey', 'brown',
|
||||
'white' ]
|
||||
'magenta', 'pink', 'grey', 'brown',
|
||||
'white' ]
|
||||
slen, clen = len( seq ), len( colors )
|
||||
reps = max( 1, slen / clen )
|
||||
colors = colors * reps
|
||||
colors = colors[ 0 : slen ]
|
||||
return colors
|
||||
|
||||
def do_plot( self, _line ):
|
||||
|
||||
def do_plot( self, line ):
|
||||
"Plot topology colored by node placement"
|
||||
# Import networkx if needed
|
||||
global nx, plt
|
||||
if not nx:
|
||||
try:
|
||||
import networkx
|
||||
nx = networkx # satisfy pylint
|
||||
from matplotlib import pyplot
|
||||
plt = pyplot # satisfiy pylint
|
||||
import networkx as nx
|
||||
import matplotlib.pyplot as plt
|
||||
import pygraphviz
|
||||
assert pygraphviz # silence pyflakes
|
||||
except ImportError:
|
||||
except:
|
||||
error( 'plot requires networkx, matplotlib and pygraphviz - '
|
||||
'please install them and try again\n' )
|
||||
return
|
||||
@@ -44,6 +40,7 @@ class ClusterCLI( CLI ):
|
||||
g = nx.Graph()
|
||||
mn = self.mn
|
||||
servers, hosts, switches = mn.servers, mn.hosts, mn.switches
|
||||
hlen, slen = len( hosts ), len( switches )
|
||||
nodes = hosts + switches
|
||||
g.add_nodes_from( nodes )
|
||||
links = [ ( link.intf1.node, link.intf2.node )
|
||||
@@ -55,14 +52,11 @@ class ClusterCLI( CLI ):
|
||||
# Plot it!
|
||||
pos = nx.graphviz_layout( g )
|
||||
opts = { 'ax': None, 'font_weight': 'bold',
|
||||
'width': 2, 'edge_color': 'darkblue' }
|
||||
hcolors = [ color[ getattr( h, 'server', 'localhost' ) ]
|
||||
for h in hosts ]
|
||||
scolors = [ color[ getattr( s, 'server', 'localhost' ) ]
|
||||
for s in switches ]
|
||||
nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800,
|
||||
label='host', node_color=hcolors, node_shape='s',
|
||||
**opts )
|
||||
'width': 2, 'edge_color': 'darkblue' }
|
||||
hcolors = [ color[ h.server ] for h in hosts ]
|
||||
scolors = [ color[ s.server ] for s in switches ]
|
||||
nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800, label='host',
|
||||
node_color=hcolors, node_shape='s', **opts )
|
||||
nx.draw_networkx( g, pos=pos, nodelist=switches, node_size=1000,
|
||||
node_color=scolors, node_shape='o', **opts )
|
||||
# Get rid of axes, add title, and show
|
||||
@@ -74,7 +68,7 @@ class ClusterCLI( CLI ):
|
||||
plt.title( 'Node Placement', fontweight='bold' )
|
||||
plt.show()
|
||||
|
||||
def do_status( self, _line ):
|
||||
def do_status( self, line ):
|
||||
"Report on node shell status"
|
||||
nodes = self.mn.hosts + self.mn.switches
|
||||
for node in nodes:
|
||||
@@ -88,7 +82,8 @@ class ClusterCLI( CLI ):
|
||||
else:
|
||||
output( 'All nodes are still running.\n' )
|
||||
|
||||
def do_placement( self, _line ):
|
||||
|
||||
def do_placement( self, line ):
|
||||
"Describe node placement"
|
||||
mn = self.mn
|
||||
nodes = mn.hosts + mn.switches + mn.controllers
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from mininet.examples.cluster import MininetCluster, SwitchBinPlacer
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.examples.clustercli import ClusterCLI as CLI
|
||||
from mininet.examples.clustercli import DemoCLI as CLI
|
||||
|
||||
def demo():
|
||||
"Simple Demo of Cluster Mode"
|
||||
@@ -20,3 +20,4 @@ def demo():
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
demo()
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ class ConsoleApp( Frame ):
|
||||
elif units[0] == 'b':
|
||||
val *= 10 ** -9
|
||||
self.updates += 1
|
||||
self.bw += val
|
||||
self.bw += val
|
||||
if self.updates >= self.hostCount:
|
||||
self.graph.addBar( self.bw )
|
||||
self.bw = 0
|
||||
|
||||
@@ -35,7 +35,7 @@ class DataController( Controller ):
|
||||
class MininetFacade( object ):
|
||||
"""Mininet object facade that allows a single CLI to
|
||||
talk to one or more networks"""
|
||||
|
||||
|
||||
def __init__( self, net, *args, **kwargs ):
|
||||
"""Create MininetFacade object.
|
||||
net: Primary Mininet object
|
||||
@@ -114,7 +114,7 @@ class ControlNetwork( Topo ):
|
||||
|
||||
def run():
|
||||
"Create control and data networks, and invoke the CLI"
|
||||
|
||||
|
||||
info( '* Creating Control Network\n' )
|
||||
ctopo = ControlNetwork( n=4, dataController=DataController )
|
||||
cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24', controller=None )
|
||||
|
||||
+2
-2
@@ -8,8 +8,9 @@ from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom, waitListening
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.log import setLogLevel, output, info
|
||||
|
||||
from time import sleep
|
||||
|
||||
def bwtest( cpuLimits, period_us=100000, seconds=5 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
@@ -27,7 +28,6 @@ def bwtest( cpuLimits, period_us=100000, seconds=5 ):
|
||||
cpu=cpu )
|
||||
try:
|
||||
net = Mininet( topo=topo, host=host )
|
||||
# pylint: disable=bare-except
|
||||
except:
|
||||
info( '*** Skipping host %s\n' % sched )
|
||||
break
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@ This example shows how to add an interface (for example a real
|
||||
hardware interface) to a network after the network is created.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import re, sys
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel, info, error
|
||||
|
||||
@@ -19,9 +19,9 @@ def intfOptions():
|
||||
h2 = net.addHost( 'h2' )
|
||||
s1 = net.addSwitch( 's1' )
|
||||
link1 = net.addLink( h1, s1, cls=TCLink )
|
||||
net.addLink( h2, s1 )
|
||||
link2 = net.addLink( h2, s1 )
|
||||
net.start()
|
||||
|
||||
|
||||
# flush out latency from reactive forwarding delay
|
||||
net.pingAll()
|
||||
|
||||
@@ -34,12 +34,12 @@ def intfOptions():
|
||||
link1.intf1.config( loss=50 )
|
||||
info( '\n' )
|
||||
net.iperf( ( h1, h2 ), l4Type='UDP' )
|
||||
|
||||
|
||||
info( '\n*** Configuring one intf with delay of 15ms\n' )
|
||||
link1.intf1.config( delay='15ms' )
|
||||
info( '\n*** Run a ping to confirm delay\n' )
|
||||
net.pingPairFull()
|
||||
|
||||
|
||||
info( '\n*** Done testing\n' )
|
||||
net.stop()
|
||||
|
||||
+2
-4
@@ -27,11 +27,9 @@ def limit( bw=10, cpu=.1 ):
|
||||
info( '*** Testing with', sched, 'bandwidth limiting\n' )
|
||||
if sched == 'rt':
|
||||
release = quietRun( 'uname -r' ).strip('\r\n')
|
||||
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s'
|
||||
% release )
|
||||
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
|
||||
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
|
||||
info( '*** RT Scheduler is not enabled in your kernel. '
|
||||
'Skipping this test\n' )
|
||||
info( '*** RT Scheduler is not enabled in your kernel. Skipping this test\n' )
|
||||
continue
|
||||
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
|
||||
net = Mininet( topo=myTopo, intf=intf, host=host )
|
||||
|
||||
@@ -27,7 +27,7 @@ from mininet.net import Mininet
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch, Controller
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import lg
|
||||
from mininet.util import irange, quietRun
|
||||
from mininet.util import irange
|
||||
from mininet.link import TCLink
|
||||
from functools import partial
|
||||
|
||||
@@ -78,10 +78,6 @@ def linearBandwidthTest( lengths ):
|
||||
|
||||
topo = LinearTestTopo( hostCount )
|
||||
|
||||
# Select TCP Reno
|
||||
output = quietRun( 'sysctl -w net.ipv4.tcp_congestion_control=reno' )
|
||||
assert 'reno' in output
|
||||
|
||||
for datapath in switches.keys():
|
||||
print "*** testing", datapath, "datapath"
|
||||
Switch = switches[ datapath ]
|
||||
@@ -101,7 +97,7 @@ def linearBandwidthTest( lengths ):
|
||||
# since the reference controller is reactive
|
||||
src.cmd( 'telnet', dst.IP(), '5001' )
|
||||
print "testing", src.name, "<->", dst.name,
|
||||
bandwidth = net.iperf( [ src, dst ], seconds=10 )
|
||||
bandwidth = net.iperf( [ src, dst ] )
|
||||
print bandwidth
|
||||
flush()
|
||||
results[ datapath ] += [ ( n, bandwidth ) ]
|
||||
|
||||
+11
-17
@@ -2,7 +2,7 @@
|
||||
|
||||
"""
|
||||
linuxrouter.py: Example network with Linux IP router
|
||||
|
||||
|
||||
This example converts a Node into a router using IP forwarding
|
||||
already built into Linux.
|
||||
|
||||
@@ -16,7 +16,7 @@ The topology contains a router with three IP subnets:
|
||||
- h2 (IP: 172.16.0.100)
|
||||
- h3 (IP: 10.0.0.100)
|
||||
|
||||
Routing entries can be added to the routing tables of the
|
||||
Routing entries can be added to the routing tables of the
|
||||
hosts or router using the "ip route add" or "route add" command.
|
||||
See the man pages for more details.
|
||||
|
||||
@@ -27,6 +27,7 @@ from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.cli import CLI
|
||||
from mininet.util import irange
|
||||
|
||||
class LinuxRouter( Node ):
|
||||
"A Node with IP forwarding enabled."
|
||||
@@ -44,25 +45,18 @@ class LinuxRouter( Node ):
|
||||
class NetworkTopo( Topo ):
|
||||
"A simple topology of a router with three subnets (one host in each)."
|
||||
|
||||
def build( self, **_opts ):
|
||||
def build( self, n=2, h=1, **opts ):
|
||||
router = self.addNode( 'r0', cls=LinuxRouter, ip='192.168.1.1/24' )
|
||||
h1 = self.addHost( 'h1', ip='192.168.1.100/24',
|
||||
defaultRoute='via 192.168.1.1' )
|
||||
h2 = self.addHost( 'h2', ip='172.16.0.100/12',
|
||||
defaultRoute='via 172.16.0.1' )
|
||||
h3 = self.addHost( 'h3', ip='10.0.0.100/8',
|
||||
defaultRoute='via 10.0.0.1' )
|
||||
self.addLink( h1, router, intfName2='r0-eth1',
|
||||
params2={ 'ip' : '192.168.1.1/24' } )
|
||||
self.addLink( h2, router, intfName2='r0-eth2',
|
||||
params2={ 'ip' : '172.16.0.1/12' } )
|
||||
self.addLink( h3, router, intfName2='r0-eth3',
|
||||
params2={ 'ip' : '10.0.0.1/8' } )
|
||||
h1 = self.addHost( 'h1', ip='192.168.1.100/24', defaultRoute='via 192.168.1.1' )
|
||||
h2 = self.addHost( 'h2', ip='172.16.0.100/12', defaultRoute='via 172.16.0.1' )
|
||||
h3 = self.addHost( 'h3', ip='10.0.0.100/8', defaultRoute='via 10.0.0.1' )
|
||||
self.addLink( h1, router, intfName2='r0-eth1', params2={ 'ip' : '192.168.1.1/24' } )
|
||||
self.addLink( h2, router, intfName2='r0-eth2', params2={ 'ip' : '172.16.0.1/12' } )
|
||||
self.addLink( h3, router, intfName2='r0-eth3', params2={ 'ip' : '10.0.0.1/8' } )
|
||||
|
||||
def run():
|
||||
"Test linux router"
|
||||
topo = NetworkTopo()
|
||||
net = Mininet( topo=topo, controller=None ) # no controller needed
|
||||
net = Mininet( topo=topo, controller=None ) # no controller needed
|
||||
net.start()
|
||||
info( '*** Routing Table on Router\n' )
|
||||
print net[ 'r0' ].cmd( 'route' )
|
||||
|
||||
+905
-910
File diff suppressed because it is too large
Load Diff
@@ -22,9 +22,11 @@ to-do:
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import OVSSwitch
|
||||
from mininet.topo import LinearTopo
|
||||
from mininet.util import quietRun
|
||||
from mininet.log import output, warn
|
||||
|
||||
from random import randint
|
||||
from re import findall
|
||||
|
||||
|
||||
class MobilitySwitch( OVSSwitch ):
|
||||
@@ -59,7 +61,7 @@ class MobilitySwitch( OVSSwitch ):
|
||||
def validatePort( self, intf ):
|
||||
"Validate intf's OF port number"
|
||||
ofport = int( self.cmd( 'ovs-vsctl get Interface', intf,
|
||||
'ofport' ) )
|
||||
'ofport' ) )
|
||||
if ofport != self.ports[ intf ]:
|
||||
warn( 'WARNING: ofport for', intf, 'is actually', ofport,
|
||||
'\n' )
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
This is a simple example that demonstrates multiple links
|
||||
between nodes.
|
||||
"""
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import Topo
|
||||
|
||||
def runMultiLink():
|
||||
"Create and run multiple link network"
|
||||
topo = simpleMultiLinkTopo( n=2 )
|
||||
net = Mininet( topo=topo )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
class simpleMultiLinkTopo( Topo ):
|
||||
"Simple topology with multiple links"
|
||||
|
||||
def __init__( self, n, **kwargs ):
|
||||
Topo.__init__( self, **kwargs )
|
||||
|
||||
h1, h2 = self.addHost( 'h1' ), self.addHost( 'h2' )
|
||||
s1 = self.addSwitch( 's1' )
|
||||
|
||||
for _ in range( n ):
|
||||
self.addLink( s1, h1 )
|
||||
self.addLink( s1, h2 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
runMultiLink()
|
||||
+1
-1
@@ -22,7 +22,7 @@ def startNAT( root, inetIntf='eth0', subnet='10.0/8' ):
|
||||
subnet: Mininet subnet (default 10.0/8)="""
|
||||
|
||||
# Identify the interface connecting to the mininet network
|
||||
localIntf = root.defaultIntf()
|
||||
localIntf = root.defaultIntf()
|
||||
|
||||
# Flush any currently active rules
|
||||
root.cmd( 'iptables -F' )
|
||||
|
||||
+6
-5
@@ -14,7 +14,7 @@ natnet.py: Example network with NATs
|
||||
| |
|
||||
s1 s2
|
||||
| |
|
||||
h1 h2
|
||||
h1 h2
|
||||
|
||||
"""
|
||||
|
||||
@@ -27,7 +27,7 @@ from mininet.util import irange
|
||||
|
||||
class InternetTopo(Topo):
|
||||
"Single switch connected to n hosts."
|
||||
def __init__(self, n=2, **opts):
|
||||
def __init__(self, n=2, h=1, **opts):
|
||||
Topo.__init__(self, **opts)
|
||||
|
||||
# set up inet switch
|
||||
@@ -44,15 +44,15 @@ class InternetTopo(Topo):
|
||||
localSubnet = '192.168.%d.0/24' % i
|
||||
natParams = { 'ip' : '%s/24' % localIP }
|
||||
# add NAT to topology
|
||||
nat = self.addNode('nat%d' % i, cls=NAT, subnet=localSubnet,
|
||||
nat = self.addNode('nat%d' % i, cls=NAT, subnet=localSubnet,
|
||||
inetIntf=inetIntf, localIntf=localIntf)
|
||||
switch = self.addSwitch('s%d' % i)
|
||||
# connect NAT to inet and local switches
|
||||
self.addLink(nat, inetSwitch, intfName1=inetIntf)
|
||||
self.addLink(nat, switch, intfName1=localIntf, params1=natParams)
|
||||
# add host and connect to local switch
|
||||
host = self.addHost('h%d' % i,
|
||||
ip='192.168.%d.100/24' % i,
|
||||
host = self.addHost('h%d' % i,
|
||||
ip='192.168.%d.100/24' % i,
|
||||
defaultRoute='via %s' % localIP)
|
||||
self.addLink(host, switch)
|
||||
|
||||
@@ -67,3 +67,4 @@ def run():
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
run()
|
||||
|
||||
+14
-17
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Create a network with 5 hosts, numbered 1-4 and 9.
|
||||
Create a network with 5 hosts, numbered 1-4 and 9.
|
||||
Validate that the port numbers match to the interface name,
|
||||
and that the ovs ports match the mininet ports.
|
||||
"""
|
||||
@@ -9,23 +9,21 @@ and that the ovs ports match the mininet ports.
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Controller
|
||||
from mininet.log import setLogLevel, info, warn
|
||||
from mininet.node import Node
|
||||
|
||||
def validatePort( switch, intf ):
|
||||
"Validate intf's OF port number"
|
||||
ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf,
|
||||
'ofport' ) )
|
||||
'ofport' ) )
|
||||
if ofport != switch.ports[ intf ]:
|
||||
warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' )
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def testPortNumbering():
|
||||
def net():
|
||||
|
||||
"""Test port numbering:
|
||||
Create a network with 5 hosts (using Mininet's
|
||||
mid-level API) and check that implicit and
|
||||
explicit port numbering works as expected."""
|
||||
"Create a network with 5 hosts."
|
||||
|
||||
net = Mininet( controller=Controller )
|
||||
|
||||
@@ -48,25 +46,23 @@ def testPortNumbering():
|
||||
net.addLink( h2, s1 )
|
||||
net.addLink( h3, s1 )
|
||||
net.addLink( h4, s1 )
|
||||
# specify a different port to connect host 5 to on the switch.
|
||||
net.addLink( h5, s1, port1=1, port2= 9)
|
||||
net.addLink( h5, s1, port1 = 1, port2 = 9 ) # specify a different port to connect host 5 to on the switch.
|
||||
|
||||
root = Node( 'root', inNamespace=False )
|
||||
info( '*** Starting network\n' )
|
||||
net.start()
|
||||
|
||||
# print the interfaces and their port numbers
|
||||
info( '\n*** printing and validating the ports '
|
||||
'running on each interface\n' )
|
||||
info( '\n*** printing and validating the ports running on each interface\n' )
|
||||
for intfs in s1.intfList():
|
||||
if not intfs.name == "lo":
|
||||
info( intfs, ': ', s1.ports[intfs],
|
||||
'\n' )
|
||||
info( 'Validating that', intfs,
|
||||
'is actually on port', s1.ports[intfs], '... ' )
|
||||
info( intfs, ': ', s1.ports[intfs],
|
||||
'\n' )
|
||||
info ( 'Validating that', intfs, 'is actually on port', s1.ports[intfs], '... ' )
|
||||
if validatePort( s1, intfs ):
|
||||
info( 'Validated.\n' )
|
||||
print '\n'
|
||||
|
||||
|
||||
# test the network with pingall
|
||||
net.pingAll()
|
||||
print '\n'
|
||||
@@ -76,4 +72,5 @@ def testPortNumbering():
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
testPortNumbering()
|
||||
net()
|
||||
|
||||
|
||||
@@ -33,14 +33,14 @@ def perfTest():
|
||||
"Create network and run simple performance test"
|
||||
topo = SingleSwitchTopo( n=4 )
|
||||
net = Mininet( topo=topo,
|
||||
host=CPULimitedHost, link=TCLink,
|
||||
autoStaticArp=True )
|
||||
host=CPULimitedHost, link=TCLink,
|
||||
autoStaticArp=True )
|
||||
net.start()
|
||||
print "Dumping host connections"
|
||||
dumpNodeConnections(net.hosts)
|
||||
print "Testing bandwidth between h1 and h4"
|
||||
h1, h4 = net.getNodeByName('h1', 'h4')
|
||||
net.iperf( ( h1, h4 ), l4Type='UDP' )
|
||||
results = net.iperf( ( h1, h4 ), l4Type='UDP' )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+2
-2
@@ -81,6 +81,6 @@ if __name__ == '__main__':
|
||||
net = TreeNet( depth=1, fanout=4 )
|
||||
# get sshd args from the command line or use default args
|
||||
# useDNS=no -u0 to avoid reverse DNS lookup timeout
|
||||
argvopts = ' '.join( sys.argv[ 1: ] ) if len( sys.argv ) > 1 else (
|
||||
opts = ' '.join( sys.argv[ 1: ] ) if len( sys.argv ) > 1 else (
|
||||
'-D -o UseDNS=no -u0' )
|
||||
sshd( net, opts=argvopts )
|
||||
sshd( net, opts=opts )
|
||||
|
||||
@@ -36,7 +36,7 @@ class testBareSSHD( unittest.TestCase ):
|
||||
'-o StrictModes=no' )
|
||||
p = pexpect.spawn( cmd )
|
||||
runOpts = [ 'You may now ssh into h1 at 10.0.0.1',
|
||||
'after 5 seconds, h1 is not listening on port 22',
|
||||
'after 5 seconds, h1 is not listening on port 22',
|
||||
pexpect.EOF, pexpect.TIMEOUT ]
|
||||
while True:
|
||||
index = p.expect( runOpts )
|
||||
|
||||
@@ -6,7 +6,7 @@ Test for cpu.py
|
||||
results format:
|
||||
|
||||
sched cpu client MB/s
|
||||
|
||||
|
||||
cfs 45.00% 13254.669841
|
||||
cfs 40.00% 11822.441399
|
||||
cfs 30.00% 5112.963009
|
||||
@@ -28,13 +28,13 @@ class testCPU( unittest.TestCase ):
|
||||
"Verify that CPU utilization is monotonically decreasing for each scheduler"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.cpu' )
|
||||
# matches each line from results( shown above )
|
||||
opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.]+)',
|
||||
opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.]+)',
|
||||
pexpect.EOF ]
|
||||
scheds = []
|
||||
while True:
|
||||
index = p.expect( opts, timeout=600 )
|
||||
if index == 0:
|
||||
sched = p.match.group( 1 )
|
||||
sched = p.match.group( 1 )
|
||||
cpu = float( p.match.group( 2 ) )
|
||||
bw = float( p.match.group( 3 ) )
|
||||
if sched not in scheds:
|
||||
|
||||
@@ -19,7 +19,7 @@ class testEmptyNet( unittest.TestCase ):
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
self.assertEqual( percent, 0 )
|
||||
p.expect( self.prompt )
|
||||
# iperf test
|
||||
p.sendline( 'iperf' )
|
||||
|
||||
@@ -12,7 +12,7 @@ class testIntfOptions( unittest.TestCase ):
|
||||
|
||||
def testIntfOptions( self ):
|
||||
"verify that intf.config is correctly limiting traffic"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.intfoptions ' )
|
||||
p = pexpect.spawn( 'python -m mininet.examples.intfOptions ' )
|
||||
tolerance = .8
|
||||
opts = [ "Results: \['([\d\.]+) .bits/sec",
|
||||
"Results: \['10M', '([\d\.]+) .bits/sec",
|
||||
@@ -14,8 +14,8 @@ class testLimit( unittest.TestCase ):
|
||||
def testLimit( self ):
|
||||
"Verify that CPU limits are within a 2% tolerance of limit for each scheduler"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.limit' )
|
||||
opts = [ '\*\*\* Testing network ([\d\.]+) Mbps',
|
||||
'\*\*\* Results: \[([\d\., ]+)\]',
|
||||
opts = [ '\*\*\* Testing network ([\d\.]+) Mbps',
|
||||
'\*\*\* Results: \[([\d\., ]+)\]',
|
||||
pexpect.EOF ]
|
||||
count = 0
|
||||
bw = 0
|
||||
|
||||
@@ -15,8 +15,8 @@ class testLinearBandwidth( unittest.TestCase ):
|
||||
"Verify that bandwidth is monotonically decreasing as # of hops increases"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.linearbandwidth' )
|
||||
count = 0
|
||||
opts = [ '\*\*\* Linear network results',
|
||||
'(\d+)\s+([\d\.]+) (.bits)',
|
||||
opts = [ '\*\*\* Linear network results',
|
||||
'(\d+)\s+([\d\.]+) (.bits)',
|
||||
pexpect.EOF ]
|
||||
while True:
|
||||
index = p.expect( opts, timeout=600 )
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Test for multiple links between nodes
|
||||
validates mininet interfaces against systems interfaces
|
||||
'''
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
|
||||
class testMultiLink( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def testMultiLink(self):
|
||||
p = pexpect.spawn( 'python -m mininet.examples.multilink' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'intfs' )
|
||||
p.expect( 's(\d): lo' )
|
||||
intfsOutput = p.before
|
||||
# parse interfaces from mininet intfs, and store them in a list
|
||||
hostToIntfs = intfsOutput.split( '\r\n' )[ 1:3 ]
|
||||
intfList = []
|
||||
for hostToIntf in hostToIntfs:
|
||||
intfList += [ intf for intf in
|
||||
hostToIntf.split()[1].split(',') ]
|
||||
|
||||
# get interfaces from system by running ifconfig on every host
|
||||
sysIntfList = []
|
||||
opts = [ 'h(\d)-eth(\d)', self.prompt ]
|
||||
p.expect( self.prompt )
|
||||
|
||||
p.sendline( 'h1 ifconfig' )
|
||||
while True:
|
||||
p.expect( opts )
|
||||
if p.after == self.prompt:
|
||||
break
|
||||
sysIntfList.append( p.after )
|
||||
|
||||
p.sendline( 'h2 ifconfig' )
|
||||
while True:
|
||||
p.expect( opts )
|
||||
if p.after == self.prompt:
|
||||
break
|
||||
sysIntfList.append( p.after )
|
||||
|
||||
failMsg = ( 'The systems interfaces and mininet interfaces\n'
|
||||
'are not the same' )
|
||||
|
||||
self.assertEqual( sysIntfList, intfList, msg=failMsg )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -11,7 +11,7 @@ from collections import defaultdict
|
||||
class testMultiPing( unittest.TestCase ):
|
||||
|
||||
def testMultiPing( self ):
|
||||
"""Verify that each target is pinged at least once, and
|
||||
"""Verify that each target is pinged at least once, and
|
||||
that pings to 'real' targets are successful and unknown targets fail"""
|
||||
p = pexpect.spawn( 'python -m mininet.examples.multiping' )
|
||||
opts = [ "Host (h\d+) \(([\d.]+)\) will be pinging ips: ([\d\. ]+)",
|
||||
|
||||
@@ -14,7 +14,7 @@ class testNAT( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@unittest.skipIf( '0 received' in quietRun( 'ping -c 1 %s' % destIP ),
|
||||
@unittest.skipIf( '0 received' in quietRun( 'ping -c 1 %s' % destIP ),
|
||||
'Destination IP is not reachable' )
|
||||
def testNAT( self ):
|
||||
"Attempt to ping an IP on the Internet and verify 0% packet loss"
|
||||
|
||||
@@ -15,8 +15,8 @@ class testNumberedports( unittest.TestCase ):
|
||||
def testConsistency( self ):
|
||||
"""verify consistency between mininet and ovs ports"""
|
||||
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
|
||||
opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.',
|
||||
'Validating that s1-eth\d is actually on port \d ... WARNING',
|
||||
opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.',
|
||||
'Validating that s1-eth\d is actually on port \d ... WARNING',
|
||||
pexpect.EOF ]
|
||||
correct_ports = True
|
||||
count = 0
|
||||
@@ -34,7 +34,7 @@ class testNumberedports( unittest.TestCase ):
|
||||
def testNumbering( self ):
|
||||
"""verify that all of the port numbers are printed correctly and consistent with their interface"""
|
||||
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
|
||||
opts = [ 's1-eth(\d+) : (\d+)',
|
||||
opts = [ 's1-eth(\d+) : (\d+)',
|
||||
pexpect.EOF ]
|
||||
count_intfs = 0
|
||||
while True:
|
||||
|
||||
@@ -14,7 +14,7 @@ class testSSHD( unittest.TestCase ):
|
||||
|
||||
def connected( self, ip ):
|
||||
"Log into ssh server, check banner, then exit"
|
||||
# Note: this test will fail if "Welcome" is not in the sshd banner
|
||||
# Note: this test will fail if "Welcome" is not in the sshd banner
|
||||
# and '#'' or '$'' are not in the prompt
|
||||
p = pexpect.spawn( 'ssh -i /tmp/ssh/test_rsa %s' % ip, timeout=10 )
|
||||
while True:
|
||||
@@ -26,7 +26,7 @@ class testSSHD( unittest.TestCase ):
|
||||
return False
|
||||
elif index == 2:
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
p.wait()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -47,4 +47,4 @@ class testVLANHost( unittest.TestCase ):
|
||||
self.assertEqual( i, 0 ) # check vlan intf is present
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
+12
-11
@@ -30,18 +30,17 @@ from mininet.util import quietRun
|
||||
from mininet.log import error
|
||||
|
||||
class VLANHost( Host ):
|
||||
"Host connected to VLAN interface"
|
||||
|
||||
def config( self, vlan=100, **params ):
|
||||
def config( self, vlan=100, **params ):
|
||||
"""Configure VLANHost according to (optional) parameters:
|
||||
vlan: VLAN ID for default interface"""
|
||||
|
||||
r = super( VLANHost, self ).config( **params )
|
||||
r = super( Host, self ).config( **params )
|
||||
|
||||
intf = self.defaultIntf()
|
||||
# remove IP from default, "physical" interface
|
||||
self.cmd( 'ifconfig %s inet 0' % intf )
|
||||
# create VLAN interface
|
||||
# create VLAN interface
|
||||
self.cmd( 'vconfig add %s %d' % ( intf, vlan ) )
|
||||
# assign the host's IP to the VLAN interface
|
||||
self.cmd( 'ifconfig %s.%d inet %s' % ( intf, vlan, params['ip'] ) )
|
||||
@@ -70,8 +69,6 @@ def exampleAllHosts( vlan ):
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
|
||||
class VLANStarTopo( Topo ):
|
||||
"""Example topology that uses host in multiple VLANs
|
||||
|
||||
@@ -93,7 +90,7 @@ class VLANStarTopo( Topo ):
|
||||
self.addLink( h, s1 )
|
||||
|
||||
|
||||
def exampleCustomTags():
|
||||
def exampleCustomTags( vlan ):
|
||||
"""Simple example that exercises VLANStarTopo"""
|
||||
|
||||
net = Mininet( topo=VLANStarTopo() )
|
||||
@@ -113,12 +110,16 @@ if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
|
||||
if not quietRun( 'which vconfig' ):
|
||||
error( "Cannot find command 'vconfig'\nThe package",
|
||||
error( "Cannot find command 'vconfig'\nThe packge",
|
||||
"'vlan' is required in Ubuntu or Debian,",
|
||||
"or 'vconfig' in Fedora\n" )
|
||||
exit()
|
||||
try:
|
||||
vlan = int( sys.argv[ 1 ] )
|
||||
except Exception:
|
||||
vlan = None
|
||||
|
||||
if len( sys.argv ) >= 2:
|
||||
exampleAllHosts( vlan=int( sys.argv[ 1 ] ) )
|
||||
if vlan:
|
||||
exampleAllHosts( vlan )
|
||||
else:
|
||||
exampleCustomTags()
|
||||
exampleCustomTags( vlan )
|
||||
|
||||
+9
-11
@@ -10,14 +10,12 @@ It may also get rid of 'false positives', but hopefully
|
||||
nothing irreplaceable!
|
||||
"""
|
||||
|
||||
from subprocess import ( Popen, PIPE, check_output as co,
|
||||
CalledProcessError )
|
||||
from subprocess import Popen, PIPE, check_output as co
|
||||
import time
|
||||
|
||||
from mininet.log import info
|
||||
from mininet.term import cleanUpScreens
|
||||
|
||||
|
||||
def sh( cmd ):
|
||||
"Print a command and send it to the shell"
|
||||
info( cmd + '\n' )
|
||||
@@ -29,12 +27,12 @@ def killprocs( pattern ):
|
||||
# Make sure they are gone
|
||||
while True:
|
||||
try:
|
||||
pids = co( [ 'pgrep', '-f', pattern ] )
|
||||
except CalledProcessError:
|
||||
pids = co( 'pgrep -f %s' % pattern )
|
||||
except:
|
||||
pids = ''
|
||||
if pids:
|
||||
sh( 'pkill -9 -f %s' % pattern )
|
||||
time.sleep( .5 )
|
||||
sh( 'pkill -f 9 mininet:' )
|
||||
sleep( .5 )
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -51,7 +49,7 @@ def cleanup():
|
||||
# you can't connect to them either, so they're mostly harmless.
|
||||
# Send SIGTERM first to give processes a chance to shutdown cleanly.
|
||||
sh( 'killall ' + zombies + ' 2> /dev/null' )
|
||||
time.sleep( 1 )
|
||||
time.sleep(1)
|
||||
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
|
||||
|
||||
# And kill off sudo mnexec
|
||||
@@ -73,7 +71,7 @@ def cleanup():
|
||||
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
|
||||
if dps:
|
||||
sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp
|
||||
for dp in dps if dp ) )
|
||||
for dp in dps if dp ) )
|
||||
# And in case the above didn't work...
|
||||
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
|
||||
for dp in dps:
|
||||
@@ -89,9 +87,9 @@ def cleanup():
|
||||
info( "*** Killing stale mininet node processes\n" )
|
||||
killprocs( 'mininet:' )
|
||||
|
||||
info( "*** Shutting down stale tunnels\n" )
|
||||
info ( "*** Shutting down stale tunnels\n" )
|
||||
killprocs( 'Tunnel=Ethernet' )
|
||||
killprocs( '.ssh/mn')
|
||||
sh( 'rm -f ~/.ssh/mn/*' )
|
||||
|
||||
|
||||
info( "*** Cleanup complete.\n" )
|
||||
|
||||
+14
-31
@@ -36,8 +36,8 @@ import atexit
|
||||
|
||||
from mininet.log import info, output, error
|
||||
from mininet.term import makeTerms, runX11
|
||||
from mininet.util import ( quietRun, dumpNodeConnections,
|
||||
dumpPorts )
|
||||
from mininet.util import ( quietRun, isShellBuiltin, dumpNodeConnections,
|
||||
dumpPorts )
|
||||
|
||||
class CLI( Cmd ):
|
||||
"Simple command-line interface to talk to nodes."
|
||||
@@ -93,6 +93,11 @@ class CLI( Cmd ):
|
||||
self.locals.update( self.mn )
|
||||
return self.locals
|
||||
|
||||
# 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=R0201
|
||||
|
||||
helpStr = (
|
||||
'You may also send a command to a node using:\n'
|
||||
' <node> command {args}\n'
|
||||
@@ -123,7 +128,7 @@ class CLI( Cmd ):
|
||||
nodes = ' '.join( sorted( self.mn ) )
|
||||
output( 'available nodes are: \n%s\n' % nodes )
|
||||
|
||||
def do_ports( self, _line ):
|
||||
def do_ports( self, line ):
|
||||
"display ports and interfaces for each switch"
|
||||
dumpPorts( self.mn.switches )
|
||||
|
||||
@@ -134,11 +139,10 @@ class CLI( Cmd ):
|
||||
def do_sh( self, line ):
|
||||
"""Run an external shell command
|
||||
Usage: sh [cmd args]"""
|
||||
assert self # satisfy pylint and allow override
|
||||
call( line, shell=True )
|
||||
|
||||
# do_py() and do_px() need to catch any exception during eval()/exec()
|
||||
# pylint: disable=broad-except
|
||||
# pylint: disable-msg=W0703
|
||||
|
||||
def do_py( self, line ):
|
||||
"""Evaluate a Python expression.
|
||||
@@ -155,7 +159,7 @@ class CLI( Cmd ):
|
||||
output( str( e ) + '\n' )
|
||||
|
||||
# We are in fact using the exec() pseudo-function
|
||||
# pylint: disable=exec-used
|
||||
# pylint: disable-msg=W0122
|
||||
|
||||
def do_px( self, line ):
|
||||
"""Execute a Python statement.
|
||||
@@ -165,7 +169,7 @@ class CLI( Cmd ):
|
||||
except Exception, e:
|
||||
output( str( e ) + '\n' )
|
||||
|
||||
# pylint: enable=broad-except,exec-used
|
||||
# pylint: enable-msg=W0703,W0122
|
||||
|
||||
def do_pingall( self, line ):
|
||||
"Ping between all hosts."
|
||||
@@ -280,7 +284,6 @@ class CLI( Cmd ):
|
||||
|
||||
def do_exit( self, _line ):
|
||||
"Exit"
|
||||
assert self # satisfy pylint and allow override
|
||||
return 'exited by user command'
|
||||
|
||||
def do_quit( self, line ):
|
||||
@@ -343,33 +346,11 @@ class CLI( Cmd ):
|
||||
elapsed = time.time() - start
|
||||
self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
|
||||
|
||||
def do_links( self, _line ):
|
||||
def do_links( self, line ):
|
||||
"Report on links"
|
||||
for link in self.mn.links:
|
||||
print link, link.status()
|
||||
|
||||
def do_switch( self, line ):
|
||||
"Starts or stops a switch"
|
||||
args = line.split()
|
||||
if len(args) != 2:
|
||||
error( 'invalid number of args: switch <switch name>'
|
||||
'{start, stop}\n' )
|
||||
return
|
||||
sw = args[ 0 ]
|
||||
command = args[ 1 ]
|
||||
if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches:
|
||||
error( 'invalid switch: %s\n' % args[ 1 ] )
|
||||
else:
|
||||
sw = args[ 0 ]
|
||||
command = args[ 1 ]
|
||||
if command == 'start':
|
||||
self.mn.get( sw ).start( self.mn.controllers )
|
||||
elif command == 'stop':
|
||||
self.mn.get( sw ).stop( deleteIntfs=False )
|
||||
else:
|
||||
error( 'invalid command: '
|
||||
'switch <switch name> {start, stop}\n' )
|
||||
|
||||
def default( self, line ):
|
||||
"""Called on an input line when the command prefix is not recognized.
|
||||
Overridden to run shell commands when a node is the first CLI argument.
|
||||
@@ -396,6 +377,8 @@ class CLI( Cmd ):
|
||||
else:
|
||||
error( '*** Unknown command: %s\n' % line )
|
||||
|
||||
# pylint: enable-msg=R0201
|
||||
|
||||
def waitForNode( self, node ):
|
||||
"Wait for a node to finish, and print its output."
|
||||
# Pollers
|
||||
|
||||
+10
-19
@@ -33,7 +33,7 @@ class Intf( object ):
|
||||
"Basic interface object that can configure itself."
|
||||
|
||||
def __init__( self, name, node=None, port=None, link=None,
|
||||
mac=None, **params ):
|
||||
mac=None, srcNode=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
|
||||
@@ -43,7 +43,7 @@ class Intf( object ):
|
||||
self.link = link
|
||||
self.mac = mac
|
||||
self.ip, self.prefixLen = None, None
|
||||
|
||||
|
||||
# if interface is lo, we know the ip is 127.0.0.1.
|
||||
# This saves an ifconfig command per node
|
||||
if self.name == 'lo':
|
||||
@@ -70,9 +70,6 @@ class Intf( object ):
|
||||
self.ip, self.prefixLen = ipstr.split( '/' )
|
||||
return self.ifconfig( ipstr, 'up' )
|
||||
else:
|
||||
if prefixLen is None:
|
||||
raise Exception( 'No prefix length set for IP address %s'
|
||||
% ( ipstr, ) )
|
||||
self.ip, self.prefixLen = ipstr, prefixLen
|
||||
return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
|
||||
|
||||
@@ -91,8 +88,7 @@ class Intf( object ):
|
||||
"Return updated IP address based on ifconfig"
|
||||
# use pexec instead of node.cmd so that we dont read
|
||||
# backgrounded output from the cli.
|
||||
ifconfig, _err, _exitCode = self.node.pexec(
|
||||
'ifconfig %s' % self.name )
|
||||
ifconfig, _err, _exitCode = self.node.pexec( 'ifconfig %s' % self.name )
|
||||
ips = self._ipMatchRegex.findall( ifconfig )
|
||||
self.ip = ips[ 0 ] if ips else None
|
||||
return self.ip
|
||||
@@ -162,9 +158,9 @@ class Intf( object ):
|
||||
f = getattr( self, method, None )
|
||||
if not f or value is None:
|
||||
return
|
||||
if isinstance( value, list ):
|
||||
if type( value ) is list:
|
||||
result = f( *value )
|
||||
elif isinstance( value, dict ):
|
||||
elif type( value ) is dict:
|
||||
result = f( **value )
|
||||
else:
|
||||
result = f( value )
|
||||
@@ -198,7 +194,7 @@ class Intf( object ):
|
||||
|
||||
def status( self ):
|
||||
"Return intf status as a string"
|
||||
links, _err, _result = self.node.pexec( 'ip link show' )
|
||||
links, err_, result_ = self.node.pexec( 'ip link show' )
|
||||
if self.name in links:
|
||||
return "OK"
|
||||
else:
|
||||
@@ -334,9 +330,8 @@ class TCIntf( Intf ):
|
||||
|
||||
# Delay/jitter/loss/max_queue_size using netem
|
||||
delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
|
||||
loss=loss,
|
||||
max_queue_size=max_queue_size,
|
||||
parent=parent )
|
||||
loss=loss, max_queue_size=max_queue_size,
|
||||
parent=parent )
|
||||
cmds += delaycmds
|
||||
|
||||
# Ugly but functional: display configuration info
|
||||
@@ -420,21 +415,17 @@ class Link( object ):
|
||||
# All we are is dust in the wind, and our two interfaces
|
||||
self.intf1, self.intf2 = intf1, intf2
|
||||
|
||||
def intfName( self, node, n ):
|
||||
def intfName( _self, node, n ):
|
||||
"Construct a canonical interface name node-ethN for interface n."
|
||||
# Leave this as an instance method for now
|
||||
assert self
|
||||
return node.name + '-eth' + repr( n )
|
||||
|
||||
@classmethod
|
||||
def makeIntfPair( cls, intfname1, intfname2, addr1=None, addr2=None ):
|
||||
def makeIntfPair( _cls, intfname1, intfname2, addr1=None, addr2=None ):
|
||||
"""Create pair of interfaces
|
||||
intfname1: name of interface 1
|
||||
intfname2: name of interface 2
|
||||
(override this method [and possibly delete()]
|
||||
to change link type)"""
|
||||
# Leave this as a class method for now
|
||||
assert cls
|
||||
return makeIntfPair( intfname1, intfname2, addr1, addr2 )
|
||||
|
||||
def delete( self ):
|
||||
|
||||
+3
-3
@@ -124,8 +124,8 @@ class MininetLogger( Logger, object ):
|
||||
self.setLevel( level )
|
||||
self.handlers[ 0 ].setLevel( level )
|
||||
|
||||
# pylint: disable=method-hidden
|
||||
# "An attribute inherited from mininet.log hide this method" (sic)
|
||||
# pylint: disable-msg=E0202
|
||||
# "An attribute inherited from mininet.log hide this method"
|
||||
# Not sure why this is occurring - this function definitely gets called.
|
||||
|
||||
# See /usr/lib/python2.5/logging/__init__.py; modified from warning()
|
||||
@@ -142,7 +142,7 @@ class MininetLogger( Logger, object ):
|
||||
if self.isEnabledFor( OUTPUT ):
|
||||
self._log( OUTPUT, msg, args, kwargs )
|
||||
|
||||
# pylint: enable=method-hidden
|
||||
# pylint: enable-msg=E0202
|
||||
|
||||
lg = MininetLogger()
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ def moduleDeps( subtract=None, add=None ):
|
||||
add: string or list of module names to add, if not already loaded"""
|
||||
subtract = subtract if subtract is not None else []
|
||||
add = add if add is not None else []
|
||||
if isinstance( subtract, basestring ):
|
||||
if type( subtract ) is str:
|
||||
subtract = [ subtract ]
|
||||
if isinstance( add, basestring ):
|
||||
if type( add ) is str:
|
||||
add = [ add ]
|
||||
for mod in subtract:
|
||||
if mod in lsmod():
|
||||
|
||||
+54
-81
@@ -91,15 +91,14 @@ import re
|
||||
import select
|
||||
import signal
|
||||
import random
|
||||
|
||||
import copy
|
||||
from time import sleep
|
||||
from itertools import chain, groupby
|
||||
from math import ceil
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import info, error, debug, output, warn
|
||||
from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
|
||||
Controller )
|
||||
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller
|
||||
from mininet.nodelib import NAT
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
|
||||
@@ -107,7 +106,7 @@ from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||
from mininet.term import cleanUpScreens, makeTerms
|
||||
|
||||
# Mininet version: should be consistent with README and LICENSE
|
||||
VERSION = "2.2.0"
|
||||
VERSION = "2.1.0+"
|
||||
|
||||
class Mininet( object ):
|
||||
"Network emulation with hosts spawned in network namespaces."
|
||||
@@ -170,6 +169,7 @@ class Mininet( object ):
|
||||
if topo and build:
|
||||
self.build()
|
||||
|
||||
|
||||
def waitConnected( self, timeout=None, delay=.5 ):
|
||||
"""wait for each switch to connect to a controller,
|
||||
up to 5 seconds
|
||||
@@ -253,35 +253,25 @@ class Mininet( object ):
|
||||
if isinstance( name, Controller ):
|
||||
controller_new = name
|
||||
# Pylint thinks controller is a str()
|
||||
# pylint: disable=maybe-no-member
|
||||
# pylint: disable=E1103
|
||||
name = controller_new.name
|
||||
# pylint: enable=maybe-no-member
|
||||
# pylint: enable=E1103
|
||||
else:
|
||||
controller_new = controller( name, **params )
|
||||
# Add new controller to net
|
||||
if controller_new: # allow controller-less setups
|
||||
if controller_new: # allow controller-less setups
|
||||
self.controllers.append( controller_new )
|
||||
self.nameToNode[ name ] = controller_new
|
||||
return controller_new
|
||||
|
||||
def addNAT( self, name='nat0', connect=True, inNamespace=False,
|
||||
**params):
|
||||
"""Add a NAT to the Mininet network
|
||||
name: name of NAT node
|
||||
connect: switch to connect to | True (s1) | None
|
||||
inNamespace: create in a network namespace
|
||||
params: other NAT node params, notably:
|
||||
ip: used as default gateway address"""
|
||||
nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
|
||||
def addNAT( self, name='nat0', connect=True, inNamespace=False, **params ):
|
||||
nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
|
||||
subnet=self.ipBase, **params )
|
||||
# find first switch and create link
|
||||
if connect:
|
||||
if not isinstance( connect, Node ):
|
||||
# Use first switch if not specified
|
||||
connect = self.switches[ 0 ]
|
||||
# Connect the nat to the switch
|
||||
# connect the nat to the first switch
|
||||
self.addLink( nat, self.switches[ 0 ] )
|
||||
# Set the default route on hosts
|
||||
# set the default route on hosts
|
||||
natIP = nat.params[ 'ip' ].split('/')[ 0 ]
|
||||
for host in self.hosts:
|
||||
if host.inNamespace:
|
||||
@@ -331,36 +321,25 @@ class Mininet( object ):
|
||||
"return (key,value) tuple list for every node in net"
|
||||
return zip( self.keys(), self.values() )
|
||||
|
||||
@staticmethod
|
||||
def randMac():
|
||||
"Return a random, non-multicast MAC address"
|
||||
return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
|
||||
0x020000000000 )
|
||||
|
||||
def addLink( self, node1, node2, port1=None, port2=None,
|
||||
cls=None, **params ):
|
||||
""""Add a link from node1 to node2
|
||||
node1: source node (or name)
|
||||
node2: dest node (or name)
|
||||
port1: source port (optional)
|
||||
port2: dest port (optional)
|
||||
cls: link class (optional)
|
||||
params: additional link params (optional)
|
||||
node1: source node
|
||||
node2: dest node
|
||||
port1: source port
|
||||
port2: dest port
|
||||
returns: link object"""
|
||||
# Accept node objects or names
|
||||
node1 = node1 if not isinstance( node1, basestring ) else self[ node1 ]
|
||||
node2 = node2 if not isinstance( node2, basestring ) else self[ node2 ]
|
||||
options = dict( params )
|
||||
# Port is optional
|
||||
if port1 is not None:
|
||||
options.setdefault( 'port1', port1 )
|
||||
if port2 is not None:
|
||||
options.setdefault( 'port2', port2 )
|
||||
# Set default MAC - this should probably be in Link
|
||||
options.setdefault( 'addr1', self.randMac() )
|
||||
options.setdefault( 'addr2', self.randMac() )
|
||||
cls = self.link if cls is None else cls
|
||||
link = cls( node1, node2, **options )
|
||||
mac1 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
|
||||
mac2 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
|
||||
defaults = { 'port1': port1,
|
||||
'port2': port2,
|
||||
'addr1': mac1,
|
||||
'addr2': mac2,
|
||||
'intf': self.intf }
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.link
|
||||
link = cls( node1, node2, **defaults )
|
||||
self.links.append( link )
|
||||
return link
|
||||
|
||||
@@ -398,7 +377,7 @@ class Mininet( object ):
|
||||
# Add a default controller
|
||||
info( '*** Adding controller\n' )
|
||||
classes = self.controller
|
||||
if not isinstance( classes, list ):
|
||||
if type( classes ) is not list:
|
||||
classes = [ classes ]
|
||||
for i, cls in enumerate( classes ):
|
||||
# Allow Controller objects because nobody understands currying
|
||||
@@ -418,10 +397,12 @@ class Mininet( object ):
|
||||
info( switchName + ' ' )
|
||||
|
||||
info( '\n*** Adding links:\n' )
|
||||
for srcName, dstName, params in topo.links(
|
||||
sort=True, withInfo=True ):
|
||||
self.addLink( **params )
|
||||
info( '(%s, %s) ' % ( srcName, dstName ) )
|
||||
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' )
|
||||
|
||||
@@ -474,9 +455,7 @@ class Mininet( object ):
|
||||
self.build()
|
||||
info( '*** Starting controller\n' )
|
||||
for controller in self.controllers:
|
||||
info( controller.name + ' ')
|
||||
controller.start()
|
||||
info( '\n' )
|
||||
info( '*** Starting %s switches\n' % len( self.switches ) )
|
||||
for switch in self.switches:
|
||||
info( switch.name + ' ')
|
||||
@@ -496,8 +475,7 @@ class Mininet( object ):
|
||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||
self.stopXterms()
|
||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
||||
for swclass, switches in groupby(
|
||||
sorted( self.switches, key=type ), type ):
|
||||
for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
|
||||
if hasattr( swclass, 'batchShutdown' ):
|
||||
swclass.batchShutdown( switches )
|
||||
for switch in self.switches:
|
||||
@@ -532,13 +510,13 @@ class Mininet( object ):
|
||||
if hosts is None:
|
||||
hosts = self.hosts
|
||||
poller = select.poll()
|
||||
h1 = hosts[ 0 ] # so we can call class method fdToNode
|
||||
Node = hosts[ 0 ] # so we can call class method fdToNode
|
||||
for host in hosts:
|
||||
poller.register( host.stdout )
|
||||
while True:
|
||||
ready = poller.poll( timeoutms )
|
||||
for fd, event in ready:
|
||||
host = h1.fdToNode( fd )
|
||||
host = Node.fdToNode( fd )
|
||||
if event & select.POLLIN:
|
||||
line = host.readline()
|
||||
if line is not None:
|
||||
@@ -585,8 +563,7 @@ class Mininet( object ):
|
||||
if timeout:
|
||||
opts = '-W %s' % timeout
|
||||
if dest.intfs:
|
||||
result = node.cmd( 'ping -c1 %s %s' %
|
||||
(opts, dest.IP()) )
|
||||
result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
|
||||
sent, received = self._parsePing( result )
|
||||
else:
|
||||
sent, received = 0, 0
|
||||
@@ -711,23 +688,21 @@ class Mininet( object ):
|
||||
|
||||
# XXX This should be cleaned up
|
||||
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
|
||||
seconds=5):
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
|
||||
"""Run iperf between two hosts.
|
||||
hosts: list of hosts; if None, uses first and last hosts
|
||||
hosts: list of hosts; if None, uses opposite hosts
|
||||
l4Type: string, one of [ TCP, UDP ]
|
||||
udpBw: bandwidth target for UDP test
|
||||
fmt: iperf format argument if any
|
||||
seconds: iperf time to transmit
|
||||
returns: two-element array of [ server, client ] speeds
|
||||
returns: results two-element array of [ server, client ] speeds
|
||||
note: send() is buffered, so client rate can be much higher than
|
||||
the actual transmission rate; on an unloaded system, server
|
||||
rate should be much closer to the actual receive rate"""
|
||||
if not quietRun( 'which telnet' ):
|
||||
error( 'Cannot find telnet in $PATH - required for iperf test' )
|
||||
return
|
||||
hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
|
||||
assert len( hosts ) == 2
|
||||
if not hosts:
|
||||
hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
|
||||
else:
|
||||
assert len( hosts ) == 2
|
||||
client, server = hosts
|
||||
output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
|
||||
output( "%s and %s\n" % ( client.name, server.name ) )
|
||||
@@ -739,8 +714,8 @@ class Mininet( object ):
|
||||
bwArgs = '-b ' + udpBw + ' '
|
||||
elif l4Type != 'TCP':
|
||||
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
||||
if fmt:
|
||||
iperfArgs += '-f %s ' % fmt
|
||||
if format:
|
||||
iperfArgs += '-f %s ' %format
|
||||
server.sendCmd( iperfArgs + '-s', printPid=True )
|
||||
servout = ''
|
||||
while server.lastPid is None:
|
||||
@@ -750,8 +725,8 @@ class Mininet( object ):
|
||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
||||
info( 'Waiting for iperf to start up...' )
|
||||
sleep(.5)
|
||||
cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
|
||||
server.IP() + ' ' + bwArgs )
|
||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||
bwArgs )
|
||||
debug( 'Client output: %s\n' % cliout )
|
||||
server.sendInt()
|
||||
servout += server.waitOutput()
|
||||
@@ -765,7 +740,7 @@ class Mininet( object ):
|
||||
def runCpuLimitTest( self, cpu, duration=5 ):
|
||||
"""run CPU limit test with 'while true' processes.
|
||||
cpu: desired CPU fraction of each host
|
||||
duration: test duration in seconds (integer)
|
||||
duration: test duration in seconds
|
||||
returns a single list of measured CPU fractions as floats.
|
||||
"""
|
||||
cores = int( quietRun( 'nproc' ) )
|
||||
@@ -786,14 +761,12 @@ class Mininet( object ):
|
||||
# get the initial cpu time for each host
|
||||
for host in hosts:
|
||||
outputs[ host ] = []
|
||||
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
|
||||
host, 'r' ) as f:
|
||||
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
|
||||
time[ host ] = float( f.read() )
|
||||
for _ in range( duration ):
|
||||
for _ in range( 5 ):
|
||||
sleep( 1 )
|
||||
for host in hosts:
|
||||
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
|
||||
host, 'r' ) as f:
|
||||
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
|
||||
readTime = float( f.read() )
|
||||
outputs[ host ].append( ( ( readTime - time[ host ] )
|
||||
/ 1000000000 ) / cores * 100 )
|
||||
@@ -820,9 +793,9 @@ class Mininet( object ):
|
||||
elif dst not in self.nameToNode:
|
||||
error( 'dst not in network: %s\n' % dst )
|
||||
else:
|
||||
if isinstance( src, basestring ):
|
||||
if type( src ) is str:
|
||||
src = self.nameToNode[ src ]
|
||||
if isinstance( dst, basestring ):
|
||||
if type( dst ) is str:
|
||||
dst = self.nameToNode[ dst ]
|
||||
connections = src.connectionsTo( dst )
|
||||
if len( connections ) == 0:
|
||||
|
||||
+122
-181
@@ -11,13 +11,16 @@ Host: a virtual host. By default, a host is simply a shell; commands
|
||||
may be sent using Cmd (which waits for output), or using sendCmd(),
|
||||
which returns immediately, allowing subsequent monitoring using
|
||||
monitor(). Examples of how to run experiments using this
|
||||
functionality are provided in the examples/ directory. By default,
|
||||
hosts share the root file system, but they may also specify private
|
||||
directories.
|
||||
functionality are provided in the examples/ directory.
|
||||
|
||||
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
|
||||
RT or CFS bandwidth limiting.
|
||||
|
||||
HostWithPrivateDirs: a virtual host that has user-specified private
|
||||
directories. These may be temporary directories stored as a tmpfs,
|
||||
or persistent directories that are mounted from another directory in
|
||||
the root filesystem.
|
||||
|
||||
Switch: superclass for switch nodes.
|
||||
|
||||
UserSwitch: a switch using the user-space switch from the OpenFlow
|
||||
@@ -51,7 +54,7 @@ import pty
|
||||
import re
|
||||
import signal
|
||||
import select
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
from operator import or_
|
||||
from time import sleep
|
||||
|
||||
@@ -72,14 +75,12 @@ class Node( object ):
|
||||
def __init__( self, name, inNamespace=True, **params ):
|
||||
"""name: name of node
|
||||
inNamespace: in network namespace?
|
||||
privateDirs: list of private directory strings or tuples
|
||||
params: Node parameters (see config() for details)"""
|
||||
|
||||
# Make sure class actually works
|
||||
self.checkSetup()
|
||||
|
||||
self.name = params.get( 'name', name )
|
||||
self.privateDirs = params.get( 'privateDirs', [] )
|
||||
self.inNamespace = params.get( 'inNamespace', inNamespace )
|
||||
|
||||
# Stash configuration parameters for future reference
|
||||
@@ -99,7 +100,6 @@ class Node( object ):
|
||||
|
||||
# Start command interpreter shell
|
||||
self.startShell()
|
||||
self.mountPrivateDirs()
|
||||
|
||||
# File descriptor to node mapping support
|
||||
# Class variables and methods
|
||||
@@ -129,7 +129,7 @@ class Node( object ):
|
||||
# bash -m: enable job control, i: force interactive
|
||||
# -s: pass $* to shell, and make process easy to find in ps
|
||||
# prompt is set to sentinel chr( 127 )
|
||||
cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ),
|
||||
cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ),
|
||||
'bash', '--norc', '-mis', 'mininet:' + self.name ]
|
||||
# Spawn a shell subprocess in a pseudo-tty, to disable buffering
|
||||
# in the subprocess and insulate it from signals (e.g. SIGINT)
|
||||
@@ -161,36 +161,10 @@ class Node( object ):
|
||||
self.cmd( 'stty -echo' )
|
||||
self.cmd( 'set +m' )
|
||||
|
||||
def mountPrivateDirs( self ):
|
||||
"mount private directories"
|
||||
for directory in self.privateDirs:
|
||||
if isinstance( directory, tuple ):
|
||||
# mount given private directory
|
||||
privateDir = directory[ 1 ] % self.__dict__
|
||||
mountPoint = directory[ 0 ]
|
||||
self.cmd( 'mkdir -p %s' % privateDir )
|
||||
self.cmd( 'mkdir -p %s' % mountPoint )
|
||||
self.cmd( 'mount --bind %s %s' %
|
||||
( privateDir, mountPoint ) )
|
||||
else:
|
||||
# mount temporary filesystem on directory
|
||||
self.cmd( 'mkdir -p %s' % directory )
|
||||
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
|
||||
|
||||
def unmountPrivateDirs( self ):
|
||||
"mount private directories"
|
||||
for directory in self.privateDirs:
|
||||
if isinstance( directory, tuple ):
|
||||
self.cmd( 'umount ', directory[ 0 ] )
|
||||
else:
|
||||
self.cmd( 'umount ', directory )
|
||||
|
||||
def _popen( self, cmd, **params ):
|
||||
"""Internal method: spawn and return a process
|
||||
cmd: command to run (list)
|
||||
params: parameters to Popen()"""
|
||||
# Leave this is as an instance method for now
|
||||
assert self
|
||||
return Popen( cmd, **params )
|
||||
|
||||
def cleanup( self ):
|
||||
@@ -236,17 +210,13 @@ class Node( object ):
|
||||
|
||||
def terminate( self ):
|
||||
"Send kill signal to Node and clean up after it."
|
||||
self.unmountPrivateDirs()
|
||||
if self.shell:
|
||||
if self.shell.poll() is None:
|
||||
os.killpg( self.shell.pid, signal.SIGHUP )
|
||||
self.cleanup()
|
||||
|
||||
def stop( self, deleteIntfs=False ):
|
||||
"""Stop node.
|
||||
deleteIntfs: delete interfaces? (False)"""
|
||||
if deleteIntfs:
|
||||
self.deleteIntfs()
|
||||
def stop( self ):
|
||||
"Stop node."
|
||||
self.terminate()
|
||||
|
||||
def waitReadable( self, timeoutms=None ):
|
||||
@@ -263,7 +233,7 @@ class Node( object ):
|
||||
assert not self.waiting
|
||||
printPid = kwargs.get( 'printPid', True )
|
||||
# Allow sendCmd( [ list ] )
|
||||
if len( args ) == 1 and isinstance( args[ 0 ], list ):
|
||||
if len( args ) == 1 and type( args[ 0 ] ) is list:
|
||||
cmd = args[ 0 ]
|
||||
# Allow sendCmd( cmd, arg1, arg2... )
|
||||
elif len( args ) > 0:
|
||||
@@ -329,7 +299,7 @@ class Node( object ):
|
||||
log = info if verbose else debug
|
||||
output = ''
|
||||
while self.waiting:
|
||||
data = self.monitor( findPid=findPid )
|
||||
data = self.monitor()
|
||||
output += data
|
||||
log( data )
|
||||
return output
|
||||
@@ -357,10 +327,10 @@ class Node( object ):
|
||||
[ 'mnexec', '-da', str( self.pid ) ] }
|
||||
defaults.update( kwargs )
|
||||
if len( args ) == 1:
|
||||
if isinstance( args[ 0 ], list ):
|
||||
if type( args[ 0 ] ) is list:
|
||||
# popen([cmd, arg1, arg2...])
|
||||
cmd = args[ 0 ]
|
||||
elif isinstance( args[ 0 ], basestring ):
|
||||
elif type( args[ 0 ] ) is str:
|
||||
# popen("cmd arg1 arg2...")
|
||||
cmd = args[ 0 ].split()
|
||||
else:
|
||||
@@ -380,7 +350,7 @@ class Node( object ):
|
||||
"""Execute a command using popen
|
||||
returns: out, err, exitcode"""
|
||||
popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
|
||||
**kwargs )
|
||||
**kwargs )
|
||||
# Warning: this can fail with large numbers of fds!
|
||||
out, err = popen.communicate()
|
||||
exitcode = popen.wait()
|
||||
@@ -426,7 +396,7 @@ class Node( object ):
|
||||
warn( '*** defaultIntf: warning:', self.name,
|
||||
'has no interfaces\n' )
|
||||
|
||||
def intf( self, intf=None ):
|
||||
def intf( self, intf='' ):
|
||||
"""Return our interface object with given string name,
|
||||
default intf if name is falsy (None, empty string, etc).
|
||||
or the input intf arg.
|
||||
@@ -437,7 +407,7 @@ class Node( object ):
|
||||
"""
|
||||
if not intf:
|
||||
return self.defaultIntf()
|
||||
elif isinstance( intf, basestring):
|
||||
elif type( intf ) is str:
|
||||
return self.nameToIntf[ intf ]
|
||||
else:
|
||||
return intf
|
||||
@@ -489,12 +459,12 @@ class Node( object ):
|
||||
"""Set the default route to go through intf.
|
||||
intf: Intf or {dev <intfname> via <gw-ip> ...}"""
|
||||
# Note setParam won't call us if intf is none
|
||||
if isinstance( intf, basestring ) and ' ' in intf:
|
||||
if type( intf ) is str and ' ' in intf:
|
||||
params = intf
|
||||
else:
|
||||
params = 'dev %s' % intf
|
||||
# Do this in one line in case we're messing with the root namespace
|
||||
self.cmd( 'ip route del default; ip route add default', params )
|
||||
self.cmd( 'ip route del default' )
|
||||
return self.cmd( 'ip route add default', params )
|
||||
|
||||
# Convenience and configuration methods
|
||||
|
||||
@@ -539,14 +509,12 @@ class Node( object ):
|
||||
param: arg=value (ignore if value=None)
|
||||
value may also be list or dict"""
|
||||
name, value = param.items()[ 0 ]
|
||||
if value is None:
|
||||
return
|
||||
f = getattr( self, method, None )
|
||||
if not f:
|
||||
if not f or value is None:
|
||||
return
|
||||
if isinstance( value, list ):
|
||||
if type( value ) is list:
|
||||
result = f( *value )
|
||||
elif isinstance( value, dict ):
|
||||
elif type( value ) is dict:
|
||||
result = f( **value )
|
||||
else:
|
||||
result = f( value )
|
||||
@@ -622,10 +590,12 @@ class Node( object ):
|
||||
"Make sure our class dependencies are available"
|
||||
pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
|
||||
|
||||
|
||||
class Host( Node ):
|
||||
"A host is simply a Node"
|
||||
pass
|
||||
|
||||
|
||||
class CPULimitedHost( Host ):
|
||||
|
||||
"CPU limited host"
|
||||
@@ -647,9 +617,13 @@ class CPULimitedHost( Host ):
|
||||
# still does better with larger period values.
|
||||
self.period_us = kwargs.get( 'period_us', 100000 )
|
||||
self.sched = sched
|
||||
if sched == 'rt':
|
||||
self.checkRtGroupSched()
|
||||
self.rtprio = 20
|
||||
if self.sched == 'rt':
|
||||
release = quietRun( 'uname -r' ).strip('\r\n')
|
||||
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
|
||||
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
|
||||
error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
|
||||
exit( 1 )
|
||||
self.rtprio = 20
|
||||
|
||||
def cgroupSet( self, param, value, resource='cpu' ):
|
||||
"Set a cgroup parameter and return its value"
|
||||
@@ -681,14 +655,8 @@ class CPULimitedHost( Host ):
|
||||
# Tell mnexec to execute command in our cgroup
|
||||
mncmd = [ 'mnexec', '-g', self.name,
|
||||
'-da', str( self.pid ) ]
|
||||
# if our cgroup is not given any cpu time,
|
||||
# we cannot assign the RR Scheduler.
|
||||
if self.sched == 'rt':
|
||||
if int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) ) <= 0:
|
||||
mncmd += [ '-r', str( self.rtprio ) ]
|
||||
else:
|
||||
debug( '*** error: not enough cpu time available for %s.' %
|
||||
self.name, 'Using cfs scheduler for subprocess\n' )
|
||||
mncmd += [ '-r', str( self.rtprio ) ]
|
||||
return Host.popen( self, *args, mncmd=mncmd, **kwargs )
|
||||
|
||||
def cleanup( self ):
|
||||
@@ -696,21 +664,6 @@ class CPULimitedHost( Host ):
|
||||
super( CPULimitedHost, self ).cleanup()
|
||||
retry( retries=3, delaySecs=1, fn=self.cgroupDel )
|
||||
|
||||
_rtGroupSched = False # internal class var: Is CONFIG_RT_GROUP_SCHED set?
|
||||
|
||||
@classmethod
|
||||
def checkRtGroupSched( cls ):
|
||||
"Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT"
|
||||
if not cls._rtGroupSched:
|
||||
release = quietRun( 'uname -r' ).strip('\r\n')
|
||||
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' %
|
||||
release )
|
||||
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
|
||||
error( '\n*** error: please enable RT_GROUP_SCHED '
|
||||
'in your kernel\n' )
|
||||
exit( 1 )
|
||||
cls._rtGroupSched = True
|
||||
|
||||
def chrt( self ):
|
||||
"Set RT scheduling priority"
|
||||
quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
|
||||
@@ -728,7 +681,7 @@ class CPULimitedHost( Host ):
|
||||
quota = int( self.period_us * f )
|
||||
return pstr, qstr, self.period_us, quota
|
||||
|
||||
def cfsInfo( self, f ):
|
||||
def cfsInfo( self, f):
|
||||
"Internal method: return parameters for CFS bandwidth"
|
||||
pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
|
||||
# CFS uses wall clock time for period and CPU time for quota.
|
||||
@@ -738,9 +691,6 @@ class CPULimitedHost( Host ):
|
||||
debug( '(cfsInfo: increasing default period) ' )
|
||||
quota = 1000
|
||||
period = int( quota / f / numCores() )
|
||||
# Reset to unlimited on negative quota
|
||||
if quota < 0:
|
||||
quota = -1
|
||||
return pstr, qstr, period, quota
|
||||
|
||||
# BL comment:
|
||||
@@ -752,36 +702,35 @@ class CPULimitedHost( Host ):
|
||||
# to CPU seconds per second, essentially assuming that
|
||||
# all CPUs are the same.
|
||||
|
||||
def setCPUFrac( self, f, sched=None ):
|
||||
def setCPUFrac( self, f=-1, sched=None):
|
||||
"""Set overall CPU fraction for this host
|
||||
f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited)
|
||||
f: CPU bandwidth limit (fraction)
|
||||
sched: 'rt' or 'cfs'
|
||||
Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
|
||||
and 'rt' requires CONFIG_RT_GROUP_SCHED"""
|
||||
Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
|
||||
if not f:
|
||||
return
|
||||
if not sched:
|
||||
sched = self.sched
|
||||
if sched == 'rt':
|
||||
if not f or f < 0:
|
||||
raise Exception( 'Please set a positive CPU fraction'
|
||||
' for sched=rt\n' )
|
||||
pstr, qstr, period, quota = self.rtInfo( f )
|
||||
elif sched == 'cfs':
|
||||
pstr, qstr, period, quota = self.cfsInfo( f )
|
||||
else:
|
||||
return
|
||||
if quota < 0:
|
||||
# Reset to unlimited
|
||||
quota = -1
|
||||
# Set cgroup's period and quota
|
||||
setPeriod = self.cgroupSet( pstr, period )
|
||||
setQuota = self.cgroupSet( qstr, quota )
|
||||
self.cgroupSet( pstr, period )
|
||||
self.cgroupSet( qstr, quota )
|
||||
if sched == 'rt':
|
||||
# Set RT priority if necessary
|
||||
sched = self.chrt()
|
||||
info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
|
||||
self.chrt()
|
||||
info( '(%s %d/%dus) ' % ( sched, quota, period ) )
|
||||
|
||||
def setCPUs( self, cores, mems=0 ):
|
||||
"Specify (real) cores that our cgroup can run on"
|
||||
if not cores:
|
||||
return
|
||||
if isinstance( cores, list ):
|
||||
if type( cores ) is list:
|
||||
cores = ','.join( [ str( c ) for c in cores ] )
|
||||
self.cgroupSet( resource='cpuset', param='cpus',
|
||||
value=cores )
|
||||
@@ -794,7 +743,7 @@ class CPULimitedHost( Host ):
|
||||
errFail( 'cgclassify -g cpuset:/%s %s' % (
|
||||
self.name, self.pid ) )
|
||||
|
||||
def config( self, cpu=-1, cores=None, **params ):
|
||||
def config( self, cpu=None, cores=None, **params ):
|
||||
"""cpu: desired overall system CPU fraction
|
||||
cores: (real) core(s) this host can run on
|
||||
params: parameters for Node.config()"""
|
||||
@@ -813,6 +762,33 @@ class CPULimitedHost( Host ):
|
||||
mountCgroups()
|
||||
cls.inited = True
|
||||
|
||||
class HostWithPrivateDirs( Host ):
|
||||
"Host with private directories"
|
||||
|
||||
def __init__( self, name, *args, **kwargs ):
|
||||
"privateDirs: list of private directory strings or tuples"
|
||||
self.name = name
|
||||
self.privateDirs = kwargs.pop( 'privateDirs', [] )
|
||||
Host.__init__( self, name, *args, **kwargs )
|
||||
self.mountPrivateDirs()
|
||||
|
||||
def mountPrivateDirs( self ):
|
||||
"mount private directories"
|
||||
for directory in self.privateDirs:
|
||||
if isinstance( directory, tuple ):
|
||||
# mount given private directory
|
||||
privateDir = directory[ 1 ] % self.__dict__
|
||||
mountPoint = directory[ 0 ]
|
||||
self.cmd( 'mkdir -p %s' % privateDir )
|
||||
self.cmd( 'mkdir -p %s' % mountPoint )
|
||||
self.cmd( 'mount --bind %s %s' %
|
||||
( privateDir, mountPoint ) )
|
||||
else:
|
||||
# mount temporary filesystem on directory
|
||||
self.cmd( 'mkdir -p %s' % directory )
|
||||
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
|
||||
|
||||
|
||||
|
||||
# Some important things to note:
|
||||
#
|
||||
@@ -888,11 +864,7 @@ class Switch( Node ):
|
||||
|
||||
def connected( self ):
|
||||
"Is the switch connected to a controller? (override this method)"
|
||||
# Assume that we are connected by default to whatever we need to
|
||||
# be connected to. This should be overridden by any OpenFlow
|
||||
# switch, but not by a standalone bridge.
|
||||
debug( 'Assuming', repr( self ), 'is connected to a controller\n' )
|
||||
return True
|
||||
return False and self # satisfy pylint
|
||||
|
||||
def __repr__( self ):
|
||||
"More informative string representation"
|
||||
@@ -901,7 +873,6 @@ class Switch( Node ):
|
||||
return '<%s %s: %s pid=%s> ' % (
|
||||
self.__class__.__name__, self.name, intfs, self.pid )
|
||||
|
||||
|
||||
class UserSwitch( Switch ):
|
||||
"User-space switch."
|
||||
|
||||
@@ -949,13 +920,13 @@ class UserSwitch( Switch ):
|
||||
over tc queuing disciplines. To resolve the conflict,
|
||||
we re-create the user switch's configuration, but as a
|
||||
leaf of the TCIntf-created configuration."""
|
||||
if isinstance( intf, TCIntf ):
|
||||
ifspeed = 10000000000 # 10 Gbps
|
||||
if type( intf ) is TCIntf:
|
||||
ifspeed = 10000000000 # 10 Gbps
|
||||
minspeed = ifspeed * 0.001
|
||||
|
||||
res = intf.config( **intf.params )
|
||||
|
||||
if res is None: # link may not have TC parameters
|
||||
if res is None: # link may not have TC parameters
|
||||
return
|
||||
|
||||
# Re-add qdisc, root, and default classes user switch created, but
|
||||
@@ -988,17 +959,17 @@ class UserSwitch( Switch ):
|
||||
' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
|
||||
if "no-slicing" not in self.dpopts:
|
||||
# Only TCReapply if slicing is enable
|
||||
sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
|
||||
sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
|
||||
for intf in self.intfList():
|
||||
if not intf.IP():
|
||||
self.TCReapply( intf )
|
||||
|
||||
def stop( self, deleteIntfs=True ):
|
||||
"""Stop OpenFlow reference user datapath.
|
||||
deleteIntfs: delete interfaces? (True)"""
|
||||
def stop( self ):
|
||||
"Stop OpenFlow reference user datapath."
|
||||
self.cmd( 'kill %ofdatapath' )
|
||||
self.cmd( 'kill %ofprotocol' )
|
||||
super( UserSwitch, self ).stop( deleteIntfs )
|
||||
self.deleteIntfs()
|
||||
|
||||
|
||||
class OVSLegacyKernelSwitch( Switch ):
|
||||
"""Open VSwitch legacy kernel-space switch using ovs-openflowd.
|
||||
@@ -1043,19 +1014,18 @@ class OVSLegacyKernelSwitch( Switch ):
|
||||
' 1>' + ofplog + ' 2>' + ofplog + '&' )
|
||||
self.execed = False
|
||||
|
||||
def stop( self, deleteIntfs=True ):
|
||||
"""Terminate kernel datapath."
|
||||
deleteIntfs: delete interfaces? (True)"""
|
||||
def stop( self ):
|
||||
"Terminate kernel datapath."
|
||||
quietRun( 'ovs-dpctl del-dp ' + self.dp )
|
||||
self.cmd( 'kill %ovs-openflowd' )
|
||||
super( OVSLegacyKernelSwitch, self ).stop( deleteIntfs )
|
||||
self.deleteIntfs()
|
||||
|
||||
|
||||
class OVSSwitch( Switch ):
|
||||
"Open vSwitch switch. Depends on ovs-vsctl."
|
||||
|
||||
def __init__( self, name, failMode='secure', datapath='kernel',
|
||||
inband=False, protocols=None, **params ):
|
||||
inband=False, protocols=None, **params ):
|
||||
"""Init.
|
||||
name: name for switch
|
||||
failMode: controller loss behavior (secure|open)
|
||||
@@ -1086,14 +1056,13 @@ class OVSSwitch( Switch ):
|
||||
'You may wish to try '
|
||||
'"service openvswitch-switch start".\n' )
|
||||
exit( 1 )
|
||||
version = quietRun( 'ovs-vsctl --version' )
|
||||
cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
|
||||
info = quietRun( 'ovs-vsctl --version' )
|
||||
cls.OVSVersion = findall( '\d+\.\d+', info )[ 0 ]
|
||||
|
||||
@classmethod
|
||||
def isOldOVS( cls ):
|
||||
"Is OVS ersion < 1.10?"
|
||||
return ( StrictVersion( cls.OVSVersion ) <
|
||||
StrictVersion( '1.10' ) )
|
||||
StrictVersion( '1.10' ) )
|
||||
|
||||
@classmethod
|
||||
def batchShutdown( cls, switches ):
|
||||
@@ -1111,7 +1080,7 @@ class OVSSwitch( Switch ):
|
||||
"""Unfortunately OVS and Mininet are fighting
|
||||
over tc queuing disciplines. As a quick hack/
|
||||
workaround, we clear OVS's and reapply our own."""
|
||||
if isinstance( intf, TCIntf ):
|
||||
if type( intf ) is TCIntf:
|
||||
intf.config( **intf.params )
|
||||
|
||||
def attach( self, intf ):
|
||||
@@ -1128,7 +1097,7 @@ class OVSSwitch( Switch ):
|
||||
"Return ovsdb UUIDs for our controllers"
|
||||
uuids = []
|
||||
controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
|
||||
'Controller' ).strip()
|
||||
'Controller' ).strip()
|
||||
if controllers.startswith( '[' ) and controllers.endswith( ']' ):
|
||||
controllers = controllers[ 1 : -1 ]
|
||||
uuids = [ c.strip() for c in controllers.split( ',' ) ]
|
||||
@@ -1137,7 +1106,7 @@ class OVSSwitch( Switch ):
|
||||
def connected( self ):
|
||||
"Are we connected to at least one of our controllers?"
|
||||
results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
|
||||
uuid, 'is_connected' )
|
||||
uuid, 'is_connected' )
|
||||
for uuid in self.controllerUUIDs() ]
|
||||
return reduce( or_, results, False )
|
||||
|
||||
@@ -1148,15 +1117,15 @@ class OVSSwitch( Switch ):
|
||||
'OVS kernel switch does not work in a namespace' )
|
||||
# Annoyingly, --if-exists option seems not to work
|
||||
self.cmd( 'ovs-vsctl del-br', self )
|
||||
int( self.dpid, 16 ) # DPID must be a hex string
|
||||
int( self.dpid, 16 ) # DPID must be a hex string
|
||||
# Interfaces and controllers
|
||||
intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
|
||||
'-- set Interface %s ' % intf +
|
||||
'ofport_request=%s ' % self.ports[ intf ]
|
||||
for intf in self.intfList()
|
||||
if self.ports[ intf ] and not intf.IP() )
|
||||
for intf in self.intfList()
|
||||
if self.ports[ intf ] and not intf.IP() )
|
||||
clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
|
||||
for c in controllers )
|
||||
for c in controllers )
|
||||
if self.listenPort:
|
||||
clist += ' ptcp:%s' % self.listenPort
|
||||
# Construct big ovs-vsctl command for new versions of OVS
|
||||
@@ -1196,33 +1165,21 @@ class OVSSwitch( Switch ):
|
||||
for intf in self.intfList():
|
||||
self.TCReapply( intf )
|
||||
|
||||
def stop( self, deleteIntfs=True ):
|
||||
"""Terminate OVS switch.
|
||||
deleteIntfs: delete interfaces? (True)"""
|
||||
|
||||
def stop( self ):
|
||||
"Terminate OVS switch."
|
||||
self.cmd( 'ovs-vsctl del-br', self )
|
||||
if self.datapath == 'user':
|
||||
self.cmd( 'ip link del', self )
|
||||
super( OVSSwitch, self ).stop( deleteIntfs )
|
||||
|
||||
self.deleteIntfs()
|
||||
|
||||
OVSKernelSwitch = OVSSwitch
|
||||
|
||||
|
||||
class OVSBridge( OVSSwitch ):
|
||||
"OVSBridge is an OVSSwitch in standalone/bridge mode"
|
||||
class IVSSwitch(Switch):
|
||||
"""IVS virtual switch"""
|
||||
|
||||
def __init__( self, args, **kwargs ):
|
||||
kwargs.update( failMode='standalone' )
|
||||
OVSSwitch.__init__( self, args, **kwargs )
|
||||
|
||||
def start( self, controllers ):
|
||||
OVSSwitch.start( self, controllers=[] )
|
||||
|
||||
|
||||
class IVSSwitch( Switch ):
|
||||
"Indigo Virtual Switch"
|
||||
|
||||
def __init__( self, name, verbose=False, **kwargs ):
|
||||
def __init__( self, name, verbose=True, **kwargs ):
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
self.verbose = verbose
|
||||
|
||||
@@ -1265,12 +1222,11 @@ class IVSSwitch( Switch ):
|
||||
|
||||
self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
|
||||
|
||||
def stop( self, deleteIntfs=True ):
|
||||
"""Terminate IVS switch.
|
||||
deleteIntfs: delete interfaces? (True)"""
|
||||
def stop( self ):
|
||||
"Terminate IVS switch."
|
||||
self.cmd( 'kill %ivs' )
|
||||
self.cmd( 'wait' )
|
||||
super( IVSSwitch, self ).stop( deleteIntfs )
|
||||
self.deleteIntfs()
|
||||
|
||||
def attach( self, intf ):
|
||||
"Connect a data port"
|
||||
@@ -1334,11 +1290,11 @@ class Controller( Node ):
|
||||
' 1>' + cout + ' 2>' + cout + ' &' )
|
||||
self.execed = False
|
||||
|
||||
def stop( self, *args, **kwargs ):
|
||||
def stop( self ):
|
||||
"Stop controller."
|
||||
self.cmd( 'kill %' + self.command )
|
||||
self.cmd( 'wait %' + self.command )
|
||||
super( Controller, self ).stop( *args, **kwargs )
|
||||
self.terminate()
|
||||
|
||||
def IP( self, intf=None ):
|
||||
"Return IP address of the Controller"
|
||||
@@ -1353,24 +1309,19 @@ class Controller( Node ):
|
||||
return '<%s %s: %s:%s pid=%s> ' % (
|
||||
self.__class__.__name__, self.name,
|
||||
self.IP(), self.port, self.pid )
|
||||
|
||||
@classmethod
|
||||
def isAvailable( cls ):
|
||||
"Is controller available?"
|
||||
def isAvailable( self ):
|
||||
return quietRun( 'which controller' )
|
||||
|
||||
|
||||
class OVSController( Controller ):
|
||||
"Open vSwitch controller"
|
||||
def __init__( self, name, command='ovs-controller', **kwargs ):
|
||||
if quietRun( 'which test-controller' ):
|
||||
command = 'test-controller'
|
||||
Controller.__init__( self, name, command=command, **kwargs )
|
||||
|
||||
@classmethod
|
||||
def isAvailable( cls ):
|
||||
return ( quietRun( 'which ovs-controller' ) or
|
||||
quietRun( 'which test-controller' ) )
|
||||
def isAvailable( self ):
|
||||
return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
|
||||
|
||||
class NOX( Controller ):
|
||||
"Controller to run a NOX application."
|
||||
@@ -1413,11 +1364,11 @@ class RYU( Controller ):
|
||||
ryuArgs = [ ryuArgs ]
|
||||
|
||||
Controller.__init__( self, name,
|
||||
command='ryu-manager',
|
||||
cargs='--ofp-tcp-listen-port %s ' +
|
||||
' '.join( ryuArgs ),
|
||||
cdir=ryuCoreDir,
|
||||
**kwargs )
|
||||
command='ryu-manager',
|
||||
cargs='--ofp-tcp-listen-port %s ' +
|
||||
' '.join( ryuArgs ),
|
||||
cdir=ryuCoreDir,
|
||||
**kwargs )
|
||||
|
||||
class RemoteController( Controller ):
|
||||
"Controller running outside of Mininet's control."
|
||||
@@ -1447,18 +1398,8 @@ class RemoteController( Controller ):
|
||||
warn( "Unable to contact the remote controller"
|
||||
" at %s:%d\n" % ( self.ip, self.port ) )
|
||||
|
||||
|
||||
DefaultControllers = ( Controller, OVSController )
|
||||
|
||||
def findController( controllers=DefaultControllers ):
|
||||
"Return first available controller from list, if any"
|
||||
for controller in controllers:
|
||||
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
|
||||
"find any controller that is available and run it"
|
||||
for controller in order:
|
||||
if controller.isAvailable():
|
||||
return controller
|
||||
|
||||
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
|
||||
"Find a controller that is available and instantiate it"
|
||||
controller = findController( controllers )
|
||||
if not controller:
|
||||
raise Exception( 'Could not find a default OpenFlow controller' )
|
||||
return controller( name, **kwargs )
|
||||
return controller( name, **kwargs )
|
||||
|
||||
+18
-51
@@ -5,10 +5,7 @@ This contains additional Node types which you may find to be useful.
|
||||
"""
|
||||
|
||||
from mininet.node import Node, Switch
|
||||
from mininet.log import info, warn
|
||||
from mininet.moduledeps import pathCheck
|
||||
|
||||
import re
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
class LinuxBridge( Switch ):
|
||||
"Linux Bridge (with optional spanning tree)"
|
||||
@@ -32,9 +29,8 @@ class LinuxBridge( Switch ):
|
||||
return 'forwarding' in self.cmd( 'brctl showstp', self )
|
||||
else:
|
||||
return True
|
||||
|
||||
def start( self, _controllers ):
|
||||
"Start Linux bridge"
|
||||
|
||||
def start( self, controllers ):
|
||||
self.cmd( 'ifconfig', self, 'down' )
|
||||
self.cmd( 'brctl delbr', self )
|
||||
self.cmd( 'brctl addbr', self )
|
||||
@@ -46,43 +42,29 @@ class LinuxBridge( Switch ):
|
||||
self.cmd( 'brctl addif', self, i )
|
||||
self.cmd( 'ifconfig', self, 'up' )
|
||||
|
||||
def stop( self, deleteIntfs=True ):
|
||||
"""Stop Linux bridge
|
||||
deleteIntfs: delete interfaces? (True)"""
|
||||
def stop( self ):
|
||||
self.cmd( 'ifconfig', self, 'down' )
|
||||
self.cmd( 'brctl delbr', self )
|
||||
super( LinuxBridge, self ).stop( deleteIntfs )
|
||||
|
||||
def dpctl( self, *args ):
|
||||
"Run brctl command"
|
||||
return self.cmd( 'brctl', *args )
|
||||
|
||||
@classmethod
|
||||
def setup( cls ):
|
||||
"Make sure our class dependencies are available"
|
||||
pathCheck( 'brctl', moduleName='bridge-utils' )
|
||||
|
||||
|
||||
class NAT( Node ):
|
||||
"NAT: Provides connectivity to external network"
|
||||
"""NAT: Provides connectivity to external network"""
|
||||
|
||||
def __init__( self, name, inetIntf=None, subnet='10.0/8',
|
||||
localIntf=None, **params):
|
||||
"""Start NAT/forwarding between Mininet and external network
|
||||
inetIntf: interface for internet access
|
||||
subnet: Mininet subnet (default 10.0/8)="""
|
||||
def __init__( self, name, inetIntf='eth0', subnet='10.0/8', localIntf=None, **params):
|
||||
super( NAT, self ).__init__( name, **params )
|
||||
|
||||
self.inetIntf = inetIntf if inetIntf else self.getGatewayIntf()
|
||||
"""Start NAT/forwarding between Mininet and external network
|
||||
inetIntf: interface for internet access
|
||||
subnet: Mininet subnet (default 10.0/8)="""
|
||||
self.inetIntf = inetIntf
|
||||
self.subnet = subnet
|
||||
self.localIntf = localIntf
|
||||
|
||||
def config( self, **params ):
|
||||
"""Configure the NAT and iptables"""
|
||||
super( NAT, self).config( **params )
|
||||
"""Configure the NAT and iptables"""
|
||||
|
||||
if not self.localIntf:
|
||||
self.localIntf = self.defaultIntf()
|
||||
self.localIntf = self.defaultIntf()
|
||||
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
||||
|
||||
@@ -97,14 +79,10 @@ class NAT( Node ):
|
||||
self.cmd( 'iptables -P FORWARD DROP' )
|
||||
|
||||
# Configure NAT
|
||||
self.cmd( 'iptables -I FORWARD',
|
||||
'-i', self.localIntf, '-d', self.subnet, '-j DROP' )
|
||||
self.cmd( 'iptables -A FORWARD',
|
||||
'-i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -A FORWARD',
|
||||
'-i', self.inetIntf, '-d', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -t nat -A POSTROUTING',
|
||||
'-o', self.inetIntf, '-s', self.subnet, '-j MASQUERADE' )
|
||||
self.cmd( 'iptables -I FORWARD -i', self.localIntf, '-d', self.subnet, '-j DROP' )
|
||||
self.cmd( 'iptables -A FORWARD -i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -A FORWARD -i', self.inetIntf, '-d', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -t nat -A POSTROUTING -o ', self.inetIntf, '-j MASQUERADE' )
|
||||
|
||||
# Instruct the kernel to perform forwarding
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
|
||||
@@ -116,25 +94,13 @@ class NAT( Node ):
|
||||
line = '\niface %s inet manual\n' % intf
|
||||
config = open( cfile ).read()
|
||||
if ( line ) not in config:
|
||||
info( '*** Adding "' + line.strip() + '" to ' + cfile + '\n' )
|
||||
info( '*** Adding "' + line.strip() + '" to ' + cfile )
|
||||
with open( cfile, 'a' ) as f:
|
||||
f.write( line )
|
||||
# Probably need to restart network-manager to be safe -
|
||||
# hopefully this won't disconnect you
|
||||
self.cmd( 'service network-manager restart' )
|
||||
|
||||
def getGatewayIntf( self, fallback='eth0' ):
|
||||
"""Return gateway interface name
|
||||
fallback: default device to fall back to"""
|
||||
routes = self.cmd( 'ip route show' )
|
||||
match = re.search( r'default via \S+ dev (\S+)', routes )
|
||||
if match:
|
||||
return match.group( 1 )
|
||||
else:
|
||||
warn( 'There is no default route set.',
|
||||
'Using', fallback, 'as gateway interface...\n' )
|
||||
return fallback
|
||||
|
||||
def terminate( self ):
|
||||
"""Stop NAT/forwarding between Mininet and external network"""
|
||||
# Flush any currently active rules
|
||||
@@ -146,3 +112,4 @@ class NAT( Node ):
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
||||
|
||||
super( NAT, self ).terminate()
|
||||
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ def tunnelX11( node, display=None):
|
||||
port = 6000 + int( float( screen ) )
|
||||
connection = r'TCP\:%s\:%s' % ( host, port )
|
||||
cmd = [ "socat", "TCP-LISTEN:%d,fork,reuseaddr" % port,
|
||||
"EXEC:'mnexec -a 1 socat STDIO %s'" % connection ]
|
||||
"EXEC:'mnexec -a 1 socat STDIO %s'" % connection ]
|
||||
return 'localhost:' + screen, node.popen( cmd )
|
||||
|
||||
def makeTerm( node, title='Node', term='xterm', display=None ):
|
||||
@@ -41,7 +41,7 @@ def makeTerm( node, title='Node', term='xterm', display=None ):
|
||||
title: base title
|
||||
term: 'xterm' or 'gterm'
|
||||
returns: two Popen objects, tunnel and terminal"""
|
||||
title = '"%s: %s"' % ( title, node.name )
|
||||
title += ': ' + node.name
|
||||
if not node.inNamespace:
|
||||
title += ' (root)'
|
||||
cmds = {
|
||||
|
||||
@@ -6,7 +6,7 @@ Run all mininet core tests
|
||||
-quick : skip tests that take more than ~30 seconds
|
||||
"""
|
||||
|
||||
from unittest import defaultTestLoader, TextTestRunner
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
from mininet.util import ensureRoot
|
||||
@@ -19,13 +19,13 @@ def runTests( testDir, verbosity=1 ):
|
||||
ensureRoot()
|
||||
cleanup()
|
||||
# discover all tests in testDir
|
||||
testSuite = defaultTestLoader.discover( testDir )
|
||||
testSuite = unittest.defaultTestLoader.discover( testDir )
|
||||
# run tests
|
||||
TextTestRunner( verbosity=verbosity ).run( testSuite )
|
||||
unittest.TextTestRunner( verbosity=verbosity ).run( testSuite )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
# get the directory containing example tests
|
||||
thisdir = os.path.dirname( os.path.realpath( __file__ ) )
|
||||
vlevel = 2 if '-v' in sys.argv else 1
|
||||
runTests( testDir=thisdir, verbosity=vlevel )
|
||||
testDir = os.path.dirname( os.path.realpath( __file__ ) )
|
||||
verbosity = 2 if '-v' in sys.argv else 1
|
||||
runTests( testDir, verbosity )
|
||||
|
||||
+14
-26
@@ -4,7 +4,6 @@
|
||||
Test creation and pings for topologies with link and/or CPU options."""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
@@ -14,7 +13,6 @@ from mininet.link import TCLink
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import quietRun
|
||||
from mininet.clean import cleanup
|
||||
|
||||
# Number of hosts for each test
|
||||
N = 2
|
||||
@@ -40,13 +38,7 @@ class testOptionsTopoCommon( object ):
|
||||
"""Verify ability to create networks with host and link options
|
||||
(common code)."""
|
||||
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
@staticmethod
|
||||
def tearDown():
|
||||
"Clean up if necessary"
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
cleanup()
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
def runOptionsTopoTest( self, n, msg, hopts=None, lopts=None ):
|
||||
"Generic topology-with-options test runner."
|
||||
@@ -87,7 +79,7 @@ class testOptionsTopoCommon( object ):
|
||||
upperBound, lowerBound ) )
|
||||
msg += info
|
||||
|
||||
self.assertGreaterEqual( float( measured ), lowerBound, msg=msg )
|
||||
self.assertGreaterEqual( float( measured ),lowerBound, msg=msg )
|
||||
self.assertLessEqual( float( measured ), upperBound, msg=msg )
|
||||
|
||||
def testCPULimits( self ):
|
||||
@@ -104,8 +96,7 @@ class testOptionsTopoCommon( object ):
|
||||
results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
|
||||
mn.stop()
|
||||
hostUsage = '\n'.join( 'h%s: %s' %
|
||||
( n + 1,
|
||||
results[ (n - 1) * 5 : (n * 5) - 1 ] )
|
||||
( n + 1, results[ ( n - 1 ) * 5: ( n * 5 ) - 1 ] )
|
||||
for n in range( N ) )
|
||||
hoptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in hopts.items() )
|
||||
@@ -115,8 +106,7 @@ class testOptionsTopoCommon( object ):
|
||||
'hopts = %s\n'
|
||||
'host = CPULimitedHost\n'
|
||||
'Switch = %s\n'
|
||||
% ( CPU_FRACTION * 100, hostUsage, N, hoptsStr,
|
||||
self.switchClass ) )
|
||||
% ( CPU_FRACTION * 100, hostUsage, N, hoptsStr, self.switchClass ) )
|
||||
for pct in results:
|
||||
#divide cpu by 100 to convert from percentage to fraction
|
||||
self.assertWithinTolerance( pct/100, CPU_FRACTION,
|
||||
@@ -125,8 +115,7 @@ class testOptionsTopoCommon( object ):
|
||||
def testLinkBandwidth( self ):
|
||||
"Verify that link bandwidths are accurate within a bound."
|
||||
if self.switchClass is UserSwitch:
|
||||
self.skipTest( 'UserSwitch has very poor performance -'
|
||||
' skipping for now' )
|
||||
self.skipTest ( 'UserSwitch has very poor performance, so skip for now' )
|
||||
BW = 5 # Mbps
|
||||
BW_TOLERANCE = 0.8 # BW fraction below which test should fail
|
||||
# Verify ability to create limited-link topo first;
|
||||
@@ -135,7 +124,7 @@ class testOptionsTopoCommon( object ):
|
||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
||||
link=TCLink, switch=self.switchClass,
|
||||
waitConnected=True )
|
||||
bw_strs = mn.run( mn.iperf, fmt='m' )
|
||||
bw_strs = mn.run( mn.iperf, format='m' )
|
||||
loptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in lopts.items() )
|
||||
msg = ( '\nTesting link bandwidth limited to %d Mbps per link\n'
|
||||
@@ -152,7 +141,7 @@ class testOptionsTopoCommon( object ):
|
||||
# As long as the kernel doesn't wait a long time before
|
||||
# delivering bytes to the iperf server, its reported data rate
|
||||
# should be close to the actual receive rate.
|
||||
serverRate, _clientRate = bw_strs
|
||||
serverRate, clientRate = bw_strs
|
||||
bw = float( serverRate.split(' ')[0] )
|
||||
self.assertWithinTolerance( bw, BW, BW_TOLERANCE, msg )
|
||||
|
||||
@@ -171,12 +160,12 @@ class testOptionsTopoCommon( object ):
|
||||
mn.stop()
|
||||
test_outputs = ping_delays[0]
|
||||
# Ignore unused variables below
|
||||
# pylint: disable=W0612
|
||||
# pylint: disable-msg=W0612
|
||||
node, dest, ping_outputs = test_outputs
|
||||
sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
|
||||
pingFailMsg = 'sent %s pings, only received %s' % ( sent, received )
|
||||
self.assertEqual( sent, received, msg=pingFailMsg )
|
||||
# pylint: enable=W0612
|
||||
# pylint: enable-msg=W0612
|
||||
loptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in lopts.items() )
|
||||
msg = ( '\nTesting Link Delay of %s ms\n'
|
||||
@@ -192,9 +181,10 @@ class testOptionsTopoCommon( object ):
|
||||
|
||||
for rttval in [rttmin, rttavg, rttmax]:
|
||||
# Multiply delay by 4 to cover there & back on two links
|
||||
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
|
||||
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
|
||||
DELAY_TOLERANCE, msg )
|
||||
|
||||
|
||||
def testLinkLoss( self ):
|
||||
"Verify that we see packet drops with a high configured loss rate."
|
||||
LOSS_PERCENT = 99
|
||||
@@ -222,8 +212,7 @@ class testOptionsTopoCommon( object ):
|
||||
'lopts = %s\n'
|
||||
'host = default\n'
|
||||
'switch = %s\n'
|
||||
% ( LOSS_PERCENT, dropped_total, N, loptsStr,
|
||||
self.switchClass ) )
|
||||
% ( LOSS_PERCENT, dropped_total, N, loptsStr, self.switchClass ) )
|
||||
|
||||
self.assertGreater( dropped_total, 0, msg )
|
||||
|
||||
@@ -256,10 +245,9 @@ class testOptionsTopoIVS( testOptionsTopoCommon, unittest.TestCase ):
|
||||
switchClass = IVSSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
|
||||
'Reference user switch is not installed' )
|
||||
'Reference user switch is not installed' )
|
||||
class testOptionsTopoUserspace( testOptionsTopoCommon, unittest.TestCase ):
|
||||
"""Verify ability to create networks with host and link options
|
||||
(UserSwitch)."""
|
||||
"Verify ability to create networks with host and link options (UserSwitch)."
|
||||
longMessage = True
|
||||
switchClass = UserSwitch
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
Test creation and all-pairs ping for each included mininet topo type."""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
@@ -13,7 +12,6 @@ from mininet.node import UserSwitch, OVSSwitch, IVSSwitch
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import quietRun
|
||||
from mininet.clean import cleanup
|
||||
|
||||
# Tell pylint not to complain about calls to other class
|
||||
# pylint: disable=E1101
|
||||
@@ -21,13 +19,7 @@ from mininet.clean import cleanup
|
||||
class testSingleSwitchCommon( object ):
|
||||
"Test ping with single switch topology (common code)."
|
||||
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
@staticmethod
|
||||
def tearDown():
|
||||
"Clean up if necessary"
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
cleanup()
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
def testMinimal( self ):
|
||||
"Ping test on minimal topology"
|
||||
@@ -59,7 +51,7 @@ class testSingleSwitchIVS( testSingleSwitchCommon, unittest.TestCase ):
|
||||
switchClass = IVSSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
|
||||
'Reference user switch is not installed' )
|
||||
'Reference user switch is not installed' )
|
||||
class testSingleSwitchUserspace( testSingleSwitchCommon, unittest.TestCase ):
|
||||
"Test ping with single switch topology (Userspace switch)."
|
||||
switchClass = UserSwitch
|
||||
@@ -71,12 +63,11 @@ class testSingleSwitchUserspace( testSingleSwitchCommon, unittest.TestCase ):
|
||||
class testLinearCommon( object ):
|
||||
"Test all-pairs ping with LinearNet (common code)."
|
||||
|
||||
switchClass = None # overridden in subclasses
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
def testLinear5( self ):
|
||||
"Ping test on a 5-switch topology"
|
||||
mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host,
|
||||
Controller, waitConnected=True )
|
||||
mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller, waitConnected=True )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Package: mininet
|
||||
Regression tests for switch dpid assignment."""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host, Controller
|
||||
from mininet.node import ( UserSwitch, OVSSwitch, OVSLegacyKernelSwitch,
|
||||
IVSSwitch )
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import quietRun
|
||||
from mininet.clean import cleanup
|
||||
|
||||
|
||||
class TestSwitchDpidAssignmentOVS( unittest.TestCase ):
|
||||
"Verify Switch dpid assignment."
|
||||
|
||||
switchClass = OVSSwitch # overridden in subclasses
|
||||
|
||||
def tearDown( self ):
|
||||
"Clean up if necessary"
|
||||
# satisfy pylint
|
||||
assert self
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
cleanup()
|
||||
|
||||
def testDefaultDpid( self ):
|
||||
"""Verify that the default dpid is assigned using a valid provided
|
||||
canonical switchname if no dpid is passed in switch creation."""
|
||||
switch = Mininet( Topo(),
|
||||
self.switchClass,
|
||||
Host, Controller ).addSwitch( 's1' )
|
||||
self.assertEqual( switch.defaultDpid(), switch.dpid )
|
||||
|
||||
def dpidFrom( self, num ):
|
||||
"Compute default dpid from number"
|
||||
fmt = ( '%0' + str( self.switchClass.dpidLen ) + 'x' )
|
||||
return fmt % num
|
||||
|
||||
def testActualDpidAssignment( self ):
|
||||
"""Verify that Switch dpid is the actual dpid assigned if dpid is
|
||||
passed in switch creation."""
|
||||
dpid = self.dpidFrom( 0xABCD )
|
||||
switch = Mininet( Topo(), self.switchClass,
|
||||
Host, Controller ).addSwitch(
|
||||
's1', dpid=dpid )
|
||||
self.assertEqual( switch.dpid, dpid )
|
||||
|
||||
def testDefaultDpidAssignmentFailure( self ):
|
||||
"""Verify that Default dpid assignment raises an Exception if the
|
||||
name of the switch does not contin a digit. Also verify the
|
||||
exception message."""
|
||||
with self.assertRaises( Exception ) as raises_cm:
|
||||
Mininet( Topo(), self.switchClass,
|
||||
Host, Controller ).addSwitch( 'A' )
|
||||
self.assertEqual(raises_cm.exception.message, 'Unable to derive '
|
||||
'default datapath ID - please either specify a dpid '
|
||||
'or use a canonical switch name such as s23.')
|
||||
|
||||
def testDefaultDpidLen( self ):
|
||||
"""Verify that Default dpid length is 16 characters consisting of
|
||||
16 - len(hex of first string of contiguous digits passed in switch
|
||||
name) 0's followed by hex of first string of contiguous digits passed
|
||||
in switch name."""
|
||||
switch = Mininet( Topo(), self.switchClass,
|
||||
Host, Controller ).addSwitch( 's123' )
|
||||
|
||||
self.assertEqual( switch.dpid, self.dpidFrom( 123 ) )
|
||||
|
||||
class OVSUser( OVSSwitch):
|
||||
"OVS User Switch convenience class"
|
||||
def __init__( self, *args, **kwargs ):
|
||||
kwargs.update( datapath='user' )
|
||||
OVSSwitch.__init__( self, *args, **kwargs )
|
||||
|
||||
class testSwitchOVSUser( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignnment of OVS User Switch."
|
||||
switchClass = OVSUser
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ovs-openflowd' ),
|
||||
'OVS Legacy Kernel switch is not installed' )
|
||||
class testSwitchOVSLegacyKernel( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignnment of OVS Legacy Kernel Switch."
|
||||
switchClass = OVSLegacyKernelSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ivs-ctl' ),
|
||||
'IVS switch is not installed' )
|
||||
class testSwitchIVS( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignment of IVS switch."
|
||||
switchClass = IVSSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
|
||||
'Reference user switch is not installed' )
|
||||
class testSwitchUserspace( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignment of Userspace switch."
|
||||
switchClass = UserSwitch
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
unittest.main()
|
||||
@@ -14,16 +14,11 @@ from mininet.util import quietRun
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
def tsharkVersion():
|
||||
"Return tshark version"
|
||||
versionStr = quietRun( 'tshark -v' )
|
||||
versionMatch = re.findall( r'TShark \d+.\d+.\d+', versionStr )[0]
|
||||
versionMatch = re.findall( 'TShark \d+.\d+.\d+', versionStr )[0]
|
||||
return versionMatch.split()[ 1 ]
|
||||
|
||||
# pylint doesn't understand pexpect.match, unfortunately!
|
||||
# pylint:disable=maybe-no-member
|
||||
|
||||
class testWalkthrough( unittest.TestCase ):
|
||||
"Test Mininet walkthrough"
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@@ -36,8 +31,6 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
def testWireshark( self ):
|
||||
"Use tshark to test the of dissector"
|
||||
# Satisfy pylint
|
||||
assert self
|
||||
if StrictVersion( tsharkVersion() ) < StrictVersion( '1.12.0' ):
|
||||
tshark = pexpect.spawn( 'tshark -i lo -R of' )
|
||||
else:
|
||||
@@ -58,7 +51,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
self.assertEqual( index, 0, 'No output for "help" command')
|
||||
# nodes command
|
||||
p.sendline( 'nodes' )
|
||||
p.expect( r'([chs]\d ?){4}' )
|
||||
p.expect( '([chs]\d ?){4}' )
|
||||
nodes = p.match.group( 0 ).split()
|
||||
self.assertEqual( len( nodes ), 4, 'No nodes in "nodes" command')
|
||||
p.expect( self.prompt )
|
||||
@@ -74,15 +67,14 @@ class testWalkthrough( unittest.TestCase ):
|
||||
p.expect( self.prompt )
|
||||
# dump command
|
||||
p.sendline( 'dump' )
|
||||
expected = [ r'<\w+ (%s)' % n for n in nodes ]
|
||||
expected = [ '<\w+ (%s)' % n for n in nodes ]
|
||||
actual = []
|
||||
for _ in nodes:
|
||||
index = p.expect( expected )
|
||||
node = p.match.group( 1 )
|
||||
actual.append( node )
|
||||
p.expect( '\n' )
|
||||
self.assertEqual( actual.sort(), nodes.sort(),
|
||||
'"nodes" and "dump" differ' )
|
||||
self.assertEqual( actual.sort(), nodes.sort(), '"nodes" and "dump" differ' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
@@ -169,7 +161,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
p.expect( pexpect.EOF )
|
||||
# test iperf
|
||||
p = pexpect.spawn( 'mn --test iperf' )
|
||||
p.expect( r"Results: \['([\d\.]+) .bits/sec'," )
|
||||
p.expect( "Results: \['([\d\.]+) .bits/sec'," )
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertTrue( bw > 0 )
|
||||
p.expect( pexpect.EOF )
|
||||
@@ -178,7 +170,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
"Test pingall on single,3 and linear,4 topos"
|
||||
# testing single,3
|
||||
p = pexpect.spawn( 'mn --test pingall --topo single,3' )
|
||||
p.expect( r'(\d+)/(\d+) received')
|
||||
p.expect( '(\d+)/(\d+) received')
|
||||
received = int( p.match.group( 1 ) )
|
||||
sent = int( p.match.group( 2 ) )
|
||||
self.assertEqual( sent, 6, 'Wrong number of pings sent in single,3' )
|
||||
@@ -186,7 +178,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
p.expect( pexpect.EOF )
|
||||
# testing linear,4
|
||||
p = pexpect.spawn( 'mn --test pingall --topo linear,4' )
|
||||
p.expect( r'(\d+)/(\d+) received')
|
||||
p.expect( '(\d+)/(\d+) received')
|
||||
received = int( p.match.group( 1 ) )
|
||||
sent = int( p.match.group( 2 ) )
|
||||
self.assertEqual( sent, 12, 'Wrong number of pings sent in linear,4' )
|
||||
@@ -199,15 +191,14 @@ class testWalkthrough( unittest.TestCase ):
|
||||
# test bw
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'iperf' )
|
||||
p.expect( r"Results: \['([\d\.]+) Mbits/sec'," )
|
||||
p.expect( "Results: \['([\d\.]+) Mbits/sec'," )
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertTrue( bw < 10.1, 'Bandwidth > 10 Mb/s')
|
||||
self.assertTrue( bw > 9.0, 'Bandwidth < 9 Mb/s')
|
||||
p.expect( self.prompt )
|
||||
# test delay
|
||||
p.sendline( 'h1 ping -c 4 h2' )
|
||||
p.expect( r'rtt min/avg/max/mdev = '
|
||||
r'([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' )
|
||||
p.expect( 'rtt min/avg/max/mdev = ([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' )
|
||||
delay = float( p.match.group( 2 ) )
|
||||
self.assertTrue( delay > 40, 'Delay < 40ms' )
|
||||
self.assertTrue( delay < 45, 'Delay > 40ms' )
|
||||
@@ -231,13 +222,10 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
def testCustomTopo( self ):
|
||||
"Start Mininet using a custom topo, then run pingall"
|
||||
# Satisfy pylint
|
||||
assert self
|
||||
custom = os.path.dirname( os.path.realpath( __file__ ) )
|
||||
custom = os.path.join( custom, '../../custom/topo-2sw-2host.py' )
|
||||
custom = os.path.normpath( custom )
|
||||
p = pexpect.spawn(
|
||||
'mn --custom %s --topo mytopo --test pingall' % custom )
|
||||
p = pexpect.spawn( 'mn --custom %s --topo mytopo --test pingall' % custom )
|
||||
p.expect( '0% dropped' )
|
||||
p.expect( pexpect.EOF )
|
||||
|
||||
@@ -255,7 +243,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
switches = [ 'user', 'ovsk' ]
|
||||
for sw in switches:
|
||||
p = pexpect.spawn( 'mn --switch %s --test iperf' % sw )
|
||||
p.expect( r"Results: \['([\d\.]+) .bits/sec'," )
|
||||
p.expect( "Results: \['([\d\.]+) .bits/sec'," )
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertTrue( bw > 0 )
|
||||
p.expect( pexpect.EOF )
|
||||
@@ -263,7 +251,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
def testBenchmark( self ):
|
||||
"Run benchmark and verify that it takes less than 2 seconds"
|
||||
p = pexpect.spawn( 'mn --test none' )
|
||||
p.expect( r'completed in ([\d\.]+) seconds' )
|
||||
p.expect( 'completed in ([\d\.]+) seconds' )
|
||||
time = float( p.match.group( 1 ) )
|
||||
self.assertTrue( time < 2, 'Benchmark takes more than 2 seconds' )
|
||||
|
||||
@@ -287,7 +275,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
self.assertEqual( ifcount, 2, 'Missing interfaces on s1' )
|
||||
# verify that all hosts a reachable
|
||||
p.sendline( 'pingall' )
|
||||
p.expect( r'(\d+)% dropped' )
|
||||
p.expect( '(\d+)% dropped' )
|
||||
dropped = int( p.match.group( 1 ) )
|
||||
self.assertEqual( dropped, 0, 'pingall failed')
|
||||
p.expect( self.prompt )
|
||||
@@ -339,15 +327,11 @@ class testWalkthrough( unittest.TestCase ):
|
||||
'Github is not reachable; cannot download Pox' )
|
||||
def testRemoteController( self ):
|
||||
"Test Mininet using Pox controller"
|
||||
# Satisfy pylint
|
||||
assert self
|
||||
if not os.path.exists( '/tmp/pox' ):
|
||||
p = pexpect.spawn(
|
||||
'git clone https://github.com/noxrepo/pox.git /tmp/pox' )
|
||||
p = pexpect.spawn( 'git clone https://github.com/noxrepo/pox.git /tmp/pox' )
|
||||
p.expect( pexpect.EOF )
|
||||
pox = pexpect.spawn( '/tmp/pox/pox.py forwarding.l2_learning' )
|
||||
net = pexpect.spawn(
|
||||
'mn --controller=remote,ip=127.0.0.1,port=6633 --test pingall' )
|
||||
net = pexpect.spawn( 'mn --controller=remote,ip=127.0.0.1,port=6633 --test pingall' )
|
||||
net.expect( '0% dropped' )
|
||||
net.expect( pexpect.EOF )
|
||||
pox.sendintr()
|
||||
|
||||
+110
-202
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
"""@package topo
|
||||
'''@package topo
|
||||
|
||||
Network topology creation.
|
||||
|
||||
@@ -9,291 +9,203 @@ This package includes code to represent network topologies.
|
||||
|
||||
A Topo object can be a topology database for NOX, can represent a physical
|
||||
setup for testing, and can even be emulated with the Mininet package.
|
||||
"""
|
||||
'''
|
||||
|
||||
from mininet.util import irange, natural, naturalSeq
|
||||
|
||||
class MultiGraph( object ):
|
||||
"Utility class to track nodes and edges - replaces networkx.MultiGraph"
|
||||
"Utility class to track nodes and edges - replaces networkx.Graph"
|
||||
|
||||
def __init__( self ):
|
||||
self.node = {}
|
||||
self.edge = {}
|
||||
self.data = {}
|
||||
|
||||
def add_node( self, node, attr_dict=None, **attrs):
|
||||
"""Add node to graph
|
||||
attr_dict: attribute dict (optional)
|
||||
attrs: more attributes (optional)
|
||||
warning: updates attr_dict with attrs"""
|
||||
attr_dict = {} if attr_dict is None else attr_dict
|
||||
attr_dict.update( attrs )
|
||||
self.node[ node ] = attr_dict
|
||||
def add_node( self, node ):
|
||||
"Add node to graph"
|
||||
self.data.setdefault( node, [] )
|
||||
|
||||
def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ):
|
||||
"""Add edge to graph
|
||||
key: optional key
|
||||
attr_dict: optional attribute dict
|
||||
attrs: more attributes
|
||||
warning: udpates attr_dict with attrs"""
|
||||
attr_dict = {} if attr_dict is None else attr_dict
|
||||
attr_dict.update( attrs )
|
||||
self.node.setdefault( src, {} )
|
||||
self.node.setdefault( dst, {} )
|
||||
self.edge.setdefault( src, {} )
|
||||
self.edge.setdefault( dst, {} )
|
||||
self.edge[ src ].setdefault( dst, {} )
|
||||
entry = self.edge[ dst ][ src ] = self.edge[ src ][ dst ]
|
||||
# If no key, pick next ordinal number
|
||||
if key is None:
|
||||
keys = [ k for k in entry.keys() if isinstance( k, int ) ]
|
||||
key = max( [ 0 ] + keys ) + 1
|
||||
entry[ key ] = attr_dict
|
||||
return key
|
||||
def add_edge( self, src, dest ):
|
||||
"Add edge to graph"
|
||||
src, dest = sorted( ( src, dest ) )
|
||||
self.add_node( src )
|
||||
self.add_node( dest )
|
||||
self.data[ src ].append( dest )
|
||||
|
||||
def nodes( self, data=False):
|
||||
"""Return list of graph nodes
|
||||
data: return list of ( node, attrs)"""
|
||||
return self.node.items() if data else self.node.keys()
|
||||
def nodes( self ):
|
||||
"Return list of graph nodes"
|
||||
return self.data.keys()
|
||||
|
||||
def edges_iter( self, data=False, keys=False ):
|
||||
def edges( self ):
|
||||
"Iterator: return graph edges"
|
||||
for src, entry in self.edge.iteritems():
|
||||
for dst, keys in entry.iteritems():
|
||||
if src > dst:
|
||||
# Skip duplicate edges
|
||||
continue
|
||||
for k, attrs in keys.iteritems():
|
||||
if data:
|
||||
if keys:
|
||||
yield( src, dst, k, attrs )
|
||||
else:
|
||||
yield( src, dst, attrs )
|
||||
else:
|
||||
if keys:
|
||||
yield( src, dst, k )
|
||||
else:
|
||||
yield( src, dst )
|
||||
|
||||
def edges( self, data=False, keys=False ):
|
||||
"Return list of graph edges"
|
||||
return list( self.edges_iter( data=data, keys=keys ) )
|
||||
for src in self.data.keys():
|
||||
for dest in self.data[ src ]:
|
||||
yield ( src, dest )
|
||||
|
||||
def __getitem__( self, node ):
|
||||
"Return link dict for given src node"
|
||||
return self.edge[ node ]
|
||||
|
||||
def __len__( self ):
|
||||
"Return the number of nodes"
|
||||
return len( self.node )
|
||||
|
||||
def convertTo( self, cls, data=False, keys=False ):
|
||||
"""Convert to a new object of networkx.MultiGraph-like class cls
|
||||
data: include node and edge data
|
||||
keys: include edge keys as well as edge data"""
|
||||
g = cls()
|
||||
g.add_nodes_from( self.nodes( data=data ) )
|
||||
g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) )
|
||||
return g
|
||||
"Return link dict for the given node"
|
||||
return self.data[node]
|
||||
|
||||
|
||||
class Topo( object ):
|
||||
class Topo(object):
|
||||
"Data center network representation for structured multi-trees."
|
||||
|
||||
def __init__( self, *args, **params ):
|
||||
"""Topo object.
|
||||
def __init__(self, *args, **params):
|
||||
"""Topo object.
|
||||
Optional named parameters:
|
||||
hinfo: default host options
|
||||
sopts: default switch options
|
||||
lopts: default link options
|
||||
calls build()"""
|
||||
self.g = MultiGraph()
|
||||
self.node_info = {}
|
||||
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
|
||||
self.hopts = params.pop( 'hopts', {} )
|
||||
self.sopts = params.pop( 'sopts', {} )
|
||||
self.lopts = params.pop( 'lopts', {} )
|
||||
# ports[src][dst][sport] is port on dst that connects to src
|
||||
self.ports = {}
|
||||
self.ports = {} # ports[src][dst] is port on src that connects to dst
|
||||
self.build( *args, **params )
|
||||
|
||||
def build( self, *args, **params ):
|
||||
"Override this method to build your topology."
|
||||
pass
|
||||
|
||||
def addNode( self, name, **opts ):
|
||||
def addNode(self, name, **opts):
|
||||
"""Add Node to graph.
|
||||
name: name
|
||||
opts: node options
|
||||
returns: node name"""
|
||||
self.g.add_node( name, **opts )
|
||||
self.g.add_node(name)
|
||||
self.node_info[name] = opts
|
||||
return name
|
||||
|
||||
def addHost( self, name, **opts ):
|
||||
def addHost(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.addNode( name, **opts )
|
||||
return self.addNode(name, **opts)
|
||||
|
||||
def addSwitch( self, name, **opts ):
|
||||
def addSwitch(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.addNode( name, isSwitch=True, **opts )
|
||||
result = self.addNode(name, isSwitch=True, **opts)
|
||||
return result
|
||||
|
||||
def addLink( self, node1, node2, port1=None, port2=None,
|
||||
key=None, **opts ):
|
||||
def addLink(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
|
||||
port1, port2 = self.addPort( node1, node2, port1, port2 )
|
||||
opts = dict( opts )
|
||||
opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
|
||||
self.g.add_edge(node1, node2, key, opts )
|
||||
self.addPort(node1, node2, port1, port2)
|
||||
key = tuple(self.sorted([node1, node2]))
|
||||
self.link_info[key] = opts
|
||||
self.g.add_edge(*key)
|
||||
return key
|
||||
|
||||
def nodes( self, sort=True ):
|
||||
def addPort(self, src, dst, sport=None, dport=None):
|
||||
'''Generate port mapping for new edge.
|
||||
@param src source switch name
|
||||
@param dst destination switch name
|
||||
'''
|
||||
self.ports.setdefault(src, {})
|
||||
self.ports.setdefault(dst, {})
|
||||
# New port: number of outlinks + base
|
||||
src_base = 1 if self.isSwitch(src) else 0
|
||||
dst_base = 1 if self.isSwitch(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 nodes(self, sort=True):
|
||||
"Return nodes in graph"
|
||||
if sort:
|
||||
return self.sorted( self.g.nodes() )
|
||||
else:
|
||||
return self.g.nodes()
|
||||
|
||||
def isSwitch( self, n ):
|
||||
"Returns true if node is a switch."
|
||||
return self.g.node[ n ].get( 'isSwitch', False )
|
||||
def isSwitch(self, n):
|
||||
'''Returns true if node is a switch.'''
|
||||
info = self.node_info[n]
|
||||
return info and info.get('isSwitch', False)
|
||||
|
||||
def switches( self, sort=True ):
|
||||
"""Return switches.
|
||||
sort: sort switches alphabetically
|
||||
returns: dpids list of dpids"""
|
||||
return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
|
||||
def switches(self, sort=True):
|
||||
'''Return switches.
|
||||
sort: sort switches alphabetically
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
return [n for n in self.nodes(sort) if self.isSwitch(n)]
|
||||
|
||||
def hosts( self, sort=True ):
|
||||
"""Return hosts.
|
||||
sort: sort hosts alphabetically
|
||||
returns: list of hosts"""
|
||||
return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
|
||||
def hosts(self, sort=True):
|
||||
'''Return hosts.
|
||||
sort: sort hosts alphabetically
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
return [n for n in self.nodes(sort) if not self.isSwitch(n)]
|
||||
|
||||
def iterLinks( self, withKeys=False, withInfo=False ):
|
||||
"""Return links (iterator)
|
||||
withKeys: return link keys
|
||||
withInfo: return link info
|
||||
returns: list of ( src, dst [,key, info ] )"""
|
||||
for _src, _dst, key, info in self.g.edges_iter( data=True, keys=True ):
|
||||
node1, node2 = info[ 'node1' ], info[ 'node2' ]
|
||||
if withKeys:
|
||||
if withInfo:
|
||||
yield( node1, node2, key, info )
|
||||
else:
|
||||
yield( node1, node2, key )
|
||||
else:
|
||||
if withInfo:
|
||||
yield( node1, node2, info )
|
||||
else:
|
||||
yield( node1, node2 )
|
||||
|
||||
def links( self, sort=False, withKeys=False, withInfo=False ):
|
||||
"""Return links
|
||||
sort: sort links alphabetically, preserving (src, dst) order
|
||||
withKeys: return link keys
|
||||
withInfo: return link info
|
||||
returns: list of ( src, dst [,key, info ] )"""
|
||||
links = list( self.iterLinks( withKeys, withInfo ) )
|
||||
def links(self, sort=True):
|
||||
'''Return links.
|
||||
sort: sort links alphabetically
|
||||
@return links list of name pairs
|
||||
'''
|
||||
if not sort:
|
||||
return links
|
||||
# Ignore info when sorting
|
||||
tupleSize = 3 if withKeys else 2
|
||||
return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) )
|
||||
return self.g.edges()
|
||||
else:
|
||||
links = [tuple(self.sorted(e)) for e in self.g.edges()]
|
||||
return sorted( links, key=naturalSeq )
|
||||
|
||||
# This legacy port management mechanism is clunky and will probably
|
||||
# be removed at some point.
|
||||
def port(self, src, dst):
|
||||
'''Get port number.
|
||||
|
||||
def addPort( self, src, dst, sport=None, dport=None ):
|
||||
"""Generate port mapping for new edge.
|
||||
src: source switch name
|
||||
dst: destination switch name"""
|
||||
# Initialize if necessary
|
||||
ports = self.ports
|
||||
ports.setdefault( src, {} )
|
||||
ports.setdefault( dst, {} )
|
||||
# New port: number of outlinks + base
|
||||
if sport is None:
|
||||
src_base = 1 if self.isSwitch( src ) else 0
|
||||
sport = len( ports[ src ] ) + src_base
|
||||
if dport is None:
|
||||
dst_base = 1 if self.isSwitch( dst ) else 0
|
||||
dport = len( ports[ dst ] ) + dst_base
|
||||
ports[ src ][ sport ] = ( dst, dport )
|
||||
ports[ dst ][ dport ] = ( src, sport )
|
||||
return sport, dport
|
||||
@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
|
||||
'''
|
||||
if src in self.ports and dst in self.ports[src]:
|
||||
assert dst in self.ports and src in self.ports[dst]
|
||||
return self.ports[src][dst], self.ports[dst][src]
|
||||
|
||||
def port( self, src, dst ):
|
||||
"""Get port numbers.
|
||||
src: source switch name
|
||||
dst: destination switch name
|
||||
sport: optional source port (otherwise use lowest src port)
|
||||
returns: tuple (sport, dport), where
|
||||
sport = port on source switch leading to the destination switch
|
||||
dport = port on destination switch leading to the source switch
|
||||
Note that you can also look up ports using linkInfo()"""
|
||||
# A bit ugly and slow vs. single-link implementation ;-(
|
||||
ports = [ ( sport, entry[ 1 ] )
|
||||
for sport, entry in self.ports[ src ].items()
|
||||
if entry[ 0 ] == dst ]
|
||||
return ports if len( ports ) != 1 else ports[ 0 ]
|
||||
def linkInfo( self, src, dst ):
|
||||
"Return link metadata"
|
||||
src, dst = self.sorted([src, dst])
|
||||
return self.link_info[(src, dst)]
|
||||
|
||||
def _linkEntry( self, src, dst, key=None ):
|
||||
"Helper function: return link entry and key"
|
||||
entry = self.g[ src ][ dst ]
|
||||
if key is None:
|
||||
key = min( entry )
|
||||
return entry, key
|
||||
|
||||
def linkInfo( self, src, dst, key=None ):
|
||||
"Return link metadata dict"
|
||||
entry, key = self._linkEntry( src, dst, key )
|
||||
return entry[ key ]
|
||||
|
||||
def setlinkInfo( self, src, dst, info, key=None ):
|
||||
"Set link metadata dict"
|
||||
entry, key = self._linkEntry( src, dst, key )
|
||||
entry[ key ] = info
|
||||
def setlinkInfo( self, src, dst, info ):
|
||||
"Set link metadata"
|
||||
src, dst = self.sorted([src, dst])
|
||||
self.link_info[(src, dst)] = info
|
||||
|
||||
def nodeInfo( self, name ):
|
||||
"Return metadata (dict) for node"
|
||||
return self.g.node[ name ]
|
||||
info = self.node_info[ name ]
|
||||
return info if info is not None else {}
|
||||
|
||||
def setNodeInfo( self, name, info ):
|
||||
"Set metadata (dict) for node"
|
||||
self.g.node[ name ] = info
|
||||
|
||||
def convertTo( self, cls, data=True, keys=True ):
|
||||
"""Convert to a new object of networkx.MultiGraph-like class cls
|
||||
data: include node and edge data (default True)
|
||||
keys: include edge keys as well as edge data (default True)"""
|
||||
return self.g.convertTo( cls, data=data, keys=keys )
|
||||
self.node_info[ name ] = info
|
||||
|
||||
@staticmethod
|
||||
def sorted( items ):
|
||||
"Items sorted in natural (i.e. alphabetical) order"
|
||||
return sorted( items, key=natural )
|
||||
return sorted(items, key=natural)
|
||||
|
||||
|
||||
# Our idiom defines additional parameters in build(param...)
|
||||
# pylint: disable=arguments-differ
|
||||
|
||||
class SingleSwitchTopo( Topo ):
|
||||
"Single switch connected to k hosts."
|
||||
|
||||
def build( self, k=2, **_opts ):
|
||||
def build( self, k=2, **opts ):
|
||||
"k: number of hosts"
|
||||
self.k = k
|
||||
switch = self.addSwitch( 's1' )
|
||||
@@ -305,8 +217,7 @@ class SingleSwitchTopo( Topo ):
|
||||
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."""
|
||||
Useful to verify that Mininet properly handles custom port numberings."""
|
||||
|
||||
def build( self, k=2 ):
|
||||
"k: number of hosts"
|
||||
@@ -317,11 +228,10 @@ class SingleSwitchReversedTopo( Topo ):
|
||||
self.addLink( host, switch,
|
||||
port1=0, port2=( k - h + 1 ) )
|
||||
|
||||
|
||||
class LinearTopo( Topo ):
|
||||
"Linear topology of k switches, with n hosts per switch."
|
||||
|
||||
def build( self, k=2, n=1, **_opts):
|
||||
def build( self, k=2, n=1, **opts):
|
||||
"""k: number of switches
|
||||
n: number of hosts per switch"""
|
||||
self.k = k
|
||||
@@ -344,5 +254,3 @@ class LinearTopo( Topo ):
|
||||
if lastSwitch:
|
||||
self.addLink( switch, lastSwitch )
|
||||
lastSwitch = switch
|
||||
|
||||
# pylint: enable=arguments-differ
|
||||
|
||||
+5
-8
@@ -3,9 +3,6 @@
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
|
||||
# The build() method is expected to do this:
|
||||
# pylint: disable=arguments-differ
|
||||
|
||||
class TreeTopo( Topo ):
|
||||
"Topology for a tree network with a given depth and fanout."
|
||||
|
||||
@@ -44,11 +41,11 @@ class TorusTopo( Topo ):
|
||||
with the default controller or any Ethernet bridge
|
||||
without STP turned on! It can be used with STP, e.g.:
|
||||
# mn --topo torus,3,3 --switch lxbr,stp=1 --test pingall"""
|
||||
|
||||
|
||||
def build( self, x, y ):
|
||||
if x < 3 or y < 3:
|
||||
raise Exception( 'Please use 3x3 or greater for compatibility '
|
||||
'with 2.1' )
|
||||
'with 2.1' )
|
||||
hosts, switches, dpid = {}, {}, 0
|
||||
# Create and wire interior
|
||||
for i in range( 0, x ):
|
||||
@@ -56,8 +53,7 @@ class TorusTopo( Topo ):
|
||||
loc = '%dx%d' % ( i + 1, j + 1 )
|
||||
# dpid cannot be zero for OVS
|
||||
dpid = ( i + 1 ) * 256 + ( j + 1 )
|
||||
switch = switches[ i, j ] = self.addSwitch(
|
||||
's' + loc, dpid='%016x' % dpid )
|
||||
switch = switches[ i, j ] = self.addSwitch( 's' + loc, dpid='%016x' % dpid )
|
||||
host = hosts[ i, j ] = self.addHost( 'h' + loc )
|
||||
self.addLink( host, switch )
|
||||
# Connect switches
|
||||
@@ -69,4 +65,5 @@ class TorusTopo( Topo ):
|
||||
self.addLink( sw1, sw2 )
|
||||
self.addLink( sw1, sw3 )
|
||||
|
||||
# pylint: enable=arguments-differ
|
||||
|
||||
|
||||
|
||||
+117
-23
@@ -11,6 +11,7 @@ from fcntl import fcntl, F_GETFL, F_SETFL
|
||||
from os import O_NONBLOCK
|
||||
import os
|
||||
from functools import partial
|
||||
import tempfile
|
||||
|
||||
# Command execution support
|
||||
|
||||
@@ -25,7 +26,7 @@ def checkRun( cmd ):
|
||||
return check_call( cmd.split( ' ' ) )
|
||||
|
||||
# pylint doesn't understand explicit type checking
|
||||
# pylint: disable=maybe-no-member
|
||||
# pylint: disable-msg=E1103
|
||||
|
||||
def oldQuietRun( *cmd ):
|
||||
"""Run a command, routing stderr to stdout, and return the output.
|
||||
@@ -119,7 +120,8 @@ def quietRun( cmd, **kwargs ):
|
||||
"Run a command and return merged stdout and stderr"
|
||||
return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
|
||||
|
||||
# pylint: enable=maybe-no-member
|
||||
# pylint: enable-msg=E1103
|
||||
# pylint: disable-msg=E1101
|
||||
|
||||
def isShellBuiltin( cmd ):
|
||||
"Return True if cmd is a bash builtin."
|
||||
@@ -132,6 +134,8 @@ def isShellBuiltin( cmd ):
|
||||
|
||||
isShellBuiltin.builtIns = None
|
||||
|
||||
# pylint: enable-msg=E1101
|
||||
|
||||
# Interface management
|
||||
#
|
||||
# Interfaces are managed as strings which are simply the
|
||||
@@ -145,22 +149,22 @@ isShellBuiltin.builtIns = None
|
||||
# live in the root namespace and thus do not have to be
|
||||
# explicitly moved.
|
||||
|
||||
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, runCmd=quietRun ):
|
||||
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, run=quietRun ):
|
||||
"""Make a veth pair connecting intf1 and intf2.
|
||||
intf1: string, interface
|
||||
intf2: string, interface
|
||||
runCmd: function to run shell commands (quietRun)
|
||||
node: node to run on or None (default)
|
||||
returns: ip link add result"""
|
||||
# Delete any old interfaces with the same names
|
||||
runCmd( 'ip link del ' + intf1 )
|
||||
runCmd( 'ip link del ' + intf2 )
|
||||
run( 'ip link del ' + intf1 )
|
||||
run( 'ip link del ' + intf2 )
|
||||
# Create new pair
|
||||
if addr1 is None and addr2 is None:
|
||||
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
|
||||
else:
|
||||
cmd = ( 'ip link add name ' + intf1 + ' address ' + addr1 +
|
||||
' type veth peer name ' + intf2 + ' address ' + addr2 )
|
||||
cmdOutput = runCmd( cmd )
|
||||
cmdOutput = run( cmd )
|
||||
if cmdOutput == '':
|
||||
return True
|
||||
else:
|
||||
@@ -199,8 +203,8 @@ def moveIntfNoRetry( intf, dstNode, printError=False ):
|
||||
return False
|
||||
return True
|
||||
|
||||
def moveIntf( intf, dstNode, printError=True,
|
||||
retries=3, delaySecs=0.001 ):
|
||||
def moveIntf( intf, dstNode, srcNode=None, printError=True,
|
||||
retries=3, delaySecs=0.001 ):
|
||||
"""Move interface to node, retrying on failure.
|
||||
intf: string, interface
|
||||
dstNode: destination Node
|
||||
@@ -293,7 +297,7 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
|
||||
def ipParse( ip ):
|
||||
"Parse an IP address and return an unsigned int."
|
||||
args = [ int( arg ) for arg in ip.split( '.' ) ]
|
||||
while len(args) < 4:
|
||||
while ( len(args) < 4 ):
|
||||
args.append( 0 )
|
||||
return ipNum( *args )
|
||||
|
||||
@@ -385,7 +389,7 @@ def sysctlTestAndSet( name, limit ):
|
||||
#read limit
|
||||
with open( name, 'r' ) as readFile:
|
||||
oldLimit = readFile.readline()
|
||||
if isinstance( limit, int ):
|
||||
if type( limit ) is int:
|
||||
#compare integer limits before overriding
|
||||
if int( oldLimit ) < limit:
|
||||
with open( name, 'w' ) as writeFile:
|
||||
@@ -424,12 +428,9 @@ def fixLimits():
|
||||
sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 )
|
||||
#Increase number of PTYs for nodes
|
||||
sysctlTestAndSet( 'kernel.pty.max', 20000 )
|
||||
# pylint: disable=broad-except
|
||||
except Exception:
|
||||
except:
|
||||
warn( "*** Error setting resource limits. "
|
||||
"Mininet's performance may be affected.\n" )
|
||||
# pylint: enable=broad-except
|
||||
|
||||
|
||||
def mountCgroups():
|
||||
"Make sure cgroups file system is mounted"
|
||||
@@ -448,7 +449,7 @@ def natural( text ):
|
||||
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+)', str( text ) ) ]
|
||||
return [ num( s ) for s in re.split( r'(\d+)', text ) ]
|
||||
|
||||
def naturalSeq( t ):
|
||||
"Natural sort key function for sequences"
|
||||
@@ -545,16 +546,15 @@ def ensureRoot():
|
||||
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
|
||||
"""Wait until server is listening on port.
|
||||
returns True if server is listening"""
|
||||
runCmd = ( client.cmd if client else
|
||||
partial( quietRun, shell=True ) )
|
||||
if not runCmd( 'which telnet' ):
|
||||
run = ( client.cmd if client else
|
||||
partial( quietRun, shell=True ) )
|
||||
if not run( 'which telnet' ):
|
||||
raise Exception('Could not find telnet' )
|
||||
# pylint: disable=maybe-no-member
|
||||
serverIP = server if isinstance( server, basestring ) else server.IP()
|
||||
serverIP = server if type( server ) is str else server.IP()
|
||||
cmd = ( 'sh -c "echo A | telnet -e A %s %s"' %
|
||||
( serverIP, port ) )
|
||||
( serverIP, port ) )
|
||||
time = 0
|
||||
while 'Connected' not in runCmd( cmd ):
|
||||
while 'Connected' not in run( cmd ):
|
||||
if timeout:
|
||||
print time
|
||||
if time >= timeout:
|
||||
@@ -566,3 +566,97 @@ def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
|
||||
sleep( .5 )
|
||||
time += .5
|
||||
return True
|
||||
|
||||
def updateMininet( option, opt_str, value, parser, *args, **kwargs ):
|
||||
# TODO check root
|
||||
# TODO check dependencies (git and make)
|
||||
url = 'https://github.com/mininet/mininet'
|
||||
choice = None
|
||||
if parser.rargs:
|
||||
choice = parser.rargs[ 0 ]
|
||||
current = kwargs['version']
|
||||
print 'Your current version is: %s' % current
|
||||
|
||||
# get available releases
|
||||
if not choice:
|
||||
s = quietRun('git ls-remote --tags %s' % url)
|
||||
#TODO check output for network failure
|
||||
tags = []
|
||||
for t in re.finditer(r'refs/tags/(\d\S*)', s):
|
||||
v = t.group(1)
|
||||
if v > current:
|
||||
tags.append( t.group(1) )
|
||||
if tags:
|
||||
print 'Available versions:\n', '\n'.join( tags )
|
||||
else:
|
||||
print 'Congratulations! You are up-to-date.'
|
||||
|
||||
# ask user to select a version
|
||||
while not choice:
|
||||
choice = raw_input('Select a version: ')
|
||||
if choice in tags:
|
||||
break
|
||||
elif 'exit' in choice:
|
||||
exit()
|
||||
else:
|
||||
print 'Error: Please type an available version.'
|
||||
choice = None
|
||||
|
||||
# sanity check
|
||||
if choice < current:
|
||||
print 'WARNING: The version that you have selected is a downgrade.'
|
||||
elif choice == current:
|
||||
print 'Version %s is already installed' % current
|
||||
exit()
|
||||
|
||||
# confirm choice
|
||||
if not re.match( r'[yY][eE]?[sS]?',
|
||||
raw_input( 'Do you want to upgrade to %s? [y/N] ' % choice ) ):
|
||||
print 'ABORTED'
|
||||
exit()
|
||||
|
||||
# proceed with installation
|
||||
# 1. detect make develop
|
||||
if 'dist-packages' in __file__:
|
||||
## mkdir; cd; git init
|
||||
develop = False
|
||||
installDir = tempfile.mkdtemp() + '/mininet'
|
||||
os.mkdir( installDir )
|
||||
os.chdir( installDir )
|
||||
quietRun( 'git init' )
|
||||
else:
|
||||
## cd to mininet directory
|
||||
develop = True
|
||||
print ( 'FAILURE: You have enabled "sudo make develop".\n\n'
|
||||
'To upgrade, check out the latest code and run "util/install.sh -n" '
|
||||
'and then re-run "sudo make develop".' )
|
||||
exit()
|
||||
# 2. fetch the tag
|
||||
if run( 'git fetch https://github.com/mininet/mininet %s' % choice ):
|
||||
print 'FAILURE: Could not update to %s' % choice
|
||||
exit()
|
||||
# 3. check to see if there is a branch conflict
|
||||
if run( 'git show-ref --verify --quiet refs/heads/%s' % choice ):
|
||||
# branch does not exist
|
||||
print run( 'git checkout -b %s FETCH_HEAD' % choice )
|
||||
else:
|
||||
# This should not happen until develop is supported...
|
||||
# TODO: branch already exists
|
||||
## git checkout %s
|
||||
## git reset --hard FETCH_HEAD
|
||||
print '... make develop enabled'
|
||||
# 4. perform installation
|
||||
if develop:
|
||||
## sudo make develop
|
||||
print '... make develop enabled'
|
||||
else:
|
||||
# remove mininet prior to upgrade
|
||||
oldDir = __file__.split( 'mininet/util' )[0]
|
||||
for f in os.listdir( oldDir + '/..' ):
|
||||
print f
|
||||
if 'mininet' in f:
|
||||
print run( 'rm -rfv %s' % oldDir + '/../' + f )
|
||||
# run the install script
|
||||
os.execl( installDir+'/util/install.sh', 'install.sh', '-n' )
|
||||
#TODO remove temp dir
|
||||
exit()
|
||||
|
||||
@@ -99,7 +99,6 @@ int main(int argc, char *argv[])
|
||||
char path[PATH_MAX];
|
||||
int nsid;
|
||||
int pid;
|
||||
char *cwd = get_current_dir_name();
|
||||
|
||||
static struct sched_param sp;
|
||||
while ((c = getopt(argc, argv, "+cdnpa:g:r:vh")) != -1)
|
||||
@@ -158,17 +157,18 @@ int main(int argc, char *argv[])
|
||||
sprintf(path, "/proc/%d/ns/mnt", pid);
|
||||
nsid = open(path, O_RDONLY);
|
||||
if (nsid < 0 || setns(nsid, 0) != 0) {
|
||||
char *cwd = get_current_dir_name();
|
||||
/* Plan B: chroot/chdir into pid's root file system */
|
||||
sprintf(path, "/proc/%d/root", pid);
|
||||
if (chroot(path) < 0) {
|
||||
perror(path);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/* chdir to correct working directory */
|
||||
if (chdir(cwd) != 0) {
|
||||
perror(cwd);
|
||||
return 1;
|
||||
/* need to chdir to correct working directory */
|
||||
if (chdir(cwd) != 0) {
|
||||
perror(cwd);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
|
||||
+3
-3
@@ -30,7 +30,7 @@ def fixParam( line ):
|
||||
def fixReturns( line ):
|
||||
"Change returns: foo to @return foo"
|
||||
return re.sub( 'returns:', r'@returns', line )
|
||||
|
||||
|
||||
def fixLine( line ):
|
||||
global comment
|
||||
match = spaces.match( line )
|
||||
@@ -69,7 +69,7 @@ def funTest():
|
||||
).splitlines( True )
|
||||
|
||||
fixLines( testFun )
|
||||
|
||||
|
||||
def fixLines( lines, fid ):
|
||||
for line in lines:
|
||||
os.write( fid, fixLine( line ) )
|
||||
@@ -86,4 +86,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,9 +140,6 @@ function mn_deps {
|
||||
function mn_dev {
|
||||
echo "Installing Mininet developer dependencies"
|
||||
$install doxygen doxypy texlive-fonts-recommended
|
||||
if ! $install doxygen-latex; then
|
||||
echo "doxygen-latex not needed"
|
||||
fi
|
||||
}
|
||||
|
||||
# The following will cause a full OF install, covering:
|
||||
|
||||
@@ -9,7 +9,7 @@ else
|
||||
host=$1
|
||||
fi
|
||||
|
||||
pid=`ps ax | grep "mininet:$host$" | grep bash | grep -v mnexec | awk '{print $1};'`
|
||||
pid=`ps ax | grep "mininet:$host$" | grep bash | awk '{print $1};'`
|
||||
|
||||
if echo $pid | grep -q ' '; then
|
||||
echo "Error: found multiple mininet:$host processes"
|
||||
|
||||
@@ -8,7 +8,7 @@ version = 'Mininet ' + co( 'PYTHONPATH=. bin/mn --version', shell=True )
|
||||
version = version.strip()
|
||||
|
||||
# Find all Mininet path references
|
||||
lines = co( "egrep -or 'Mininet [0-9\.]+\w*' *", shell=True )
|
||||
lines = co( "grep -or 'Mininet \w\+\.\w\+\.\w\+[+]*' *", shell=True )
|
||||
|
||||
error = False
|
||||
|
||||
|
||||
+38
-51
@@ -162,8 +162,8 @@ def srun( cmd, **kwargs ):
|
||||
def depend():
|
||||
"Install package dependencies"
|
||||
log( '* Installing package dependencies' )
|
||||
run( 'sudo apt-get -qy update' )
|
||||
run( 'sudo apt-get -qy install'
|
||||
run( 'sudo apt-get -y update' )
|
||||
run( 'sudo apt-get install -y'
|
||||
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
||||
' e2fsprogs dnsmasq curl'
|
||||
' python-setuptools mtools zip' )
|
||||
@@ -333,11 +333,9 @@ skipx
|
||||
|
||||
# Tell the Ubuntu/Debian installer to stop asking stupid questions
|
||||
|
||||
PreseedText = ( """
|
||||
"""
|
||||
#d-i mirror/country string manual
|
||||
#d-i mirror/http/hostname string mirrors.kernel.org
|
||||
"""
|
||||
PreseedText = """
|
||||
d-i mirror/country string manual
|
||||
d-i mirror/http/hostname string mirrors.kernel.org
|
||||
d-i mirror/http/directory string /ubuntu
|
||||
d-i mirror/http/proxy string
|
||||
d-i partman/confirm_write_new_label boolean true
|
||||
@@ -347,7 +345,7 @@ d-i partman/confirm_nooverwrite boolean true
|
||||
d-i user-setup/allow-password-weak boolean true
|
||||
d-i finish-install/reboot_in_progress note
|
||||
d-i debian-installer/exit/poweroff boolean true
|
||||
""" )
|
||||
"""
|
||||
|
||||
def makeKickstartFloppy():
|
||||
"Create and return kickstart floppy, kickstart, preseed"
|
||||
@@ -489,24 +487,22 @@ def login( vm, user='mininet', password='mininet' ):
|
||||
log( '* Waiting for login...' )
|
||||
|
||||
|
||||
def removeNtpd( vm, prompt=Prompt, ntpPackage='ntp' ):
|
||||
"Remove ntpd and set clock immediately"
|
||||
log( '* Removing ntpd' )
|
||||
vm.sendline( 'sudo -n apt-get -qy remove ' + ntpPackage )
|
||||
def disableNtpd( vm, prompt=Prompt ):
|
||||
"Turn off ntpd and set clock immediately"
|
||||
log( '* Turning off ntpd' )
|
||||
vm.sendline( 'sudo -n service ntp stop' )
|
||||
vm.expect( prompt )
|
||||
# Try to make sure that it isn't still running
|
||||
vm.sendline( 'sudo -n pkill ntpd' )
|
||||
log( '* Setting clock' )
|
||||
# -gq: set and quit immediately
|
||||
vm.sendline( 'sudo -n ntpd -gq' )
|
||||
vm.expect( prompt )
|
||||
log( '* Getting seconds since epoch from this server' )
|
||||
# Note r'date +%s' specifies a format for 'date', not python!
|
||||
seconds = int( run( r'date +%s' ) )
|
||||
log( '* Setting VM clock' )
|
||||
vm.sendline( 'sudo -n date -s @%d' % seconds )
|
||||
log( '* Waiting one second and running date command' )
|
||||
vm.sendline( 'sleep 1 && date ' )
|
||||
|
||||
|
||||
def sanityTest( vm ):
|
||||
"Run Mininet sanity test (pingall) in vm"
|
||||
vm.sendline( 'sudo -n mn --test pingall' )
|
||||
vm.sendline( 'sudo mn --test pingall' )
|
||||
if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=45 ) == 0:
|
||||
log( '* Sanity check OK' )
|
||||
else:
|
||||
@@ -518,9 +514,9 @@ def sanityTest( vm ):
|
||||
def coreTest( vm, prompt=Prompt ):
|
||||
"Run core tests (make test) in VM"
|
||||
log( '* Making sure cgroups are mounted' )
|
||||
vm.sendline( 'sudo -n service cgroup-lite restart' )
|
||||
vm.sendline( 'sudo service cgroup-lite restart' )
|
||||
vm.expect( prompt )
|
||||
vm.sendline( 'sudo -n cgroups-mount' )
|
||||
vm.sendline( 'sudo cgroups-mount' )
|
||||
vm.expect( prompt )
|
||||
log( '* Running make test' )
|
||||
vm.sendline( 'cd ~/mininet; sudo make test' )
|
||||
@@ -536,35 +532,29 @@ def coreTest( vm, prompt=Prompt ):
|
||||
log( '* Test', test, 'output:' )
|
||||
log( vm.before )
|
||||
|
||||
|
||||
def installPexpect( vm, prompt=Prompt ):
|
||||
"install pexpect"
|
||||
vm.sendline( 'sudo -n apt-get -qy install python-pexpect' )
|
||||
vm.expect( prompt )
|
||||
|
||||
|
||||
def noneTest( vm, prompt=Prompt ):
|
||||
def noneTest( vm ):
|
||||
"This test does nothing"
|
||||
installPexpect( vm, prompt )
|
||||
vm.sendline( 'echo' )
|
||||
|
||||
|
||||
def examplesquickTest( vm, prompt=Prompt ):
|
||||
"Quick test of mininet examples"
|
||||
installPexpect( vm, prompt )
|
||||
vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v -quick' )
|
||||
vm.sendline( 'sudo apt-get install python-pexpect' )
|
||||
vm.expect( prompt )
|
||||
vm.sendline( 'sudo python ~/mininet/examples/test/runner.py -v -quick' )
|
||||
|
||||
|
||||
def examplesfullTest( vm, prompt=Prompt ):
|
||||
"Full (slow) test of mininet examples"
|
||||
installPexpect( vm, prompt )
|
||||
vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v' )
|
||||
vm.sendline( 'sudo apt-get install python-pexpect' )
|
||||
vm.expect( prompt )
|
||||
vm.sendline( 'sudo python ~/mininet/examples/test/runner.py -v' )
|
||||
|
||||
|
||||
def walkthroughTest( vm, prompt=Prompt ):
|
||||
"Test mininet walkthrough"
|
||||
installPexpect( vm, prompt )
|
||||
vm.sendline( 'sudo -n python ~/mininet/mininet/test/test_walkthrough.py -v' )
|
||||
vm.sendline( 'sudo apt-get install python-pexpect' )
|
||||
vm.expect( prompt )
|
||||
vm.sendline( 'sudo python ~/mininet/mininet/test/test_walkthrough.py -v' )
|
||||
|
||||
|
||||
def useTest( vm, prompt=Prompt ):
|
||||
@@ -590,7 +580,7 @@ def checkOutBranch( vm, branch, prompt=Prompt ):
|
||||
vm.sendline( 'cd ~/mininet; git fetch --all; git checkout '
|
||||
+ branch + '; git pull --rebase origin ' + branch )
|
||||
vm.expect( prompt )
|
||||
vm.sendline( 'sudo -n make install' )
|
||||
vm.sendline( 'sudo make install' )
|
||||
|
||||
|
||||
def interact( vm, tests, pre='', post='', prompt=Prompt ):
|
||||
@@ -609,7 +599,7 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ):
|
||||
'install-mininet-vm.sh' % branch )
|
||||
vm.expect( prompt )
|
||||
log( '* Running VM install script' )
|
||||
installcmd = 'bash -v install-mininet-vm.sh'
|
||||
installcmd = 'bash install-mininet-vm.sh'
|
||||
if Branch:
|
||||
installcmd += ' ' + Branch
|
||||
vm.sendline( installcmd )
|
||||
@@ -840,14 +830,12 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
|
||||
os.chdir( '..' )
|
||||
|
||||
|
||||
def runTests( vm, tests=None, pre='', post='', prompt=Prompt, uninstallNtpd=False ):
|
||||
def runTests( vm, tests=None, pre='', post='', prompt=Prompt ):
|
||||
"Run tests (list) in vm (pexpect object)"
|
||||
# We disable ntpd and set the time so that ntpd won't be
|
||||
# messing with the time during tests. Set to true for a COW
|
||||
# disk and False for a non-COW disk.
|
||||
if uninstallNtpd:
|
||||
removeNtpd( vm )
|
||||
vm.expect( prompt )
|
||||
# We disable ntpd and set the time so that it won't be
|
||||
# messing with the time during tests
|
||||
disableNtpd( vm )
|
||||
vm.expect( prompt )
|
||||
if Branch:
|
||||
checkOutBranch( vm, branch=Branch )
|
||||
vm.expect( prompt )
|
||||
@@ -881,7 +869,7 @@ def getMininetVersion( vm ):
|
||||
return version
|
||||
|
||||
|
||||
def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None,
|
||||
def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None,
|
||||
runFunction=None, **runArgs ):
|
||||
"""Boot and test VM
|
||||
tests: list of tests to run
|
||||
@@ -914,7 +902,7 @@ def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None,
|
||||
if runFunction:
|
||||
runFunction( vm, **runArgs )
|
||||
log( '* Shutting down' )
|
||||
vm.sendline( 'sudo -n shutdown -h now ' )
|
||||
vm.sendline( 'sudo shutdown -h now ' )
|
||||
log( '* Waiting for shutdown' )
|
||||
vm.wait()
|
||||
if outputFile:
|
||||
@@ -1023,8 +1011,7 @@ def parseArgs():
|
||||
exit( 1 )
|
||||
for image in args.image:
|
||||
bootAndRun( image, runFunction=runTests, tests=args.test, pre=args.run,
|
||||
post=args.post, memory=args.memory, outputFile=args.out,
|
||||
uninstallNtpd=True )
|
||||
post=args.post, memory=args.memory, outputFile=args.out )
|
||||
if not ( args.depend or args.list or args.clean or args.flavor
|
||||
or args.image ):
|
||||
parser.print_help()
|
||||
|
||||
@@ -6,21 +6,20 @@
|
||||
#
|
||||
# optional argument: Mininet branch to install
|
||||
set -e
|
||||
echo "$(whoami) ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers > /dev/null
|
||||
echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers > /dev/null
|
||||
sudo sed -i -e 's/Default/#Default/' /etc/sudoers
|
||||
echo mininet-vm | sudo tee /etc/hostname > /dev/null
|
||||
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
|
||||
# Update from official archive
|
||||
sudo apt-get update
|
||||
# 12.10 and earlier
|
||||
#sudo sed -i -e 's/us.archive.ubuntu.com/mirrors.kernel.org/' \
|
||||
# /etc/apt/sources.list
|
||||
sudo sed -i -e 's/us.archive.ubuntu.com/mirrors.kernel.org/' \
|
||||
/etc/apt/sources.list
|
||||
# 13.04 and later
|
||||
#sudo sed -i -e 's/\/archive.ubuntu.com/\/mirrors.kernel.org/' \
|
||||
# /etc/apt/sources.list
|
||||
sudo sed -i -e 's/\/archive.ubuntu.com/\/mirrors.kernel.org/' \
|
||||
/etc/apt/sources.list
|
||||
sudo apt-get update
|
||||
# Clean up vmware easy install junk if present
|
||||
if [ -e /etc/issue.backup ]; then
|
||||
sudo mv /etc/issue.backup /etc/issue
|
||||
@@ -34,9 +33,7 @@ git clone git://github.com/mininet/mininet
|
||||
# Optionally check out branch
|
||||
if [ "$1" != "" ]; then
|
||||
pushd mininet
|
||||
#git checkout -b $1 $1
|
||||
# TODO branch will in detached HEAD state if it is not master
|
||||
git checkout $1
|
||||
git checkout -b $1 $1
|
||||
popd
|
||||
fi
|
||||
# Install Mininet
|
||||
|
||||
Reference in New Issue
Block a user