Compare commits

...

233 Commits

Author SHA1 Message Date
Brian O'Connor ab97dfa19c fixing no --nat issue 2014-11-24 18:36:57 -08:00
Brian O'Connor af1ccf93a5 Updating NAT class to use gateway interface
Also, passing CLI args to NAT constructor

fixes #437
2014-11-24 18:16:57 -08:00
lantz 015cd9e776 Merge pull request #443 from cdburkard/devel/cluster
use rcmd instead of quietRun when shutting down remote nodes
2014-11-24 12:30:52 -08:00
Bob Lantz 3d44bcdcc4 MiniNet -> Mininet 2014-11-23 17:32:11 -08:00
Bob Lantz 1817cbc3a4 Pass pyflakes 2014-11-23 17:31:43 -08:00
Bob Lantz e0bf8ece3c Minor code cleanup 2014-11-23 17:17:21 -08:00
Bob Lantz 37bdf14b49 Rename examples.{intfOptions,multiLink} -> {intfoptions,multilink} 2014-11-23 17:11:06 -08:00
Bob Lantz 292e69f89a Renamed to intfoptions.py for consistency 2014-11-23 17:07:32 -08:00
Bob Lantz dd876e69c8 DemoCLI -> ClusterCLI 2014-11-23 17:04:07 -08:00
lantz 4e644d7465 Merge pull request #450 from mininet/sw-cmd
adding deleteIntfs option to switches and corresponding CLI command
2014-11-24 11:27:59 -08:00
lantz 596fd9d0c6 Merge pull request #449 from cdburkard/devel/cluster_controlPaths
use ControlPersist ssh option to fix ControlPath shutdown
2014-11-23 14:59:11 -08:00
Bob Lantz 474f68600e Make pylint happier for no particular reason 2014-11-23 11:13:54 -08:00
Bob Lantz 50774e407c Remove unused imports 2014-11-23 11:11:03 -08:00
Bob Lantz 8e63e2c540 Fix undefined sleep() 2014-11-23 11:10:43 -08:00
Bob Lantz 273c4e9403 Fix typo. ;-p 2014-11-23 11:09:21 -08:00
Bob Lantz c273f49077 type( foo ) is bar -> isinstance( foo, bar ) 2014-11-23 11:06:12 -08:00
Bob Lantz 9a8bdfd765 use isinstance( obj, basestring) to allow unicode strings
fixes #448
2014-11-23 10:59:08 -08:00
Brian O'Connor 23dfbe4ae2 explicit param call in cli command 2014-11-21 18:35:37 -08:00
Brian O'Connor b57e5d93b8 adding deleteIntfs option to switches and corresponding CLI command 2014-11-21 18:33:06 -08:00
Bob Lantz f0f9907b9d Add setup() and dpctl() methods for LinuxBridge
Also adds docstrings.
Fixes #422
2014-11-19 09:32:56 -08:00
Bob Lantz c4fc630413 Correctly call cli() rather than CLI() 2014-11-19 09:18:05 -08:00
Bob Lantz aabbf29542 Revert to old "Adding controller" message 2014-11-19 08:58:17 -08:00
backb1 b7898befef raise exception when no prefixLen is set 2014-11-19 08:58:17 -08:00
backb1 6721f0655e Some clarifications 2014-11-19 08:58:17 -08:00
lantz d37d6ecd99 Merge pull request #241 from moijes12/fix44
(pending) Create test_switchdpidassignment.py
2014-11-20 14:14:14 -08:00
Bob Lantz e4db698184 Fix indentation error so we don't wait forever. 2014-11-19 05:58:36 -08:00
lantz e661a4b1c3 Merge pull request #447 from rlane/ivs-verbose-off
IVSSwitch: turn off verbose logging by default
2014-11-20 11:56:42 -08:00
Rich Lane f5164f86b7 IVSSwitch: turn off verbose logging by default
Most users don't need this much logging and it slows down the switch.
2014-11-19 18:24:49 -08:00
Bob Lantz 9e0c1549f4 Only uninstall ntpd on COW disk. 2014-11-19 04:59:23 -08:00
Bob Lantz 55b455e9ae Fix typo in depend() 2014-11-19 03:20:48 -08:00
cody burkard 34933ef741 use ControlPersist ssh option to create a ControlMaster connection that will not die when a node dies 2014-11-18 17:22:06 -08:00
cody burkard bbf94cdb63 use rcmd instead of quietRun when shutting down remote nodes 2014-11-17 17:55:06 -08:00
Brian O'Connor 593fc365d0 fixing --custom in mn (there was one two many selfs) 2014-11-17 17:46:24 -08:00
Brian O'Connor 1edf3515e3 Merge pull request #439 from cdburkard/patches/sudoers
fix sudoers file to allow -u option
2014-11-17 17:01:50 -08:00
cody burkard 0d271f9477 fix sudoers file to allow -u option 2014-11-17 16:19:28 -08:00
Bob Lantz 3534f77761 Remove extra git clone line 2014-11-12 18:31:10 -08:00
Bob Lantz 64bbaeccc4 Add doxygen-latex if needed (14.04+) 2014-11-12 17:45:36 -08:00
Bob Lantz de002b0d9a Remove ^S which was in this file (thanks emacs bindings) 2014-11-12 17:39:50 -08:00
Bob Lantz 307302ed32 Clarify dependencies 2014-11-12 15:52:53 -08:00
Bob Lantz a60f77ad36 Clarify checking out a version, and add Debian/Fedora 2014-11-12 15:50:41 -08:00
Bob Lantz 383c3fb3e4 in-line documentation link 2014-11-12 15:50:24 -08:00
Bob Lantz a64f8c28bd Don't blow away parameters that aren't specified in node.config() 2014-11-12 15:17:17 -08:00
Bob Lantz 377d1b1cd8 2.2.0b0 -> 2.2.0b1 2014-11-12 13:33:34 -08:00
Bob Lantz dde2263fe7 Disable shared SSH connections by default.
Note that we do still provide a default if you specify
ControlPath=True
2014-11-12 13:29:58 -08:00
Bob Lantz 434619053a Satisfy pyflakes by making a local cli variable 2014-11-12 13:12:22 -08:00
Bob Lantz b739cd11c8 Remove obsolete util.custom(), and make custom() a method 2014-11-12 13:08:36 -08:00
lantz bc6ef0dad8 Merge pull request #435 from mininet/devel/custom
Adding support for multiple custom files
2014-11-12 12:25:46 -08:00
Brian O'Connor 3ac0dd7093 Adding support for multiple custom files
Works in a few ways:
 * a single file: --custom foo.py (as before)
 * comma-separated list: --custom foo.py,bar.py
 * multiple custom args: --custom foo.py --custom bar.py
2014-11-11 03:14:46 -08:00
Bob Lantz 0f0fe82350 Fix typo, ugh. 2014-11-11 00:14:11 -08:00
Bob Lantz 6be4bfd026 Avoid mirrors.kernel.org for now 2014-11-10 23:31:08 -08:00
Bob Lantz d9d209f34d Update from official archive since mirror seems to be failing 2014-11-10 23:00:56 -08:00
Bob Lantz 2059786f7b Use sudo -n when talking to VM 2014-11-10 22:45:42 -08:00
lantz 6008f987d3 Merge pull request #436 from cdburkard/patches/plot
fix plot command to work when standard classes are present
2014-11-11 11:54:56 -08:00
Bob Lantz 635e8f11f3 Add -q option to apt-get for quieter logging 2014-11-10 19:37:11 -08:00
Bob Lantz 9b5fa1d7ed Always chdir() to current working directory. 2014-11-10 16:57:12 -08:00
Bob Lantz 1955e90493 Minor cleanup. 2014-11-10 16:50:24 -08:00
Bob Lantz 222e87daeb Rearrange init code slightly. 2014-11-10 16:48:20 -08:00
Bob Lantz a89ccb789e Fix problem of ssh'ing into "localhost" on a remote node. 2014-11-10 16:46:43 -08:00
lantz 2013b7ae81 Merge pull request #428 from cdburkard/patches/cluster_servers
standardize on localhost for local server's name
2014-11-10 16:23:59 -08:00
Bob Lantz 3e1100b71a Clarify MultiTopo docstrs and copy addLInk opts
Note: it's a bit confusing, but we need to copy the link
parameter dicts (since we update them with node info), but we
can share the node dicts. Perhaps we should copy the node
dicts as well...
2014-11-10 15:18:05 -08:00
lantz 086afe852b Merge pull request #434 from cdburkard/patches/cluster_x11
wrap the title string in quotes so that bash interprets it correctly
2014-11-10 14:12:39 -08:00
Bob Lantz abcdf18547 Uninstall ntpd to disable it more reliably 2014-11-10 14:09:35 -08:00
Bob Lantz 4a304688f4 Select TCP Reno and run iperf for a longer time interval
The hope is that this will make the results a bit more consistent
when running in a VM environment.
2014-11-10 12:39:55 -08:00
Bob Lantz d3377bf911 Add seconds option to iperf() 2014-11-10 12:37:54 -08:00
Bob Lantz 481cbea10c Update mininet docs ref to markdown format 2014-11-07 15:23:18 -08:00
Bob Lantz b817cbc0ed Update documentation link 2014-11-07 15:12:12 -08:00
lantz 15275048b6 Merge pull request #433 from mininet/devel/update-version-2.2b0
Initial update of README, text files and versions for 2.2.0b0
2014-11-07 16:58:12 -08:00
lantz 3baccfee3a Merge pull request #416 from mininet/devel/multitopo
Multi-link topology support
2014-11-07 16:16:25 -08:00
Bob Lantz bb76c21275 Use 2.2.0b0 for consistency with earlier Mininet releases 2014-11-07 15:08:15 -08:00
Bob Lantz c92c4efb66 Add a few clarifying comments 2014-11-07 13:47:03 -08:00
Bob Lantz 39203484a6 Make port1, port2 truly optional and don't pass them to Link() 2014-11-07 13:42:50 -08:00
Bob Lantz 2a2d605074 Get rid of paramDict and simplify things a bit 2014-11-07 13:40:00 -08:00
cody burkard 0676346aeb fix plot command to work when standard classes are present 2014-11-07 05:10:06 -08:00
cody burkard 93fdb69ee3 standardize on localhost for local server's name 2014-11-07 02:32:42 -08:00
Brian O'Connor c7921fe402 Merge pull request #432 from mininet/devel/fallback
Fall back to OVSBridge if no controller is available for default switch
2014-11-07 01:29:06 -08:00
cody burkard 3660e6d02f wrap the title string in quotes so that bash interprets it correctly 2014-11-07 01:11:40 -08:00
Bob Lantz 083322a217 Draft update for Mininet 2.2b0 2014-11-06 12:05:55 -08:00
Bob Lantz 8225105cf2 Fix to allow more flexible version numbers 2014-11-06 12:05:33 -08:00
Bob Lantz ccd3276dcf Raise exception if DefaultController cannot find a controller 2014-11-05 18:43:58 -08:00
Bob Lantz f51eddef6d Return controller correctly. 2014-11-05 18:41:00 -08:00
Bob Lantz 4f8aa1d8a0 Don't check rt_runtime_us for CFS scheduler 2014-11-05 16:49:28 -08:00
Bob Lantz 060d46a282 Set VM date based on host date.
This should fix #398 for real; note that if we try to shut down ntpd
right at boot, it doesn't work! ;-(

However, setting the Unix time in the traditional manner using seconds
since 1970 should do the trick!
2014-11-05 16:18:13 -08:00
Bob Lantz 820c3be7df Reorganize CFS and RT default/error conditions. 2014-11-04 17:20:35 -08:00
Bob Lantz a562ca1be3 Move RT check into its own method, and save value. 2014-11-04 16:27:34 -08:00
lantz 658761d953 Merge pull request #419 from cdburkard/patches/rt_failure_output
fix silent failures when rt cannot be assigned - will follow up on this
2014-11-04 16:09:25 -08:00
Bob Lantz 1b69ea13f5 Merge branch 'fallback' of https://github.com/thinred/mininet into thinred-fallback
Conflicts:
	bin/mn
	mininet/node.py
