Compare commits

...
Author SHA1 Message Date
Brian O'Connor 993b73c1be Adding --upgrade to fetch the latest from GitHub. 2014-10-31 23:04:15 -07:00
Bob Lantz f6de358b06 Try to prime the pump to avoid PACKET_INs during iperf test
Background: the reference controller is reactive and installs exact
match rules. By attempting to start a telnet session we make sure that
the ARP caches and TCP flow rules are set up (in one direction at
least) before the test starts.

This is intended to help with #413
2014-10-31 15:06:15 -07:00
lantz aae0affae4 Merge pull request #411 from cdburkard/devel/cli_usage
add cli usage information
2014-10-14 13:42:40 -07:00
lantz 8190e81ba8 Merge pull request #410 from cdburkard/patches/tshark_walkthrough_1404
support wireshark versions greater than 1.11 in test_walkthrough
2014-10-14 13:36:24 -07:00
lantz 16a2a6dc55 Merge pull request #400 from cdburkard/patches/fixEmptyPing
Mininet crashes when running ping between two hosts with no interfaces
2014-10-13 18:11:19 -07:00
lantz b7999978f9 Merge pull request #409 from cdburkard/patches/cleanup_tests
if a test fails or exits with an error, run cleanup as a precaution
2014-10-13 18:06:44 -07:00
Bob Lantz e1711f357a Use server receive rate rather than client send()/buffering rate
Fixes #412
2014-10-13 17:52:47 -07:00
Bob Lantz 501eb4f916 Add more information for test condition failure 2014-10-13 13:52:43 -07:00
Cody Burkard f341159300 support wireshark versions greater than 1.11 2014-10-13 10:52:37 -07:00
cody burkard cac98f5fd5 add cli usage information 2014-10-10 20:32:15 -07:00
lantz 5eca0802d2 Merge pull request #407 from cdburkard/patches/baresshd_waitListening
wait for sshd to start in baresshd example
2014-10-10 12:33:45 -07:00
Bob Lantz 6159e923e6 Add VM port forwarding option: --forward tcp:2222:22 2014-10-09 14:30:09 -07:00
Bob Lantz 2ceb57915a Merge branch 'cdburkard-patches/test_walkthrough' 2014-10-07 16:11:03 -07:00
Bob Lantz 61c144b9f6 Minor fixes to wireshark test 2014-10-07 16:08:57 -07:00
lantz 8537e8d9fa Merge pull request #404 from cdburkard/patches/default_cli
use node.pexec() to update IP address of intf instead of node.cmd()
2014-10-07 14:27:46 -07:00
cody burkard 33d42e25e6 if a test fails or exits with an error, run cleanup as a precaution 2014-10-03 09:50:39 -07:00
cody burkard 7c5d2771f7 wait for sshd to start in example 2014-10-03 04:52:21 -07:00
Bob Lantz 098bede0ec Wait for controller shutdown.
Unfortunately, this can slow things down a bit - perhaps
100-200 ms in the case of ovs-controller, but I am hoping that
it may help slightly with #399.
2014-10-02 20:09:17 -07:00
Bob Lantz 629e58ca5b Add 'use' test for using VM interactively 2014-10-02 18:45:54 -07:00
cody burkard e3ab3fc239 fix a few small issues with walkthrough tests 2014-10-02 11:10:17 -07:00
cody burkard f1123e71ae update interface IP address with pexec so that backgrounded process output from the cli cannot interfere 2014-10-02 09:19:07 -07:00
cody burkard 778267aa75 if there are no interfaces to ping, there are no packets sent 2014-10-01 23:05:34 -07:00
Bob Lantz d6da13d4e3 ntpd doesn't take a server argument 2014-10-01 17:32:15 -07:00
Bob Lantz 92a4f2ddbf Try using ntpd since ntpdate doesn't always work 2014-10-01 17:29:04 -07:00
Bob Lantz ded25a9ef8 disableNtpd: wait 1 second and print out date just to be sure 2014-10-01 14:42:43 -07:00
Bob Lantz 1bae1aab03 Turn of ntpd and set date manually before tests
This should fix the problem where we see the first test
taking negative time, as well as possibly other issues
with performance tests which may be sensitive to changes
in wall clock time.

Fixes #398
2014-10-01 14:20:59 -07:00
lantz 01a1e8e400 Merge pull request #397 from cdburkard/patches/test_nets
wait for switches to connect during test_nets
2014-09-30 15:19:54 -07:00
Bob Lantz 9487cb508d Fix typo 2014-09-29 19:57:26 -07:00
Bob Lantz 461751e5cd Merge branch 'cdburkard-patches/linear_bw' 2014-09-29 19:06:31 -07:00
Bob Lantz 762479c15b Merge branch 'patches/linear_bw' of https://github.com/cdburkard/mininet into cdburkard-patches/linear_bw 2014-09-29 19:06:08 -07:00
Bob Lantz c8607467bd Merge branch 'cdburkard-patches/fix_sshd' 2014-09-29 18:51:00 -07:00
Bob Lantz f8e98d6a7f Merge branch 'patches/fix_sshd' of https://github.com/cdburkard/mininet into cdburkard-patches/fix_sshd 2014-09-29 18:50:41 -07:00
lantz 4aa0b82381 Merge pull request #395 from cdburkard/patches/vlan_fail_output
check for vlan dependency
2014-09-29 18:33:38 -07:00
lantz f9522b30dc Merge pull request #394 from cdburkard/patches/cpu_test
continue to test cfs if rt is not enabled in kernel
2014-09-29 17:41:07 -07:00
Bob Lantz ec26c7492d Install vconfig in VM for VLAN example
This should help with #393 although it doesn't solve
the root issue of the example failing silently when
vconfig is missing.
2014-09-29 16:30:24 -07:00
cody burkard 684092bae1 wait for switches to connect during test_nets 2014-09-27 06:43:39 -07:00
cody burkard 9cbf4688b2 add 1ms delay to all links to exaggerate TCP bandwidth decrease across an increasing number of links 2014-09-27 04:48:57 -07:00
cody burkard 74857ba474 remove User Switch from linearBandwidth due to poor performance 2014-09-27 04:47:56 -07:00
cody burkard f1b61c629a Merge branch 'master' of github.com:mininet/mininet into patches/fix_sshd
Conflicts:
	examples/cpu.py
2014-09-27 04:31:46 -07:00
cody burkard a565bdd57d fix popen to work with shell 2014-09-27 04:30:16 -07:00
cody burkard cf5bbd597a promote waitListening to util.py 2014-09-27 04:30:16 -07:00
cody burkard c0d8fc0d37 wait until sshd has started on each host 2014-09-27 04:30:16 -07:00
cody burkard eef43402b6 check for vlan dependency 2014-09-27 04:24:27 -07:00
cody burkard 54bd9e6112 continue to test cfs if rt is not enabled in kernel 2014-09-27 03:20:07 -07:00
lantz fa7edec7c8 Merge pull request #391 from cdburkard/patches/fix_popen
workaround: attach to cgroup first, then mount namespace
2014-09-26 16:47:06 -07:00
lantz 89cc29b4c2 Merge pull request #367 from cdburkard/devel/Ryu
add Ryu controller support to Mininet
2014-09-26 16:41:45 -07:00
cody burkard 686a9993f5 add Ryu controller to mininet 2014-09-26 15:42:15 -07:00
cody burkard e16c5fe905 attach to cgroup first, then mount namespace 2014-09-25 22:51:48 -07:00
Bob Lantz dedb06b2f5 Wait for crlf after OK/FAILED for better -v output 2014-09-25 18:43:07 -07:00
lantz 5fc3f57ede Merge pull request #386 from cdburkard/patches/cpu_test
Skip test_cpu.py if RT_GROUP_SCHED is not enabled
2014-09-25 14:19:55 -07:00
lantz 5789dae8be Merge pull request #387 from cdburkard/patches/iperf_bw
use udp with iperf to measure loss. pings are not reliable
2014-09-25 14:17:09 -07:00
lantz 0efde9c4ed Merge pull request #388 from cdburkard/patches/intfOptions
measure loss with udp iperf
2014-09-25 14:16:26 -07:00
cody burkard 7eeaed992c use udp with iperf to measure loss. pings are not reliable 2014-09-25 13:09:24 -07:00
cody burkard f0ce6f501d measure loss with udp iperf 2014-09-25 12:39:42 -07:00
cody burkard 823d1b9990 skip test if RT_GROUP_SCHED is not enabled 2014-09-25 10:16:55 -07:00
Bob Lantz 61760eabc5 Make sure we 'sudo kill' our sudo pexpect process in close().
This should more reliably shut down pexpect subprocesses when
build.py exits before completion.
2014-09-24 11:57:46 -07:00
Bob Lantz 12095a12f4 Try to install openvswitch-testcontroller if needed 2014-09-23 17:49:46 -07:00
Bob Lantz 412726d39c Fix -a 2014-09-23 17:18:59 -07:00
Bob Lantz 136c959191 Fix wireshark namespace conflict and don't reinstall 2014-09-23 16:32:30 -07:00
lantz 2ac4cd43da Merge pull request #384 from mininet/devel/loxigen
Switch to loxigen-built openflow.lua wireshark plugin
2014-09-23 14:09:02 -07:00
Bob Lantz f603052b35 Install coloring rules regardless of plugin. Also don't clobber. 2014-09-23 14:01:40 -07:00
Bob Lantz 47be38e63c Don't install lua plugin for wireshark 1.12+
Apparently there is a conflict where the lua plugin
conflicts with the built-in dissector for openflow
that is included with 1.12 and up. For now, we will
just not install the plugin. This should fix the
14.10 VM build.

Additionally, I have added a handy function,
version_ge, which compares version numbers in
canonical x.y.z format. Thanks to stackoverflow for
pointing out the incredibly useful sort -V option!
2014-09-23 13:45:20 -07:00
Bob Lantz 9ca775cba3 Switch to loxigen-built openflow.lua wireshark plugin
The older wireshark dissectors were not well-maintained
and were a pain to build. They also added tons of extra junk
into our VM images! The ones built into the current
wireshark are deficient for 1.3. The solution for the
future is almost certainly to go with the lua-based plugin,
since it is architecture-independent and should be much
easier to maintain and upgrade.
2014-09-22 15:54:58 -07:00
lantz e4d49e6df7 Merge pull request #229 from mininet/devel/mobility-example
A simple mobility API and example
2014-09-22 12:31:31 -07:00
Brian O'Connor 73ef3e9a39 Merge pull request #375 from cdburkard/patches/multi_core_rt
fix host --rt
2014-09-21 00:43:25 -07:00
Brian O'Connor 55ef99b667 Merge pull request #379 from cdburkard/devel/show_ports
adding 'ports' command to cli
2014-09-21 00:13:58 -07:00
Brian O'Connor 57686d3149 Merge pull request #381 from cdburkard/examples/interfaces
adding example and test for intf.config()
2014-09-20 23:31:30 -07:00
Brian O'Connor 8396960ec0 Merge pull request #380 from cdburkard/devel/cli_comments
parse comments out of CLI
2014-09-20 23:27:33 -07:00
Brian O'Connor 3e8df323b3 Merge pull request #374 from cdburkard/patches/rt
check if RT_GROUP_SCHED is enabled in kernel
2014-09-20 23:21:06 -07:00
cody burkard 706229da77 adding example and test for intf.config 2014-09-17 12:55:59 -07:00
lantz 2c10a8e687 Merge pull request #376 from cdburkard/patches/pingOutput
if we do not receive a ping and cannot parse output, return errorTuple
2014-09-16 13:43:06 -07:00
lantz e4c4891a47 Merge pull request #359 from mininet/devel/cluster
Cluster Support Prototype
2014-09-16 13:40:45 -07:00
Bob Lantz 80d647a9b0 add findUser() to clean up user identification 2014-09-16 13:31:51 -07:00
cody burkard c5e8f09b10 adding comments to CLI 2014-09-15 15:52:14 -07:00
cody burkard 08643fe679 adding 'ports' command to cli 2014-09-15 15:33:58 -07:00
cody burkard 3df3610199 adding sanity check for cluster edition 2014-09-09 22:30:20 -07:00
cody burkard 00cbb348a7 if we do not receive a ping and cannot parse output, return errorTuple 2014-09-09 13:59:41 -07:00
lantz cde6c3aaf4 Merge pull request #369 from cdburkard/patches/hifi_multicore
Fix runCpuLimitTest with multiple cores
2014-09-05 15:44:23 -07:00
Bob Lantz c265deedef Cluster edition prototype: remote nodes and links.
We add a new experimental feature to allow Mininet to run across
a cluster of machines. This is currently implemented via a set
mix-in classes that provide remote nodes that are implemented
via a connection to a remote shell, and remote links which are
tunnels across servers. In this preliminary implementation,
both control and data connections are made via ssh, but this
could change in the future.

A MininetCluster class is provided which allows existing code
to be used with minimal modification - all that is required is
to provide a list of servers to use. A customizable placement
algorithm may also be specified. An experimental CLI subclass
is also provided to make it easier to examine node placement;
status and links commands can also check whether nodes and
tunnels are still running.

Although this is an experimental feature, it does include a
--cluster option to make it convenient to start up a Mininet
simulation over a cluster, and a script to assist with setting
up the prerequisite authentication via ssh key pairs.

The cluster feature is preliminary and missing some obvious
important features, such as parallel startup and multiple tunnel
types, which we hope to add in the future.
2014-09-04 23:07:01 -07:00
Bob Lantz 0333d3dbf4 qcow2size(): use qemu-image instead of file
file no longer returns image size on 14.04
fixes #373
2014-09-04 21:51:21 -07:00
cody burkard 58324bdc50 check if RT_GROUP_SCHED is enabled in kernel 2014-09-04 12:24:18 -07:00
cody burkard 04c1c098ed wall clock time makes rt quota independent of nprocs 2014-09-04 10:46:57 -07:00
Bob Lantz f2458d1dcf Accept 'ISO' or 'boot' in file *.iso command output
Fixes #372
2014-09-04 06:50:54 -07:00
cody burkard ce781a1832 use cgroups to calculate percentage of cpu used 2014-09-04 04:21:25 -07:00
Bob Lantz b85943dc0a chdir() to correct path after calling chroot()
Since chroot() doesn't chdir() by default, we are left in
an unreachable directory in node.pexec() (and in xterms.)

fixes #370
2014-09-04 02:22:13 -07:00
Brian O'Connor d4ca1db60b Merge pull request #364 from cdburkard/patches/testCPULimit
Fix output of backgrounded processes
2014-08-29 19:34:32 -07:00
Brian O'Connor 47d567e53c Merge pull request #365 from cdburkard/devel/test_output
print useful output for tests upon failure
2014-08-29 18:47:30 -07:00
Brian O'Connor 6b8d3538ef adding comment to VLANStarTopo in vlanhost.py 2014-08-28 05:57:10 -07:00
Brian O'Connor 05f3fbae73 Merge pull request #362 from mininet/devel/vlanhost
Adding VLANHost to Examples
2014-08-28 17:49:25 -07:00
Brian O'Connor 65e33fed9b Merge pull request #361 from mininet/devel/linuxrouter
Adding LinuxRouter to Examples
2014-08-28 17:49:07 -07:00
Brian O'Connor d334c1ccfe adding test for vlanhost.py and adding vlantopo example 2014-08-28 02:48:46 -07:00
Brian O'Connor fe8358add2 chmod +x vlanhost.py 2014-08-27 23:07:37 -07:00
Brian O'Connor 2c76ab718b linuxrouter.py: changing name, printing routing table, and some more documentation 2014-08-27 22:43:04 -07:00
Brian O'Connor aa4dfda44c adding documentation and test for linuxrouter.py 2014-08-27 22:01:18 -07:00
lantz 66ae58de17 Merge pull request #363 from cdburkard/patches/testLinkDelay
Fix UserSwitch.connected() as well as testLinkDelay() in test_hifi
We need more than one iteration since the first iteration may show reactive forwarding and ARP delay; currently we have three for good measure, although two is probably enough.
2014-08-27 16:36:30 -07:00
cody burkard ce1673803f clean up logic for backgrounded processes 2014-08-27 11:07:22 -07:00
cody burkard 73adba8b81 print useful output for tests upon failure 2014-08-27 10:55:08 -07:00
cody burkard c11d577349 parse pid printed when backgrounding a process 2014-08-27 08:44:08 -07:00
Brian O'Connor be1ed10363 adding vlanhost.py 2014-08-27 03:58:36 -07:00
Brian O'Connor 8a987b9c55 adding linuxrouter.py 2014-08-27 03:57:38 -07:00
cody burkard c75ff7ecd9 fixes for LinkDelay test in test_hifi 2014-08-26 22:34:34 -07:00
lantz 92075113d8 Merge pull request #342 from cdburkard/devel/startup
improve startup performance, largely by removing unnecessary ifconfigs
2014-08-26 19:59:12 -07:00
cody burkard 720a846cf8 use kernel's mac generation 2014-08-26 18:48:08 -07:00
cody burkard 417d79780f merging master 2014-08-13 17:35:34 -07:00
cody burkard 41a54f05cb adding comments and removing random access spaces 2014-08-13 17:33:00 -07:00
cody burkard 84c1c24ce2 skip this because of poor UserSwitch performance 2014-08-13 17:06:00 -07:00
cody burkard af4c9719b3 autostaticarp is broken without this 2014-08-13 15:08:04 -07:00
cody burkard 891a9e8bdf fixed syntax error 2014-08-06 17:52:40 -07:00
cody burkard 7ae39fffc2 stop using ONLAB OUI for generated mac addressses 2014-08-06 17:51:32 -07:00
cody burkard 88763cfbe1 removed more unnecessary ifconfigs 2014-08-01 13:22:02 -07:00
cody burkard 19dd7f70c1 switched back to node.cmd for OVS commands. this is faster.. 2014-08-01 12:18:50 -07:00
cody burkard 42cdda38bb added some documentation 2014-08-01 11:27:25 -07:00
cody burkard f0fd84770a Merge branch 'devel/startup' of github.com:cdburkard/mininet into devel/startup 2014-08-01 11:00:58 -07:00
cody burkard a2d0ea78be fixed issue with regex matching 2014-08-01 11:00:08 -07:00
cody burkard e9d034bd31 adding old changes 2014-08-01 11:00:07 -07:00
cody burkard a3d51b77e9 few small fixes to syntax errors 2014-08-01 11:00:07 -07:00
cody burkard 4b65110e4d removed comments and cleaned up code. 2014-08-01 11:00:07 -07:00
cody burkard eba13f0ca8 removed many of the commands being run to maximize startup performance 2014-08-01 11:00:07 -07:00
cody burkard c1934706bb testing link stuff 2014-08-01 11:00:07 -07:00
cody burkard 859bfea502 fixed issue with regex matching 2014-07-25 03:41:13 -07:00
cody burkard 2973798ca1 Merge branch 'devel/pty' of github.com:mininet/mininet into startup 2014-07-24 16:31:10 -07:00
cody burkard b9a15f071c adding old changes 2014-07-24 16:30:48 -07:00
cody burkard f11dbe81d2 few small fixes to syntax errors 2014-07-24 15:59:38 -07:00
cody burkard 6bc9d68485 removed comments and cleaned up code. 2014-07-22 23:00:43 -07:00
cody burkard 5cd6b553a5 removed many of the commands being run to maximize startup performance 2014-07-22 19:27:45 -07:00
cody burkard 5b13dc62ce Merge branch 'master' of git://github.com/mininet/mininet into devel/startup 2014-07-19 02:09:03 -07:00
cody burkard 4d381f0b58 testing link stuff 2014-07-19 02:05:37 -07:00
38 changed files with 2490 additions and 354 deletions

