Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6eb8973c0b | |||
| 0848c5f3c1 | |||
| 41ac7c4a6e | |||
| 5ae59fdbc9 | |||
| ac7c68089b | |||
| ba569f4d82 | |||
| 615f37dbbe | |||
| aaa8886328 | |||
| 05ee42ee3c | |||
| 7685e41a3e | |||
| 39103f4c9d | |||
| 5b1b376336 | |||
| 8725056d7b | |||
| 88f14e946a | |||
| 0cefa0b8de | |||
| 1b4c7d706d | |||
| 5e909c6e4f | |||
| 740acfb350 | |||
| a978ce9484 | |||
| 1e546cb73b | |||
| a2cb3e4051 | |||
| 4cfe97ee49 | |||
| b762d0bf96 | |||
| 4840bc20c5 | |||
| 30a5fd0df4 | |||
| 4707e90028 | |||
| cf5675876c | |||
| 1169982c0a | |||
| 32a7126d2b | |||
| 678add6202 | |||
| 936d303f5d | |||
| c5413a74f5 | |||
| 3f5503d773 | |||
| aa0176fce6 | |||
| bdd2f8d87f | |||
| 1a47699f21 | |||
| 270a6ba333 | |||
| c3ba039a97 | |||
| 8a50d3867c | |||
| d1b0b32c8c | |||
| 71d3e58d8c | |||
| 9edbb3b4a3 | |||
| 57294d013e | |||
| bfb6b9f4c4 | |||
| 1b55f09c4e | |||
| d7f399d7a1 | |||
| 1c42632978 | |||
| 5ef6e1dedc | |||
| f8e54cad47 | |||
| fc3152d724 | |||
| 9a3a3edf75 | |||
| 377a4b5af5 | |||
| 4a1dbac09b | |||
| 92fe3cc120 | |||
| 39ed456de2 | |||
| 5d4ec1ab9e | |||
| 6b90434b9c | |||
| c2fb4d2e8c | |||
| 7b240ce5b8 | |||
| c7de350a4e | |||
| 06dae1adc1 | |||
| f0c726a42f | |||
| a882d68a5f | |||
| 9b69c99f4e | |||
| 904796436d | |||
| ec2d93d482 | |||
| dffddc338f | |||
| b6ad3a1619 | |||
| 96ac94a985 | |||
| 715db45b9d | |||
| e07a29b4e6 | |||
| 2255bc7da3 | |||
| 12f4d5b8ae | |||
| f9d5ef3461 | |||
| dad451bf8f | |||
| 77938e0a85 | |||
| e4003290e0 | |||
| 9517f6c197 | |||
| d8c30910cb | |||
| 53975940de | |||
| 77c473687e | |||
| 0847991030 | |||
| 462929b3af | |||
| 26c7c70024 | |||
| 942745e91f | |||
| dcc39a7b25 | |||
| ebdb3a5107 | |||
| 6def304585 | |||
| e02e338e3c | |||
| b7c412073a | |||
| afdf9fd571 | |||
| 336b01ae34 | |||
| 537e8242fc | |||
| cab8970b0a | |||
| 3625149356 | |||
| fd9e011277 | |||
| 591a961762 | |||
| bfc42f6d02 | |||
| 458d20e3fd | |||
| 73b2ecc5fe | |||
| 6a5adb48d2 | |||
| 5f69bf0ade | |||
| d5b4aa829b | |||
| 1704541cc9 | |||
| f56a12a1ab | |||
| 70c643f8de | |||
| fa9fd770ca | |||
| dd41ecaae4 | |||
| 287c1fb154 | |||
| 1c268e7667 | |||
| 2b8d254cc0 | |||
| 8fc867b1f4 | |||
| 6af291c968 | |||
| c0582b9c5e | |||
| 023c53aec6 | |||
| bd1a442a17 | |||
| 6c17e1ade7 | |||
| e56f6839f1 | |||
| e0436642ae | |||
| c5f626ce27 | |||
| 10e758e80a | |||
| de28f67a97 | |||
| 46242acdd4 | |||
| e203808a20 | |||
| 3b3a11b6dd | |||
| 7c0d1506e7 | |||
| c5f23b9f82 | |||
| cfb0a6d3f0 | |||
| 664ba39383 | |||
| 598d694058 | |||
| f92e3d3432 | |||
| ce4f1e0c92 | |||
| 31f44e1856 | |||
| 36d2b21187 | |||
| 1888001555 | |||
| fdc3156a91 | |||
| cf6da391fa | |||
| f170cc6f64 | |||
| fd96de6485 | |||
| 73f530b569 | |||
| c7a27b8876 | |||
| b7a6b8137f | |||
| d072e531c2 | |||
| 8139d7d1b4 | |||
| 273b14b771 | |||
| a73e776695 | |||
| bfda33544a | |||
| 1f4525ed0a | |||
| 1969669f51 | |||
| c639f342e5 | |||
| 6e887d0788 | |||
| 092863f113 | |||
| 5100a38fcd | |||
| 3bcecd8e08 | |||
| 486676d617 | |||
| 9e5280b4d1 | |||
| b70eed69d3 | |||
| d96b35694a | |||
| c7deeae11c | |||
| 323989953b | |||
| 17f9756e5c | |||
| 71f3931f2f | |||
| 1e9ca5f7fc | |||
| 8933c996cb | |||
| afa83cd5ae | |||
| 3e81ea7455 | |||
| 8102704726 | |||
| f4490069ca | |||
| 2ac4f92af3 | |||
| f98154a323 | |||
| 7b48460e9e | |||
| 3a0ef258d1 | |||
| 0d9a6796df | |||
| 08a59783f4 | |||
| e37afe996b | |||
| 6a387faa04 | |||
| 1a134cb4d2 | |||
| af4921adc5 | |||
| f94ee8ec97 | |||
| 356b024d6b | |||
| 2283bb01e6 | |||
| 3eef584c6b | |||
| 2e00a7de97 | |||
| e28348f6cd | |||
| 4b744d1fb0 | |||
| 2822998cad | |||
| 853815500d | |||
| 1a2925923f | |||
| cac884a85e | |||
| f314a6626a | |||
| bf83f4f343 | |||
| 1ef12e450a | |||
| 56fc6c3955 | |||
| 88cbf4ec42 | |||
| 26b3959968 | |||
| 966dd20b4b | |||
| 82998cac8b | |||
| 34b1f4161a | |||
| dfb297901f | |||
| 67236e9db3 | |||
| 8a00c3abf8 | |||
| 037f7f5921 |
@@ -1,14 +1,15 @@
|
||||
<!--
|
||||
Mininet uses GitHub issues for bug reports and feature requests only.
|
||||
These issues can be viewed at bugs.mininet.org
|
||||
|
||||
If you have a question that is not a bug report or a feature request,
|
||||
If you have a question that is not a **bug report** or a **feature request**,
|
||||
please use the documentation at docs.mininet.org, the FAQ
|
||||
at faq.mininet.org, and the mininet-discuss mailing list.
|
||||
|
||||
For bug reports, please fill in the following information in detail,
|
||||
and also feel free to include additional information such as debug
|
||||
output from mn -v debug, etc.
|
||||
--- Cut Here ---
|
||||
-->
|
||||
### Expected/Desired Behavior:
|
||||
|
||||
### Actual Behavior:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
name: code-check
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
code-check:
|
||||
name: Mininet Code Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: Check out Mininet source
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Mininet code check dependencies
|
||||
run: |
|
||||
PYTHON=`which python` util/install.sh -n
|
||||
python -m pip install pylint==2.15.7
|
||||
- name: Run code check
|
||||
run: make codecheck
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
name: mininet-tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Mininet Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, ubuntu-20.04]
|
||||
py: [python3, python2]
|
||||
steps:
|
||||
- name: Check out Mininet source
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Python ${{ matrix.py }}
|
||||
run: sudo apt install ${{ matrix.py }}
|
||||
- name: Install Mininet and base dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
# This seems too slow unfortunately:
|
||||
# sudo apt-get upgrade -y -qq
|
||||
PYTHON=${{ matrix.py }} util/install.sh -nv
|
||||
- name: Disable slow udevd
|
||||
run: sudo systemctl stop systemd-udevd
|
||||
systemd-udevd-kernel.socket
|
||||
systemd-udevd-control.socket
|
||||
|| echo "couldn't disable udevd"
|
||||
- name: Sanity test
|
||||
run: |
|
||||
export sudo="sudo env PATH=$PATH"
|
||||
export PYTHON=${{ matrix.py }}
|
||||
# Newer OvS tries OpenFlow15 which crashes ovsc on ubuntu-20.04
|
||||
$sudo mn --switch ovs,protocols=OpenFlow13 --test pingall
|
||||
- name: Install test dependencies
|
||||
run: |
|
||||
sudo apt-get install -qq vlan
|
||||
export PYTHON=${{ matrix.py }}
|
||||
sudo $PYTHON -m pip install pexpect
|
||||
util/install.sh -fw
|
||||
- name: Run core tests
|
||||
run: |
|
||||
export sudo="sudo env PATH=$PATH"
|
||||
export PYTHON=${{ matrix.py }}
|
||||
$sudo $PYTHON mininet/test/runner.py -v
|
||||
- name: Run examples tests
|
||||
run: |
|
||||
export sudo="sudo env PATH=$PATH"
|
||||
export PYTHON=${{ matrix.py }}
|
||||
$sudo $PYTHON examples/test/runner.py -v
|
||||
@@ -16,7 +16,7 @@
|
||||
#init-hook=
|
||||
|
||||
# Profiled execution.
|
||||
profile=no
|
||||
#profile=no
|
||||
|
||||
# Add <file or directory> to the black list. It should be a base name, not a
|
||||
# path. You may set this option multiple times.
|
||||
@@ -41,10 +41,17 @@ 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, locally-enabled
|
||||
#
|
||||
# Note: we may want to re-enable some of these at some point, but many of them
|
||||
# are just style issues rather than errors.
|
||||
#
|
||||
disable=invalid-name, super-init-not-called, fixme,
|
||||
too-many-instance-attributes, too-few-public-methods,
|
||||
too-many-locals, too-many-public-methods, duplicate-code,
|
||||
locally-disabled,
|
||||
useless-object-inheritance, unnecessary-pass, no-else-return,
|
||||
no-else-raise, no-else-continue, super-with-arguments,
|
||||
consider-using-f-string, unspecified-encoding
|
||||
|
||||
# bad-continuation, wrong-import-order
|
||||
|
||||
@@ -56,12 +63,12 @@ output-format=colorized
|
||||
msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
|
||||
|
||||
# Include message's id in output
|
||||
include-ids=yes
|
||||
# include-ids=yes
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
# files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
@@ -75,7 +82,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
|
||||
|
||||
# Add a comment according to your evaluation note. This is used by the global
|
||||
# evaluation report (R0004).
|
||||
comment=no
|
||||
#comment=no
|
||||
|
||||
# Enable the report(s) with the given id(s).
|
||||
#enable-report=
|
||||
@@ -97,7 +104,7 @@ comment=no
|
||||
[BASIC]
|
||||
|
||||
# Required attributes for module, separated by a comma
|
||||
required-attributes=
|
||||
#required-attributes=
|
||||
|
||||
# Regular expression which should only match functions or classes name which do
|
||||
# not require a docstring
|
||||
@@ -138,7 +145,7 @@ good-names=i,j,k,ex,Run,_
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,apply,inpu
|
||||
#bad-functions=map,filter,apply,inpu
|
||||
|
||||
|
||||
# try to find bugs in the code using type inference
|
||||
@@ -155,7 +162,7 @@ ignored-classes=SQLObjec
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
#zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed.
|
||||
@@ -193,7 +200,7 @@ additional-builtins=
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines in Zope's Interface base class.
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
#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
|
||||
@@ -215,7 +222,7 @@ max-locals=15
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branchs=12
|
||||
#max-branchs=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
language: python
|
||||
sudo: required
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- dist: trusty
|
||||
env: dist="14.04 LTS trusty"
|
||||
# - dist: xenial
|
||||
# env: dist="16.04 LTS xenial"
|
||||
# Travis-CI only proposes 14.04 LTS Trusty and there is no plan to update to 16.04 xenial
|
||||
# (c.f. https://github.com/travis-ci/travis-ci/issues/5821)
|
||||
# It is useless to add a second job because it will run in the same Ubuntu version (14.04)
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq vlan
|
||||
- sudo util/install.sh -n
|
||||
|
||||
install:
|
||||
- bash -c "if [ `lsb_release -rs` == '14.04' ]; then make codecheck; fi"
|
||||
- sudo util/install.sh -fnvw
|
||||
|
||||
script:
|
||||
- sudo mn --test pingall
|
||||
- sudo python mininet/test/runner.py -v -quick
|
||||
- sudo python examples/test/runner.py -v -quick
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
# More details: https://docs.travis-ci.com/user/notifications#Configuring-email-notifications
|
||||
+30
-1
@@ -18,34 +18,63 @@ Cody Burkard
|
||||
|
||||
Additional Mininet Contributors
|
||||
|
||||
Joseph Beshay
|
||||
M S Vishwanath Bhat
|
||||
Muhammad Umair Bhatti
|
||||
Arie Bregman
|
||||
Tomasz Buchert
|
||||
Gustavo Pantuza Coelho Pinto
|
||||
Fernando Cappi
|
||||
HW Chiu
|
||||
Ryan Cox
|
||||
Shaun Crampton
|
||||
Jason Croft
|
||||
Hantao Cui
|
||||
Nirmoy Das
|
||||
Lenoardo D'avila
|
||||
Giuseppe Di Lena
|
||||
David Erickson
|
||||
Juan Gascon
|
||||
Glen Gibb
|
||||
Andrew Ferguson
|
||||
Eder Leao Fernandes
|
||||
Julian Filter
|
||||
Ben Frankel
|
||||
Tim Gates
|
||||
Gregory Gee
|
||||
Jon Hall
|
||||
Roan Huang
|
||||
Vitaly Ivanov
|
||||
Theo Jepsen
|
||||
Mathieu Jadin
|
||||
Babis Kaidos
|
||||
Rich Lane
|
||||
Rémy Léone
|
||||
Xiaozhou Li
|
||||
Zi Shen Lim
|
||||
David Mahler
|
||||
Felix Maurer
|
||||
Murphy McCauley
|
||||
Alex Moijes
|
||||
Felician Nemeth
|
||||
José Pedro Oliveira
|
||||
James Page
|
||||
Gustavo Pantuza Coelho Pinto
|
||||
Ramon Pujianto
|
||||
Stempha Reiter
|
||||
Damien Saucez
|
||||
Shan Sikdar
|
||||
Angad Singh
|
||||
Piyush Srivastava
|
||||
Ed Swierk
|
||||
Darshan Thaker
|
||||
Olivier Tl]ilmans
|
||||
Niels van Adrichem
|
||||
Brad Walker
|
||||
Andreas Wundsam
|
||||
Vikas Yadav
|
||||
Isaku Yamahata
|
||||
Baohua Yang
|
||||
Zhuo
|
||||
|
||||
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.3.0d1
|
||||
Mininet 2.3.1b4
|
||||
---
|
||||
|
||||
The supported installation methods for Mininet are 1) using a
|
||||
@@ -32,28 +32,31 @@ like to contribute an installation script, we would welcome it!)
|
||||
2. Next-easiest option: use our Ubuntu package!
|
||||
|
||||
To install Mininet itself (i.e. `mn` and the Python API) on Ubuntu
|
||||
12.10+:
|
||||
16.04+:
|
||||
|
||||
sudo apt-get install mininet
|
||||
|
||||
Note: if you are upgrading from an older version of Mininet, make
|
||||
sure you remove the old OVS from `/usr/local`:
|
||||
|
||||
sudo rm /usr/local/bin/ovs*
|
||||
sudo rm /usr/local/sbin/ovs*
|
||||
Note: this may install an older version of Mininet which may not
|
||||
support Python 3. If you would like the latest version of Mininet,
|
||||
consider installing from source as described in the next section.
|
||||
|
||||
3. Native installation from source
|
||||
|
||||
3.1. Native installation from source on Ubuntu 12.04+
|
||||
If you are running Ubuntu, Debian, or Fedora, you may be able to use
|
||||
our handy `install.sh` script, which is in `util/`. Please read the
|
||||
following sections first.
|
||||
|
||||
3.1. Obtaining the Mininet source code
|
||||
|
||||
If you're reading this, you've probably already done so, but the
|
||||
command to download the Mininet source code is:
|
||||
|
||||
git clone git://github.com/mininet/mininet.git
|
||||
git clone https://github.com/mininet/mininet.git
|
||||
|
||||
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
|
||||
Mininet (which we recommend!) If you want to run the last
|
||||
tagged/released version of Mininet, you can look at the release tags
|
||||
using
|
||||
|
||||
cd mininet
|
||||
git tag
|
||||
@@ -64,21 +67,38 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
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 `util/`.
|
||||
3.1.1 *CAUTION: USE AT YOUR OWN RISK!*
|
||||
|
||||
*WARNING: USE AT YOUR OWN RISK!*
|
||||
|
||||
`install.sh` is a bit intrusive and may possibly damage your OS
|
||||
`install.sh` can be 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.
|
||||
trying it in a VM before trying it on a system you use from day to
|
||||
day.
|
||||
|
||||
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!
|
||||
|
||||
To install Mininet itself, the OpenFlow reference implementation, and
|
||||
You can change the directory where the dependencies are installed
|
||||
using the -s <directory> flag.
|
||||
|
||||
util/install.sh -s <directory> ...
|
||||
|
||||
3.1.2 Running `install.sh`
|
||||
|
||||
Installing a "minimal" version of Mininet with Open vSwitch should
|
||||
be reasonably non-perturbing since it should not create directories
|
||||
for other tools:
|
||||
|
||||
util/install.sh -nv
|
||||
|
||||
Note this will not install a controller, so you will have to either
|
||||
install your own controller, or use a switch such OVSBridge that does
|
||||
not require a controller:
|
||||
|
||||
sudo mn --switch ovsbr --test pingall
|
||||
|
||||
To install Mininet itself, the OpenFlow reference controller, and
|
||||
Open vSwitch, you may use:
|
||||
|
||||
util/install.sh -fnv
|
||||
@@ -88,6 +108,27 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
sudo mn --test pingall
|
||||
|
||||
3.1.3 Python 3 and Python 2 support
|
||||
|
||||
Mininet supports Python 3 and Python 2. By default, `install.sh`
|
||||
will use whatever `python` is on your system. To specify a
|
||||
specific version of Python, you can set the PYTHON environment
|
||||
variable:
|
||||
|
||||
PYTHON=python3 util/install.sh -fnv
|
||||
|
||||
You can install Mininet for both Python 3 and Python 2:
|
||||
|
||||
PYTHON=python2 util/install.sh -fnv
|
||||
PYTHON=python3 util/install.sh -n
|
||||
|
||||
Whichever version was installed last will be the default for `mn`.
|
||||
As long as Mininet is installed for the appropriate version of
|
||||
Python, you can run it using that version of Python:
|
||||
|
||||
python3 `which mn`
|
||||
python2 `which mn`
|
||||
|
||||
To install ALL of the software which we use for OpenFlow tutorials,
|
||||
including POX, the OpenFlow WireShark dissector, the `oftest`
|
||||
framework, and other potentially useful software, you may use:
|
||||
@@ -96,12 +137,7 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
This takes about 4 minutes on our test system.
|
||||
|
||||
You can change the directory where the dependencies are installed using
|
||||
the -s <directory> flag.
|
||||
|
||||
util/install.sh -s <directory> -a
|
||||
|
||||
3.2. Native installation from source on Fedora 18+.
|
||||
3.2. (Experimental) Native installation from source on Fedora:
|
||||
|
||||
As root execute the following operations:
|
||||
|
||||
@@ -109,21 +145,9 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
yum install git
|
||||
|
||||
* create an user account (e.g. mininet) and add it to the wheel group
|
||||
|
||||
useradd [...] mininet
|
||||
usermod -a -G wheel mininet
|
||||
|
||||
* change the SElinux setting to permissive. It can be done
|
||||
temporarily with:
|
||||
|
||||
setenforce 0
|
||||
|
||||
then login with the new account (e.g. mininet) and do the following:
|
||||
|
||||
* clone the Mininet repository
|
||||
|
||||
git clone git://github.com/mininet/mininet.git
|
||||
git clone https://github.com/mininet/mininet.git
|
||||
|
||||
* install Mininet, the OpenFlow reference implementation, and
|
||||
Open vSwitch
|
||||
@@ -139,7 +163,10 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
sudo mn --test pingall
|
||||
|
||||
4. Creating your own Mininet/OpenFlow tutorial VM
|
||||
Note that `install.sh -fnv `may not install all dependencies on Fedora,
|
||||
and many tests may still fail.
|
||||
|
||||
4. Creating your own Mininet/OpenFlow tutorial VM on Ubuntu/Debian
|
||||
|
||||
Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial
|
||||
is easy! First, create a new Ubuntu VM. Next, run two commands in it:
|
||||
@@ -155,7 +182,8 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
Although we don't support other Linux distributions directly, it
|
||||
should be possible to install and run Mininet with some degree of
|
||||
manual effort.
|
||||
manual effort. People have even gotten `mn --switch user` to run
|
||||
in a ChromeOS container.
|
||||
|
||||
In general, you must have:
|
||||
|
||||
@@ -172,8 +200,11 @@ like to contribute an installation script, we would welcome it!)
|
||||
support other Linux distributions.
|
||||
|
||||
|
||||
Good luck!
|
||||
As always, please feel free to submit issues or pull requests for
|
||||
installation-related features.
|
||||
|
||||
Mininet Team
|
||||
Good luck, and have fun!
|
||||
|
||||
Mininet Developers
|
||||
|
||||
---
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
Mininet 2.3.0d1 License
|
||||
BSD 3-Clause License (original Mininet authors: Bob Lantz and Brandon Heller)
|
||||
|
||||
Copyright (c) 2013-2016 Open Networking Laboratory
|
||||
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
|
||||
The Leland Stanford Junior University
|
||||
Copyright (c) 2013-2022 Open Networking Foundation
|
||||
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of The Leland Stanford Junior University
|
||||
|
||||
Original authors: Bob Lantz and Brandon Heller
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
We are making Mininet available for public use and benefit with the
|
||||
expectation that others will use, modify and enhance the Software and
|
||||
contribute those enhancements back to the community. However, since we
|
||||
would like to make the Software available for broadest use, with as few
|
||||
restrictions as possible permission is hereby granted, free of charge, to
|
||||
any person obtaining a copy of this Software to deal in the Software
|
||||
under the copyrights without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
The name and trademarks of copyright holder(s) may NOT be used in
|
||||
advertising or publicity pertaining to the Software or any derivatives
|
||||
without specific, written prior permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@@ -2,16 +2,19 @@ MININET = mininet/*.py
|
||||
TEST = mininet/test/*.py
|
||||
EXAMPLES = mininet/examples/*.py
|
||||
MN = bin/mn
|
||||
PYMN = python -B bin/mn
|
||||
PYTHON ?= python
|
||||
PYMN = $(PYTHON) -B bin/mn
|
||||
BIN = $(MN)
|
||||
PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
|
||||
MNEXEC = mnexec
|
||||
MANPAGES = mn.1 mnexec.1
|
||||
P8IGN = E251,E201,E302,E202,E126,E127,E203,E226
|
||||
BINDIR = /usr/bin
|
||||
MANDIR = /usr/share/man/man1
|
||||
P8IGN = E251,E201,E302,E202,E126,E127,E203,E226,E402,W504,W503,E731
|
||||
PREFIX ?= /usr
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
MANDIR ?= $(PREFIX)/share/man/man1
|
||||
DOCDIRS = doc/html doc/latex
|
||||
PDF = doc/latex/refman.pdf
|
||||
CC ?= cc
|
||||
|
||||
CFLAGS += -Wall -Wextra
|
||||
|
||||
@@ -23,14 +26,14 @@ clean:
|
||||
codecheck: $(PYSRC)
|
||||
-echo "Running code check"
|
||||
util/versioncheck.py
|
||||
pyflakes $(PYSRC)
|
||||
pyflakes3 $(PYSRC) || pyflakes $(PYSRC)
|
||||
pylint --rcfile=.pylint $(PYSRC)
|
||||
# Exclude miniedit from pep8 checking for now
|
||||
pep8 --repeat --ignore=$(P8IGN) `ls $(PYSRC) | grep -v miniedit.py`
|
||||
|
||||
errcheck: $(PYSRC)
|
||||
-echo "Running check for errors only"
|
||||
pyflakes $(PYSRC)
|
||||
pyflakes3 $(PYSRC) || pyflakes $(PYSRC)
|
||||
pylint -E --rcfile=.pylint $(PYSRC)
|
||||
|
||||
test: $(MININET) $(TEST)
|
||||
@@ -44,18 +47,26 @@ slowtest: $(MININET)
|
||||
mininet/examples/test/runner.py -v
|
||||
|
||||
mnexec: mnexec.c $(MN) mininet/net.py
|
||||
cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(PYMN) --version`\" $< -o $@
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) \
|
||||
-DVERSION=\"`PYTHONPATH=. $(PYMN) --version 2>&1`\" $< -o $@
|
||||
|
||||
install: $(MNEXEC) $(MANPAGES)
|
||||
install $(MNEXEC) $(BINDIR)
|
||||
install $(MANPAGES) $(MANDIR)
|
||||
python setup.py install
|
||||
install-mnexec: $(MNEXEC)
|
||||
install -D $(MNEXEC) $(BINDIR)/$(MNEXEC)
|
||||
|
||||
install-manpages: $(MANPAGES)
|
||||
install -D -t $(MANDIR) $(MANPAGES)
|
||||
|
||||
install: install-mnexec install-manpages
|
||||
# This seems to work on all pip versions
|
||||
$(PYTHON) -m pip uninstall -y mininet || true
|
||||
$(PYTHON) -m pip install .
|
||||
|
||||
develop: $(MNEXEC) $(MANPAGES)
|
||||
# Perhaps we should link these as well
|
||||
install $(MNEXEC) $(BINDIR)
|
||||
install $(MANPAGES) $(MANDIR)
|
||||
python setup.py develop
|
||||
$(PYTHON) -m pip uninstall -y mininet || true
|
||||
$(PYTHON) -m pip install -e . --no-binary :all:
|
||||
|
||||
man: $(MANPAGES)
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ Mininet: Rapid Prototyping for Software Defined Networks
|
||||
========================================================
|
||||
*The best way to emulate almost any network on your laptop!*
|
||||
|
||||
Mininet 2.3.0d1
|
||||
Mininet 2.3.1b4
|
||||
|
||||
[![Build Status][1]](https://github.com/mininet/mininet/actions)
|
||||
|
||||
[![Build Status][1]](https://travis-ci.org/mininet/mininet)
|
||||
|
||||
### What is Mininet?
|
||||
|
||||
@@ -67,27 +68,35 @@ Mininet includes:
|
||||
|
||||
`mn -c`
|
||||
|
||||
### New features in this release
|
||||
### Python 3 Support
|
||||
|
||||
This is primarily a performance improvement and bug fix release.
|
||||
- Mininet 2.3.1b4 supports Python 3 and Python 2
|
||||
|
||||
- Batch startup has been implemented for Open vSwitch, improving
|
||||
startup performance.
|
||||
- You can install both the Python 3 and Python 2 versions of
|
||||
Mininet side by side, but the most recent installation will
|
||||
determine which Python version is used by default by `mn`.
|
||||
|
||||
- OVS patch links have been implemented via OVSLink and --link ovs
|
||||
- You can run `mn` directly with Python 2 or Python 3,
|
||||
as long as the appropriate version of Mininet is installed,
|
||||
e.g.
|
||||
|
||||
Warning! These links have *serious limitations* compared to
|
||||
virtual Ethernet pairs: they are not attached to real Linux
|
||||
interfaces so you cannot use tcpdump or wireshark with them;
|
||||
they also cannot be used in long chains - we don't recommend more
|
||||
than 64 OVSLinks, for example --linear,64. However, they can offer
|
||||
significantly better performance than veth pairs, for certain
|
||||
configurations.
|
||||
$ sudo python2 `which mn`
|
||||
|
||||
- You can now easily install Mininet on a Raspberry Pi ;-)
|
||||
- More information regarding Python 3 and Python 2 support
|
||||
may be found in the release notes on http://docs.mininet.org.
|
||||
|
||||
- Additional information for this release and previous releases
|
||||
may be found in the release notes on docs.mininet.org
|
||||
### Other Enhancements and Information
|
||||
|
||||
- Support for Ubuntu 22.04 LTS (and 20.04)
|
||||
|
||||
- More reliable testing and CI via github actions
|
||||
|
||||
- Preliminary support for cgroups v2 (and v1)
|
||||
|
||||
- Minor bug fixes (2.3.1)
|
||||
|
||||
- Additional information about this release and previous releases
|
||||
may be found in the release notes on http://docs.mininet.org.
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -100,7 +109,8 @@ information, including a Mininet walkthrough and an introduction
|
||||
to the Python API, is available on the
|
||||
[Mininet Web Site](http://mininet.org).
|
||||
There is also a wiki which you are encouraged to read and to
|
||||
contribute to, particularly the Frequently Asked Questions (FAQ.)
|
||||
contribute to, particularly the Frequently Asked Questions
|
||||
(FAQ) at http://faq.mininet.org.
|
||||
|
||||
### Support
|
||||
|
||||
@@ -111,22 +121,22 @@ Mininet mailing list, `mininet-discuss` at:
|
||||
|
||||
### Join Us
|
||||
|
||||
Thanks again to all of the Mininet contributors!
|
||||
Thanks again to all of the Mininet contributors and users!
|
||||
|
||||
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.
|
||||
at <https://github.com/mininet>. You are encouraged to download,
|
||||
examine, and modify the code, and to 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
|
||||
|
||||
Best wishes, and we look forward to seeing what you can do with
|
||||
Mininet to change the networking world!
|
||||
Have fun! We look forward to seeing what you will do with Mininet
|
||||
to change the networking world.
|
||||
|
||||
Bob Lantz
|
||||
Mininet Core Team
|
||||
Bob Lantz,
|
||||
on behalf of the Mininet Contributors
|
||||
|
||||
[1]: https://travis-ci.org/mininet/mininet.svg?branch=master
|
||||
[1]: https://github.com/mininet/mininet/workflows/mininet-tests/badge.svg
|
||||
|
||||
@@ -11,15 +11,20 @@ Example to pull custom params (topo, switch, etc.) from a file:
|
||||
sudo mn --custom ~/mininet/custom/custom_example.py
|
||||
"""
|
||||
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from functools import partial
|
||||
from optparse import OptionParser # pylint: disable=deprecated-module
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
|
||||
# Fix setuptools' evil madness, and open up (more?) security holes
|
||||
if 'PYTHONPATH' in os.environ:
|
||||
sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
|
||||
from mininet.clean import cleanup
|
||||
import mininet.cli
|
||||
from mininet.log import lg, LEVELS, info, debug, warn, error, output
|
||||
@@ -34,10 +39,7 @@ from mininet.link import Link, TCLink, TCULink, OVSLink
|
||||
from mininet.topo import ( SingleSwitchTopo, LinearTopo,
|
||||
SingleSwitchReversedTopo, MinimalTopo )
|
||||
from mininet.topolib import TreeTopo, TorusTopo
|
||||
from mininet.util import customClass, specialClass, splitArgs
|
||||
from mininet.util import buildTopo
|
||||
|
||||
from functools import partial
|
||||
from mininet.util import customClass, specialClass, splitArgs, buildTopo
|
||||
|
||||
# Experimental! cluster edition prototype
|
||||
from mininet.examples.cluster import ( MininetCluster, RemoteHost,
|
||||
@@ -46,6 +48,7 @@ from mininet.examples.cluster import ( MininetCluster, RemoteHost,
|
||||
ClusterCleanup )
|
||||
from mininet.examples.clustercli import ClusterCLI
|
||||
|
||||
|
||||
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
|
||||
|
||||
# built in topologies, created only when run
|
||||
@@ -98,7 +101,6 @@ CLI = None # Set below if needed
|
||||
# Locally defined tests
|
||||
def allTest( net ):
|
||||
"Run ping and iperf tests"
|
||||
net.waitConnected()
|
||||
net.start()
|
||||
net.ping()
|
||||
net.iperf()
|
||||
@@ -107,6 +109,7 @@ def nullTest( _net ):
|
||||
"Null 'test' (does nothing)"
|
||||
pass
|
||||
|
||||
|
||||
TESTS.update( all=allTest, none=nullTest, build=nullTest )
|
||||
|
||||
# Map to alternate spellings of Mininet() methods
|
||||
@@ -127,7 +130,6 @@ def runTests( mn, options ):
|
||||
if callable( testfn ):
|
||||
testfn( mn, *args, **kwargs )
|
||||
elif hasattr( mn, test ):
|
||||
mn.waitConnected()
|
||||
getattr( mn, test )( *args, **kwargs )
|
||||
else:
|
||||
raise Exception( 'Test %s is unknown - please specify one of '
|
||||
@@ -186,8 +188,11 @@ class MininetRunner( object ):
|
||||
for fileName in files:
|
||||
customs = {}
|
||||
if os.path.isfile( fileName ):
|
||||
execfile( fileName, customs, customs )
|
||||
for name, val in customs.iteritems():
|
||||
# pylint: disable=exec-used
|
||||
with open( fileName ) as f:
|
||||
exec( compile( f.read(), fileName, 'exec' ),
|
||||
customs, customs )
|
||||
for name, val in customs.items():
|
||||
self.setCustom( name, val )
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
@@ -256,7 +261,7 @@ class MininetRunner( object ):
|
||||
opts.add_option( '--arp', action='store_true',
|
||||
default=False, help='set all-pairs ARP entries' )
|
||||
opts.add_option( '--verbosity', '-v', type='choice',
|
||||
choices=LEVELS.keys(), default = 'info',
|
||||
choices=list( LEVELS.keys() ), default = 'info',
|
||||
help = '|'.join( LEVELS.keys() ) )
|
||||
opts.add_option( '--innamespace', action='store_true',
|
||||
default=False, help='sw and ctrl in namespace?' )
|
||||
@@ -282,11 +287,16 @@ class MininetRunner( object ):
|
||||
" 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( '--wait', '-w', action='store_true',
|
||||
default=False, help='wait for switches to connect' )
|
||||
opts.add_option( '--twait', '-t', action='store', type='int',
|
||||
dest='wait',
|
||||
help='timed wait (s) for switches to connect' )
|
||||
opts.add_option( '--cluster', type='string', default=None,
|
||||
metavar='server1,server2...',
|
||||
help=( 'run on multiple servers (experimental!)' ) )
|
||||
opts.add_option( '--placement', type='choice',
|
||||
choices=PLACEMENT.keys(), default='block',
|
||||
choices=list( PLACEMENT.keys() ), default='block',
|
||||
metavar='block|random',
|
||||
help=( 'node placement for --cluster '
|
||||
'(experimental!) ' ) )
|
||||
@@ -378,14 +388,24 @@ class MininetRunner( object ):
|
||||
placement=PLACEMENT[ opts.placement ] )
|
||||
mininet.cli.CLI = ClusterCLI
|
||||
|
||||
# Wait for controllers to connect unless we're running null test
|
||||
if ( opts.test and opts.test != [ 'none' ] and
|
||||
isinstance( opts.wait, bool ) ):
|
||||
opts.wait = True
|
||||
|
||||
mn = Net( topo=topo,
|
||||
switch=switch, host=host, controller=controller, link=link,
|
||||
ipBase=opts.ipbase, inNamespace=opts.innamespace,
|
||||
xterms=opts.xterms, autoSetMacs=opts.mac,
|
||||
autoStaticArp=opts.arp, autoPinCpus=opts.pin,
|
||||
waitConnected=opts.wait,
|
||||
listenPort=opts.listenport )
|
||||
|
||||
if opts.ensure_value( 'nat', False ):
|
||||
with open( '/etc/resolv.conf' ) as f:
|
||||
if 'nameserver 127.' in f.read():
|
||||
warn( '*** Warning: loopback address in /etc/resolv.conf '
|
||||
'may break host DNS over NAT\n')
|
||||
mn.addNAT( *opts.nat_args, **opts.nat_kwargs ).configDefault()
|
||||
|
||||
# --custom files can set CLI or change mininet.cli.CLI
|
||||
@@ -416,7 +436,7 @@ if __name__ == "__main__":
|
||||
except KeyboardInterrupt:
|
||||
info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
|
||||
cleanup()
|
||||
except Exception:
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Print exception
|
||||
type_, val_, trace_ = sys.exc_info()
|
||||
errorMsg = ( "-"*80 + "\n" +
|
||||
|
||||
@@ -13,12 +13,9 @@ from mininet.topo import Topo
|
||||
class MyTopo( Topo ):
|
||||
"Simple topology example."
|
||||
|
||||
def __init__( self ):
|
||||
def build( self ):
|
||||
"Create custom topo."
|
||||
|
||||
# Initialize topology
|
||||
Topo.__init__( self )
|
||||
|
||||
# Add hosts and switches
|
||||
leftHost = self.addHost( 'h1' )
|
||||
rightHost = self.addHost( 'h2' )
|
||||
|
||||
+1927
-821
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -158,7 +158,7 @@ A simple example of configuring network and CPU bandwidth limits.
|
||||
|
||||
This example shows how to run an `sshd` process in each host, allowing
|
||||
you to log in via `ssh`. This requires connecting the Mininet data network
|
||||
to an interface in the root namespace (generaly the control network
|
||||
to an interface in the root namespace (generally the control network
|
||||
already lives in the root namespace, so it does not need to be explicitly
|
||||
connected.)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"This example doesn't use OpenFlow, but attempts to run sshd in a namespace."
|
||||
|
||||
@@ -27,9 +27,8 @@ h1.setIP( '10.0.0.1', 8 )
|
||||
root.setIP( '10.0.0.2', 8 )
|
||||
|
||||
info( "*** Creating banner file\n" )
|
||||
f = open( '/tmp/%s.banner' % h1.name, 'w' )
|
||||
f.write( 'Welcome to %s at %s\n' % ( h1.name, h1.IP() ) )
|
||||
f.close()
|
||||
with open( '/tmp/%s.banner' % h1.name, 'w' ) as f:
|
||||
f.write( 'Welcome to %s at %s\n' % ( h1.name, h1.IP() ) )
|
||||
|
||||
info( "*** Running sshd\n" )
|
||||
cmd = '/usr/sbin/sshd -o UseDNS=no -u0 -o "Banner /tmp/%s.banner"' % h1.name
|
||||
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
bind.py: Bind mount example
|
||||
@@ -34,14 +34,14 @@ and '/var/run'. It also has a temporary private directory mounted
|
||||
on '/var/mn'
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host
|
||||
from mininet.cli import CLI
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from functools import partial
|
||||
|
||||
|
||||
# Sample usage
|
||||
|
||||
@@ -53,7 +53,7 @@ def testHostWithPrivateDirs():
|
||||
'/var/mn' ]
|
||||
host = partial( Host,
|
||||
privateDirs=privateDirs )
|
||||
net = Mininet( topo=topo, host=host )
|
||||
net = Mininet( topo=topo, host=host, waitConnected=True )
|
||||
net.start()
|
||||
directories = [ directory[ 0 ] if isinstance( directory, tuple )
|
||||
else directory for directory in privateDirs ]
|
||||
@@ -61,6 +61,7 @@ def testHostWithPrivateDirs():
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
testHostWithPrivateDirs()
|
||||
|
||||
+112
-58
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
cluster.py: prototyping/experimentation for distributed Mininet,
|
||||
@@ -74,17 +74,6 @@ Things to do:
|
||||
- hifi support (e.g. delay compensation)
|
||||
"""
|
||||
|
||||
|
||||
from mininet.node import Node, Host, OVSSwitch, Controller
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import LinearTopo
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import quietRun, errRun
|
||||
from mininet.examples.clustercli import CLI
|
||||
from mininet.log import setLogLevel, debug, info, error
|
||||
from mininet.clean import addCleanupCallback
|
||||
|
||||
from signal import signal, SIGINT, SIG_IGN
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
import os
|
||||
@@ -93,7 +82,18 @@ import sys
|
||||
import re
|
||||
from itertools import groupby
|
||||
from operator import attrgetter
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
from mininet.node import Node, Host, OVSSwitch, Controller
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import LinearTopo
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import quietRun, errRun, decode, StrictVersion
|
||||
from mininet.examples.clustercli import CLI
|
||||
from mininet.log import setLogLevel, debug, info, error
|
||||
from mininet.clean import addCleanupCallback
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
def findUser():
|
||||
@@ -126,7 +126,7 @@ class ClusterCleanup( object ):
|
||||
def cleanup( cls ):
|
||||
"Clean up"
|
||||
info( '*** Cleaning up cluster\n' )
|
||||
for server, user in cls.serveruser.iteritems():
|
||||
for server, user in cls.serveruser.items():
|
||||
if server == 'localhost':
|
||||
# Handled by mininet.clean.cleanup()
|
||||
continue
|
||||
@@ -246,7 +246,7 @@ class RemoteMixin( object ):
|
||||
result = ''
|
||||
while True:
|
||||
poll = popen.poll()
|
||||
result += popen.stdout.read()
|
||||
result += decode( popen.stdout.read() )
|
||||
if poll is not None:
|
||||
break
|
||||
return result
|
||||
@@ -261,7 +261,7 @@ class RemoteMixin( object ):
|
||||
cmd: remote command to run (list)
|
||||
**params: parameters to Popen()
|
||||
returns: Popen() object"""
|
||||
if type( cmd ) is str:
|
||||
if isinstance( cmd, str):
|
||||
cmd = cmd.split()
|
||||
if self.isRemote:
|
||||
if sudo:
|
||||
@@ -289,6 +289,7 @@ class RemoteMixin( object ):
|
||||
def addIntf( self, *args, **kwargs ):
|
||||
"Override: use RemoteLink.moveIntf"
|
||||
# kwargs.update( moveIntfFn=RemoteLink.moveIntf )
|
||||
# pylint: disable=useless-super-delegation
|
||||
return super( RemoteMixin, self).addIntf( *args, **kwargs )
|
||||
|
||||
|
||||
@@ -312,7 +313,7 @@ class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
|
||||
kwargs.update( batch=True )
|
||||
super( RemoteOVSSwitch, self ).__init__( *args, **kwargs )
|
||||
|
||||
def isOldOVS( self ):
|
||||
def isOldOVS( self ): # pylint: disable=arguments-differ
|
||||
"Is remote switch using an old OVS version?"
|
||||
cls = type( self )
|
||||
if self.server not in cls.OVSVersions:
|
||||
@@ -325,6 +326,7 @@ class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
|
||||
StrictVersion( '1.10' ) )
|
||||
|
||||
@classmethod
|
||||
# pylint: disable=arguments-differ
|
||||
def batchStartup( cls, switches, **_kwargs ):
|
||||
"Start up switches in per-server batches"
|
||||
key = attrgetter( 'server' )
|
||||
@@ -336,6 +338,7 @@ class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
|
||||
return switches
|
||||
|
||||
@classmethod
|
||||
# pylint: disable=arguments-differ
|
||||
def batchShutdown( cls, switches, **_kwargs ):
|
||||
"Stop switches in per-server batches"
|
||||
key = attrgetter( 'server' )
|
||||
@@ -372,8 +375,9 @@ class RemoteLink( Link ):
|
||||
Link.stop( self )
|
||||
self.tunnel = None
|
||||
|
||||
def makeIntfPair( self, intfname1, intfname2, addr1=None, addr2=None,
|
||||
node1=None, node2=None, deleteIntfs=True ):
|
||||
def makeIntfPair( self, # pylint: disable=arguments-renamed
|
||||
intfname1, intfname2, addr1=None, addr2=None,
|
||||
node1=None, node2=None, deleteIntfs=True ):
|
||||
"""Create pair of interfaces
|
||||
intfname1: name of interface 1
|
||||
intfname2: name of interface 2
|
||||
@@ -413,8 +417,9 @@ class RemoteLink( Link ):
|
||||
# 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 )
|
||||
return self.makeTunnel( node1=node2, node2=node1,
|
||||
intfname1=intfname2, intfname2=intfname1,
|
||||
addr1=addr2, addr2=addr1 )
|
||||
debug( '\n*** Make SSH tunnel ' + node1.server + ':' + intfname1 +
|
||||
' == ' + node2.server + ':' + intfname2 )
|
||||
# 1. Create tap interfaces
|
||||
@@ -435,13 +440,16 @@ class RemoteLink( Link ):
|
||||
# When we receive the character '@', it means that our
|
||||
# tunnel should be set up
|
||||
debug( 'Waiting for tunnel to come up...\n' )
|
||||
ch = tunnel.stdout.read( 1 )
|
||||
ch = decode( tunnel.stdout.read( 1 ) )
|
||||
if ch != '@':
|
||||
raise Exception( 'makeTunnel:\n',
|
||||
'Tunnel setup failed for',
|
||||
'%s:%s' % ( node1, node1.dest ), 'to',
|
||||
'%s:%s\n' % ( node2, node2.dest ),
|
||||
'command was:', cmd, '\n' )
|
||||
ch += decode( tunnel.stdout.read() )
|
||||
cmd = ' '.join( cmd )
|
||||
raise Exception( 'makeTunnel:\n'
|
||||
'Tunnel setup failed for '
|
||||
'%s:%s' % ( node1, node1.dest ) + ' to '
|
||||
'%s:%s\n' % ( node2, node2.dest ) +
|
||||
'command was: %s' % cmd + '\n' +
|
||||
'result was: ' + ch )
|
||||
# 3. Move interfaces if necessary
|
||||
for node in node1, node2:
|
||||
if not self.moveIntf( 'tap9', node ):
|
||||
@@ -526,8 +534,9 @@ class RemoteGRELink( RemoteLink ):
|
||||
# We should never try to create a tunnel to ourselves!
|
||||
assert node1.server != node2.server
|
||||
if node2.server == 'localhost':
|
||||
return self.makeTunnel( node2, node1, intfname2, intfname1,
|
||||
addr2, addr1 )
|
||||
return self.makeTunnel( node1=node2, node2=node1,
|
||||
intfname1=intfname2, intfname2=intfname1,
|
||||
addr1=addr2, addr2=addr1 )
|
||||
IP1, IP2 = node1.serverIP, node2.serverIP
|
||||
# GRE tunnel needs to be set up with the IP of the local interface
|
||||
# that connects the remote node, NOT '127.0.0.1' of localhost
|
||||
@@ -555,6 +564,7 @@ class RemoteGRELink( RemoteLink ):
|
||||
node.rcmd('ip link set dev %s mtu 1450' % intfname)
|
||||
if not self.moveIntf(intfname, node):
|
||||
raise Exception('interface move failed on node %s' % node)
|
||||
return None # May want to return something useful here
|
||||
|
||||
|
||||
# Some simple placement algorithms for MininetCluster
|
||||
@@ -589,10 +599,10 @@ class Placer( object ):
|
||||
|
||||
class RandomPlacer( Placer ):
|
||||
"Random placement"
|
||||
def place( self, nodename ):
|
||||
def place( self, node ):
|
||||
"""Random placement function
|
||||
nodename: node name"""
|
||||
assert nodename # please pylint
|
||||
node: node"""
|
||||
assert node # please pylint
|
||||
# This may be slow with lots of servers
|
||||
return self.servers[ randrange( 0, len( self.servers ) ) ]
|
||||
|
||||
@@ -606,10 +616,10 @@ class RoundRobinPlacer( Placer ):
|
||||
Placer.__init__( self, *args, **kwargs )
|
||||
self.next = 0
|
||||
|
||||
def place( self, nodename ):
|
||||
def place( self, node ):
|
||||
"""Round-robin placement function
|
||||
nodename: node name"""
|
||||
assert nodename # please pylint
|
||||
node: node"""
|
||||
assert node # please pylint
|
||||
# This may be slow with lots of servers
|
||||
server = self.servers[ self.next ]
|
||||
self.next = ( self.next + 1 ) % len( self.servers )
|
||||
@@ -647,7 +657,7 @@ class SwitchBinPlacer( Placer ):
|
||||
tickets = sum( [ binsizes[ server ] * [ server ]
|
||||
for server in servers ], [] )
|
||||
# And assign one ticket to each node
|
||||
return { node: ticket for node, ticket in zip( nodes, tickets ) }
|
||||
return dict( zip( nodes, tickets ) )
|
||||
|
||||
def calculatePlacement( self ):
|
||||
"Pre-calculate node placement"
|
||||
@@ -704,21 +714,21 @@ class HostSwitchBinPlacer( Placer ):
|
||||
self.cset = frozenset( self.controllers )
|
||||
self.hind, self.sind, self.cind = 0, 0, 0
|
||||
|
||||
def place( self, nodename ):
|
||||
def place( self, node ):
|
||||
"""Simple placement algorithm:
|
||||
place nodes into evenly sized bins"""
|
||||
# Place nodes into bins
|
||||
if nodename in self.hset:
|
||||
if node in self.hset:
|
||||
server = self.servdict[ self.hind / self.hbin ]
|
||||
self.hind += 1
|
||||
elif nodename in self.sset:
|
||||
elif node in self.sset:
|
||||
server = self.servdict[ self.sind / self.sbin ]
|
||||
self.sind += 1
|
||||
elif nodename in self.cset:
|
||||
elif node in self.cset:
|
||||
server = self.servdict[ self.cind / self.cbin ]
|
||||
self.cind += 1
|
||||
else:
|
||||
info( 'warning: unknown node', nodename )
|
||||
info( 'warning: unknown node', node )
|
||||
server = self.servdict[ 0 ]
|
||||
return server
|
||||
|
||||
@@ -764,13 +774,16 @@ class MininetCluster( Mininet ):
|
||||
# Make sure control directory exists
|
||||
self.cdir = os.environ[ 'HOME' ] + '/.ssh/mn'
|
||||
errRun( [ 'mkdir', '-p', self.cdir ] )
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
Mininet.__init__( self, *args, **params )
|
||||
|
||||
def popen( self, cmd ):
|
||||
"Popen() for server connections"
|
||||
assert self # please pylint
|
||||
old = signal( SIGINT, SIG_IGN )
|
||||
# pylint: disable=consider-using-with
|
||||
conn = Popen( cmd, stdin=PIPE, stdout=PIPE, close_fds=True )
|
||||
# pylint: enable=consider-using-with
|
||||
signal( SIGINT, old )
|
||||
return conn
|
||||
|
||||
@@ -840,16 +853,43 @@ class MininetCluster( Mininet ):
|
||||
if cfile:
|
||||
config.setdefault( 'controlPath', cfile )
|
||||
|
||||
@staticmethod
|
||||
def isLoopback( ipaddr ):
|
||||
"Is ipaddr an IPv4 loopback address?"
|
||||
return ipaddr.startswith( '127.' )
|
||||
|
||||
# pylint: disable=arguments-differ,signature-differs
|
||||
def addController( self, *args, **kwargs ):
|
||||
"Patch to update IP address to global IP address"
|
||||
controller = Mininet.addController( self, *args, **kwargs )
|
||||
# Update IP address for controller that may not be local
|
||||
if ( isinstance( controller, Controller)
|
||||
and controller.IP() == '127.0.0.1'
|
||||
and ' eth0:' in controller.cmd( 'ip link show' ) ):
|
||||
Intf( 'eth0', node=controller ).updateIP()
|
||||
controllerIP = controller.IP()
|
||||
if ( not isinstance( controller, Controller ) or
|
||||
not self.isLoopback( controller.IP() ) ):
|
||||
return controller
|
||||
# Find route to a different server IP address
|
||||
serverIPs = [ ip for ip in self.serverIP.values()
|
||||
if ip != controllerIP ]
|
||||
if not serverIPs:
|
||||
return None # no remote servers - loopback is fine
|
||||
for remoteIP in serverIPs:
|
||||
# Route should contain 'dev <intfname>'
|
||||
route = controller.cmd( 'ip route get', remoteIP,
|
||||
r'| egrep -o "dev\s[^[:space:]]+"' )
|
||||
if not route:
|
||||
raise Exception('addController: no route from', controller,
|
||||
'to', remoteIP )
|
||||
intf = route.split()[ 1 ].strip()
|
||||
if intf != 'lo':
|
||||
break
|
||||
if intf == 'lo':
|
||||
raise Exception( 'addController: could not find external '
|
||||
'interface/IP for %s' % controller )
|
||||
debug( 'adding', intf, 'to', controller )
|
||||
Intf( intf, node=controller ).updateIP()
|
||||
debug( controller, 'IP address updated to', controller.IP() )
|
||||
return controller
|
||||
|
||||
# pylint: disable=arguments-differ,signature-differs
|
||||
def buildFromTopo( self, *args, **kwargs ):
|
||||
"Start network"
|
||||
info( '*** Placing nodes\n' )
|
||||
@@ -858,9 +898,12 @@ class MininetCluster( Mininet ):
|
||||
Mininet.buildFromTopo( self, *args, **kwargs )
|
||||
|
||||
|
||||
def testNsTunnels( remote='ubuntu2', link=RemoteGRELink ):
|
||||
# Default remote server for tests
|
||||
remoteServer = 'ubuntu2'
|
||||
|
||||
def testNsTunnels( remote=remoteServer, link=RemoteGRELink ):
|
||||
"Test tunnels between nodes in namespaces"
|
||||
net = Mininet( host=RemoteHost, link=link )
|
||||
net = Mininet( host=RemoteHost, link=link, waitConnected=True )
|
||||
h1 = net.addHost( 'h1')
|
||||
h2 = net.addHost( 'h2', server=remote )
|
||||
net.addLink( h1, h2 )
|
||||
@@ -873,13 +916,14 @@ def testNsTunnels( remote='ubuntu2', link=RemoteGRELink ):
|
||||
# This shows how node options may be used to manage
|
||||
# cluster placement using the net.add*() API
|
||||
|
||||
def testRemoteNet( remote='ubuntu2', link=RemoteGRELink ):
|
||||
def testRemoteNet( remote=remoteServer, link=RemoteGRELink ):
|
||||
"Test remote Node classes"
|
||||
info( '*** Remote Node Test\n' )
|
||||
net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch, link=link )
|
||||
net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch,
|
||||
link=link, controller=ClusterController,
|
||||
waitConnected=True )
|
||||
c0 = net.addController( 'c0' )
|
||||
# Make sure controller knows its non-loopback address
|
||||
Intf( 'eth0', node=c0 ).updateIP()
|
||||
info( "*** Creating local h1\n" )
|
||||
h1 = net.addHost( 'h1' )
|
||||
info( "*** Creating remote h2\n" )
|
||||
@@ -911,7 +955,7 @@ def testRemoteNet( remote='ubuntu2', link=RemoteGRELink ):
|
||||
|
||||
remoteHosts = [ 'h2' ]
|
||||
remoteSwitches = [ 's2' ]
|
||||
remoteServer = 'ubuntu2'
|
||||
|
||||
|
||||
def HostPlacer( name, *args, **params ):
|
||||
"Custom Host() constructor which places hosts on servers"
|
||||
@@ -928,10 +972,21 @@ def SwitchPlacer( name, *args, **params ):
|
||||
return RemoteOVSSwitch( name, *args, **params )
|
||||
|
||||
def ClusterController( *args, **kwargs):
|
||||
"Custom Controller() constructor which updates its eth0 IP address"
|
||||
"Custom Controller() constructor which updates its intf IP address"
|
||||
intf = kwargs.pop( 'intf', '' )
|
||||
controller = Controller( *args, **kwargs )
|
||||
# Find out its IP address so that cluster switches can connect
|
||||
Intf( 'eth0', node=controller ).updateIP()
|
||||
if not intf:
|
||||
output = controller.cmd(
|
||||
r"ip a | egrep -o '\w+:\s\w+'" ).split( '\n' )
|
||||
for line in output:
|
||||
intf = line.split()[ -1 ]
|
||||
if intf != 'lo':
|
||||
break
|
||||
if intf == 'lo':
|
||||
raise Exception( 'Could not find non-loopback interface'
|
||||
'for %s' % controller )
|
||||
Intf( intf, node=controller ).updateIP()
|
||||
return controller
|
||||
|
||||
def testRemoteTopo( link=RemoteGRELink ):
|
||||
@@ -948,7 +1003,7 @@ def testRemoteTopo( link=RemoteGRELink ):
|
||||
# do random switch placement rather than completely random
|
||||
# host placement.
|
||||
|
||||
def testRemoteSwitches( remote='ubuntu2', link=RemoteGRELink ):
|
||||
def testRemoteSwitches( remote=remoteServer, link=RemoteGRELink ):
|
||||
"Test with local hosts and remote switches"
|
||||
servers = [ 'localhost', remote]
|
||||
topo = TreeTopo( depth=4, fanout=2 )
|
||||
@@ -966,7 +1021,7 @@ def testRemoteSwitches( remote='ubuntu2', link=RemoteGRELink ):
|
||||
# functions, for maximum ease of use. MininetCluster() also
|
||||
# pre-flights and multiplexes server connections.
|
||||
|
||||
def testMininetCluster( remote='ubuntu2', link=RemoteGRELink ):
|
||||
def testMininetCluster( remote=remoteServer, link=RemoteGRELink ):
|
||||
"Test MininetCluster()"
|
||||
servers = [ 'localhost', remote ]
|
||||
topo = TreeTopo( depth=3, fanout=3 )
|
||||
@@ -976,7 +1031,7 @@ def testMininetCluster( remote='ubuntu2', link=RemoteGRELink ):
|
||||
net.pingAll()
|
||||
net.stop()
|
||||
|
||||
def signalTest( remote='ubuntu2'):
|
||||
def signalTest( remote=remoteServer):
|
||||
"Make sure hosts are robust to signals"
|
||||
h = RemoteHost( 'h0', server=remote )
|
||||
h.shell.send_signal( SIGINT )
|
||||
@@ -991,7 +1046,6 @@ def signalTest( remote='ubuntu2'):
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
remoteServer = 'ubuntu2'
|
||||
remoteLink = RemoteSSHLink
|
||||
testRemoteTopo(link=remoteLink)
|
||||
testNsTunnels( remote=remoteServer, link=remoteLink )
|
||||
|
||||
@@ -17,6 +17,7 @@ def clusterSanity():
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
clusterSanity()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"CLI for Mininet Cluster Edition prototype demo"
|
||||
|
||||
@@ -30,7 +30,8 @@ class ClusterCLI( CLI ):
|
||||
global nx, plt, graphviz_layout
|
||||
if not nx:
|
||||
try:
|
||||
# pylint: disable=import-error
|
||||
# pylint: disable=import-error,no-member
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import networkx
|
||||
nx = networkx # satisfy pylint
|
||||
from matplotlib import pyplot
|
||||
@@ -42,7 +43,7 @@ class ClusterCLI( CLI ):
|
||||
graphviz_layout = nx.graphviz_layout
|
||||
else:
|
||||
graphviz_layout = nx.drawing.nx_agraph.graphviz_layout
|
||||
# pylint: enable=import-error
|
||||
# pylint: enable=import-error,no-member
|
||||
except ImportError:
|
||||
error( 'plot requires networkx, matplotlib and pygraphviz - '
|
||||
'please install them and try again\n' )
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"clusterdemo.py: demo of Mininet Cluster Edition prototype"
|
||||
|
||||
@@ -13,12 +13,13 @@ def demo():
|
||||
"Simple Demo of Cluster Mode"
|
||||
servers = [ 'localhost', 'ubuntu2', 'ubuntu3' ]
|
||||
topo = TreeTopo( depth=3, fanout=3 )
|
||||
net = MininetCluster( topo=topo, servers=servers, Link=RemoteLink,
|
||||
net = MininetCluster( topo=topo, servers=servers, link=RemoteLink,
|
||||
placement=SwitchBinPlacer )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
demo()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"clusterperf.py compare the maximum throughput between SSH and GRE tunnels"
|
||||
|
||||
@@ -8,7 +8,7 @@ from mininet.log import setLogLevel
|
||||
|
||||
def perf(Link):
|
||||
"Test connectivity nand performance over Link"
|
||||
net = Mininet( host=RemoteHost, link=Link )
|
||||
net = Mininet( host=RemoteHost, link=Link, waitConnected=True )
|
||||
h1 = net.addHost( 'h1')
|
||||
h2 = net.addHost( 'h2', server='ubuntu2' )
|
||||
net.addLink( h1, h2 )
|
||||
@@ -17,6 +17,7 @@ def perf(Link):
|
||||
net.iperf()
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
perf( RemoteSSHLink )
|
||||
|
||||
+14
-9
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
consoles.py: bring up a bunch of miniature consoles on a virtual network
|
||||
@@ -27,6 +27,7 @@ Bob Lantz, April 2010
|
||||
|
||||
import re
|
||||
|
||||
# pylint: disable=import-error
|
||||
from Tkinter import Frame, Button, Label, Text, Scrollbar, Canvas, Wm, READABLE
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
@@ -34,6 +35,9 @@ from mininet.topolib import TreeNet
|
||||
from mininet.term import makeTerms, cleanUpScreens
|
||||
from mininet.util import quietRun
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
class Console( Frame ):
|
||||
"A simple console on a host."
|
||||
|
||||
@@ -65,7 +69,10 @@ class Console( Frame ):
|
||||
self.bindEvents()
|
||||
self.sendCmd( 'export TERM=dumb' )
|
||||
|
||||
self.outputHook = None
|
||||
def outputHook( _obj, _text):
|
||||
return True
|
||||
|
||||
self.outputHook = outputHook
|
||||
|
||||
def makeWidgets( self ):
|
||||
"Make a label, a text area, and a scroll bar."
|
||||
@@ -107,10 +114,8 @@ class Console( Frame ):
|
||||
self.text.insert( 'end', text )
|
||||
self.text.mark_set( 'insert', 'end' )
|
||||
self.text.see( 'insert' )
|
||||
outputHook = lambda x, y: True # make pylint happier
|
||||
if self.outputHook:
|
||||
outputHook = self.outputHook
|
||||
outputHook( self, text )
|
||||
if callable( self.outputHook ):
|
||||
self.outputHook( self, text )
|
||||
|
||||
def handleKey( self, event ):
|
||||
"If it's an interactive command, send it to the node."
|
||||
@@ -290,10 +295,10 @@ class ConsoleApp( Frame ):
|
||||
'switches': 'Switch',
|
||||
'controllers': 'Controller'
|
||||
}
|
||||
for name in titles:
|
||||
for name, title in titles.items():
|
||||
nodes = getattr( net, name )
|
||||
frame, consoles = self.createConsoles(
|
||||
cframe, nodes, width, titles[ name ] )
|
||||
cframe, nodes, width, title )
|
||||
self.consoles[ name ] = Object( frame=frame, consoles=consoles )
|
||||
self.selected = None
|
||||
self.select( 'hosts' )
|
||||
@@ -319,7 +324,7 @@ class ConsoleApp( Frame ):
|
||||
if not m:
|
||||
return
|
||||
val, units = float( m.group( 1 ) ), m.group( 2 )
|
||||
#convert to Gbps
|
||||
# convert to Gbps
|
||||
if units[0] == 'M':
|
||||
val *= 10 ** -3
|
||||
elif units[0] == 'K':
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Create a network where different switches are connected to
|
||||
@@ -26,8 +26,9 @@ class MultiSwitch( OVSSwitch ):
|
||||
def start( self, controllers ):
|
||||
return OVSSwitch.start( self, [ cmap[ self.name ] ] )
|
||||
|
||||
|
||||
topo = TreeTopo( depth=2, fanout=2 )
|
||||
net = Mininet( topo=topo, switch=MultiSwitch, build=False )
|
||||
net = Mininet( topo=topo, switch=MultiSwitch, build=False, waitConnected=True )
|
||||
for c in [ c0, c1 ]:
|
||||
net.addController(c)
|
||||
net.build()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This example creates a multi-controller network from semi-scratch by
|
||||
@@ -20,7 +20,8 @@ from mininet.log import setLogLevel, info
|
||||
def multiControllerNet():
|
||||
"Create a network from semi-scratch with multiple controllers."
|
||||
|
||||
net = Mininet( controller=Controller, switch=OVSSwitch )
|
||||
net = Mininet( controller=Controller, switch=OVSSwitch,
|
||||
waitConnected=True )
|
||||
|
||||
info( "*** Creating (reference) controllers\n" )
|
||||
c1 = net.addController( 'c1', port=6633 )
|
||||
@@ -57,6 +58,7 @@ def multiControllerNet():
|
||||
info( "*** Stopping network\n" )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' ) # for CLI output
|
||||
multiControllerNet()
|
||||
|
||||
+11
-8
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
controlnet.py: Mininet with a custom control network
|
||||
@@ -49,7 +49,7 @@ class MininetFacade( object ):
|
||||
args: unnamed networks passed as arguments
|
||||
kwargs: named networks passed as arguments"""
|
||||
self.net = net
|
||||
self.nets = [ net ] + list( args ) + kwargs.values()
|
||||
self.nets = [ net ] + list( args ) + list( kwargs.values() )
|
||||
self.nameToNet = kwargs
|
||||
self.nameToNet['net'] = net
|
||||
|
||||
@@ -59,13 +59,14 @@ class MininetFacade( object ):
|
||||
|
||||
def __getitem__( self, key ):
|
||||
"returns primary/named networks or node from any net"
|
||||
#search kwargs for net named key
|
||||
# search kwargs for net named key
|
||||
if key in self.nameToNet:
|
||||
return self.nameToNet[ key ]
|
||||
#search each net for node named key
|
||||
# search each net for node named key
|
||||
for net in self.nets:
|
||||
if key in net:
|
||||
return net[ key ]
|
||||
return None
|
||||
|
||||
def __iter__( self ):
|
||||
"Iterate through all nodes in all Mininet objects"
|
||||
@@ -100,10 +101,10 @@ class MininetFacade( object ):
|
||||
|
||||
class ControlNetwork( Topo ):
|
||||
"Control Network Topology"
|
||||
def __init__( self, n, dataController=DataController, **kwargs ):
|
||||
# pylint: disable=arguments-differ
|
||||
def build( self, n, dataController=DataController, **_kwargs ):
|
||||
"""n: number of data network controller nodes
|
||||
dataController: class for data network controllers"""
|
||||
Topo.__init__( self, **kwargs )
|
||||
# Connect everything to a single switch
|
||||
cs0 = self.addSwitch( 'cs0' )
|
||||
# Add hosts which will serve as data network controllers
|
||||
@@ -124,7 +125,8 @@ def run():
|
||||
|
||||
info( '* Creating Control Network\n' )
|
||||
ctopo = ControlNetwork( n=4, dataController=DataController )
|
||||
cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24', controller=None )
|
||||
cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24',
|
||||
controller=None, waitConnected=True )
|
||||
info( '* Adding Control Network Controller\n')
|
||||
cnet.addController( 'cc0', controller=Controller )
|
||||
info( '* Starting Control Network\n')
|
||||
@@ -134,7 +136,8 @@ def run():
|
||||
topo = TreeTopo( depth=2, fanout=2 )
|
||||
# UserSwitch so we can easily test failover
|
||||
sw = partial( UserSwitch, opts='--inactivity-probe=1 --max-backoff=1' )
|
||||
net = Mininet( topo=topo, switch=sw, controller=None )
|
||||
net = Mininet( topo=topo, switch=sw, controller=None,
|
||||
waitConnected=True )
|
||||
info( '* Adding Controllers to Data Network\n' )
|
||||
for host in cnet.hosts:
|
||||
if isinstance(host, Controller):
|
||||
|
||||
+16
-10
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
cpu.py: test iperf bandwidth for varying cpu limits
|
||||
@@ -31,9 +31,9 @@ rate includes buffering.
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom, waitListening
|
||||
from mininet.util import custom, waitListening, decode
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from mininet.clean import cleanup
|
||||
|
||||
def bwtest( cpuLimits, period_us=100000, seconds=10 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
@@ -52,10 +52,11 @@ def bwtest( cpuLimits, period_us=100000, seconds=10 ):
|
||||
period_us=period_us,
|
||||
cpu=.5*cpu )
|
||||
try:
|
||||
net = Mininet( topo=topo, host=host )
|
||||
net = Mininet( topo=topo, host=host, waitConnected=True )
|
||||
# pylint: disable=bare-except
|
||||
except:
|
||||
info( '*** Skipping scheduler %s\n' % sched )
|
||||
except: # noqa
|
||||
info( '*** Skipping scheduler %s and cleaning up\n' % sched )
|
||||
cleanup()
|
||||
break
|
||||
net.start()
|
||||
net.pingAll()
|
||||
@@ -67,11 +68,16 @@ def bwtest( cpuLimits, period_us=100000, seconds=10 ):
|
||||
# the client's buffer fill rate
|
||||
popen = server.popen( 'iperf -yc -s -p 5001' )
|
||||
waitListening( client, server, 5001 )
|
||||
# ignore empty result from waitListening/telnet
|
||||
popen.stdout.readline()
|
||||
client.cmd( 'iperf -yc -t %s -c %s' % ( seconds, server.IP() ) )
|
||||
result = popen.stdout.readline().split( ',' )
|
||||
bps = float( result[ -1 ] )
|
||||
# ignore empty result from waitListening/telnet for old iperf
|
||||
svals = {}
|
||||
while not svals or int( svals[ 'rate' ] ) == 0:
|
||||
line = decode( popen.stdout.readline() )
|
||||
# Probably shouldn't depend on an internal method, but
|
||||
# this is the easiest way
|
||||
svals = Mininet._iperfVals( # pylint: disable=protected-access
|
||||
line, server.IP() )
|
||||
bps = float( svals[ 'rate' ] )
|
||||
popen.terminate()
|
||||
net.stop()
|
||||
updated = results.get( sched, [] )
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This example shows how to create an empty Mininet object
|
||||
@@ -14,7 +14,7 @@ def emptyNet():
|
||||
|
||||
"Create an empty network and add nodes to it."
|
||||
|
||||
net = Mininet( controller=Controller )
|
||||
net = Mininet( controller=Controller, waitConnected=True )
|
||||
|
||||
info( '*** Adding controller\n' )
|
||||
net.addController( 'c0' )
|
||||
@@ -39,6 +39,7 @@ def emptyNet():
|
||||
info( '*** Stopping network' )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
emptyNet()
|
||||
|
||||
+6
-2
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This example shows how to add an interface (for example a real
|
||||
@@ -8,6 +8,8 @@ hardware interface) to a network after the network is created.
|
||||
import re
|
||||
import sys
|
||||
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel, info, error
|
||||
from mininet.net import Mininet
|
||||
@@ -15,6 +17,7 @@ from mininet.link import Intf
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import quietRun
|
||||
|
||||
|
||||
def checkIntf( intf ):
|
||||
"Make sure intf exists and is not configured."
|
||||
config = quietRun( 'ifconfig %s 2>/dev/null' % intf, shell=True )
|
||||
@@ -27,6 +30,7 @@ def checkIntf( intf ):
|
||||
'and is probably in use!\n' )
|
||||
exit( 1 )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
|
||||
@@ -38,7 +42,7 @@ if __name__ == '__main__':
|
||||
checkIntf( intfName )
|
||||
|
||||
info( '*** Creating network\n' )
|
||||
net = Mininet( topo=TreeTopo( depth=1, fanout=2 ) )
|
||||
net = Mininet( topo=TreeTopo( depth=1, fanout=2 ), waitConnected=True )
|
||||
|
||||
switch = net.switches[ 0 ]
|
||||
info( '*** Adding hardware interface', intfName, 'to switch',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
example of using various TCIntf options.
|
||||
@@ -13,7 +13,7 @@ from mininet.link import TCLink
|
||||
|
||||
def intfOptions():
|
||||
"run various traffic control commands on a single interface"
|
||||
net = Mininet( autoStaticArp=True )
|
||||
net = Mininet( autoStaticArp=True, waitConnected=True )
|
||||
net.addController( 'c0' )
|
||||
h1 = net.addHost( 'h1' )
|
||||
h2 = net.addHost( 'h2' )
|
||||
@@ -25,10 +25,10 @@ def intfOptions():
|
||||
# flush out latency from reactive forwarding delay
|
||||
net.pingAll()
|
||||
|
||||
info( '\n*** Configuring one intf with bandwidth of 5 Mb\n' )
|
||||
link1.intf1.config( bw=5 )
|
||||
info( '\n*** Configuring one intf with bandwidth of 10 Mb\n' )
|
||||
link1.intf1.config( bw=10 )
|
||||
info( '\n*** Running iperf to test\n' )
|
||||
net.iperf()
|
||||
net.iperf( seconds=10 )
|
||||
|
||||
info( '\n*** Configuring one intf with loss of 50%\n' )
|
||||
link1.intf1.config( loss=50 )
|
||||
@@ -43,6 +43,7 @@ def intfOptions():
|
||||
info( '\n*** Done testing\n' )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
intfOptions()
|
||||
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
limit.py: example of using link and CPU limits
|
||||
@@ -34,7 +34,7 @@ def limit( bw=10, cpu=.1 ):
|
||||
'Skipping this test\n' )
|
||||
continue
|
||||
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
|
||||
net = Mininet( topo=myTopo, intf=intf, host=host )
|
||||
net = Mininet( topo=myTopo, intf=intf, host=host, waitConnected=True )
|
||||
net.start()
|
||||
testLinkLimit( net, bw=bw )
|
||||
net.runCpuLimitTest( cpu=cpu )
|
||||
@@ -43,7 +43,7 @@ def limit( bw=10, cpu=.1 ):
|
||||
def verySimpleLimit( bw=150 ):
|
||||
"Absurdly simple limiting test"
|
||||
intf = custom( TCIntf, bw=bw )
|
||||
net = Mininet( intf=intf )
|
||||
net = Mininet( intf=intf, waitConnected=True )
|
||||
h1, h2 = net.addHost( 'h1' ), net.addHost( 'h2' )
|
||||
net.addLink( h1, h2 )
|
||||
net.start()
|
||||
@@ -55,6 +55,7 @@ def verySimpleLimit( bw=150 ):
|
||||
h2.cmdPrint( 'tc -d class show dev', h2.defaultIntf() )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
limit()
|
||||
|
||||
+16
-16
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test bandwidth (using iperf) on linear networks of varying size,
|
||||
@@ -24,25 +24,25 @@ of switches, this example demonstrates:
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch, Controller
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import lg, info
|
||||
from mininet.util import irange, quietRun
|
||||
from mininet.link import TCLink
|
||||
from functools import partial
|
||||
|
||||
import sys
|
||||
flush = sys.stdout.flush
|
||||
|
||||
|
||||
class LinearTestTopo( Topo ):
|
||||
"Topology for a string of N hosts and N-1 switches."
|
||||
|
||||
def __init__( self, N, **params ):
|
||||
|
||||
# Initialize topology
|
||||
Topo.__init__( self, **params )
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def build( self, N, **params ):
|
||||
# Create switches and hosts
|
||||
hosts = [ self.addHost( 'h%s' % h )
|
||||
for h in irange( 1, N ) ]
|
||||
@@ -83,14 +83,13 @@ def linearBandwidthTest( lengths ):
|
||||
output = quietRun( 'sysctl -w net.ipv4.tcp_congestion_control=reno' )
|
||||
assert 'reno' in output
|
||||
|
||||
for datapath in switches.keys():
|
||||
for datapath, Switch in switches.items():
|
||||
info( "*** testing", datapath, "datapath\n" )
|
||||
Switch = switches[ datapath ]
|
||||
results[ datapath ] = []
|
||||
link = partial( TCLink, delay='2ms', bw=10 )
|
||||
link = partial( TCLink, delay='30ms', bw=100 )
|
||||
net = Mininet( topo=topo, switch=Switch,
|
||||
controller=Controller, waitConnected=True,
|
||||
link=link )
|
||||
controller=Controller, link=link,
|
||||
waitConnected=True )
|
||||
net.start()
|
||||
info( "*** testing basic connectivity\n" )
|
||||
for n in lengths:
|
||||
@@ -103,13 +102,13 @@ def linearBandwidthTest( lengths ):
|
||||
src.cmd( 'telnet', dst.IP(), '5001' )
|
||||
info( "testing", src.name, "<->", dst.name, '\n' )
|
||||
# serverbw = received; _clientbw = buffered
|
||||
serverbw, _clientbw = net.iperf( [ src, dst ], seconds=10 )
|
||||
serverbw, _clientbw = net.iperf( [ src, dst ], seconds=5 )
|
||||
info( serverbw, '\n' )
|
||||
flush()
|
||||
results[ datapath ] += [ ( n, serverbw ) ]
|
||||
net.stop()
|
||||
|
||||
for datapath in switches.keys():
|
||||
for datapath in switches:
|
||||
info( "\n*** Linear network results for", datapath, "datapath:\n" )
|
||||
result = results[ datapath ]
|
||||
info( "SwitchCount\tiperf Results\n" )
|
||||
@@ -119,8 +118,9 @@ def linearBandwidthTest( lengths ):
|
||||
info( '\n')
|
||||
info( '\n' )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info' )
|
||||
sizes = [ 1, 10, 20, 40, 60, 80 ]
|
||||
sizes = [ 1, 2, 3, 4 ]
|
||||
info( "*** Running linearBandwidthTest", sizes, '\n' )
|
||||
linearBandwidthTest( sizes )
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
linuxrouter.py: Example network with Linux IP router
|
||||
@@ -38,6 +38,7 @@ from mininet.cli import CLI
|
||||
class LinuxRouter( Node ):
|
||||
"A Node with IP forwarding enabled."
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def config( self, **params ):
|
||||
super( LinuxRouter, self).config( **params )
|
||||
# Enable forwarding on the router
|
||||
@@ -51,6 +52,7 @@ class LinuxRouter( Node ):
|
||||
class NetworkTopo( Topo ):
|
||||
"A LinuxRouter connecting three IP subnets"
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def build( self, **_opts ):
|
||||
|
||||
defaultIP = '192.168.1.1/24' # IP address for r0-eth1
|
||||
@@ -79,13 +81,15 @@ class NetworkTopo( Topo ):
|
||||
def run():
|
||||
"Test linux router"
|
||||
topo = NetworkTopo()
|
||||
net = Mininet( topo=topo ) # controller is used by s1-s3
|
||||
net = Mininet( topo=topo,
|
||||
waitConnected=True ) # controller is used by s1-s3
|
||||
net.start()
|
||||
info( '*** Routing Table on Router:\n' )
|
||||
info( net[ 'r0' ].cmd( 'route' ) )
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
run()
|
||||
|
||||
+143
-128
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
MiniEdit: a simple network editor for Mininet
|
||||
@@ -13,57 +13,71 @@ Controller icon from http://semlabs.co.uk/
|
||||
OpenFlow icon from https://www.opennetworking.org/
|
||||
"""
|
||||
|
||||
# Miniedit needs some work in order to pass pylint...
|
||||
# pylint: disable=line-too-long,too-many-branches
|
||||
# pylint: disable=too-many-statements,attribute-defined-outside-init
|
||||
# pylint: disable=missing-docstring
|
||||
|
||||
MINIEDIT_VERSION = '2.2.0.1'
|
||||
|
||||
from optparse import OptionParser
|
||||
# from Tkinter import *
|
||||
from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu, Checkbutton,
|
||||
Menu, Toplevel, Button, BitmapImage, PhotoImage, Canvas,
|
||||
Scrollbar, Wm, TclError, StringVar, IntVar,
|
||||
E, W, EW, NW, Y, VERTICAL, SOLID, CENTER,
|
||||
RIGHT, LEFT, BOTH, TRUE, FALSE )
|
||||
from ttk import Notebook
|
||||
from tkMessageBox import showerror
|
||||
from subprocess import call
|
||||
import tkFont
|
||||
import tkFileDialog
|
||||
import tkSimpleDialog
|
||||
import re
|
||||
import json
|
||||
from distutils.version import StrictVersion
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from functools import partial
|
||||
|
||||
if 'PYTHONPATH' in os.environ:
|
||||
sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
|
||||
|
||||
# someday: from ttk import *
|
||||
from optparse import OptionParser # pylint: disable=deprecated-module
|
||||
from subprocess import call
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
|
||||
from mininet.log import info, debug, warn, setLogLevel
|
||||
from mininet.net import Mininet, VERSION
|
||||
from mininet.util import netParse, ipAdd, quietRun
|
||||
from mininet.util import buildTopo
|
||||
from mininet.util import custom, customClass
|
||||
from mininet.util import (netParse, ipAdd, quietRun,
|
||||
buildTopo, custom, customClass, StrictVersion )
|
||||
from mininet.term import makeTerm, cleanUpScreens
|
||||
from mininet.node import Controller, RemoteController, NOX, OVSController
|
||||
from mininet.node import CPULimitedHost, Host, Node
|
||||
from mininet.node import OVSSwitch, UserSwitch
|
||||
from mininet.node import (Controller, RemoteController, NOX, OVSController,
|
||||
CPULimitedHost, Host, Node,
|
||||
OVSSwitch, UserSwitch, IVSSwitch )
|
||||
from mininet.link import TCLink, Intf, Link
|
||||
from mininet.cli import CLI
|
||||
from mininet.moduledeps import moduleDeps
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||
from mininet.topolib import TreeTopo
|
||||
|
||||
# pylint: disable=import-error
|
||||
if sys.version_info[0] == 2:
|
||||
from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu,
|
||||
Checkbutton, Menu, Toplevel, Button, BitmapImage,
|
||||
PhotoImage, Canvas, Scrollbar, Wm, TclError,
|
||||
StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID,
|
||||
CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE )
|
||||
from ttk import Notebook
|
||||
from tkMessageBox import showerror
|
||||
import tkFont
|
||||
import tkFileDialog
|
||||
import tkSimpleDialog
|
||||
else:
|
||||
from tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu,
|
||||
Checkbutton, Menu, Toplevel, Button, BitmapImage,
|
||||
PhotoImage, Canvas, Scrollbar, Wm, TclError,
|
||||
StringVar, IntVar, E, W, EW, NW, Y, VERTICAL, SOLID,
|
||||
CENTER, RIGHT, LEFT, BOTH, TRUE, FALSE )
|
||||
from tkinter.ttk import Notebook
|
||||
from tkinter.messagebox import showerror
|
||||
from tkinter import font as tkFont
|
||||
from tkinter import simpledialog as tkSimpleDialog
|
||||
from tkinter import filedialog as tkFileDialog
|
||||
# someday: from ttk import *
|
||||
# pylint: enable=import-error
|
||||
|
||||
|
||||
# Miniedit still needs work in order to pass pylint...
|
||||
# pylint: disable=line-too-long,too-many-branches
|
||||
# pylint: disable=too-many-statements,attribute-defined-outside-init
|
||||
# pylint: disable=missing-docstring,too-many-ancestors
|
||||
# pylint: disable=too-many-nested-blocks,too-many-arguments
|
||||
|
||||
|
||||
MINIEDIT_VERSION = '2.2.0.1'
|
||||
|
||||
if 'PYTHONPATH' in os.environ:
|
||||
sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
|
||||
|
||||
info( 'MiniEdit running against Mininet '+VERSION, '\n' )
|
||||
MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION)
|
||||
if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
|
||||
from mininet.node import IVSSwitch
|
||||
|
||||
TOPODEF = 'none'
|
||||
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
||||
@@ -123,6 +137,7 @@ class LegacyRouter( Node ):
|
||||
def __init__( self, name, inNamespace=True, **params ):
|
||||
Node.__init__( self, name, inNamespace, **params )
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def config( self, **_params ):
|
||||
if self.intfs:
|
||||
self.setParam( _params, 'setIP', ip='0.0.0.0' )
|
||||
@@ -803,8 +818,8 @@ class VerticalScrolledTable(LabelFrame):
|
||||
* This frame only allows vertical scrolling
|
||||
|
||||
"""
|
||||
def __init__(self, parent, rows=2, columns=2, title=None, *args, **kw):
|
||||
LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, *args, **kw)
|
||||
def __init__(self, parent, rows=2, columns=2, title=None, **kw):
|
||||
LabelFrame.__init__(self, parent, text=title, padx=5, pady=5, **kw)
|
||||
|
||||
# create a canvas object and a vertical scrollbar for scrolling it
|
||||
vscrollbar = Scrollbar(self, orient=VERTICAL)
|
||||
@@ -840,8 +855,6 @@ class VerticalScrolledTable(LabelFrame):
|
||||
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
|
||||
canvas.bind('<Configure>', _configure_canvas)
|
||||
|
||||
return
|
||||
|
||||
class TableFrame(Frame):
|
||||
def __init__(self, parent, rows=2, columns=2):
|
||||
|
||||
@@ -873,7 +886,7 @@ class TableFrame(Frame):
|
||||
label.grid(row=self.rows, column=column, sticky="wens", padx=1, pady=1)
|
||||
if value is not None:
|
||||
label.insert(0, value[column])
|
||||
if readonly == True:
|
||||
if readonly:
|
||||
label.configure(state='readonly')
|
||||
current_row.append(label)
|
||||
self._widgets.append(current_row)
|
||||
@@ -1349,7 +1362,7 @@ class MiniEdit( Frame ):
|
||||
|
||||
# Tools
|
||||
for tool in self.tools:
|
||||
cmd = ( lambda t=tool: self.activate( t ) )
|
||||
cmd = partial( self.activate, tool )
|
||||
b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
|
||||
if tool in self.images:
|
||||
b.config( height=35, image=self.images[ tool ] )
|
||||
@@ -1386,11 +1399,11 @@ class MiniEdit( Frame ):
|
||||
|
||||
def addNode( self, node, nodeNum, x, y, name=None):
|
||||
"Add a new node to our canvas."
|
||||
if 'Switch' == node:
|
||||
if node == 'Switch':
|
||||
self.switchCount += 1
|
||||
if 'Host' == node:
|
||||
if node == 'Host':
|
||||
self.hostCount += 1
|
||||
if 'Controller' == node:
|
||||
if node == 'Controller':
|
||||
self.controllerCount += 1
|
||||
if name is None:
|
||||
name = self.nodePrefixes[ node ] + nodeNum
|
||||
@@ -1407,14 +1420,14 @@ class MiniEdit( Frame ):
|
||||
|
||||
def convertJsonUnicode(self, text):
|
||||
"Some part of Mininet don't like Unicode"
|
||||
unicode = globals().get( 'unicode', str )
|
||||
if isinstance(text, dict):
|
||||
return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in text.iteritems()}
|
||||
elif isinstance(text, list):
|
||||
return {self.convertJsonUnicode(key): self.convertJsonUnicode(value) for key, value in text.items()}
|
||||
if isinstance(text, list):
|
||||
return [self.convertJsonUnicode(element) for element in text]
|
||||
elif isinstance(text, unicode):
|
||||
if isinstance(text, unicode): # pylint: disable=undefined-variable
|
||||
return text.encode('utf-8')
|
||||
else:
|
||||
return text
|
||||
return text
|
||||
|
||||
def loadTopology( self ):
|
||||
"Load command."
|
||||
@@ -1425,14 +1438,14 @@ class MiniEdit( Frame ):
|
||||
('All Files','*'),
|
||||
]
|
||||
f = tkFileDialog.askopenfile(filetypes=myFormats, mode='rb')
|
||||
if f == None:
|
||||
if f is None:
|
||||
return
|
||||
self.newTopology()
|
||||
loadedTopology = self.convertJsonUnicode(json.load(f))
|
||||
|
||||
# Load application preferences
|
||||
if 'application' in loadedTopology:
|
||||
self.appPrefs = dict(self.appPrefs.items() + loadedTopology['application'].items())
|
||||
self.appPrefs.update(loadedTopology['application'])
|
||||
if "ovsOf10" not in self.appPrefs["openFlowVersions"]:
|
||||
self.appPrefs["openFlowVersions"]["ovsOf10"] = '0'
|
||||
if "ovsOf11" not in self.appPrefs["openFlowVersions"]:
|
||||
@@ -1586,10 +1599,11 @@ class MiniEdit( Frame ):
|
||||
for widget in self.widgetToItem:
|
||||
if name == widget[ 'text' ]:
|
||||
return widget
|
||||
return None
|
||||
|
||||
def newTopology( self ):
|
||||
"New command."
|
||||
for widget in self.widgetToItem.keys():
|
||||
for widget in tuple( self.widgetToItem ):
|
||||
self.deleteItem( self.widgetToItem[ widget ] )
|
||||
self.hostCount = 0
|
||||
self.switchCount = 0
|
||||
@@ -1617,10 +1631,10 @@ class MiniEdit( Frame ):
|
||||
hostsToSave = []
|
||||
switchesToSave = []
|
||||
controllersToSave = []
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
x1, y1 = self.canvas.coords( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
x1, y1 = self.canvas.coords( item )
|
||||
if 'Switch' in tags or 'LegacySwitch' in tags or 'LegacyRouter' in tags:
|
||||
nodeNum = self.switchOpts[name]['nodeNum']
|
||||
nodeToSave = {'number':str(nodeNum),
|
||||
@@ -1665,14 +1679,13 @@ class MiniEdit( Frame ):
|
||||
savingDictionary['application'] = self.appPrefs
|
||||
|
||||
try:
|
||||
f = open(fileName, 'wb')
|
||||
f.write(json.dumps(savingDictionary, sort_keys=True, indent=4, separators=(',', ': ')))
|
||||
# pylint: disable=broad-except
|
||||
except Exception as er:
|
||||
with open(fileName, 'w') as f:
|
||||
f.write(
|
||||
json.dumps(savingDictionary,
|
||||
sort_keys=True,
|
||||
indent=4, separators=(',', ': ')))
|
||||
except Exception as er: # pylint: disable=broad-except
|
||||
warn( er, '\n' )
|
||||
# pylint: enable=broad-except
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def exportScript( self ):
|
||||
"Export command."
|
||||
@@ -1684,9 +1697,9 @@ class MiniEdit( Frame ):
|
||||
fileName = tkFileDialog.asksaveasfilename(filetypes=myFormats ,title="Export the topology as...")
|
||||
if len(fileName ) > 0:
|
||||
# debug( "Now saving under %s\n" % fileName )
|
||||
f = open(fileName, 'wb')
|
||||
f = open(fileName, 'w') # pylint: disable=consider-using-with
|
||||
|
||||
f.write("#!/usr/bin/python\n")
|
||||
f.write("#!/usr/bin/env python\n")
|
||||
f.write("\n")
|
||||
f.write("from mininet.net import Mininet\n")
|
||||
f.write("from mininet.node import Controller, RemoteController, OVSController\n")
|
||||
@@ -1700,9 +1713,9 @@ class MiniEdit( Frame ):
|
||||
f.write("from subprocess import call\n")
|
||||
|
||||
inBandCtrl = False
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
|
||||
if 'Controller' in tags:
|
||||
opts = self.controllers[name]
|
||||
@@ -1710,7 +1723,7 @@ class MiniEdit( Frame ):
|
||||
if controllerType == 'inband':
|
||||
inBandCtrl = True
|
||||
|
||||
if inBandCtrl == True:
|
||||
if inBandCtrl:
|
||||
f.write("\n")
|
||||
f.write("class InbandController( RemoteController ):\n")
|
||||
f.write("\n")
|
||||
@@ -1728,9 +1741,9 @@ class MiniEdit( Frame ):
|
||||
f.write(" ipBase='"+self.appPrefs['ipBase']+"')\n")
|
||||
f.write("\n")
|
||||
f.write(" info( '*** Adding controller\\n' )\n")
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
|
||||
if 'Controller' in tags:
|
||||
opts = self.controllers[name]
|
||||
@@ -1762,9 +1775,9 @@ class MiniEdit( Frame ):
|
||||
|
||||
# Save Switches and Hosts
|
||||
f.write(" info( '*** Add switches\\n')\n")
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'LegacyRouter' in tags:
|
||||
f.write(" "+name+" = net.addHost('"+name+"', cls=Node, ip='0.0.0.0')\n")
|
||||
f.write(" "+name+".cmd('sysctl -w net.ipv4.ip_forward=1')\n")
|
||||
@@ -1802,9 +1815,9 @@ class MiniEdit( Frame ):
|
||||
|
||||
f.write("\n")
|
||||
f.write(" info( '*** Add hosts\\n')\n")
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Host' in tags:
|
||||
opts = self.hostOpts[name]
|
||||
ip = None
|
||||
@@ -1835,7 +1848,7 @@ class MiniEdit( Frame ):
|
||||
|
||||
# Save Links
|
||||
f.write(" info( '*** Add links\\n')\n")
|
||||
for key,linkDetail in self.links.iteritems():
|
||||
for key,linkDetail in self.links.items():
|
||||
tags = self.canvas.gettags(key)
|
||||
if 'data' in tags:
|
||||
optsExist = False
|
||||
@@ -1897,9 +1910,9 @@ class MiniEdit( Frame ):
|
||||
f.write("\n")
|
||||
|
||||
f.write(" info( '*** Starting switches\\n')\n")
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Switch' in tags or 'LegacySwitch' in tags:
|
||||
opts = self.switchOpts[name]
|
||||
ctrlList = ",".join(opts['controllers'])
|
||||
@@ -1908,9 +1921,9 @@ class MiniEdit( Frame ):
|
||||
f.write("\n")
|
||||
|
||||
f.write(" info( '*** Post configure switches and hosts\\n')\n")
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Switch' in tags:
|
||||
opts = self.switchOpts[name]
|
||||
if opts['switchType'] == 'default':
|
||||
@@ -1938,9 +1951,9 @@ class MiniEdit( Frame ):
|
||||
if 'switchIP' in opts:
|
||||
if len(opts['switchIP']) > 0:
|
||||
f.write(" "+name+".cmd('ifconfig "+name+" "+opts['switchIP']+"')\n")
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Host' in tags:
|
||||
opts = self.hostOpts[name]
|
||||
# Attach vlan interfaces
|
||||
@@ -1962,9 +1975,9 @@ class MiniEdit( Frame ):
|
||||
if len(nflowValues['nflowTarget']) > 0:
|
||||
nflowEnabled = False
|
||||
nflowSwitches = ''
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
|
||||
if 'Switch' in tags:
|
||||
opts = self.switchOpts[name]
|
||||
@@ -1986,9 +1999,9 @@ class MiniEdit( Frame ):
|
||||
if len(sflowValues['sflowTarget']) > 0:
|
||||
sflowEnabled = False
|
||||
sflowSwitches = ''
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
|
||||
if 'Switch' in tags:
|
||||
opts = self.switchOpts[name]
|
||||
@@ -2003,9 +2016,9 @@ class MiniEdit( Frame ):
|
||||
|
||||
f.write("\n")
|
||||
f.write(" CLI(net)\n")
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem:
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Host' in tags:
|
||||
opts = self.hostOpts[name]
|
||||
# Run User Defined Stop Command
|
||||
@@ -2107,7 +2120,7 @@ class MiniEdit( Frame ):
|
||||
c = self.canvas
|
||||
x, y = c.canvasx( event.x ), c.canvasy( event.y )
|
||||
name = self.nodePrefixes[ node ]
|
||||
if 'Switch' == node:
|
||||
if node == 'Switch':
|
||||
self.switchCount += 1
|
||||
name = self.nodePrefixes[ node ] + str( self.switchCount )
|
||||
self.switchOpts[name] = {}
|
||||
@@ -2115,14 +2128,14 @@ class MiniEdit( Frame ):
|
||||
self.switchOpts[name]['hostname']=name
|
||||
self.switchOpts[name]['switchType']='default'
|
||||
self.switchOpts[name]['controllers']=[]
|
||||
if 'LegacyRouter' == node:
|
||||
if node == 'LegacyRouter':
|
||||
self.switchCount += 1
|
||||
name = self.nodePrefixes[ node ] + str( self.switchCount )
|
||||
self.switchOpts[name] = {}
|
||||
self.switchOpts[name]['nodeNum']=self.switchCount
|
||||
self.switchOpts[name]['hostname']=name
|
||||
self.switchOpts[name]['switchType']='legacyRouter'
|
||||
if 'LegacySwitch' == node:
|
||||
if node == 'LegacySwitch':
|
||||
self.switchCount += 1
|
||||
name = self.nodePrefixes[ node ] + str( self.switchCount )
|
||||
self.switchOpts[name] = {}
|
||||
@@ -2130,13 +2143,13 @@ class MiniEdit( Frame ):
|
||||
self.switchOpts[name]['hostname']=name
|
||||
self.switchOpts[name]['switchType']='legacySwitch'
|
||||
self.switchOpts[name]['controllers']=[]
|
||||
if 'Host' == node:
|
||||
if node == 'Host':
|
||||
self.hostCount += 1
|
||||
name = self.nodePrefixes[ node ] + str( self.hostCount )
|
||||
self.hostOpts[name] = {'sched':'host'}
|
||||
self.hostOpts[name]['nodeNum']=self.hostCount
|
||||
self.hostOpts[name]['hostname']=name
|
||||
if 'Controller' == node:
|
||||
if node == 'Controller':
|
||||
name = self.nodePrefixes[ node ] + str( self.controllerCount )
|
||||
ctrlr = { 'controllerType': 'ref',
|
||||
'hostname': name,
|
||||
@@ -2154,15 +2167,15 @@ class MiniEdit( Frame ):
|
||||
self.itemToWidget[ item ] = icon
|
||||
self.selectItem( item )
|
||||
icon.links = {}
|
||||
if 'Switch' == node:
|
||||
if node == 'Switch':
|
||||
icon.bind('<Button-3>', self.do_switchPopup )
|
||||
if 'LegacyRouter' == node:
|
||||
if node == 'LegacyRouter':
|
||||
icon.bind('<Button-3>', self.do_legacyRouterPopup )
|
||||
if 'LegacySwitch' == node:
|
||||
if node == 'LegacySwitch':
|
||||
icon.bind('<Button-3>', self.do_legacySwitchPopup )
|
||||
if 'Host' == node:
|
||||
if node == 'Host':
|
||||
icon.bind('<Button-3>', self.do_hostPopup )
|
||||
if 'Controller' == node:
|
||||
if node == 'Controller':
|
||||
icon.bind('<Button-3>', self.do_controllerPopup )
|
||||
|
||||
def clickController( self, event ):
|
||||
@@ -2232,7 +2245,7 @@ class MiniEdit( Frame ):
|
||||
|
||||
def clickNode( self, event ):
|
||||
"Node click handler."
|
||||
if self.active is 'NetLink':
|
||||
if self.active == 'NetLink':
|
||||
self.startLink( event )
|
||||
else:
|
||||
self.selectNode( event )
|
||||
@@ -2240,14 +2253,14 @@ class MiniEdit( Frame ):
|
||||
|
||||
def dragNode( self, event ):
|
||||
"Node drag handler."
|
||||
if self.active is 'NetLink':
|
||||
if self.active == 'NetLink':
|
||||
self.dragNetLink( event )
|
||||
else:
|
||||
self.dragNodeAround( event )
|
||||
|
||||
def releaseNode( self, event ):
|
||||
"Node release handler."
|
||||
if self.active is 'NetLink':
|
||||
if self.active == 'NetLink':
|
||||
self.finishLink( event )
|
||||
|
||||
# Specific node handlers
|
||||
@@ -2359,6 +2372,8 @@ class MiniEdit( Frame ):
|
||||
# For now, don't allow hosts to be directly linked
|
||||
stags = self.canvas.gettags( self.widgetToItem[ source ] )
|
||||
dtags = self.canvas.gettags( target )
|
||||
# TODO: Make this less confusing
|
||||
# pylint: disable=too-many-boolean-expressions
|
||||
if (('Host' in stags and 'Host' in dtags) or
|
||||
('Controller' in dtags and 'LegacyRouter' in stags) or
|
||||
('Controller' in stags and 'LegacyRouter' in dtags) or
|
||||
@@ -2425,7 +2440,8 @@ class MiniEdit( Frame ):
|
||||
line3.pack(pady=10 )
|
||||
line4.pack(pady=10 )
|
||||
line5.pack(pady=10 )
|
||||
hide = ( lambda about=about: about.withdraw() )
|
||||
def hide():
|
||||
about.withdraw()
|
||||
self.aboutBox = about
|
||||
# Hide on close rather than destroying window
|
||||
Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
|
||||
@@ -2600,9 +2616,9 @@ class MiniEdit( Frame ):
|
||||
info( 'New controller details for ' + name + ' = ' + str(self.controllers[name]), '\n' )
|
||||
# Find references to controller and change name
|
||||
if oldName != name:
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
switchName = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Switch' in tags:
|
||||
switch = self.switchOpts[switchName]
|
||||
if oldName in switch['controllers']:
|
||||
@@ -2642,7 +2658,7 @@ class MiniEdit( Frame ):
|
||||
linkopts = {}
|
||||
source.links[ dest ] = self.link
|
||||
dest.links[ source ] = self.link
|
||||
self.links[ self.link ] = {'type' :linktype,
|
||||
self.links[ self.link ] = {'type':linktype,
|
||||
'src':source,
|
||||
'dest':dest,
|
||||
'linkOpts':linkopts}
|
||||
@@ -2683,14 +2699,13 @@ class MiniEdit( Frame ):
|
||||
tags = self.canvas.gettags(item)
|
||||
if 'Controller' in tags:
|
||||
# remove from switch controller lists
|
||||
for serachwidget in self.widgetToItem:
|
||||
name = serachwidget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ serachwidget ] )
|
||||
for searchwidget, searchitem in self.widgetToItem.items():
|
||||
name = searchwidget[ 'text' ]
|
||||
tags = self.canvas.gettags( searchitem )
|
||||
if 'Switch' in tags:
|
||||
if widget['text'] in self.switchOpts[name]['controllers']:
|
||||
self.switchOpts[name]['controllers'].remove(widget['text'])
|
||||
|
||||
for link in widget.links.values():
|
||||
for link in tuple( widget.links.values() ):
|
||||
# Delete from view and model
|
||||
self.deleteItem( link )
|
||||
del self.itemToWidget[ item ]
|
||||
@@ -2699,9 +2714,9 @@ class MiniEdit( Frame ):
|
||||
def buildNodes( self, net):
|
||||
# Make nodes
|
||||
info( "Getting Hosts and Switches.\n" )
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
# debug( name+' has '+str(tags), '\n' )
|
||||
|
||||
if 'Switch' in tags:
|
||||
@@ -2875,7 +2890,7 @@ class MiniEdit( Frame ):
|
||||
def buildLinks( self, net):
|
||||
# Make links
|
||||
info( "Getting Links.\n" )
|
||||
for key,link in self.links.iteritems():
|
||||
for key,link in self.links.items():
|
||||
tags = self.canvas.gettags(key)
|
||||
if 'data' in tags:
|
||||
src=link['src']
|
||||
@@ -2915,9 +2930,9 @@ class MiniEdit( Frame ):
|
||||
def postStartSetup( self ):
|
||||
|
||||
# Setup host details
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Host' in tags:
|
||||
newHost = self.net.get(name)
|
||||
opts = self.hostOpts[name]
|
||||
@@ -2942,9 +2957,9 @@ class MiniEdit( Frame ):
|
||||
if len(nflowValues['nflowTarget']) > 0:
|
||||
nflowEnabled = False
|
||||
nflowSwitches = ''
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
|
||||
if 'Switch' in tags:
|
||||
opts = self.switchOpts[name]
|
||||
@@ -2972,9 +2987,9 @@ class MiniEdit( Frame ):
|
||||
if len(sflowValues['sflowTarget']) > 0:
|
||||
sflowEnabled = False
|
||||
sflowSwitches = ''
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
|
||||
if 'Switch' in tags:
|
||||
opts = self.switchOpts[name]
|
||||
@@ -2996,7 +3011,7 @@ class MiniEdit( Frame ):
|
||||
## NOTE: MAKE SURE THIS IS LAST THING CALLED
|
||||
# Start the CLI if enabled
|
||||
if self.appPrefs['startCLI'] == '1':
|
||||
info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this sessoin.\n\n")
|
||||
info( "\n\n NOTE: PLEASE REMEMBER TO EXIT THE CLI BEFORE YOU PRESS THE STOP BUTTON. Not exiting will prevent MiniEdit from quitting and will prevent you from starting the network again during this session.\n\n")
|
||||
CLI(self.net)
|
||||
|
||||
def start( self ):
|
||||
@@ -3017,9 +3032,9 @@ class MiniEdit( Frame ):
|
||||
#for switch in self.net.switches:
|
||||
# info( switch.name + ' ')
|
||||
# switch.start( self.net.controllers )
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Switch' in tags:
|
||||
opts = self.switchOpts[name]
|
||||
switchControllers = []
|
||||
@@ -3039,9 +3054,9 @@ class MiniEdit( Frame ):
|
||||
"Stop network."
|
||||
if self.net is not None:
|
||||
# Stop host details
|
||||
for widget in self.widgetToItem:
|
||||
for widget, item in self.widgetToItem.items():
|
||||
name = widget[ 'text' ]
|
||||
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
||||
tags = self.canvas.gettags( item )
|
||||
if 'Host' in tags:
|
||||
newHost = self.net.get(name)
|
||||
opts = self.hostOpts[name]
|
||||
@@ -3184,7 +3199,7 @@ class MiniEdit( Frame ):
|
||||
addDictOption( opts, LINKS, LINKDEF, 'link' )
|
||||
|
||||
opts.add_option( '--custom', type='string', default=None,
|
||||
help='read custom topo and node params from .py' +
|
||||
help='read custom topo and node params from .py ' +
|
||||
'file' )
|
||||
|
||||
self.options, self.args = opts.parse_args()
|
||||
@@ -3210,8 +3225,9 @@ class MiniEdit( Frame ):
|
||||
"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():
|
||||
with open( fileName, 'r' ) as f:
|
||||
exec( f.read() ) # pylint: disable=exec-used
|
||||
for name, val in customs.items():
|
||||
self.setCustom( name, val )
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
@@ -3577,8 +3593,7 @@ def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
app = MiniEdit()
|
||||
### import topology if specified ###
|
||||
app.parseArgs()
|
||||
### import topology if specified ###
|
||||
app.importTopo()
|
||||
|
||||
app.mainloop()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Simple example of Mobility with Mininet
|
||||
@@ -19,14 +19,13 @@ to-do:
|
||||
- think about clearing last hop - why doesn't that work?
|
||||
"""
|
||||
|
||||
from random import randint
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import OVSSwitch
|
||||
from mininet.topo import LinearTopo
|
||||
from mininet.log import info, output, warn, setLogLevel
|
||||
|
||||
from random import randint
|
||||
|
||||
|
||||
class MobilitySwitch( OVSSwitch ):
|
||||
"Switch that can reattach and rename interfaces"
|
||||
@@ -38,6 +37,7 @@ class MobilitySwitch( OVSSwitch ):
|
||||
del self.intfs[ port ]
|
||||
del self.nameToIntf[ intf.name ]
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def addIntf( self, intf, rename=False, **kwargs ):
|
||||
"Add (and reparent) an interface"
|
||||
OVSSwitch.addIntf( self, intf, **kwargs )
|
||||
@@ -107,7 +107,8 @@ def moveHost( host, oldSwitch, newSwitch, newPort=None ):
|
||||
def mobilityTest():
|
||||
"A simple test of mobility"
|
||||
info( '* Simple mobility test\n' )
|
||||
net = Mininet( topo=LinearTopo( 3 ), switch=MobilitySwitch )
|
||||
net = Mininet( topo=LinearTopo( 3 ), switch=MobilitySwitch,
|
||||
waitConnected=True )
|
||||
info( '* Starting network:\n' )
|
||||
net.start()
|
||||
printConnections( net.switches )
|
||||
@@ -131,6 +132,7 @@ def mobilityTest():
|
||||
old = new
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
mobilityTest()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This is a simple example that demonstrates multiple links
|
||||
@@ -13,7 +13,7 @@ from mininet.topo import Topo
|
||||
def runMultiLink():
|
||||
"Create and run multiple link network"
|
||||
topo = simpleMultiLinkTopo( n=2 )
|
||||
net = Mininet( topo=topo )
|
||||
net = Mininet( topo=topo, waitConnected=True )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
@@ -21,9 +21,8 @@ def runMultiLink():
|
||||
class simpleMultiLinkTopo( Topo ):
|
||||
"Simple topology with multiple links"
|
||||
|
||||
def __init__( self, n, **kwargs ):
|
||||
Topo.__init__( self, **kwargs )
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def build( self, n, **_kwargs ):
|
||||
h1, h2 = self.addHost( 'h1' ), self.addHost( 'h2' )
|
||||
s1 = self.addSwitch( 's1' )
|
||||
|
||||
@@ -31,6 +30,7 @@ class simpleMultiLinkTopo( Topo ):
|
||||
self.addLink( s1, h1 )
|
||||
self.addLink( s1, h2 )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
runMultiLink()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
multiping.py: monitor multiple sets of hosts using ping
|
||||
@@ -8,18 +8,18 @@ multiple hosts and monitor their output interactively for a period=
|
||||
of time.
|
||||
"""
|
||||
|
||||
from select import poll, POLLIN
|
||||
from time import time
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import info, setLogLevel
|
||||
|
||||
from select import poll, POLLIN
|
||||
from time import time
|
||||
|
||||
def chunks( l, n ):
|
||||
def chunks( items, n ):
|
||||
"Divide list l into chunks of size n - thanks Stackoverflow"
|
||||
return [ l[ i: i + n ] for i in range( 0, len( l ), n ) ]
|
||||
return [ items[ i: i + n ] for i in range( 0, len( items ), n ) ]
|
||||
|
||||
def startpings( host, targetips ):
|
||||
"Tell host to repeatedly ping targets"
|
||||
@@ -45,7 +45,7 @@ def multiping( netsize, chunksize, seconds):
|
||||
|
||||
# Create network and identify subnets
|
||||
topo = SingleSwitchTopo( netsize )
|
||||
net = Mininet( topo=topo )
|
||||
net = Mininet( topo=topo, waitConnected=True )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
subnets = chunks( hosts, chunksize )
|
||||
@@ -59,7 +59,7 @@ def multiping( netsize, chunksize, seconds):
|
||||
# Start pings
|
||||
for subnet in subnets:
|
||||
ips = [ host.IP() for host in subnet ]
|
||||
#adding bogus to generate packet loss
|
||||
# adding bogus to generate packet loss
|
||||
ips.append( '10.0.0.200' )
|
||||
for host in subnet:
|
||||
startpings( host, ips )
|
||||
|
||||
+13
-11
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Simple example of sending output to multiple files and
|
||||
@@ -6,22 +6,24 @@ monitoring them
|
||||
"""
|
||||
|
||||
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.net import Mininet
|
||||
from mininet.log import info, setLogLevel
|
||||
|
||||
from time import time
|
||||
from select import poll, POLLIN
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.net import Mininet
|
||||
from mininet.log import info, setLogLevel
|
||||
from mininet.util import decode
|
||||
|
||||
|
||||
def monitorFiles( outfiles, seconds, timeoutms ):
|
||||
"Monitor set of files and return [(host, line)...]"
|
||||
devnull = open( '/dev/null', 'w' )
|
||||
devnull = open( '/dev/null', 'w' ) # pylint: disable=consider-using-with
|
||||
tails, fdToFile, fdToHost = {}, {}, {}
|
||||
for h, outfile in outfiles.iteritems():
|
||||
tail = Popen( [ 'tail', '-f', outfile ],
|
||||
stdout=PIPE, stderr=devnull )
|
||||
for h, outfile in outfiles.items():
|
||||
tail = Popen( # pylint: disable=consider-using-with
|
||||
[ 'tail', '-f', outfile ],
|
||||
stdout=PIPE, stderr=devnull )
|
||||
fd = tail.stdout.fileno()
|
||||
tails[ h ] = tail
|
||||
fdToFile[ fd ] = tail.stdout
|
||||
@@ -40,7 +42,7 @@ def monitorFiles( outfiles, seconds, timeoutms ):
|
||||
host = fdToHost[ fd ]
|
||||
# Wait for a line of output
|
||||
line = f.readline().strip()
|
||||
yield host, line
|
||||
yield host, decode( line )
|
||||
else:
|
||||
# If we timed out, return nothing
|
||||
yield None, ''
|
||||
@@ -52,7 +54,7 @@ def monitorFiles( outfiles, seconds, timeoutms ):
|
||||
def monitorTest( N=3, seconds=3 ):
|
||||
"Run pings and monitor multiple hosts"
|
||||
topo = SingleSwitchTopo( N )
|
||||
net = Mininet( topo )
|
||||
net = Mininet( topo, waitConnected=True )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
info( "Starting test...\n" )
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This example shows how to create a network and run multiple tests.
|
||||
@@ -17,12 +17,14 @@ def ifconfigTest( net ):
|
||||
for host in hosts:
|
||||
info( host.cmd( 'ifconfig' ) )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info' )
|
||||
info( "*** Initializing Mininet and kernel modules\n" )
|
||||
OVSKernelSwitch.setup()
|
||||
info( "*** Creating network\n" )
|
||||
network = Mininet( TreeTopo( depth=2, fanout=2 ), switch=OVSKernelSwitch )
|
||||
network = Mininet( TreeTopo( depth=2, fanout=2), switch=OVSKernelSwitch,
|
||||
waitConnected=True )
|
||||
info( "*** Starting network\n" )
|
||||
network.start()
|
||||
info( "*** Running ping test\n" )
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Example to create a Mininet topology and connect it to the internet via NAT
|
||||
@@ -12,7 +12,7 @@ from mininet.topolib import TreeNet
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info')
|
||||
net = TreeNet( depth=1, fanout=4 )
|
||||
net = TreeNet( depth=1, fanout=4, waitConnected=True )
|
||||
# Add NAT connectivity
|
||||
net.addNAT().configDefault()
|
||||
net.start()
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
natnet.py: Example network with NATs
|
||||
@@ -27,9 +27,8 @@ from mininet.util import irange
|
||||
|
||||
class InternetTopo(Topo):
|
||||
"Single switch connected to n hosts."
|
||||
def __init__(self, n=2, **opts):
|
||||
Topo.__init__(self, **opts)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def build(self, n=2, **_kwargs ):
|
||||
# set up inet switch
|
||||
inetSwitch = self.addSwitch('s0')
|
||||
# add inet host
|
||||
@@ -59,11 +58,12 @@ class InternetTopo(Topo):
|
||||
def run():
|
||||
"Create network and run the CLI"
|
||||
topo = InternetTopo()
|
||||
net = Mininet(topo=topo)
|
||||
net = Mininet(topo=topo, waitConnected=True )
|
||||
net.start()
|
||||
CLI(net)
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
run()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Create a network with 5 hosts, numbered 1-4 and 9.
|
||||
@@ -28,7 +28,7 @@ def testPortNumbering():
|
||||
mid-level API) and check that implicit and
|
||||
explicit port numbering works as expected."""
|
||||
|
||||
net = Mininet( controller=Controller )
|
||||
net = Mininet( controller=Controller, waitConnected=True )
|
||||
|
||||
info( '*** Adding controller\n' )
|
||||
net.addController( 'c0' )
|
||||
@@ -75,6 +75,7 @@ def testPortNumbering():
|
||||
info( '*** Stopping network\n' )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
testPortNumbering()
|
||||
|
||||
+5
-8
@@ -1,23 +1,19 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This example monitors a number of hosts using host.popen() and
|
||||
pmonitor()
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.util import custom, pmonitor
|
||||
from mininet.util import pmonitor
|
||||
|
||||
def monitorhosts( hosts=5, sched='cfs' ):
|
||||
def monitorhosts( hosts=5 ):
|
||||
"Start a bunch of pings and monitor them using popen"
|
||||
mytopo = SingleSwitchTopo( hosts )
|
||||
cpu = .5 / hosts
|
||||
myhost = custom( CPULimitedHost, cpu=cpu, sched=sched )
|
||||
net = Mininet( topo=mytopo, host=myhost )
|
||||
net = Mininet( topo=mytopo, waitConnected=True )
|
||||
net.start()
|
||||
# Start a bunch of pings
|
||||
popens = {}
|
||||
@@ -32,6 +28,7 @@ def monitorhosts( hosts=5, sched='cfs' ):
|
||||
# Done
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
monitorhosts( hosts=5 )
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"Monitor multiple hosts using popen()/pmonitor()"
|
||||
|
||||
from time import time
|
||||
from signal import SIGINT
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.util import pmonitor
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from time import time
|
||||
from signal import SIGINT
|
||||
|
||||
def pmonitorTest( N=3, seconds=10 ):
|
||||
"Run pings and monitor multiple hosts using pmonitor"
|
||||
topo = SingleSwitchTopo( N )
|
||||
net = Mininet( topo )
|
||||
net = Mininet( topo, waitConnected=True )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
info( "Starting test...\n" )
|
||||
@@ -31,6 +32,7 @@ def pmonitorTest( N=3, seconds=10 ):
|
||||
p.send_signal( SIGINT )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
pmonitorTest()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Build a simple network from scratch, using mininet primitives.
|
||||
@@ -8,6 +8,7 @@ but it exposes the configuration details and allows customization.
|
||||
For most tasks, the higher-level API will be preferable.
|
||||
"""
|
||||
|
||||
from time import sleep
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
@@ -15,7 +16,6 @@ from mininet.link import Link
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.util import quietRun
|
||||
|
||||
from time import sleep
|
||||
|
||||
def scratchNet( cname='controller', cargs='-v ptcp:' ):
|
||||
"Create network from scratch using Open vSwitch."
|
||||
@@ -62,6 +62,7 @@ def scratchNet( cname='controller', cargs='-v ptcp:' ):
|
||||
switch.deleteIntfs()
|
||||
info( '\n' )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (kernel datapath)\n' )
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Build a simple network from scratch, using mininet primitives.
|
||||
@@ -66,6 +66,7 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
switch.deleteIntfs()
|
||||
info( '\n' )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
info( '*** Scratch network demo (user datapath)\n' )
|
||||
|
||||
+10
-3
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Simple example of setting network and CPU parameters
|
||||
@@ -9,6 +9,7 @@ iperf will hang indefinitely if the TCP handshake fails
|
||||
to complete.
|
||||
"""
|
||||
|
||||
from sys import argv
|
||||
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
@@ -17,7 +18,6 @@ from mininet.link import TCLink
|
||||
from mininet.util import dumpNodeConnections
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from sys import argv
|
||||
|
||||
# It would be nice if we didn't have to do this:
|
||||
# pylint: disable=arguments-differ
|
||||
@@ -49,12 +49,19 @@ def perfTest( lossy=True ):
|
||||
net.start()
|
||||
info( "Dumping host connections\n" )
|
||||
dumpNodeConnections(net.hosts)
|
||||
info( "Testing bandwidth between h1 and h4\n" )
|
||||
info( "Testing bandwidth between h1 and h4 (lossy=%s)\n" % lossy )
|
||||
h1, h4 = net.getNodeByName('h1', 'h4')
|
||||
net.iperf( ( h1, h4 ), l4Type='UDP' )
|
||||
# Debugging
|
||||
h1.cmd('jobs')
|
||||
h4.cmd('jobs')
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
# Debug for now
|
||||
if 'testmode' in argv:
|
||||
setLogLevel( 'debug' )
|
||||
# Prevent test_simpleperf from failing due to packet loss
|
||||
perfTest( lossy=( 'testmode' not in argv ) )
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Create a network and start sshd(8) on each host.
|
||||
@@ -29,7 +29,7 @@ from mininet.util import waitListening
|
||||
def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
topo = TreeTopo( depth, fanout )
|
||||
return Mininet( topo, **kwargs )
|
||||
return Mininet( topo, waitConnected=True, **kwargs )
|
||||
|
||||
def connectToRootNS( network, switch, ip, routes ):
|
||||
"""Connect hosts to root namespace via switch. Starts network.
|
||||
@@ -47,6 +47,7 @@ def connectToRootNS( network, switch, ip, routes ):
|
||||
for route in routes:
|
||||
root.cmd( 'route add -net ' + route + ' dev ' + str( intf ) )
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def sshd( network, cmd='/usr/sbin/sshd', opts='-D',
|
||||
ip='10.123.123.1/32', routes=None, switch=None ):
|
||||
"""Start a network, connect it to root ns, and run sshd on all hosts.
|
||||
@@ -73,6 +74,7 @@ def sshd( network, cmd='/usr/sbin/sshd', opts='-D',
|
||||
host.cmd( 'kill %' + cmd )
|
||||
network.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info')
|
||||
net = TreeNet( depth=1, fanout=4 )
|
||||
|
||||
@@ -5,8 +5,9 @@ Tests for baresshd.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
from mininet.clean import cleanup, sh
|
||||
from sys import stdout
|
||||
|
||||
class testBareSSHD( unittest.TestCase ):
|
||||
|
||||
@@ -14,7 +15,9 @@ class testBareSSHD( unittest.TestCase ):
|
||||
|
||||
def connected( self ):
|
||||
"Log into ssh server, check banner, then exit"
|
||||
p = pexpect.spawn( 'ssh 10.0.0.1 -o StrictHostKeyChecking=no -i /tmp/ssh/test_rsa exit' )
|
||||
p = pexpect.spawn( 'ssh 10.0.0.1 -o ConnectTimeout=1 '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-i /tmp/ssh/test_rsa exit' )
|
||||
while True:
|
||||
index = p.expect( self.opts )
|
||||
if index == 0:
|
||||
@@ -22,6 +25,7 @@ class testBareSSHD( unittest.TestCase ):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def setUp( self ):
|
||||
# verify that sshd is not running
|
||||
self.assertFalse( self.connected() )
|
||||
@@ -55,7 +59,7 @@ class testBareSSHD( unittest.TestCase ):
|
||||
|
||||
def tearDown( self ):
|
||||
# kill the ssh process
|
||||
sh( "ps aux | grep 'ssh.*Banner' | awk '{ print $2 }' | xargs kill" )
|
||||
sh( "ps aux | grep ssh |grep Banner| awk '{ print $2 }' | xargs kill" )
|
||||
cleanup()
|
||||
# remove public key pair
|
||||
sh( 'rm -rf /tmp/ssh' )
|
||||
|
||||
@@ -5,7 +5,7 @@ Tests for bind.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testBind( unittest.TestCase ):
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ A simple sanity check test for cluster edition
|
||||
'''
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class clusterSanityCheck( unittest.TestCase ):
|
||||
|
||||
@@ -14,6 +14,8 @@ class clusterSanityCheck( unittest.TestCase ):
|
||||
def testClusterPingAll( self ):
|
||||
p = pexpect.spawn( 'python -m mininet.examples.clusterSanity' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'py net.waitConnected()' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
|
||||
@@ -5,7 +5,7 @@ Tests for controllers.py and controllers2.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testControllers( unittest.TestCase ):
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ Test for controlnet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
from sys import stdout
|
||||
|
||||
class testControlNet( unittest.TestCase ):
|
||||
|
||||
@@ -13,7 +15,7 @@ class testControlNet( unittest.TestCase ):
|
||||
|
||||
def testPingall( self ):
|
||||
"Simple pingall test that verifies 0% packet drop in data network"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.controlnet' )
|
||||
p = pexpect.spawn( 'python -m mininet.examples.controlnet', logfile=stdout)
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
@@ -26,9 +28,9 @@ class testControlNet( unittest.TestCase ):
|
||||
def testFailover( self ):
|
||||
"Kill controllers and verify that switch, s1, fails over properly"
|
||||
count = 1
|
||||
p = pexpect.spawn( 'python -m mininet.examples.controlnet' )
|
||||
p = pexpect.spawn( 'python -m mininet.examples.controlnet', logfile=stdout )
|
||||
p.expect( self.prompt )
|
||||
lp = pexpect.spawn( 'tail -f /tmp/s1-ofp.log' )
|
||||
lp = pexpect.spawn( 'tail -f /tmp/s1-ofp.log', logfile=stdout )
|
||||
lp.expect( 'tcp:\d+\.\d+\.\d+\.(\d+):\d+: connected' )
|
||||
ip = int( lp.match.group( 1 ) )
|
||||
self.assertEqual( count, ip )
|
||||
|
||||
@@ -15,7 +15,7 @@ cfs 10% 1.29e+09
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testCPU( unittest.TestCase ):
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for emptynet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testEmptyNet( unittest.TestCase ):
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Test for hwintf.py
|
||||
import unittest
|
||||
import re
|
||||
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.node import Node
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for intfOptions.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testIntfOptions( unittest.TestCase ):
|
||||
@@ -13,7 +13,7 @@ class testIntfOptions( unittest.TestCase ):
|
||||
def testIntfOptions( self ):
|
||||
"verify that intf.config is correctly limiting traffic"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.intfoptions ' )
|
||||
tolerance = .2 # plus or minus 20%
|
||||
tolerance = .25 # plus or minus 25% for cloud CI tests
|
||||
opts = [ "Results: \['([\d\.]+) .bits/sec",
|
||||
"Results: \['10M', '([\d\.]+) .bits/sec",
|
||||
"h(\d+)->h(\d+): (\d)/(\d),"
|
||||
@@ -22,7 +22,7 @@ class testIntfOptions( unittest.TestCase ):
|
||||
while True:
|
||||
index = p.expect( opts, timeout=600 )
|
||||
if index == 0:
|
||||
BW = 5
|
||||
BW = 10
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertGreaterEqual( bw, BW * ( 1 - tolerance ) )
|
||||
self.assertLessEqual( bw, BW * ( 1 + tolerance ) )
|
||||
@@ -30,8 +30,10 @@ class testIntfOptions( unittest.TestCase ):
|
||||
BW = 10
|
||||
measuredBw = float( p.match.group( 1 ) )
|
||||
loss = ( measuredBw / BW ) * 100
|
||||
self.assertGreaterEqual( loss, 50 * ( 1 - tolerance ) )
|
||||
self.assertLessEqual( loss, 50 * ( 1 + tolerance ) )
|
||||
self.assertGreaterEqual( loss, 50 * ( 1 - tolerance ),
|
||||
'loss of %d%% << 50%%' % loss )
|
||||
self.assertLessEqual( loss, 50 * ( 1 + tolerance ),
|
||||
'loss of %d%% >> 50%%' % loss )
|
||||
elif index == 2:
|
||||
delay = float( p.match.group( 6 ) )
|
||||
self.assertGreaterEqual( delay, 15 * ( 1 - tolerance ) )
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for limit.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testLimit( unittest.TestCase ):
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for linearbandwidth.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testLinearBandwidth( unittest.TestCase ):
|
||||
|
||||
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@ Test for linuxrouter.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
from mininet.util import quietRun
|
||||
|
||||
class testLinuxRouter( unittest.TestCase ):
|
||||
|
||||
@@ -6,7 +6,7 @@ validates mininet interfaces against systems interfaces
|
||||
'''
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testMultiLink( unittest.TestCase ):
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for multiping.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
from collections import defaultdict
|
||||
|
||||
class testMultiPing( unittest.TestCase ):
|
||||
@@ -31,18 +31,20 @@ class testMultiPing( unittest.TestCase ):
|
||||
target = p.match.group(3)
|
||||
received = int( p.match.group(4) )
|
||||
if target == '10.0.0.200':
|
||||
self.assertEqual( received, 0 )
|
||||
self.assertEqual( received, 0, p.match.group(0) + '\n' +
|
||||
target + ' received %d != 0 packets' % received )
|
||||
else:
|
||||
self.assertEqual( received, 1 )
|
||||
self.assertEqual( received, 1, p.match.group(0) + '\n' +
|
||||
target + ' received %d != 1 packets' % received )
|
||||
try:
|
||||
pings[ name ].remove( target )
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
self.assertTrue( len( pings ) > 0 )
|
||||
self.assertTrue( len( pings ) > 0, 'too few pings' )
|
||||
for t in pings.values():
|
||||
self.assertEqual( len( t ), 0 )
|
||||
self.assertEqual( len( t ), 0, 'missed ping target(s): %s' % t )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for multipoll.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testMultiPoll( unittest.TestCase ):
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for multitest.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testMultiTest( unittest.TestCase ):
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for nat.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
from mininet.util import quietRun
|
||||
|
||||
destIP = '8.8.8.8' # Google DNS
|
||||
|
||||
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@ Test for natnet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
from mininet.util import quietRun
|
||||
|
||||
class testNATNet( unittest.TestCase ):
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for numberedports.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
from collections import defaultdict
|
||||
from mininet.node import OVSSwitch
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for popen.py and popenpoll.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testPopen( unittest.TestCase ):
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for scratchnet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testScratchNet( unittest.TestCase ):
|
||||
|
||||
@@ -16,6 +16,7 @@ class testScratchNet( unittest.TestCase ):
|
||||
p = pexpect.spawn( 'python -m %s' % name )
|
||||
index = p.expect( self.opts, timeout=120 )
|
||||
self.assertEqual( index, 0 )
|
||||
p.wait()
|
||||
|
||||
def testPingKernel( self ):
|
||||
self.pingTest( 'mininet.examples.scratchnet' )
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for simpleperf.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
@@ -18,10 +18,13 @@ class testSimplePerf( unittest.TestCase ):
|
||||
"Run the example and verify iperf results"
|
||||
# 10 Mb/s, plus or minus 20% tolerance
|
||||
BW = 10
|
||||
TOLERANCE = .2
|
||||
p = pexpect.spawn( 'python -m mininet.examples.simpleperf testmode' )
|
||||
TOLERANCE = .2
|
||||
p = pexpect.spawn(
|
||||
'python -m mininet.examples.simpleperf testmode' )
|
||||
# Log since this seems to be failing intermittently
|
||||
p.logfile = sys.stdout
|
||||
# check iperf results
|
||||
p.expect( "Results: \['10M', '([\d\.]+) .bits/sec", timeout=480 )
|
||||
p.expect( "Results: \['10M', '([\d\.]+) .bits/sec", timeout=90 )
|
||||
measuredBw = float( p.match.group( 1 ) )
|
||||
lowerBound = BW * ( 1 - TOLERANCE )
|
||||
upperBound = BW + ( 1 + TOLERANCE )
|
||||
@@ -30,5 +33,5 @@ class testSimplePerf( unittest.TestCase ):
|
||||
p.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
setLogLevel( 'debug' )
|
||||
unittest.main()
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for sshd.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
from mininet.clean import sh
|
||||
|
||||
class testSSHD( unittest.TestCase ):
|
||||
@@ -16,7 +16,8 @@ class testSSHD( unittest.TestCase ):
|
||||
"Log into ssh server, check banner, then exit"
|
||||
# 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 )
|
||||
ssh = 'ssh -o StrictHostKeyChecking=no -i /tmp/ssh/test_rsa ' + ip
|
||||
p = pexpect.spawn( ssh, timeout=5 )
|
||||
while True:
|
||||
index = p.expect( self.opts )
|
||||
if index == 0:
|
||||
@@ -57,4 +58,3 @@ class testSSHD( unittest.TestCase ):
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for tree1024.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testTree1024( unittest.TestCase ):
|
||||
|
||||
@@ -5,7 +5,7 @@ Test for treeping64.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testTreePing64( unittest.TestCase ):
|
||||
|
||||
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@ Test for vlanhost.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
from mininet.util import quietRun
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Create a 1024-host network, and run the CLI on it.
|
||||
@@ -11,8 +11,10 @@ from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.node import OVSSwitch
|
||||
from mininet.topolib import TreeNet
|
||||
from mininet.examples.treeping64 import HostV4
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
network = TreeNet( depth=2, fanout=32, switch=OVSSwitch )
|
||||
network = TreeNet( depth=2, fanout=32, host=HostV4,
|
||||
switch=OVSSwitch, waitConnected=True)
|
||||
network.run( CLI, network )
|
||||
|
||||
+19
-8
@@ -1,24 +1,34 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"Create a 64-node tree network, and test connectivity using ping."
|
||||
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch # , KernelSwitch
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch, Host
|
||||
from mininet.topolib import TreeNet
|
||||
|
||||
|
||||
class HostV4( Host ):
|
||||
"Try to IPv6 and its awful neighbor discovery"
|
||||
def __init__( self, *args, **kwargs ):
|
||||
super( HostV4, self ).__init__( *args, **kwargs )
|
||||
cfgs = [ 'all.disable_ipv6=1', 'default.disable_ipv6=1',
|
||||
'default.autoconf=0', 'lo.autoconf=0' ]
|
||||
for cfg in cfgs:
|
||||
self.cmd( 'sysctl -w net.ipv6.conf.' + cfg )
|
||||
|
||||
|
||||
def treePing64():
|
||||
"Run ping test on 64-node tree networks."
|
||||
|
||||
results = {}
|
||||
switches = { # 'reference kernel': KernelSwitch,
|
||||
'reference user': UserSwitch,
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
switches = { 'reference user': UserSwitch,
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
|
||||
for name in switches:
|
||||
for name, switch in switches.items():
|
||||
info( "*** Testing", name, "datapath\n" )
|
||||
switch = switches[ name ]
|
||||
network = TreeNet( depth=2, fanout=8, switch=switch )
|
||||
network = TreeNet( depth=2, fanout=8, switch=switch,
|
||||
waitConnected=True )
|
||||
result = network.run( network.pingAll )
|
||||
results[ name ] = result
|
||||
|
||||
@@ -27,6 +37,7 @@ def treePing64():
|
||||
info( "%s: %d%% packet loss\n" % ( name, results[ name ] ) )
|
||||
info( '\n' )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
treePing64()
|
||||
|
||||
@@ -24,14 +24,18 @@ Usage (example uses VLAN ID=1000):
|
||||
|
||||
"""
|
||||
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
|
||||
from mininet.node import Host
|
||||
from mininet.topo import Topo
|
||||
from mininet.util import quietRun
|
||||
from mininet.log import error
|
||||
|
||||
|
||||
class VLANHost( Host ):
|
||||
"Host connected to VLAN interface"
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def config( self, vlan=100, **params ):
|
||||
"""Configure VLANHost according to (optional) parameters:
|
||||
vlan: VLAN ID for default interface"""
|
||||
@@ -54,6 +58,7 @@ class VLANHost( Host ):
|
||||
|
||||
return r
|
||||
|
||||
|
||||
hosts = { 'vlan': VLANHost }
|
||||
|
||||
|
||||
@@ -65,7 +70,7 @@ def exampleAllHosts( vlan ):
|
||||
|
||||
# Start a basic network using our VLANHost
|
||||
topo = SingleSwitchTopo( k=2 )
|
||||
net = Mininet( host=host, topo=topo )
|
||||
net = Mininet( host=host, topo=topo, waitConnected=True )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
@@ -96,11 +101,12 @@ class VLANStarTopo( Topo ):
|
||||
def exampleCustomTags():
|
||||
"""Simple example that exercises VLANStarTopo"""
|
||||
|
||||
net = Mininet( topo=VLANStarTopo() )
|
||||
net = Mininet( topo=VLANStarTopo(), waitConnected=True )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../bin/mn
|
||||
+8
-6
@@ -16,12 +16,15 @@ import time
|
||||
|
||||
from mininet.log import info
|
||||
from mininet.term import cleanUpScreens
|
||||
|
||||
from mininet.util import decode
|
||||
|
||||
def sh( cmd ):
|
||||
"Print a command and send it to the shell"
|
||||
info( cmd + '\n' )
|
||||
return Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ]
|
||||
p = Popen( # pylint: disable=consider-using-with
|
||||
[ '/bin/sh', '-c', cmd ], stdout=PIPE )
|
||||
result = p.communicate()[ 0 ]
|
||||
return decode( result )
|
||||
|
||||
def killprocs( pattern ):
|
||||
"Reliably terminate processes matching a pattern (including args)"
|
||||
@@ -50,9 +53,9 @@ class Cleanup( object ):
|
||||
|
||||
info( "*** Removing excess controllers/ofprotocols/ofdatapaths/"
|
||||
"pings/noxes\n" )
|
||||
zombies = ( 'controller ofprotocol ofdatapath ping nox_core'
|
||||
'lt-nox_core ovs-openflowd ovs-controller'
|
||||
'ovs-testcontroller udpbwtest mnexec ivs ryu-manager' )
|
||||
zombies = ( 'controller ofprotocol ofdatapath ping nox_core '
|
||||
'lt-nox_core ovs-openflowd ovs-controller '
|
||||
'ovs-testcontroller udpbwtest mnexec ivs ryu-manager ' )
|
||||
# Note: real zombie processes can't actually be killed, since they
|
||||
# are already (un)dead. Then again,
|
||||
# you can't connect to them either, so they're mostly harmless.
|
||||
@@ -76,7 +79,6 @@ class Cleanup( object ):
|
||||
for dp in dps:
|
||||
if dp:
|
||||
sh( 'dpctl deldp ' + dp )
|
||||
|
||||
info( "*** Removing OVS datapaths\n" )
|
||||
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
|
||||
if dps:
|
||||
|
||||
+41
-15
@@ -29,6 +29,8 @@ from subprocess import call
|
||||
from cmd import Cmd
|
||||
from os import isatty
|
||||
from select import poll, POLLIN
|
||||
import select
|
||||
import errno
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
@@ -44,7 +46,8 @@ class CLI( Cmd ):
|
||||
|
||||
prompt = 'mininet> '
|
||||
|
||||
def __init__( self, mininet, stdin=sys.stdin, script=None ):
|
||||
def __init__( self, mininet, stdin=sys.stdin, script=None,
|
||||
**kwargs ):
|
||||
"""Start and run interactive or batch mode CLI
|
||||
mininet: Mininet network object
|
||||
stdin: standard input for CLI
|
||||
@@ -53,11 +56,10 @@ class CLI( Cmd ):
|
||||
# Local variable bindings for py command
|
||||
self.locals = { 'net': mininet }
|
||||
# Attempt to handle input
|
||||
self.stdin = stdin
|
||||
self.inPoller = poll()
|
||||
self.inPoller.register( stdin )
|
||||
self.inputFile = script
|
||||
Cmd.__init__( self )
|
||||
Cmd.__init__( self, stdin=stdin, **kwargs )
|
||||
info( '*** Starting CLI:\n' )
|
||||
|
||||
if self.inputFile:
|
||||
@@ -77,6 +79,7 @@ class CLI( Cmd ):
|
||||
return
|
||||
cls.readlineInited = True
|
||||
try:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from readline import ( read_history_file, write_history_file,
|
||||
set_history_length )
|
||||
except ImportError:
|
||||
@@ -86,7 +89,15 @@ class CLI( Cmd ):
|
||||
if os.path.isfile( history_path ):
|
||||
read_history_file( history_path )
|
||||
set_history_length( 1000 )
|
||||
atexit.register( lambda: write_history_file( history_path ) )
|
||||
|
||||
def writeHistory():
|
||||
"Write out history file"
|
||||
try:
|
||||
write_history_file( history_path )
|
||||
except IOError:
|
||||
# Ignore probably spurious IOError
|
||||
pass
|
||||
atexit.register( writeHistory )
|
||||
|
||||
def run( self ):
|
||||
"Run our cmdloop(), catching KeyboardInterrupt"
|
||||
@@ -139,10 +150,10 @@ class CLI( Cmd ):
|
||||
' mininet> xterm h2\n\n'
|
||||
)
|
||||
|
||||
def do_help( self, line ):
|
||||
def do_help( self, line ): # pylint: disable=arguments-renamed
|
||||
"Describe available CLI commands."
|
||||
Cmd.do_help( self, line )
|
||||
if line is '':
|
||||
if line == '':
|
||||
output( self.helpStr )
|
||||
|
||||
def do_nodes( self, _line ):
|
||||
@@ -171,8 +182,9 @@ class CLI( Cmd ):
|
||||
"""Evaluate a Python expression.
|
||||
Node names may be used, e.g.: py h1.cmd('ls')"""
|
||||
try:
|
||||
# pylint: disable=eval-used
|
||||
result = eval( line, globals(), self.getLocals() )
|
||||
if not result:
|
||||
if result is None:
|
||||
return
|
||||
elif isinstance( result, str ):
|
||||
output( result + '\n' )
|
||||
@@ -340,13 +352,13 @@ class CLI( Cmd ):
|
||||
error( 'usage: source <file>\n' )
|
||||
return
|
||||
try:
|
||||
self.inputFile = open( args[ 0 ] )
|
||||
while True:
|
||||
line = self.inputFile.readline()
|
||||
if len( line ) > 0:
|
||||
self.onecmd( line )
|
||||
else:
|
||||
break
|
||||
with open( args[ 0 ] ) as self.inputFile:
|
||||
while True:
|
||||
line = self.inputFile.readline()
|
||||
if len( line ) > 0:
|
||||
self.onecmd( line )
|
||||
else:
|
||||
break
|
||||
except IOError:
|
||||
error( 'error reading file %s\n' % args[ 0 ] )
|
||||
self.inputFile.close()
|
||||
@@ -397,6 +409,10 @@ class CLI( Cmd ):
|
||||
error( 'invalid command: '
|
||||
'switch <switch name> {start, stop}\n' )
|
||||
|
||||
def do_wait( self, _line ):
|
||||
"Wait until all switches have connected to a controller"
|
||||
self.mn.waitConnected()
|
||||
|
||||
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
|
||||
@@ -440,12 +456,14 @@ class CLI( Cmd ):
|
||||
try:
|
||||
bothPoller.poll()
|
||||
# XXX BL: this doesn't quite do what we want.
|
||||
# pylint: disable=condition-evals-to-constant
|
||||
if False and self.inputFile:
|
||||
key = self.inputFile.read( 1 )
|
||||
if key is not '':
|
||||
if key != '':
|
||||
node.write( key )
|
||||
else:
|
||||
self.inputFile = None
|
||||
# pylint: enable=condition-evals-to-constant
|
||||
if isReadable( self.inPoller ):
|
||||
key = self.stdin.read( 1 )
|
||||
node.write( key )
|
||||
@@ -459,6 +477,13 @@ class CLI( Cmd ):
|
||||
# it's possible to interrupt ourselves after we've
|
||||
# read data but before it has been printed.
|
||||
node.sendInt()
|
||||
except select.error as e:
|
||||
# pylint: disable=unpacking-non-sequence
|
||||
# pylint: disable=unbalanced-tuple-unpacking
|
||||
errno_, errmsg = e.args
|
||||
if errno_ != errno.EINTR:
|
||||
error( "select.error: %s, %s" % (errno_, errmsg) )
|
||||
node.sendInt()
|
||||
|
||||
def precmd( self, line ):
|
||||
"allow for comments in the cli"
|
||||
@@ -475,3 +500,4 @@ def isReadable( poller ):
|
||||
mask = fdmask[ 1 ]
|
||||
if mask & POLLIN:
|
||||
return True
|
||||
return False
|
||||
|
||||
+38
-33
@@ -24,9 +24,14 @@ TCIntf: interface with bandwidth limiting and delay via tc
|
||||
Link: basic link class for creating veth pairs
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from mininet.log import info, error, debug
|
||||
from mininet.util import makeIntfPair
|
||||
import re
|
||||
|
||||
# Make pylint happy:
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
class Intf( object ):
|
||||
|
||||
@@ -146,6 +151,9 @@ class Intf( object ):
|
||||
|
||||
def rename( self, newname ):
|
||||
"Rename interface"
|
||||
if self.node and self.name in self.node.nameToIntf:
|
||||
# rename intf in node's nameToIntf
|
||||
self.node.nameToIntf[newname] = self.node.nameToIntf.pop(self.name)
|
||||
self.ifconfig( 'down' )
|
||||
result = self.cmd( 'ip link set', self.name, 'name', newname )
|
||||
self.name = newname
|
||||
@@ -164,10 +172,10 @@ class Intf( object ):
|
||||
method: config method name
|
||||
param: arg=value (ignore if value=None)
|
||||
value may also be list or dict"""
|
||||
name, value = param.items()[ 0 ]
|
||||
name, value = list( param.items() )[ 0 ]
|
||||
f = getattr( self, method, None )
|
||||
if not f or value is None:
|
||||
return
|
||||
return None
|
||||
if isinstance( value, list ):
|
||||
result = f( *value )
|
||||
elif isinstance( value, dict ):
|
||||
@@ -285,18 +293,14 @@ class TCIntf( Intf ):
|
||||
loss=None, max_queue_size=None ):
|
||||
"Internal method: return tc commands for delay and loss"
|
||||
cmds = []
|
||||
if delay and delay < 0:
|
||||
error( 'Negative delay', delay, '\n' )
|
||||
elif jitter and jitter < 0:
|
||||
error( 'Negative jitter', jitter, '\n' )
|
||||
elif loss and ( loss < 0 or loss > 100 ):
|
||||
if loss and ( loss < 0 or loss > 100 ):
|
||||
error( 'Bad loss percentage', loss, '%%\n' )
|
||||
else:
|
||||
# Delay/jitter/loss/max queue size
|
||||
netemargs = '%s%s%s%s' % (
|
||||
'delay %s ' % delay if delay is not None else '',
|
||||
'%s ' % jitter if jitter is not None else '',
|
||||
'loss %.5f ' % loss if loss is not None else '',
|
||||
'loss %.5f ' % loss if (loss is not None and loss > 0) else '',
|
||||
'limit %d' % max_queue_size if max_queue_size is not None
|
||||
else '' )
|
||||
if netemargs:
|
||||
@@ -312,7 +316,9 @@ class TCIntf( Intf ):
|
||||
debug(" *** executing command: %s\n" % c)
|
||||
return self.cmd( c )
|
||||
|
||||
def config( self, bw=None, delay=None, jitter=None, loss=None,
|
||||
def config( # pylint: disable=arguments-renamed,arguments-differ
|
||||
self,
|
||||
bw=None, delay=None, jitter=None, loss=None,
|
||||
gro=False, txo=True, rxo=True,
|
||||
speedup=0, use_hfsc=False, use_tbf=False,
|
||||
latency_ms=None, enable_ecn=False, enable_red=False,
|
||||
@@ -352,7 +358,7 @@ class TCIntf( Intf ):
|
||||
# Question: what happens if we want to reset things?
|
||||
if ( bw is None and not delay and not loss
|
||||
and max_queue_size is None ):
|
||||
return
|
||||
return None
|
||||
|
||||
# Clear existing configuration
|
||||
tcoutput = self.tc( '%s qdisc show dev %s' )
|
||||
@@ -408,7 +414,7 @@ class Link( object ):
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None, addr1=None, addr2=None,
|
||||
intf=Intf, cls1=None, cls2=None, params1=None,
|
||||
params2=None, fast=True ):
|
||||
params2=None, fast=True, **params ):
|
||||
"""Create veth link to another node, making two new interfaces.
|
||||
node1: first node
|
||||
node2: second node
|
||||
@@ -418,18 +424,15 @@ class Link( object ):
|
||||
cls1, cls2: optional interface-specific constructors
|
||||
intfName1: node1 interface name (optional)
|
||||
intfName2: node2 interface name (optional)
|
||||
params1: parameters for interface 1
|
||||
params2: parameters for interface 2"""
|
||||
params1: parameters for interface 1 (optional)
|
||||
params2: parameters for interface 2 (optional)
|
||||
**params: additional parameters for both interfaces"""
|
||||
|
||||
# This is a bit awkward; it seems that having everything in
|
||||
# params is more orthogonal, but being able to specify
|
||||
# in-line arguments is more convenient! So we support both.
|
||||
if params1 is None:
|
||||
params1 = {}
|
||||
if params2 is None:
|
||||
params2 = {}
|
||||
# Allow passing in params1=params2
|
||||
if params2 is params1:
|
||||
params2 = dict( params1 )
|
||||
params1 = dict( params1 ) if params1 else {}
|
||||
params2 = dict( params2 ) if params2 else {}
|
||||
if port1 is not None:
|
||||
params1[ 'port' ] = port1
|
||||
if port2 is not None:
|
||||
@@ -443,6 +446,10 @@ class Link( object ):
|
||||
if not intfName2:
|
||||
intfName2 = self.intfName( node2, params2[ 'port' ] )
|
||||
|
||||
# Update with remaining parameter list
|
||||
params1.update( params )
|
||||
params2.update( params )
|
||||
|
||||
self.fast = fast
|
||||
if fast:
|
||||
params1.setdefault( 'moveIntfFn', self._ignore )
|
||||
@@ -464,6 +471,7 @@ class Link( object ):
|
||||
|
||||
# All we are is dust in the wind, and our two interfaces
|
||||
self.intf1, self.intf2 = intf1, intf2
|
||||
|
||||
# pylint: enable=too-many-branches
|
||||
|
||||
@staticmethod
|
||||
@@ -532,7 +540,9 @@ class OVSLink( Link ):
|
||||
|
||||
def __init__( self, node1, node2, **kwargs ):
|
||||
"See Link.__init__() for options"
|
||||
from mininet.node import OVSSwitch
|
||||
if 'OVSSwitch' not in globals():
|
||||
# pylint: disable=import-outside-toplevel,cyclic-import
|
||||
from mininet.node import OVSSwitch
|
||||
self.isPatchLink = False
|
||||
if ( isinstance( node1, OVSSwitch ) and
|
||||
isinstance( node2, OVSSwitch ) ):
|
||||
@@ -540,6 +550,7 @@ class OVSLink( Link ):
|
||||
kwargs.update( cls1=OVSIntf, cls2=OVSIntf )
|
||||
Link.__init__( self, node1, node2, **kwargs )
|
||||
|
||||
# pylint: disable=arguments-renamed, arguments-differ, signature-differs
|
||||
def makeIntfPair( self, *args, **kwargs ):
|
||||
"Usually delegated to OVSSwitch"
|
||||
if self.isPatchLink:
|
||||
@@ -549,17 +560,11 @@ class OVSLink( Link ):
|
||||
|
||||
|
||||
class TCLink( Link ):
|
||||
"Link with symmetric TC interfaces configured via opts"
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None,
|
||||
addr1=None, addr2=None, **params ):
|
||||
Link.__init__( self, node1, node2, port1=port1, port2=port2,
|
||||
intfName1=intfName1, intfName2=intfName2,
|
||||
cls1=TCIntf,
|
||||
cls2=TCIntf,
|
||||
addr1=addr1, addr2=addr2,
|
||||
params1=params,
|
||||
params2=params )
|
||||
"Link with TC interfaces"
|
||||
def __init__( self, *args, **kwargs):
|
||||
kwargs.setdefault( 'cls1', TCIntf )
|
||||
kwargs.setdefault( 'cls2', TCIntf )
|
||||
Link.__init__( self, *args, **kwargs)
|
||||
|
||||
|
||||
class TCULink( TCLink ):
|
||||
|
||||
+23
-28
@@ -4,6 +4,7 @@ import logging
|
||||
from logging import Logger
|
||||
import types
|
||||
|
||||
|
||||
# Create a new loglevel, 'CLI info', which enables a Mininet user to see only
|
||||
# the output of the commands they execute, plus any errors or warnings. This
|
||||
# level is in between info and warning. CLI info-level commands should not be
|
||||
@@ -14,13 +15,14 @@ LEVELS = { 'debug': logging.DEBUG,
|
||||
'info': logging.INFO,
|
||||
'output': OUTPUT,
|
||||
'warning': logging.WARNING,
|
||||
'warn': logging.WARNING,
|
||||
'error': logging.ERROR,
|
||||
'critical': logging.CRITICAL }
|
||||
|
||||
# change this to logging.INFO to get printouts when running unit tests
|
||||
LOGLEVELDEFAULT = OUTPUT
|
||||
|
||||
#default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
# default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
LOGMSGFORMAT = '%(message)s'
|
||||
|
||||
|
||||
@@ -51,7 +53,7 @@ class StreamHandlerNoNewline( logging.StreamHandler ):
|
||||
self.flush()
|
||||
except ( KeyboardInterrupt, SystemExit ):
|
||||
raise
|
||||
except:
|
||||
except: # noqa pylint: disable=bare-except
|
||||
self.handleError( record )
|
||||
|
||||
|
||||
@@ -95,9 +97,9 @@ class MininetLogger( Logger, object ):
|
||||
|
||||
__metaclass__ = Singleton
|
||||
|
||||
def __init__( self ):
|
||||
def __init__( self, name="mininet" ):
|
||||
|
||||
Logger.__init__( self, "mininet" )
|
||||
Logger.__init__( self, name )
|
||||
|
||||
# create console handler
|
||||
ch = StreamHandlerNoNewline()
|
||||
@@ -105,30 +107,22 @@ class MininetLogger( Logger, object ):
|
||||
formatter = logging.Formatter( LOGMSGFORMAT )
|
||||
# add formatter to ch
|
||||
ch.setFormatter( formatter )
|
||||
# add ch to lg
|
||||
# add ch to lg and initialize log level
|
||||
self.addHandler( ch )
|
||||
|
||||
self.ch = ch
|
||||
self.setLogLevel()
|
||||
|
||||
def setLogLevel( self, levelname=None ):
|
||||
"""Setup loglevel.
|
||||
Convenience function to support lowercase names.
|
||||
levelName: level name from LEVELS"""
|
||||
level = LOGLEVELDEFAULT
|
||||
if levelname is not None:
|
||||
if levelname not in LEVELS:
|
||||
raise Exception( 'unknown levelname seen in setLogLevel' )
|
||||
else:
|
||||
level = LEVELS.get( levelname, level )
|
||||
|
||||
if levelname and levelname not in LEVELS:
|
||||
print(LEVELS)
|
||||
raise Exception( 'setLogLevel: unknown levelname %s' % levelname )
|
||||
level = LEVELS.get( levelname, LOGLEVELDEFAULT )
|
||||
self.setLevel( level )
|
||||
self.handlers[ 0 ].setLevel( level )
|
||||
self.ch.setLevel( level )
|
||||
|
||||
# pylint: disable=method-hidden
|
||||
# "An attribute inherited from mininet.log hide this method" (sic)
|
||||
# Not sure why this is occurring - this function definitely gets called.
|
||||
|
||||
# See /usr/lib/python2.5/logging/__init__.py; modified from warning()
|
||||
def output( self, msg, *args, **kwargs ):
|
||||
"""Log 'msg % args' with severity 'OUTPUT'.
|
||||
|
||||
@@ -137,14 +131,11 @@ class MininetLogger( Logger, object ):
|
||||
|
||||
logger.warning("Houston, we have a %s", "cli output", exc_info=1)
|
||||
"""
|
||||
if self.manager.disable >= OUTPUT:
|
||||
if getattr( self.manager, 'disabled', 0 ) >= OUTPUT:
|
||||
return
|
||||
if self.isEnabledFor( OUTPUT ):
|
||||
self._log( OUTPUT, msg, args, kwargs )
|
||||
|
||||
# pylint: enable=method-hidden
|
||||
|
||||
lg = MininetLogger()
|
||||
|
||||
# Make things a bit more convenient by adding aliases
|
||||
# (info, warn, error, debug) and allowing info( 'this', 'is', 'OK' )
|
||||
@@ -168,10 +159,14 @@ def makeListCompatible( fn ):
|
||||
setattr( newfn, '__doc__', fn.__doc__ )
|
||||
return newfn
|
||||
|
||||
_loggers = lg.info, lg.output, lg.warn, lg.error, lg.debug
|
||||
_loggers = tuple( makeListCompatible( logger )
|
||||
for logger in _loggers )
|
||||
lg.info, lg.output, lg.warn, lg.error, lg.debug = _loggers
|
||||
info, output, warn, error, debug = _loggers
|
||||
|
||||
# Initialize logger and logging functions
|
||||
|
||||
logging.setLoggerClass( MininetLogger )
|
||||
lg = logging.getLogger( "mininet" )
|
||||
_loggers = lg.info, lg.output, lg.warning, lg.error, lg.debug
|
||||
_loggers = tuple( makeListCompatible( logger ) for logger in _loggers )
|
||||
lg.info, lg.output, lg.warning, lg.error, lg.debug = _loggers
|
||||
info, output, warning, error, debug = _loggers
|
||||
warn = warning # alternate/old name
|
||||
setLogLevel = lg.setLogLevel
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"Module dependency utility functions for Mininet."
|
||||
|
||||
from mininet.util import quietRun
|
||||
from mininet.log import info, error, debug
|
||||
from os import environ
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
|
||||
from mininet.util import quietRun, BaseString
|
||||
from mininet.log import info, error, debug
|
||||
|
||||
|
||||
def lsmod():
|
||||
"Return output of lsmod."
|
||||
@@ -18,6 +21,7 @@ def modprobe( mod ):
|
||||
mod: module string"""
|
||||
return quietRun( [ 'modprobe', mod ] )
|
||||
|
||||
|
||||
OF_KMOD = 'ofdatapath'
|
||||
OVS_KMOD = 'openvswitch_mod' # Renamed 'openvswitch' in OVS 1.7+/Linux 3.5+
|
||||
TUN = 'tun'
|
||||
@@ -28,9 +32,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 isinstance( subtract, BaseString ):
|
||||
subtract = [ subtract ]
|
||||
if isinstance( add, basestring ):
|
||||
if isinstance( add, BaseString ):
|
||||
add = [ add ]
|
||||
for mod in subtract:
|
||||
if mod in lsmod():
|
||||
|
||||
+69
-46
@@ -92,27 +92,29 @@ import select
|
||||
import signal
|
||||
import random
|
||||
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
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.log import info, error, output, warn, debug
|
||||
from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
|
||||
Controller )
|
||||
from mininet.nodelib import NAT
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
|
||||
macColonHex, ipStr, ipParse, netParse, ipAdd,
|
||||
waitListening )
|
||||
waitListening, BaseString, fmtBps )
|
||||
from mininet.term import cleanUpScreens, makeTerms
|
||||
|
||||
# Mininet version: should be consistent with README and LICENSE
|
||||
VERSION = "2.3.0d1"
|
||||
VERSION = "2.3.1b4"
|
||||
|
||||
class Mininet( object ):
|
||||
"Network emulation with hosts spawned in network namespaces."
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
|
||||
controller=DefaultController, link=Link, intf=Intf,
|
||||
build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
|
||||
@@ -135,7 +137,9 @@ class Mininet( object ):
|
||||
autoStaticArp: set all-pairs static MAC addrs?
|
||||
autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
|
||||
listenPort: base listening port to open; will be incremented for
|
||||
each additional switch in the net if inNamespace=False"""
|
||||
each additional switch in the net if inNamespace=False
|
||||
waitConnected: wait for switches to Connect?
|
||||
(False; True/None=wait indefinitely; time(s)=timed wait)"""
|
||||
self.topo = topo
|
||||
self.switch = switch
|
||||
self.host = host
|
||||
@@ -174,14 +178,16 @@ class Mininet( object ):
|
||||
self.build()
|
||||
|
||||
def waitConnected( self, timeout=None, delay=.5 ):
|
||||
"""wait for each switch to connect to a controller,
|
||||
up to 5 seconds
|
||||
timeout: time to wait, or None to wait indefinitely
|
||||
"""wait for each switch to connect to a controller
|
||||
timeout: time to wait, or None or True to wait indefinitely
|
||||
delay: seconds to sleep per iteration
|
||||
returns: True if all switches are connected"""
|
||||
info( '*** Waiting for switches to connect\n' )
|
||||
time = 0
|
||||
time = 0.0
|
||||
remaining = list( self.switches )
|
||||
# False: 0s timeout; None: wait forever (preserve 2.2 behavior)
|
||||
if isinstance( timeout, bool ):
|
||||
timeout = None if timeout else 0
|
||||
while True:
|
||||
for switch in tuple( remaining ):
|
||||
if switch.connected():
|
||||
@@ -190,12 +196,12 @@ class Mininet( object ):
|
||||
if not remaining:
|
||||
info( '\n' )
|
||||
return True
|
||||
if time > timeout and timeout is not None:
|
||||
if timeout is not None and time >= timeout:
|
||||
break
|
||||
sleep( delay )
|
||||
time += delay
|
||||
warn( 'Timed out after %d seconds\n' % time )
|
||||
for switch in remaining:
|
||||
for switch in remaining.copy():
|
||||
if not switch.connected():
|
||||
warn( 'Warning: %s is not connected to a controller\n'
|
||||
% switch.name )
|
||||
@@ -383,8 +389,8 @@ class Mininet( object ):
|
||||
params: additional link params (optional)
|
||||
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 ]
|
||||
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:
|
||||
@@ -437,7 +443,7 @@ class Mininet( object ):
|
||||
host.configDefault( ip=None, mac=None )
|
||||
# You're low priority, dude!
|
||||
# BL: do we want to do this here or not?
|
||||
# May not make sense if we have CPU lmiting...
|
||||
# May not make sense if we have CPU limiting...
|
||||
# quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||||
# This may not be the right place to do this, but
|
||||
# it needs to be done somewhere.
|
||||
@@ -549,14 +555,15 @@ class Mininet( object ):
|
||||
switch.start( self.controllers )
|
||||
started = {}
|
||||
for swclass, switches in groupby(
|
||||
sorted( self.switches, key=type ), type ):
|
||||
sorted( self.switches,
|
||||
key=lambda s: str( type( s ) ) ), type ):
|
||||
switches = tuple( switches )
|
||||
if hasattr( swclass, 'batchStartup' ):
|
||||
success = swclass.batchStartup( switches )
|
||||
started.update( { s: s for s in success } )
|
||||
info( '\n' )
|
||||
if self.waitConn:
|
||||
self.waitConnected()
|
||||
self.waitConnected( self.waitConn )
|
||||
|
||||
def stop( self ):
|
||||
"Stop the controller(s), switches and hosts"
|
||||
@@ -565,6 +572,10 @@ class Mininet( object ):
|
||||
info( controller.name + ' ' )
|
||||
controller.stop()
|
||||
info( '\n' )
|
||||
# Unlimit cfs hosts to speed up shutdown
|
||||
for h in self.hosts:
|
||||
if hasattr( h, 'unlimit' ):
|
||||
h.unlimit()
|
||||
if self.terms:
|
||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||
self.stopXterms()
|
||||
@@ -576,7 +587,8 @@ class Mininet( object ):
|
||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
||||
stopped = {}
|
||||
for swclass, switches in groupby(
|
||||
sorted( self.switches, key=type ), type ):
|
||||
sorted( self.switches,
|
||||
key=lambda s: str( type( s ) ) ), type ):
|
||||
switches = tuple( switches )
|
||||
if hasattr( swclass, 'batchShutdown' ):
|
||||
success = swclass.batchShutdown( switches )
|
||||
@@ -663,7 +675,7 @@ class Mininet( object ):
|
||||
if timeout:
|
||||
opts = '-W %s' % timeout
|
||||
if dest.intfs:
|
||||
result = node.cmd( 'ping -c1 %s %s' %
|
||||
result = node.cmd( 'LANG=C ping -c1 %s %s' %
|
||||
(opts, dest.IP()) )
|
||||
sent, received = self._parsePing( result )
|
||||
else:
|
||||
@@ -774,18 +786,26 @@ class Mininet( object ):
|
||||
return self.pingFull( hosts=hosts )
|
||||
|
||||
@staticmethod
|
||||
def _parseIperf( iperfOutput ):
|
||||
"""Parse iperf output and return bandwidth.
|
||||
iperfOutput: string
|
||||
returns: result string"""
|
||||
r = r'([\d\.]+ \w+/sec)'
|
||||
m = re.findall( r, iperfOutput )
|
||||
if m:
|
||||
return m[-1]
|
||||
else:
|
||||
# was: raise Exception(...)
|
||||
error( 'could not parse iperf output: ' + iperfOutput )
|
||||
return ''
|
||||
def _iperfVals( iperfcsv, serverip ):
|
||||
"""Return iperf CSV as dict
|
||||
iperfcsv: iperf -y C output
|
||||
serverip: iperf server IP address
|
||||
"""
|
||||
fields = 'date cip cport sip sport ipver interval sent rate'
|
||||
lines = iperfcsv.strip().split('\n')
|
||||
svals = {}
|
||||
for line in lines:
|
||||
if ',' not in line:
|
||||
continue
|
||||
line = line.split( ',' )
|
||||
svals = dict( zip( fields.split(), line ) )
|
||||
# Return client in cip:cport, server in sip:sport
|
||||
if svals[ 'cip' ] == serverip:
|
||||
svals[ 'cip' ], svals[ 'sip' ] = (
|
||||
svals[ 'sip' ], svals[ 'cip' ] )
|
||||
svals[ 'cport' ], svals[ 'sport' ] = (
|
||||
svals[ 'sport' ], svals[ 'cport' ] )
|
||||
return svals
|
||||
|
||||
# XXX This should be cleaned up
|
||||
|
||||
@@ -795,7 +815,7 @@ class Mininet( object ):
|
||||
hosts: list of hosts; if None, uses first and last hosts
|
||||
l4Type: string, one of [ TCP, UDP ]
|
||||
udpBw: bandwidth target for UDP test
|
||||
fmt: iperf format argument if any
|
||||
fmt: scale/format argument (e.g. m/M for Mbps)
|
||||
seconds: iperf time to transmit
|
||||
port: iperf port
|
||||
returns: two-element array of [ server, client ] speeds
|
||||
@@ -808,33 +828,36 @@ class Mininet( object ):
|
||||
output( '*** Iperf: testing', l4Type, 'bandwidth between',
|
||||
client, 'and', server, '\n' )
|
||||
server.cmd( 'killall -9 iperf' )
|
||||
iperfArgs = 'iperf -p %d ' % port
|
||||
# Note: CSV mode
|
||||
iperfArgs = 'iperf -y C -p %d ' % port
|
||||
bwArgs = ''
|
||||
if l4Type == 'UDP':
|
||||
iperfArgs += '-u '
|
||||
bwArgs = '-b ' + udpBw + ' '
|
||||
elif l4Type != 'TCP':
|
||||
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
||||
if fmt:
|
||||
iperfArgs += '-f %s ' % fmt
|
||||
server.sendCmd( iperfArgs + '-s' )
|
||||
serverip = server.IP()
|
||||
if l4Type == 'TCP':
|
||||
if not waitListening( client, server.IP(), port ):
|
||||
if not waitListening( client, serverip, port ):
|
||||
raise Exception( 'Could not connect to iperf on port %d'
|
||||
% port )
|
||||
cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
|
||||
server.IP() + ' ' + bwArgs )
|
||||
debug( 'Client output: %s\n' % cliout )
|
||||
servout = ''
|
||||
# We want the last *b/sec from the iperf server output
|
||||
# for TCP, there are two of them because of waitListening
|
||||
count = 2 if l4Type == 'TCP' else 1
|
||||
while len( re.findall( '/sec', servout ) ) < count:
|
||||
servout += server.monitor( timeoutms=5000 )
|
||||
cvals = self._iperfVals( cliout, serverip )
|
||||
debug( 'iperf client output:', cliout, cvals )
|
||||
serverout = ''
|
||||
# Wait for output from the client session
|
||||
while True:
|
||||
serverout += server.monitor( timeoutms=5000 )
|
||||
svals = self._iperfVals( serverout, serverip )
|
||||
# Check for the client's source/output port
|
||||
if ( svals and cvals[ 'sport' ] == svals[ 'sport' ]
|
||||
and int( svals[ 'rate' ] ) > 0 ):
|
||||
break
|
||||
debug( 'iperf server output:', serverout, svals )
|
||||
server.sendInt()
|
||||
servout += server.waitOutput()
|
||||
debug( 'Server output: %s\n' % servout )
|
||||
result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
|
||||
serverout += server.waitOutput()
|
||||
result = [ fmtBps( svals[ 'rate'], fmt ),
|
||||
fmtBps( cvals[ 'rate' ], fmt ) ]
|
||||
if l4Type == 'UDP':
|
||||
result.insert( 0, udpBw )
|
||||
output( '*** Results: %s\n' % result )
|
||||
|
||||
+150
-92
@@ -57,16 +57,22 @@ import pty
|
||||
import re
|
||||
import signal
|
||||
import select
|
||||
from re import findall
|
||||
from subprocess import Popen, PIPE
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
from time import sleep
|
||||
|
||||
from mininet.log import info, error, warn, debug
|
||||
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
|
||||
numCores, retry, mountCgroups )
|
||||
numCores, retry, mountCgroups, BaseString, decode,
|
||||
encode, getincrementaldecoder, Python3, which,
|
||||
StrictVersion )
|
||||
from mininet.moduledeps import moduleDeps, pathCheck, TUN
|
||||
from mininet.link import Link, Intf, TCIntf, OVSIntf
|
||||
from re import findall
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
class Node( object ):
|
||||
"""A virtual network node is simply a shell in a network namespace.
|
||||
@@ -87,12 +93,19 @@ class Node( object ):
|
||||
self.privateDirs = params.get( 'privateDirs', [] )
|
||||
self.inNamespace = params.get( 'inNamespace', inNamespace )
|
||||
|
||||
# Python 3 complains if we don't wait for shell exit
|
||||
self.waitExited = params.get( 'waitExited', Python3 )
|
||||
|
||||
# Stash configuration parameters for future reference
|
||||
self.params = params
|
||||
|
||||
self.intfs = {} # dict of port numbers to interfaces
|
||||
self.ports = {} # dict of interfaces to port numbers
|
||||
# replace with Port objects, eventually ?
|
||||
# dict of port numbers to interfaces
|
||||
self.intfs = {}
|
||||
|
||||
# dict of interfaces to port numbers
|
||||
# todo: replace with Port objects, eventually ?
|
||||
self.ports = {}
|
||||
|
||||
self.nameToIntf = {} # dict of interface names to Intfs
|
||||
|
||||
# Make pylint happy
|
||||
@@ -102,7 +115,11 @@ class Node( object ):
|
||||
self.waiting = False
|
||||
self.readbuf = ''
|
||||
|
||||
# Incremental decoder for buffered reading
|
||||
self.decoder = getincrementaldecoder()
|
||||
|
||||
# Start command interpreter shell
|
||||
self.master, self.slave = None, None # pylint
|
||||
self.startShell()
|
||||
self.mountPrivateDirs()
|
||||
|
||||
@@ -135,14 +152,18 @@ class Node( object ):
|
||||
# -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 ),
|
||||
'bash', '--norc', '-is', 'mininet:' + self.name ]
|
||||
'bash', '--norc', '--noediting',
|
||||
'-is', '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)
|
||||
# received by the parent
|
||||
master, slave = pty.openpty()
|
||||
self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave,
|
||||
close_fds=False )
|
||||
self.stdin = os.fdopen( master, 'rw' )
|
||||
self.master, self.slave = pty.openpty()
|
||||
self.shell = self._popen( cmd, stdin=self.slave, stdout=self.slave,
|
||||
stderr=self.slave, close_fds=False )
|
||||
# XXX BL: This doesn't seem right, and we should also probably
|
||||
# close our files when we exit...
|
||||
self.stdin = os.fdopen( self.master, 'r' )
|
||||
self.stdout = self.stdin
|
||||
self.pid = self.shell.pid
|
||||
self.pollOut = select.poll()
|
||||
@@ -169,7 +190,7 @@ class Node( object ):
|
||||
def mountPrivateDirs( self ):
|
||||
"mount private directories"
|
||||
# Avoid expanding a string into a list of chars
|
||||
assert not isinstance( self.privateDirs, basestring )
|
||||
assert not isinstance( self.privateDirs, BaseString )
|
||||
for directory in self.privateDirs:
|
||||
if isinstance( directory, tuple ):
|
||||
# mount given private directory
|
||||
@@ -198,7 +219,9 @@ class Node( object ):
|
||||
params: parameters to Popen()"""
|
||||
# Leave this is as an instance method for now
|
||||
assert self
|
||||
return Popen( cmd, **params )
|
||||
popen = Popen( cmd, **params ) # pylint: disable=consider-using-with
|
||||
debug( '_popen', cmd, popen.pid )
|
||||
return popen
|
||||
|
||||
def cleanup( self ):
|
||||
"Help python collect its garbage."
|
||||
@@ -207,23 +230,30 @@ class Node( object ):
|
||||
# for intfName in self.intfNames():
|
||||
# if self.name in intfName:
|
||||
# quietRun( 'ip link del ' + intfName )
|
||||
if self.shell:
|
||||
# Close ptys
|
||||
self.stdin.close()
|
||||
os.close(self.slave)
|
||||
if self.waitExited:
|
||||
debug( 'waiting for', self.pid, 'to terminate\n' )
|
||||
self.shell.wait()
|
||||
self.shell = None
|
||||
|
||||
# Subshell I/O, commands and control
|
||||
|
||||
def read( self, maxbytes=1024 ):
|
||||
def read( self, size=1024 ):
|
||||
"""Buffered read from node, potentially blocking.
|
||||
maxbytes: maximum number of bytes to return"""
|
||||
size: maximum number of characters to return"""
|
||||
count = len( self.readbuf )
|
||||
if count < maxbytes:
|
||||
data = os.read( self.stdout.fileno(), maxbytes - count )
|
||||
self.readbuf += data
|
||||
if maxbytes >= len( self.readbuf ):
|
||||
if count < size:
|
||||
data = os.read( self.stdout.fileno(), size - count )
|
||||
self.readbuf += self.decoder.decode( data )
|
||||
if size >= len( self.readbuf ):
|
||||
result = self.readbuf
|
||||
self.readbuf = ''
|
||||
else:
|
||||
result = self.readbuf[ :maxbytes ]
|
||||
self.readbuf = self.readbuf[ maxbytes: ]
|
||||
result = self.readbuf[ :size ]
|
||||
self.readbuf = self.readbuf[ size: ]
|
||||
return result
|
||||
|
||||
def readline( self ):
|
||||
@@ -240,7 +270,7 @@ class Node( object ):
|
||||
def write( self, data ):
|
||||
"""Write data to node.
|
||||
data: string"""
|
||||
os.write( self.stdin.fileno(), data )
|
||||
os.write( self.stdin.fileno(), encode( data ) )
|
||||
|
||||
def terminate( self ):
|
||||
"Send kill signal to Node and clean up after it."
|
||||
@@ -263,6 +293,7 @@ class Node( object ):
|
||||
returns: result of poll()"""
|
||||
if len( self.readbuf ) == 0:
|
||||
return self.pollOut.poll( timeoutms )
|
||||
return None
|
||||
|
||||
def sendCmd( self, *args, **kwargs ):
|
||||
"""Send a command, followed by a command to echo a sentinel,
|
||||
@@ -356,6 +387,7 @@ class Node( object ):
|
||||
return self.waitOutput( verbose )
|
||||
else:
|
||||
warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) )
|
||||
return None
|
||||
|
||||
def cmdPrint( self, *args):
|
||||
"""Call cmd and printing its output
|
||||
@@ -370,23 +402,23 @@ class Node( object ):
|
||||
'mncmd':
|
||||
[ 'mnexec', '-da', str( self.pid ) ] }
|
||||
defaults.update( kwargs )
|
||||
shell = defaults.pop( 'shell', False )
|
||||
if len( args ) == 1:
|
||||
if isinstance( args[ 0 ], list ):
|
||||
# popen([cmd, arg1, arg2...])
|
||||
cmd = args[ 0 ]
|
||||
elif isinstance( args[ 0 ], basestring ):
|
||||
elif isinstance( args[ 0 ], BaseString ):
|
||||
# popen("cmd arg1 arg2...")
|
||||
cmd = args[ 0 ].split()
|
||||
cmd = [ args[ 0 ] ] if shell else args[ 0 ].split()
|
||||
else:
|
||||
raise Exception( 'popen() requires a string or list' )
|
||||
elif len( args ) > 0:
|
||||
# popen( cmd, arg1, arg2... )
|
||||
cmd = list( args )
|
||||
if shell:
|
||||
cmd = [ os.environ[ 'SHELL' ], '-c' ] + [ ' '.join( cmd ) ]
|
||||
# Attach to our namespace using mnexec -a
|
||||
cmd = defaults.pop( 'mncmd' ) + cmd
|
||||
# Shell requires a string, not a list!
|
||||
if defaults.get( 'shell', False ):
|
||||
cmd = ' '.join( cmd )
|
||||
popen = self._popen( cmd, **defaults )
|
||||
return popen
|
||||
|
||||
@@ -398,7 +430,7 @@ class Node( object ):
|
||||
# Warning: this can fail with large numbers of fds!
|
||||
out, err = popen.communicate()
|
||||
exitcode = popen.wait()
|
||||
return out, err, exitcode
|
||||
return decode( out ), decode( err ), exitcode
|
||||
|
||||
# Interface management, configuration, and routing
|
||||
|
||||
@@ -448,6 +480,7 @@ class Node( object ):
|
||||
else:
|
||||
warn( '*** defaultIntf: warning:', self.name,
|
||||
'has no interfaces\n' )
|
||||
return None
|
||||
|
||||
def intf( self, intf=None ):
|
||||
"""Return our interface object with given string name,
|
||||
@@ -460,7 +493,7 @@ class Node( object ):
|
||||
"""
|
||||
if not intf:
|
||||
return self.defaultIntf()
|
||||
elif isinstance( intf, basestring):
|
||||
elif isinstance( intf, BaseString):
|
||||
return self.nameToIntf[ intf ]
|
||||
else:
|
||||
return intf
|
||||
@@ -487,7 +520,7 @@ class Node( object ):
|
||||
# explicitly so that we won't get errors if we run before they
|
||||
# have been removed by the kernel. Unfortunately this is very slow,
|
||||
# at least with Linux kernels before 2.6.33
|
||||
for intf in self.intfs.values():
|
||||
for intf in list( self.intfs.values() ):
|
||||
# Protect against deleting hardware interfaces
|
||||
if ( self.name in intf.name ) or ( not checkName ):
|
||||
intf.delete()
|
||||
@@ -512,7 +545,7 @@ 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 isinstance( intf, BaseString ) and ' ' in intf:
|
||||
params = intf
|
||||
else:
|
||||
params = 'dev %s' % intf
|
||||
@@ -559,12 +592,12 @@ class Node( object ):
|
||||
method: config method name
|
||||
param: arg=value (ignore if value=None)
|
||||
value may also be list or dict"""
|
||||
name, value = param.items()[ 0 ]
|
||||
name, value = list( param.items() )[ 0 ]
|
||||
if value is None:
|
||||
return
|
||||
return None
|
||||
f = getattr( self, method, None )
|
||||
if not f:
|
||||
return
|
||||
return None
|
||||
if isinstance( value, list ):
|
||||
result = f( *value )
|
||||
elif isinstance( value, dict ):
|
||||
@@ -608,7 +641,7 @@ class Node( object ):
|
||||
|
||||
def intfList( self ):
|
||||
"List of our interfaces sorted by port number"
|
||||
return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
|
||||
return [ self.intfs[ p ] for p in sorted( self.intfs.keys() ) ]
|
||||
|
||||
def intfNames( self ):
|
||||
"The names of our interfaces sorted by port number"
|
||||
@@ -632,11 +665,12 @@ class Node( object ):
|
||||
@classmethod
|
||||
def checkSetup( cls ):
|
||||
"Make sure our class and superclasses are set up"
|
||||
while cls and not getattr( cls, 'isSetup', True ):
|
||||
cls.setup()
|
||||
cls.isSetup = True
|
||||
clas = cls
|
||||
while clas and not getattr( clas, 'isSetup', True ):
|
||||
clas.setup()
|
||||
clas.isSetup = True
|
||||
# Make pylint happy
|
||||
cls = getattr( type( cls ), '__base__', None )
|
||||
clas = getattr( type( clas ), '__base__', None )
|
||||
|
||||
@classmethod
|
||||
def setup( cls ):
|
||||
@@ -651,8 +685,21 @@ class CPULimitedHost( Host ):
|
||||
|
||||
"CPU limited host"
|
||||
|
||||
def __init__( self, name, sched='cfs', **kwargs ):
|
||||
Host.__init__( self, name, **kwargs )
|
||||
def __init__( self, name, sched='cfs', **params ):
|
||||
Host.__init__( self, name, **params )
|
||||
# BL: Setting the correct period/quota is tricky, particularly
|
||||
# for RT. RT allows very small quotas, but the overhead
|
||||
# seems to be high. CFS has a minimum quota of 1 ms, but
|
||||
# still does better with larger period values.
|
||||
self.period_us = params.get( 'period_us', 100000 )
|
||||
self.sched = sched
|
||||
self.cgroupsInited = False
|
||||
self.cgroup, self.rtprio = None, None
|
||||
|
||||
def initCgroups( self ):
|
||||
"Deferred cgroup initialization"
|
||||
if self.cgroupsInited:
|
||||
return
|
||||
# Initialize class if necessary
|
||||
if not CPULimitedHost.inited:
|
||||
CPULimitedHost.init()
|
||||
@@ -662,32 +709,26 @@ class CPULimitedHost( Host ):
|
||||
# We don't add ourselves to a cpuset because you must
|
||||
# specify the cpu and memory placement first
|
||||
errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
|
||||
# BL: Setting the correct period/quota is tricky, particularly
|
||||
# for RT. RT allows very small quotas, but the overhead
|
||||
# seems to be high. CFS has a mininimum quota of 1 ms, but
|
||||
# still does better with larger period values.
|
||||
self.period_us = kwargs.get( 'period_us', 100000 )
|
||||
self.sched = sched
|
||||
if sched == 'rt':
|
||||
if self.sched == 'rt':
|
||||
self.checkRtGroupSched()
|
||||
self.rtprio = 20
|
||||
|
||||
def cgroupSet( self, param, value, resource='cpu' ):
|
||||
"Set a cgroup parameter and return its value"
|
||||
cmd = 'cgset -r %s.%s=%s /%s' % (
|
||||
resource, param, value, self.name )
|
||||
quietRun( cmd )
|
||||
nvalue = int( self.cgroupGet( param, resource ) )
|
||||
if nvalue != value:
|
||||
cmd = [ 'cgset', '-r', "%s.%s=%s" % (
|
||||
resource, param, value), '/' + self.name ]
|
||||
errFail( cmd )
|
||||
nvalue = self.cgroupGet( param, resource )
|
||||
if nvalue != str( value ):
|
||||
error( '*** error: cgroupSet: %s set to %s instead of %s\n'
|
||||
% ( param, nvalue, value ) )
|
||||
return nvalue
|
||||
|
||||
def cgroupGet( self, param, resource='cpu' ):
|
||||
"Return value of cgroup parameter"
|
||||
cmd = 'cgget -r %s.%s /%s' % (
|
||||
resource, param, self.name )
|
||||
return int( quietRun( cmd ).split()[ -1 ] )
|
||||
pname = '%s.%s' % ( resource, param )
|
||||
cmd = 'cgget -n -r %s /%s' % ( pname, self.name )
|
||||
return quietRun( cmd )[len(pname)+1:].strip()
|
||||
|
||||
def cgroupDel( self ):
|
||||
"Clean up our cgroup"
|
||||
@@ -754,6 +795,8 @@ class CPULimitedHost( Host ):
|
||||
def cfsInfo( self, f ):
|
||||
"Internal method: return parameters for CFS bandwidth"
|
||||
pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
|
||||
if self.cgversion == 'cgroup2':
|
||||
pstr, qstr = 'max', ''
|
||||
# CFS uses wall clock time for period and CPU time for quota.
|
||||
quota = int( self.period_us * f * numCores() )
|
||||
period = self.period_us
|
||||
@@ -763,7 +806,7 @@ class CPULimitedHost( Host ):
|
||||
period = int( quota / f / numCores() )
|
||||
# Reset to unlimited on negative quota
|
||||
if quota < 0:
|
||||
quota = -1
|
||||
quota = 'max' if self.cgversion == 'cgroup2' else -1
|
||||
return pstr, qstr, period, quota
|
||||
|
||||
# BL comment:
|
||||
@@ -793,12 +836,16 @@ class CPULimitedHost( Host ):
|
||||
else:
|
||||
return
|
||||
# Set cgroup's period and quota
|
||||
setPeriod = self.cgroupSet( pstr, period )
|
||||
setQuota = self.cgroupSet( qstr, quota )
|
||||
if self.cgversion == 'cgroup':
|
||||
setPeriod = self.cgroupSet( pstr, period )
|
||||
setQuota = self.cgroupSet( qstr, quota )
|
||||
else:
|
||||
setQuota, setPeriod = self.cgroupSet(
|
||||
pstr, '%s %s' % (quota, period) ).split()
|
||||
if sched == 'rt':
|
||||
# Set RT priority if necessary
|
||||
sched = self.chrt()
|
||||
info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
|
||||
info( '(%s %s/%dus) ' % ( sched, setQuota, int( setPeriod ) ) )
|
||||
|
||||
def setCPUs( self, cores, mems=0 ):
|
||||
"Specify (real) cores that our cgroup can run on"
|
||||
@@ -817,11 +864,13 @@ class CPULimitedHost( Host ):
|
||||
errFail( 'cgclassify -g cpuset:/%s %s' % (
|
||||
self.name, self.pid ) )
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def config( self, cpu=-1, cores=None, **params ):
|
||||
"""cpu: desired overall system CPU fraction
|
||||
cores: (real) core(s) this host can run on
|
||||
params: parameters for Node.config()"""
|
||||
r = Node.config( self, **params )
|
||||
self.initCgroups()
|
||||
# Was considering cpu={'cpu': cpu , 'sched': sched}, but
|
||||
# that seems redundant
|
||||
self.setParam( r, 'setCPUFrac', cpu=cpu )
|
||||
@@ -829,13 +878,19 @@ class CPULimitedHost( Host ):
|
||||
return r
|
||||
|
||||
inited = False
|
||||
cgversion = 'cgroup2'
|
||||
|
||||
@classmethod
|
||||
def init( cls ):
|
||||
"Initialization for CPULimitedHost class"
|
||||
mountCgroups()
|
||||
cls.cgversion = mountCgroups()
|
||||
cls.inited = True
|
||||
|
||||
def unlimit( self ):
|
||||
"Unlimit cpu for cfs"
|
||||
if self.sched == 'cfs' and self.params.get( 'cpu', -1 ) != -1:
|
||||
self.setCPUFrac( -1, sched=self.sched )
|
||||
|
||||
|
||||
# Some important things to note:
|
||||
#
|
||||
@@ -879,7 +934,7 @@ class Switch( Node ):
|
||||
"Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
|
||||
if dpid:
|
||||
# Remove any colons and make sure it's a good hex number
|
||||
dpid = dpid.translate( None, ':' )
|
||||
dpid = dpid.replace( ':', '' )
|
||||
assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
|
||||
else:
|
||||
# Use hex of the first number in the switch name
|
||||
@@ -887,6 +942,7 @@ class Switch( Node ):
|
||||
if nums:
|
||||
dpid = hex( int( nums[ 0 ] ) )[ 2: ]
|
||||
else:
|
||||
self.terminate() # Python 3.6 crash workaround
|
||||
raise Exception( 'Unable to derive default datapath ID - '
|
||||
'please either specify a dpid or use a '
|
||||
'canonical switch name such as s23.' )
|
||||
@@ -908,6 +964,7 @@ class Switch( Node ):
|
||||
else:
|
||||
error( '*** Error: %s has execed and cannot accept commands' %
|
||||
self.name )
|
||||
return None
|
||||
|
||||
def connected( self ):
|
||||
"Is the switch connected to a controller? (override this method)"
|
||||
@@ -1093,6 +1150,7 @@ class OVSSwitch( Switch ):
|
||||
if self.batch:
|
||||
cmd = ' '.join( str( arg ).strip() for arg in args )
|
||||
self.commands.append( cmd )
|
||||
return None
|
||||
else:
|
||||
return self.cmd( 'ovs-vsctl', *args, **kwargs )
|
||||
|
||||
@@ -1230,7 +1288,7 @@ class OVSSwitch( Switch ):
|
||||
run( cmds, shell=True )
|
||||
# Reapply link config if necessary...
|
||||
for switch in switches:
|
||||
for intf in switch.intfs.itervalues():
|
||||
for intf in switch.intfs.values():
|
||||
if isinstance( intf, TCIntf ):
|
||||
intf.config( **intf.params )
|
||||
return switches
|
||||
@@ -1256,7 +1314,7 @@ class OVSSwitch( Switch ):
|
||||
pids = ' '.join( str( switch.pid ) for switch in switches )
|
||||
run( 'kill -HUP ' + pids )
|
||||
for switch in switches:
|
||||
switch.shell = None
|
||||
switch.terminate()
|
||||
return switches
|
||||
|
||||
|
||||
@@ -1280,7 +1338,7 @@ class OVSBridge( OVSSwitch ):
|
||||
"Are we forwarding yet?"
|
||||
if self.stp:
|
||||
status = self.dpctl( 'show' )
|
||||
return 'STP_FORWARD' in status and not 'STP_LEARN' in status
|
||||
return 'STP_FORWARD' in status and 'STP_LEARN' not in status
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -1360,10 +1418,12 @@ class Controller( Node ):
|
||||
OpenFlow controller."""
|
||||
|
||||
def __init__( self, name, inNamespace=False, command='controller',
|
||||
cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
|
||||
port=6653, protocol='tcp', **params ):
|
||||
cargs='ptcp:%d', cdir=None, ip="127.0.0.1",
|
||||
port=6653, protocol='tcp', verbose=False, **params ):
|
||||
self.command = command
|
||||
self.cargs = cargs
|
||||
if verbose:
|
||||
cargs = '-v ' + cargs
|
||||
self.cdir = cdir
|
||||
# Accept 'ip:port' syntax as shorthand
|
||||
if ':' in ip:
|
||||
@@ -1405,6 +1465,7 @@ class Controller( Node ):
|
||||
' 1>' + cout + ' 2>' + cout + ' &' )
|
||||
self.execed = False
|
||||
|
||||
# pylint: disable=arguments-differ,signature-differs
|
||||
def stop( self, *args, **kwargs ):
|
||||
"Stop controller."
|
||||
self.cmd( 'kill %' + self.command )
|
||||
@@ -1428,7 +1489,7 @@ class Controller( Node ):
|
||||
@classmethod
|
||||
def isAvailable( cls ):
|
||||
"Is controller available?"
|
||||
return quietRun( 'which controller' )
|
||||
return which( 'controller' )
|
||||
|
||||
|
||||
class OVSController( Controller ):
|
||||
@@ -1440,9 +1501,9 @@ class OVSController( Controller ):
|
||||
|
||||
@classmethod
|
||||
def isAvailable( cls ):
|
||||
return ( quietRun( 'which ovs-controller' ) or
|
||||
quietRun( 'which test-controller' ) or
|
||||
quietRun( 'which ovs-testcontroller' ) ).strip()
|
||||
return (which( 'ovs-controller' ) or
|
||||
which( 'test-controller' ) or
|
||||
which( 'ovs-testcontroller' ))
|
||||
|
||||
class NOX( Controller ):
|
||||
"Controller to run a NOX application."
|
||||
@@ -1455,7 +1516,7 @@ class NOX( Controller ):
|
||||
warn( 'warning: no NOX modules specified; '
|
||||
'running packetdump only\n' )
|
||||
noxArgs = [ 'packetdump' ]
|
||||
elif type( noxArgs ) not in ( list, tuple ):
|
||||
elif not isinstance( noxArgs, ( list, tuple ) ):
|
||||
noxArgs = [ noxArgs ]
|
||||
|
||||
if 'NOX_CORE_DIR' not in os.environ:
|
||||
@@ -1464,32 +1525,26 @@ class NOX( Controller ):
|
||||
|
||||
Controller.__init__( self, name,
|
||||
command=noxCoreDir + '/nox_core',
|
||||
cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
|
||||
cargs='--libdir=/usr/local/lib -v '
|
||||
'-i ptcp:%s ' +
|
||||
' '.join( noxArgs ),
|
||||
cdir=noxCoreDir,
|
||||
**kwargs )
|
||||
|
||||
class Ryu( Controller ):
|
||||
"Controller to run Ryu application"
|
||||
def __init__( self, name, *ryuArgs, **kwargs ):
|
||||
"Ryu OpenFlow Controller"
|
||||
def __init__( self, name, ryuArgs='ryu.app.simple_switch',
|
||||
command='ryu run', **kwargs ):
|
||||
"""Init.
|
||||
name: name to give controller.
|
||||
ryuArgs: arguments and modules to pass to Ryu"""
|
||||
homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
|
||||
ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
|
||||
if not ryuArgs:
|
||||
warn( 'warning: no Ryu modules specified; '
|
||||
'running simple_switch only\n' )
|
||||
ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
|
||||
elif type( ryuArgs ) not in ( list, tuple ):
|
||||
ryuArgs = [ ryuArgs ]
|
||||
|
||||
Controller.__init__( self, name,
|
||||
command='ryu-manager',
|
||||
cargs='--ofp-tcp-listen-port %s ' +
|
||||
' '.join( ryuArgs ),
|
||||
cdir=ryuCoreDir,
|
||||
**kwargs )
|
||||
name: name to give controller.
|
||||
ryuArgs: modules to pass to Ryu (ryu.app.simple_switch)
|
||||
command: command to run Ryu ('ryu run')"""
|
||||
if isinstance( ryuArgs, ( list, tuple ) ):
|
||||
ryuArgs = ' '.join( ryuArgs )
|
||||
cargs = kwargs.pop(
|
||||
'cargs', ryuArgs + ' --ofp-tcp-listen-port %s' )
|
||||
Controller.__init__( self, name, command=command,
|
||||
cargs=cargs, **kwargs )
|
||||
|
||||
|
||||
class RemoteController( Controller ):
|
||||
@@ -1508,6 +1563,7 @@ class RemoteController( Controller ):
|
||||
"Overridden to do nothing."
|
||||
return
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def stop( self ):
|
||||
"Overridden to do nothing."
|
||||
return
|
||||
@@ -1539,6 +1595,7 @@ class RemoteController( Controller ):
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
DefaultControllers = ( Controller, OVSController )
|
||||
|
||||
def findController( controllers=DefaultControllers ):
|
||||
@@ -1546,6 +1603,7 @@ def findController( controllers=DefaultControllers ):
|
||||
for controller in controllers:
|
||||
if controller.isAvailable():
|
||||
return controller
|
||||
return None
|
||||
|
||||
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
|
||||
"Find a controller that is available and instantiate it"
|
||||
|
||||
+24
-15
@@ -84,13 +84,36 @@ class NAT( Node ):
|
||||
self.flush = flush
|
||||
self.forwardState = self.cmd( 'sysctl -n net.ipv4.ip_forward' ).strip()
|
||||
|
||||
def setManualConfig( self, intf ):
|
||||
"""Prevent network-manager/networkd from messing with our interface
|
||||
by specifying manual configuration in /etc/network/interfaces"""
|
||||
cfile = '/etc/network/interfaces'
|
||||
line = '\niface %s inet manual\n' % intf
|
||||
try:
|
||||
with open( cfile ) as f:
|
||||
config = f.read()
|
||||
except IOError:
|
||||
config = ''
|
||||
if ( line ) not in config:
|
||||
info( '*** Adding "' + line.strip() + '" to ' + cfile + '\n' )
|
||||
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 || netplan apply' )
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def config( self, **params ):
|
||||
"""Configure the NAT and iptables"""
|
||||
super( NAT, self).config( **params )
|
||||
|
||||
if not self.localIntf:
|
||||
self.localIntf = self.defaultIntf()
|
||||
|
||||
self.setManualConfig( self.localIntf )
|
||||
|
||||
# Now we can configure manually without interference
|
||||
super( NAT, self).config( **params )
|
||||
|
||||
if self.flush:
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
||||
self.cmd( 'iptables -F' )
|
||||
@@ -114,20 +137,6 @@ class NAT( Node ):
|
||||
# Instruct the kernel to perform forwarding
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
|
||||
|
||||
# Prevent network-manager from messing with our interface
|
||||
# by specifying manual configuration in /etc/network/interfaces
|
||||
intf = self.localIntf
|
||||
cfile = '/etc/network/interfaces'
|
||||
line = '\niface %s inet manual\n' % intf
|
||||
config = open( cfile ).read()
|
||||
if ( line ) not in config:
|
||||
info( '*** Adding "' + line.strip() + '" to ' + cfile + '\n' )
|
||||
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 terminate( self ):
|
||||
"Stop NAT/forwarding between Mininet and external network"
|
||||
# Remote NAT rules
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ def makeTerm( node, title='Node', term='xterm', display=None, cmd='bash'):
|
||||
}
|
||||
if term not in cmds:
|
||||
error( 'invalid terminal type: %s' % term )
|
||||
return
|
||||
return None
|
||||
display, tunnel = tunnelX11( node, display )
|
||||
if display is None:
|
||||
return []
|
||||
|
||||
@@ -25,6 +25,7 @@ def runTests( testDir, verbosity=1 ):
|
||||
.run( testSuite ).wasSuccessful() )
|
||||
sys.exit( 0 if success else 1 )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
# get the directory containing example tests
|
||||
|
||||
@@ -45,7 +45,7 @@ class testOptionsTopoCommon( object ):
|
||||
@staticmethod
|
||||
def tearDown():
|
||||
"Clean up if necessary"
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
if sys.exc_info() != ( None, None, None ):
|
||||
cleanup()
|
||||
|
||||
def runOptionsTopoTest( self, n, msg, hopts=None, lopts=None ):
|
||||
@@ -95,7 +95,7 @@ class testOptionsTopoCommon( object ):
|
||||
CPU_FRACTION = 0.1
|
||||
CPU_TOLERANCE = 0.8 # CPU fraction below which test should fail
|
||||
hopts = { 'cpu': CPU_FRACTION }
|
||||
#self.runOptionsTopoTest( N, hopts=hopts )
|
||||
# self.runOptionsTopoTest( N, hopts=hopts )
|
||||
|
||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, hopts=hopts ),
|
||||
host=CPULimitedHost, switch=self.switchClass,
|
||||
@@ -118,7 +118,7 @@ class testOptionsTopoCommon( object ):
|
||||
% ( CPU_FRACTION * 100, hostUsage, N, hoptsStr,
|
||||
self.switchClass ) )
|
||||
for pct in results:
|
||||
#divide cpu by 100 to convert from percentage to fraction
|
||||
# divide cpu by 100 to convert from percentage to fraction
|
||||
self.assertWithinTolerance( pct/100, CPU_FRACTION,
|
||||
CPU_TOLERANCE, msg )
|
||||
|
||||
@@ -131,7 +131,7 @@ class testOptionsTopoCommon( object ):
|
||||
BW_TOLERANCE = 0.8 # BW fraction below which test should fail
|
||||
# Verify ability to create limited-link topo first;
|
||||
lopts = { 'bw': BW, 'use_htb': True }
|
||||
# Also verify correctness of limit limitng within a bound.
|
||||
# Also verify correctness of limit limiting within a bound.
|
||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
||||
link=TCLink, switch=self.switchClass,
|
||||
waitConnected=True )
|
||||
@@ -263,6 +263,7 @@ class testOptionsTopoUserspace( testOptionsTopoCommon, unittest.TestCase ):
|
||||
longMessage = True
|
||||
switchClass = UserSwitch
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
unittest.main()
|
||||
|
||||
@@ -26,7 +26,7 @@ class testSingleSwitchCommon( object ):
|
||||
@staticmethod
|
||||
def tearDown():
|
||||
"Clean up if necessary"
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
if sys.exc_info() != ( None, None, None ):
|
||||
cleanup()
|
||||
|
||||
def testMinimal( self ):
|
||||
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Regression test for pty leak in Node()
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.clean import cleanup
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
|
||||
class TestPtyLeak( unittest.TestCase ):
|
||||
"Verify that there is no pty leakage"
|
||||
|
||||
@staticmethod
|
||||
def testPtyLeak():
|
||||
"Test for pty leakage"
|
||||
net = Mininet( SingleSwitchTopo() )
|
||||
net.start()
|
||||
host = net[ 'h1' ]
|
||||
for _ in range( 0, 10 ):
|
||||
oldptys = host.slave, host.master
|
||||
net.delHost( host )
|
||||
host = net.addHost( 'h1' )
|
||||
assert ( host.slave, host.master ) == oldptys
|
||||
net.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
cleanup()
|
||||
@@ -24,16 +24,16 @@ class TestSwitchDpidAssignmentOVS( unittest.TestCase ):
|
||||
"Clean up if necessary"
|
||||
# satisfy pylint
|
||||
assert self
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
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' )
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
switch = net.addSwitch( 's1' )
|
||||
self.assertEqual( switch.defaultDpid(), switch.dpid )
|
||||
net.stop()
|
||||
|
||||
def dpidFrom( self, num ):
|
||||
"Compute default dpid from number"
|
||||
@@ -44,31 +44,34 @@ class TestSwitchDpidAssignmentOVS( unittest.TestCase ):
|
||||
"""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 )
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
switch = net.addSwitch( 's1', dpid=dpid )
|
||||
self.assertEqual( switch.dpid, dpid )
|
||||
net.stop()
|
||||
|
||||
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
|
||||
name of the switch does not contain a digit. Also verify the
|
||||
exception message."""
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
with self.assertRaises( Exception ) as raises_cm:
|
||||
Mininet( Topo(), self.switchClass,
|
||||
Host, Controller ).addSwitch( 'A' )
|
||||
self.assertEqual(raises_cm.exception.message, 'Unable to derive '
|
||||
net.addSwitch( 'A' )
|
||||
self.assertTrue( 'Unable to derive '
|
||||
'default datapath ID - please either specify a dpid '
|
||||
'or use a canonical switch name such as s23.')
|
||||
'or use a canonical switch name such as s23.'
|
||||
in str( raises_cm.exception ) )
|
||||
net.stop()
|
||||
|
||||
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' )
|
||||
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
switch = net.addSwitch( 's123' )
|
||||
self.assertEqual( switch.dpid, self.dpidFrom( 123 ) )
|
||||
net.stop()
|
||||
|
||||
|
||||
class OVSUser( OVSSwitch):
|
||||
"OVS User Switch convenience class"
|
||||
@@ -77,7 +80,7 @@ class OVSUser( OVSSwitch):
|
||||
OVSSwitch.__init__( self, *args, **kwargs )
|
||||
|
||||
class testSwitchOVSUser( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignnment of OVS User Switch."
|
||||
"Test dpid assignment of OVS User Switch."
|
||||
switchClass = OVSUser
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ivs-ctl' ),
|
||||
@@ -92,6 +95,8 @@ class testSwitchUserspace( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignment of Userspace switch."
|
||||
switchClass = UserSwitch
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
unittest.main()
|
||||
cleanup()
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Package: mininet
|
||||
Test functions defined in mininet.util."""
|
||||
|
||||
import unittest
|
||||
|
||||
from mininet.util import quietRun
|
||||
|
||||
class testQuietRun( unittest.TestCase ):
|
||||
"""Test quietRun that runs a command and returns its merged output from
|
||||
STDOUT and STDIN"""
|
||||
|
||||
@staticmethod
|
||||
def getEchoCmd( n ):
|
||||
"Return a command that will print n characters"
|
||||
return "echo -n " + "x" * n
|
||||
|
||||
def testEmpty( self ):
|
||||
"Run a command that prints nothing"
|
||||
output = quietRun(testQuietRun.getEchoCmd( 0 ) )
|
||||
self.assertEqual( 0, len( output ) )
|
||||
|
||||
def testOneRead( self ):
|
||||
"""Run a command whose output is entirely read on the first call if
|
||||
each call reads at most 1024 characters
|
||||
"""
|
||||
for n in [ 42, 1024 ]:
|
||||
output = quietRun( testQuietRun.getEchoCmd( n ) )
|
||||
self.assertEqual( n, len( output ) )
|
||||
|
||||
def testMultipleReads( self ):
|
||||
"Run a command whose output is not entirely read on the first read"
|
||||
for n in [ 1025, 4242 ]:
|
||||
output = quietRun(testQuietRun.getEchoCmd( n ) )
|
||||
self.assertEqual( n, len( output ) )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -6,13 +6,15 @@ Tests for the Mininet Walkthrough
|
||||
TODO: missing xterm test
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import pexpect
|
||||
import os
|
||||
import re
|
||||
from mininet.util import quietRun
|
||||
from distutils.version import StrictVersion
|
||||
from time import sleep
|
||||
import unittest
|
||||
|
||||
from sys import stdout
|
||||
|
||||
from mininet.util import quietRun, pexpect, StrictVersion
|
||||
from mininet.clean import cleanup
|
||||
|
||||
|
||||
def tsharkVersion():
|
||||
"Return tshark version"
|
||||
@@ -28,6 +30,11 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@staticmethod
|
||||
def setup():
|
||||
"Be paranoid and run cleanup() before each test"
|
||||
cleanup()
|
||||
|
||||
# PART 1
|
||||
def testHelp( self ):
|
||||
"Check the usage message"
|
||||
@@ -43,17 +50,18 @@ class testWalkthrough( unittest.TestCase ):
|
||||
tshark = pexpect.spawn( 'tshark -i lo -R of' )
|
||||
else:
|
||||
tshark = pexpect.spawn( 'tshark -i lo -Y openflow_v1' )
|
||||
tshark.expect( [ 'Capturing on lo', "Capturing on 'Loopback'" ] )
|
||||
tshark.expect( [ 'Capturing on lo', "Capturing on 'Loopback" ] )
|
||||
mn = pexpect.spawn( 'mn --test pingall' )
|
||||
mn.expect( '0% dropped' )
|
||||
tshark.expect( [ '74 Hello', '74 of_hello', '74 Type: OFPT_HELLO' ] )
|
||||
tshark.sendintr()
|
||||
mn.expect( pexpect.EOF )
|
||||
tshark.expect( 'aptured' ) # 'xx packets captured'
|
||||
tshark.expect( pexpect.EOF )
|
||||
|
||||
def testBasic( self ):
|
||||
"Test basic CLI commands (help, nodes, net, dump)"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p = pexpect.spawn( 'mn -w' )
|
||||
p.expect( self.prompt )
|
||||
# help command
|
||||
p.sendline( 'help' )
|
||||
@@ -67,7 +75,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
p.expect( self.prompt )
|
||||
# net command
|
||||
p.sendline( 'net' )
|
||||
expected = [ x for x in nodes ]
|
||||
expected = list( nodes )
|
||||
while len( expected ) > 0:
|
||||
index = p.expect( expected )
|
||||
node = p.match.group( 0 )
|
||||
@@ -92,17 +100,18 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
def testHostCommands( self ):
|
||||
"Test ifconfig and ps on h1 and s1"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p = pexpect.spawn( 'mn -w' )
|
||||
p.expect( self.prompt )
|
||||
# Third pattern is a local interface beginning with 'eth' or 'en'
|
||||
interfaces = [ 'h1-eth0', 's1-eth1', r'[^-](eth|en)\w*\d', 'lo',
|
||||
interfaces = [ r'h1-eth0[:\s]', r's1-eth1[:\s]',
|
||||
r'[^-](eth|en)\w*\d[:\s]', r'lo[:\s]',
|
||||
self.prompt ]
|
||||
# h1 ifconfig
|
||||
p.sendline( 'h1 ifconfig -a' )
|
||||
ifcount = 0
|
||||
while True:
|
||||
index = p.expect( interfaces )
|
||||
if index == 0 or index == 3:
|
||||
if index in (0, 3):
|
||||
ifcount += 1
|
||||
elif index == 1:
|
||||
self.fail( 's1 interface displayed in "h1 ifconfig"' )
|
||||
@@ -118,7 +127,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
index = p.expect( interfaces )
|
||||
if index == 0:
|
||||
self.fail( 'h1 interface displayed in "s1 ifconfig"' )
|
||||
elif index == 1 or index == 2 or index == 3:
|
||||
elif index in (1, 2, 3):
|
||||
ifcount += 1
|
||||
else:
|
||||
break
|
||||
@@ -143,7 +152,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
def testConnectivity( self ):
|
||||
"Test ping and pingall"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p = pexpect.spawn( 'mn -w' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 ping -c 1 h2' )
|
||||
p.expect( '1 packets transmitted, 1 received' )
|
||||
@@ -156,14 +165,20 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
def testSimpleHTTP( self ):
|
||||
"Start an HTTP server on h1 and wget from h2"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
if 'Python 2' in quietRun( 'python --version' ):
|
||||
httpserver = 'SimpleHTTPServer'
|
||||
else:
|
||||
httpserver = 'http.server'
|
||||
p = pexpect.spawn( 'mn -w', logfile=stdout )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 python -m %s 80 >& /dev/null &' % httpserver )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 python -m SimpleHTTPServer 80 &' )
|
||||
# The walkthrough doesn't specify a delay here, and
|
||||
# we also don't read the output (also a possible problem),
|
||||
# but for now let's wait a couple of seconds to make
|
||||
# but for now let's wait a number of seconds to make
|
||||
# it less likely to fail due to the race condition.
|
||||
sleep( 2 )
|
||||
p.sendline( 'px from mininet.util import waitListening;'
|
||||
'waitListening(h1, port=80, timeout=30)' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( ' h2 wget -O - h1' )
|
||||
p.expect( '200 OK' )
|
||||
@@ -208,7 +223,9 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
def testLinkChange( self ):
|
||||
"Test TCLink bw and delay"
|
||||
p = pexpect.spawn( 'mn --link tc,bw=10,delay=10ms' )
|
||||
p = pexpect.spawn( 'mn -w --link tc,bw=10,delay=10ms' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 route && ping -c1 h2' )
|
||||
# test bw
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'iperf' )
|
||||
@@ -222,8 +239,8 @@ class testWalkthrough( unittest.TestCase ):
|
||||
p.expect( r'rtt min/avg/max/mdev = '
|
||||
r'([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' )
|
||||
delay = float( p.match.group( 2 ) )
|
||||
self.assertTrue( delay > 40, 'Delay < 40ms' )
|
||||
self.assertTrue( delay < 45, 'Delay > 40ms' )
|
||||
self.assertTrue( delay >= 40, 'Delay < 40ms' )
|
||||
self.assertTrue( delay <= 50, 'Delay > 50ms' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
@@ -260,7 +277,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
p.expect( self.prompt )
|
||||
for i in range( 1, 3 ):
|
||||
p.sendline( 'h%d ifconfig' % i )
|
||||
p.expect( 'HWaddr 00:00:00:00:00:0%d' % i )
|
||||
p.expect( r'\s00:00:00:00:00:0%d\s' % i )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.expect( pexpect.EOF )
|
||||
@@ -286,12 +303,14 @@ class testWalkthrough( unittest.TestCase ):
|
||||
"Test running user switch in its own namespace"
|
||||
p = pexpect.spawn( 'mn --innamespace --switch user' )
|
||||
p.expect( self.prompt )
|
||||
interfaces = [ 'h1-eth0', 's1-eth1', '[^-]eth0', 'lo', self.prompt ]
|
||||
interfaces = [ r'h1-eth0[:\s]', r's1-eth1[:\s]',
|
||||
r'[^-](eth|en)\w*\d[:\s]', r'lo[:\s]',
|
||||
self.prompt ]
|
||||
p.sendline( 's1 ifconfig -a' )
|
||||
ifcount = 0
|
||||
while True:
|
||||
index = p.expect( interfaces )
|
||||
if index == 1 or index == 3:
|
||||
if index in (1, 3):
|
||||
ifcount += 1
|
||||
elif index == 0:
|
||||
self.fail( 'h1 interface displayed in "s1 ifconfig"' )
|
||||
@@ -312,7 +331,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
# PART 3
|
||||
def testPythonInterpreter( self ):
|
||||
"Test py and px by checking IP for h1 and adding h3"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p = pexpect.spawn( 'mn -w' )
|
||||
p.expect( self.prompt )
|
||||
# test host IP
|
||||
p.sendline( 'py h1.IP()' )
|
||||
@@ -334,7 +353,7 @@ class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
def testLink( self ):
|
||||
"Test link CLI command using ping"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p = pexpect.spawn( 'mn -w' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'link s1 h1 down' )
|
||||
p.expect( self.prompt )
|
||||
|
||||
+13
-9
@@ -13,6 +13,9 @@ setup for testing, and can even be emulated with the Mininet package.
|
||||
|
||||
from mininet.util import irange, natural, naturalSeq
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
class MultiGraph( object ):
|
||||
"Utility class to track nodes and edges - replaces networkx.MultiGraph"
|
||||
|
||||
@@ -34,7 +37,7 @@ class MultiGraph( object ):
|
||||
key: optional key
|
||||
attr_dict: optional attribute dict
|
||||
attrs: more attributes
|
||||
warning: udpates attr_dict with attrs"""
|
||||
warning: updates attr_dict with attrs"""
|
||||
attr_dict = {} if attr_dict is None else attr_dict
|
||||
attr_dict.update( attrs )
|
||||
self.node.setdefault( src, {} )
|
||||
@@ -56,13 +59,13 @@ class MultiGraph( object ):
|
||||
return self.node.items() if data else self.node.keys()
|
||||
|
||||
def edges_iter( self, data=False, keys=False ):
|
||||
"Iterator: return graph edges"
|
||||
for src, entry in self.edge.iteritems():
|
||||
for dst, keys in entry.iteritems():
|
||||
"Iterator: return graph edges, optionally with data and keys"
|
||||
for src, entry in self.edge.items():
|
||||
for dst, entrykeys in entry.items():
|
||||
if src > dst:
|
||||
# Skip duplicate edges
|
||||
continue
|
||||
for k, attrs in keys.iteritems():
|
||||
for k, attrs in entrykeys.items():
|
||||
if data:
|
||||
if keys:
|
||||
yield( src, dst, k, attrs )
|
||||
@@ -156,8 +159,7 @@ class Topo( object ):
|
||||
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 )
|
||||
return key
|
||||
return self.g.add_edge(node1, node2, key, opts )
|
||||
|
||||
def nodes( self, sort=True ):
|
||||
"Return nodes in graph"
|
||||
@@ -334,9 +336,11 @@ class LinearTopo( Topo ):
|
||||
self.n = n
|
||||
|
||||
if n == 1:
|
||||
genHostName = lambda i, j: 'h%s' % i
|
||||
def genHostName( i, _j ):
|
||||
return 'h%s' % i
|
||||
else:
|
||||
genHostName = lambda i, j: 'h%ss%d' % ( j, i )
|
||||
def genHostName( i, j ):
|
||||
return 'h%ss%d' % ( j, i )
|
||||
|
||||
lastSwitch = None
|
||||
for i in irange( 1, k ):
|
||||
|
||||
+4
-2
@@ -53,9 +53,11 @@ class TorusTopo( Topo ):
|
||||
raise Exception( 'Please use 3x3 or greater for compatibility '
|
||||
'with 2.1' )
|
||||
if n == 1:
|
||||
genHostName = lambda loc, k: 'h%s' % ( loc )
|
||||
def genHostName( loc, _k ):
|
||||
return 'h%s' % ( loc )
|
||||
else:
|
||||
genHostName = lambda loc, k: 'h%sx%d' % ( loc, k )
|
||||
def genHostName( loc, k ):
|
||||
return 'h%sx%d' % ( loc, k )
|
||||
|
||||
hosts, switches, dpid = {}, {}, 0
|
||||
# Create and wire interior
|
||||
|
||||
+183
-62
@@ -1,17 +1,86 @@
|
||||
"Utility functions for Mininet."
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from mininet.log import output, info, error, warn, debug
|
||||
|
||||
from time import sleep
|
||||
from collections import namedtuple
|
||||
from fcntl import fcntl, F_GETFL, F_SETFL
|
||||
from functools import partial
|
||||
from os import O_NONBLOCK
|
||||
from resource import getrlimit, setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
|
||||
from select import poll, POLLIN, POLLHUP
|
||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||
import re
|
||||
from fcntl import fcntl, F_GETFL, F_SETFL
|
||||
from os import O_NONBLOCK
|
||||
import os
|
||||
from functools import partial
|
||||
from sys import exit # pylint: disable=redefined-builtin
|
||||
from time import sleep
|
||||
|
||||
from mininet.log import output, info, error, warn, debug
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
# Python 2/3 compatibility
|
||||
|
||||
Python3 = sys.version_info[0] == 3
|
||||
BaseString = str if Python3 else getattr( str, '__base__' )
|
||||
Encoding = 'utf-8' if Python3 else None
|
||||
class NullCodec( object ):
|
||||
"Null codec for Python 2"
|
||||
@staticmethod
|
||||
def decode( buf ):
|
||||
"Null decode"
|
||||
return buf
|
||||
|
||||
@staticmethod
|
||||
def encode( buf ):
|
||||
"Null encode"
|
||||
return buf
|
||||
|
||||
|
||||
if Python3:
|
||||
def decode( buf ):
|
||||
"Decode buffer for Python 3"
|
||||
return buf.decode( Encoding )
|
||||
|
||||
def encode( buf ):
|
||||
"Encode buffer for Python 3"
|
||||
return buf.encode( Encoding )
|
||||
getincrementaldecoder = codecs.getincrementaldecoder( Encoding )
|
||||
|
||||
else:
|
||||
decode, encode = NullCodec.decode, NullCodec.encode
|
||||
|
||||
def getincrementaldecoder():
|
||||
"Return null codec for Python 2"
|
||||
return NullCodec
|
||||
|
||||
try:
|
||||
import packaging.version # replacement for distutils.version
|
||||
StrictVersion = packaging.version.parse
|
||||
except ImportError: # python2.7 lacks ModuleNotFoundError
|
||||
import distutils.version # pylint: disable=deprecated-module
|
||||
StrictVersion = distutils.version.StrictVersion
|
||||
|
||||
try:
|
||||
oldpexpect = None
|
||||
import pexpect as oldpexpect # pylint: disable=import-error
|
||||
|
||||
class Pexpect( object ):
|
||||
"Custom pexpect that is compatible with str"
|
||||
@staticmethod
|
||||
def spawn( *args, **kwargs):
|
||||
"pexpect.spawn that is compatible with str"
|
||||
if Python3 and 'encoding' not in kwargs:
|
||||
kwargs.update( encoding='utf-8' )
|
||||
return oldpexpect.spawn( *args, **kwargs )
|
||||
|
||||
def __getattr__( self, name ):
|
||||
return getattr( oldpexpect, name )
|
||||
pexpect = Pexpect()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# Command execution support
|
||||
|
||||
@@ -33,13 +102,14 @@ def oldQuietRun( *cmd ):
|
||||
cmd: list of command params"""
|
||||
if len( cmd ) == 1:
|
||||
cmd = cmd[ 0 ]
|
||||
if isinstance( cmd, str ):
|
||||
if isinstance( cmd, BaseString ):
|
||||
cmd = cmd.split( ' ' )
|
||||
popen = Popen( cmd, stdout=PIPE, stderr=STDOUT )
|
||||
out = ''
|
||||
popen = Popen( # pylint: disable=consider-using-with
|
||||
cmd, stdout=PIPE, stderr=STDOUT )
|
||||
# We can't use Popen.communicate() because it uses
|
||||
# select(), which can't handle
|
||||
# high file descriptor numbers! poll() can, however.
|
||||
out = ''
|
||||
readable = poll()
|
||||
readable.register( popen.stdout )
|
||||
while True:
|
||||
@@ -57,7 +127,9 @@ def oldQuietRun( *cmd ):
|
||||
# This is a bit complicated, but it enables us to
|
||||
# monitor command output as it is happening
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
CmdResult = namedtuple( 'CmdResult', 'out err ret' )
|
||||
|
||||
# pylint: disable=too-many-branches,too-many-statements
|
||||
def errRun( *cmd, **kwargs ):
|
||||
"""Run a command and return stdout, stderr and return code
|
||||
cmd: string or list of command and args
|
||||
@@ -74,30 +146,34 @@ def errRun( *cmd, **kwargs ):
|
||||
if len( cmd ) == 1:
|
||||
cmd = cmd[ 0 ]
|
||||
# Allow passing in a list or a string
|
||||
if isinstance( cmd, str ) and not shell:
|
||||
if isinstance( cmd, BaseString ) and not shell:
|
||||
cmd = cmd.split( ' ' )
|
||||
cmd = [ str( arg ) for arg in cmd ]
|
||||
elif isinstance( cmd, list ) and shell:
|
||||
cmd = " ".join( arg for arg in cmd )
|
||||
debug( '*** errRun:', cmd, '\n' )
|
||||
# pylint: disable=consider-using-with
|
||||
popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
|
||||
# We use poll() because select() doesn't work with large fd numbers,
|
||||
# and thus communicate() doesn't work either
|
||||
out, err = '', ''
|
||||
poller = poll()
|
||||
poller.register( popen.stdout, POLLIN )
|
||||
fdtofile = { popen.stdout.fileno(): popen.stdout }
|
||||
fdToFile = { popen.stdout.fileno(): popen.stdout }
|
||||
fdToDecoder = { popen.stdout.fileno(): getincrementaldecoder() }
|
||||
outDone, errDone = False, True
|
||||
if popen.stderr:
|
||||
fdtofile[ popen.stderr.fileno() ] = popen.stderr
|
||||
fdToFile[ popen.stderr.fileno() ] = popen.stderr
|
||||
fdToDecoder[ popen.stderr.fileno() ] = getincrementaldecoder()
|
||||
poller.register( popen.stderr, POLLIN )
|
||||
errDone = False
|
||||
while not outDone or not errDone:
|
||||
readable = poller.poll()
|
||||
for fd, event in readable:
|
||||
f = fdtofile[ fd ]
|
||||
if event & POLLIN:
|
||||
data = f.read( 1024 )
|
||||
f = fdToFile[ fd ]
|
||||
decoder = fdToDecoder[ fd ]
|
||||
if event & ( POLLIN | POLLHUP ):
|
||||
data = decoder.decode( f.read( 1024 ) )
|
||||
if echo:
|
||||
output( data )
|
||||
if f == popen.stdout:
|
||||
@@ -108,7 +184,7 @@ def errRun( *cmd, **kwargs ):
|
||||
err += data
|
||||
if data == '':
|
||||
errDone = True
|
||||
else: # POLLHUP or something unexpected
|
||||
else: # something unexpected
|
||||
if f == popen.stdout:
|
||||
outDone = True
|
||||
elif f == popen.stderr:
|
||||
@@ -116,8 +192,13 @@ def errRun( *cmd, **kwargs ):
|
||||
poller.unregister( fd )
|
||||
|
||||
returncode = popen.wait()
|
||||
# Python 3 complains if we don't explicitly close these
|
||||
popen.stdout.close()
|
||||
if stderr == PIPE:
|
||||
popen.stderr.close()
|
||||
debug( out, err, returncode )
|
||||
return out, err, returncode
|
||||
return CmdResult( out, err, returncode )
|
||||
|
||||
# pylint: enable=too-many-branches
|
||||
|
||||
def errFail( *cmd, **kwargs ):
|
||||
@@ -126,25 +207,32 @@ def errFail( *cmd, **kwargs ):
|
||||
if ret:
|
||||
raise Exception( "errFail: %s failed with return code %s: %s"
|
||||
% ( cmd, ret, err ) )
|
||||
return out, err, ret
|
||||
return CmdResult( out, err, ret )
|
||||
|
||||
def quietRun( cmd, **kwargs ):
|
||||
"Run a command and return merged stdout and stderr"
|
||||
return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
|
||||
return errRun( cmd, stderr=STDOUT, **kwargs ).out
|
||||
|
||||
def which(cmd, **kwargs ):
|
||||
"Run a command and return merged stdout and stderr"
|
||||
out, _, ret = errRun( ["which", cmd], stderr=STDOUT, **kwargs )
|
||||
return out.rstrip() if ret == 0 else None
|
||||
|
||||
# pylint: enable=maybe-no-member
|
||||
|
||||
def isShellBuiltin( cmd ):
|
||||
"Return True if cmd is a bash builtin."
|
||||
if isShellBuiltin.builtIns is None:
|
||||
isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
|
||||
isShellBuiltin.builtIns = set(quietRun( 'bash -c enable' ).split())
|
||||
space = cmd.find( ' ' )
|
||||
if space > 0:
|
||||
cmd = cmd[ :space]
|
||||
return cmd in isShellBuiltin.builtIns
|
||||
|
||||
|
||||
isShellBuiltin.builtIns = None
|
||||
|
||||
|
||||
# Interface management
|
||||
#
|
||||
# Interfaces are managed as strings which are simply the
|
||||
@@ -331,7 +419,7 @@ def netParse( ipstr ):
|
||||
if '/' in ipstr:
|
||||
ip, pf = ipstr.split( '/' )
|
||||
prefixLen = int( pf )
|
||||
#if no prefix is specified, set the prefix to 24
|
||||
# if no prefix is specified, set the prefix to 24
|
||||
else:
|
||||
ip = ipstr
|
||||
prefixLen = 24
|
||||
@@ -374,30 +462,34 @@ def pmonitor(popens, timeoutms=500, readline=True,
|
||||
terminates: when all EOFs received"""
|
||||
poller = poll()
|
||||
fdToHost = {}
|
||||
for host, popen in popens.iteritems():
|
||||
fdToDecoder = {}
|
||||
for host, popen in popens.items():
|
||||
fd = popen.stdout.fileno()
|
||||
fdToHost[ fd ] = host
|
||||
fdToDecoder[ fd ] = getincrementaldecoder()
|
||||
poller.register( fd, POLLIN )
|
||||
if not readline:
|
||||
# Use non-blocking reads
|
||||
flags = fcntl( fd, F_GETFL )
|
||||
fcntl( fd, F_SETFL, flags | O_NONBLOCK )
|
||||
flags = fcntl( fd, F_GETFL )
|
||||
fcntl( fd, F_SETFL, flags | O_NONBLOCK )
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
while popens:
|
||||
fds = poller.poll( timeoutms )
|
||||
if fds:
|
||||
for fd, event in fds:
|
||||
host = fdToHost[ fd ]
|
||||
decoder = fdToDecoder[ fd ]
|
||||
popen = popens[ host ]
|
||||
if event & POLLIN:
|
||||
if readline:
|
||||
# Attempt to read a line of output
|
||||
# This blocks until we receive a newline!
|
||||
line = popen.stdout.readline()
|
||||
else:
|
||||
line = popen.stdout.read( readmax )
|
||||
yield host, line
|
||||
# Check for EOF
|
||||
elif event & POLLHUP:
|
||||
if event & ( POLLIN | POLLHUP ):
|
||||
while True:
|
||||
try:
|
||||
f = popen.stdout
|
||||
line = decoder.decode( f.readline() if readline
|
||||
else f.read( readmax ) )
|
||||
except IOError:
|
||||
line = ''
|
||||
if line == '':
|
||||
break
|
||||
yield host, line
|
||||
if event & POLLHUP:
|
||||
poller.unregister( fd )
|
||||
del popens[ host ]
|
||||
else:
|
||||
@@ -406,19 +498,19 @@ def pmonitor(popens, timeoutms=500, readline=True,
|
||||
# Other stuff we use
|
||||
def sysctlTestAndSet( name, limit ):
|
||||
"Helper function to set sysctl limits"
|
||||
#convert non-directory names into directory names
|
||||
# convert non-directory names into directory names
|
||||
if '/' not in name:
|
||||
name = '/proc/sys/' + name.replace( '.', '/' )
|
||||
#read limit
|
||||
# read limit
|
||||
with open( name, 'r' ) as readFile:
|
||||
oldLimit = readFile.readline()
|
||||
if isinstance( limit, int ):
|
||||
#compare integer limits before overriding
|
||||
# compare integer limits before overriding
|
||||
if int( oldLimit ) < limit:
|
||||
with open( name, 'w' ) as writeFile:
|
||||
writeFile.write( "%d" % limit )
|
||||
else:
|
||||
#overwrite non-integer limits
|
||||
# overwrite non-integer limits
|
||||
with open( name, 'w' ) as writeFile:
|
||||
writeFile.write( limit )
|
||||
|
||||
@@ -435,21 +527,21 @@ def fixLimits():
|
||||
try:
|
||||
rlimitTestAndSet( RLIMIT_NPROC, 8192 )
|
||||
rlimitTestAndSet( RLIMIT_NOFILE, 16384 )
|
||||
#Increase open file limit
|
||||
# Increase open file limit
|
||||
sysctlTestAndSet( 'fs.file-max', 10000 )
|
||||
#Increase network buffer space
|
||||
# Increase network buffer space
|
||||
sysctlTestAndSet( 'net.core.wmem_max', 16777216 )
|
||||
sysctlTestAndSet( 'net.core.rmem_max', 16777216 )
|
||||
sysctlTestAndSet( 'net.ipv4.tcp_rmem', '10240 87380 16777216' )
|
||||
sysctlTestAndSet( 'net.ipv4.tcp_wmem', '10240 87380 16777216' )
|
||||
sysctlTestAndSet( 'net.core.netdev_max_backlog', 5000 )
|
||||
#Increase arp cache size
|
||||
# Increase arp cache size
|
||||
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh1', 4096 )
|
||||
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh2', 8192 )
|
||||
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh3', 16384 )
|
||||
#Increase routing table size
|
||||
# Increase routing table size
|
||||
sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 )
|
||||
#Increase number of PTYs for nodes
|
||||
# Increase number of PTYs for nodes
|
||||
sysctlTestAndSet( 'kernel.pty.max', 20000 )
|
||||
# pylint: disable=broad-except
|
||||
except Exception:
|
||||
@@ -457,18 +549,25 @@ def fixLimits():
|
||||
"Mininet's performance may be affected.\n" )
|
||||
# pylint: enable=broad-except
|
||||
|
||||
|
||||
def mountCgroups():
|
||||
"Make sure cgroups file system is mounted"
|
||||
mounts = quietRun( 'cat /proc/mounts' )
|
||||
cgdir = '/sys/fs/cgroup'
|
||||
csdir = cgdir + '/cpuset'
|
||||
if ('cgroup %s' % cgdir not in mounts and
|
||||
'cgroups %s' % cgdir not in mounts):
|
||||
raise Exception( "cgroups not mounted on " + cgdir )
|
||||
if 'cpuset %s' % csdir not in mounts:
|
||||
errRun( 'mkdir -p ' + csdir )
|
||||
errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
|
||||
def mountCgroups( cgcontrol='cpu cpuacct cpuset' ):
|
||||
"""Mount cgroupfs if needed and return cgroup version
|
||||
cgcontrol: cgroup controllers to check ('cpu cpuacct cpuset')
|
||||
Returns: 'cgroup' | 'cgroup2' """
|
||||
# Try to read the cgroup controllers in cgcontrol
|
||||
cglist = cgcontrol.split()
|
||||
paths = ' '.join( '-g ' + c for c in cglist )
|
||||
cmd = 'cgget -n %s /' % paths
|
||||
result = errRun( cmd )
|
||||
# If it failed, mount cgroupfs and retry
|
||||
if result.ret or result.err or any(
|
||||
c not in result.out for c in cglist ):
|
||||
errFail( 'cgroupfs-mount' )
|
||||
result = errRun( cmd )
|
||||
errFail( cmd )
|
||||
# cpu.cfs_period_us is used for cgroup but not cgroup2
|
||||
if 'cpu.cfs_period_us' in result.out:
|
||||
return 'cgroup'
|
||||
return 'cgroup2'
|
||||
|
||||
def natural( text ):
|
||||
"To sort sanely/alphabetically: sorted( l, key=natural )"
|
||||
@@ -590,7 +689,6 @@ def ensureRoot():
|
||||
if os.getuid() != 0:
|
||||
error( '*** Mininet must run as root.\n' )
|
||||
exit( 1 )
|
||||
return
|
||||
|
||||
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
|
||||
"""Wait until server is listening on port.
|
||||
@@ -600,7 +698,7 @@ def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
|
||||
if not runCmd( '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 isinstance( server, BaseString ) else server.IP()
|
||||
cmd = ( 'echo A | telnet -e A %s %s' % ( serverIP, port ) )
|
||||
time = 0
|
||||
result = runCmd( cmd )
|
||||
@@ -618,3 +716,26 @@ def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
|
||||
time += .5
|
||||
result = runCmd( cmd )
|
||||
return True
|
||||
|
||||
def unitScale( num, prefix='' ):
|
||||
"Return unit scale prefix and factor"
|
||||
scale = 'kMGTP'
|
||||
if prefix:
|
||||
pos = scale.lower().index( prefix.lower() )
|
||||
return prefix, float( 10**(3*(pos+1)) )
|
||||
num, prefix, factor = float( num ), '', 1
|
||||
for i, c in enumerate(scale, start=1):
|
||||
f = 10**(3*i)
|
||||
if num < f:
|
||||
break
|
||||
prefix, factor = c, f
|
||||
return prefix, float( factor )
|
||||
|
||||
def fmtBps( bps, prefix='', fmt='%.1f %sbits/sec' ):
|
||||
"""Return bps as iperf-style formatted rate string
|
||||
prefix: lock to specific prefix (k, M, G, ...)
|
||||
fmt: default format string for bps, prefix"""
|
||||
bps = float( bps )
|
||||
prefix, factor = unitScale( bps, prefix )
|
||||
bps /= factor
|
||||
return fmt % ( bps, prefix)
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <sched.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#if !defined(VERSION)
|
||||
#define VERSION "(devel)"
|
||||
@@ -96,18 +99,26 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
int c;
|
||||
int fd;
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
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)
|
||||
switch(c) {
|
||||
case 'c':
|
||||
/* close file descriptors except stdin/out/error */
|
||||
for (fd = getdtablesize(); fd > 2; fd--)
|
||||
close(fd);
|
||||
if ((dir = opendir("/proc/self/fd"))) {
|
||||
while ((de = readdir(dir)))
|
||||
if ((fd = atoi(de->d_name)) > 2)
|
||||
close(fd);
|
||||
}
|
||||
/* fall back to old method if needed */
|
||||
else for (fd = getdtablesize(); fd > 2; fd--)
|
||||
close(fd);
|
||||
break;
|
||||
case 'd':
|
||||
/* detach from tty */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
"Setuptools params"
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import setup
|
||||
from os.path import join
|
||||
|
||||
# Get version number from source tree
|
||||
|
||||
@@ -169,7 +169,7 @@ echo
|
||||
if $persistent; then
|
||||
echo '***Setting up persistent SSH configuration between all nodes'
|
||||
persistentSetup
|
||||
echo $'\n*** Sucessfully set up ssh throughout the cluster!'
|
||||
echo $'\n*** Successfully set up ssh throughout the cluster!'
|
||||
|
||||
else
|
||||
echo '*** Setting up temporary SSH configuration between all nodes'
|
||||
|
||||
+1
-5
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
|
||||
"""
|
||||
Convert simple documentation to epydoc/pydoctor-compatible markup
|
||||
@@ -83,7 +83,3 @@ if __name__ == '__main__':
|
||||
infile.close()
|
||||
os.close( outfid )
|
||||
call( [ 'doxypy', outname ] )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user