2014-11-04 03:24:16 -08:00
Bob Lantz 6e5ac34bc2 Update module comment. 2014-11-03 20:01:22 -08:00
lantz ec9f02c7ab Merge pull request #424 from cdburkard/devel/mergePrivate
merge HostWithPrivateDirs into Node
2014-11-04 15:09:20 -08:00
lantz f75bee62b5 Merge pull request #429 from cdburkard/patches/cluster_m
ensure we retrieve a single PID when run on a cluster node
2014-11-04 12:49:08 -08:00
Bob Lantz e77123cf0e Remove unnecessary 0 2014-11-03 14:25:09 -08:00
Bob Lantz 8dea57d271 Ignore link info when sorting links. 2014-11-03 14:20:01 -08:00
Bob Lantz 634761b8a7 Fix edges() and add convertTo() to Topo() (with keys option) 2014-11-03 12:59:53 -08:00
Bob Lantz 01aac350fa Remove unused edgeinfo 2014-11-03 12:43:52 -08:00
Cody Burkard 08d611f49b fix silent failures when rt cannot be assigned 2014-10-31 20:30:36 -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
cody burkard f66904ab90 ensure we retrieve a single PID when run on a cluster node 2014-10-31 08:23:00 -07:00
cody burkard 6a363f65e3 unmount private directories after use 2014-10-31 04:59:35 -07:00
cody burkard 736db20c9f merge HostWithPrivateDirs into Host 2014-10-31 04:43:10 -07:00
Bob Lantz ba8ea8f0cc Return (src, dst) in original order, and allow keys + data 2014-10-28 20:43:10 -07:00
Bob Lantz eab4ea3fb9 Minor fixes 2014-10-28 17:05:00 -07:00
cody burkard 06d9e4bba7 add example and test for multiple links 2014-10-28 16:10:46 -07:00
Bob Lantz 38ce329e7e Allow Mininet() to accept multi-link topos w/correct params. 2014-10-28 16:06:53 -07:00
Bob Lantz 94f088d7e8 Allow natural sort to accept non-strings. 2014-10-28 16:06:53 -07:00
Bob Lantz 89fb081983 First crack at fixing multiple links
* Makes MultiGraph more like networkx.multigraph
* Adds converTo method
* Synchronizes node1 with xxx1 in link options
2014-10-28 15:50:06 -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
Tomasz Buchert 39a3b73f85 fallback to ovsb when no OF controller is unavailable 2014-08-13 17:04:24 +02:00
Tomasz Buchert e8623fdc91 introducing OVSBridge 2014-08-13 16:55:00 +02: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
moijes12 2fc5e46f1b Update test_switchdpidassignment.py 2014-01-30 23:41:46 +05:30
moijes12 09b06509a8 Create test_switchdpidassignment.py
Regression tests to verify switch datapath ID assignment.
2013-11-07 09:49:36 -08:00
55 changed files with 3219 additions and 742 deletions
+12 -1
View File
@@ -7,32 +7,43 @@ or send a pull request.
Contributors include:
Mininet Core Team
Mininet Core Team (and alumni)
Bob Lantz
Brandon Heller
Nikhil Handigol
Vimal Jeyakumar
Brian O'Connor
Cody Burkard
Additional Mininet Contributors
Tomasz Buchert
Gustavo Pantuza Coelho Pinto
Fernando Cappi
Ryan Cox
Shaun Crampton
David Erickson
Glen Gibb
Andrew Ferguson
Eder Leao Fernandes
Gregory Gee
Jon Hall
Vitaly Ivanov
Rich Lane
Zi Shen Lim
Murphy McCauley
José Pedro Oliveira
James Page
Rich Lane
Rémy Léone
Angad Singh
Piyush Srivastava
Ed Swierk
Darshan Thaker
Andreas Wundsam
Isaku Yamahata
Baohua Yang
Thanks also to everyone who has submitted issues and pull
requests on github, and to our friendly mininet-discuss
+17 -11
View File
@@ -2,7 +2,7 @@
Mininet Installation/Configuration Notes
----------------------------------------
Mininet 2.1.0+
Mininet 2.2.0b1
---
The supported installation methods for Mininet are 1) using a
@@ -53,19 +53,27 @@ like to contribute an installation script, we would welcome it!)
Note that the above git command will check out the latest and greatest
Mininet (which we recommend!) If you want to run the last tagged/released
version of Mininet, use:
version of Mininet, you can look at the release tags using
git clone git://github.com/mininet/mininet
git checkout -b 2.1.0 2.1.0
cd mininet
git tag
If you are running Ubuntu, you may be able to use our handy
`install.sh` script, which is in `mininet/util`.
and then
git checkout <release tag>
where <release tag> is the release you want to check out.
If you are running Ubuntu, Debian, or Fedora, you may be able to use
our handy `install.sh` script, which is in `mininet/util`.
*WARNING: USE AT YOUR OWN RISK!*
`install.sh` is a bit intrusive and may possibly damage your OS
and/or home directory, by creating/modifying several directories
such as `mininet`, `openflow`, `oftest`, `pox`, etc..
such as `mininet`, `openflow`, `oftest`, `pox`, etc.. We recommend
trying it in a VM before trying it on a system you use from day to day.
Although we hope it won't do anything completely terrible, you may
want to look at the script before you run it, and you should make
sure your system and home directory are backed up just in case!
@@ -153,10 +161,8 @@ like to contribute an installation script, we would welcome it!)
* A Linux kernel compiled with network namespace support enabled
* An OpenFlow implementation (either the reference user or kernel
space implementations, or Open vSwitch.) Appropriate kernel
modules (e.g. tun and ofdatapath for the reference kernel
implementation) must be loaded.
* An compatible software switch such as Open vSwitch or
the Linux bridge.
* Python, `bash`, `ping`, `iperf`, etc.
+1 -1
View File
@@ -1,4 +1,4 @@
Mininet 2.1.0+ License
Mininet 2.2.0b1 License
Copyright (c) 2013 Open Networking Laboratory
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
+35 -24
View File
@@ -3,7 +3,7 @@ Mininet: Rapid Prototyping for Software Defined Networks
*The best way to emulate almost any network on your laptop!*
Version 2.1.0+
Mininet 2.2.0b1
### What is Mininet?
@@ -66,28 +66,33 @@ Mininet includes:
`mn -c`
### New features in 2.1.0+
### New features in this release
Mininet 2.1.0+ provides a number of bug fixes as well as
This release provides a number of bug fixes as well as
several new features, including:
* Convenient access to `Mininet()` as a dict of nodes
* X11 tunneling (wireshark in Mininet hosts, finally!)
* Accurate reflection of the `Mininet()` object in the CLI
* Automatically detecting and adjusting resource limits
* Automatic cleanup on failure of the `mn` command
* Preliminary support for running OVS in user space mode
* Preliminary support (`IVSSwitch()`) for the Indigo Virtual Switch
* support for installing the OpenFlow 1.3 versions of the reference
user switch and NOX from CPqD
* The ability to import modules from `mininet.examples`
* Improved OpenFlow 1.3 support
We have provided several new examples (which can easily be
imported to provide useful functionality) including:
- `mn --switch ovs,protocols=openflow13` starts OVS in 1.3 mode
- `install.sh -w` installs 1.3-compatible Wireshark dissector using
Loxigen
- `install.sh -y` installs Ryu 1.3-compatible controller
* Modeling separate control and data networks: `mininet.examples.controlnet`
* Connecting Mininet hosts the internet (or a LAN) using NAT: `mininet.examples.nat`
* Creating per-host custom directories using bind mounts: `mininet.examples.bind`
* A new `nodelib.py` node library, and new `Node` types including
`LinuxBridge`, `OVSBridge`, `LinuxRouter` and `NAT`
* An improved MiniEdit GUI (`examples/miniedit.py`) - thanks to
Gregory Gee
* Support for multiple `--custom` arguments to `mn`
* Experimental cluster support - consult the
[documentation](http://docs.mininet.org) for details -
as well as `examples/cluster.py` and an experimental `--cluster`
option for topologies built with the default `Host` and `OVSSwitch`
classes:
`mn --cluster localhost,server1,server2`
Note that examples contain experimental features which might
"graduate" into mainline Mininet in the future, but they should
@@ -113,21 +118,27 @@ Mininet mailing list, `mininet-discuss` at:
<https://mailman.stanford.edu/mailman/listinfo/mininet-discuss>
### Contributing
### Join Us
Mininet is an open source project and is currently hosted
at <https://github.com/mininet>. You are encouraged to download
the code, examine it, modify it, and submit bug reports, bug fixes,
feature requests, new features and other issues and pull requests.
Thanks to everyone who has contributed to the project
(see CONTRIBUTORS for more info!)
Thanks to everyone who has contributed code to the Mininet project
(see CONTRIBUTORS for more info!) It is because of everyone's
hard work that Mininet continues to grow and improve.
### Enjoy Mininet
Best wishes, and we look forward to seeing what you can do with
Mininet to change the networking world!
### Credits
The Mininet 2.1.0+ Team:
The Mininet Core Team:
* Bob Lantz
* Brian O'Connor
* Cody Burkard
Thanks again to all of the Mininet contributors, particularly Gregory
Gee for his work on MiniEdit.
+110 -38
View File
@@ -22,19 +22,28 @@ 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,
UserSwitch, OVSSwitch,
RYU, NOX, RemoteController, findController, DefaultController,
UserSwitch, OVSSwitch, OVSBridge,
OVSLegacyKernelSwitch, IVSSwitch )
from mininet.nodelib import LinuxBridge
from mininet.link import Link, TCLink
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
from mininet.topolib import TreeTopo, TorusTopo
from mininet.util import custom, customConstructor
from mininet.util import customConstructor, splitArgs
from mininet.util import buildTopo
from functools import partial
# Experimental! cluster edition prototype
from mininet.examples.cluster import ( MininetCluster, RemoteHost,
RemoteOVSSwitch, RemoteLink,
SwitchBinPlacer, RandomPlacer )
from mininet.examples.clustercli import ClusterCLI
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
# built in topologies, created only when run
TOPODEF = 'minimal'
@@ -45,26 +54,29 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
'tree': TreeTopo,
'torus': TorusTopo }
SWITCHDEF = 'ovsk'
SWITCHDEF = 'default'
SWITCHES = { 'user': UserSwitch,
'ovs': OVSSwitch,
'ovsbr' : OVSBridge,
# Keep ovsk for compatibility with 2.0
'ovsk': OVSSwitch,
'ovsl': OVSLegacyKernelSwitch,
'ivs': IVSSwitch,
'lxbr': LinuxBridge }
'lxbr': LinuxBridge,
'default': OVSSwitch }
HOSTDEF = 'proc'
HOSTS = { 'proc': Host,
'rt': custom( CPULimitedHost, sched='rt' ),
'cfs': custom( CPULimitedHost, sched='cfs' ) }
'rt': partial( CPULimitedHost, sched='rt' ),
'cfs': partial( CPULimitedHost, sched='cfs' ) }
CONTROLLERDEF = 'default'
CONTROLLERS = { 'ref': Controller,
'ovsc': OVSController,
'nox': NOX,
'remote': RemoteController,
'default': DefaultController,
'ryu': RYU,
'default': DefaultController, # Note: replaced below
'none': lambda name: None }
LINKDEF = 'default'
@@ -106,6 +118,7 @@ def version( *_args ):
print "%s" % VERSION
exit()
class MininetRunner( object ):
"Build, setup, and run Mininet."
@@ -119,6 +132,29 @@ class MininetRunner( object ):
self.setup()
self.begin()
def custom( self, option, opt_str, value, parser ):
"""Parse custom file and add params.
option: option e.g. --custom
opt_str: option string e.g. --custom
value: the value the follows the option
parser: option parser instance"""
files = []
if os.path.isfile( value ):
# Accept any single file (including those with commas)
files.append( value )
else:
# Accept a comma-separated list of filenames
files += value.split(',')
for fileName in files:
customs = {}
if os.path.isfile( fileName ):
execfile( fileName, customs, customs )
for name, val in customs.iteritems():
self.setCustom( name, val )
else:
raise Exception( 'could not find custom file: %s' % fileName )
def setCustom( self, name, value ):
"Set custom parameters for MininetRunner."
if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
@@ -132,26 +168,20 @@ class MininetRunner( object ):
# Add or modify global variable or class
globals()[ name ] = value
def parseCustomFile( self, fileName ):
"Parse custom file and add params before parsing cmd-line options."
customs = {}
if os.path.isfile( fileName ):
execfile( fileName, customs, customs )
for name, val in customs.iteritems():
self.setCustom( name, val )
def setNat( self, option, opt_str, value, parser ):
parser.values.nat = True
if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-': #first arg, first char != '-'
value = parser.rargs.pop( 0 )
_, args, kwargs = splitArgs( opt_str + ',' + value )
parser.values.nat_args = args
parser.values.nat_kwargs = kwargs
else:
raise Exception( 'could not find custom file: %s' % fileName )
parser.values.nat_args = []
parser.values.nat_kwargs = {}
def parseArgs( self ):
"""Parse command-line args and return options object.
returns: opts parse options dict"""
if '--custom' in sys.argv:
index = sys.argv.index( '--custom' )
if len( sys.argv ) > index + 1:
filename = sys.argv[ index + 1 ]
self.parseCustomFile( filename )
else:
raise Exception( 'Custom file name not found' )
desc = ( "The %prog utility creates Mininet network from the\n"
"command line. It can create parametrized topologies,\n"
@@ -169,9 +199,9 @@ class MininetRunner( object ):
opts.add_option( '--clean', '-c', action='store_true',
default=False, help='clean and exit' )
opts.add_option( '--custom', type='string', default=None,
help='read custom topo and node params from .py' +
'file' )
opts.add_option( '--custom', action='callback',
callback=self.custom,
type='string', help='read custom classes or params from .py file(s)' )
opts.add_option( '--test', type='choice', choices=TESTS,
default=TESTS[ 0 ],
help='|'.join( TESTS ) )
@@ -200,10 +230,22 @@ class MininetRunner( object ):
opts.add_option( '--pin', action='store_true',
default=False, help="pin hosts to CPU cores "
"(requires --host cfs or --host rt)" )
opts.add_option( '--nat', action='store_true',
default=False, help="adds a NAT to the topology "
"that connects Mininet to the physical network" )
opts.add_option( '--version', action='callback', callback=version )
opts.add_option( '--nat', action='callback', callback=self.setNat,
help="adds a NAT to the topology that connects Mininet hosts"
" to the physical network."
" Warning: This may route any traffic on the machine that uses Mininet's"
" IP subnet into the Mininet network. If you need to change"
" Mininet's IP subnet, see the --ipbase option." )
opts.add_option( '--version', action='callback', callback=version,
help='prints the version and exits' )
opts.add_option( '--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()
@@ -232,6 +274,22 @@ class MininetRunner( object ):
start = time.time()
if self.options.controller == 'default':
# Update default based on available controllers
CONTROLLERS[ 'default' ] = findController()
if CONTROLLERS[ 'default' ] is None:
if self.options.switch == 'default':
# Fall back to OVS Bridge, which does not use an OF controller
info( '*** No default OpenFlow controller found for default switch!\n' )
info( '*** Falling back to OVS Bridge\n' )
self.options.switch = 'ovsbr'
self.options.controller = 'none'
elif self.options.switch in ( 'ovsbr', 'lxbr' ):
self.options.controller = 'none'
else:
raise Exception( "Could not find a default controller for switch %s" %
self.options.switch )
topo = buildTopo( TOPOS, self.options.topo )
switch = customConstructor( SWITCHES, self.options.switch )
host = customConstructor( HOSTS, self.options.host )
@@ -241,8 +299,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 +307,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
cli = ClusterCLI if cluster else CLI
if cluster:
warn( '*** WARNING: Experimental cluster mode!\n'
'*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
Net = partial( MininetCluster, servers=cluster.split( ',' ),
placement=PLACEMENT[ self.options.placement ] )
mn = Net( topo=topo,
switch=switch, host=host, controller=controller,
link=link,
@@ -260,12 +332,12 @@ class MininetRunner( object ):
autoStaticArp=arp, autoPinCpus=pin,
listenPort=listenPort )
if self.options.nat:
nat = mn.addNAT()
if self.options.ensure_value( 'nat', False ):
nat = mn.addNAT( *self.options.nat_args, **self.options.nat_kwargs )
nat.configDefault()
if self.options.pre:
CLI( mn, script=self.options.pre )
cli( mn, script=self.options.pre )
test = self.options.test
test = ALTSPELLING.get( test, test )
@@ -280,13 +352,13 @@ class MininetRunner( object ):
mn.ping()
mn.iperf()
elif test == 'cli':
CLI( mn )
cli( mn )
elif test != 'build':
mn.waitConnected()
getattr( mn, test )()
if self.options.post:
CLI( mn, script=self.options.post )
cli( mn, script=self.options.post )
mn.stop()
+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 ) )
+5 -8
View File
@@ -35,10 +35,10 @@ on '/var/mn'
"""
from mininet.net import Mininet
from mininet.node import Host, HostWithPrivateDirs
from mininet.node import Host
from mininet.cli import CLI
from mininet.topo import SingleSwitchTopo
from mininet.log import setLogLevel, info, debug
from mininet.log import setLogLevel, info
from functools import partial
@@ -51,15 +51,12 @@ def testHostWithPrivateDirs():
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
( '/var/run', '/tmp/%(name)s/var/run' ),
'/var/mn' ]
host = partial( HostWithPrivateDirs,
host = partial( Host,
privateDirs=privateDirs )
net = Mininet( topo=topo, host=host )
net.start()
directories = []
for directory in privateDirs:
directories.append( directory[ 0 ]
if isinstance( directory, tuple )
else directory )
directories = [ directory[ 0 ] if isinstance( directory, tuple )
else directory for directory in privateDirs ]
info( 'Private Directories:', directories, '\n' )
CLI( net )
net.stop()
+853
View File
@@ -0,0 +1,853 @@
#!/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, 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='localhost', user=None, serverIP=None,
controlPath=False, splitInit=False, **kwargs):
"""Instantiate a remote node
name: name of remote node
server: remote server (optional)
user: user on remote server (optional)
controlPath: specify shared ssh control path (optional)
splitInit: split initialization?
**kwargs: see Node()"""
# We connect to servers by IP address
self.server = server if server else 'localhost'
self.serverIP = serverIP if serverIP else self.findServerIP( self.server )
self.user = user if user else self.findUser()
if controlPath is True:
# Set a default control path for shared SSH connections
controlPath = '/tmp/mn-%r@%h:%p'
self.controlPath = controlPath
self.splitInit = splitInit
if self.user and self.server != 'localhost':
self.dest = '%s@%s' % ( self.user, self.serverIP )
self.sshcmd = [ 'sudo', '-E', '-u', self.user ] + self.sshbase
if self.controlPath:
self.sshcmd += [ '-o', 'ControlPath=' + self.controlPath,
'-o', 'ControlMaster=auto',
'-o', 'ControlPersist=' + '1' ]
self.sshcmd = self.sshcmd + [ self.dest ]
self.isRemote = True
else:
self.dest = None
self.sshcmd = []
self.isRemote = False
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 ):
"Return our server's IP address"
# First, check for an IP address
ipmatch = cls._ipMatchRegex.findall( server )
if ipmatch:
return ipmatch[ 0 ]
# Otherwise, look up remote server
output = quietRun( 'getent ahostsv4 %s' % server )
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.isRemote:
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.isRemote:
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 )
def cleanup( self ):
"Help python collect its garbage."
# Intfs may end up in root NS
for intfName in self.intfNames():
if self.name in intfName:
self.rcmd( 'ip link del ' + intfName )
self.shell = None
class RemoteNode( RemoteMixin, Node ):
"A node on a remote server"
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', 'localhost' )
server2 = getattr( node2, 'server', 'localhost' )
if server1 == 'localhost' and server2 == 'localhost':
# 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"
# We should never try to create a tunnel to ourselves!
assert node1.server != 'localhost' or node2.server != 'localhost'
# And we can't ssh into this server remotely as 'localhost',
# so try again swappping node1 and node2
if node2.server == 'localhost':
return self.makeTunnel( node2, node1, intfname2, intfname1,
addr2, addr1 )
# 1. Create tap interfaces
for node in node1, node2:
# For now we are hard-wiring tap9, which we will rename
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', [ 'localhost' ] )
servers = [ s if s else 'localhost' 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.nodeInfo( node )
# keep local server name consistent accross nodes
if 'server' in config.keys() and config[ 'server' ] == None:
config[ 'server' ] = 'localhost'
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 setLogLevel
from mininet.examples.clustercli import ClusterCLI 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 ClusterCLI( 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
assert pygraphviz # silence pyflakes
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
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[ getattr( h, 'server', 'localhost' ) ] for h in hosts ]
scolors = [ color[ getattr( s, 'server', 'localhost' ) ] for s in switches ]
nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800, label='host',
node_color=hcolors, node_shape='s', **opts )
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 ClusterCLI 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 -16
View File
@@ -7,21 +7,8 @@ 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 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)
from mininet.util import custom, waitListening
from mininet.log import setLogLevel, info
def bwtest( cpuLimits, period_us=100000, seconds=5 ):
@@ -38,7 +25,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 )
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()
+19 -3
View File
@@ -27,7 +27,9 @@ from mininet.net import Mininet
from mininet.node import UserSwitch, OVSKernelSwitch, Controller
from mininet.topo import Topo
from mininet.log import lg
from mininet.util import irange
from mininet.util import irange, quietRun
from mininet.link import TCLink
from functools import partial
import sys
flush = sys.stdout.flush
@@ -70,13 +72,24 @@ 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 )
# Select TCP Reno
output = quietRun( 'sysctl -w net.ipv4.tcp_congestion_control=reno' )
assert 'reno' in output
for datapath in switches.keys():
print "*** testing", datapath, "datapath"
Switch = switches[ datapath ]
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,8 +97,11 @@ 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 ] )
bandwidth = net.iperf( [ src, dst ], seconds=10 )
print bandwidth
flush()
results[ datapath ] += [ ( n, bandwidth ) ]
+67
View File
@@ -0,0 +1,67 @@
#!/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
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()
+23 -46
View File
@@ -16,12 +16,16 @@ OpenFlow icon from https://www.opennetworking.org/
MINIEDIT_VERSION = '2.2.0.1'
from optparse import OptionParser
from Tkinter import *
# from Tkinter import *
from Tkinter import ( Frame, Label, LabelFrame, Entry, OptionMenu, Checkbutton,
Menu, Toplevel, Button, BitmapImage, PhotoImage, Canvas,
Scrollbar, Wm, TclError, StringVar, IntVar,
E, W, EW, NW, Y, VERTICAL, SOLID, CENTER,
RIGHT, LEFT, BOTH, TRUE, FALSE )
from ttk import Notebook
from tkMessageBox import showinfo, showerror, showwarning
from tkMessageBox import showerror
from subprocess import call
import tkFont
import csv
import tkFileDialog
import tkSimpleDialog
import re
@@ -36,22 +40,22 @@ if 'PYTHONPATH' in os.environ:
# someday: from ttk import *
from mininet.log import info, error, debug, output, setLogLevel
from mininet.log import info, setLogLevel
from mininet.net import Mininet, VERSION
from mininet.util import ipStr, netParse, ipAdd, quietRun
from mininet.util import netParse, ipAdd, quietRun
from mininet.util import buildTopo
from mininet.util import custom, customConstructor
from mininet.term import makeTerm, cleanUpScreens
from mininet.node import Controller, RemoteController, NOX, OVSController
from mininet.node import CPULimitedHost, Host, Node, HostWithPrivateDirs
from mininet.node import OVSKernelSwitch, OVSSwitch, UserSwitch
from mininet.node import CPULimitedHost, Host, Node
from mininet.node import OVSSwitch, UserSwitch
from mininet.link import TCLink, Intf, Link
from mininet.cli import CLI
from mininet.moduledeps import moduleDeps, pathCheck
from mininet.moduledeps import moduleDeps
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
from mininet.topolib import TreeTopo
print 'MiniEdit running against MiniNet '+VERSION
print 'MiniEdit running against Mininet '+VERSION
MININET_VERSION = re.sub(r'[^\d\.]', '', VERSION)
if StrictVersion(MININET_VERSION) > StrictVersion('2.0'):
from mininet.node import IVSSwitch
@@ -78,32 +82,6 @@ HOSTS = { 'proc': Host,
'cfs': custom( CPULimitedHost, sched='cfs' ) }
class CPULimitedHostWithPrivateDirs( CPULimitedHost ):
"Host with private directories"
def __init__( self, name, sched='cfs', **kwargs ):
"privateDirs: list of private directory strings or tuples"
self.name = name
self.privateDirs = kwargs.pop( 'privateDirs', [] )
CPULimitedHost.__init__( self, name, sched, **kwargs )
self.mountPrivateDirs()
def mountPrivateDirs( self ):
"mount private directories"
for directory in self.privateDirs:
if isinstance( directory, tuple ):
# mount given private directory
privateDir = directory[ 1 ] % self.__dict__
mountPoint = directory[ 0 ]
self.cmd( 'mkdir -p %s' % privateDir )
self.cmd( 'mkdir -p %s' % mountPoint )
self.cmd( 'mount --bind %s %s' %
( privateDir, mountPoint ) )
else:
# mount temporary filesystem on directory
self.cmd( 'mkdir -p %s' % directory )
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
class InbandController( RemoteController ):
def checkListening( self ):
@@ -1400,7 +1378,6 @@ class MiniEdit( Frame ):
def addNamedNode( self, node, name, x, y):
"Add a new node to our canvas."
c = self.canvas
icon = self.nodeIcon( node, name )
item = self.canvas.create_window( x, y, anchor='c', window=icon,
tags=node )
@@ -1701,7 +1678,6 @@ class MiniEdit( Frame ):
f.write("from subprocess import call\n")
inBandCtrl = False
hasLegacySwitch = False
for widget in self.widgetToItem:
name = widget[ 'text' ]
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
@@ -1846,16 +1822,16 @@ class MiniEdit( Frame ):
linkopts = linkDetail['linkOpts']
srcName, dstName = src[ 'text' ], dst[ 'text' ]
bw = ''
delay = ''
loss = ''
max_queue_size = ''
# delay = ''
# loss = ''
# max_queue_size = ''
linkOpts = "{"
if 'bw' in linkopts:
bw = linkopts['bw']
linkOpts = linkOpts + "'bw':"+str(bw)
optsExist = True
if 'delay' in linkopts:
delay = linkopts['delay']
# delay = linkopts['delay']
if optsExist:
linkOpts = linkOpts + ","
linkOpts = linkOpts + "'delay':'"+linkopts['delay']+"'"
@@ -2561,8 +2537,8 @@ class MiniEdit( Frame ):
link = self.selection
linkDetail = self.links[link]
src = linkDetail['src']
dest = linkDetail['dest']
# src = linkDetail['src']
# dest = linkDetail['dest']
linkopts = linkDetail['linkOpts']
linkBox = LinkDialog(self, title='Link Details', linkDefaults=linkopts)
if linkBox.result is not None:
@@ -2653,7 +2629,7 @@ class MiniEdit( Frame ):
del source.links[ dest ]
del dest.links[ source ]
stags = self.canvas.gettags( self.widgetToItem[ source ] )
dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
# dtags = self.canvas.gettags( self.widgetToItem[ dest ] )
ltags = self.canvas.gettags( link )
if 'control' in ltags:
@@ -2784,16 +2760,17 @@ class MiniEdit( Frame ):
# Create the correct host class
if 'cores' in opts or 'cpu' in opts:
if ('privateDirectory' in opts):
hostCls = partial( CPULimitedHostWithPrivateDirs,
hostCls = partial( CPULimitedHost,
privateDirs=opts['privateDirectory'] )
else:
hostCls=CPULimitedHost
else:
if ('privateDirectory' in opts):
hostCls = partial( HostWithPrivateDirs,
hostCls = partial( Host,
privateDirs=opts['privateDirectory'] )
else:
hostCls=Host
print hostCls
newHost = net.addHost( name,
cls=hostCls,
ip=ip,
-2
View File
@@ -22,11 +22,9 @@ to-do:
from mininet.net import Mininet
from mininet.node import OVSSwitch
from mininet.topo import LinearTopo
from mininet.util import quietRun
from mininet.log import output, warn
from random import randint
from re import findall
class MobilitySwitch( OVSSwitch ):
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/python
"""
This is a simple example that demonstrates multiple links
between nodes.
"""
from mininet.cli import CLI
from mininet.log import setLogLevel
from mininet.net import Mininet
from mininet.topo import Topo
def runMultiLink():
topo = simpleMultiLinkTopo( n=2 )
net = Mininet( topo=topo )
net.start()
CLI( net )
net.stop()
class simpleMultiLinkTopo( Topo ):
def __init__( self, n, **kwargs ):
Topo.__init__( self, **kwargs )
h1, h2 = self.addHost( 'h1' ), self.addHost( 'h2' )
s1 = self.addSwitch( 's1' )
for _ in range( n ):
self.addLink( s1, h1 )
self.addLink( s1, h2 )
if __name__ == '__main__':
setLogLevel( 'info' )
runMultiLink()
-2
View File
@@ -9,7 +9,6 @@ and that the ovs ports match the mininet ports.
from mininet.net import Mininet
from mininet.node import Controller
from mininet.log import setLogLevel, info, warn
from mininet.node import Node
def validatePort( switch, intf ):
"Validate intf's OF port number"
@@ -48,7 +47,6 @@ def net():
net.addLink( h4, s1 )
net.addLink( h5, s1, port1 = 1, port2 = 9 ) # specify a different port to connect host 5 to on the switch.
root = Node( 'root', inNamespace=False )
info( '*** Starting network\n' )
net.start()
+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))
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()
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python
'''
Test for multiple links between nodes
validates mininet interfaces against systems interfaces
'''
import unittest
import pexpect
class testMultiLink( unittest.TestCase ):
prompt = 'mininet>'
def testMultiLink(self):
p = pexpect.spawn( 'python -m mininet.examples.multilink' )
p.expect( self.prompt )
p.sendline( 'intfs' )
p.expect( 's(\d): lo' )
intfsOutput = p.before
# parse interfaces from mininet intfs, and store them in a list
hostToIntfs = intfsOutput.split( '\r\n' )[ 1:3 ]
intfList = []
for hostToIntf in hostToIntfs:
intfList += [ intf for intf in
hostToIntf.split()[1].split(',') ]
# get interfaces from system by running ifconfig on every host
sysIntfList = []
opts = [ 'h(\d)-eth(\d)', self.prompt ]
p.expect( self.prompt )
p.sendline( 'h1 ifconfig' )
while True:
p.expect( opts )
if p.after == self.prompt:
break
sysIntfList.append( p.after )
p.sendline( 'h2 ifconfig' )
while True:
p.expect( opts )
if p.after == self.prompt:
break
sysIntfList.append( p.after )
failMsg = ( 'The systems interfaces and mininet interfaces\n'
'are not the same' )
self.assertEqual( sysIntfList, intfList, msg=failMsg )
p.sendline( 'exit' )
p.wait()
if __name__ == '__main__':
unittest.main()
+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 )
+23 -13
View File
@@ -16,11 +16,27 @@ import time
from mininet.log import info
from mininet.term import cleanUpScreens
def sh( cmd ):
"Print a command and send it to the shell"
info( cmd + '\n' )
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:' )
time.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!"""
@@ -34,7 +50,7 @@ def cleanup():
# you can't connect to them either, so they're mostly harmless.
# Send SIGTERM first to give processes a chance to shutdown cleanly.
sh( 'killall ' + zombies + ' 2> /dev/null' )
time.sleep(1)
time.sleep( 1 )
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
# And kill off sudo mnexec
@@ -70,17 +86,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" )
+58 -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, 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,31 @@ 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 do_switch( self, line ):
"Starts or stops a switch"
args = line.split()
if len(args) != 2:
error( 'invalid number of args: switch <switch name> {start, stop}\n' )
return
sw = args[ 0 ]
command = args[ 1 ]
if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches :
error( 'invalid switch: %s\n' % args[ 1 ] )
else:
sw = args[ 0 ]
command = args[ 1 ]
if command == 'start':
self.mn.get( sw ).start( self.mn.controllers )
elif command == 'stop':
self.mn.get( sw ).stop( deleteIntfs=False )
else:
error( 'invalid command: switch <switch name> {start, stop}\n' )
def default( self, line ):
"""Called on an input line when the command prefix is not recognized.
Overridden to run shell commands when a node is the first CLI argument.
@@ -395,6 +435,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
+89 -36
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
@@ -63,6 +70,8 @@ class Intf( object ):
self.ip, self.prefixLen = ipstr.split( '/' )
return self.ifconfig( ipstr, 'up' )
else:
if prefixLen is None:
raise Exception( 'No prefix length set for IP address %s' % ( ipstr, ) )
self.ip, self.prefixLen = ipstr, prefixLen
return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
@@ -79,7 +88,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 +102,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 +126,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"
@@ -129,9 +160,9 @@ class Intf( object ):
f = getattr( self, method, None )
if not f or value is None:
return
if type( value ) is list:
if isinstance( value, list ):
result = f( *value )
elif type( value ) is dict:
elif isinstance( value, dict ):
result = f( **value )
else:
result = f( value )
@@ -154,8 +185,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 +194,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 +365,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 +380,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 )
+2 -2
View File
@@ -28,9 +28,9 @@ def moduleDeps( subtract=None, add=None ):
add: string or list of module names to add, if not already loaded"""
subtract = subtract if subtract is not None else []
add = add if add is not None else []
if type( subtract ) is str:
if isinstance( subtract, basestring ):
subtract = [ subtract ]
if type( add ) is str:
if isinstance( add, basestring ):
add = [ add ]
for mod in subtract:
if mod in lsmod():
+96 -52
View File
@@ -90,9 +90,11 @@ import os
import re
import select
import signal
import copy
import random
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
@@ -104,7 +106,7 @@ from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
from mininet.term import cleanUpScreens, makeTerms
# Mininet version: should be consistent with README and LICENSE
VERSION = "2.1.0+"
VERSION = "2.2.0b1"
class Mininet( object ):
"Network emulation with hosts spawned in network namespaces."
@@ -155,6 +157,7 @@ class Mininet( object ):
self.hosts = []
self.switches = []
self.controllers = []
self.links = []
self.nameToNode = {} # name to Node (Host/Switch) objects
@@ -262,7 +265,7 @@ class Mininet( object ):
return controller_new
def addNAT( self, name='nat0', connect=True, inNamespace=False, **params ):
nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
subnet=self.ipBase, **params )
# find first switch and create link
if connect:
@@ -318,21 +321,38 @@ class Mininet( object ):
"return (key,value) tuple list for every node in net"
return zip( self.keys(), self.values() )
@staticmethod
def randMac():
"Return a random, non-multicast MAC address"
return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
0x020000000000 )
def addLink( self, node1, node2, port1=None, port2=None,
cls=None, **params ):
""""Add a link from node1 to node2
node1: source node
node2: dest node
port1: source port
port2: dest port
node1: source node (or name)
node2: dest node (or name)
port1: source port (optional)
port2: dest port (optional)
cls: link class (optional)
params: additional link params (optional)
returns: link object"""
defaults = { 'port1': port1,
'port2': port2,
'intf': self.intf }
defaults.update( params )
if not cls:
cls = self.link
return cls( node1, node2, **defaults )
# Accept node objects or names
node1 = node1 if not isinstance( node1, basestring ) else self[ node1 ]
node2 = node2 if not isinstance( node2, basestring ) else self[ node2 ]
options = dict( params )
# Port is optional
if port1 is not None:
options.setdefault( 'port1', port1 )
if port2 is not None:
options.setdefault( 'port2', port2 )
# Set default MAC - this should probably be in Link
options.setdefault( 'addr1', self.randMac() )
options.setdefault( 'addr2', self.randMac() )
cls = self.link if cls is None else cls
link = cls( node1, node2, **options )
self.links.append( link )
return link
def configHosts( self ):
"Configure a set of hosts."
@@ -350,7 +370,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 ):
@@ -369,7 +388,7 @@ class Mininet( object ):
# Add a default controller
info( '*** Adding controller\n' )
classes = self.controller
if type( classes ) is not list:
if not isinstance( classes, list ):
classes = [ classes ]
for i, cls in enumerate( classes ):
# Allow Controller objects because nobody understands currying
@@ -389,12 +408,10 @@ class Mininet( object ):
info( switchName + ' ' )
info( '\n*** Adding links:\n' )
for srcName, dstName in topo.links(sort=True):
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
params = topo.linkInfo( srcName, dstName )
srcPort, dstPort = topo.port( srcName, dstName )
self.addLink( src, dst, srcPort, dstPort, **params )
info( '(%s, %s) ' % ( src.name, dst.name ) )
for srcName, dstName, params in topo.links(
sort=True, withInfo=True ):
self.addLink( **params )
info( '(%s, %s) ' % ( srcName, dstName ) )
info( '\n' )
@@ -447,7 +464,9 @@ class Mininet( object ):
self.build()
info( '*** Starting controller\n' )
for controller in self.controllers:
info( controller.name + ' ')
controller.start()
info( '\n' )
info( '*** Starting %s switches\n' % len( self.switches ) )
for switch in self.switches:
info( switch.name + ' ')
@@ -473,6 +492,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 +573,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 +617,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
@@ -670,11 +699,18 @@ class Mininet( object ):
# XXX This should be cleaned up
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None,
seconds=5):
"""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"""
udpBw: bandwidth target for UDP test
format: iperf format argument if any
seconds: iperf time to transmit
returns: 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
@@ -704,8 +740,8 @@ class Mininet( object ):
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
info( 'Waiting for iperf to start up...' )
sleep(.5)
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
bwArgs )
cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
server.IP() + ' ' + bwArgs )
debug( 'Client output: %s\n' % cliout )
server.sendInt()
servout += server.waitOutput()
@@ -722,33 +758,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
@@ -764,9 +808,9 @@ class Mininet( object ):
elif dst not in self.nameToNode:
error( 'dst not in network: %s\n' % dst )
else:
if type( src ) is str:
if isinstance( src, basestring ):
src = self.nameToNode[ src ]
if type( dst ) is str:
if isinstance( dst, basestring ):
dst = self.nameToNode[ dst ]
connections = src.connectionsTo( dst )
if len( connections ) == 0:
+203 -119
View File
@@ -11,16 +11,13 @@ Host: a virtual host. By default, a host is simply a shell; commands
may be sent using Cmd (which waits for output), or using sendCmd(),
which returns immediately, allowing subsequent monitoring using
monitor(). Examples of how to run experiments using this
functionality are provided in the examples/ directory.
functionality are provided in the examples/ directory. By default,
hosts share the root file system, but they may also specify private
directories.
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
RT or CFS bandwidth limiting.
HostWithPrivateDirs: a virtual host that has user-specified private
directories. These may be temporary directories stored as a tmpfs,
or persistent directories that are mounted from another directory in
the root filesystem.
Switch: superclass for switch nodes.
UserSwitch: a switch using the user-space switch from the OpenFlow
@@ -54,7 +51,7 @@ import pty
import re
import signal
import select
from subprocess import Popen, PIPE, STDOUT
from subprocess import Popen, PIPE
from operator import or_
from time import sleep
@@ -75,13 +72,15 @@ class Node( object ):
def __init__( self, name, inNamespace=True, **params ):
"""name: name of node
inNamespace: in network namespace?
privateDirs: list of private directory strings or tuples
params: Node parameters (see config() for details)"""
# Make sure class actually works
self.checkSetup()
self.name = name
self.inNamespace = inNamespace
self.name = params.get( 'name', name )
self.privateDirs = params.get( 'privateDirs', [] )
self.inNamespace = params.get( 'inNamespace', inNamespace )
# Stash configuration parameters for future reference
self.params = params
@@ -100,6 +99,7 @@ class Node( object ):
# Start command interpreter shell
self.startShell()
self.mountPrivateDirs()
# File descriptor to node mapping support
# Class variables and methods
@@ -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,37 @@ class Node( object ):
self.pollOut.poll()
self.waiting = False
self.cmd( 'stty -echo' )
self.cmd( 'set +m' )
def mountPrivateDirs( self ):
"mount private directories"
for directory in self.privateDirs:
if isinstance( directory, tuple ):
# mount given private directory
privateDir = directory[ 1 ] % self.__dict__
mountPoint = directory[ 0 ]
self.cmd( 'mkdir -p %s' % privateDir )
self.cmd( 'mkdir -p %s' % mountPoint )
self.cmd( 'mount --bind %s %s' %
( privateDir, mountPoint ) )
else:
# mount temporary filesystem on directory
self.cmd( 'mkdir -p %s' % directory )
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
def unmountPrivateDirs( self ):
"mount private directories"
for directory in self.privateDirs:
if isinstance( directory, tuple ):
self.cmd( 'umount ', directory[ 0 ] )
else:
self.cmd( 'umount ', directory )
def _popen( self, cmd, **params ):
"""Internal method: spawn and return a process
cmd: command to run (list)
params: parameters to Popen()"""
return Popen( cmd, **params )
def cleanup( self ):
"Help python collect its garbage."
@@ -204,8 +234,10 @@ class Node( object ):
def terminate( self ):
"Send kill signal to Node and clean up after it."
self.unmountPrivateDirs()
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 ):
@@ -226,7 +258,7 @@ class Node( object ):
assert not self.waiting
printPid = kwargs.get( 'printPid', True )
# Allow sendCmd( [ list ] )
if len( args ) == 1 and type( args[ 0 ] ) is list:
if len( args ) == 1 and isinstance( args[ 0 ], list ):
cmd = args[ 0 ]
# Allow sendCmd( cmd, arg1, arg2... )
elif len( args ) > 0:
@@ -238,29 +270,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 +315,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
@@ -314,10 +352,10 @@ class Node( object ):
[ 'mnexec', '-da', str( self.pid ) ] }
defaults.update( kwargs )
if len( args ) == 1:
if type( args[ 0 ] ) is list:
if isinstance( args[ 0 ], list ):
# popen([cmd, arg1, arg2...])
cmd = args[ 0 ]
elif type( args[ 0 ] ) is str:
elif isinstance( args[ 0 ], basestring ):
# popen("cmd arg1 arg2...")
cmd = args[ 0 ].split()
else:
@@ -326,18 +364,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 +395,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"
@@ -391,7 +432,7 @@ class Node( object ):
"""
if not intf:
return self.defaultIntf()
elif type( intf ) is str:
elif isinstance( intf, basestring):
return self.nameToIntf[ intf ]
else:
return intf
@@ -443,7 +484,7 @@ class Node( object ):
"""Set the default route to go through intf.
intf: Intf or {dev <intfname> via <gw-ip> ...}"""
# Note setParam won't call us if intf is none
if type( intf ) is str and ' ' in intf:
if isinstance( intf, basestring ) and ' ' in intf:
params = intf
else:
params = 'dev %s' % intf
@@ -493,12 +534,14 @@ class Node( object ):
param: arg=value (ignore if value=None)
value may also be list or dict"""
name, value = param.items()[ 0 ]
f = getattr( self, method, None )
if not f or value is None:
if value is None:
return
if type( value ) is list:
f = getattr( self, method, None )
if not f:
return
if isinstance( value, list ):
result = f( *value )
elif type( value ) is dict:
elif isinstance( value, dict ):
result = f( **value )
else:
result = f( value )
@@ -574,12 +617,10 @@ class Node( object ):
"Make sure our class dependencies are available"
pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
class Host( Node ):
"A host is simply a Node"
pass
class CPULimitedHost( Host ):
"CPU limited host"
@@ -601,7 +642,9 @@ class CPULimitedHost( Host ):
# still does better with larger period values.
self.period_us = kwargs.get( 'period_us', 100000 )
self.sched = sched
self.rtprio = 20
if sched == 'rt':
self.checkRtGroupSched()
self.rtprio = 20
def cgroupSet( self, param, value, resource='cpu' ):
"Set a cgroup parameter and return its value"
@@ -631,10 +674,16 @@ 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 our cgroup is not given any cpu time,
# we cannot assign the RR Scheduler.
if self.sched == 'rt':
mncmd += [ '-r', str( self.rtprio ) ]
if int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) ) <= 0:
mncmd += [ '-r', str( self.rtprio ) ]
else:
debug( '*** error: not enough cpu time available for %s.' % self.name,
'Using cfs scheduler for subprocess\n' )
return Host.popen( self, *args, mncmd=mncmd, **kwargs )
def cleanup( self ):
@@ -642,6 +691,19 @@ class CPULimitedHost( Host ):
super( CPULimitedHost, self ).cleanup()
retry( retries=3, delaySecs=1, fn=self.cgroupDel )
_rtGroupSched = False # internal class var: Is CONFIG_RT_GROUP_SCHED set?
@classmethod
def checkRtGroupSched( cls ):
"Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT"
if not cls._rtGroupSched:
release = quietRun( 'uname -r' ).strip('\r\n')
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
exit( 1 )
cls._rtGroupSched = True
def chrt( self ):
"Set RT scheduling priority"
quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
@@ -656,10 +718,10 @@ 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):
def cfsInfo( self, f ):
"Internal method: return parameters for CFS bandwidth"
pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
# CFS uses wall clock time for period and CPU time for quota.
@@ -669,6 +731,9 @@ class CPULimitedHost( Host ):
debug( '(cfsInfo: increasing default period) ' )
quota = 1000
period = int( quota / f / numCores() )
# Reset to unlimited on negative quota
if quota < 0:
quota = -1
return pstr, qstr, period, quota
# BL comment:
@@ -680,35 +745,36 @@ class CPULimitedHost( Host ):
# to CPU seconds per second, essentially assuming that
# all CPUs are the same.
def setCPUFrac( self, f=-1, sched=None):
def setCPUFrac( self, f, sched=None ):
"""Set overall CPU fraction for this host
f: CPU bandwidth limit (fraction)
f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited)
sched: 'rt' or 'cfs'
Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
if not f:
return
Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
and 'rt' requires CONFIG_RT_GROUP_SCHED"""
if not sched:
sched = self.sched
if sched == 'rt':
if not f or f < 0:
raise Exception( 'Please set a positive CPU fraction for sched=rt\n' )
return
pstr, qstr, period, quota = self.rtInfo( f )
elif sched == 'cfs':
pstr, qstr, period, quota = self.cfsInfo( f )
else:
return
if quota < 0:
# Reset to unlimited
quota = -1
# Set cgroup's period and quota
self.cgroupSet( pstr, period )
self.cgroupSet( qstr, quota )
setPeriod = self.cgroupSet( pstr, period )
setQuota = self.cgroupSet( qstr, quota )
if sched == 'rt':
# Set RT priority if necessary
self.chrt()
info( '(%s %d/%dus) ' % ( sched, quota, period ) )
sched = self.chrt()
info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
def setCPUs( self, cores, mems=0 ):
"Specify (real) cores that our cgroup can run on"
if type( cores ) is list:
if not cores:
return
if isinstance( cores, list ):
cores = ','.join( [ str( c ) for c in cores ] )
self.cgroupSet( resource='cpuset', param='cpus',
value=cores )
@@ -721,7 +787,7 @@ class CPULimitedHost( Host ):
errFail( 'cgclassify -g cpuset:/%s %s' % (
self.name, self.pid ) )
def config( self, cpu=None, cores=None, **params ):
def config( self, cpu=-1, cores=None, **params ):
"""cpu: desired overall system CPU fraction
cores: (real) core(s) this host can run on
params: parameters for Node.config()"""
@@ -740,33 +806,6 @@ class CPULimitedHost( Host ):
mountCgroups()
cls.inited = True
class HostWithPrivateDirs( Host ):
"Host with private directories"
def __init__( self, name, *args, **kwargs ):
"privateDirs: list of private directory strings or tuples"
self.name = name
self.privateDirs = kwargs.pop( 'privateDirs', [] )
Host.__init__( self, name, *args, **kwargs )
self.mountPrivateDirs()
def mountPrivateDirs( self ):
"mount private directories"
for directory in self.privateDirs:
if isinstance( directory, tuple ):
# mount given private directory
privateDir = directory[ 1 ] % self.__dict__
mountPoint = directory[ 0 ]
self.cmd( 'mkdir -p %s' % privateDir )
self.cmd( 'mkdir -p %s' % mountPoint )
self.cmd( 'mount --bind %s %s' %
( privateDir, mountPoint ) )
else:
# mount temporary filesystem on directory
self.cmd( 'mkdir -p %s' % directory )
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
# Some important things to note:
#
@@ -888,7 +927,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 ):
@@ -896,7 +937,7 @@ class UserSwitch( Switch ):
over tc queuing disciplines. To resolve the conflict,
we re-create the user switch's configuration, but as a
leaf of the TCIntf-created configuration."""
if type( intf ) is TCIntf:
if isinstance( intf, TCIntf ):
ifspeed = 10000000000 # 10 Gbps
minspeed = ifspeed * 0.001
@@ -924,7 +965,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 +
@@ -941,11 +981,12 @@ class UserSwitch( Switch ):
if not intf.IP():
self.TCReapply( intf )
def stop( self ):
def stop( self, deleteIntfs=True ):
"Stop OpenFlow reference user datapath."
self.cmd( 'kill %ofdatapath' )
self.cmd( 'kill %ofprotocol' )
self.deleteIntfs()
if deleteIntfs:
self.deleteIntfs()
class OVSLegacyKernelSwitch( Switch ):
@@ -975,7 +1016,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 )
@@ -992,11 +1032,12 @@ class OVSLegacyKernelSwitch( Switch ):
' 1>' + ofplog + ' 2>' + ofplog + '&' )
self.execed = False
def stop( self ):
def stop( self, deleteIntfs=True ):
"Terminate kernel datapath."
quietRun( 'ovs-dpctl del-dp ' + self.dp )
self.cmd( 'kill %ovs-openflowd' )
self.deleteIntfs()
if deleteIntfs:
self.deleteIntfs()
class OVSSwitch( Switch ):
@@ -1058,7 +1099,7 @@ class OVSSwitch( Switch ):
"""Unfortunately OVS and Mininet are fighting
over tc queuing disciplines. As a quick hack/
workaround, we clear OVS's and reapply our own."""
if type( intf ) is TCIntf:
if isinstance( intf, TCIntf ):
intf.config( **intf.params )
def attach( self, intf ):
@@ -1093,9 +1134,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 +1141,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:
@@ -1146,20 +1185,33 @@ class OVSSwitch( Switch ):
self.TCReapply( intf )
def stop( self ):
def stop( self, deleteIntfs=True ):
"Terminate OVS switch."
self.cmd( 'ovs-vsctl del-br', self )
if self.datapath == 'user':
self.cmd( 'ip link del', self )
self.deleteIntfs()
if deleteIntfs:
self.deleteIntfs()
OVSKernelSwitch = OVSSwitch
class IVSSwitch(Switch):
class OVSBridge( OVSSwitch ):
"OVSBridge is an OVSSwitch in standalone/bridge mode"
def __init__( self, args, **kwargs ):
kwargs.update( failMode='standalone' )
OVSSwitch.__init__( self, args, **kwargs )
def start( self, controllers ):
OVSSwitch.start( self, controllers=[] )
class IVSSwitch( Switch ):
"""IVS virtual switch"""
def __init__( self, name, verbose=True, **kwargs ):
def __init__( self, name, verbose=False, **kwargs ):
Switch.__init__( self, name, **kwargs )
self.verbose = verbose
@@ -1200,14 +1252,14 @@ 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 ):
def stop( self, deleteIntfs=True ):
"Terminate IVS switch."
self.cmd( 'kill %ivs' )
self.cmd( 'wait' )
self.deleteIntfs()
if deleteIntfs:
self.deleteIntfs()
def attach( self, intf ):
"Connect a data port"
@@ -1240,7 +1292,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 +1320,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 +1381,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."
@@ -1358,8 +1431,19 @@ class RemoteController( Controller ):
warn( "Unable to contact the remote controller"
" at %s:%d\n" % ( self.ip, self.port ) )
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
"find any controller that is available and run it"
for controller in order:
DefaultControllers = [ Controller, OVSController ]
def findController( controllers=DefaultControllers ):
"Return first available controller from list, if any"
for controller in controllers:
if controller.isAvailable():
return controller( name, **kwargs )
return controller
def DefaultController( name, controllers=DefaultControllers, **kwargs ):
"Find a controller that is available and instantiate it"
controller = findController( controllers )
if not controller:
raise Exception( 'Could not find a default OpenFlow controller' )
return controller( name, **kwargs )
+29 -5
View File
@@ -5,7 +5,10 @@ This contains additional Node types which you may find to be useful.
"""
from mininet.node import Node, Switch
from mininet.log import setLogLevel, info
from mininet.log import info, warn
from mininet.moduledeps import pathCheck
import re
class LinuxBridge( Switch ):
"Linux Bridge (with optional spanning tree)"
@@ -31,6 +34,7 @@ class LinuxBridge( Switch ):
return True
def start( self, controllers ):
"Start Linux bridge"
self.cmd( 'ifconfig', self, 'down' )
self.cmd( 'brctl delbr', self )
self.cmd( 'brctl addbr', self )
@@ -43,19 +47,30 @@ class LinuxBridge( Switch ):
self.cmd( 'ifconfig', self, 'up' )
def stop( self ):
"Stop Linux bridge"
self.cmd( 'ifconfig', self, 'down' )
self.cmd( 'brctl delbr', self )
def dpctl( self, *args ):
"Run brctl command"
return self.cmd( 'brctl', *args )
@classmethod
def setup( cls ):
"Make sure our class dependencies are available"
pathCheck( 'brctl', moduleName='bridge-utils' )
class NAT( Node ):
"""NAT: Provides connectivity to external network"""
def __init__( self, name, inetIntf='eth0', subnet='10.0/8', localIntf=None, **params):
def __init__( self, name, inetIntf=None, subnet='10.0/8', localIntf=None, **params):
super( NAT, self ).__init__( name, **params )
"""Start NAT/forwarding between Mininet and external network
inetIntf: interface for internet access
subnet: Mininet subnet (default 10.0/8)="""
self.inetIntf = inetIntf
self.inetIntf = inetIntf if inetIntf else self.getGatewayIntf()
self.subnet = subnet
self.localIntf = localIntf
@@ -82,7 +97,7 @@ class NAT( Node ):
self.cmd( 'iptables -I FORWARD -i', self.localIntf, '-d', self.subnet, '-j DROP' )
self.cmd( 'iptables -A FORWARD -i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
self.cmd( 'iptables -A FORWARD -i', self.inetIntf, '-d', self.subnet, '-j ACCEPT' )
self.cmd( 'iptables -t nat -A POSTROUTING -o ', self.inetIntf, '-j MASQUERADE' )
self.cmd( 'iptables -t nat -A POSTROUTING -o ', self.inetIntf, '-s', self.subnet, '-j MASQUERADE' )
# Instruct the kernel to perform forwarding
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
@@ -94,13 +109,22 @@ class NAT( Node ):
line = '\niface %s inet manual\n' % intf
config = open( cfile ).read()
if ( line ) not in config:
info( '*** Adding "' + line.strip() + '" to ' + cfile )
info( '*** Adding "' + line.strip() + '" to ' + cfile + '\n' )
with open( cfile, 'a' ) as f:
f.write( line )
# Probably need to restart network-manager to be safe -
# hopefully this won't disconnect you
self.cmd( 'service network-manager restart' )
def getGatewayIntf( self ):
routes = self.cmd( 'ip route show' )
match = re.search('default via \S+ dev (\S+)', routes )
if match:
return match.group( 1 )
else:
warn( 'There is no default route set. Using eth0 as gateway interface...\n' )
return 'eth0'
def terminate( self ):
"""Stop NAT/forwarding between Mininet and external network"""
# Flush any currently active rules
+1 -1
View File
@@ -41,7 +41,7 @@ def makeTerm( node, title='Node', term='xterm', display=None ):
title: base title
term: 'xterm' or 'gterm'
returns: two Popen objects, tunnel and terminal"""
title += ': ' + node.name
title = '"%s: %s"' % ( title, node.name )
if not node.inNamespace:
title += ' (root)'
cmds = {
+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 )
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python
"""Package: mininet
Regression tests for switch dpid assignment."""
import unittest
from functools import partial
import re
from mininet.net import Mininet
from mininet.node import Host, Controller
from mininet.node import UserSwitch, OVSSwitch, OVSLegacyKernelSwitch, IVSSwitch
from mininet.topo import Topo
from mininet.log import setLogLevel
from mininet.util import quietRun
class testSwitchDpidAssignmentCommon ( object ):
"""Verify Switch dpid assignment."""
switchClass = None # overridden in subclasses
def testDefaultDpid ( self ):
"""Verify that the default dpid is assigned using a valid provided
canonical switchname if no dpid is passed in switch creation."""
switch = Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 's1' )
self.assertEqual( switch.defaultDpid(), switch.dpid )
def testActualDpidAssignment( self ):
"""Verify that Switch dpid is the actual dpid assigned if dpid is
passed in switch creation."""
switch = Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 'A', dpid = '000000000000ABCD' )
self.assertEqual( switch.dpid, '000000000000ABCD' )
def testDefaultDpidAssignmentFailure( self ):
"""Verify that Default dpid assignment raises an Exception if the
name of the switch does not contin a digit. Also verify the
exception message."""
with self.assertRaises( Exception ) as raises_cm:
Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 'A' )
self.assertEqual(raises_cm.exception.message, 'Unable to derive '
'default datapath ID - please either specify a dpid '
'or use a canonical switch name such as s23.')
def testDefaultDpidLen( self ):
"""Verify that Default dpid length is 16 characters consisting of
16 - len(hex of first string of contiguous digits passed in switch
name) 0's followed by hex of first string of contiguous digits passed
in switch name."""
switch = Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 's123' )
dpid = hex( int(re.findall( r'\d+', switch.name ) [0]) ) [ 2: ]
try:
if issubclass(UserSwitch, self.switchClass):
# Dpid lenght of UserSwitch = 12
self.assertEqual( switch.dpid, '0' * (12 - len(dpid)) + str(dpid) )
else:
self.assertEqual( switch.dpid, '0' * (16 - len(dpid)) + str(dpid) )
except TypeError:
# Switch is OVS User Switch
self.assertEqual( switch.dpid, '0' * (16 - len(dpid)) + str(dpid) )
class testSwitchOVSKernel( testSwitchDpidAssignmentCommon, unittest.TestCase ):
"""Test dpid assignnment of OVS Kernel Switch."""
switchClass = OVSSwitch
class testSwitchOVSUser( testSwitchDpidAssignmentCommon, unittest.TestCase ):
"""Test dpid assignnment of OVS User Switch."""
switchClass = partial(OVSSwitch, datapath = 'user')
@unittest.skipUnless( quietRun( 'which ovs-openflowd' ), 'OVS Legacy Kernel switch is not installed' )
class testSwitchOVSLegacyKernel( testSwitchDpidAssignmentCommon, unittest.TestCase ):
"""Test dpid assignnment of OVS Legacy Kernel Switch."""
switchClass = OVSLegacyKernelSwitch
@unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS switch is not installed' )
class testSwitchIVS( testSwitchDpidAssignmentCommon, unittest.TestCase ):
"""Test dpid assignment of IVS switch."""
switchClass = IVSSwitch
@unittest.skipUnless( quietRun( 'which ofprotocol' ), 'Reference user switch is not installed' )
class testSwitchUserspace( testSwitchDpidAssignmentCommon, unittest.TestCase ):
"""Test dpid assignment of Userspace switch."""
switchClass = UserSwitch
if __name__ == '__main__':
setLogLevel( 'warning' )
unittest.main()
+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()
+193 -107
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
'''@package topo
"""@package topo
Network topology creation.
@@ -9,46 +9,98 @@ This package includes code to represent network topologies.
A Topo object can be a topology database for NOX, can represent a physical
setup for testing, and can even be emulated with the Mininet package.
'''
"""
from mininet.util import irange, natural, naturalSeq
class MultiGraph( object ):
"Utility class to track nodes and edges - replaces networkx.Graph"
"Utility class to track nodes and edges - replaces networkx.MultiGraph"
def __init__( self ):
self.data = {}
self.node = {}
self.edge = {}
def add_node( self, node ):
"Add node to graph"
self.data.setdefault( node, [] )
def add_node( self, node, attr_dict=None, **attrs):
"""Add node to graph
attr_dict: attribute dict (optional)
attrs: more attributes (optional)
warning: updates attr_dict with attrs"""
attr_dict = {} if attr_dict is None else attr_dict
attr_dict.update( attrs )
self.node[ node ] = attr_dict
def add_edge( self, src, dest ):
"Add edge to graph"
src, dest = sorted( ( src, dest ) )
self.add_node( src )
self.add_node( dest )
self.data[ src ].append( dest )
def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ):
"""Add edge to graph
key: optional key
attr_dict: optional attribute dict
attrs: more attributes
warning: udpates attr_dict with attrs"""
attr_dict = {} if attr_dict is None else attr_dict
attr_dict.update( attrs )
self.node.setdefault( src, {} )
self.node.setdefault( dst, {} )
self.edge.setdefault( src, {} )
self.edge.setdefault( dst, {} )
self.edge[ src ].setdefault( dst, {} )
entry = self.edge[ dst ][ src ] = self.edge[ src ][ dst ]
# If no key, pick next ordinal number
if key is None:
keys = [ k for k in entry.keys() if isinstance( k, int ) ]
key = max( [ 0 ] + keys ) + 1
entry[ key ] = attr_dict
return key
def nodes( self ):
"Return list of graph nodes"
return self.data.keys()
def nodes( self, data=False):
"""Return list of graph nodes
data: return list of ( node, attrs)"""
return self.node.items() if data else self.node.keys()
def edges( self ):
def edges_iter( self, data=False, keys=False ):
"Iterator: return graph edges"
for src in self.data.keys():
for dest in self.data[ src ]:
yield ( src, dest )
for src, entry in self.edge.iteritems():
for dst, keys in entry.iteritems():
if src > dst:
# Skip duplicate edges
continue
for k, attrs in keys.iteritems():
if data:
if keys:
yield( src, dst, k, attrs )
else:
yield( src, dst, attrs )
else:
if keys:
yield( src, dst, k )
else:
yield( src, dst )
def edges( self, data=False, keys=False ):
"Return list of graph edges"
return list( self.edges_iter( data=data, keys=keys ) )
def __getitem__( self, node ):
"Return link dict for the given node"
return self.data[node]
"Return link dict for given src node"
return self.edge[ node ]
def __len__( self ):
"Return the number of nodes"
return len( self.node )
def convertTo( self, cls, data=False, keys=False ):
"""Convert to a new object of networkx.MultiGraph-like class cls
data: include node and edge data
keys: include edge keys as well as edge data"""
g = cls()
g.add_nodes_from( self.nodes( data=data ) )
g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) )
return g
class Topo(object):
class Topo( object ):
"Data center network representation for structured multi-trees."
def __init__(self, *args, **params):
def __init__( self, *args, **params ):
"""Topo object.
Optional named parameters:
hinfo: default host options
@@ -56,150 +108,183 @@ class Topo(object):
lopts: default link options
calls build()"""
self.g = MultiGraph()
self.node_info = {}
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
self.hopts = params.pop( 'hopts', {} )
self.sopts = params.pop( 'sopts', {} )
self.lopts = params.pop( 'lopts', {} )
self.ports = {} # ports[src][dst] is port on src that connects to dst
self.ports = {} # ports[src][dst][sport] is port on dst that connects to src
self.build( *args, **params )
def build( self, *args, **params ):
"Override this method to build your topology."
pass
def addNode(self, name, **opts):
def addNode( self, name, **opts ):
"""Add Node to graph.
name: name
opts: node options
returns: node name"""
self.g.add_node(name)
self.node_info[name] = opts
self.g.add_node( name, **opts )
return name
def addHost(self, name, **opts):
def addHost( self, name, **opts ):
"""Convenience method: Add host to graph.
name: host name
opts: host options
returns: host name"""
if not opts and self.hopts:
opts = self.hopts
return self.addNode(name, **opts)
return self.addNode( name, **opts )
def addSwitch(self, name, **opts):
def addSwitch( self, name, **opts ):
"""Convenience method: Add switch to graph.
name: switch name
opts: switch options
returns: switch name"""
if not opts and self.sopts:
opts = self.sopts
result = self.addNode(name, isSwitch=True, **opts)
result = self.addNode( name, isSwitch=True, **opts )
return result
def addLink(self, node1, node2, port1=None, port2=None,
**opts):
def addLink( self, node1, node2, port1=None, port2=None,
key=None, **opts ):
"""node1, node2: nodes to link together
port1, port2: ports (optional)
opts: link options (optional)
returns: link info key"""
if not opts and self.lopts:
opts = self.lopts
self.addPort(node1, node2, port1, port2)
key = tuple(self.sorted([node1, node2]))
self.link_info[key] = opts
self.g.add_edge(*key)
port1, port2 = self.addPort( node1, node2, port1, port2 )
opts = dict( opts )
opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
self.g.add_edge(node1, node2, key, opts )
return key
def addPort(self, src, dst, sport=None, dport=None):
'''Generate port mapping for new edge.
@param src source switch name
@param dst destination switch name
'''
self.ports.setdefault(src, {})
self.ports.setdefault(dst, {})
# New port: number of outlinks + base
src_base = 1 if self.isSwitch(src) else 0
dst_base = 1 if self.isSwitch(dst) else 0
if sport is None:
sport = len(self.ports[src]) + src_base
if dport is None:
dport = len(self.ports[dst]) + dst_base
self.ports[src][dst] = sport
self.ports[dst][src] = dport
def nodes(self, sort=True):
def nodes( self, sort=True ):
"Return nodes in graph"
if sort:
return self.sorted( self.g.nodes() )
else:
return self.g.nodes()
def isSwitch(self, n):
'''Returns true if node is a switch.'''
info = self.node_info[n]
return info and info.get('isSwitch', False)
def isSwitch( self, n ):
"Returns true if node is a switch."
return self.g.node[ n ].get( 'isSwitch', False )
def switches(self, sort=True):
'''Return switches.
sort: sort switches alphabetically
@return dpids list of dpids
'''
return [n for n in self.nodes(sort) if self.isSwitch(n)]
def switches( self, sort=True ):
"""Return switches.
sort: sort switches alphabetically
returns: dpids list of dpids"""
return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
def hosts(self, sort=True):
'''Return hosts.
sort: sort hosts alphabetically
@return dpids list of dpids
'''
return [n for n in self.nodes(sort) if not self.isSwitch(n)]
def hosts( self, sort=True ):
"""Return hosts.
sort: sort hosts alphabetically
returns: list of hosts"""
return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
def links(self, sort=True):
'''Return links.
sort: sort links alphabetically
@return links list of name pairs
'''
if not sort:
return self.g.edges()
else:
links = [tuple(self.sorted(e)) for e in self.g.edges()]
return sorted( links, key=naturalSeq )
def iterLinks( self, withKeys=False, withInfo=False ):
"""Return links (iterator)
withKeys: return link keys
withInfo: return link info
returns: list of ( src, dst [,key, info ] )"""
for src, dst, key, info in self.g.edges_iter( data=True, keys=True ):
node1, node2 = info[ 'node1' ], info[ 'node2' ]
if withKeys:
if withInfo:
yield( node1, node2, key, info )
else:
yield( node1, node2, key )
else:
if withInfo:
yield( node1, node2, info )
else:
yield( node1, node2 )
def port(self, src, dst):
'''Get port number.
def links( self, sort=False, withKeys=False, withInfo=False ):
"""Return links
sort: sort links alphabetically, preserving (src, dst) order
withKeys: return link keys
withInfo: return link info
returns: list of ( src, dst [,key, info ] )"""
links = list( self.iterLinks( withKeys, withInfo ) )
if not sorted:
return links
# Ignore info when sorting
tupleSize = 3 if withKeys else 2
return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) )
@param src source switch name
@param dst destination switch name
@return tuple (src_port, dst_port):
src_port: port on source switch leading to the destination switch
dst_port: port on destination switch leading to the source switch
'''
if src in self.ports and dst in self.ports[src]:
assert dst in self.ports and src in self.ports[dst]
return self.ports[src][dst], self.ports[dst][src]
# This legacy port management mechanism is clunky and will probably
# be removed at some point.
def linkInfo( self, src, dst ):
"Return link metadata"
src, dst = self.sorted([src, dst])
return self.link_info[(src, dst)]
def addPort( self, src, dst, sport=None, dport=None ):
"""Generate port mapping for new edge.
src: source switch name
dst: destination switch name"""
# Initialize if necessary
ports = self.ports
ports.setdefault( src, {} )
ports.setdefault( dst, {} )
# New port: number of outlinks + base
if sport is None:
src_base = 1 if self.isSwitch( src ) else 0
sport = len( ports[ src ] ) + src_base
if dport is None:
dst_base = 1 if self.isSwitch( dst ) else 0
dport = len( ports[ dst ] ) + dst_base
ports[ src ][ sport ] = ( dst, dport )
ports[ dst ][ dport ] = ( src, sport )
return sport, dport
def setlinkInfo( self, src, dst, info ):
"Set link metadata"
src, dst = self.sorted([src, dst])
self.link_info[(src, dst)] = info
def port( self, src, dst ):
"""Get port numbers.
src: source switch name
dst: destination switch name
sport: optional source port (otherwise use lowest src port)
returns: tuple (sport, dport), where
sport = port on source switch leading to the destination switch
dport = port on destination switch leading to the source switch
Note that you can also look up ports using linkInfo()"""
# A bit ugly and slow vs. single-link implementation ;-(
ports = [ ( sport, entry[ 1 ] )
for sport, entry in self.ports[ src ].items()
if entry[ 0 ] == dst ]
return ports if len( ports ) != 1 else ports[ 0 ]
def _linkEntry( self, src, dst, key=None ):
"Helper function: return link entry and key"
entry = self.g[ src ][ dst ]
if key is None:
key = min( entry )
return entry, key
def linkInfo( self, src, dst, key=None ):
"Return link metadata dict"
entry, key = self._linkEntry( src, dst, key )
return entry[ key ]
def setlinkInfo( self, src, dst, info, key=None ):
"Set link metadata dict"
entry, key = self._linkEntry( src, dst, key )
entry[ key ] = info
def nodeInfo( self, name ):
"Return metadata (dict) for node"
info = self.node_info[ name ]
return info if info is not None else {}
return self.g.node[ name ]
def setNodeInfo( self, name, info ):
"Set metadata (dict) for node"
self.node_info[ name ] = info
self.g.node[ name ] = info
def convertTo( self, cls, data=True, keys=True ):
"""Convert to a new object of networkx.MultiGraph-like class cls
data: include node and edge data (default True)
keys: include edge keys as well as edge data (default True)"""
return self.g.convertTo( cls, data=data, keys=keys )
@staticmethod
def sorted( items ):
"Items sorted in natural (i.e. alphabetical) order"
return sorted(items, key=natural)
return sorted( items, key=natural )
class SingleSwitchTopo( Topo ):
@@ -228,6 +313,7 @@ class SingleSwitchReversedTopo( Topo ):
self.addLink( host, switch,
port1=0, port2=( k - h + 1 ) )
class LinearTopo( Topo ):
"Linear topology of k switches, with n hosts per switch."
+64 -25
View File
@@ -10,6 +10,7 @@ import re
from fcntl import fcntl, F_GETFL, F_SETFL
from os import O_NONBLOCK
import os
from functools import partial
# Command execution support
@@ -61,12 +62,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 +69,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 +148,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 +184,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 +237,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 ):
@@ -373,7 +388,7 @@ def sysctlTestAndSet( name, limit ):
#read limit
with open( name, 'r' ) as readFile:
oldLimit = readFile.readline()
if type( limit ) is int:
if isinstance( limit, int ):
#compare integer limits before overriding
if int( oldLimit ) < limit:
with open( name, 'w' ) as writeFile:
@@ -433,7 +448,7 @@ def natural( text ):
def num( s ):
"Convert text segment to int if necessary"
return int( s ) if s.isdigit() else s
return [ num( s ) for s in re.split( r'(\d+)', text ) ]
return [ num( s ) for s in re.split( r'(\d+)', str( text ) ) ]
def naturalSeq( t ):
"Natural sort key function for sequences"
@@ -526,3 +541,27 @@ 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 isinstance( server, basestring ) 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
+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,8 @@ int main(int argc, char *argv[])
char path[PATH_MAX];
int nsid;
int pid;
char *cwd = get_current_dir_name();
static struct sched_param sp;
while ((c = getopt(argc, argv, "+cdnpa:g:r:vh")) != -1)
switch(c) {
@@ -156,13 +158,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 */
/* Plan B: chroot/chdir into pid's root file system */
sprintf(path, "/proc/%d/root", pid);
if (chroot(path) < 0) {
perror(path);
return 1;
}
}
/* chdir to correct working directory */
if (chdir(cwd) != 0) {
perror(cwd);
return 1;
}
break;
case 'g':
/* Attach to cgroup */
@@ -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
+110 -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:-""}
@@ -133,6 +140,9 @@ function mn_deps {
function mn_dev {
echo "Installing Mininet developer dependencies"
$install doxygen doxypy texlive-fonts-recommended
if ! $install doxygen-latex; then
echo "doxygen-latex not needed"
fi
}
# The following will cause a full OF install, covering:
@@ -200,65 +210,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 +258,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 +329,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 +386,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 +446,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 +585,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 +641,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 +687,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 +714,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 +727,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 +754,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;;
+2 -2
View File
@@ -9,7 +9,7 @@ else
host=$1
fi
pid=`ps ax | grep "mininet:$host$" | grep bash | awk '{print $1};'`
pid=`ps ax | grep "mininet:$host$" | grep bash | grep -v mnexec | awk '{print $1};'`
if echo $pid | grep -q ' '; then
echo "Error: found multiple mininet:$host processes"
@@ -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
+1 -1
View File
@@ -8,7 +8,7 @@ version = 'Mininet ' + co( 'PYTHONPATH=. bin/mn --version', shell=True )
version = version.strip()
# Find all Mininet path references
lines = co( "grep -or 'Mininet \w\+\.\w\+\.\w\+[+]*' *", shell=True )
lines = co( "egrep -or 'Mininet [0-9\.]+\w*' *", shell=True )
error = False
+95 -33
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'
@@ -161,8 +162,8 @@ def srun( cmd, **kwargs ):
def depend():
"Install package dependencies"
log( '* Installing package dependencies' )
run( 'sudo apt-get -y update' )
run( 'sudo apt-get install -y'
run( 'sudo apt-get -qy update' )
run( 'sudo apt-get -qy install'
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
' e2fsprogs dnsmasq curl'
' python-setuptools mtools zip' )
@@ -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
@@ -329,9 +333,11 @@ skipx
# Tell the Ubuntu/Debian installer to stop asking stupid questions
PreseedText = """
d-i mirror/country string manual
d-i mirror/http/hostname string mirrors.kernel.org
PreseedText = ( """
"""
#d-i mirror/country string manual
#d-i mirror/http/hostname string mirrors.kernel.org
"""
d-i mirror/http/directory string /ubuntu
d-i mirror/http/proxy string
d-i partman/confirm_write_new_label boolean true
@@ -341,7 +347,7 @@ d-i partman/confirm_nooverwrite boolean true
d-i user-setup/allow-password-weak boolean true
d-i finish-install/reboot_in_progress note
d-i debian-installer/exit/poweroff boolean true
"""
""" )
def makeKickstartFloppy():
"Create and return kickstart floppy, kickstart, preseed"
@@ -438,7 +444,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 +467,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,9 +489,24 @@ def login( vm, user='mininet', password='mininet' ):
log( '* Waiting for login...' )
def removeNtpd( vm, prompt=Prompt, ntpPackage='ntp' ):
"Remove ntpd and set clock immediately"
log( '* Removing ntpd' )
vm.sendline( 'sudo -n apt-get -qy remove ' + ntpPackage )
vm.expect( prompt )
# Try to make sure that it isn't still running
vm.sendline( 'sudo -n pkill ntpd' )
vm.expect( prompt )
log( '* Getting seconds since epoch from this server' )
# Note r'date +%s' specifies a format for 'date', not python!
seconds = int( run( r'date +%s' ) )
log( '* Setting VM clock' )
vm.sendline( 'sudo -n date -s @%d' % seconds )
def sanityTest( vm ):
"Run Mininet sanity test (pingall) in vm"
vm.sendline( 'sudo mn --test pingall' )
vm.sendline( 'sudo -n mn --test pingall' )
if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=45 ) == 0:
log( '* Sanity check OK' )
else:
@@ -490,9 +518,9 @@ def sanityTest( vm ):
def coreTest( vm, prompt=Prompt ):
"Run core tests (make test) in VM"
log( '* Making sure cgroups are mounted' )
vm.sendline( 'sudo service cgroup-lite restart' )
vm.sendline( 'sudo -n service cgroup-lite restart' )
vm.expect( prompt )
vm.sendline( 'sudo cgroups-mount' )
vm.sendline( 'sudo -n cgroups-mount' )
vm.expect( prompt )
log( '* Running make test' )
vm.sendline( 'cd ~/mininet; sudo make test' )
@@ -501,36 +529,56 @@ 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' )
log( '* Test', test, 'output:' )
log( vm.before )
def noneTest( vm ):
def installPexpect( vm, prompt=Prompt ):
"install pexpect"
vm.sendline( 'sudo -n apt-get -qy install python-pexpect' )
vm.expect( prompt )
def noneTest( vm, prompt=Prompt ):
"This test does nothing"
installPexpect( vm, prompt )
vm.sendline( 'echo' )
def examplesquickTest( vm, prompt=Prompt ):
"Quick test of mininet examples"
vm.sendline( 'sudo apt-get install python-pexpect' )
vm.expect( prompt )
vm.sendline( 'sudo python ~/mininet/examples/test/runner.py -v -quick' )
installPexpect( vm, prompt )
vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v -quick' )
def examplesfullTest( vm, prompt=Prompt ):
"Full (slow) test of mininet examples"
vm.sendline( 'sudo apt-get install python-pexpect' )
vm.expect( prompt )
vm.sendline( 'sudo python ~/mininet/examples/test/runner.py -v' )
installPexpect( vm, prompt )
vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v' )
def walkthroughTest( vm, prompt=Prompt ):
"Test mininet walkthrough"
vm.sendline( 'sudo apt-get install python-pexpect' )
vm.expect( prompt )
vm.sendline( 'sudo python ~/mininet/mininet/test/test_walkthrough.py -v' )
installPexpect( vm, prompt )
vm.sendline( 'sudo -n 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 ):
@@ -542,7 +590,7 @@ def checkOutBranch( vm, branch, prompt=Prompt ):
vm.sendline( 'cd ~/mininet; git fetch --all; git checkout '
+ branch + '; git pull --rebase origin ' + branch )
vm.expect( prompt )
vm.sendline( 'sudo make install' )
vm.sendline( 'sudo -n make install' )
def interact( vm, tests, pre='', post='', prompt=Prompt ):
@@ -561,7 +609,7 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ):
'install-mininet-vm.sh' % branch )
vm.expect( prompt )
log( '* Running VM install script' )
installcmd = 'bash install-mininet-vm.sh'
installcmd = 'bash -v install-mininet-vm.sh'
if Branch:
installcmd += ' ' + Branch
vm.sendline( installcmd )
@@ -725,9 +773,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
@@ -789,8 +840,14 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
os.chdir( '..' )
def runTests( vm, tests=None, pre='', post='', prompt=Prompt ):
def runTests( vm, tests=None, pre='', post='', prompt=Prompt, uninstallNtpd=False ):
"Run tests (list) in vm (pexpect object)"
# We disable ntpd and set the time so that ntpd won't be
# messing with the time during tests. Set to true for a COW
# disk and False for a non-COW disk.
if uninstallNtpd:
removeNtpd( vm )
vm.expect( prompt )
if Branch:
checkOutBranch( vm, branch=Branch )
vm.expect( prompt )
@@ -857,7 +914,7 @@ def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None,
if runFunction:
runFunction( vm, **runArgs )
log( '* Shutting down' )
vm.sendline( 'sudo shutdown -h now ' )
vm.sendline( 'sudo -n shutdown -h now ' )
log( '* Waiting for shutdown' )
vm.wait()
if outputFile:
@@ -892,7 +949,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 +987,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 +1006,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:
@@ -962,7 +1023,8 @@ def parseArgs():
exit( 1 )
for image in args.image:
bootAndRun( image, runFunction=runTests, tests=args.test, pre=args.run,
post=args.post, memory=args.memory, outputFile=args.out )
post=args.post, memory=args.memory, outputFile=args.out,
uninstallNtpd=True )
if not ( args.depend or args.list or args.clean or args.flavor
or args.image ):
parser.print_help()
+8 -7
View File
@@ -6,20 +6,21 @@
#
# optional argument: Mininet branch to install
set -e
echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers > /dev/null
echo "$(whoami) ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers > /dev/null
sudo sed -i -e 's/Default/#Default/' /etc/sudoers
echo mininet-vm | sudo tee /etc/hostname > /dev/null
sudo sed -i -e 's/ubuntu/mininet-vm/g' /etc/hosts
sudo hostname `cat /etc/hostname`
sudo sed -i -e 's/quiet splash/text/' /etc/default/grub
sudo update-grub
# 12.10 and earlier
sudo sed -i -e 's/us.archive.ubuntu.com/mirrors.kernel.org/' \
/etc/apt/sources.list
# 13.04 and later
sudo sed -i -e 's/\/archive.ubuntu.com/\/mirrors.kernel.org/' \
/etc/apt/sources.list
# Update from official archive
sudo apt-get update
# 12.10 and earlier
#sudo sed -i -e 's/us.archive.ubuntu.com/mirrors.kernel.org/' \
# /etc/apt/sources.list
# 13.04 and later
#sudo sed -i -e 's/\/archive.ubuntu.com/\/mirrors.kernel.org/' \
# /etc/apt/sources.list
# Clean up vmware easy install junk if present
if [ -e /etc/issue.backup ]; then
sudo mv /etc/issue.backup /etc/issue