No files matched your search

+42 -4
View File
@@ -22,10 +22,10 @@ if 'PYTHONPATH' in os.environ:
from mininet.clean import cleanup
from mininet.cli import CLI
from mininet.log import lg, LEVELS, info, debug, error
from mininet.log import lg, LEVELS, info, debug, warn, error
from mininet.net import Mininet, MininetWithControlNet, VERSION
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
NOX, RemoteController, DefaultController,
RYU, NOX, RemoteController, DefaultController,
UserSwitch, OVSSwitch,
OVSLegacyKernelSwitch, IVSSwitch )
from mininet.nodelib import LinuxBridge
@@ -34,7 +34,17 @@ from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
from mininet.topolib import TreeTopo, TorusTopo
from mininet.util import custom, customConstructor
from mininet.util import buildTopo
from mininet.util import updateMininet
from functools import partial
# Experimental! cluster edition prototype
from mininet.examples.cluster import ( MininetCluster, RemoteHost,
RemoteOVSSwitch, RemoteLink,
SwitchBinPlacer, RandomPlacer )
from mininet.examples.clustercli import DemoCLI as ClusterCLI
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
# built in topologies, created only when run
TOPODEF = 'minimal'
@@ -65,6 +75,7 @@ CONTROLLERS = { 'ref': Controller,
'nox': NOX,
'remote': RemoteController,
'default': DefaultController,
'ryu': RYU,
'none': lambda name: None }
LINKDEF = 'default'
@@ -204,6 +215,17 @@ class MininetRunner( object ):
default=False, help="adds a NAT to the topology "
"that connects Mininet to the physical network" )
opts.add_option( '--version', action='callback', callback=version )
opts.add_option( '--upgrade', action='callback', callback=updateMininet,
callback_kwargs={ 'version':VERSION },
help='check for Mininet updates' )
opts.add_option( '--cluster', type='string', default=None,
metavar='server1,server2...',
help=( 'run on multiple servers (experimental!)' ) )
opts.add_option( '--placement', type='choice',
choices=PLACEMENT.keys(), default='block',
metavar='block|random',
help=( 'node placement for --cluster '
'(experimental!) ' ) )
self.options, self.args = opts.parse_args()
@@ -226,6 +248,8 @@ class MininetRunner( object ):
def begin( self ):
"Create and run mininet."
global CLI
if self.options.clean:
cleanup()
exit()
@@ -241,8 +265,6 @@ class MininetRunner( object ):
if self.validate:
self.validate( self.options )
inNamespace = self.options.innamespace
Net = MininetWithControlNet if inNamespace else Mininet
ipBase = self.options.ipbase
xterms = self.options.xterms
mac = self.options.mac
@@ -251,6 +273,22 @@ class MininetRunner( object ):
listenPort = None
if not self.options.nolistenport:
listenPort = self.options.listenport
# Handle inNamespace, cluster options
inNamespace = self.options.innamespace
cluster = self.options.cluster
if inNamespace and cluster:
print "Please specify --innamespace OR --cluster"
exit()
Net = MininetWithControlNet if inNamespace else Mininet
if cluster:
warn( '*** WARNING: Experimental cluster mode!\n'
'*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
CLI = ClusterCLI
Net = partial( MininetCluster, servers=cluster.split( ',' ),
placement=PLACEMENT[ self.options.placement ] )
mn = Net( topo=topo,
switch=switch, host=host, controller=controller,
link=link,
+13 -3
View File
@@ -56,6 +56,11 @@ This example shows how to use link and CPU limits.
This example shows how to create a custom topology programatically
by subclassing Topo, and how to run a series of tests on it.
### linuxrouter.py:
This example shows how to create and configure a router in Mininet
that uses Linux IP forwarding.
#### miniedit.py:
This example demonstrates creating a network via a graphical editor.
@@ -84,6 +89,11 @@ This example shows how to connect a Mininet network to the Internet
using NAT. It also answers the eternal question "why can't I ping
`google.com`?"
#### numberedports.py
This example verifies the mininet ofport numbers match up to the ovs port numbers.
It also verifies that the port numbers match up to the interface numbers
#### popen.py:
This example monitors a number of hosts using `host.popen()` and
@@ -123,7 +133,7 @@ memory and `sysctl` configuration (see `INSTALL`.)
This example creates a 64-host tree network, and attempts to check full
connectivity using `ping`, for different switch/datapath types.
#### numberedports.py
#### vlanhost.py:
An example of how to subclass Host to use a VLAN on its primary interface.
This example verifies the mininet ofport numbers match up to the ovs port numbers.
It also verifies that the port numbers match up to the interface numbers
+8 -2
View File
@@ -4,9 +4,10 @@
import sys
from mininet.node import Host
from mininet.util import ensureRoot
from mininet.util import ensureRoot, waitListening
ensureRoot()
timeout = 5
print "*** Creating nodes"
h1 = Host( 'h1' )
@@ -33,5 +34,10 @@ cmd = '/usr/sbin/sshd -o UseDNS=no -u0 -o "Banner /tmp/%s.banner"' % h1.name
if len( sys.argv ) > 1:
cmd += ' ' + ' '.join( sys.argv[ 1: ] )
h1.cmd( cmd )
listening = waitListening( server=h1, port=22, timeout=timeout )
print "*** You may now ssh into", h1.name, "at", h1.IP()
if listening:
print "*** You may now ssh into", h1.name, "at", h1.IP()
else:
print ( "*** Warning: after %s seconds, %s is not listening on port 22"
% ( timeout, h1.name ) )
+839
View File
@@ -0,0 +1,839 @@
#!/usr/bin/python
"""
cluster.py: prototyping/experimentation for distributed Mininet,
aka Mininet: Cluster Edition
Author: Bob Lantz
Core classes:
RemoteNode: a Node() running on a remote server
RemoteOVSSwitch(): an OVSSwitch() running on a remote server
RemoteLink: a Link() on a remote server
Tunnel: a Link() between a local Node() and a RemoteNode()
These are largely interoperable with local objects.
- One Mininet to rule them all
It is important that the same topologies, APIs, and CLI can be used
with minimal or no modification in both local and distributed environments.
- Multiple placement models
Placement should be as easy as possible. We should provide basic placement
support and also allow for explicit placement.
Questions:
What is the basic communication mechanism?
To start with? Probably a single multiplexed ssh connection between each
pair of mininet servers that needs to communicate.
How are tunnels created?
We have several options including ssh, GRE, OF capsulator, socat, VDE, l2tp,
etc.. It's not clear what the best one is. For now, we use ssh tunnels since
they are encrypted and semi-automatically shared. We will probably want to
support GRE as well because it's very easy to set up with OVS.
How are tunnels destroyed?
They are destroyed when the links are deleted in Mininet.stop()
How does RemoteNode.popen() work?
It opens a shared ssh connection to the remote server and attaches to
the namespace using mnexec -a -g.
Is there any value to using Paramiko vs. raw ssh?
Maybe, but it doesn't seem to support L2 tunneling.
Should we preflight the entire network, including all server-to-server
connections?
Yes! We don't yet do this with remote server-to-server connections yet.
Should we multiplex the link ssh connections?
Yes, this is done automatically with ControlMaster=auto.
Note on ssh and DNS:
Please add UseDNS: no to your /etc/ssh/sshd_config!!!
Things to do:
- asynchronous/pipelined/parallel startup
- ssh debugging/profiling
- make connections into real objects
- support for other tunneling schemes
- tests and benchmarks
- 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, makeIntfPair, errRun, retry
from mininet.examples.clustercli import CLI
from mininet.log import setLogLevel, debug, info, error
from signal import signal, SIGINT, SIGHUP, SIG_IGN
from subprocess import Popen, PIPE, STDOUT
import os
from random import randrange
from sys import exit
import re
from distutils.version import StrictVersion
# BL note: so little code is required for remote nodes,
# we will probably just want to update the main Node()
# class to enable it for remote access! However, there
# are a large number of potential failure conditions with
# remote nodes which we may want to detect and handle.
# Another interesting point is that we could put everything
# in a mix-in class and easily add cluster mode to 2.0.
class RemoteMixin( object ):
"A mix-in class to turn local nodes into remote nodes"
# ssh base command
# -q: don't print stupid diagnostic messages
# BatchMode yes: don't ask for password
# ForwardAgent yes: forward authentication credentials
sshbase = [ 'ssh', '-q',
'-o', 'BatchMode=yes',
'-o', 'ForwardAgent=yes', '-tt' ]
def __init__( self, name, server=None, user=None, serverIP=None,
controlPath='/tmp/mn-%r@%h:%p', splitInit=False, **kwargs):
"""Instantiate a remote node
name: name of remote node
server: remote server (optional)
user: user on remote server (optional)
controlPath: ssh control path template (optional)
splitInit: split initialization?
**kwargs: see Node()"""
# We connect to servers by IP address
if server == 'localhost':
server = None
self.server = server
self.serverIP = serverIP if serverIP else self.findServerIP( server )
self.user = user if user else self.findUser()
if self.user and self.server:
self.dest = '%s@%s' % ( self.user, self.serverIP )
else:
self.dest = None
self.controlPath = controlPath
self.sshcmd = []
if self.dest:
self.sshcmd = [ 'sudo', '-E', '-u', self.user ] + self.sshbase
if self.controlPath:
self.sshcmd += [ '-o', 'ControlPath=' + self.controlPath,
'-o', 'ControlMaster=auto' ]
self.sshcmd = self.sshcmd + [ self.dest ]
self.splitInit = splitInit
super( RemoteMixin, self ).__init__( name, **kwargs )
@staticmethod
def findUser():
"Try to return logged-in (usually non-root) user"
try:
# If we're running sudo
return os.environ[ 'SUDO_USER' ]
except:
try:
# Logged-in user (if we have a tty)
return quietRun( 'who am i' ).split()[ 0 ]
except:
# Give up and return effective user
return quietRun( 'whoami' )
# Determine IP address of local host
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
@classmethod
def findServerIP( cls, server, intf='eth0' ):
"Return our server's IP address"
# Check for this server
if not server:
output = quietRun( 'ifconfig %s' % intf )
# Otherwise, handle remote server
else:
# First, check for an IP address
if server:
ipmatch = cls._ipMatchRegex.findall( server )
if ipmatch:
return ipmatch[ 0 ]
# Otherwise, look up remote server
output = quietRun( 'getent ahostsv4 %s' % server )
ips = cls._ipMatchRegex.findall( output )
ip = ips[ 0 ] if ips else None
return ip
# Command support via shell process in namespace
def startShell( self, *args, **kwargs ):
"Start a shell process for running commands"
if self.dest:
kwargs.update( mnopts='-c' )
super( RemoteMixin, self ).startShell( *args, **kwargs )
if self.splitInit:
self.sendCmd( 'echo $$' )
else:
self.pid = int( self.cmd( 'echo $$' ) )
def finishInit( self ):
self.pid = int( self.waitOutput() )
def rpopen( self, *cmd, **opts ):
"Return a Popen object on underlying server in root namespace"
params = { 'stdin': PIPE,
'stdout': PIPE,
'stderr': STDOUT,
'sudo': True }
params.update( opts )
return self._popen( *cmd, **params )
def rcmd( self, *cmd, **opts):
"""rcmd: run a command on underlying server
in root namespace
args: string or list of strings
returns: stdout and stderr"""
popen = self.rpopen( *cmd, **opts )
# print 'RCMD: POPEN:', popen
# These loops are tricky to get right.
# Once the process exits, we can read
# EOF twice if necessary.
result = ''
while True:
poll = popen.poll()
result += popen.stdout.read()
if poll is not None:
break
return result
@staticmethod
def _ignoreSignal():
"Detach from process group to ignore all signals"
os.setpgrp()
def _popen( self, cmd, sudo=True, tt=True, **params):
"""Spawn a process on a remote node
cmd: remote command to run (list)
**params: parameters to Popen()
returns: Popen() object"""
if type( cmd ) is str:
cmd = cmd.split()
if self.dest:
if sudo:
cmd = [ 'sudo', '-E' ] + cmd
if tt:
cmd = self.sshcmd + cmd
else:
# Hack: remove -tt
sshcmd = list( self.sshcmd )
sshcmd.remove( '-tt' )
cmd = sshcmd + cmd
else:
if self.user and not sudo:
# Drop privileges
cmd = [ 'sudo', '-E', '-u', self.user ] + cmd
params.update( preexec_fn=self._ignoreSignal )
debug( '_popen', ' '.join(cmd), params )
popen = super( RemoteMixin, self )._popen( cmd, **params )
return popen
def popen( self, *args, **kwargs ):
"Override: disable -tt"
return super( RemoteMixin, self).popen( *args, tt=False, **kwargs )
def addIntf( self, *args, **kwargs ):
"Override: use RemoteLink.moveIntf"
return super( RemoteMixin, self).addIntf( *args,
moveIntfFn=RemoteLink.moveIntf, **kwargs )
class RemoteNode( RemoteMixin, Node ):
"A node on a remote server"
pass
class RemoteHost( RemoteNode ):
"A RemoteHost is simply a RemoteNode"
pass
class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
"Remote instance of Open vSwitch"
OVSVersions = {}
def isOldOVS( self ):
"Is remote switch using an old OVS version?"
cls = type( self )
if self.server not in cls.OVSVersions:
vers = self.cmd( 'ovs-vsctl --version' )
cls.OVSVersions[ self.server ] = re.findall( '\d+\.\d+', vers )[ 0 ]
return ( StrictVersion( cls.OVSVersions[ self.server ] ) <
StrictVersion( '1.10' ) )
class RemoteLink( Link ):
"A RemoteLink is a link between nodes which may be on different servers"
def __init__( self, node1, node2, **kwargs ):
"""Initialize a RemoteLink
see Link() for parameters"""
# Create links on remote node
self.node1 = node1
self.node2 = node2
self.tunnel = None
kwargs.setdefault( 'params1', {} )
kwargs.setdefault( 'params2', {} )
Link.__init__( self, node1, node2, **kwargs )
def stop( self ):
"Stop this link"
if self.tunnel:
self.tunnel.terminate()
self.tunnel = None
def makeIntfPair( self, intfname1, intfname2, addr1=None, addr2=None ):
"""Create pair of interfaces
intfname1: name of interface 1
intfname2: name of interface 2
(override this method [and possibly delete()]
to change link type)"""
node1, node2 = self.node1, self.node2
server1 = getattr( node1, 'server', None )
server2 = getattr( node2, 'server', None )
if not server1 and not server2:
# Local link
return makeIntfPair( intfname1, intfname2, addr1, addr2 )
elif server1 == server2:
# Remote link on same remote server
return makeIntfPair( intfname1, intfname2, addr1, addr2,
run=node1.rcmd )
# Otherwise, make a tunnel
self.tunnel = self.makeTunnel( node1, node2, intfname1, intfname2, addr1, addr2 )
return self.tunnel
@staticmethod
def moveIntf( intf, node, printError=True ):
"""Move remote interface from root ns to node
intf: string, interface
dstNode: destination Node
srcNode: source Node or None (default) for root ns
printError: if true, print error"""
intf = str( intf )
cmd = 'ip link set %s netns %s' % ( intf, node.pid )
node.rcmd( cmd )
links = node.cmd( 'ip link show' )
if not ( ' %s:' % intf ) in links:
if printError:
error( '*** Error: RemoteLink.moveIntf: ' + intf +
' not successfully moved to ' + node.name + '\n' )
return False
return True
def makeTunnel( self, node1, node2, intfname1, intfname2,
addr1=None, addr2=None ):
"Make a tunnel across switches on different servers"
# 1. Create tap interfaces
for node in node1, node2:
# For now we are hard-wiring tap9, which we will rename
node.rcmd( 'ip link delete tap9', stderr=PIPE )
cmd = 'ip tuntap add dev tap9 mode tap user ' + node.user
node.rcmd( cmd )
links = node.rcmd( 'ip link show' )
# print 'after add, links =', links
assert 'tap9' in links
# 2. Create ssh tunnel between tap interfaces
# -n: close stdin
dest = '%s@%s' % ( node2.user, node2.serverIP )
cmd = [ 'ssh', '-n', '-o', 'Tunnel=Ethernet', '-w', '9:9',
dest, 'echo @' ]
self.cmd = cmd
tunnel = node1.rpopen( cmd, sudo=False )
# 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 )
if ch != '@':
error( 'makeTunnel:\n',
'Tunnel setup failed for',
'%s:%s' % ( node1, node1.dest ), 'to',
'%s:%s\n' % ( node2, node2.dest ),
'command was:', cmd, '\n' )
tunnel.terminate()
tunnel.wait()
error( ch + tunnel.stdout.read() )
error( tunnel.stderr.read() )
exit( 1 )
# 3. Move interfaces if necessary
for node in node1, node2:
if node.inNamespace:
retry( 3, .01, RemoteLink.moveIntf, 'tap9', node )
# 4. Rename tap interfaces to desired names
for node, intf, addr in ( ( node1, intfname1, addr1 ),
( node2, intfname2, addr2 ) ):
if not addr:
node.cmd( 'ip link set tap9 name', intf )
else:
node.cmd( 'ip link set tap9 name', intf, 'address', addr )
for node, intf in ( ( node1, intfname1 ), ( node2, intfname2 ) ):
assert intf in node.cmd( 'ip link show' )
return tunnel
def status( self ):
"Detailed representation of link"
if self.tunnel:
if self.tunnel.poll() is not None:
status = "Tunnel EXITED %s" % self.tunnel.returncode
else:
status = "Tunnel Running (%s: %s)" % (
self.tunnel.pid, self.cmd )
else:
status = "OK"
result = "%s %s" % ( Link.status( self ), status )
return result
# Some simple placement algorithms for MininetCluster
class Placer( object ):
"Node placement algorithm for MininetCluster"
def __init__( self, servers=None, nodes=None, hosts=None,
switches=None, controllers=None, links=None ):
"""Initialize placement object
servers: list of servers
nodes: list of all nodes
hosts: list of hosts
switches: list of switches
controllers: list of controllers
links: list of links
(all arguments are optional)
returns: server"""
self.servers = servers or []
self.nodes = nodes or []
self.hosts = hosts or []
self.switches = switches or []
self.controllers = controllers or []
self.links = links or []
def place( self, node ):
"Return server for a given node"
# Default placement: run locally
return None
class RandomPlacer( Placer ):
"Random placement"
def place( self, nodename ):
"""Random placement function
nodename: node name"""
# This may be slow with lots of servers
return self.servers[ randrange( 0, len( self.servers ) ) ]
class RoundRobinPlacer( Placer ):
"""Round-robin placement
Note this will usually result in cross-server links between
hosts and switches"""
def __init__( self, *args, **kwargs ):
Placer.__init__( self, *args, **kwargs )
self.next = 0
def place( self, nodename ):
"""Round-robin placement function
nodename: node name"""
# This may be slow with lots of servers
server = self.servers[ self.next ]
self.next = ( self.next + 1 ) % len( self.servers )
return server
class SwitchBinPlacer( Placer ):
"""Place switches (and controllers) into evenly-sized bins,
and attempt to co-locate hosts and switches"""
def __init__( self, *args, **kwargs ):
Placer.__init__( self, *args, **kwargs )
# Easy lookup for servers and node sets
self.servdict = dict( enumerate( self.servers ) )
self.hset = frozenset( self.hosts )
self.sset = frozenset( self.switches )
self.cset = frozenset( self.controllers )
# Server and switch placement indices
self.placement = self.calculatePlacement()
@staticmethod
def bin( nodes, servers ):
"Distribute nodes evenly over servers"
# Calculate base bin size
nlen = len( nodes )
slen = len( servers )
# Basic bin size
quotient = int( nlen / slen )
binsizes = { server: quotient for server in servers }
# Distribute remainder
remainder = nlen % slen
for server in servers[ 0 : remainder ]:
binsizes[ server ] += 1
# Create binsize[ server ] tickets for each server
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 ) }
def calculatePlacement( self ):
"Pre-calculate node placement"
placement = {}
# Create host-switch connectivity map,
# associating host with last switch that it's
# connected to
switchFor = {}
for src, dst in self.links:
if src in self.hset and dst in self.sset:
switchFor[ src ] = dst
if dst in self.hset and src in self.sset:
switchFor[ dst ] = src
# Place switches
placement = self.bin( self.switches, self.servers )
# Place controllers and merge into placement dict
placement.update( self.bin( self.controllers, self.servers ) )
# Co-locate hosts with their switches
for h in self.hosts:
if h in placement:
# Host is already placed - leave it there
continue
if h in switchFor:
placement[ h ] = placement[ switchFor[ h ] ]
else:
raise Exception(
"SwitchBinPlacer: cannot place isolated host " + h )
return placement
def place( self, node ):
"""Simple placement algorithm:
place switches into evenly sized bins,
and place hosts near their switches"""
return self.placement[ node ]
class HostSwitchBinPlacer( Placer ):
"""Place switches *and hosts* into evenly-sized bins
Note that this will usually result in cross-server
links between hosts and switches"""
def __init__( self, *args, **kwargs ):
Placer.__init__( self, *args, **kwargs )
# Calculate bin sizes
scount = len( self.servers )
self.hbin = max( int( len( self.hosts ) / scount ), 1 )
self.sbin = max( int( len( self.switches ) / scount ), 1 )
self.cbin = max( int( len( self.controllers ) / scount ) , 1 )
info( 'scount:', scount )
info( 'bins:', self.hbin, self.sbin, self.cbin, '\n' )
self.servdict = dict( enumerate( self.servers ) )
self.hset = frozenset( self.hosts )
self.sset = frozenset( self.switches )
self.cset = frozenset( self.controllers )
self.hind, self.sind, self.cind = 0, 0, 0
def place( self, nodename ):
"""Simple placement algorithm:
place nodes into evenly sized bins"""
# Place nodes into bins
if nodename in self.hset:
server = self.servdict[ self.hind / self.hbin ]
self.hind += 1
elif nodename in self.sset:
server = self.servdict[ self.sind / self.sbin ]
self.sind += 1
elif nodename in self.cset:
server = self.servdict[ self.cind / self.cbin ]
self.cind += 1
else:
info( 'warning: unknown node', nodename )
server = self.servdict[ 0 ]
return server
# The MininetCluster class is not strictly necessary.
# However, it has several purposes:
# 1. To set up ssh connection sharing/multiplexing
# 2. To pre-flight the system so that everything is more likely to work
# 3. To allow connection/connectivity monitoring
# 4. To support pluggable placement algorithms
class MininetCluster( Mininet ):
"Cluster-enhanced version of Mininet class"
# Default ssh command
# BatchMode yes: don't ask for password
# ForwardAgent yes: forward authentication credentials
sshcmd = [ 'ssh', '-o', 'BatchMode=yes', '-o', 'ForwardAgent=yes' ]
def __init__( self, *args, **kwargs ):
"""servers: a list of servers to use (note: include
localhost or None to use local system as well)
user: user name for server ssh
placement: Placer() subclass"""
params = { 'host': RemoteHost,
'switch': RemoteOVSSwitch,
'link': RemoteLink,
'precheck': True }
params.update( kwargs )
servers = params.pop( 'servers', [ None ] )
servers = [ s if s != 'localhost' else None for s in servers ]
self.servers = servers
self.serverIP = params.pop( 'serverIP', {} )
if not self.serverIP:
self.serverIP = { server: RemoteMixin.findServerIP( server )
for server in self.servers }
self.user = params.pop( 'user', RemoteMixin.findUser() )
if params.pop( 'precheck' ):
self.precheck()
self.connections = {}
self.placement = params.pop( 'placement', SwitchBinPlacer )
# Make sure control directory exists
self.cdir = os.environ[ 'HOME' ] + '/.ssh/mn'
errRun( [ 'mkdir', '-p', self.cdir ] )
Mininet.__init__( self, *args, **params )
def popen( self, cmd ):
"Popen() for server connections"
old = signal( SIGINT, SIG_IGN )
conn = Popen( cmd, stdin=PIPE, stdout=PIPE, close_fds=True )
signal( SIGINT, old )
return conn
def baddLink( self, *args, **kwargs ):
"break addlink for testing"
pass
def precheck( self ):
"""Pre-check to make sure connection works and that
we can call sudo without a password"""
result = 0
info( '*** Checking servers\n' )
for server in self.servers:
ip = self.serverIP[ server ]
if not server or server == 'localhost':
continue
info( server, '' )
dest = '%s@%s' % ( self.user, ip )
cmd = [ 'sudo', '-E', '-u', self.user ]
cmd += self.sshcmd + [ '-n', dest, 'sudo true' ]
debug( ' '.join( cmd ), '\n' )
out, err, code = errRun( cmd )
if code != 0:
error( '\nstartConnection: server connection check failed '
'to %s using command:\n%s\n'
% ( server, ' '.join( cmd ) ) )
result |= code
if result:
error( '*** Server precheck failed.\n'
'*** Make sure that the above ssh command works correctly.\n'
'*** You may also need to run mn -c on all nodes, and/or\n'
'*** use sudo -E.\n' )
exit( 1 )
info( '\n' )
def modifiedaddHost( self, *args, **kwargs ):
"Slightly modify addHost"
kwargs[ 'splitInit' ] = True
return Mininet.addHost( *args, **kwargs )
def placeNodes( self ):
"""Place nodes on servers (if they don't have a server), and
start shell processes"""
if not self.servers or not self.topo:
# No shirt, no shoes, no service
return
nodes = self.topo.nodes()
placer = self.placement( servers=self.servers,
nodes=self.topo.nodes(),
hosts=self.topo.hosts(),
switches=self.topo.switches(),
links=self.topo.links() )
for node in nodes:
config = self.topo.node_info[ node ]
server = config.setdefault( 'server', placer.place( node ) )
if server:
config.setdefault( 'serverIP', self.serverIP[ server ] )
info( '%s:%s ' % ( node, server ) )
key = ( None, server )
_dest, cfile, _conn = self.connections.get(
key, ( None, None, None ) )
if cfile:
config.setdefault( 'controlPath', cfile )
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()
return controller
def buildFromTopo( self, *args, **kwargs ):
"Start network"
info( '*** Placing nodes\n' )
self.placeNodes()
info( '\n' )
Mininet.buildFromTopo( self, *args, **kwargs )
def testNsTunnels():
"Test tunnels between nodes in namespaces"
net = Mininet( host=RemoteHost, link=RemoteLink )
h1 = net.addHost( 'h1' )
h2 = net.addHost( 'h2', server='ubuntu2' )
net.addLink( h1, h2 )
net.start()
net.pingAll()
net.stop()
# Manual topology creation with net.add*()
#
# This shows how node options may be used to manage
# cluster placement using the net.add*() API
def testRemoteNet( remote='ubuntu2' ):
"Test remote Node classes"
print '*** Remote Node Test'
net = Mininet( host=RemoteHost, switch=RemoteOVSSwitch,
link=RemoteLink )
c0 = net.addController( 'c0' )
# Make sure controller knows its non-loopback address
Intf( 'eth0', node=c0 ).updateIP()
print "*** Creating local h1"
h1 = net.addHost( 'h1' )
print "*** Creating remote h2"
h2 = net.addHost( 'h2', server=remote )
print "*** Creating local s1"
s1 = net.addSwitch( 's1' )
print "*** Creating remote s2"
s2 = net.addSwitch( 's2', server=remote )
print "*** Adding links"
net.addLink( h1, s1 )
net.addLink( s1, s2 )
net.addLink( h2, s2 )
net.start()
print 'Mininet is running on', quietRun( 'hostname' ).strip()
for node in c0, h1, h2, s1, s2:
print 'Node', node, 'is running on', node.cmd( 'hostname' ).strip()
net.pingAll()
CLI( net )
net.stop()
# High-level/Topo API example
#
# This shows how existing Mininet topologies may be used in cluster
# mode by creating node placement functions and a controller which
# can be accessed remotely. This implements a very compatible version
# of cluster edition with a minimum of code!
remoteHosts = [ 'h2' ]
remoteSwitches = [ 's2' ]
remoteServer = 'ubuntu2'
def HostPlacer( name, *args, **params ):
"Custom Host() constructor which places hosts on servers"
if name in remoteHosts:
return RemoteHost( name, *args, server=remoteServer, **params )
else:
return Host( name, *args, **params )
def SwitchPlacer( name, *args, **params ):
"Custom Switch() constructor which places switches on servers"
if name in remoteSwitches:
return RemoteOVSSwitch( name, *args, server=remoteServer, **params )
else:
return RemoteOVSSwitch( name, *args, **params )
def ClusterController( *args, **kwargs):
"Custom Controller() constructor which updates its eth0 IP address"
controller = Controller( *args, **kwargs )
# Find out its IP address so that cluster switches can connect
Intf( 'eth0', node=controller ).updateIP()
return controller
def testRemoteTopo():
"Test remote Node classes using Mininet()/Topo() API"
topo = LinearTopo( 2 )
net = Mininet( topo=topo, host=HostPlacer, switch=SwitchPlacer,
link=RemoteLink, controller=ClusterController )
net.start()
net.pingAll()
net.stop()
# Need to test backwards placement, where each host is on
# a server other than its switch!! But seriously we could just
# do random switch placement rather than completely random
# host placement.
def testRemoteSwitches():
"Test with local hosts and remote switches"
servers = [ 'localhost', 'ubuntu2']
topo = TreeTopo( depth=4, fanout=2 )
net = MininetCluster( topo=topo, servers=servers,
placement=RoundRobinPlacer )
net.start()
net.pingAll()
net.stop()
#
# For testing and demo purposes it would be nice to draw the
# network graph and color it based on server.
# The MininetCluster() class integrates pluggable placement
# functions, for maximum ease of use. MininetCluster() also
# pre-flights and multiplexes server connections.
def testMininetCluster():
"Test MininetCluster()"
servers = [ 'localhost', 'ubuntu2' ]
topo = TreeTopo( depth=3, fanout=3 )
net = MininetCluster( topo=topo, servers=servers,
placement=SwitchBinPlacer )
net.start()
net.pingAll()
net.stop()
def signalTest():
"Make sure hosts are robust to signals"
h = RemoteHost( 'h0', server='ubuntu1' )
h.shell.send_signal( SIGINT )
h.shell.poll()
if h.shell.returncode is None:
print 'OK: ', h, 'has not exited'
else:
print 'FAILURE:', h, 'exited with code', h.shell.returncode
h.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
# testRemoteTopo()
# testRemoteNet()
# testMininetCluster()
# testRemoteSwitches()
signalTest()
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env python
'''
A sanity check for cluster edition
'''
from mininet.examples.cluster import MininetCluster
from mininet.log import info, setLogLevel
from mininet.examples.clustercli import DemoCLI as CLI
from mininet.topo import SingleSwitchTopo
def clusterSanity():
"Sanity check for cluster mode"
topo = SingleSwitchTopo()
net = MininetCluster( topo=topo )
net.start()
CLI( net )
net.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
clusterSanity()
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/python
"CLI for Mininet Cluster Edition prototype demo"
from mininet.cli import CLI
from mininet.log import output, error
nx, graphviz_layout, plt = None, None, None # Will be imported on demand
class DemoCLI( CLI ):
"CLI with additional commands for Cluster Edition demo"
@staticmethod
def colorsFor( seq ):
"Return a list of background colors for a sequence"
colors = [ 'red', 'lightgreen', 'cyan', 'yellow', 'orange',
'magenta', 'pink', 'grey', 'brown',
'white' ]
slen, clen = len( seq ), len( colors )
reps = max( 1, slen / clen )
colors = colors * reps
colors = colors[ 0 : slen ]
return colors
def do_plot( self, line ):
"Plot topology colored by node placement"
# Import networkx if needed
global nx, plt
if not nx:
try:
import networkx as nx
import matplotlib.pyplot as plt
import pygraphviz
except:
error( 'plot requires networkx, matplotlib and pygraphviz - '
'please install them and try again\n' )
return
# Make a networkx Graph
g = nx.Graph()
mn = self.mn
servers, hosts, switches = mn.servers, mn.hosts, mn.switches
hlen, slen = len( hosts ), len( switches )
nodes = hosts + switches
g.add_nodes_from( nodes )
links = [ ( link.intf1.node, link.intf2.node )
for link in self.mn.links ]
g.add_edges_from( links )
# Pick some shapes and colors
# shapes = hlen * [ 's' ] + slen * [ 'o' ]
color = dict( zip( servers, self.colorsFor( servers ) ) )
# Plot it!
pos = nx.graphviz_layout( g )
opts = { 'ax': None, 'font_weight': 'bold',
'width': 2, 'edge_color': 'darkblue' }
hcolors = [ color[ h.server ] for h in hosts ]
scolors = [ color[ s.server ] for s in switches ]
nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800, label='host',
node_color=hcolors, node_shape='s', **opts )
nx.draw_networkx( g, pos=pos, nodelist=switches, node_size=1000,
node_color=scolors, node_shape='o', **opts )
# Get rid of axes, add title, and show
fig = plt.gcf()
ax = plt.gca()
ax.get_xaxis().set_visible( False )
ax.get_yaxis().set_visible( False )
fig.canvas.set_window_title( 'Mininet')
plt.title( 'Node Placement', fontweight='bold' )
plt.show()
def do_status( self, line ):
"Report on node shell status"
nodes = self.mn.hosts + self.mn.switches
for node in nodes:
node.shell.poll()
exited = [ node for node in nodes
if node.shell.returncode is not None ]
if exited:
for node in exited:
output( '%s has exited with code %d\n'
% ( node, node.shell.returncode ) )
else:
output( 'All nodes are still running.\n' )
def do_placement( self, line ):
"Describe node placement"
mn = self.mn
nodes = mn.hosts + mn.switches + mn.controllers
for server in mn.servers:
names = [ n.name for n in nodes if hasattr( n, 'server' )
and n.server == server ]
output( '%s: %s\n' % ( server, ' '.join( names ) ) )
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/python
"clusterdemo.py: demo of Mininet Cluster Edition prototype"
from mininet.examples.cluster import MininetCluster, SwitchBinPlacer
from mininet.topolib import TreeTopo
from mininet.log import setLogLevel
from mininet.examples.clustercli import DemoCLI as CLI
def demo():
"Simple Demo of Cluster Mode"
servers = [ 'localhost', 'ubuntu2', 'ubuntu3' ]
topo = TreeTopo( depth=3, fanout=3 )
net = MininetCluster( topo=topo, servers=servers,
placement=SwitchBinPlacer )
net.start()
CLI( net )
net.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
demo()
+7 -15
View File
@@ -7,23 +7,11 @@ cpu.py: test iperf bandwidth for varying cpu limits
from mininet.net import Mininet
from mininet.node import CPULimitedHost
from mininet.topolib import TreeTopo
from mininet.util import custom
from mininet.log import setLogLevel, output
from mininet.util import custom, waitListening
from mininet.log import setLogLevel, output, info
from time import sleep
def waitListening(client, server, port):
"Wait until server is listening on port"
if not client.cmd('which telnet'):
raise Exception('Could not find telnet')
cmd = ('sh -c "echo A | telnet -e A %s %s"' %
(server.IP(), port))
while 'Connected' not in client.cmd(cmd):
output('waiting for', server,
'to listen on port', port, '\n')
sleep(.5)
def bwtest( cpuLimits, period_us=100000, seconds=5 ):
"""Example/test of link and CPU bandwidth limits
cpu: cpu limit as fraction of overall CPU time"""
@@ -38,7 +26,11 @@ def bwtest( cpuLimits, period_us=100000, seconds=5 ):
host = custom( CPULimitedHost, sched=sched,
period_us=period_us,
cpu=cpu )
net = Mininet( topo=topo, host=host )
try:
net = Mininet( topo=topo, host=host )
except:
info( '*** Skipping host %s\n' % sched )
break
net.start()
net.pingAll()
hosts = [ net.getNodeByName( h ) for h in topo.hosts() ]
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/python
'''
example of using various TCIntf options.
reconfigures a single interface using intf.config()
to use different traffic control commands to test
bandwidth, loss, and delay
'''
from mininet.net import Mininet
from mininet.log import setLogLevel, info
from mininet.link import TCLink
def intfOptions():
"run various traffic control commands on a single interface"
net = Mininet( autoStaticArp=True )
net.addController( 'c0' )
h1 = net.addHost( 'h1' )
h2 = net.addHost( 'h2' )
s1 = net.addSwitch( 's1' )
link1 = net.addLink( h1, s1, cls=TCLink )
link2 = net.addLink( h2, s1 )
net.start()
# 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*** Running iperf to test\n' )
net.iperf()
info( '\n*** Configuring one intf with loss of 50%\n' )
link1.intf1.config( loss=50 )
info( '\n' )
net.iperf( ( h1, h2 ), l4Type='UDP' )
info( '\n*** Configuring one intf with delay of 15ms\n' )
link1.intf1.config( delay='15ms' )
info( '\n*** Run a ping to confirm delay\n' )
net.pingPairFull()
info( '\n*** Done testing\n' )
net.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
intfOptions()
+11 -6
View File
@@ -8,15 +8,14 @@ from mininet.net import Mininet
from mininet.link import TCIntf
from mininet.node import CPULimitedHost
from mininet.topolib import TreeTopo
from mininet.util import custom
from mininet.log import setLogLevel
from mininet.util import custom, quietRun
from mininet.log import setLogLevel, info
def testLinkLimit( net, bw ):
"Run bandwidth limit test"
print '*** Testing network %.2f Mbps bandwidth limit' % bw
net.iperf( )
info( '*** Testing network %.2f Mbps bandwidth limit\n' % bw )
net.iperf()
def limit( bw=10, cpu=.1 ):
"""Example/test of link and CPU bandwidth limits
@@ -25,7 +24,13 @@ def limit( bw=10, cpu=.1 ):
intf = custom( TCIntf, bw=bw )
myTopo = TreeTopo( depth=1, fanout=2 )
for sched in 'rt', 'cfs':
print '*** Testing with', sched, 'bandwidth limiting'
info( '*** Testing with', sched, 'bandwidth limiting\n' )
if sched == 'rt':
release = quietRun( 'uname -r' ).strip('\r\n')
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
info( '*** RT Scheduler is not enabled in your kernel. Skipping this test\n' )
continue
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
net = Mininet( topo=myTopo, intf=intf, host=host )
net.start()
+13 -1
View File
@@ -28,6 +28,8 @@ from mininet.node import UserSwitch, OVSKernelSwitch, Controller
from mininet.topo import Topo
from mininet.log import lg
from mininet.util import irange
from mininet.link import TCLink
from functools import partial
import sys
flush = sys.stdout.flush
@@ -70,13 +72,20 @@ def linearBandwidthTest( lengths ):
switches = { 'reference user': UserSwitch,
'Open vSwitch kernel': OVSKernelSwitch }
# UserSwitch is horribly slow with recent kernels.
# We can reinstate it once its performance is fixed
del switches[ 'reference user' ]
topo = LinearTestTopo( hostCount )
for datapath in switches.keys():
print "*** testing", datapath, "datapath"
Switch = switches[ datapath ]
results[ datapath ] = []
net = Mininet( topo=topo, switch=Switch, controller=Controller, waitConnected=True )
link = partial( TCLink, delay='1ms' )
net = Mininet( topo=topo, switch=Switch,
controller=Controller, waitConnected=True,
link=link )
net.start()
print "*** testing basic connectivity"
for n in lengths:
@@ -84,6 +93,9 @@ def linearBandwidthTest( lengths ):
print "*** testing bandwidth"
for n in lengths:
src, dst = net.hosts[ 0 ], net.hosts[ n ]
# Try to prime the pump to reduce PACKET_INs during test
# since the reference controller is reactive
src.cmd( 'telnet', dst.IP(), '5001' )
print "testing", src.name, "<->", dst.name,
bandwidth = net.iperf( [ src, dst ] )
print bandwidth
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/python
"""
linuxrouter.py: Example network with Linux IP router
This example converts a Node into a router using IP forwarding
already built into Linux.
The topology contains a router with three IP subnets:
- 192.168.1.0/24 (interface IP: 192.168.1.1)
- 172.16.0.0/12 (interface IP: 172.16.0.1)
- 10.0.0.0/8 (interface IP: 10.0.0.1)
It also contains three hosts, one in each subnet:
- h1 (IP: 192.168.1.100)
- h2 (IP: 172.16.0.100)
- h3 (IP: 10.0.0.100)
Routing entries can be added to the routing tables of the
hosts or router using the "ip route add" or "route add" command.
See the man pages for more details.
"""
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.node import Node
from mininet.log import setLogLevel, info
from mininet.cli import CLI
from mininet.util import irange
class LinuxRouter( Node ):
"A Node with IP forwarding enabled."
def config( self, **params ):
super( LinuxRouter, self).config( **params )
# Enable forwarding on the router
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
def terminate( self ):
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
super( LinuxRouter, self ).terminate()
class NetworkTopo( Topo ):
"A simple topology of a router with three subnets (one host in each)."
def build( self, n=2, h=1, **opts ):
router = self.addNode( 'r0', cls=LinuxRouter, ip='192.168.1.1/24' )
h1 = self.addHost( 'h1', ip='192.168.1.100/24', defaultRoute='via 192.168.1.1' )
h2 = self.addHost( 'h2', ip='172.16.0.100/12', defaultRoute='via 172.16.0.1' )
h3 = self.addHost( 'h3', ip='10.0.0.100/8', defaultRoute='via 10.0.0.1' )
self.addLink( h1, router, intfName2='r0-eth1', params2={ 'ip' : '192.168.1.1/24' } )
self.addLink( h2, router, intfName2='r0-eth2', params2={ 'ip' : '172.16.0.1/12' } )
self.addLink( h3, router, intfName2='r0-eth3', params2={ 'ip' : '10.0.0.1/8' } )
def run():
topo = NetworkTopo()
net = Mininet( topo=topo, controller=None ) # no controller needed
net.start()
info( '*** Routing Table on Router\n' )
print net[ 'r0' ].cmd( 'route' )
CLI( net )
net.stop()
if __name__ == '__main__':
setLogLevel( 'info' )
run()
+5 -6
View File
@@ -31,17 +31,16 @@ class SingleSwitchTopo(Topo):
def perfTest():
"Create network and run simple performance test"
topo = SingleSwitchTopo(n=4)
net = Mininet(topo=topo,
host=CPULimitedHost, link=TCLink)
topo = SingleSwitchTopo( n=4 )
net = Mininet( topo=topo,
host=CPULimitedHost, link=TCLink,
autoStaticArp=True )
net.start()
print "Dumping host connections"
dumpNodeConnections(net.hosts)
print "Testing network connectivity"
net.pingAll()
print "Testing bandwidth between h1 and h4"
h1, h4 = net.getNodeByName('h1', 'h4')
net.iperf((h1, h4))
results = net.iperf( ( h1, h4 ), l4Type='UDP' )
net.stop()
if __name__ == '__main__':
+5
View File
@@ -24,6 +24,7 @@ from mininet.log import lg
from mininet.node import Node
from mininet.topolib import TreeTopo
from mininet.link import Link
from mininet.util import waitListening
def TreeNet( depth=1, fanout=2, **kwargs ):
"Convenience function for creating tree networks."
@@ -59,6 +60,10 @@ def sshd( network, cmd='/usr/sbin/sshd', opts='-D',
connectToRootNS( network, switch, ip, routes )
for host in network.hosts:
host.cmd( cmd + ' ' + opts + '&' )
print "*** Waiting for ssh daemons to start"
for server in network.hosts:
waitListening( server=server, port=22, timeout=5 )
print
print "*** Hosts are running sshd at the following addresses:"
print
+13 -1
View File
@@ -12,6 +12,18 @@ import sys
from mininet.util import ensureRoot
from mininet.clean import cleanup
class MininetTestResult( unittest.TextTestResult ):
def addFailure( self, test, err ):
super( MininetTestResult, self ).addFailure( test, err )
cleanup()
def addError( self,test, err ):
super( MininetTestResult, self ).addError( test, err )
cleanup()
class MininetTestRunner( unittest.TextTestRunner ):
def _makeResult( self ):
return MininetTestResult( self.stream, self.descriptions, self.verbosity )
def runTests( testDir, verbosity=1 ):
"discover and run all tests in testDir"
# ensure root and cleanup before starting tests
@@ -20,7 +32,7 @@ def runTests( testDir, verbosity=1 ):
# discover all tests in testDir
testSuite = unittest.defaultTestLoader.discover( testDir )
# run tests
unittest.TextTestRunner( verbosity=verbosity ).run( testSuite )
MininetTestRunner( verbosity=verbosity ).run( testSuite )
if __name__ == '__main__':
# get the directory containing example tests
+14 -12
View File
@@ -6,21 +6,18 @@ Tests for baresshd.py
import unittest
import pexpect
from time import sleep
from mininet.clean import cleanup, sh
class testBareSSHD( unittest.TestCase ):
opts = [ '\(yes/no\)\?', 'Welcome to h1', 'refused', pexpect.EOF, pexpect.TIMEOUT ]
opts = [ 'Welcome to h1', pexpect.EOF, pexpect.TIMEOUT ]
def connected( self ):
"Log into ssh server, check banner, then exit"
p = pexpect.spawn( 'ssh 10.0.0.1 -i /tmp/ssh/test_rsa exit' )
p = pexpect.spawn( 'ssh 10.0.0.1 -o StrictHostKeyChecking=no -i /tmp/ssh/test_rsa exit' )
while True:
index = p.expect( self.opts )
if index == 0:
p.sendline( 'yes' )
elif index == 1:
return True
else:
return False
@@ -37,18 +34,23 @@ class testBareSSHD( unittest.TestCase ):
cmd = ( 'python -m mininet.examples.baresshd '
'-o AuthorizedKeysFile=/tmp/ssh/authorized_keys '
'-o StrictModes=no' )
sh( cmd )
p = pexpect.spawn( cmd )
runOpts = [ 'You may now ssh into h1 at 10.0.0.1',
'after 5 seconds, h1 is not listening on port 22',
pexpect.EOF, pexpect.TIMEOUT ]
while True:
index = p.expect( runOpts )
if index == 0:
break
else:
self.tearDown()
self.fail( 'sshd failed to start in host h1' )
def testSSH( self ):
"Simple test to verify that we can ssh into h1"
result = False
# try to connect up to 3 times; sshd can take a while to start
for _ in range( 3 ):
result = self.connected()
if result:
break
else:
sleep( 1 )
result = self.connected()
self.assertTrue( result )
def tearDown( self ):
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python
'''
A simple sanity check test for cluster edition
'''
import unittest
import pexpect
class clusterSanityCheck( unittest.TestCase ):
prompt = 'mininet>'
def testClusterPingAll( self ):
p = pexpect.spawn( 'python -m mininet.examples.clusterSanity' )
p.expect( self.prompt )
p.sendline( 'pingall' )
p.expect ( '(\d+)% dropped' )
percent = int( p.match.group( 1 ) ) if p.match else -1
self.assertEqual( percent, 0 )
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
if __name__ == '__main__':
unittest.main()
+16 -3
View File
@@ -2,6 +2,17 @@
"""
Test for cpu.py
results format:
sched cpu client MB/s
cfs 45.00% 13254.669841
cfs 40.00% 11822.441399
cfs 30.00% 5112.963009
cfs 20.00% 3449.090009
cfs 10.00% 2271.741564
"""
import unittest
@@ -16,7 +27,9 @@ class testCPU( unittest.TestCase ):
def testCPU( self ):
"Verify that CPU utilization is monotonically decreasing for each scheduler"
p = pexpect.spawn( 'python -m mininet.examples.cpu' )
opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.]+)', pexpect.EOF ]
# matches each line from results( shown above )
opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.]+)',
pexpect.EOF ]
scheds = []
while True:
index = p.expect( opts, timeout=600 )
@@ -26,8 +39,8 @@ class testCPU( unittest.TestCase ):
bw = float( p.match.group( 3 ) )
if sched not in scheds:
scheds.append( sched )
previous_bw = 10 ** 4 # 10 GB/s
self.assertTrue( bw < previous_bw )
else:
self.assertTrue( bw < previous_bw )
previous_bw = bw
else:
break
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env python
"""
Test for intfOptions.py
"""
import unittest
import pexpect
import sys
class testIntfOptions( unittest.TestCase ):
def testIntfOptions( self ):
"verify that intf.config is correctly limiting traffic"
p = pexpect.spawn( 'python -m mininet.examples.intfOptions ' )
tolerance = .8
opts = [ "Results: \['([\d\.]+) .bits/sec",
"Results: \['10M', '([\d\.]+) .bits/sec",
"h(\d+)->h(\d+): (\d)/(\d), rtt min/avg/max/mdev ([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms",
pexpect.EOF ]
while True:
index = p.expect( opts, timeout=600 )
if index == 0:
bw = float( p.match.group( 1 ) )
self.assertGreaterEqual( bw, float( 5 * tolerance ) )
self.assertLessEqual( bw, float( 5 + 5 * ( 1 - tolerance ) ) )
elif index == 1:
BW = 10
measuredBw = float( p.match.group( 1 ) )
loss = ( measuredBw / BW ) * 100
self.assertGreaterEqual( loss, 50 * tolerance )
self.assertLessEqual( loss, 50 + 50 * ( 1 - tolerance ) )
elif index == 2:
delay = float( p.match.group( 6 ) )
self.assertGreaterEqual( delay, 15 * tolerance )
self.assertLessEqual( delay, 15 + 15 * ( 1 - tolerance ) )
else:
break
if __name__ == '__main__':
unittest.main()
+8 -3
View File
@@ -21,7 +21,6 @@ class testLinearBandwidth( unittest.TestCase ):
while True:
index = p.expect( opts, timeout=600 )
if index == 0:
previous_bw = 10 ** 10 # 10 Gbits
count += 1
elif index == 1:
n = int( p.match.group( 1 ) )
@@ -33,11 +32,17 @@ class testLinearBandwidth( unittest.TestCase ):
bw *= 10 ** 6
elif unit[ 0 ] == 'G':
bw *= 10 ** 9
self.assertTrue( bw < previous_bw )
previous_bw = bw
# check that we have a previous result to compare to
if n != 1:
info = ( 'bw: %d bits/s across %d switches, '
'previous: %d bits/s across %d switches' %
( bw, n, previous_bw, previous_n ) )
self.assertTrue( bw < previous_bw, info )
previous_bw, previous_n = bw, n
else:
break
# verify that we received results from at least one switch
self.assertTrue( count > 0 )
if __name__ == '__main__':
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python
"""
Test for linuxrouter.py
"""
import unittest
import pexpect
from mininet.util import quietRun
class testLinuxRouter( unittest.TestCase ):
prompt = 'mininet>'
def testPingall( self ):
"Test connectivity between hosts"
p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' )
p.expect( self.prompt )
p.sendline( 'pingall' )
p.expect ( '(\d+)% dropped' )
percent = int( p.match.group( 1 ) ) if p.match else -1
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
self.assertEqual( percent, 0 )
def testRouterPing( self ):
"Test connectivity from h1 to router"
p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' )
p.expect( self.prompt )
p.sendline( 'h1 ping -c 1 r0' )
p.expect ( '(\d+)% packet loss' )
percent = int( p.match.group( 1 ) ) if p.match else -1
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
self.assertEqual( percent, 0 )
def testTTL( self ):
"Verify that the TTL is decremented"
p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' )
p.expect( self.prompt )
p.sendline( 'h1 ping -c 1 h2' )
p.expect ( 'ttl=(\d+)' )
ttl = int( p.match.group( 1 ) ) if p.match else -1
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
self.assertEqual( ttl, 63 ) # 64 - 1
if __name__ == '__main__':
unittest.main()
+10 -29
View File
@@ -6,12 +6,8 @@ Test for simpleperf.py
import unittest
import pexpect
import re
import sys
from mininet.log import setLogLevel
from mininet.net import Mininet
from mininet.node import CPULimitedHost
from mininet.link import TCLink
from mininet.examples.simpleperf import SingleSwitchTopo
@@ -19,35 +15,20 @@ class testSimplePerf( unittest.TestCase ):
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
def testE2E( self ):
"Run the example and verify ping and iperf results"
"Run the example and verify iperf results"
BW = 10
TOLERANCE = .8
expectedBw = BW * TOLERANCE
p = pexpect.spawn( 'python -m mininet.examples.simpleperf' )
# check ping results
p.expect( "Results: (\d+)% dropped", timeout=120 )
loss = int( p.match.group( 1 ) )
self.assertTrue( 0 < loss < 100 )
# check iperf results
p.expect( "Results: \['([\d\.]+) .bits/sec", timeout=480 )
bw = float( p.match.group( 1 ) )
self.assertTrue( bw > 0 )
p.expect( "Results: \['10M', '([\d\.]+) .bits/sec", timeout=480 )
measuredBw = float( p.match.group( 1 ) )
lowerBound = expectedBw * TOLERANCE
upperBound = expectedBw + expectedBw * ( 1 - TOLERANCE )
self.assertGreaterEqual( measuredBw, lowerBound )
self.assertLessEqual( measuredBw, upperBound )
p.wait()
def testTopo( self ):
"""Import SingleSwitchTopo from example and test connectivity between two hosts
Note: this test may fail very rarely because it is non-deterministic
i.e. links are configured with 10% packet loss, but if we get unlucky and
none or all of the packets are dropped, the test will fail"""
topo = SingleSwitchTopo( n=4 )
net = Mininet( topo=topo, host=CPULimitedHost, link=TCLink )
net.start()
h1, h4 = net.get( 'h1', 'h4' )
# have h1 ping h4 ten times
expectStr = '(\d+) packets transmitted, (\d+) received, (\d+)% packet loss'
output = h1.cmd( 'ping -c 10 %s' % h4.IP() )
m = re.search( expectStr, output )
loss = int( m.group( 3 ) )
net.stop()
self.assertTrue( 0 < loss < 100 )
if __name__ == '__main__':
setLogLevel( 'warning' )
unittest.main()
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python
"""
Test for vlanhost.py
"""
import unittest
import pexpect
import sys
from mininet.util import quietRun
class testVLANHost( unittest.TestCase ):
prompt = 'mininet>'
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
def testVLANTopo( self ):
"Test connectivity (or lack thereof) between hosts in VLANTopo"
p = pexpect.spawn( 'python -m mininet.examples.vlanhost' )
p.expect( self.prompt )
p.sendline( 'pingall 1' ) #ping timeout=1
p.expect( '(\d+)% dropped', timeout=30 ) # there should be 24 failed pings
percent = int( p.match.group( 1 ) ) if p.match else -1
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
self.assertEqual( percent, 80 )
def testSpecificVLAN( self ):
"Test connectivity between hosts on a specific VLAN"
vlan = 1001
p = pexpect.spawn( 'python -m mininet.examples.vlanhost %d' % vlan )
p.expect( self.prompt )
p.sendline( 'h1 ping -c 1 h2' )
p.expect ( '(\d+)% packet loss' )
percent = int( p.match.group( 1 ) ) if p.match else -1
p.expect( self.prompt )
p.sendline( 'h1 ifconfig' )
i = p.expect( ['h1-eth0.%d' % vlan, pexpect.TIMEOUT ], timeout=2 )
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
self.assertEqual( percent, 0 ) # no packet loss on ping
self.assertEqual( i, 0 ) # check vlan intf is present
if __name__ == '__main__':
unittest.main()
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python
"""
vlanhost.py: Host subclass that uses a VLAN tag for the default interface.
Dependencies:
This class depends on the "vlan" package
$ sudo apt-get install vlan
Usage (example uses VLAN ID=1000):
From the command line:
sudo mn --custom vlanhost.py --host vlan,vlan=1000
From a script (see exampleUsage function below):
from functools import partial
from vlanhost import VLANHost
....
host = partial( VLANHost, vlan=1000 )
net = Mininet( host=host, ... )
Directly running this script:
sudo python vlanhost.py 1000
"""
from mininet.node import Host
from mininet.topo import Topo
from mininet.util import quietRun
from mininet.log import error
class VLANHost( Host ):
def config( self, vlan=100, **params ):
"""Configure VLANHost according to (optional) parameters:
vlan: VLAN ID for default interface"""
r = super( Host, self ).config( **params )
intf = self.defaultIntf()
# remove IP from default, "physical" interface
self.cmd( 'ifconfig %s inet 0' % intf )
# create VLAN interface
self.cmd( 'vconfig add %s %d' % ( intf, vlan ) )
# assign the host's IP to the VLAN interface
self.cmd( 'ifconfig %s.%d inet %s' % ( intf, vlan, params['ip'] ) )
# update the intf name and host's intf map
newName = '%s.%d' % ( intf, vlan )
# update the (Mininet) interface to refer to VLAN interface name
intf.name = newName
# add VLAN interface to host's name to intf map
self.nameToIntf[ newName ] = intf
return r
hosts = { 'vlan': VLANHost }
def exampleAllHosts( vlan ):
"""Simple example of how VLANHost can be used in a script"""
# This is where the magic happens...
host = partial( VLANHost, vlan=vlan )
# vlan (type: int): VLAN ID to be used by all hosts
# Start a basic network using our VLANHost
topo = SingleSwitchTopo( k=2 )
net = Mininet( host=host, topo=topo )
net.start()
CLI( net )
net.stop()
class VLANStarTopo( Topo ):
"""Example topology that uses host in multiple VLANs
The topology has a single switch. There are k VLANs with
n hosts in each, all connected to the single switch. There
are also n hosts that are not in any VLAN, also connected to
the switch."""
def build( self, k=2, n=2, vlanBase=100 ):
s1 = self.addSwitch( 's1' )
for i in range( k ):
vlan = vlanBase + i
for j in range(n):
name = 'h%d-%d' % ( j+1, vlan )
h = self.addHost( name, cls=VLANHost, vlan=vlan )
self.addLink( h, s1 )
for j in range( n ):
h = self.addHost( 'h%d' % (j+1) )
self.addLink( h, s1 )
def exampleCustomTags( vlan ):
"""Simple example that exercises VLANStarTopo"""
net = Mininet( topo=VLANStarTopo() )
net.start()
CLI( net )
net.stop()
if __name__ == '__main__':
import sys
from functools import partial
from mininet.net import Mininet
from mininet.cli import CLI
from mininet.topo import SingleSwitchTopo
from mininet.log import setLogLevel
setLogLevel( 'info' )
if not quietRun( 'which vconfig' ):
error( "Cannot find command 'vconfig'\nThe packge",
"'vlan' is required in Ubuntu or Debian,",
"or 'vconfig' in Fedora\n" )
exit()
try:
vlan = int( sys.argv[ 1 ] )
except Exception:
vlan = None
if vlan:
exampleAllHosts( vlan )
else:
exampleCustomTags( vlan )
+21 -12
View File
@@ -21,6 +21,21 @@ def sh( cmd ):
info( cmd + '\n' )
return Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ]
def killprocs( pattern ):
"Reliably terminate processes matching a pattern (including args)"
sh( 'pkill -9 -f %s' % pattern )
# Make sure they are gone
while True:
try:
pids = co( 'pgrep -f %s' % pattern )
except:
pids = ''
if pids:
sh( 'pkill -f 9 mininet:' )
sleep( .5 )
else:
break
def cleanup():
"""Clean up junk which might be left over from old runs;
do fast stuff before slow dp and link removal!"""
@@ -70,17 +85,11 @@ def cleanup():
sh( "ip link del " + link )
info( "*** Killing stale mininet node processes\n" )
sh( 'pkill -9 -f mininet:' )
# Make sure they are gone
while True:
try:
pids = co( 'pgrep -f mininet:'.split() )
except:
pids = ''
if pids:
sh( 'pkill -f 9 mininet:' )
sleep( .5 )
else:
break
killprocs( 'mininet:' )
info ( "*** Shutting down stale tunnels\n" )
killprocs( 'Tunnel=Ethernet' )
killprocs( '.ssh/mn')
sh( 'rm -f ~/.ssh/mn/*' )
info( "*** Cleanup complete.\n" )
+38 -12
View File
@@ -36,7 +36,8 @@ import atexit
from mininet.log import info, output, error
from mininet.term import makeTerms, runX11
from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
from mininet.util import ( quietRun, isShellBuiltin, dumpNodeConnections,
dumpPorts )
class CLI( Cmd ):
"Simple command-line interface to talk to nodes."
@@ -75,7 +76,7 @@ class CLI( Cmd ):
for node in self.mn.values():
while node.waiting:
node.sendInt()
node.monitor()
node.waitOutput()
if self.isatty():
quietRun( 'stty echo sane intr "^C"' )
self.cmdloop()
@@ -127,12 +128,17 @@ class CLI( Cmd ):
nodes = ' '.join( sorted( self.mn ) )
output( 'available nodes are: \n%s\n' % nodes )
def do_ports( self, line ):
"display ports and interfaces for each switch"
dumpPorts( self.mn.switches )
def do_net( self, _line ):
"List network connections."
dumpNodeConnections( self.mn.values() )
def do_sh( self, line ):
"Run an external shell command"
"""Run an external shell command
Usage: sh [cmd args]"""
call( line, shell=True )
# do_py() and do_px() need to catch any exception during eval()/exec()
@@ -182,7 +188,8 @@ class CLI( Cmd ):
self.mn.pingPairFull()
def do_iperf( self, line ):
"Simple iperf TCP test between two (optionally specified) hosts."
"""Simple iperf TCP test between two (optionally specified) hosts.
Usage: iperf node1 node2"""
args = line.split()
if not args:
self.mn.iperf()
@@ -201,7 +208,8 @@ class CLI( Cmd ):
error( 'invalid number of args: iperf src dst\n' )
def do_iperfudp( self, line ):
"Simple iperf UDP test between two (optionally specified) hosts."
"""Simple iperf UDP test between two (optionally specified) hosts.
Usage: iperfudp bw node1 node2"""
args = line.split()
if not args:
self.mn.iperf( l4Type='UDP' )
@@ -233,7 +241,8 @@ class CLI( Cmd ):
output( '%s\n' % repr( node ) )
def do_link( self, line ):
"Bring link(s) between two nodes up or down."
"""Bring link(s) between two nodes up or down.
Usage: link node1 node2 [up/down]"""
args = line.split()
if len(args) != 3:
error( 'invalid number of args: link end1 end2 [up down]\n' )
@@ -243,7 +252,8 @@ class CLI( Cmd ):
self.mn.configLinkStatus( *args )
def do_xterm( self, line, term='xterm' ):
"Spawn xterm(s) for the given node(s)."
"""Spawn xterm(s) for the given node(s).
Usage: xterm node1 node2 ..."""
args = line.split()
if not args:
error( 'usage: %s node1 node2 ...\n' % term )
@@ -257,7 +267,8 @@ class CLI( Cmd ):
def do_x( self, line ):
"""Create an X11 tunnel to the given node,
optionally starting a client."""
optionally starting a client.
Usage: x node [cmd args]"""
args = line.split()
if not args:
error( 'usage: x node [cmd args]...\n' )
@@ -267,7 +278,8 @@ class CLI( Cmd ):
self.mn.terms += runX11( node, cmd )
def do_gterm( self, line ):
"Spawn gnome-terminal(s) for the given node(s)."
"""Spawn gnome-terminal(s) for the given node(s).
Usage: gterm node1 node2 ..."""
self.do_xterm( line, term='gterm' )
def do_exit( self, _line ):
@@ -288,7 +300,8 @@ class CLI( Cmd ):
return isatty( self.stdin.fileno() )
def do_noecho( self, line ):
"Run an interactive command with echoing turned off."
"""Run an interactive command with echoing turned off.
Usage: noecho [cmd args]"""
if self.isatty():
quietRun( 'stty -echo' )
self.default( line )
@@ -296,7 +309,8 @@ class CLI( Cmd ):
quietRun( 'stty echo' )
def do_source( self, line ):
"Read commands from an input file."
"""Read commands from an input file.
Usage: source <file>"""
args = line.split()
if len(args) != 1:
error( 'usage: source <file>\n' )
@@ -315,7 +329,8 @@ class CLI( Cmd ):
self.inputFile = None
def do_dpctl( self, line ):
"Run dpctl (or ovs-ofctl) command on all switches."
"""Run dpctl (or ovs-ofctl) command on all switches.
Usage: dpctl command [arg1] [arg2] ..."""
args = line.split()
if len(args) < 1:
error( 'usage: dpctl command [arg1] [arg2] ...\n' )
@@ -331,6 +346,11 @@ class CLI( Cmd ):
elapsed = time.time() - start
self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
def do_links( self, line ):
"Report on links"
for link in self.mn.links:
print link, link.status()
def default( self, line ):
"""Called on an input line when the command prefix is not recognized.
Overridden to run shell commands when a node is the first CLI argument.
@@ -395,6 +415,12 @@ class CLI( Cmd ):
# read data but before it has been printed.
node.sendInt()
def precmd( self, line ):
"allow for comments in the cli"
if '#' in line:
line = line.split( '#' )[ 0 ]
return line
# Helper functions
+85 -34
View File
@@ -32,7 +32,8 @@ class Intf( object ):
"Basic interface object that can configure itself."
def __init__( self, name, node=None, port=None, link=None, **params ):
def __init__( self, name, node=None, port=None, link=None,
mac=None, srcNode=None, **params ):
"""name: interface name (e.g. h1-eth0)
node: owning node (where this intf most likely lives)
link: parent link if we're part of a link
@@ -40,7 +41,13 @@ class Intf( object ):
self.node = node
self.name = name
self.link = link
self.mac, self.ip, self.prefixLen = None, None, None
self.mac = mac
self.ip, self.prefixLen = None, None
# if interface is lo, we know the ip is 127.0.0.1.
# This saves an ifconfig command per node
if self.name == 'lo':
self.ip = '127.0.0.1'
# Add to node (and move ourselves if necessary )
node.addIntf( self, port=port )
# Save params for future reference
@@ -79,7 +86,9 @@ class Intf( object ):
def updateIP( self ):
"Return updated IP address based on ifconfig"
ifconfig = self.ifconfig()
# use pexec instead of node.cmd so that we dont read
# backgrounded output from the cli.
ifconfig, _err, _exitCode = self.node.pexec( 'ifconfig %s' % self.name )
ips = self._ipMatchRegex.findall( ifconfig )
self.ip = ips[ 0 ] if ips else None
return self.ip
@@ -91,6 +100,19 @@ class Intf( object ):
self.mac = macs[ 0 ] if macs else None
return self.mac
# Instead of updating ip and mac separately,
# use one ifconfig call to do it simultaneously.
# This saves an ifconfig command, which improves performance.
def updateAddr( self ):
"Return IP address and MAC address based on ifconfig."
ifconfig = self.ifconfig()
ips = self._ipMatchRegex.findall( ifconfig )
macs = self._macMatchRegex.findall( ifconfig )
self.ip = ips[ 0 ] if ips else None
self.mac = macs[ 0 ] if macs else None
return self.ip, self.mac
def IP( self ):
"Return IP address"
return self.ip
@@ -102,8 +124,15 @@ class Intf( object ):
def isUp( self, setUp=False ):
"Return whether interface is up"
if setUp:
self.ifconfig( 'up' )
return "UP" in self.ifconfig()
cmdOutput = self.ifconfig( 'up' )
# no output indicates success
if cmdOutput:
error( "Error setting %s up: %s " % ( self.name, cmdOutput ) )
return False
else:
return True
else:
return "UP" in self.ifconfig()
def rename( self, newname ):
"Rename interface"
@@ -154,8 +183,6 @@ class Intf( object ):
self.setParam( r, 'setIP', ip=ip )
self.setParam( r, 'isUp', up=up )
self.setParam( r, 'ifconfig', ifconfig=ifconfig )
self.updateIP()
self.updateMAC()
return r
def delete( self ):
@@ -165,6 +192,14 @@ class Intf( object ):
# Link may have been dumped into root NS
quietRun( 'ip link del ' + self.name )
def status( self ):
"Return intf status as a string"
links, err_, result_ = self.node.pexec( 'ip link show' )
if self.name in links:
return "OK"
else:
return "MISSING"
def __repr__( self ):
return '<%s %s>' % ( self.__class__.__name__, self.name )
@@ -328,7 +363,7 @@ class Link( object ):
Other types of links could be tunnels, link emulators, etc.."""
def __init__( self, node1, node2, port1=None, port2=None,
intfName1=None, intfName2=None,
intfName1=None, intfName2=None, addr1=None, addr2=None,
intf=Intf, cls1=None, cls2=None, params1=None,
params2=None ):
"""Create veth link to another node, making two new interfaces.
@@ -343,65 +378,81 @@ class Link( object ):
params1: parameters for interface 1
params2: parameters for interface 2"""
# This is a bit awkward; it seems that having everything in
# params would be more orthogonal, but being able to specify
# in-line arguments is more convenient!
if port1 is None:
port1 = node1.newPort()
if port2 is None:
port2 = node2.newPort()
# 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 )
if port1 is not None:
params1[ 'port' ] = port1
if port2 is not None:
params2[ 'port' ] = port2
if 'port' not in params1:
params1[ 'port' ] = node1.newPort()
if 'port' not in params2:
params2[ 'port' ] = node2.newPort()
if not intfName1:
intfName1 = self.intfName( node1, port1 )
intfName1 = self.intfName( node1, params1[ 'port' ] )
if not intfName2:
intfName2 = self.intfName( node2, port2 )
intfName2 = self.intfName( node2, params2[ 'port' ] )
self.makeIntfPair( intfName1, intfName2 )
self.makeIntfPair( intfName1, intfName2, addr1, addr2 )
if not cls1:
cls1 = intf
if not cls2:
cls2 = intf
if not params1:
params1 = {}
if not params2:
params2 = {}
intf1 = cls1( name=intfName1, node=node1, port=port1,
link=self, **params1 )
intf2 = cls2( name=intfName2, node=node2, port=port2,
link=self, **params2 )
intf1 = cls1( name=intfName1, node=node1,
link=self, mac=addr1, **params1 )
intf2 = cls2( name=intfName2, node=node2,
link=self, mac=addr2, **params2 )
# All we are is dust in the wind, and our two interfaces
self.intf1, self.intf2 = intf1, intf2
@classmethod
def intfName( cls, node, n ):
def intfName( _self, node, n ):
"Construct a canonical interface name node-ethN for interface n."
return node.name + '-eth' + repr( n )
@classmethod
def makeIntfPair( cls, intf1, intf2 ):
def makeIntfPair( _cls, intfname1, intfname2, addr1=None, addr2=None ):
"""Create pair of interfaces
intf1: name of interface 1
intf2: name of interface 2
(override this class method [and possibly delete()]
intfname1: name of interface 1
intfname2: name of interface 2
(override this method [and possibly delete()]
to change link type)"""
makeIntfPair( intf1, intf2 )
return makeIntfPair( intfname1, intfname2, addr1, addr2 )
def delete( self ):
"Delete this link"
self.intf1.delete()
self.intf2.delete()
def stop( self ):
"Override to stop and clean up link as needed"
pass
def status( self ):
"Return link status as a string"
return "(%s %s)" % ( self.intf1.status(), self.intf2.status() )
def __str__( self ):
return '%s<->%s' % ( self.intf1, self.intf2 )
class TCLink( Link ):
"Link with symmetric TC interfaces configured via opts"
def __init__( self, node1, node2, port1=None, port2=None,
intfName1=None, intfName2=None, **params ):
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)
params2=params )
+56 -27
View File
@@ -90,9 +90,11 @@ import os
import re
import select
import signal
import random
import copy
from time import sleep
from itertools import chain, groupby
from math import ceil
from mininet.cli import CLI
from mininet.log import info, error, debug, output, warn
@@ -155,6 +157,7 @@ class Mininet( object ):
self.hosts = []
self.switches = []
self.controllers = []
self.links = []
self.nameToNode = {} # name to Node (Host/Switch) objects
@@ -326,13 +329,19 @@ class Mininet( object ):
port1: source port
port2: dest port
returns: link object"""
mac1 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
mac2 = macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff | 0x020000000000 )
defaults = { 'port1': port1,
'port2': port2,
'addr1': mac1,
'addr2': mac2,
'intf': self.intf }
defaults.update( params )
if not cls:
cls = self.link
return cls( node1, node2, **defaults )
link = cls( node1, node2, **defaults )
self.links.append( link )
return link
def configHosts( self ):
"Configure a set of hosts."
@@ -350,7 +359,6 @@ class Mininet( object ):
# quietRun( 'renice +18 -p ' + repr( host.pid ) )
# This may not be the right place to do this, but
# it needs to be done somewhere.
host.cmd( 'ifconfig lo up' )
info( '\n' )
def buildFromTopo( self, topo=None ):
@@ -473,6 +481,11 @@ class Mininet( object ):
for switch in self.switches:
info( switch.name + ' ' )
switch.stop()
switch.terminate()
info( '\n' )
info( '*** Stopping %i links\n' % len( self.links ) )
for link in self.links:
link.stop()
info( '\n' )
info( '*** Stopping %i hosts\n' % len( self.hosts ) )
for host in self.hosts:
@@ -549,8 +562,11 @@ class Mininet( object ):
opts = ''
if timeout:
opts = '-W %s' % timeout
result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
sent, received = self._parsePing( result )
if dest.intfs:
result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
sent, received = self._parsePing( result )
else:
sent, received = 0, 0
packets += sent
if received > sent:
error( '*** Error: received too many packets' )
@@ -590,6 +606,8 @@ class Mininet( object ):
r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
m = re.search( r, pingOutput )
if m is None:
if received == 0:
return errorTuple
error( '*** Error: could not parse ping output: %s\n' %
pingOutput )
return errorTuple
@@ -674,7 +692,10 @@ class Mininet( object ):
"""Run iperf between two hosts.
hosts: list of hosts; if None, uses opposite hosts
l4Type: string, one of [ TCP, UDP ]
returns: results two-element array of server and client speeds"""
returns: results two-element array of [ server, client ] speeds
note: send() is buffered, so client rate can be much higher than
the actual transmission rate; on an unloaded system, server
rate should be much closer to the actual receive rate"""
if not quietRun( 'which telnet' ):
error( 'Cannot find telnet in $PATH - required for iperf test' )
return
@@ -722,33 +743,41 @@ class Mininet( object ):
duration: test duration in seconds
returns a single list of measured CPU fractions as floats.
"""
cores = int( quietRun( 'nproc' ) )
pct = cpu * 100
info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
hosts = self.hosts
cores = int( quietRun( 'nproc' ) )
# number of processes to run a while loop on per host
num_procs = int( ceil( cores * cpu ) )
pids = {}
for h in hosts:
h.cmd( 'while true; do a=1; done &' )
pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
pids_str = ",".join(["%s" % pid for pid in pids])
cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
# It's a shame that this is what pylint prefers
outputs = []
for _ in range( duration ):
pids[ h ] = []
for _core in range( num_procs ):
h.cmd( 'while true; do a=1; done &' )
pids[ h ].append( h.cmd( 'echo $!' ).strip() )
outputs = {}
time = {}
# get the initial cpu time for each host
for host in hosts:
outputs[ host ] = []
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
time[ host ] = float( f.read() )
for _ in range( 5 ):
sleep( 1 )
outputs.append( quietRun( cmd ).strip() )
for h in hosts:
h.cmd( 'kill %1' )
for host in hosts:
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
readTime = float( f.read() )
outputs[ host ].append( ( ( readTime - time[ host ] )
/ 1000000000 ) / cores * 100 )
time[ host ] = readTime
for h, pids in pids.items():
for pid in pids:
h.cmd( 'kill -9 %s' % pid )
cpu_fractions = []
for test_output in outputs:
# Split by line. Ignore first line, which looks like this:
# PID %CPU COMMAND\n
for line in test_output.split('\n')[1:]:
r = r'\d+\s*(\d+\.\d+)'
m = re.search( r, line )
if m is None:
error( '*** Error: could not extract CPU fraction: %s\n' %
line )
return None
cpu_fractions.append( float( m.group( 1 ) ) )
for _host, outputs in outputs.items():
for pct in outputs:
cpu_fractions.append( pct )
output( '*** Results: %s\n' % cpu_fractions )
return cpu_fractions
+81 -41
View File
@@ -80,8 +80,8 @@ class Node( object ):
# Make sure class actually works
self.checkSetup()
self.name = name
self.inNamespace = inNamespace
self.name = params.get( 'name', name )
self.inNamespace = params.get( 'inNamespace', inNamespace )
# Stash configuration parameters for future reference
self.params = params
@@ -116,29 +116,28 @@ class Node( object ):
return node or cls.inToNode.get( fd )
# Command support via shell process in namespace
def startShell( self ):
def startShell( self, mnopts=None ):
"Start a shell process for running commands"
if self.shell:
error( "%s: shell is already running" )
error( "%s: shell is already running\n" % self.name )
return
# mnexec: (c)lose descriptors, (d)etach from tty,
# (p)rint pid, and run in (n)amespace
opts = '-cd'
opts = '-cd' if mnopts is None else mnopts
if self.inNamespace:
opts += 'n'
# bash -m: enable job control, i: force interactive
# -s: pass $* to shell, and make process easy to find in ps
# prompt is set to sentinel chr( 127 )
os.environ[ 'PS1' ] = chr( 127 )
cmd = [ 'mnexec', opts, 'bash', '--norc', '-mis', 'mininet:' + self.name ]
cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ),
'bash', '--norc', '-mis', 'mininet:' + self.name ]
# Spawn a shell subprocess in a pseudo-tty, to disable buffering
# in the subprocess and insulate it from signals (e.g. SIGINT)
# received by the parent
master, slave = pty.openpty()
self.shell = Popen( cmd, stdin=slave, stdout=slave, stderr=slave,
self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave,
close_fds=False )
self.stdin = os.fdopen( master )
self.stdin = os.fdopen( master, 'rw' )
self.stdout = self.stdin
self.pid = self.shell.pid
self.pollOut = select.poll()
@@ -160,6 +159,13 @@ class Node( object ):
self.pollOut.poll()
self.waiting = False
self.cmd( 'stty -echo' )
self.cmd( 'set +m' )
def _popen( self, cmd, **params ):
"""Internal method: spawn and return a process
cmd: command to run (list)
params: parameters to Popen()"""
return Popen( cmd, **params )
def cleanup( self ):
"Help python collect its garbage."
@@ -205,7 +211,8 @@ class Node( object ):
def terminate( self ):
"Send kill signal to Node and clean up after it."
if self.shell:
os.killpg( self.pid, signal.SIGHUP )
if self.shell.poll() is None:
os.killpg( self.shell.pid, signal.SIGHUP )
self.cleanup()
def stop( self ):
@@ -238,29 +245,35 @@ class Node( object ):
# Replace empty commands with something harmless
cmd = 'echo -n'
self.lastCmd = cmd
if printPid and not isShellBuiltin( cmd ):
if len( cmd ) > 0 and cmd[ -1 ] == '&':
# print ^A{pid}\n so monitor() can set lastPid
cmd += ' printf "\\001%d\n" $! \n'
else:
cmd = 'mnexec -p ' + cmd
# if a builtin command is backgrounded, it still yields a PID
if len( cmd ) > 0 and cmd[ -1 ] == '&':
# print ^A{pid}\n so monitor() can set lastPid
cmd += ' printf "\\001%d\\012" $! '
elif printPid and not isShellBuiltin( cmd ):
cmd = 'mnexec -p ' + cmd
self.write( cmd + '\n' )
self.lastPid = None
self.waiting = True
def sendInt( self, intr=chr( 3 ) ):
"Interrupt running command."
debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
self.write( intr )
def monitor( self, timeoutms=None, findPid=True ):
"""Monitor and return the output of a command.
Set self.waiting to False if command has completed.
timeoutms: timeout in ms or None to wait indefinitely."""
timeoutms: timeout in ms or None to wait indefinitely
findPid: look for PID from mnexec -p"""
self.waitReadable( timeoutms )
data = self.read( 1024 )
pidre = r'\[\d+\] \d+\r\n'
# Look for PID
marker = chr( 1 ) + r'\d+\r\n'
if findPid and chr( 1 ) in data:
# suppress the job and PID of a backgrounded command
if re.findall( pidre, data ):
data = re.sub( pidre, '', data )
# Marker can be read in chunks; continue until all of it is read
while not re.findall( marker, data ):
data += self.read( 1024 )
@@ -277,7 +290,7 @@ class Node( object ):
data = data.replace( chr( 127 ), '' )
return data
def waitOutput( self, verbose=False ):
def waitOutput( self, verbose=False, findPid=True ):
"""Wait for a command to complete.
Completion is signaled by a sentinel character, ASCII(127)
appearing in the output stream. Wait for the sentinel and return
@@ -326,18 +339,19 @@ class Node( object ):
# popen( cmd, arg1, arg2... )
cmd = list( args )
# Attach to our namespace using mnexec -a
mncmd = defaults[ 'mncmd' ]
del defaults[ 'mncmd' ]
cmd = mncmd + cmd
cmd = defaults.pop( 'mncmd' ) + cmd
# Shell requires a string, not a list!
if defaults.get( 'shell', False ):
cmd = ' '.join( cmd )
return Popen( cmd, **defaults )
popen = self._popen( cmd, **defaults )
return popen
def pexec( self, *args, **kwargs ):
"""Execute a command using popen
returns: out, err, exitcode"""
popen = self.popen( *args, **kwargs)
popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
**kwargs )
# Warning: this can fail with large numbers of fds!
out, err = popen.communicate()
exitcode = popen.wait()
return out, err, exitcode
@@ -356,20 +370,22 @@ class Node( object ):
return max( self.ports.values() ) + 1
return self.portBase
def addIntf( self, intf, port=None ):
def addIntf( self, intf, port=None, moveIntfFn=moveIntf ):
"""Add an interface.
intf: interface
port: port number (optional, typically OpenFlow port number)"""
port: port number (optional, typically OpenFlow port number)
moveIntfFn: function to move interface (optional)"""
if port is None:
port = self.newPort()
self.intfs[ port ] = intf
self.ports[ intf ] = port
self.nameToIntf[ intf.name ] = intf
debug( '\n' )
debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
debug( 'added intf %s (%d) to node %s\n' % (
intf, port, self.name ) )
if self.inNamespace:
debug( 'moving', intf, 'into namespace for', self.name, '\n' )
moveIntf( intf.name, self )
moveIntfFn( intf.name, self )
def defaultIntf( self ):
"Return interface for lowest port"
@@ -601,6 +617,12 @@ class CPULimitedHost( Host ):
# still does better with larger period values.
self.period_us = kwargs.get( 'period_us', 100000 )
self.sched = sched
if self.sched == 'rt':
release = quietRun( 'uname -r' ).strip('\r\n')
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
exit( 1 )
self.rtprio = 20
def cgroupSet( self, param, value, resource='cpu' ):
@@ -631,8 +653,8 @@ class CPULimitedHost( Host ):
args: Popen() args, single list, or string
kwargs: Popen() keyword args"""
# Tell mnexec to execute command in our cgroup
mncmd = [ 'mnexec', '-da', str( self.pid ),
'-g', self.name ]
mncmd = [ 'mnexec', '-g', self.name,
'-da', str( self.pid ) ]
if self.sched == 'rt':
mncmd += [ '-r', str( self.rtprio ) ]
return Host.popen( self, *args, mncmd=mncmd, **kwargs )
@@ -656,7 +678,7 @@ class CPULimitedHost( Host ):
"Internal method: return parameters for RT bandwidth"
pstr, qstr = 'rt_period_us', 'rt_runtime_us'
# RT uses wall clock time for period and quota
quota = int( self.period_us * f * numCores() )
quota = int( self.period_us * f )
return pstr, qstr, self.period_us, quota
def cfsInfo( self, f):
@@ -888,7 +910,9 @@ class UserSwitch( Switch ):
def connected( self ):
"Is the switch connected to a controller?"
return 'remote.is-connected=true' in self.dpctl( 'status' )
status = self.dpctl( 'status' )
return ( 'remote.is-connected=true' in status and
'local.is-connected=true' in status )
@staticmethod
def TCReapply( intf ):
@@ -924,7 +948,6 @@ class UserSwitch( Switch ):
for c in controllers ] )
ofdlog = '/tmp/' + self.name + '-ofd.log'
ofplog = '/tmp/' + self.name + '-ofp.log'
self.cmd( 'ifconfig lo up' )
intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
@@ -975,7 +998,6 @@ class OVSLegacyKernelSwitch( Switch ):
def start( self, controllers ):
"Start up kernel datapath."
ofplog = '/tmp/' + self.name + '-ofp.log'
quietRun( 'ifconfig lo up' )
# Delete local datapath if it exists;
# then create a new one monitoring the given interfaces
self.cmd( 'ovs-dpctl del-dp ' + self.dp )
@@ -1093,9 +1115,6 @@ class OVSSwitch( Switch ):
if self.inNamespace:
raise Exception(
'OVS kernel switch does not work in a namespace' )
# We should probably call config instead, but this
# requires some rethinking...
self.cmd( 'ifconfig lo up' )
# Annoyingly, --if-exists option seems not to work
self.cmd( 'ovs-vsctl del-br', self )
int( self.dpid, 16 ) # DPID must be a hex string
@@ -1103,7 +1122,8 @@ class OVSSwitch( Switch ):
intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
'-- set Interface %s ' % intf +
'ofport_request=%s ' % self.ports[ intf ]
for intf in self.intfList() if not intf.IP() )
for intf in self.intfList()
if self.ports[ intf ] and not intf.IP() )
clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
for c in controllers )
if self.listenPort:
@@ -1200,7 +1220,6 @@ class IVSSwitch(Switch):
logfile = '/tmp/ivs.%s.log' % self.name
self.cmd( 'ifconfig lo up' )
self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
def stop( self ):
@@ -1240,7 +1259,6 @@ class Controller( Node ):
self.protocol = protocol
Node.__init__( self, name, inNamespace=inNamespace,
ip=ip, **params )
self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
self.checkListening()
def checkListening( self ):
@@ -1269,12 +1287,13 @@ class Controller( Node ):
if self.cdir is not None:
self.cmd( 'cd ' + self.cdir )
self.cmd( self.command + ' ' + self.cargs % self.port +
' 1>' + cout + ' 2>' + cout + '&' )
' 1>' + cout + ' 2>' + cout + ' &' )
self.execed = False
def stop( self ):
"Stop controller."
self.cmd( 'kill %' + self.command )
self.cmd( 'wait %' + self.command )
self.terminate()
def IP( self, intf=None ):
@@ -1329,6 +1348,27 @@ class NOX( Controller ):
cdir=noxCoreDir,
**kwargs )
class RYU( Controller ):
"Controller to run Ryu application"
def __init__( self, name, *ryuArgs, **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 )
class RemoteController( Controller ):
"Controller running outside of Mininet's control."
+117 -23
View File
@@ -40,24 +40,47 @@ class testOptionsTopoCommon( object ):
switchClass = None # overridden in subclasses
def runOptionsTopoTest( self, n, hopts=None, lopts=None ):
def runOptionsTopoTest( self, n, msg, hopts=None, lopts=None ):
"Generic topology-with-options test runner."
mn = Mininet( topo=SingleSwitchOptionsTopo( n=n, hopts=hopts,
lopts=lopts ),
host=CPULimitedHost, link=TCLink,
switch=self.switchClass )
switch=self.switchClass, waitConnected=True )
dropped = mn.run( mn.ping )
self.assertEqual( dropped, 0 )
hoptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in hopts.items() )
loptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in lopts.items() )
msg += ( '%s%% of pings were dropped during mininet.ping().\n'
'Topo = SingleSwitchTopo, %s hosts\n'
'hopts = %s\n'
'lopts = %s\n'
'host = CPULimitedHost\n'
'link = TCLink\n'
'Switch = %s\n'
% ( dropped, n, hoptsStr, loptsStr, self.switchClass ) )
def assertWithinTolerance(self, measured, expected, tolerance_frac):
self.assertEqual( dropped, 0, msg=msg )
def assertWithinTolerance( self, measured, expected, tolerance_frac, msg ):
"""Check that a given value is within a tolerance of expected
tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance.
"""
self.assertGreaterEqual( float(measured),
float(expected) * tolerance_frac )
self.assertLessEqual( float( measured ),
float(expected) + (1-tolerance_frac)
* float( expected ) )
upperBound = ( float( expected ) + ( 1 - tolerance_frac ) *
float( expected ) )
lowerBound = float( expected ) * tolerance_frac
info = ( 'measured value is out of bounds\n'
'expected value: %s\n'
'measured value: %s\n'
'failure tolerance: %s\n'
'upper bound: %s\n'
'lower bound: %s\n'
% ( expected, measured, tolerance_frac,
upperBound, lowerBound ) )
msg += info
self.assertGreaterEqual( float( measured ),lowerBound, msg=msg )
self.assertLessEqual( float( measured ), upperBound, msg=msg )
def testCPULimits( self ):
"Verify topology creation with CPU limits set for both schedulers."
@@ -67,47 +90,99 @@ class testOptionsTopoCommon( object ):
#self.runOptionsTopoTest( N, hopts=hopts )
mn = Mininet( SingleSwitchOptionsTopo( n=N, hopts=hopts ),
host=CPULimitedHost, switch=self.switchClass )
host=CPULimitedHost, switch=self.switchClass,
waitConnected=True )
mn.start()
results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
mn.stop()
hostUsage = '\n'.join( 'h%s: %s' %
( n + 1, results[ ( n - 1 ) * 5: ( n * 5 ) - 1 ] )
for n in range( N ) )
hoptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in hopts.items() )
msg = ( '\nTesting cpu limited to %d%% of cpu per host\n'
'cpu usage percent per host:\n%s\n'
'Topo = SingleSwitchTopo, %s hosts\n'
'hopts = %s\n'
'host = CPULimitedHost\n'
'Switch = %s\n'
% ( CPU_FRACTION * 100, hostUsage, N, hoptsStr, self.switchClass ) )
for pct in results:
#divide cpu by 100 to convert from percentage to fraction
self.assertWithinTolerance( pct/100, CPU_FRACTION, CPU_TOLERANCE )
self.assertWithinTolerance( pct/100, CPU_FRACTION,
CPU_TOLERANCE, msg )
def testLinkBandwidth( self ):
"Verify that link bandwidths are accurate within a bound."
BW = .5 # Mbps
if self.switchClass is UserSwitch:
self.skipTest ( 'UserSwitch has very poor performance, so skip for now' )
BW = 5 # Mbps
BW_TOLERANCE = 0.8 # BW fraction below which test should fail
# Verify ability to create limited-link topo first;
lopts = { 'bw': BW, 'use_htb': True }
# Also verify correctness of limit limitng within a bound.
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
link=TCLink, switch=self.switchClass )
link=TCLink, switch=self.switchClass,
waitConnected=True )
bw_strs = mn.run( mn.iperf, format='m' )
for bw_str in bw_strs:
bw = float( bw_str.split(' ')[0] )
self.assertWithinTolerance( bw, BW, BW_TOLERANCE )
loptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in lopts.items() )
msg = ( '\nTesting link bandwidth limited to %d Mbps per link\n'
'iperf results[ client, server ]: %s\n'
'Topo = SingleSwitchTopo, %s hosts\n'
'Link = TCLink\n'
'lopts = %s\n'
'host = default\n'
'switch = %s\n'
% ( BW, bw_strs, N, loptsStr, self.switchClass ) )
# On the client side, iperf doesn't wait for ACKs - it simply
# reports how long it took to fill up the TCP send buffer.
# As long as the kernel doesn't wait a long time before
# delivering bytes to the iperf server, its reported data rate
# should be close to the actual receive rate.
serverRate, clientRate = bw_strs
bw = float( serverRate.split(' ')[0] )
self.assertWithinTolerance( bw, BW, BW_TOLERANCE, msg )
def testLinkDelay( self ):
"Verify that link delays are accurate within a bound."
DELAY_MS = 15
DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail
REPS = 3
lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True }
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
link=TCLink, switch=self.switchClass, autoStaticArp=True )
ping_delays = mn.run( mn.pingFull )
link=TCLink, switch=self.switchClass, autoStaticArp=True,
waitConnected=True )
mn.start()
for _ in range( REPS ):
ping_delays = mn.pingFull()
mn.stop()
test_outputs = ping_delays[0]
# Ignore unused variables below
# pylint: disable-msg=W0612
node, dest, ping_outputs = test_outputs
sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
self.assertEqual( sent, received )
pingFailMsg = 'sent %s pings, only received %s' % ( sent, received )
self.assertEqual( sent, received, msg=pingFailMsg )
# pylint: enable-msg=W0612
loptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in lopts.items() )
msg = ( '\nTesting Link Delay of %s ms\n'
'ping results across 4 links:\n'
'(Sent, Received, rttmin, rttavg, rttmax, rttdev)\n'
'%s\n'
'Topo = SingleSwitchTopo, %s hosts\n'
'Link = TCLink\n'
'lopts = %s\n'
'host = default'
'switch = %s\n'
% ( DELAY_MS, ping_outputs, N, loptsStr, self.switchClass ) )
for rttval in [rttmin, rttavg, rttmax]:
# Multiply delay by 4 to cover there & back on two links
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
DELAY_TOLERANCE)
DELAY_TOLERANCE, msg )
def testLinkLoss( self ):
@@ -117,7 +192,8 @@ class testOptionsTopoCommon( object ):
lopts = { 'loss': LOSS_PERCENT, 'use_htb': True }
mn = Mininet( topo=SingleSwitchOptionsTopo( n=N, lopts=lopts ),
host=CPULimitedHost, link=TCLink,
switch=self.switchClass )
switch=self.switchClass,
waitConnected=True )
# Drops are probabilistic, but the chance of no dropped packets is
# 1 in 100 million with 4 hops for a link w/99% loss.
dropped_total = 0
@@ -125,36 +201,54 @@ class testOptionsTopoCommon( object ):
for _ in range(REPS):
dropped_total += mn.ping(timeout='1')
mn.stop()
self.assertGreater( dropped_total, 0 )
loptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in lopts.items() )
msg = ( '\nTesting packet loss with %d%% loss rate\n'
'number of dropped pings during mininet.ping(): %s\n'
'expected number of dropped packets: 1\n'
'Topo = SingleSwitchTopo, %s hosts\n'
'Link = TCLink\n'
'lopts = %s\n'
'host = default\n'
'switch = %s\n'
% ( LOSS_PERCENT, dropped_total, N, loptsStr, self.switchClass ) )
self.assertGreater( dropped_total, 0, msg )
def testMostOptions( self ):
"Verify topology creation with most link options and CPU limits."
lopts = { 'bw': 10, 'delay': '5ms', 'use_htb': True }
hopts = { 'cpu': 0.5 / N }
self.runOptionsTopoTest( N, hopts=hopts, lopts=lopts )
msg = '\nTesting many cpu and link options\n'
self.runOptionsTopoTest( N, msg, hopts=hopts, lopts=lopts )
# pylint: enable=E1101
class testOptionsTopoOVSKernel( testOptionsTopoCommon, unittest.TestCase ):
"""Verify ability to create networks with host and link options
(OVS kernel switch)."""
longMessage = True
switchClass = OVSSwitch
@unittest.skip( 'Skipping OVS user switch test for now' )
class testOptionsTopoOVSUser( testOptionsTopoCommon, unittest.TestCase ):
"""Verify ability to create networks with host and link options
(OVS user switch)."""
longMessage = True
switchClass = partial( OVSSwitch, datapath='user' )
@unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS is not installed' )
class testOptionsTopoIVS( testOptionsTopoCommon, unittest.TestCase ):
"Verify ability to create networks with host and link options (IVS)."
longMessage = True
switchClass = IVSSwitch
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
'Reference user switch is not installed' )
class testOptionsTopoUserspace( testOptionsTopoCommon, unittest.TestCase ):
"Verify ability to create networks with host and link options (UserSwitch)."
longMessage = True
switchClass = UserSwitch
if __name__ == '__main__':
+3 -2
View File
@@ -23,14 +23,15 @@ class testSingleSwitchCommon( object ):
def testMinimal( self ):
"Ping test on minimal topology"
mn = Mininet( SingleSwitchTopo(), self.switchClass, Host, Controller )
mn = Mininet( SingleSwitchTopo(), self.switchClass, Host, Controller,
waitConnected=True )
dropped = mn.run( mn.ping )
self.assertEqual( dropped, 0 )
def testSingle5( self ):
"Ping test on 5-host single-switch topology"
mn = Mininet( SingleSwitchTopo( k=5 ), self.switchClass, Host,
Controller )
Controller, waitConnected=True )
dropped = mn.run( mn.ping )
self.assertEqual( dropped, 0 )
+19 -8
View File
@@ -9,7 +9,14 @@ TODO: missing xterm test
import unittest
import pexpect
import os
import re
from mininet.util import quietRun
from distutils.version import StrictVersion
def tsharkVersion():
versionStr = quietRun( 'tshark -v' )
versionMatch = re.findall( 'TShark \d+.\d+.\d+', versionStr )[0]
return versionMatch.split()[ 1 ]
class testWalkthrough( unittest.TestCase ):
@@ -24,11 +31,14 @@ class testWalkthrough( unittest.TestCase ):
def testWireshark( self ):
"Use tshark to test the of dissector"
tshark = pexpect.spawn( 'tshark -i lo -R of' )
tshark.expect( 'Capturing on lo' )
if StrictVersion( tsharkVersion() ) < StrictVersion( '1.12.0' ):
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'" ] )
mn = pexpect.spawn( 'mn --test pingall' )
mn.expect( '0% dropped' )
tshark.expect( 'OFP 74 Hello' )
tshark.expect( [ '74 Hello', '74 of_hello', '74 Type: OFPT_HELLO' ] )
tshark.sendintr()
def testBasic( self ):
@@ -64,7 +74,7 @@ class testWalkthrough( unittest.TestCase ):
node = p.match.group( 1 )
actual.append( node )
p.expect( '\n' )
self.assertEqual( actual.sort(), nodes.sort(), '"nodes" and "dump" differ' )
self.assertEqual( actual.sort(), nodes.sort(), '"nodes" and "dump" differ' )
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
@@ -101,11 +111,11 @@ class testWalkthrough( unittest.TestCase ):
break
self.assertEqual( ifcount, 3, 'Missing interfaces on s1')
# h1 ps
p.sendline( 'h1 ps -a' )
p.sendline( "h1 ps -a | egrep -v 'ps|grep'" )
p.expect( self.prompt )
h1Output = p.before
# s1 ps
p.sendline( 's1 ps -a' )
p.sendline( "s1 ps -a | egrep -v 'ps|grep'" )
p.expect( self.prompt )
s1Output = p.before
# strip command from ps output
@@ -208,7 +218,7 @@ class testWalkthrough( unittest.TestCase ):
p = pexpect.spawn( 'mn -v debug --test none' )
p.expect( pexpect.EOF )
lines = p.before.split( '\n' )
self.assertTrue( len( lines ) > 100, "Debug output is too short" )
self.assertTrue( len( lines ) > 70, "Debug output is too short" )
def testCustomTopo( self ):
"Start Mininet using a custom topo, then run pingall"
@@ -327,5 +337,6 @@ class testWalkthrough( unittest.TestCase ):
pox.sendintr()
pox.wait()
if __name__ == '__main__':
unittest.main()
unittest.main()
+157 -23
View File
@@ -10,6 +10,8 @@ import re
from fcntl import fcntl, F_GETFL, F_SETFL
from os import O_NONBLOCK
import os
from functools import partial
import tempfile
# Command execution support
@@ -61,12 +63,6 @@ def errRun( *cmd, **kwargs ):
stderr: STDOUT to merge stderr with stdout
shell: run command using shell
echo: monitor output to console"""
# Allow passing in a list or a string
if len( cmd ) == 1:
cmd = cmd[ 0 ]
if isinstance( cmd, str ):
cmd = cmd.split( ' ' )
cmd = [ str( arg ) for arg in cmd ]
# By default we separate stderr, don't run in a shell, and don't echo
stderr = kwargs.get( 'stderr', PIPE )
shell = kwargs.get( 'shell', False )
@@ -74,6 +70,14 @@ def errRun( *cmd, **kwargs ):
if echo:
# cmd goes to stderr, output goes to stdout
info( cmd, '\n' )
if len( cmd ) == 1:
cmd = cmd[ 0 ]
# Allow passing in a list or a string
if isinstance( cmd, str ) 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 )
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
@@ -145,17 +149,22 @@ isShellBuiltin.builtIns = None
# live in the root namespace and thus do not have to be
# explicitly moved.
def makeIntfPair( intf1, intf2 ):
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, run=quietRun ):
"""Make a veth pair connecting intf1 and intf2.
intf1: string, interface
intf2: string, interface
returns: success boolean"""
node: node to run on or None (default)
returns: ip link add result"""
# Delete any old interfaces with the same names
quietRun( 'ip link del ' + intf1 )
quietRun( 'ip link del ' + intf2 )
run( 'ip link del ' + intf1 )
run( 'ip link del ' + intf2 )
# Create new pair
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
cmdOutput = quietRun( cmd )
if addr1 is None and addr2 is None:
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
else:
cmd = ( 'ip link add name ' + intf1 + ' address ' + addr1 +
' type veth peer name ' + intf2 + ' address ' + addr2 )
cmdOutput = run( cmd )
if cmdOutput == '':
return True
else:
@@ -176,34 +185,32 @@ def retry( retries, delaySecs, fn, *args, **keywords ):
error( "*** gave up after %i retries\n" % tries )
exit( 1 )
def moveIntfNoRetry( intf, dstNode, srcNode=None, printError=False ):
def moveIntfNoRetry( intf, dstNode, printError=False ):
"""Move interface to node, without retrying.
intf: string, interface
dstNode: destination Node
srcNode: source Node or None (default) for root ns
printError: if true, print error"""
intf = str( intf )
cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid )
if srcNode:
srcNode.cmd( cmd )
else:
quietRun( cmd )
if ( ' %s:' % intf ) not in dstNode.cmd( 'ip link show', intf ):
cmdOutput = quietRun( cmd )
# If ip link set does not produce any output, then we can assume
# that the link has been moved successfully.
if cmdOutput:
if printError:
error( '*** Error: moveIntf: ' + intf +
' not successfully moved to ' + dstNode.name + '\n' )
' not successfully moved to ' + dstNode.name + ':\n',
cmdOutput )
return False
return True
def moveIntf( intf, dstNode, srcNode=None, printError=False,
def moveIntf( intf, dstNode, srcNode=None, printError=True,
retries=3, delaySecs=0.001 ):
"""Move interface to node, retrying on failure.
intf: string, interface
dstNode: destination Node
srcNode: source Node or None (default) for root ns
printError: if true, print error"""
retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode,
srcNode=srcNode, printError=printError )
printError=printError )
# Support for dumping network
@@ -231,6 +238,15 @@ def dumpNetConnections( net ):
nodes = net.controllers + net.switches + net.hosts
dumpNodeConnections( nodes )
def dumpPorts( switches ):
"dump interface to openflow port mappings for each switch"
for switch in switches:
output( '%s ' % switch.name )
for intf in switch.intfList():
port = switch.ports[ intf ]
output( '%s:%d ' % ( intf, port ) )
output( '\n' )
# IP and Mac address formatting and parsing
def _colonHex( val, bytecount ):
@@ -526,3 +542,121 @@ def ensureRoot():
print "*** Mininet must run as root."
exit( 1 )
return
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
"""Wait until server is listening on port.
returns True if server is listening"""
run = ( client.cmd if client else
partial( quietRun, shell=True ) )
if not run( 'which telnet' ):
raise Exception('Could not find telnet' )
serverIP = server if type( server ) is str else server.IP()
cmd = ( 'sh -c "echo A | telnet -e A %s %s"' %
( serverIP, port ) )
time = 0
while 'Connected' not in run( cmd ):
if timeout:
print time
if time >= timeout:
error( 'could not connect to %s on port %d\n'
% ( server, port ) )
return False
output('waiting for', server,
'to listen on port', port, '\n')
sleep( .5 )
time += .5
return True
def updateMininet( option, opt_str, value, parser, *args, **kwargs ):
# TODO check root
# TODO check dependencies (git and make)
url = 'https://github.com/mininet/mininet'
choice = None
if parser.rargs:
choice = parser.rargs[ 0 ]
current = kwargs['version']
print 'Your current version is: %s' % current
# get available releases
if not choice:
s = quietRun('git ls-remote --tags %s' % url)
#TODO check output for network failure
tags = []
for t in re.finditer(r'refs/tags/(\d\S*)', s):
v = t.group(1)
if v > current:
tags.append( t.group(1) )
if tags:
print 'Available versions:\n', '\n'.join( tags )
else:
print 'Congratulations! You are up-to-date.'
# ask user to select a version
while not choice:
choice = raw_input('Select a version: ')
if choice in tags:
break
elif 'exit' in choice:
exit()
else:
print 'Error: Please type an available version.'
choice = None
# sanity check
if choice < current:
print 'WARNING: The version that you have selected is a downgrade.'
elif choice == current:
print 'Version %s is already installed' % current
exit()
# confirm choice
if not re.match( r'[yY][eE]?[sS]?',
raw_input( 'Do you want to upgrade to %s? [y/N] ' % choice ) ):
print 'ABORTED'
exit()
# proceed with installation
# 1. detect make develop
if 'dist-packages' in __file__:
## mkdir; cd; git init
develop = False
installDir = tempfile.mkdtemp() + '/mininet'
os.mkdir( installDir )
os.chdir( installDir )
quietRun( 'git init' )
else:
## cd to mininet directory
develop = True
print ( 'FAILURE: You have enabled "sudo make develop".\n\n'
'To upgrade, check out the latest code and run "util/install.sh -n" '
'and then re-run "sudo make develop".' )
exit()
# 2. fetch the tag
if run( 'git fetch https://github.com/mininet/mininet %s' % choice ):
print 'FAILURE: Could not update to %s' % choice
exit()
# 3. check to see if there is a branch conflict
if run( 'git show-ref --verify --quiet refs/heads/%s' % choice ):
# branch does not exist
print run( 'git checkout -b %s FETCH_HEAD' % choice )
else:
# This should not happen until develop is supported...
# TODO: branch already exists
## git checkout %s
## git reset --hard FETCH_HEAD
print '... make develop enabled'
# 4. perform installation
if develop:
## sudo make develop
print '... make develop enabled'
else:
# remove mininet prior to upgrade
oldDir = __file__.split( 'mininet/util' )[0]
for f in os.listdir( oldDir + '/..' ):
print f
if 'mininet' in f:
print run( 'rm -rfv %s' % oldDir + '/../' + f )
# run the install script
os.execl( installDir+'/util/install.sh', 'install.sh', '-n' )
#TODO remove temp dir
exit()
+11 -4
View File
@@ -29,7 +29,7 @@
#define VERSION "(devel)"
#endif
void usage(char *name)
void usage(char *name)
{
printf("Execution utility for Mininet\n\n"
"Usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...\n\n"
@@ -99,6 +99,7 @@ int main(int argc, char *argv[])
char path[PATH_MAX];
int nsid;
int pid;
static struct sched_param sp;
while ((c = getopt(argc, argv, "+cdnpa:g:r:vh")) != -1)
switch(c) {
@@ -156,12 +157,18 @@ int main(int argc, char *argv[])
sprintf(path, "/proc/%d/ns/mnt", pid);
nsid = open(path, O_RDONLY);
if (nsid < 0 || setns(nsid, 0) != 0) {
/* Plan B: chroot into pid's root file system */
char *cwd = get_current_dir_name();
/* Plan B: chroot/chdir into pid's root file system */
sprintf(path, "/proc/%d/root", pid);
if (chroot(path) < 0) {
perror(path);
return 1;
}
/* need to chdir to correct working directory */
if (chdir(cwd) != 0) {
perror(cwd);
return 1;
}
}
break;
case 'g':
@@ -184,7 +191,7 @@ int main(int argc, char *argv[])
exit(0);
default:
usage(argv[0]);
exit(1);
exit(1);
}
if (optind < argc) {
@@ -192,7 +199,7 @@ int main(int argc, char *argv[])
perror(argv[optind]);
return 1;
}
usage(argv[0]);
return 0;
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# Mininet ssh authentication script for cluster edition
# This script will create a single key pair, which is then
# propagated throughout the entire cluster.
# There are two options for setup; temporary setup
# persistent setup. If no options are specified, and the script
# is only given ip addresses or host names, it will default to
# the temporary setup. An ssh directory is then created in
# /tmp/mn/ssh on each node, and mounted with the keys over the
# user's ssh directory. This setup can easily be torn down by running
# clustersetup with the -c option.
# If the -p option is used, the setup will be persistent. In this
# case, the key pair will be be distributed directly to each node's
# ssh directory, but will be called cluster_key. An option to
# specify this key for use will be added to the config file in each
# user's ssh directory.
set -e
num_options=0
persistent=false
showHelp=false
clean=false
declare -a hosts=()
user=$(whoami)
SSHDIR=/tmp/mn/ssh
USERDIR=/home/$user/.ssh
usage=$'./clustersetup.sh [ -p|h|c ] [ host1 ] [ host2 ] ...\n
Authenticate yourself and other cluster nodes to each other
via ssh for mininet cluster edition. By default, we use a
temporary ssh setup. An ssh directory is mounted over
/home/user/.ssh on each machine in the cluster.
-h: display this help
-p: create a persistent ssh setup. This will add
new ssh keys and known_hosts to each nodes
/home/user/.ssh directory
-c: method to clean up a temporary ssh setup.
Any hosts taken as arguments will be cleaned
'
persistentSetup() {
echo "***creating key pair"
ssh-keygen -t rsa -C "Cluster_Edition_Key" -f $USERDIR/cluster_key -N '' &> /dev/null
cat $USERDIR/cluster_key.pub >> $USERDIR/authorized_keys
echo "***configuring ssh"
echo "IdentityFile $USERDIR/cluster_key" >> $USERDIR/config
echo "IdentityFile $USERDIR/id_rsa" >> $USERDIR/config
for host in $hosts; do
echo "***copying public key to $host"
ssh-copy-id -i $USERDIR/cluster_key.pub $user@$host &> /dev/null
echo "***copying key pair to remote host"
scp $USERDIR/cluster_key $user@$host:$USERDIR
scp $USERDIR/cluster_key.pub $user@$host:$USERDIR
echo "***configuring remote host"
ssh -o ForwardAgent=yes $user@$host "
echo 'IdentityFile $USERDIR/cluster_key' >> $USERDIR/config
echo 'IdentityFile $USERDIR/id_rsa' >> $USERDIR/config"
done
for host in $hosts; do
echo "***copying known_hosts to $host"
scp $USERDIR/known_hosts $user@$host:$USERDIR/cluster_known_hosts
ssh $user@$host "
cat $USERDIR/cluster_known_hosts >> $USERDIR/known_hosts
rm $USERDIR/cluster_known_hosts"
done
}
tempSetup() {
echo "***creating temporary ssh directory"
mkdir -p $SSHDIR
echo "***creating key pair"
ssh-keygen -t rsa -C "Cluster_Edition_Key" -f /tmp/mn/ssh/id_rsa -N '' &> /dev/null
echo "***mounting temporary ssh directory"
sudo mount --bind $SSHDIR /home/$user/.ssh
cp $SSHDIR/id_rsa.pub $SSHDIR/authorized_keys
for host in $hosts; do
echo "***copying public key to $host"
ssh-copy-id $user@$host &> /dev/null
echo "***mounting remote temporary ssh directory for $host"
ssh -o ForwardAgent=yes $user@$host "
mkdir -p /tmp/mn/ssh
cp /home/$user/.ssh/authorized_keys $SSHDIR/authorized_keys
sudo mount --bind $SSHDIR /home/$user/.ssh"
echo "***copying key pair to $host"
scp $SSHDIR/{id_rsa,id_rsa.pub} $user@$host:$SSHDIR
done
for host in $hosts; do
echo "***copying known_hosts to $host"
scp $SSHDIR/known_hosts $user@$host:$SSHDIR
done
}
cleanup() {
for host in $hosts; do
echo "***cleaning up $host"
ssh $user@$host "sudo umount /home/$user/.ssh
sudo rm -rf $SSHDIR"
done
echo "**unmounting local directories"
sudo umount /home/$user/.ssh
echo "***removing temporary ssh directory"
sudo rm -rf $SSHDIR
echo "done!"
}
if [ $# -eq 0 ]; then
echo "ERROR: No Arguments"
echo "$usage"
exit
else
while getopts 'hpc' OPTION
do
((num_options+=1))
case $OPTION in
h) showHelp=true;;
p) persistent=true;;
c) clean=true;;
?) showHelp=true;;
esac
done
shift $(($OPTIND - 1))
fi
if [ "$num_options" -gt 1 ]; then
echo "ERROR: Too Many Options"
echo "$usage"
exit
fi
if $showHelp; then
echo "$usage"
exit
fi
for i in "$@"; do
output=$(getent ahostsv4 "$i")
if [ -z "$output" ]; then
echo '***WARNING: could not find hostname "$i"'
echo ""
else
hosts+="$i "
fi
done
if $clean; then
cleanup
exit
fi
echo "***authenticating to:"
for host in $hosts; do
echo "$host"
done
echo
if $persistent; then
echo '***Setting up persistent SSH configuration between all nodes'
persistentSetup
echo $'\n*** Sucessfully set up ssh throughout the cluster!'
else
echo '*** Setting up temporary SSH configuration between all nodes'
tempSetup
echo $'\n***Finished temporary setup. When you are done with your cluster'
echo $' session, tear down the SSH connections with'
echo $' ./clustersetup.sh -c '$hosts''
fi
echo
+107 -73
View File
@@ -74,6 +74,17 @@ fi
# More distribution info
DIST_LC=`echo $DIST | tr [A-Z] [a-z]` # as lower case
# Determine whether version $1 >= version $2
# usage: if version_ge 1.20 1.2.3; then echo "true!"; fi
function version_ge {
# sort -V sorts by *version number*
latest=`printf "$1\n$2" | sort -V | tail -1`
# If $1 is latest version, then $1 >= $2
[ "$1" == "$latest" ]
}
# Kernel Deb pkg to be removed:
KERNEL_IMAGE_OLD=linux-image-2.6.26-33-generic
@@ -85,10 +96,6 @@ OVS_BUILDSUFFIX=-ignore # was -2
OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar
OVS_TAG=v$OVS_RELEASE
# Command-line versions overrides that simplify custom VM creation
# To use, pass in the vars on the cmd line before install.sh, e.g.
# WS_DISSECTOR_REV=pre-ws-1.10.0 install.sh -w
WS_DISSECTOR_REV=${WS_DISSECTOR_REV:-""}
OF13_SWITCH_REV=${OF13_SWITCH_REV:-""}
@@ -200,65 +207,44 @@ function of13 {
cd $BUILD_DIR
}
function wireshark_version_check {
# Check Wireshark version
WS=$(which wireshark)
WS_VER_PATCH=(1 10) # targetting wireshark 1.10.0
WS_VER=($($WS --version | sed 's/[a-z ]*\([0-9]*\).\([0-9]*\).\([0-9]*\).*/\1 \2 \3/'))
if [ "${WS_VER[0]}" -lt "${WS_VER_PATCH[0]}" ] ||
[[ "${WS_VER[0]}" -le "${WS_VER_PATCH[0]}" && "${WS_VER[1]}" -lt "${WS_VER_PATCH[1]}" ]]
then
# pre-1.10.0 wireshark
echo "Setting revision: pre-ws-1.10.0"
WS_DISSECTOR_REV="pre-ws-1.10.0"
fi
}
function wireshark {
echo "Installing Wireshark dissector..."
if [ "$DIST" = "Fedora" ]; then
# Just install Fedora's wireshark RPMS
# Fedora's wirehark >= 1.10.2-2 includes an OF dissector
# (it has been backported from the future Wireshark 1.12 code base)
$install wireshark wireshark-gnome
return
fi
$install wireshark tshark libgtk2.0-dev
if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" != "10.04" ]; then
# Install newer version
$install scons mercurial libglib2.0-dev
$install libwiretap-dev libwireshark-dev
cd $BUILD_DIR
hg clone https://bitbucket.org/barnstorm/of-dissector
if [[ -z "$WS_DISSECTOR_REV" ]]; then
wireshark_version_check
function install_wireshark {
if ! which wireshark; then
echo "Installing Wireshark"
if [ "$DIST" = "Fedora" ]; then
$install wireshark wireshark-gnome
else
$install wireshark tshark
fi
cd of-dissector
if [[ -n "$WS_DISSECTOR_REV" ]]; then
hg checkout ${WS_DISSECTOR_REV}
fi
# Build dissector
cd src
export WIRESHARK=/usr/include/wireshark
scons
# libwireshark0/ on 11.04; libwireshark1/ on later
WSDIR=`find /usr/lib -type d -name 'libwireshark*' | head -1`
WSPLUGDIR=$WSDIR/plugins/
sudo cp openflow.so $WSPLUGDIR
echo "Copied openflow plugin to $WSPLUGDIR"
else
# Install older version from reference source
cd $BUILD_DIR/openflow/utilities/wireshark_dissectors/openflow
make
sudo make install
fi
# Copy coloring rules: OF is white-on-blue:
echo "Optionally installing wireshark color filters"
mkdir -p $HOME/.wireshark
cp $MININET_DIR/mininet/util/colorfilters $HOME/.wireshark
cp -n $MININET_DIR/mininet/util/colorfilters $HOME/.wireshark
echo "Checking Wireshark version"
WSVER=`wireshark -v | egrep -o '[0-9\.]+' | head -1`
if version_ge $WSVER 1.12; then
echo "Wireshark version $WSVER >= 1.12 - returning"
return
fi
echo "Cloning LoxiGen and building openflow.lua dissector"
cd $BUILD_DIR
git clone https://github.com/floodlight/loxigen.git
cd loxigen
make wireshark
# Copy into plugin directory
# libwireshark0/ on 11.04; libwireshark1/ on later
WSDIR=`find /usr/lib -type d -name 'libwireshark*' | head -1`
WSPLUGDIR=$WSDIR/plugins/
PLUGIN=loxi_output/wireshark/openflow.lua
sudo cp $PLUGIN $WSPLUGDIR
echo "Copied openflow plugin $PLUGIN to $WSPLUGDIR"
cd $BUILD_DIR
}
@@ -269,7 +255,7 @@ function ubuntuOvs {
OVS_SRC=$BUILD_DIR/openvswitch
OVS_TARBALL_LOC=http://openvswitch.org/releases
if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then
if [ "$DIST" = "Ubuntu" ] && version_ge $RELEASE 12.04; then
rm -rf $OVS_SRC
mkdir -p $OVS_SRC
cd $OVS_SRC
@@ -340,17 +326,24 @@ function ovs {
$install openvswitch-datapath-dkms
fi
$install openvswitch-switch openvswitch-controller
$install openvswitch-switch
if $install openvswitch-controller; then
# Switch can run on its own, but
# Mininet should control the controller
# This appears to only be an issue on Ubuntu/Debian
if sudo service openvswitch-controller stop; then
echo "Stopped running controller"
fi
if [ -e /etc/init.d/openvswitch-controller ]; then
sudo update-rc.d openvswitch-controller disable
fi
else
echo "Attempting to install openvswitch-testcontroller"
if ! $install openvswitch-testcontroller; then
echo "Failed - giving up"
fi
fi
# Switch can run on its own, but
# Mininet should control the controller
# This appears to only be an issue on Ubuntu/Debian
if sudo service openvswitch-controller stop; then
echo "Stopped running controller"
fi
if [ -e /etc/init.d/openvswitch-controller ]; then
sudo update-rc.d openvswitch-controller disable
fi
}
function remove_ovs {
@@ -390,6 +383,38 @@ function ivs {
sudo make install
}
# Install RYU
function ryu {
echo "Installing RYU..."
# install Ryu dependencies"
$install autoconf automake g++ libtool python make
if [ "$DIST" = "Ubuntu" ]; then
$install libxml2 libxslt-dev python-pip python-dev
sudo pip install gevent
elif [ "$DIST" = "Debian" ]; then
$install libxml2 libxslt-dev python-pip python-dev
sudo pip install gevent
fi
# if needed, update python-six
SIX_VER=`pip show six | grep Version | awk '{print $2}'`
if version_ge 1.7.0 $SIX_VER; then
echo "Installing python-six version 1.7.0..."
sudo pip install -I six==1.7.0
fi
# fetch RYU
cd $BUILD_DIR/
git clone git://github.com/osrg/ryu.git ryu
cd ryu
# install ryu
sudo python ./setup.py install
# Add symbolic link to /usr/bin
sudo ln -s ./bin/ryu-manager /usr/local/bin/ryu-manager
}
# Install NOX with tutorial files
function nox {
echo "Installing NOX w/tutorial files..."
@@ -418,7 +443,7 @@ function nox {
# Apply patches
git checkout -b tutorial-destiny
git am $MININET_DIR/mininet/util/nox-patches/*tutorial-port-nox-destiny*.patch
if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then
if [ "$DIST" = "Ubuntu" ] && version_ge $RELEASE 12.04; then
git am $MININET_DIR/mininet/util/nox-patches/*nox-ubuntu12-hacks.patch
fi
@@ -557,6 +582,13 @@ net.ipv6.conf.lo.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf > /dev/null
# Install NTP
$install ntp
# Install vconfig for VLAN example
if [ "$DIST" = "Fedora" ]; then
$install vconfig
else
$install vlan
fi
# Set git to colorize everything.
git config --global color.diff auto
git config --global color.status auto
@@ -606,7 +638,7 @@ function all {
# Skip mn_dev (doxypy/texlive/fonts/etc.) because it's huge
# mn_dev
of
wireshark
install_wireshark
ovs
# We may add ivs once it's more mature
# ivs
@@ -652,7 +684,7 @@ function vm_clean {
}
function usage {
printf '\nUsage: %s [-abcdfhikmnprtvVwx03]\n\n' $(basename $0) >&2
printf '\nUsage: %s [-abcdfhikmnprtvVwxy03]\n\n' $(basename $0) >&2
printf 'This install script attempts to install useful packages\n' >&2
printf 'for Mininet. It should (hopefully) work on Ubuntu 11.10+\n' >&2
@@ -679,6 +711,7 @@ function usage {
printf -- ' -v: install Open (V)switch\n' >&2
printf -- ' -V <version>: install a particular version of Open (V)switch on Ubuntu\n' >&2
printf -- ' -w: install OpenFlow (W)ireshark dissector\n' >&2
printf -- ' -y: install R(y)u Controller\n' >&2
printf -- ' -x: install NO(X) Classic OpenFlow controller\n' >&2
printf -- ' -0: (default) -0[fx] installs OpenFlow 1.0 versions\n' >&2
printf -- ' -3: -3[fx] installs OpenFlow 1.3 versions\n' >&2
@@ -691,7 +724,7 @@ if [ $# -eq 0 ]
then
all
else
while getopts 'abcdefhikmnprs:tvV:wx03' OPTION
while getopts 'abcdefhikmnprs:tvV:wxy03' OPTION
do
case $OPTION in
a) all;;
@@ -718,12 +751,13 @@ else
v) ovs;;
V) OVS_RELEASE=$OPTARG;
ubuntuOvs;;
w) wireshark;;
w) install_wireshark;;
x) case $OF_VERSION in
1.0) nox;;
1.3) nox13;;
*) echo "Invalid OpenFlow version $OF_VERSION";;
esac;;
y) ryu;;
0) OF_VERSION=1.0;;
3) OF_VERSION=1.3;;
?) usage;;
+1 -1
View File
@@ -40,5 +40,5 @@ if [ -d $rootdir -a -x $rootdir/bin/bash ]; then
cmd="chroot $rootdir /bin/bash -c $cmd"
fi
cmd="exec sudo mnexec -a $pid $cg $cmd"
cmd="exec sudo mnexec $cg -a $pid $cmd"
eval $cmd
+58 -9
View File
@@ -52,7 +52,8 @@ LogToConsole = False # VM output to console rather than log file
SaveQCOW2 = False # Save QCOW2 image rather than deleting it
NoKVM = False # Don't use kvm and use emulation instead
Branch = None # Branch to update and check out before testing
Zip = False # Archive .ovf and .vmdk into a .zip file
Zip = False # Archive .ovf and .vmdk into a .zip file
Forward = [] # VM port forwarding options (-redir)
VMImageDir = os.environ[ 'HOME' ] + '/vm-images'
@@ -192,7 +193,10 @@ def findiso( flavor ):
if not path.exists( iso ) or ( stat( iso )[ ST_MODE ] & 0777 != 0444 ):
log( '* Retrieving', url )
run( 'curl -C - -o %s %s' % ( iso, url ) )
if 'ISO' not in run( 'file ' + iso ):
# Make sure the file header/type is something reasonable like
# 'ISO' or 'x86 boot sector', and not random html or text
result = run( 'file ' + iso )
if 'ISO' not in result and 'boot' not in result:
os.remove( iso )
raise Exception( 'findiso: could not download iso from ' + url )
# Write-protect iso, signaling it is complete
@@ -438,7 +442,12 @@ def boot( cow, kernel, initrd, logfile, memory=1024 ):
returns: pexpect object to qemu process"""
# pexpect might not be installed until after depend() is called
global pexpect
import pexpect
if not pexpect:
import pexpect
class Spawn( pexpect.spawn ):
"Subprocess is sudo, so we have to sudo kill it"
def close( self, force=False ):
srun( 'kill %d' % self.pid )
arch = archFor( kernel )
log( '* Detected kernel architecture', arch )
if NoKVM:
@@ -456,10 +465,12 @@ def boot( cow, kernel, initrd, logfile, memory=1024 ):
'-initrd', initrd,
'-drive file=%s,if=virtio' % cow,
'-append "root=/dev/vda1 init=/sbin/init console=ttyS0" ' ]
if Forward:
cmd += sum( [ [ '-redir', f ] for f in Forward ], [] )
cmd = ' '.join( cmd )
log( '* BOOTING VM FROM', cow )
log( cmd )
vm = pexpect.spawn( cmd, timeout=TIMEOUT, logfile=logfile )
vm = Spawn( cmd, timeout=TIMEOUT, logfile=logfile )
return vm
@@ -476,6 +487,19 @@ def login( vm, user='mininet', password='mininet' ):
log( '* Waiting for login...' )
def disableNtpd( vm, prompt=Prompt ):
"Turn off ntpd and set clock immediately"
log( '* Turning off ntpd' )
vm.sendline( 'sudo -n service ntp stop' )
vm.expect( prompt )
log( '* Setting clock' )
# -gq: set and quit immediately
vm.sendline( 'sudo -n ntpd -gq' )
vm.expect( prompt )
log( '* Waiting one second and running date command' )
vm.sendline( 'sleep 1 && date ' )
def sanityTest( vm ):
"Run Mininet sanity test (pingall) in vm"
vm.sendline( 'sudo mn --test pingall' )
@@ -501,7 +525,7 @@ def coreTest( vm, prompt=Prompt ):
# know the time for each test, which means that this
# script will have to change as we add more tests.
for test in range( 0, 2 ):
if vm.expect( [ 'OK', 'FAILED', pexpect.TIMEOUT ], timeout=180 ) == 0:
if vm.expect( [ 'OK.*\r\n', 'FAILED.*\r\n', pexpect.TIMEOUT ], timeout=180 ) == 0:
log( '* Test', test, 'OK' )
else:
log( '* Test', test, 'FAILED' )
@@ -533,6 +557,20 @@ def walkthroughTest( vm, prompt=Prompt ):
vm.sendline( 'sudo python ~/mininet/mininet/test/test_walkthrough.py -v' )
def useTest( vm, prompt=Prompt ):
"Use VM interactively - exit by pressing control-]"
old = vm.logfile
if old == stdout:
# Avoid doubling every output character!
log( '* Temporarily disabling logging to stdout' )
vm.logfile = None
log( '* Switching to interactive use - press control-] to exit' )
vm.interact()
if old == stdout:
log( '* Restoring logging to stdout' )
vm.logfile = stdout
def checkOutBranch( vm, branch, prompt=Prompt ):
# This is a bit subtle; it will check out an existing branch (e.g. master)
# if it exists; otherwise it will create a detached branch.
@@ -725,9 +763,12 @@ def generateOVF( name, osname, osid, diskname, disksize, mem=1024, cpus=1,
def qcow2size( qcow2 ):
"Return virtual disk size (in bytes) of qcow2 image"
output = check_output( [ 'file', qcow2 ] )
assert 'QCOW' in output
bytes = int( re.findall( '(\d+) bytes', output )[ 0 ] )
output = check_output( [ 'qemu-img', 'info', qcow2 ] )
try:
assert 'format: qcow' in output
bytes = int( re.findall( '(\d+) bytes', output )[ 0 ] )
except:
raise Exception( 'Could not determine size of %s' % qcow2 )
return bytes
@@ -791,6 +832,10 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
def runTests( vm, tests=None, pre='', post='', prompt=Prompt ):
"Run tests (list) in vm (pexpect object)"
# We disable ntpd and set the time so that it won't be
# messing with the time during tests
disableNtpd( vm )
vm.expect( prompt )
if Branch:
checkOutBranch( vm, branch=Branch )
vm.expect( prompt )
@@ -892,7 +937,7 @@ def testString():
def parseArgs():
"Parse command line arguments and run"
global LogToConsole, NoKVM, Branch, Zip, TIMEOUT
global LogToConsole, NoKVM, Branch, Zip, TIMEOUT, Forward
parser = argparse.ArgumentParser( description='Mininet VM build script',
epilog=buildFlavorString() + ' ' +
testString() )
@@ -930,6 +975,8 @@ def parseArgs():
help='archive .ovf and .vmdk into .zip file' )
parser.add_argument( '-o', '--out',
help='output file for test image (vmdk)' )
parser.add_argument( '-f', '--forward', default=[], action='append',
help='forward VM ports to local server, e.g. tcp:5555::22' )
args = parser.parse_args()
if args.depend:
depend()
@@ -947,6 +994,8 @@ def parseArgs():
Zip = True
if args.timeout:
TIMEOUT = args.timeout
if args.forward:
Forward = args.forward
if not args.test and not args.run and not args.post:
args.test = [ 'sanity', 'core' ]
for flavor in args.flavor: