Compare commits

...
Author SHA1 Message Date
Bob Lantz f649fa2456 Controller: remove default '-v' flag!! 2015-08-17 09:41:50 -07:00
Bob Lantz 9da9ebe085 Automatically map remote classes, and add RemoteUserSwitch 2015-08-17 07:56:56 -07:00
Bob Lantz a17ec6dd9f Fix color list for larger numbers of servers 2015-08-17 07:46:13 -07:00
Bob Lantz 65cc3eab1a Change findServerIP to return last IP... 2015-08-14 14:01:28 -07:00
Bob Lantz bc8875c2d6 Add simple host test for the moment 2015-08-14 13:57:40 -07:00
Bob Lantz 75cbb67203 Get rid of whitespace and junk 2015-08-14 13:53:46 -07:00
Bob Lantz c41d88fb8a Import paramiko on demand so that we don't fail if it's missing 2015-08-10 09:53:06 -07:00
Bob Lantz dfa400d7fd Raise connection multiplexing to 100 2015-08-10 07:13:22 -07:00
Bob Lantz 49a6480e85 Change switch link bringup 2015-08-10 05:18:29 -07:00
Bob Lantz e9c207ea7b Add stopConnections(), and clean up by IP address 2015-08-10 04:51:29 -07:00
Bob Lantz 122141c6e4 Change to 12 nodes by default to match demo config. ;-) 2015-08-10 04:34:47 -07:00
Bob Lantz d826d03643 Avoid using loopback address; also remove '&' from ssh cmd
For the moment, we are deprecating using localhost. We may allow
it again in the future.
2015-08-10 04:33:37 -07:00
Bob Lantz 0efa65ef08 Various changes: remove prints, sendInt, change IP range 2015-08-10 01:47:54 -07:00
Bob Lantz 8c41029817 Change IP range to match testbed 2015-08-10 01:45:10 -07:00
Bob Lantz 994e8efe3e Enable CLI to work with RemoteSwitch
We may wish to change the way this works...
2015-08-10 01:00:33 -07:00
Bob Lantz 5c882419f7 Put batchShutdown loop in correct place 2015-08-10 00:33:17 -07:00
Bob Lantz 50faa927d0 Fix undefined SIGKILL 2015-08-10 00:13:41 -07:00
Bob Lantz 9535291882 Parallel link startup 2015-08-09 23:24:28 -07:00
Bob Lantz 9f7f9b8594 peristentSetup: don't add duplicate entries into config files 2015-08-09 19:05:47 -07:00
Bob Lantz abd39c9dd1 Fix hostname lookup check 2015-08-11 16:24:03 -07:00
Bob Lantz d6a2909f8c Import cluster CLI 2015-08-11 15:15:43 -07:00
Bob Lantz 4e7bf368fa Remove debug prints 2015-08-11 15:08:38 -07:00
Bob Lantz 8dfc3bf091 Don't crash if there is no ssh agent connection 2015-08-11 15:06:58 -07:00
Bob Lantz 24769aba40 Change to 10.x in case host names change 2015-08-10 13:30:54 -07:00
Bob Lantz 97a7fc9d0a Add --link mn as well 2015-08-10 13:30:34 -07:00
Bob Lantz e088c85a04 Add 'mn' host so we can use --custom fakecluster.py --host mn 2015-08-10 12:49:59 -07:00
Bob Lantz 9cc2255569 New cluster.py; save old version as well for now for comparison 2015-08-09 00:43:59 -07:00
Bob Lantz 2d5627677a Use send_signal to kill (possibly remote) xterms 2015-08-08 21:26:13 -07:00
Bob Lantz 073021973b Add command line argument for cluster size 2015-08-08 20:28:34 -07:00
Bob Lantz bad5adc773 Change sendInt() to kill the process group
Note this will work with pid namespaces.
2015-08-06 05:02:34 -07:00
Bob Lantz 03a8b01273 Set self.ns correctly 2015-06-29 12:57:55 -07:00
Bob Lantz 08d3c86c9a Add a blank line 2015-06-25 15:44:40 -07:00
Bob Lantz e332a7f49d More changes to use ns param
You can either specify the old inNamespace parameter, in which case
you'll get a net + mnt namespace, or the new ns parameter, which
allows you to pick namespaces explicitly.

inNamespace is set to true if you're in a network namespace.
2015-06-25 15:44:40 -07:00
Bob Lantz a0a35c1093 Fix to use self.ns 2015-06-25 15:44:40 -07:00
Bob Lantz 8d6043df94 Change to ns=['net','mnt'] and deprecate inNamespace 2015-06-25 15:44:40 -07:00
Bob Lantz f190c7d8e3 Check for /var/log and /var/run in overlay dirs 2015-06-25 15:44:40 -07:00
Bob Lantz a8a2799174 Move Server to nodelib and add --host server option 2015-06-25 15:44:40 -07:00
Bob Lantz b3efefc561 8 servers + LinuxBridge 2015-06-25 15:44:40 -07:00
Bob Lantz d32ab759a5 debug -> info 2015-06-25 15:44:40 -07:00
Bob Lantz 1819730925 Add /etc/openvswitch to private dirs 2015-06-25 15:44:40 -07:00
Bob Lantz 47fdafe93e Specify runCmd, and update controller IP correctly 2015-06-25 15:44:39 -07:00
Bob Lantz bd0f710078 Fix ^c on cluster edition; still broken for pid ns 2015-06-25 15:44:39 -07:00
Bob Lantz 31b29cc8bc still working on this... 2015-06-25 15:44:39 -07:00
Bob Lantz 6b06431e86 Revert a bit to node.py from master 2015-06-25 15:44:39 -07:00
Bob Lantz 721c6a3ace use runCmd 2015-06-25 15:44:39 -07:00
Bob Lantz 615f63f8f7 Allow runCmd 2015-06-25 15:44:39 -07:00
Bob Lantz 9b45382bdc Remove debug print 2015-06-25 15:44:39 -07:00
Bob Lantz 240278bb22 Create new pgrp so that we can use killpg 2015-06-25 15:44:39 -07:00
Bob Lantz 872be9a796 Remove debug print 2015-06-25 15:44:38 -07:00
Bob Lantz 587997dc20 Respect runCmd in makeIntfPair() 2015-06-25 15:44:38 -07:00
Bob Lantz 1268bed091 Still working on it. 2015-06-25 15:44:38 -07:00
Bob Lantz cfb2518ecf Change to sudo -E to preserve ssh forwarding 2015-06-25 15:44:38 -07:00
Bob Lantz 19c03c3842 Add API and mn support for --host proc,ns=[net,mnt,pid] 2015-06-25 15:44:38 -07:00
Bob Lantz 55ceed2ab0 First attempt fixing x11 to work with pid namespace 2015-06-25 15:44:38 -07:00
Bob Lantz 7245babab6 Fix pid namespace by making sure we fork on attach 2015-06-25 15:44:38 -07:00
Bob Lantz 9d578e5550 First crack at a pid namespace-capable mnexec
The detach is a bit ugly because we fork twice, which shouldn't be
necessary!!
2015-06-25 15:44:38 -07:00
Bob Lantz f3a82b9b88 Experiments with pid ns 2015-06-25 15:44:38 -07:00
Bob Lantz c65997ac7d Avoid expanding a string into a list of chars 2015-06-25 15:44:38 -07:00
Bob Lantz ab65aabc32 Use ['/etc/foo'] vs. ('/etc/foo',) to avoid deleted comma problem 2015-06-25 15:44:38 -07:00
Bob Lantz 0183e29765 Use motd instead of ssh banner message 2015-06-25 15:44:37 -07:00
Bob Lantz f44559651f Clarify comment 2015-06-25 15:44:37 -07:00
Bob Lantz cac9dd0090 Add docstring for terminate() 2015-06-25 15:44:37 -07:00
Bob Lantz 5fd74d5d8f Document config params 2015-06-25 15:44:37 -07:00
Bob Lantz 16a2f1bfc3 add private btmp 2015-06-25 15:44:37 -07:00
Bob Lantz 9fe29fafdf Comment changes 2015-06-25 15:44:37 -07:00
Bob Lantz c13170add4 Add documentation for overlayDirs 2015-06-25 15:44:37 -07:00
Bob Lantz 844fe4bd41 Change to super() call in __init__ 2015-06-25 15:44:37 -07:00
Bob Lantz 1bb3784bf4 Simplify startup/shutdown, and a few comments 2015-06-25 15:44:37 -07:00
Bob Lantz dfdbea029c Private utmp and wtmp for w and last 2015-06-25 15:44:37 -07:00
Bob Lantz 2727444ede Minor changes 2015-06-25 15:44:37 -07:00
Bob Lantz 9537107063 Clarify parameter processing for overlayDirs 2015-06-25 15:44:37 -07:00
Bob Lantz be9cddbcff Fake cluster example 2015-06-25 15:44:36 -07:00
Bob Lantz 2cb17590c2 Remove blank line
I'm not sure whether we really need to turn off ip_forward before
flushing iptables, but it might possibly be useful to avoid certain
unexpected behavior.
2015-06-25 15:42:22 -07:00
Bob Lantz f7601da006 Change quoting for '!' 2015-06-25 15:38:50 -07:00
lantz 65a0e5f3b6 Merge pull request #528 from pichuang/cluster
Fixed `whoami` output problem in cluster.py
2015-06-25 13:36:21 -07:00
Roan Huang d5d66f1276 Fixed whoami output problem in cluster.py
Remove the last character when use `whoami`
2015-06-25 05:37:20 +00:00
Bob Lantz 9ed14fa0f4 Remove explicit NAT code and use built-in NAT functionality.
This API isn't great - we should try to improve it in the future.
2015-06-23 15:17:58 -07:00
lantz f24ebc438e Merge pull request #530 from jonohart/master
Enable NAT to use all interfaces.
2015-06-23 15:08:47 -07:00
Jonathan Hart 90d50dcbc4 Enable NAT to use all interfaces.
Also put the net.ipv4.ip_forward option back to what it was before on exit,
rather than always setting back to 0.
2015-06-23 13:57:19 -07:00
Bob Lantz 5f68be2273 Remove debug print 2015-06-11 00:16:55 -07:00
Bob Lantz 68ae67dc58 Support custom links in custom file 2015-06-10 20:35:53 -07:00
Bob Lantz c4a85ab1d1 Add flush option to disable flushing iptables 2015-06-10 20:09:29 -07:00
Bob Lantz ef3c885630 Add help regarding --nat [option=val...] 2015-06-10 20:08:44 -07:00
Bob Lantz bb35d04102 Fix TorusTopo for UserSwitch
UserSwitch requires a 12-digit dpid.
2015-06-07 22:10:18 -07:00
Bob Lantz ba05fd363b Warn if bridge netfilter (firewall) is enabled
Newer linux kernels enable filtering on the linux bridge;
this can prevent it from working in mininet!
2015-06-05 16:10:21 -07:00
Brian O'Connor e988b0f660 Merge pull request #522 from mininet/devel/torus
Adding number of hosts per switch option to torus topo
2015-06-05 01:45:01 -07:00
Brian O'Connor b27cce08af Adding number of hosts per switch option to torus topo 2015-05-27 23:45:19 -07:00
Bob Lantz a38896c254 Avoid expanding a string into a list of chars 2015-05-16 08:24:15 -07:00
Bob Lantz 63ae13fcf9 Fix #520 2015-05-12 23:50:37 -07:00
Bob Lantz c5f6d0ff17 Restore use of self.intf (if present) in addLink
fixes #515
2015-05-02 13:16:48 -07:00
Bob Lantz ab8c4e9198 2.2.1rc1 -> 2.2.1 2015-04-16 16:05:30 -07:00
Bob Lantz 8daa4193c7 Fix typo 2015-04-15 16:21:55 -07:00
Bob Lantz 5c895eaad8 Added note on Raspberry Pi ;-) 2015-04-14 14:40:27 -07:00
Bob Lantz 435d0d68b3 -> 2.2.1rc1 2015-04-14 14:33:08 -07:00
Bob Lantz 66e9845f05 Add babisk 2015-04-13 17:58:47 -07:00
Bob Lantz fe3340077c Add dmahler and pichuang 2015-04-13 17:56:11 -07:00
lantz c589660e17 Merge pull request #494 from pichuang/master
Modify directory path in clustersetup.sh
2015-04-13 17:52:06 -07:00
Bob Lantz d9117c77e1 Merge branch 'dmahler-master' 2015-04-13 17:35:52 -07:00
Bob Lantz 6cb68f26c9 Simply use class names as option help
The justification for this is that 1) the options are
already documented in the API documentation and 2) the
class names are fairly self-explanatory, so adding short
descriptions doesn't add much.

In the future, however, we could add a short description
by simply using the first line or sentence of the docstring;
note that docstrings would have to be canonicalized, which
they aren't now.
2015-04-13 17:30:55 -07:00
David Mahler c60764c34f Update to mn --help to provide details on options 2015-04-11 06:20:42 +00:00
lantz ba723826b7 Merge pull request #489 from BabisK/master
New netbee lib, fixes need for very old bison
2015-04-10 17:06:05 -07:00
Bob Lantz 0165d7bdff Implement Switch.stop(), which doesn't call terminate
When I changed the switch implementations to call super().stop(),
I neglected to include a Switch.stop() method which didn't call
terminate(). This broke switch s1 stop and s1.stop().

Fixes #497
2015-04-09 23:19:40 -07:00
Bob Lantz 1df1f9a1c5 Fix sorting & remove duplicate 2015-04-09 17:56:37 -07:00
Roan Huang 2c916acc06 Modify cluster help information 2015-04-07 03:37:53 +00:00
Roan Huang 0a810b226c Modify SSHDIR and USERDIR path 2015-04-06 11:19:26 +00:00
Roan Huang 57c8f5934f Fixed code indentation 2015-04-06 11:12:27 +00:00
Bob Lantz 125e669723 Support multiple --controller arguments 2015-03-30 20:40:07 -07:00
Bob Lantz a2486a6d66 Allow node.setIP() to pass extra arguments to intf.setIP() 2015-03-30 19:43:51 -07:00
Bob Lantz 15f2898f26 Add a space after ipv6.disable=1 so as not to break text boot 2015-03-30 16:26:05 -07:00
Bob Lantz 1c4adde1b6 Remove redundant processing for '/' 2015-03-24 15:22:55 -07:00
Bob Lantz 4d22991245 Allow install.sh -V on debian 2015-03-23 18:48:30 -07:00
Bob Lantz 90ea6c6bd1 Usually kernel install is unnecessary, so don't quit if it fails 2015-03-23 17:42:51 -07:00
Bob Lantz 269cecd3ee openvswitch-datapath-dkms is no longer needed for 14.04+ 2015-03-23 17:15:59 -07:00
Bob Lantz c2be20f09e Rename node.RYU to node.Ryu
Perhaps it should be RyuController, but just Ryu for now.
RYU made no sense because it's not an acronym.
2015-03-18 16:00:28 -07:00
Bob Lantz df56fa2716 2.2.1d1 -> 2.2.1d2 2015-03-18 15:47:53 -07:00
Bob Lantz f49e2539b7 Minor formatting, pass code check 2015-03-18 15:32:21 -07:00
Bob Lantz 4da7b3b7f0 Fix exception for unknown class 2015-03-18 15:31:40 -07:00
Bob Lantz e5a5cd0070 customConstructor -> customClass 2015-03-18 15:18:02 -07:00
Bob Lantz c5779deeb6 Pass code check 2015-03-18 15:17:33 -07:00
Bob Lantz 79c944aef4 Remove obsolete OVSLegacyKernelSwitch 2015-03-18 15:12:09 -07:00
Bob Lantz a23c6a2871 Fix undefined constructors in customClass() 2015-03-18 15:10:20 -07:00
Babis Kaidos 7acc693277 New netbee library is installed for Ubuntu 14.04 and newer. For older releases or for other OSs the old netbee is installed 2015-03-18 17:59:29 +02:00
Babis Kaidos 889698f7bd New netbee lib, fixes need for very old bison 2015-03-16 15:28:20 +02:00
Bob Lantz f6f6d9282b CustomConstructor -> CustomClass, which calls specialClass
specialClass is an analog of functools.partial but for classes.
We can now use it instead of partial() in mn, so that Mininet
can introspect on the actual base class.

Fixes #488
2015-03-13 21:17:43 -07:00
lantz 5224884e5e Merge pull request #473 from joerango/master
Support for running arbitrary command when starting terminal
2015-03-13 15:09:11 -07:00
lantz f063b023bd Merge pull request #478 from rlane/fix-readline-history
cli: don't read/write readline history more than once
2015-03-02 14:22:19 -08:00
Rich Lane a1edb167b1 Merge pull request #1 from lantz/devel/fix-readline-history
Move cmdloop() wrapper into a new run() method
2015-03-02 14:20:11 -08:00
Bob Lantz 613fac4bab Move cmdloop() wrapper into a new run() method
I also added another try/catch block so that interrupting
the 'Interrupt' message should no longer occur.
2015-02-20 16:46:18 -08:00
Rich Lane e0cd11ab21 cli: don't read/write readline history more than once
Previously, when creating multiple CLI objects, each one would append the
~/.mininet_history file to readline's internal list. When writing the file back
it would be duplicated for each CLI object created. So, over a few mininet runs
the history file would grow exponentially.

This change moves the readline setup code out of the CLI constructor into a
class method and adds a flag to prevent it from being executed more than once.
2015-02-20 10:47:43 -08:00
Joseph Beshay 5b818ad75c Support for running arbitrary command when starting terminal (other than bash) 2015-02-09 12:55:25 -06:00
Bob Lantz 17ba6a7c4d Add ip:port shorthand to Controller
Fixes #475
2015-02-04 11:16:42 -08:00
Bob Lantz f77a8b9e17 Fix customConstructor to work correctly with newargs 2015-02-04 10:56:32 -08:00
Bob Lantz 3dd8c2cda6 A controller should delete its interfaces if necessary
This was causing controlnet.py to not clean up its interfaces, since
those interfaces were linked from the controller to the switch
rather than vice-versa!!

deleteIntfs already will only delete interfaces whose names match
our name, which should usually prevent us from accidentally deleting
a hardware interface from the root namespace!
2015-02-04 10:30:26 -08:00
Bob Lantz 48a8ed857e 2.2.0+ -> 2.2.1d1 2015-02-04 05:27:51 -08:00
Bob Lantz 74c3511d5c pass code check 2015-02-04 05:27:14 -08:00
Bob Lantz 5ac113cfaa Batch link delete commands (and minor edits) 2015-02-04 05:21:15 -08:00
Bob Lantz cd02954c91 pass code check 2015-02-04 04:45:46 -08:00
Bob Lantz 93be1d0401 Fix OVS protocols option 2015-02-04 04:44:55 -08:00
Bob Lantz 6a38811f1a Clarify makeIntfPair behavior and pass code check 2015-02-04 04:44:14 -08:00
Bob Lantz 4ac45a3967 Fix super() arg in DataController 2015-02-04 04:43:23 -08:00
Bob Lantz d7e01bb821 Pass code check 2015-02-04 04:43:02 -08:00
Bob Lantz 340bf3cb7a Remove now-unused quietRun import 2015-02-04 04:29:08 -08:00
Bob Lantz 5f8547a5e0 Remove now-unused Link import 2015-02-04 04:28:57 -08:00
Bob Lantz 09e9c0550a Indent one line for consistency 2015-02-04 04:27:31 -08:00
Bob Lantz c1dc80571a Correctly group switches for batch operations 2015-02-04 04:26:58 -08:00
Bob Lantz ec9b23ba9c Delete tap9 for CE
In the future, we should probably not hardwire this,
or should at least make it a constant!!
2015-02-04 04:15:19 -08:00
Bob Lantz 7c0b56f9ba Delete both tunnel interfaces
We also clean things up a bit and check for error messages,
which now can cause exceptions which should invoke cleanup.
2015-02-04 04:14:40 -08:00
Bob Lantz d90a45514f node shell: remove unnecessary -m and unset HISTFILE
Since we already disable job notification with +m, it doesn't
make sense to set it in the original invocation!

It's also annoying if all of the host commands end up overwriting
your regular bash_history!!
2015-02-03 22:02:29 -08:00
Bob Lantz b2fe0778dc Change iperf() to use waitListening() 2015-02-03 21:44:21 -08:00
lantz 3e4f254573 Merge pull request #474 from mininet/devel/ovsbatch
Batch startup support for OVS

Currently, every ovs-vsctl command requires reading the entire OVS
configuration database. This means that its performance gets linearly
slower as more switches and ports are added. To mitigate this, we
batch multiple configuration operations into individual, long,
ovs-vsctl commands.

This patch set makes a couple of other notable changes, including
setting printPid=False by default (avoids using mnexec unnecessarily)
and running certain commands using errRun rather than quietRun.
Additionally we no longer look for leftover links in the root
namespace, so code relying on that functionality may have to change
slightly (as in controlnet.py and sshd.py for example.) It also adds
cluster support to mn -c.

The performance result is that mn --topo linear,200 --test none now
completes in 60 seconds rather than 95 seconds (on my laptop) without
the patch (vs. 101 seconds in 2.2.0).

This is still slower than I would like - we should be able to make
some additional improvements.
2015-02-03 19:24:03 -08:00
Bob Lantz 2e4dd13482 Turn off printPid by default to avoid mnexec fork/exec 2015-02-03 17:40:20 -08:00
Bob Lantz 19331ca287 use net.addLink() so that link is cleaned up 2015-02-03 16:02:07 -08:00
Bob Lantz 9483f6378f Make sure DataController's interfaces are deleted 2015-02-03 15:30:16 -08:00
Bob Lantz a4e933688a Set batch=False in OVSSwitch for low-level API
If you try to use the low-level API, you are probably
not going to call batchStartup()! So, we set batch=False
by default. This means that buildFromTopo() needs to set
it to True, so we add a bit of irritatingly complex machinery
to allow this to happen. The good fallout of this is that
now customConstructor() returns a real subclass, not simply
a constructor function! We also detect errors where people
are incorrectly attempting to give parameters to a lambda
function - since none of our lambdas accept parameters!!

Note that this is a bit like functools.partial for classes -
it would be nice if functools had a true subclassing function.
2015-01-29 00:26:15 -08:00
Bob Lantz c11e9f3316 Fix OVS user switch (remove unnecessary % parameter) 2015-01-28 17:04:58 -08:00
Bob Lantz acdcf9b6ae cluster: add batchStartup/Shutdown, cleanup 2015-01-27 15:27:26 -08:00
Bob Lantz c702840a0a Remove debug print lines 2015-01-27 15:24:29 -08:00
Bob Lantz 254fae2dc9 Clarify which intf pair failed and raise exception 2015-01-27 15:23:48 -08:00
Bob Lantz bdad3e8c8e Merge OVSBatch into OVSSwitch
Note that we are changing the interface of batchStartup/Shutdown
slightly so that the method can choose not to start some of the
switches. We might wish to refine this a bit...
2015-01-26 18:01:20 -08:00
Bob Lantz 574d634fc2 Don't clean up links that may have been dumped into root NS.
This should rarely happen - in the usual case, either the
links will be shut down by Mininet.stop(), or the interfaces
will be deleted by node.stop( deleteIntfs=True ), or the
links or interfaces will be explicitly deleted or stopped
using the low-level API.

Cases that are relying on links being automatically deleted
in cleanup() will potentially find that they are now no longer
deleted, but these cases should be rare.
2015-01-26 14:06:23 -08:00
Bob Lantz eafbd2a597 Change to OVSSwitch 2015-01-26 14:06:23 -08:00
Bob Lantz 7485b035af make 'ovs-vsctl' string symmetric 2015-01-26 14:06:23 -08:00
Bob Lantz 8014a7023c Fix super() typo 2015-01-26 14:06:23 -08:00
Bob Lantz bec34e7227 Clean up - TCReapply still broken! 2015-01-26 14:06:23 -08:00
Bob Lantz 9ca6322603 Remove shared reconnectms, improve self.started
We still need to set it in batchShutdown()
2015-01-26 14:06:23 -08:00
Bob Lantz 957fe1db93 Refactor for compatibility with isOldOVS() == True 2015-01-26 14:06:23 -08:00
Bob Lantz 3b4738c2ca First crack at setting controller backoff in single command 2015-01-26 14:06:23 -08:00
Bob Lantz 30ebb852a3 errRun: add debug( results ) 2015-01-26 14:06:22 -08:00
Bob Lantz 959586bc8f Add debug(cmd) to errRun() 2015-01-26 14:06:22 -08:00
Bob Lantz 9bda98486d Add OVSBatch class (experimental)
This implements batch startup for OVS switches.
2015-01-26 14:06:22 -08:00
Bob Lantz c68e4e76f4 Clarify bandwidth limit for TCIntfs
In the future, we would like to support higher bandwidth
limits for TCIntfs, but we'll probably need to adjust some
of the parameters dynamically.
2015-01-26 13:55:38 -08:00
Bob Lantz 98a8231cef Exit poll loop on POLLHUP or anything unexpected
In my opinion, we really shouldn't even get POLLHUP, but
we do. In case we get anything else odd, quite polling on
that fd.
2015-01-23 16:43:49 -08:00
Bob Lantz 28ce13d18e Fix polling in errRun
It's tricky to get this right, but basically we want to read
if there is something to read; if not, we want to check for
EOF.
2015-01-23 16:41:29 -08:00
Bob Lantz ef59cd88dc Return correct success condition in cgroupDel
Without this, we end up retrying until we fail, instead
of returning immediately on success!
2015-01-23 16:39:50 -08:00
Bob Lantz 9db6cdc261 Call delete() in link.stop() ; warn on exited node.cmd()
We should think a bit about the semantics that we want here.
The comments say "stop and clean up link" so perhaps that's
what we want. However, we could also imagine stop stopping
forwarding on the link (and possibly allowing restarts).

We warn on exited node.cmd() because we terminate the controller
before stopping/deleting the links. This makes sense to avoid a
storm of link/port down events, but since the controller's
shell has exited we cannot call link.stop() on any of its
links. We may want to simply stop the controller and not
terminate it, but at least it doesn't hang for now.
2015-01-22 03:22:43 -08:00
Bob Lantz f7b29333f5 2.2.0 -> 2.2.0+ 2015-01-18 23:35:54 -08:00
Bob Lantz 24520fc982 Allow + in version number 2015-01-18 23:35:29 -08:00
Bob Lantz b93cc989f4 Pass code check 2015-01-18 23:35:12 -08:00
Bob Lantz a8cc243aa7 Add stp param to OVS and connected() to OVSBridge
This allows --switch ovsbr,stp=True to work correctly
2015-01-18 22:59:21 -08:00
Bob Lantz e65dc4c6d6 OVSLink: use isinstance() to detect OVSSwitch subclasses 2015-01-18 21:56:38 -08:00
Bob Lantz d4be92713a Merge del-br into cmd, and add reconnectms param
With newer versions of OVS, this allows us to set up a switch
with a single OVS command (if reconnectms is zero.) If reconnectms
is specified, then it slows things down slightly (but not much.)
2015-01-18 16:09:02 -08:00
Bob Lantz 79f5d39db5 Comment edits and pass code check 2015-01-17 13:56:37 -08:00
Bob Lantz 6da3fcdef1 Add bridges s1-s3 to topology and explain some details 2015-01-17 13:38:55 -08:00
Bob Lantz 026130bd5f pass code check 2015-01-17 13:33:59 -08:00
Bob Lantz c1b48fb5c8 Stub out RemoteOVSSwitch.batchShutdown()
Eventually we should implement true batch shutdown.
In the mean time, we just ignore it. Note there's no good
way that I know of for a subclass to remove a superclass
method, so we changed the protocol a bit to require a return
value of True.
2015-01-15 02:43:38 -08:00
Bob Lantz b1983548aa Fix indentation error 2015-01-15 02:36:34 -08:00
Bob Lantz c62812a944 Update cluster.py for new makeIntfPair 2015-01-15 02:29:51 -08:00
Bob Lantz d66b96260a Don't stop switches that we've already stopped.
Note that this also changes the way that links are deleted;
the reason is that the batch shutdown doesn't currently delete
the links, but OVSSwitch.stop() does. We may wish to revisit
this in the future.
2015-01-15 02:07:00 -08:00
Bob Lantz d7e9c3bbfd Add comment about redundant intf.delete() 2015-01-15 02:07:00 -08:00
Bob Lantz 7a4a865bdb customize makeIntfPair to eliminate fastIntfPair 2015-01-15 02:07:00 -08:00
Bob Lantz da4dcf3753 Add addresses to fastIntfPair() and fix codecheck 2015-01-15 02:07:00 -08:00
Bob Lantz 5383b0e603 Update comment to reflect OVS patch link limits 2015-01-15 02:07:00 -08:00
Bob Lantz 9d2e6404b3 add fastIntfPair to speed up link creation 2015-01-15 02:07:00 -08:00
Bob Lantz 91a73bd191 use isinstance( intf, OVSIntf ) 2015-01-15 02:07:00 -08:00
Bob Lantz c069542c5c Add OVSLink/--link ovs, which uses OVS patch links when possible 2015-01-15 02:07:00 -08:00
Bob Lantz 127f35a9bc Revert to using OVS default OpenFlow versions.
It makes sense to follow Open vSwitch's lead here.
OVS 2.3 enables OpenFlow 1.0 through 1.3 by default.
OVS 2.0 has incomplete support for 1.3, but you can enable
it with protocols=OpenFlow13
2015-01-14 14:18:53 -08:00
Bob Lantz 171e815122 Set OVSSwitch default protocols to OpenFlow10,OpenFlow13
For OVS versions 1.9 or earlier, this setting is ignored.
2015-01-14 14:15:14 -08:00
Bob Lantz 3ac5cafe53 Fix code minor code check errors 2015-01-06 16:26:33 -08:00
Bob Lantz a7ad739036 Disable IPv6 via grub command line
Unfortunately disabling IPv6 via sysctl doesn't actually
disable it on all of the interfaces by default. Disabling
it via grub disables it entirely in the VM.

Helps with #454
2014-12-17 13:49:12 -08:00
Bob Lantz a84bec9709 Disable splash and quiet individually (more robust) 2014-12-17 13:48:43 -08:00
Bob Lantz 05dbf82edb Correctly set controller backoff for OVS.
Also report connected in standalone/bridge mode

Fixes #460

Conflicts:
	mininet/node.py
2014-12-11 17:03:49 -08:00
Bob Lantz c75eb47158 2.2.0rc1 -> 2.2.0 2014-12-09 13:37:43 -08:00
Bob Lantz 9945864a32 2.2.0rc1 -> 2.2.0rc2
Basically no changes except for whitespace and satisfying pep8.
2014-12-08 15:37:50 -08:00
Bob Lantz beeea4b292 Exclude miniedit from pep8 checking for now
(Also untabbed comment lines, flagged by emacs et al.)
2014-12-08 15:27:13 -08:00
Bob Lantz 7a3159c9af Spacing tweaks for pep8 checker 2014-12-08 15:10:32 -08:00
Bob Lantz ccd6b5cd7d version -> 2.2.0rc1 and update copyright date 2014-12-04 09:28:19 -08:00
Bob Lantz 908e85d9f9 Remove PLFMT since options are moved to .pylint 2014-12-04 09:27:53 -08:00
Bob Lantz e341526f46 Raise line limit to 4000 for miniedit.py (see note)
Future versions of pylint will allow this to be disabled
in the file itself, so we can drop the limit back to
1500 or something more reasonable!!
2014-12-04 09:26:11 -08:00
Bob Lantz 03461ce908 Add 'slowtest' make target to test walkthrough, examples 2014-12-04 09:17:13 -08:00
Bob Lantz 8c37975d44 Remove erroneous self.cmd = None 2014-12-04 09:03:46 -08:00
Bob Lantz 4d55ef1132 Restore missing space in RT_GROUP_SCHED message 2014-12-04 08:57:28 -08:00
Bob Lantz 342d47cfb5 call to run() should be to runCmd() 2014-12-04 08:56:08 -08:00
Bob Lantz 554abdd57a warn -> debug in connected; change IVS class comment 2014-12-04 08:38:54 -08:00
Bob Lantz 061598f011 Change from numeric to symbolic pylint error codes 2014-12-04 08:21:53 -08:00
Bob Lantz d754a7ceea Call super(deleteIntfs)
Maybe this is better - maybe not. ;-p
2014-12-04 07:36:53 -08:00
Bob Lantz 643c9f912f More pylint changes... 2014-12-04 05:55:57 -08:00
Bob Lantz 4965421215 More pylint fixes... 2014-12-04 02:57:36 -08:00
Bob Lantz 18aab5b786 More pylint changes 2014-12-04 00:51:05 -08:00
Bob Lantz b905dddf19 Reorganize and pass pylint 2014-12-02 20:26:34 -08:00
Bob Lantz 11a9c46904 Fix missing imports 2014-12-02 23:22:56 -08:00
Bob Lantz b1ec912db5 Fixing pylint errors 2014-12-02 23:00:50 -08:00
Bob Lantz db45b7c644 Parseable output format for newer pylint 2014-12-02 23:00:14 -08:00
Bob Lantz 2256a53830 Fix pylint error 2014-12-02 22:59:32 -08:00
Bob Lantz 03ef55672d Add cleanup and fix pylint errors 2014-12-02 22:58:57 -08:00
Bob Lantz c45bfab318 Add cleanup 2014-12-02 22:58:38 -08:00
Bob Lantz b2fcab827d Add cleanup and fix pylint errors 2014-12-02 22:58:10 -08:00
Bob Lantz 1471da95a9 Fix pylint errors 2014-12-02 22:57:32 -08:00
Bob Lantz 5a530af189 Remove trailing whitespace. ;-/ 2014-12-01 15:39:44 -08:00
Bob Lantz 3c9f5ad56e Disable pep8 whitespace errors 2014-12-01 15:38:38 -08:00
Bob Lantz c5d9e0e03c Set default route in one cmd line to avoid dc'ing root NS
Usually you won't want to create a node in the root namespace,
and usually you won't want to use the Mininet API to set your
(real) default route, but if you do then you will probably want
to use a single command line to avoid disconnecting an SSH
session while we wait for the reulst of the 'ip route del default
command.
2014-12-01 15:10:44 -08:00
Brian O'Connor 0094997aa1 fixing install-mininet-vm.sh 2014-11-25 17:18:49 -08:00
lantz 7a411b6bb5 Merge pull request #453 from cdburkard/master
update examples README with new examples
2014-11-25 13:01:31 -08:00
cody burkard c2341cd47a update examples README with new examples 2014-11-24 19:43:15 -08:00
Bob Lantz 4219b22978 2.2.0b2 2014-11-24 19:35:34 -08:00
lantz 08ab7e8de7 Merge pull request #452 from mininet/nat-cmd
Updating NAT class to use gateway interface
2014-11-24 19:13:33 -08:00
lantz 3ef6bcface Additional info about --nat and LinuxRouter 2014-11-24 19:04:15 -08:00
67 changed files with 4525 additions and 2299 deletions

No files matched your search

+9 -5
View File
@@ -41,16 +41,19 @@ load-plugins=
# can either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once).
disable=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801,I0011
disable=pointless-except, invalid-name, super-init-not-called, fixme, star-args,
too-many-instance-attributes, too-few-public-methods, too-many-arguments,
too-many-locals, too-many-public-methods, duplicate-code, bad-whitespace,
locally-disabled
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=colorized
msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
# Include message's id in outpu
# Include message's id in output
include-ids=yes
# Put messages in a separate file for each module / package specified on the
@@ -191,7 +194,7 @@ additional-builtins=
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
defining-attr-methods=__init__,__new__,setUp,build
# checks for sign of poor/misdesign:
@@ -264,7 +267,8 @@ int-import-graph=
max-line-length=80
# Maximum number of lines in a module
max-module-lines=1500
# XXX 1500 -> 4000 for miniedit.py
max-module-lines=4000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
+4 -2
View File
@@ -29,14 +29,16 @@ Andrew Ferguson
Eder Leao Fernandes
Gregory Gee
Jon Hall
Roan Huang
Vitaly Ivanov
Babis Kaidos
Rich Lane
Rémy Léone
Zi Shen Lim
David Mahler
Murphy McCauley
José Pedro Oliveira
James Page
Rich Lane
Rémy Léone
Angad Singh
Piyush Srivastava
Ed Swierk
+1 -1
View File
@@ -2,7 +2,7 @@
Mininet Installation/Configuration Notes
----------------------------------------
Mininet 2.2.0b1
Mininet 2.2.1
---
The supported installation methods for Mininet are 1) using a
+2 -2
View File
@@ -1,6 +1,6 @@
Mininet 2.2.0b1 License
Mininet 2.2.1 License
Copyright (c) 2013 Open Networking Laboratory
Copyright (c) 2013-2015 Open Networking Laboratory
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
The Leland Stanford Junior University
+10 -4
View File
@@ -6,7 +6,7 @@ BIN = $(MN)
PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
MNEXEC = mnexec
MANPAGES = mn.1 mnexec.1
P8IGN = E251,E201,E302,E202
P8IGN = E251,E201,E302,E202,E126,E127,E203,E226
BINDIR = /usr/bin
MANDIR = /usr/share/man/man1
DOCDIRS = doc/html doc/latex
@@ -24,7 +24,8 @@ codecheck: $(PYSRC)
util/versioncheck.py
pyflakes $(PYSRC)
pylint --rcfile=.pylint $(PYSRC)
pep8 --repeat --ignore=$(P8IGN) $(PYSRC)
# Exclude miniedit from pep8 checking for now
pep8 --repeat --ignore=$(P8IGN) `ls $(PYSRC) | grep -v miniedit.py`
errcheck: $(PYSRC)
-echo "Running check for errors only"
@@ -36,6 +37,11 @@ test: $(MININET) $(TEST)
mininet/test/test_nets.py
mininet/test/test_hifi.py
slowtest: $(MININET)
-echo "Running slower tests (walkthrough, examples)"
mininet/test/test_walkthrough.py -v
mininet/examples/test/runner.py -v
mnexec: mnexec.c $(MN) mininet/net.py
cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@
@@ -45,7 +51,7 @@ install: $(MNEXEC) $(MANPAGES)
python setup.py install
develop: $(MNEXEC) $(MANPAGES)
# Perhaps we should link these as well
# Perhaps we should link these as well
install $(MNEXEC) $(BINDIR)
install $(MANPAGES) $(MANDIR)
python setup.py develop
@@ -58,7 +64,7 @@ mn.1: $(MN)
mnexec.1: mnexec
help2man -N -n "execution utility for Mininet." \
-h "-h" -v "-v" --no-discard-stderr ./$< -o $@
-h "-h" -v "-v" --no-discard-stderr ./$< -o $@
.PHONY: doc
+19 -34
View File
@@ -3,7 +3,7 @@ Mininet: Rapid Prototyping for Software Defined Networks
*The best way to emulate almost any network on your laptop!*
Mininet 2.2.0b1
Mininet 2.2.1
### What is Mininet?
@@ -68,35 +68,25 @@ Mininet includes:
### New features in this release
This release provides a number of bug fixes as well as
several new features, including:
This is primarily a performance improvement and bug fix release.
* Improved OpenFlow 1.3 support
- Batch startup has been implemented for Open vSwitch, improving
startup performance.
- `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
- OVS patch links have been implemented via OVSLink and --link ovs
* A new `nodelib.py` node library, and new `Node` types including
`LinuxBridge`, `OVSBridge`, `LinuxRouter` and `NAT`
Warning! These links have *serious limitations* compared to
virtual Ethernet pairs: they are not attached to real Linux
interfaces so you cannot use tcpdump or wireshark with them;
they also cannot be used in long chains - we don't recommend more
than 64 OVSLinks, for example --linear,64. However, they can offer
significantly better performance than veth pairs, for certain
configurations.
* An improved MiniEdit GUI (`examples/miniedit.py`) - thanks to
Gregory Gee
- You can now easily install Mininet on a Raspberry Pi ;-)
* 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
not be considered a stable part of the Mininet API!
- Additional information for this release and previous releases
may be found in the release notes on docs.mininet.org
### Installation
@@ -120,6 +110,8 @@ Mininet mailing list, `mininet-discuss` at:
### Join Us
Thanks again to all of the Mininet contributors!
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,
@@ -133,12 +125,5 @@ hard work that Mininet continues to grow and improve.
Best wishes, and we look forward to seeing what you can do with
Mininet to change the networking world!
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.
Bob Lantz
Mininet Core Team
+77 -60
View File
@@ -25,29 +25,31 @@ from mininet.cli import CLI
from mininet.log import lg, LEVELS, info, debug, warn, error
from mininet.net import Mininet, MininetWithControlNet, VERSION
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
RYU, NOX, RemoteController, findController, DefaultController,
Ryu, NOX, RemoteController, findController,
DefaultController, NullController,
UserSwitch, OVSSwitch, OVSBridge,
OVSLegacyKernelSwitch, IVSSwitch )
from mininet.nodelib import LinuxBridge
from mininet.link import Link, TCLink
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
IVSSwitch )
from mininet.nodelib import LinuxBridge, Server
from mininet.link import Link, TCLink, OVSLink
from mininet.topo import ( SingleSwitchTopo, LinearTopo,
SingleSwitchReversedTopo, MinimalTopo )
from mininet.topolib import TreeTopo, TorusTopo
from mininet.util import customConstructor, splitArgs
from mininet.util import customClass, specialClass, 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.cluster import ( MininetCluster,
SwitchBinPlacer, RandomPlacer,
ClusterCleanup )
from mininet.examples.clustercli import ClusterCLI
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
# built in topologies, created only when run
TOPODEF = 'minimal'
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
TOPOS = { 'minimal': MinimalTopo,
'linear': LinearTopo,
'reversed': SingleSwitchReversedTopo,
'single': SingleSwitchTopo,
@@ -56,32 +58,34 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
SWITCHDEF = 'default'
SWITCHES = { 'user': UserSwitch,
'ovs': OVSSwitch,
'ovs': OVSSwitch,
'ovsbr' : OVSBridge,
# Keep ovsk for compatibility with 2.0
'ovsk': OVSSwitch,
'ovsl': OVSLegacyKernelSwitch,
'ivs': IVSSwitch,
'lxbr': LinuxBridge,
'default': OVSSwitch }
HOSTDEF = 'proc'
HOSTS = { 'proc': Host,
'rt': partial( CPULimitedHost, sched='rt' ),
'cfs': partial( CPULimitedHost, sched='cfs' ) }
'server': Server,
'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ),
'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) }
CONTROLLERDEF = 'default'
CONTROLLERS = { 'ref': Controller,
'ovsc': OVSController,
'nox': NOX,
'remote': RemoteController,
'ryu': RYU,
'ryu': Ryu,
'default': DefaultController, # Note: replaced below
'none': lambda name: None }
'none': NullController }
LINKDEF = 'default'
LINKS = { 'default': Link,
'tc': TCLink }
'tc': TCLink,
'ovs': OVSLink }
# optional tests to run
@@ -94,24 +98,21 @@ ALTSPELLING = { 'pingall': 'pingAll',
'iperfUDP': 'iperfUdp' }
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
def addDictOption( opts, choicesDict, default, name, **kwargs ):
"""Convenience function to add choices dicts to OptionParser.
opts: OptionParser instance
choicesDict: dictionary of valid choices, must include default
default: default choice key
name: long option name
help: string"""
if default not in choicesDict:
raise Exception( 'Invalid default %s for choices dict: %s' %
( default, name ) )
if not helpStr:
helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
'[,param=value...]' )
opts.add_option( '--' + name,
type='string',
default = default,
help = helpStr )
kwargs: additional arguments to add_option"""
helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
'[,param=value...]' )
helpList = [ '%s=%s' % ( k, v.__name__ )
for k, v in choicesDict.items() ]
helpStr += ' ' + ( ' '.join( helpList ) )
params = dict( type='string', default=default, help=helpStr )
params.update( **kwargs )
opts.add_option( '--' + name, **params )
def version( *_args ):
"Print Mininet version and exit"
@@ -132,7 +133,7 @@ class MininetRunner( object ):
self.setup()
self.begin()
def custom( self, option, opt_str, value, parser ):
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
@@ -145,7 +146,7 @@ class MininetRunner( object ):
else:
# Accept a comma-separated list of filenames
files += value.split(',')
for fileName in files:
customs = {}
if os.path.isfile( fileName ):
@@ -157,7 +158,7 @@ class MininetRunner( object ):
def setCustom( self, name, value ):
"Set custom parameters for MininetRunner."
if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
if name in ( 'topos', 'switches', 'hosts', 'controllers', 'links' ):
# Update dictionaries
param = name.upper()
globals()[ param ].update( value )
@@ -168,9 +169,12 @@ class MininetRunner( object ):
# Add or modify global variable or class
globals()[ name ] = value
def setNat( self, option, opt_str, value, parser ):
def setNat( self, _option, opt_str, value, parser ):
"Set NAT option(s)"
assert self # satisfy pylint
parser.values.nat = True
if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-': #first arg, first char != '-'
# first arg, first char != '-'
if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-':
value = parser.rargs.pop( 0 )
_, args, kwargs = splitArgs( opt_str + ',' + value )
parser.values.nat_args = args
@@ -193,7 +197,7 @@ class MininetRunner( object ):
opts = OptionParser( description=desc, usage=usage )
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
addDictOption( opts, HOSTS, HOSTDEF, 'host' )
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
addDictOption( opts, CONTROLLERS, [], 'controller', action='append' )
addDictOption( opts, LINKS, LINKDEF, 'link' )
addDictOption( opts, TOPOS, TOPODEF, 'topo' )
@@ -201,7 +205,10 @@ class MininetRunner( object ):
default=False, help='clean and exit' )
opts.add_option( '--custom', action='callback',
callback=self.custom,
type='string', help='read custom classes or params from .py file(s)' )
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 ) )
@@ -231,10 +238,12 @@ class MininetRunner( object ):
default=False, help="pin hosts to CPU cores "
"(requires --host cfs or --host rt)" )
opts.add_option( '--nat', action='callback', callback=self.setNat,
help="adds a NAT to the topology that connects Mininet hosts"
" to the physical network."
" Warning: This may route any traffic on the machine that uses Mininet's"
" IP subnet into the Mininet network. If you need to change"
help="[option=val...] 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' )
@@ -265,36 +274,45 @@ class MininetRunner( object ):
% self.options.verbosity )
lg.setLogLevel( self.options.verbosity )
# Maybe we'll reorganize this someday...
# pylint: disable=too-many-branches,too-many-statements
def begin( self ):
"Create and run mininet."
if self.options.cluster:
servers = self.options.cluster.split( ',' )
for server in servers:
ClusterCleanup.add( server )
if self.options.clean:
cleanup()
exit()
start = time.time()
if self.options.controller == 'default':
if not self.options.controller:
# Update default based on available controllers
CONTROLLERS[ 'default' ] = findController()
if CONTROLLERS[ 'default' ] is None:
self.options.controller = [ 'default' ]
if not CONTROLLERS[ 'default' ]:
self.options.controller = [ '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( '*** 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" %
elif self.options.switch not in ( 'ovsbr', 'lxbr' ):
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 )
controller = customConstructor( CONTROLLERS, self.options.controller )
link = customConstructor( LINKS, self.options.link )
switch = customClass( SWITCHES, self.options.switch )
host = customClass( HOSTS, self.options.host )
controller = [ customClass( CONTROLLERS, c )
for c in self.options.controller ]
link = customClass( LINKS, self.options.link )
if self.validate:
self.validate( self.options )
@@ -317,10 +335,8 @@ class MininetRunner( object ):
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( ',' ),
warn( '*** WARNING: Experimental cluster mode!\n' )
Net = partial( MininetCluster, servers=servers,
placement=PLACEMENT[ self.options.placement ] )
mn = Net( topo=topo,
@@ -333,7 +349,8 @@ class MininetRunner( object ):
listenPort=listenPort )
if self.options.ensure_value( 'nat', False ):
nat = mn.addNAT( *self.options.nat_args, **self.options.nat_kwargs )
nat = mn.addNAT( *self.options.nat_args,
**self.options.nat_kwargs )
nat.configDefault()
if self.options.pre:
+43 -3
View File
@@ -11,6 +11,31 @@ Mininet's Python API.
This example uses Mininet's medium-level API to create an sshd
process running in a namespace. Doesn't use OpenFlow.
#### bind.py:
This example shows how you can create private directories for each
node in a Mininet topology.
#### cluster.py:
This example contains all of the code for experimental cluster
edition. Remote classes and MininetCluster can be imported from
here to create a topology with nodes on remote machines.
#### clusterSanity.py:
This example runs cluster edition locally as a sanity check to test
basic functionality.
#### clustercli.py:
This example contains a CLI for experimental cluster edition.
#### clusterdemo.py:
This example is a basic demo of cluster edition on 3 servers with
a tree topology of depth 3 and fanout 3.
#### consoles.py:
This example creates a grid of console windows, one for each node,
@@ -47,6 +72,11 @@ topology object) and adding nodes to it.
This example shows how to add an interface (for example a real
hardware interface) to a network after the network is created.
#### intfoptions.py:
This example reconfigures a TCIntf during runtime with different
traffic control commands to test bandwidth, loss, and delay.
#### limit.py:
This example shows how to use link and CPU limits.
@@ -56,7 +86,7 @@ This example shows how to use link and CPU limits.
This example shows how to create a custom topology programatically
by subclassing Topo, and how to run a series of tests on it.
### linuxrouter.py:
#### linuxrouter.py:
This example shows how to create and configure a router in Mininet
that uses Linux IP forwarding.
@@ -65,11 +95,16 @@ that uses Linux IP forwarding.
This example demonstrates creating a network via a graphical editor.
#### mobility.py
#### mobility.py:
This example demonstrates detaching an interface from one switch and
attaching it another as a basic way to move a host around a network.
#### multiLink.py:
This example demonstrates the creation of multiple links between
nodes using a custom Topology class.
#### multiping.py:
This example demonstrates one method for
@@ -89,7 +124,12 @@ 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
#### natnet.py:
This example demonstrates how to create a network using a NAT node
to connect hosts to the internet.
#### 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
+4 -6
View File
@@ -22,7 +22,7 @@ to temporary private directories. To do this, simply create a list of
directories to be made private. A tmpfs will then be mounted on them.
You may use both temporary and persistent directories at the same
time. In the following privateDirs string, each host will have a
time. In the following privateDirs string, each host will have a
persistent directory in the root filesystem at
"/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also
have a temporary private directory mounted on "/var/log".
@@ -48,8 +48,8 @@ from functools import partial
def testHostWithPrivateDirs():
"Test bind mounts"
topo = SingleSwitchTopo( 10 )
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
( '/var/run', '/tmp/%(name)s/var/run' ),
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
( '/var/run', '/tmp/%(name)s/var/run' ),
'/var/mn' ]
host = partial( Host,
privateDirs=privateDirs )
@@ -57,7 +57,7 @@ def testHostWithPrivateDirs():
net.start()
directories = [ directory[ 0 ] if isinstance( directory, tuple )
else directory for directory in privateDirs ]
info( 'Private Directories:', directories, '\n' )
info( 'Private Directories:', directories, '\n' )
CLI( net )
net.stop()
@@ -65,5 +65,3 @@ if __name__ == '__main__':
setLogLevel( 'info' )
testHostWithPrivateDirs()
info( 'Done.\n')
+918
View File
@@ -0,0 +1,918 @@
#!/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, errRun
from mininet.examples.clustercli import CLI
from mininet.log import setLogLevel, debug, info, error
from mininet.clean import addCleanupCallback
from signal import signal, SIGINT, SIG_IGN
from subprocess import Popen, PIPE, STDOUT
import os
from random import randrange
import sys
import re
from itertools import groupby
from operator import attrgetter
from distutils.version import StrictVersion
def findUser():
"Try to return logged-in (usually non-root) user"
return (
# If we're running sudo
os.environ.get( 'SUDO_USER', False ) or
# Logged-in user (if we have a tty)
( quietRun( 'who am i' ).split() or [ False ] )[ 0 ] or
# Give up and return effective user
quietRun( 'whoami' ).strip() )
class ClusterCleanup( object ):
"Cleanup callback"
inited = False
serveruser = {}
@classmethod
def add( cls, server, user='' ):
"Add an entry to server: user dict"
if not cls.inited:
addCleanupCallback( cls.cleanup )
if not user:
user = findUser()
cls.serveruser[ server ] = user
@classmethod
def cleanup( cls ):
"Clean up"
info( '*** Cleaning up cluster\n' )
for server, user in cls.serveruser.iteritems():
if server == 'localhost':
# Handled by mininet.clean.cleanup()
continue
else:
cmd = [ 'su', user, '-c',
'ssh %s@%s sudo mn -c' % ( user, server ) ]
info( cmd, '\n' )
info( quietRun( cmd ) )
# 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=True, 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 findUser()
ClusterCleanup.add( server=server, user=user )
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.dest ]
self.isRemote = True
else:
self.dest = None
self.sshcmd = []
self.isRemote = False
# Satisfy pylint
self.shell, self.pid = None, None
super( RemoteMixin, self ).__init__( name, **kwargs )
# 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='-cp' )
super( RemoteMixin, self ).startShell( *args, **kwargs )
# Optional split initialization
self.sendCmd( 'echo $$' )
if not self.splitInit:
self.finishInit()
def finishInit( self ):
"Wait for split initialization to complete"
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', cmd, '\n' )
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"
kwargs.update( moveIntfFn=RemoteLink.moveIntf )
return super( RemoteMixin, self).addIntf( *args, **kwargs )
class RemoteNode( RemoteMixin, Node ):
"A node on a remote server"
pass
class RemoteHost( RemoteNode ):
"A RemoteHost is simply a RemoteNode"
pass
class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
"Remote instance of Open vSwitch"
OVSVersions = {}
def __init__( self, *args, **kwargs ):
# No batch startup yet
kwargs.update( batch=True )
super( RemoteOVSSwitch, self ).__init__( *args, **kwargs )
def isOldOVS( self ):
"Is remote switch using an old OVS version?"
cls = type( self )
if self.server not in cls.OVSVersions:
# pylint: disable=not-callable
vers = self.cmd( 'ovs-vsctl --version' )
# pylint: enable=not-callable
cls.OVSVersions[ self.server ] = re.findall(
r'\d+\.\d+', vers )[ 0 ]
return ( StrictVersion( cls.OVSVersions[ self.server ] ) <
StrictVersion( '1.10' ) )
@classmethod
def batchStartup( cls, switches, **_kwargs ):
"Start up switches in per-server batches"
key = attrgetter( 'server' )
for server, switchGroup in groupby( sorted( switches, key=key ), key ):
info( '(%s)' % server )
group = tuple( switchGroup )
switch = group[ 0 ]
OVSSwitch.batchStartup( group, run=switch.cmd )
return switches
@classmethod
def batchShutdown( cls, switches, **_kwargs ):
"Stop switches in per-server batches"
key = attrgetter( 'server' )
for server, switchGroup in groupby( sorted( switches, key=key ), key ):
info( '(%s)' % server )
group = tuple( switchGroup )
switch = group[ 0 ]
OVSSwitch.batchShutdown( group, run=switch.rcmd )
return switches
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', {} )
self.cmd = None # satisfy pylint
Link.__init__( self, node1, node2, **kwargs )
def stop( self ):
"Stop this link"
if self.tunnel:
self.tunnel.terminate()
self.intf1.delete()
self.intf2.delete()
else:
Link.stop( self )
self.tunnel = None
def makeIntfPair( self, intfname1, intfname2, addr1=None, addr2=None,
node1=None, node2=None, deleteIntfs=True ):
"""Create pair of interfaces
intfname1: name of interface 1
intfname2: name of interface 2
(override this method [and possibly delete()]
to change link type)"""
node1 = self.node1 if node1 is None else node1
node2 = self.node2 if node2 is None else node2
server1 = getattr( node1, 'server', 'localhost' )
server2 = getattr( node2, 'server', 'localhost' )
if server1 == server2:
# Link within same server
return Link.makeIntfPair( intfname1, intfname2, addr1, addr2,
node1, node2, deleteIntfs=deleteIntfs,
runCmd=None )
# 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
cmd = 'ip tuntap add dev tap9 mode tap user ' + node.user
result = node.rcmd( cmd )
if result:
raise Exception( 'error creating tap9 on %s: %s' %
( node, result ) )
# 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 != '@':
raise Exception( 'makeTunnel:\n',
'Tunnel setup failed for',
'%s:%s' % ( node1, node1.dest ), 'to',
'%s:%s\n' % ( node2, node2.dest ),
'command was:', cmd, '\n' )
# 3. Move interfaces if necessary
for node in node1, node2:
if not self.moveIntf( 'tap9', node ):
raise Exception( 'interface move failed on node %s' % node )
# 4. Rename tap interfaces to desired names
for node, intf, addr in ( ( node1, intfname1, addr1 ),
( node2, intfname2, addr2 ) ):
if not addr:
result = node.cmd( 'ip link set tap9 name', intf )
else:
result = node.cmd( 'ip link set tap9 name', intf,
'address', addr )
if result:
raise Exception( 'error renaming %s: %s' % ( intf, result ) )
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"
assert self, node # satisfy pylint
# Default placement: run locally
return 'localhost'
class RandomPlacer( Placer ):
"Random placement"
def place( self, nodename ):
"""Random placement function
nodename: node name"""
assert nodename # please pylint
# This may be slow with lots of servers
return self.servers[ randrange( 0, len( self.servers ) ) ]
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"""
assert nodename # please pylint
# This may be slow with lots of servers
server = self.servers[ self.next ]
self.next = ( self.next + 1 ) % len( self.servers )
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', 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"
assert self # please pylint
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' )
sys.exit( 1 )
info( '\n' )
def modifiedaddHost( self, *args, **kwargs ):
"Slightly modify addHost"
assert self # please pylint
kwargs[ 'splitInit' ] = True
return Mininet.addHost( *args, **kwargs )
def placeNodes( self ):
"""Place nodes on servers (if they don't have a server), and
start shell processes"""
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' ] is 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' ):
links = controller.cmd( 'ip link show' )
eth0 = re.findall( ' (.*eth0):', links )
if not eth0:
raise Exception( 'Cannot find IP address for controller eth0' )
Intf( eth0[ 0 ], 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()
+751 -269
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -12,11 +12,11 @@ from mininet.topo import SingleSwitchTopo
def clusterSanity():
"Sanity check for cluster mode"
topo = SingleSwitchTopo()
net = MininetCluster( topo=topo )
net = MininetCluster( topo=topo )
net.start()
CLI( net )
net.stop()
if __name__ == '__main__':
if __name__ == '__main__':
setLogLevel( 'info' )
clusterSanity()
+23 -17
View File
@@ -5,6 +5,7 @@
from mininet.cli import CLI
from mininet.log import output, error
# pylint: disable=global-statement
nx, graphviz_layout, plt = None, None, None # Will be imported on demand
@@ -15,25 +16,28 @@ class ClusterCLI( CLI ):
def colorsFor( seq ):
"Return a list of background colors for a sequence"
colors = [ 'red', 'lightgreen', 'cyan', 'yellow', 'orange',
'magenta', 'pink', 'grey', 'brown',
'white' ]
'magenta', 'pink', 'grey', 'brown',
'white' ]
slen, clen = len( seq ), len( colors )
reps = max( 1, slen / clen )
colors = colors * reps
colors = colors * ( slen / clen + 1 )
colors = colors[ 0 : slen ]
return colors
def do_plot( self, line ):
def do_plot( self, _line ):
"Plot topology colored by node placement"
# Import networkx if needed
global nx, plt
if not nx:
try:
import networkx as nx
import matplotlib.pyplot as plt
# pylint: disable=import-error
import networkx
nx = networkx # satisfy pylint
from matplotlib import pyplot
plt = pyplot # satisfiy pylint
import pygraphviz
assert pygraphviz # silence pyflakes
except:
# pylint: enable=import-error
except ImportError:
error( 'plot requires networkx, matplotlib and pygraphviz - '
'please install them and try again\n' )
return
@@ -52,11 +56,14 @@ class ClusterCLI( CLI ):
# Plot it!
pos = nx.graphviz_layout( g )
opts = { 'ax': None, 'font_weight': 'bold',
'width': 2, 'edge_color': 'darkblue' }
hcolors = [ color[ getattr( h, 'server', 'localhost' ) ] for h in hosts ]
scolors = [ color[ getattr( s, 'server', 'localhost' ) ] for s in switches ]
nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800, label='host',
node_color=hcolors, node_shape='s', **opts )
'width': 2, 'edge_color': 'darkblue' }
hcolors = [ color[ 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
@@ -68,7 +75,7 @@ class ClusterCLI( CLI ):
plt.title( 'Node Placement', fontweight='bold' )
plt.show()
def do_status( self, line ):
def do_status( self, _line ):
"Report on node shell status"
nodes = self.mn.hosts + self.mn.switches
for node in nodes:
@@ -82,8 +89,7 @@ class ClusterCLI( CLI ):
else:
output( 'All nodes are still running.\n' )
def do_placement( self, line ):
def do_placement( self, _line ):
"Describe node placement"
mn = self.mn
nodes = mn.hosts + mn.switches + mn.controllers
-1
View File
@@ -20,4 +20,3 @@ def demo():
if __name__ == '__main__':
setLogLevel( 'info' )
demo()
+1 -1
View File
@@ -327,7 +327,7 @@ class ConsoleApp( Frame ):
elif units[0] == 'b':
val *= 10 ** -9
self.updates += 1
self.bw += val
self.bw += val
if self.updates >= self.hostCount:
self.graph.addBar( self.bw )
self.bw = 0
+10 -3
View File
@@ -27,15 +27,22 @@ from mininet.log import setLogLevel, info
class DataController( Controller ):
"""Data Network Controller.
patched to avoid checkListening error"""
patched to avoid checkListening error and to delete intfs"""
def checkListening( self ):
"Ignore spurious error"
pass
def stop( self, *args, **kwargs ):
"Make sure intfs are deleted"
kwargs.update( deleteIntfs=True )
super( DataController, self ).stop( *args, **kwargs )
class MininetFacade( object ):
"""Mininet object facade that allows a single CLI to
talk to one or more networks"""
def __init__( self, net, *args, **kwargs ):
"""Create MininetFacade object.
net: Primary Mininet object
@@ -114,7 +121,7 @@ class ControlNetwork( Topo ):
def run():
"Create control and data networks, and invoke the CLI"
info( '* Creating Control Network\n' )
ctopo = ControlNetwork( n=4, dataController=DataController )
cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24', controller=None )
+1
View File
@@ -27,6 +27,7 @@ def bwtest( cpuLimits, period_us=100000, seconds=5 ):
cpu=cpu )
try:
net = Mininet( topo=topo, host=host )
# pylint: disable=bare-except
except:
info( '*** Skipping host %s\n' % sched )
break
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/python
"""
fakecluster.py: a fake cluster for testing Mininet cluster edition!!!
We are going to self-host Mininet by creating a virtual cluster
for cluster edition.
Note: ssh is kind of a mess - you end up having to do things
like h1 sudo -E -u openflow ssh 10.2
"""
from mininet.net import Mininet
from mininet.nodelib import LinuxBridge, Server
from mininet.cli import CLI
from mininet.topo import Topo, SingleSwitchTopo
from mininet.log import setLogLevel, warn
from mininet.util import errRun, quietRun
from mininet.link import Link
from functools import partial
from sys import argv
class MininetServer( Server ):
"A server (for nested Mininet) that runs ssh and ovs"
privateDirs = [ '/var/run/sshd', '/etc/openvswitch',
'/var/run/openvswitch', '/var/log/openvswitch' ]
def __init__( self, *args, **kwargs ):
"Turn on ovs by default"
kwargs.setdefault( 'ovs', True )
super( MininetServer, self ).__init__( *args, **kwargs )
def config( self, **kwargs ):
"""Configure/start sshd and other stuff
ovs: start Open vSwitch?"""
self.ovs = kwargs.get( 'ovs' )
super( MininetServer, self ).config( **kwargs )
if self.ovs:
self.service( 'openvswitch-switch start' )
def terminate( self, *args, **kwargs ):
"Shut down services and terminate server"
if self.ovs:
self.service( 'openvswitch-switch stop' )
super( MininetServer, self ).terminate( *args, **kwargs )
class ServerLink( Link ):
def intfName( self, node, n ):
"Override to avoid destruction by cleanup!"
# This is kind of ugly... for some reason 'eth0' fails so
# we just use 'm1eth0'; however, this should nest reasonably.
return ( node.name + 'eth' + repr( n ) if isinstance( node, Server )
else node.name + '-eth' + repr( n ) )
def makeIntfPair( self, *args, **kwargs ):
"Override to use quietRun"
kwargs.update( runCmd=quietRun )
super( ServerLink, self ).makeIntfPair( *args, **kwargs )
class ClusterTopo( Topo ):
"Cluster topology: m1..mN"
def build( self, n ):
ms1 = self.addSwitch( 'ms1' )
for i in range( 1, n + 1 ):
h = self.addHost( 'm%d' % i )
self.addLink( h, ms1, cls=ServerLink )
def test( serverCount ):
"Test this setup"
setLogLevel( 'info' )
topo = ClusterTopo( serverCount )
host = partial( MininetServer, ssh=True, ovs=True)
net = Mininet( topo=topo, host=host, switch=LinuxBridge, ipBase='10.0.1.0/24' )
MininetServer.updateHostsFiles( net.hosts )
# addNAT().configDefault() also connects root namespace to Mininet
net.addNAT().configDefault()
net.start()
CLI( net )
net.stop()
hosts = { 'mn': MininetServer }
links = { 'mn': ServerLink }
if __name__ == '__main__':
n = 12 if len( argv ) != 2 else int( argv[ 1 ] )
test( n )
+2 -1
View File
@@ -5,7 +5,8 @@ This example shows how to add an interface (for example a real
hardware interface) to a network after the network is created.
"""
import re, sys
import re
import sys
from mininet.cli import CLI
from mininet.log import setLogLevel, info, error
+3 -3
View File
@@ -21,7 +21,7 @@ def intfOptions():
link1 = net.addLink( h1, s1, cls=TCLink )
net.addLink( h2, s1 )
net.start()
# flush out latency from reactive forwarding delay
net.pingAll()
@@ -34,12 +34,12 @@ def intfOptions():
link1.intf1.config( loss=50 )
info( '\n' )
net.iperf( ( h1, h2 ), l4Type='UDP' )
info( '\n*** Configuring one intf with delay of 15ms\n' )
link1.intf1.config( delay='15ms' )
info( '\n*** Run a ping to confirm delay\n' )
net.pingPairFull()
info( '\n*** Done testing\n' )
net.stop()
+4 -2
View File
@@ -27,9 +27,11 @@ def limit( bw=10, cpu=.1 ):
info( '*** Testing with', sched, 'bandwidth limiting\n' )
if sched == 'rt':
release = quietRun( 'uname -r' ).strip('\r\n')
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s'
% release )
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
info( '*** RT Scheduler is not enabled in your kernel. Skipping this test\n' )
info( '*** RT Scheduler is not enabled in your kernel. '
'Skipping this test\n' )
continue
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
net = Mininet( topo=myTopo, intf=intf, host=host )
+45 -23
View File
@@ -2,24 +2,29 @@
"""
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)
The example topology creates a router and three IP subnets:
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)
- 192.168.1.0/24 (r0-eth1, IP: 192.168.1.1)
- 172.16.0.0/12 (r0-eth2, IP: 172.16.0.1)
- 10.0.0.0/8 (r0-eth3, IP: 10.0.0.1)
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.
Each subnet consists of a single host connected to
a single switch:
r0-eth1 - s1-eth1 - h1-eth0 (IP: 192.168.1.100)
r0-eth2 - s2-eth1 - h2-eth0 (IP: 172.16.0.100)
r0-eth3 - s3-eth1 - h3-eth0 (IP: 10.0.0.100)
The example relies on default routing entries that are
automatically created for each router interface, as well
as 'defaultRoute' parameters for the host interfaces.
Additional routes may be added to the router or hosts by
executing 'ip route' or 'route' commands on the router or hosts.
"""
from mininet.topo import Topo
@@ -42,22 +47,39 @@ class LinuxRouter( Node ):
class NetworkTopo( Topo ):
"A simple topology of a router with three subnets (one host in each)."
"A LinuxRouter connecting three IP subnets"
def build( self, **_opts ):
defaultIP = '192.168.1.1/24' # IP address for r0-eth1
router = self.addNode( 'r0', cls=LinuxRouter, ip=defaultIP )
s1, s2, s3 = [ self.addSwitch( s ) for s in 's1', 's2', 's3' ]
self.addLink( s1, router, intfName2='r0-eth1',
params2={ 'ip' : defaultIP } ) # for clarity
self.addLink( s2, router, intfName2='r0-eth2',
params2={ 'ip' : '172.16.0.1/12' } )
self.addLink( s3, router, intfName2='r0-eth3',
params2={ 'ip' : '10.0.0.1/8' } )
h1 = self.addHost( 'h1', ip='192.168.1.100/24',
defaultRoute='via 192.168.1.1' )
h2 = self.addHost( 'h2', ip='172.16.0.100/12',
defaultRoute='via 172.16.0.1' )
h3 = self.addHost( 'h3', ip='10.0.0.100/8',
defaultRoute='via 10.0.0.1' )
for h, s in [ (h1, s1), (h2, s2), (h3, s3) ]:
self.addLink( h, s )
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():
"Test linux router"
topo = NetworkTopo()
net = Mininet( topo=topo, controller=None ) # no controller needed
net = Mininet( topo=topo ) # controller is used by s1-s3
net.start()
info( '*** Routing Table on Router\n' )
info( '*** Routing Table on Router:\n' )
print net[ 'r0' ].cmd( 'route' )
CLI( net )
net.stop()
+893 -865
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -59,7 +59,7 @@ class MobilitySwitch( OVSSwitch ):
def validatePort( self, intf ):
"Validate intf's OF port number"
ofport = int( self.cmd( 'ovs-vsctl get Interface', intf,
'ofport' ) )
'ofport' ) )
if ofport != self.ports[ intf ]:
warn( 'WARNING: ofport for', intf, 'is actually', ofport,
'\n' )
+4 -3
View File
@@ -9,9 +9,9 @@ from mininet.cli import CLI
from mininet.log import setLogLevel
from mininet.net import Mininet
from mininet.topo import Topo
def runMultiLink():
"Create and run multiple link network"
topo = simpleMultiLinkTopo( n=2 )
net = Mininet( topo=topo )
net.start()
@@ -19,13 +19,14 @@ def runMultiLink():
net.stop()
class simpleMultiLinkTopo( Topo ):
"Simple topology with multiple links"
def __init__( self, n, **kwargs ):
Topo.__init__( self, **kwargs )
h1, h2 = self.addHost( 'h1' ), self.addHost( 'h2' )
s1 = self.addSwitch( 's1' )
for _ in range( n ):
self.addLink( s1, h1 )
self.addLink( s1, h2 )
+3 -93
View File
@@ -2,11 +2,6 @@
"""
Example to create a Mininet topology and connect it to the internet via NAT
through eth0 on the host.
Glen Gibb, February 2011
(slight modifications by BL, 5/13)
"""
from mininet.cli import CLI
@@ -14,99 +9,14 @@ from mininet.log import lg
from mininet.node import Node
from mininet.topolib import TreeNet
#################################
def startNAT( root, inetIntf='eth0', subnet='10.0/8' ):
"""Start NAT/forwarding between Mininet and external network
root: node to access iptables from
inetIntf: interface for internet access
subnet: Mininet subnet (default 10.0/8)="""
# Identify the interface connecting to the mininet network
localIntf = root.defaultIntf()
# Flush any currently active rules
root.cmd( 'iptables -F' )
root.cmd( 'iptables -t nat -F' )
# Create default entries for unmatched traffic
root.cmd( 'iptables -P INPUT ACCEPT' )
root.cmd( 'iptables -P OUTPUT ACCEPT' )
root.cmd( 'iptables -P FORWARD DROP' )
# Configure NAT
root.cmd( 'iptables -I FORWARD -i', localIntf, '-d', subnet, '-j DROP' )
root.cmd( 'iptables -A FORWARD -i', localIntf, '-s', subnet, '-j ACCEPT' )
root.cmd( 'iptables -A FORWARD -i', inetIntf, '-d', subnet, '-j ACCEPT' )
root.cmd( 'iptables -t nat -A POSTROUTING -o ', inetIntf, '-j MASQUERADE' )
# Instruct the kernel to perform forwarding
root.cmd( 'sysctl net.ipv4.ip_forward=1' )
def stopNAT( root ):
"""Stop NAT/forwarding between Mininet and external network"""
# Flush any currently active rules
root.cmd( 'iptables -F' )
root.cmd( 'iptables -t nat -F' )
# Instruct the kernel to stop forwarding
root.cmd( 'sysctl net.ipv4.ip_forward=0' )
def fixNetworkManager( root, intf ):
"""Prevent network-manager from messing with our interface,
by specifying manual configuration in /etc/network/interfaces
root: a node in the root namespace (for running commands)
intf: interface name"""
cfile = '/etc/network/interfaces'
line = '\niface %s inet manual\n' % intf
config = open( cfile ).read()
if line not in config:
print '*** Adding', line.strip(), 'to', cfile
with open( cfile, 'a' ) as f:
f.write( line )
# Probably need to restart network-manager to be safe -
# hopefully this won't disconnect you
root.cmd( 'service network-manager restart' )
def connectToInternet( network, switch='s1', rootip='10.254', subnet='10.0/8'):
"""Connect the network to the internet
switch: switch to connect to root namespace
rootip: address for interface in root namespace
subnet: Mininet subnet"""
switch = network.get( switch )
prefixLen = subnet.split( '/' )[ 1 ]
# Create a node in root namespace
root = Node( 'root', inNamespace=False )
# Prevent network-manager from interfering with our interface
fixNetworkManager( root, 'root-eth0' )
# Create link between root NS and switch
link = network.addLink( root, switch )
link.intf1.setIP( rootip, prefixLen )
# Start network that now includes link to root namespace
network.start()
# Start NAT and establish forwarding
startNAT( root )
# Establish routes from end hosts
for host in network.hosts:
host.cmd( 'ip route flush root 0/0' )
host.cmd( 'route add -net', subnet, 'dev', host.defaultIntf() )
host.cmd( 'route add default gw', rootip )
return root
if __name__ == '__main__':
lg.setLogLevel( 'info')
net = TreeNet( depth=1, fanout=4 )
# Configure and start NATted connectivity
rootnode = connectToInternet( net )
# Add NAT connectivity
net.addNAT().configDefault()
net.start()
print "*** Hosts are running and should have internet connectivity"
print "*** Type 'exit' or control-D to shut down network"
CLI( net )
# Shut down NAT
stopNAT( rootnode )
net.stop()
+5 -6
View File
@@ -14,7 +14,7 @@ natnet.py: Example network with NATs
| |
s1 s2
| |
h1 h2
h1 h2
"""
@@ -27,7 +27,7 @@ from mininet.util import irange
class InternetTopo(Topo):
"Single switch connected to n hosts."
def __init__(self, n=2, h=1, **opts):
def __init__(self, n=2, **opts):
Topo.__init__(self, **opts)
# set up inet switch
@@ -44,15 +44,15 @@ class InternetTopo(Topo):
localSubnet = '192.168.%d.0/24' % i
natParams = { 'ip' : '%s/24' % localIP }
# add NAT to topology
nat = self.addNode('nat%d' % i, cls=NAT, subnet=localSubnet,
nat = self.addNode('nat%d' % i, cls=NAT, subnet=localSubnet,
inetIntf=inetIntf, localIntf=localIntf)
switch = self.addSwitch('s%d' % i)
# connect NAT to inet and local switches
self.addLink(nat, inetSwitch, intfName1=inetIntf)
self.addLink(nat, switch, intfName1=localIntf, params1=natParams)
# add host and connect to local switch
host = self.addHost('h%d' % i,
ip='192.168.%d.100/24' % i,
host = self.addHost('h%d' % i,
ip='192.168.%d.100/24' % i,
defaultRoute='via %s' % localIP)
self.addLink(host, switch)
@@ -67,4 +67,3 @@ def run():
if __name__ == '__main__':
setLogLevel('info')
run()
+17 -12
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
"""
Create a network with 5 hosts, numbered 1-4 and 9.
Create a network with 5 hosts, numbered 1-4 and 9.
Validate that the port numbers match to the interface name,
and that the ovs ports match the mininet ports.
"""
@@ -13,16 +13,19 @@ from mininet.log import setLogLevel, info, warn
def validatePort( switch, intf ):
"Validate intf's OF port number"
ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf,
'ofport' ) )
'ofport' ) )
if ofport != switch.ports[ intf ]:
warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' )
return 0
else:
return 1
def net():
def testPortNumbering():
"Create a network with 5 hosts."
"""Test port numbering:
Create a network with 5 hosts (using Mininet's
mid-level API) and check that implicit and
explicit port numbering works as expected."""
net = Mininet( controller=Controller )
@@ -45,22 +48,25 @@ def net():
net.addLink( h2, s1 )
net.addLink( h3, s1 )
net.addLink( h4, s1 )
net.addLink( h5, s1, port1 = 1, port2 = 9 ) # specify a different port to connect host 5 to on the switch.
# specify a different port to connect host 5 to on the switch.
net.addLink( h5, s1, port1=1, port2= 9)
info( '*** Starting network\n' )
net.start()
# print the interfaces and their port numbers
info( '\n*** printing and validating the ports running on each interface\n' )
info( '\n*** printing and validating the ports '
'running on each interface\n' )
for intfs in s1.intfList():
if not intfs.name == "lo":
info( intfs, ': ', s1.ports[intfs],
'\n' )
info ( 'Validating that', intfs, 'is actually on port', s1.ports[intfs], '... ' )
info( intfs, ': ', s1.ports[intfs],
'\n' )
info( 'Validating that', intfs,
'is actually on port', s1.ports[intfs], '... ' )
if validatePort( s1, intfs ):
info( 'Validated.\n' )
print '\n'
# test the network with pingall
net.pingAll()
print '\n'
@@ -70,5 +76,4 @@ def net():
if __name__ == '__main__':
setLogLevel( 'info' )
net()
testPortNumbering()
+2 -2
View File
@@ -33,8 +33,8 @@ def perfTest():
"Create network and run simple performance test"
topo = SingleSwitchTopo( n=4 )
net = Mininet( topo=topo,
host=CPULimitedHost, link=TCLink,
autoStaticArp=True )
host=CPULimitedHost, link=TCLink,
autoStaticArp=True )
net.start()
print "Dumping host connections"
dumpNodeConnections(net.hosts)
+3 -4
View File
@@ -23,7 +23,6 @@ from mininet.cli import CLI
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 ):
@@ -39,7 +38,7 @@ def connectToRootNS( network, switch, ip, routes ):
routes: host networks to route to"""
# Create a node in root namespace and link to switch 0
root = Node( 'root', inNamespace=False )
intf = Link( root, switch ).intf1
intf = network.addLink( root, switch ).intf1
root.setIP( ip, intf=intf )
# Start network that now includes link to root namespace
network.start()
@@ -81,6 +80,6 @@ if __name__ == '__main__':
net = TreeNet( depth=1, fanout=4 )
# get sshd args from the command line or use default args
# useDNS=no -u0 to avoid reverse DNS lookup timeout
opts = ' '.join( sys.argv[ 1: ] ) if len( sys.argv ) > 1 else (
argvopts = ' '.join( sys.argv[ 1: ] ) if len( sys.argv ) > 1 else (
'-D -o UseDNS=no -u0' )
sshd( net, opts=opts )
sshd( net, opts=argvopts )
+1 -1
View File
@@ -36,7 +36,7 @@ class testBareSSHD( unittest.TestCase ):
'-o StrictModes=no' )
p = pexpect.spawn( cmd )
runOpts = [ 'You may now ssh into h1 at 10.0.0.1',
'after 5 seconds, h1 is not listening on port 22',
'after 5 seconds, h1 is not listening on port 22',
pexpect.EOF, pexpect.TIMEOUT ]
while True:
index = p.expect( runOpts )
+3 -3
View File
@@ -6,7 +6,7 @@ Test for cpu.py
results format:
sched cpu client MB/s
cfs 45.00% 13254.669841
cfs 40.00% 11822.441399
cfs 30.00% 5112.963009
@@ -28,13 +28,13 @@ class testCPU( unittest.TestCase ):
"Verify that CPU utilization is monotonically decreasing for each scheduler"
p = pexpect.spawn( 'python -m mininet.examples.cpu' )
# matches each line from results( shown above )
opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.]+)',
opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.]+)',
pexpect.EOF ]
scheds = []
while True:
index = p.expect( opts, timeout=600 )
if index == 0:
sched = p.match.group( 1 )
sched = p.match.group( 1 )
cpu = float( p.match.group( 2 ) )
bw = float( p.match.group( 3 ) )
if sched not in scheds:
+1 -1
View File
@@ -19,7 +19,7 @@ class testEmptyNet( unittest.TestCase ):
p.sendline( 'pingall' )
p.expect ( '(\d+)% dropped' )
percent = int( p.match.group( 1 ) ) if p.match else -1
self.assertEqual( percent, 0 )
self.assertEqual( percent, 0 )
p.expect( self.prompt )
# iperf test
p.sendline( 'iperf' )
+2 -2
View File
@@ -14,8 +14,8 @@ class testLimit( unittest.TestCase ):
def testLimit( self ):
"Verify that CPU limits are within a 2% tolerance of limit for each scheduler"
p = pexpect.spawn( 'python -m mininet.examples.limit' )
opts = [ '\*\*\* Testing network ([\d\.]+) Mbps',
'\*\*\* Results: \[([\d\., ]+)\]',
opts = [ '\*\*\* Testing network ([\d\.]+) Mbps',
'\*\*\* Results: \[([\d\., ]+)\]',
pexpect.EOF ]
count = 0
bw = 0
+2 -2
View File
@@ -15,8 +15,8 @@ class testLinearBandwidth( unittest.TestCase ):
"Verify that bandwidth is monotonically decreasing as # of hops increases"
p = pexpect.spawn( 'python -m mininet.examples.linearbandwidth' )
count = 0
opts = [ '\*\*\* Linear network results',
'(\d+)\s+([\d\.]+) (.bits)',
opts = [ '\*\*\* Linear network results',
'(\d+)\s+([\d\.]+) (.bits)',
pexpect.EOF ]
while True:
index = p.expect( opts, timeout=600 )
+2 -2
View File
@@ -22,14 +22,14 @@ class testMultiLink( unittest.TestCase ):
hostToIntfs = intfsOutput.split( '\r\n' )[ 1:3 ]
intfList = []
for hostToIntf in hostToIntfs:
intfList += [ intf for intf in
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 )
+1 -1
View File
@@ -11,7 +11,7 @@ from collections import defaultdict
class testMultiPing( unittest.TestCase ):
def testMultiPing( self ):
"""Verify that each target is pinged at least once, and
"""Verify that each target is pinged at least once, and
that pings to 'real' targets are successful and unknown targets fail"""
p = pexpect.spawn( 'python -m mininet.examples.multiping' )
opts = [ "Host (h\d+) \(([\d.]+)\) will be pinging ips: ([\d\. ]+)",
+1 -1
View File
@@ -14,7 +14,7 @@ class testNAT( unittest.TestCase ):
prompt = 'mininet>'
@unittest.skipIf( '0 received' in quietRun( 'ping -c 1 %s' % destIP ),
@unittest.skipIf( '0 received' in quietRun( 'ping -c 1 %s' % destIP ),
'Destination IP is not reachable' )
def testNAT( self ):
"Attempt to ping an IP on the Internet and verify 0% packet loss"
+3 -3
View File
@@ -15,8 +15,8 @@ class testNumberedports( unittest.TestCase ):
def testConsistency( self ):
"""verify consistency between mininet and ovs ports"""
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.',
'Validating that s1-eth\d is actually on port \d ... WARNING',
opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.',
'Validating that s1-eth\d is actually on port \d ... WARNING',
pexpect.EOF ]
correct_ports = True
count = 0
@@ -34,7 +34,7 @@ class testNumberedports( unittest.TestCase ):
def testNumbering( self ):
"""verify that all of the port numbers are printed correctly and consistent with their interface"""
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
opts = [ 's1-eth(\d+) : (\d+)',
opts = [ 's1-eth(\d+) : (\d+)',
pexpect.EOF ]
count_intfs = 0
while True:
+2 -2
View File
@@ -14,7 +14,7 @@ class testSSHD( unittest.TestCase ):
def connected( self, ip ):
"Log into ssh server, check banner, then exit"
# Note: this test will fail if "Welcome" is not in the sshd banner
# Note: this test will fail if "Welcome" is not in the sshd banner
# and '#'' or '$'' are not in the prompt
p = pexpect.spawn( 'ssh -i /tmp/ssh/test_rsa %s' % ip, timeout=10 )
while True:
@@ -26,7 +26,7 @@ class testSSHD( unittest.TestCase ):
return False
elif index == 2:
p.sendline( 'exit' )
p.wait()
p.wait()
return True
else:
return False
+1 -1
View File
@@ -47,4 +47,4 @@ class testVLANHost( unittest.TestCase ):
self.assertEqual( i, 0 ) # check vlan intf is present
if __name__ == '__main__':
unittest.main()
unittest.main()
+2 -2
View File
@@ -9,10 +9,10 @@ and running sysctl -p. Check util/sysctl_addon.
from mininet.cli import CLI
from mininet.log import setLogLevel
from mininet.node import OVSKernelSwitch
from mininet.node import OVSSwitch
from mininet.topolib import TreeNet
if __name__ == '__main__':
setLogLevel( 'info' )
network = TreeNet( depth=2, fanout=32, switch=OVSKernelSwitch )
network = TreeNet( depth=2, fanout=32, switch=OVSSwitch )
network.run( CLI, network )
+11 -12
View File
@@ -30,17 +30,18 @@ from mininet.util import quietRun
from mininet.log import error
class VLANHost( Host ):
"Host connected to VLAN interface"
def config( self, vlan=100, **params ):
def config( self, vlan=100, **params ):
"""Configure VLANHost according to (optional) parameters:
vlan: VLAN ID for default interface"""
r = super( Host, self ).config( **params )
r = super( VLANHost, self ).config( **params )
intf = self.defaultIntf()
# remove IP from default, "physical" interface
self.cmd( 'ifconfig %s inet 0' % intf )
# create VLAN interface
# create VLAN interface
self.cmd( 'vconfig add %s %d' % ( intf, vlan ) )
# assign the host's IP to the VLAN interface
self.cmd( 'ifconfig %s.%d inet %s' % ( intf, vlan, params['ip'] ) )
@@ -69,6 +70,8 @@ def exampleAllHosts( vlan ):
CLI( net )
net.stop()
# pylint: disable=arguments-differ
class VLANStarTopo( Topo ):
"""Example topology that uses host in multiple VLANs
@@ -90,7 +93,7 @@ class VLANStarTopo( Topo ):
self.addLink( h, s1 )
def exampleCustomTags( vlan ):
def exampleCustomTags():
"""Simple example that exercises VLANStarTopo"""
net = Mininet( topo=VLANStarTopo() )
@@ -110,16 +113,12 @@ if __name__ == '__main__':
setLogLevel( 'info' )
if not quietRun( 'which vconfig' ):
error( "Cannot find command 'vconfig'\nThe packge",
error( "Cannot find command 'vconfig'\nThe package",
"'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 )
if len( sys.argv ) >= 2:
exampleAllHosts( vlan=int( sys.argv[ 1 ] ) )
else:
exampleCustomTags( vlan )
exampleCustomTags()
+85 -55
View File
@@ -10,7 +10,8 @@ It may also get rid of 'false positives', but hopefully
nothing irreplaceable!
"""
from subprocess import Popen, PIPE, check_output as co
from subprocess import ( Popen, PIPE, check_output as co,
CalledProcessError )
import time
from mininet.log import info
@@ -24,73 +25,102 @@ def sh( cmd ):
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 = ''
pids = co( [ 'pgrep', '-f', pattern ] ).split( '\n' )
except CalledProcessError:
pids = []
# Don't kill init
pids = [ pid for pid in pids if pid and pid != '1' ]
if pids:
sh( 'pkill -f 9 mininet:' )
sh( "kill -9 %s" % ' '.join( pids ) )
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!"""
class Cleanup( object ):
"Wrapper for cleanup()"
info("*** Removing excess controllers/ofprotocols/ofdatapaths/pings/noxes"
"\n")
zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
zombies += 'ovs-openflowd ovs-controller udpbwtest mnexec ivs'
# Note: real zombie processes can't actually be killed, since they
# are already (un)dead. Then again,
# you can't connect to them either, so they're mostly harmless.
# Send SIGTERM first to give processes a chance to shutdown cleanly.
sh( 'killall ' + zombies + ' 2> /dev/null' )
time.sleep( 1 )
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
callbacks = []
# And kill off sudo mnexec
sh( 'pkill -9 -f "sudo mnexec"')
@classmethod
def cleanup( cls):
"""Clean up junk which might be left over from old runs;
do fast stuff before slow dp and link removal!"""
info( "*** Removing junk from /tmp\n" )
sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' )
info( "*** Removing excess controllers/ofprotocols/ofdatapaths/"
"pings/noxes\n" )
zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
zombies += 'ovs-openflowd ovs-controller udpbwtest mnexec ivs'
# Note: real zombie processes can't actually be killed, since they
# are already (un)dead. Then again,
# you can't connect to them either, so they're mostly harmless.
# Send SIGTERM first to give processes a chance to shutdown cleanly.
sh( 'killall ' + zombies + ' 2> /dev/null' )
time.sleep( 1 )
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
info( "*** Removing old X11 tunnels\n" )
cleanUpScreens()
# And kill off sudo mnexec
sh( 'pkill -9 -f "sudo mnexec"')
info( "*** Removing excess kernel datapaths\n" )
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines()
for dp in dps:
if dp:
sh( 'dpctl deldp ' + dp )
info( "*** Removing junk from /tmp\n" )
sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' )
info( "*** Removing OVS datapaths" )
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
if dps:
sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp
for dp in dps if dp ) )
# And in case the above didn't work...
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
for dp in dps:
sh( 'ovs-vsctl del-br ' + dp )
info( "*** Removing old X11 tunnels\n" )
cleanUpScreens()
info( "*** Removing all links of the pattern foo-ethX\n" )
links = sh( "ip link show | "
"egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines()
for link in links:
if link:
sh( "ip link del " + link )
info( "*** Removing excess kernel datapaths\n" )
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'"
).splitlines()
for dp in dps:
if dp:
sh( 'dpctl deldp ' + dp )
info( "*** Killing stale mininet node processes\n" )
killprocs( 'mininet:' )
info( "*** Removing OVS datapaths\n" )
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
if dps:
sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp
for dp in dps if dp ) )
# And in case the above didn't work...
dps = sh( "ovs-vsctl --timeout=1 list-br" ).strip().splitlines()
for dp in dps:
sh( 'ovs-vsctl del-br ' + dp )
info ( "*** Shutting down stale tunnels\n" )
killprocs( 'Tunnel=Ethernet' )
killprocs( '.ssh/mn')
sh( 'rm -f ~/.ssh/mn/*' )
info( "*** Cleanup complete.\n" )
info( "*** Removing all links of the pattern foo-ethX\n" )
links = sh( "ip link show | "
"egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'"
).splitlines()
# Delete blocks of links
n = 1000 # chunk size
for i in xrange( 0, len( links ), n ):
cmd = ';'.join( 'ip link del %s' % link
for link in links[ i : i + n ] )
sh( '( %s ) 2> /dev/null' % cmd )
if 'tap9' in sh( 'ip link show' ):
info( "*** Removing tap9 - assuming it's from cluster edition\n" )
sh( 'ip link del tap9' )
info( "*** Killing stale mininet node processes\n" )
killprocs( 'mininet:' )
info( "*** Shutting down stale tunnels\n" )
killprocs( 'Tunnel=Ethernet' )
killprocs( '.ssh/mn')
sh( 'rm -f ~/.ssh/mn/*' )
# Call any additional cleanup code if necessary
for callback in cls.callbacks:
callback()
info( "*** Cleanup complete.\n" )
@classmethod
def addCleanupCallback( cls, callback ):
"Add cleanup callback"
if callback not in cls.callbacks:
cls.callbacks.append( callback )
cleanup = Cleanup.cleanup
addCleanupCallback = Cleanup.addCleanupCallback
+51 -29
View File
@@ -37,7 +37,7 @@ import atexit
from mininet.log import info, output, error
from mininet.term import makeTerms, runX11
from mininet.util import ( quietRun, dumpNodeConnections,
dumpPorts )
dumpPorts )
class CLI( Cmd ):
"Simple command-line interface to talk to nodes."
@@ -45,6 +45,10 @@ class CLI( Cmd ):
prompt = 'mininet> '
def __init__( self, mininet, stdin=sys.stdin, script=None ):
"""Start and run interactive or batch mode CLI
mininet: Mininet network object
stdin: standard input for CLI
script: script to run in batch mode"""
self.mn = mininet
# Local variable bindings for py command
self.locals = { 'net': mininet }
@@ -56,33 +60,54 @@ class CLI( Cmd ):
Cmd.__init__( self )
info( '*** Starting CLI:\n' )
# Set up history if readline is available
try:
import readline
except ImportError:
pass
else:
history_path = os.path.expanduser('~/.mininet_history')
if os.path.isfile(history_path):
readline.read_history_file(history_path)
atexit.register(lambda: readline.write_history_file(history_path))
if self.inputFile:
self.do_source( self.inputFile )
return
self.initReadline()
self.run()
readlineInited = False
@classmethod
def initReadline( cls ):
"Set up history if readline is available"
# Only set up readline once to prevent multiplying the history file
if cls.readlineInited:
return
cls.readlineInited = True
try:
from readline import read_history_file, write_history_file
except ImportError:
pass
else:
history_path = os.path.expanduser( '~/.mininet_history' )
if os.path.isfile( history_path ):
read_history_file( history_path )
atexit.register( lambda: write_history_file( history_path ) )
def run( self ):
"Run our cmdloop(), catching KeyboardInterrupt"
while True:
try:
# Make sure no nodes are still waiting
for node in self.mn.values():
while node.waiting:
info( 'stopping', node, '\n' )
node.sendInt()
node.waitOutput()
if self.isatty():
quietRun( 'stty echo sane intr "^C"' )
quietRun( 'stty echo sane intr ^C' )
self.cmdloop()
break
except KeyboardInterrupt:
output( '\nInterrupt\n' )
# Output a message - unless it's also interrupted
# pylint: disable=broad-except
try:
output( '\nInterrupt\n' )
except Exception:
pass
# pylint: enable=broad-except
def emptyline( self ):
"Don't repeat last command when you hit return."
@@ -93,11 +118,6 @@ class CLI( Cmd ):
self.locals.update( self.mn )
return self.locals
# Disable pylint "Unused argument: 'arg's'" messages, as well as
# "method could be a function" warning, since each CLI function
# must have the same interface
# pylint: disable-msg=R0201
helpStr = (
'You may also send a command to a node using:\n'
' <node> command {args}\n'
@@ -128,7 +148,7 @@ class CLI( Cmd ):
nodes = ' '.join( sorted( self.mn ) )
output( 'available nodes are: \n%s\n' % nodes )
def do_ports( self, line ):
def do_ports( self, _line ):
"display ports and interfaces for each switch"
dumpPorts( self.mn.switches )
@@ -139,10 +159,11 @@ class CLI( Cmd ):
def do_sh( self, line ):
"""Run an external shell command
Usage: sh [cmd args]"""
assert self # satisfy pylint and allow override
call( line, shell=True )
# do_py() and do_px() need to catch any exception during eval()/exec()
# pylint: disable-msg=W0703
# pylint: disable=broad-except
def do_py( self, line ):
"""Evaluate a Python expression.
@@ -159,7 +180,7 @@ class CLI( Cmd ):
output( str( e ) + '\n' )
# We are in fact using the exec() pseudo-function
# pylint: disable-msg=W0122
# pylint: disable=exec-used
def do_px( self, line ):
"""Execute a Python statement.
@@ -169,7 +190,7 @@ class CLI( Cmd ):
except Exception, e:
output( str( e ) + '\n' )
# pylint: enable-msg=W0703,W0122
# pylint: enable=broad-except,exec-used
def do_pingall( self, line ):
"Ping between all hosts."
@@ -284,6 +305,7 @@ class CLI( Cmd ):
def do_exit( self, _line ):
"Exit"
assert self # satisfy pylint and allow override
return 'exited by user command'
def do_quit( self, line ):
@@ -346,7 +368,7 @@ class CLI( Cmd ):
elapsed = time.time() - start
self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
def do_links( self, line ):
def do_links( self, _line ):
"Report on links"
for link in self.mn.links:
print link, link.status()
@@ -355,11 +377,12 @@ class CLI( Cmd ):
"Starts or stops a switch"
args = line.split()
if len(args) != 2:
error( 'invalid number of args: switch <switch name> {start, stop}\n' )
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 :
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 ]
@@ -369,7 +392,8 @@ class CLI( Cmd ):
elif command == 'stop':
self.mn.get( sw ).stop( deleteIntfs=False )
else:
error( 'invalid command: switch <switch name> {start, stop}\n' )
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.
@@ -397,8 +421,6 @@ class CLI( Cmd ):
else:
error( '*** Unknown command: %s\n' % line )
# pylint: enable-msg=R0201
def waitForNode( self, node ):
"Wait for a node to finish, and print its output."
# Pollers
+97 -22
View File
@@ -26,6 +26,7 @@ Link: basic link class for creating veth pairs
from mininet.log import info, error, debug
from mininet.util import makeIntfPair, quietRun
import mininet.node
import re
class Intf( object ):
@@ -33,7 +34,7 @@ class Intf( object ):
"Basic interface object that can configure itself."
def __init__( self, name, node=None, port=None, link=None,
mac=None, srcNode=None, **params ):
mac=None, **params ):
"""name: interface name (e.g. h1-eth0)
node: owning node (where this intf most likely lives)
link: parent link if we're part of a link
@@ -43,13 +44,17 @@ class Intf( object ):
self.link = link
self.mac = mac
self.ip, self.prefixLen = None, None
# if interface is lo, we know the ip is 127.0.0.1.
# This saves an ifconfig command per node
if self.name == 'lo':
self.ip = '127.0.0.1'
# Add to node (and move ourselves if necessary )
node.addIntf( self, port=port )
moveIntfFn = params.pop( 'moveIntfFn', None )
if moveIntfFn:
node.addIntf( self, port=port, moveIntfFn=moveIntfFn )
else:
node.addIntf( self, port=port )
# Save params for future reference
self.params = params
self.config( **params )
@@ -71,7 +76,8 @@ class Intf( object ):
return self.ifconfig( ipstr, 'up' )
else:
if prefixLen is None:
raise Exception( 'No prefix length set for IP address %s' % ( ipstr, ) )
raise Exception( 'No prefix length set for IP address %s'
% ( ipstr, ) )
self.ip, self.prefixLen = ipstr, prefixLen
return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
@@ -90,7 +96,8 @@ class Intf( object ):
"Return updated IP address based on ifconfig"
# use pexec instead of node.cmd so that we dont read
# backgrounded output from the cli.
ifconfig, _err, _exitCode = self.node.pexec( 'ifconfig %s' % self.name )
ifconfig, _err, _exitCode = self.node.pexec(
'ifconfig %s' % self.name )
ips = self._ipMatchRegex.findall( ifconfig )
self.ip = ips[ 0 ] if ips else None
return self.ip
@@ -190,13 +197,14 @@ class Intf( object ):
def delete( self ):
"Delete interface"
self.cmd( 'ip link del ' + self.name )
if self.node.inNamespace:
# Link may have been dumped into root NS
quietRun( 'ip link del ' + self.name )
# We used to do this, but it slows us down:
# if self.node.inNamespace:
# 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' )
links, _err, _result = self.node.pexec( 'ip link show' )
if self.name in links:
return "OK"
else:
@@ -214,15 +222,19 @@ class TCIntf( Intf ):
Allows specification of bandwidth limits (various methods)
as well as delay, loss and max queue length"""
# The parameters we use seem to work reasonably up to 1 Gb/sec
# For higher data rates, we will probably need to change them.
bwParamMax = 1000
def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
latency_ms=None, enable_ecn=False, enable_red=False ):
"Return tc commands to set bandwidth"
cmds, parent = [], ' root '
if bw and ( bw < 0 or bw > 1000 ):
error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
if bw and ( bw < 0 or bw > self.bwParamMax ):
error( 'Bandwidth limit', bw, 'is outside supported range 0..%d'
% self.bwParamMax, '- ignoring\n' )
elif bw is not None:
# BL: this seems a bit brittle...
if ( speedup > 0 and
@@ -332,8 +344,9 @@ class TCIntf( Intf ):
# Delay/jitter/loss/max_queue_size using netem
delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
loss=loss, max_queue_size=max_queue_size,
parent=parent )
loss=loss,
max_queue_size=max_queue_size,
parent=parent )
cmds += delaycmds
# Ugly but functional: display configuration info
@@ -364,10 +377,11 @@ class Link( object ):
"""A basic link is just a veth pair.
Other types of links could be tunnels, link emulators, etc.."""
# pylint: disable=too-many-branches
def __init__( self, node1, node2, port1=None, port2=None,
intfName1=None, intfName2=None, addr1=None, addr2=None,
intf=Intf, cls1=None, cls2=None, params1=None,
params2=None ):
params2=None, fast=True ):
"""Create veth link to another node, making two new interfaces.
node1: first node
node2: second node
@@ -402,7 +416,14 @@ class Link( object ):
if not intfName2:
intfName2 = self.intfName( node2, params2[ 'port' ] )
self.makeIntfPair( intfName1, intfName2, addr1, addr2 )
self.fast = fast
if fast:
params1.setdefault( 'moveIntfFn', self._ignore )
params2.setdefault( 'moveIntfFn', self._ignore )
self.makeIntfPair( intfName1, intfName2, addr1, addr2,
node1, node2, deleteIntfs=False )
else:
self.makeIntfPair( intfName1, intfName2, addr1, addr2 )
if not cls1:
cls1 = intf
@@ -416,28 +437,47 @@ class Link( object ):
# All we are is dust in the wind, and our two interfaces
self.intf1, self.intf2 = intf1, intf2
# pylint: enable=too-many-branches
@staticmethod
def _ignore( *args, **kwargs ):
"Ignore any arguments"
pass
def intfName( self, node, n ):
"Construct a canonical interface name node-ethN for interface n."
# Leave this as an instance method for now
assert self
return node.name + '-eth' + repr( n )
@classmethod
def makeIntfPair( _cls, intfname1, intfname2, addr1=None, addr2=None ):
def makeIntfPair( cls, intfname1, intfname2, addr1=None, addr2=None,
node1=None, node2=None, deleteIntfs=True,
runCmd=quietRun ):
"""Create pair of interfaces
intfname1: name of interface 1
intfname2: name of interface 2
intfname1: name for interface 1
intfname2: name for interface 2
addr1: MAC address for interface 1 (optional)
addr2: MAC address for interface 2 (optional)
node1: home node for interface 1 (optional)
node2: home node for interface 2 (optional)
(override this method [and possibly delete()]
to change link type)"""
return makeIntfPair( intfname1, intfname2, addr1, addr2 )
# Leave this as a class method for now
assert cls
return makeIntfPair( intfname1, intfname2, addr1, addr2, node1, node2,
deleteIntfs=deleteIntfs, runCmd=runCmd )
def delete( self ):
"Delete this link"
self.intf1.delete()
self.intf2.delete()
# We only need to delete one side, though this doesn't seem to
# cost us much and might help subclasses.
# self.intf2.delete()
def stop( self ):
"Override to stop and clean up link as needed"
pass
self.delete()
def status( self ):
"Return link status as a string"
@@ -446,6 +486,41 @@ class Link( object ):
def __str__( self ):
return '%s<->%s' % ( self.intf1, self.intf2 )
class OVSIntf( Intf ):
"Patch interface on an OVSSwitch"
def ifconfig( self, *args ):
cmd = ' '.join( args )
if cmd == 'up':
# OVSIntf is always up
return
else:
raise Exception( 'OVSIntf cannot do ifconfig ' + cmd )
class OVSLink( Link ):
"""Link that makes patch links between OVSSwitches
Warning: in testing we have found that no more
than ~64 OVS patch links should be used in row."""
def __init__( self, node1, node2, **kwargs ):
"See Link.__init__() for options"
self.isPatchLink = False
if ( isinstance( node1, mininet.node.OVSSwitch ) and
isinstance( node2, mininet.node.OVSSwitch ) ):
self.isPatchLink = True
kwargs.update( cls1=OVSIntf, cls2=OVSIntf )
Link.__init__( self, node1, node2, **kwargs )
def makeIntfPair( self, *args, **kwargs ):
"Usually delegated to OVSSwitch"
if self.isPatchLink:
return None, None
else:
return Link.makeIntfPair( *args, **kwargs )
class TCLink( Link ):
"Link with symmetric TC interfaces configured via opts"
def __init__( self, node1, node2, port1=None, port2=None,
+3 -3
View File
@@ -124,8 +124,8 @@ class MininetLogger( Logger, object ):
self.setLevel( level )
self.handlers[ 0 ].setLevel( level )
# pylint: disable-msg=E0202
# "An attribute inherited from mininet.log hide this method"
# pylint: disable=method-hidden
# "An attribute inherited from mininet.log hide this method" (sic)
# Not sure why this is occurring - this function definitely gets called.
# See /usr/lib/python2.5/logging/__init__.py; modified from warning()
@@ -142,7 +142,7 @@ class MininetLogger( Logger, object ):
if self.isEnabledFor( OUTPUT ):
self._log( OUTPUT, msg, args, kwargs )
# pylint: enable-msg=E0202
# pylint: enable=method-hidden
lg = MininetLogger()
+82 -56
View File
@@ -98,15 +98,17 @@ from math import ceil
from mininet.cli import CLI
from mininet.log import info, error, debug, output, warn
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller
from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
Controller )
from mininet.nodelib import NAT
from mininet.link import Link, Intf
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
macColonHex, ipStr, ipParse, netParse, ipAdd,
waitListening )
from mininet.term import cleanUpScreens, makeTerms
# Mininet version: should be consistent with README and LICENSE
VERSION = "2.2.0b1"
VERSION = "2.2.1"
class Mininet( object ):
"Network emulation with hosts spawned in network namespaces."
@@ -169,7 +171,6 @@ class Mininet( object ):
if topo and build:
self.build()
def waitConnected( self, timeout=None, delay=.5 ):
"""wait for each switch to connect to a controller,
up to 5 seconds
@@ -253,25 +254,35 @@ class Mininet( object ):
if isinstance( name, Controller ):
controller_new = name
# Pylint thinks controller is a str()
# pylint: disable=E1103
# pylint: disable=maybe-no-member
name = controller_new.name
# pylint: enable=E1103
# pylint: enable=maybe-no-member
else:
controller_new = controller( name, **params )
# Add new controller to net
if controller_new: # allow controller-less setups
if controller_new: # allow controller-less setups
self.controllers.append( controller_new )
self.nameToNode[ name ] = controller_new
return controller_new
def addNAT( self, name='nat0', connect=True, inNamespace=False, **params ):
def addNAT( self, name='nat0', connect=True, inNamespace=False,
**params):
"""Add a NAT to the Mininet network
name: name of NAT node
connect: switch to connect to | True (s1) | None
inNamespace: create in a network namespace
params: other NAT node params, notably:
ip: used as default gateway address"""
nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
subnet=self.ipBase, **params )
# find first switch and create link
if connect:
# connect the nat to the first switch
if not isinstance( connect, Node ):
# Use first switch if not specified
connect = self.switches[ 0 ]
# Connect the nat to the switch
self.addLink( nat, self.switches[ 0 ] )
# set the default route on hosts
# Set the default route on hosts
natIP = nat.params[ 'ip' ].split('/')[ 0 ]
for host in self.hosts:
if host.inNamespace:
@@ -324,7 +335,7 @@ class Mininet( object ):
@staticmethod
def randMac():
"Return a random, non-multicast MAC address"
return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
0x020000000000 )
def addLink( self, node1, node2, port1=None, port2=None,
@@ -346,6 +357,8 @@ class Mininet( object ):
options.setdefault( 'port1', port1 )
if port2 is not None:
options.setdefault( 'port2', port2 )
if self.intf is not None:
options.setdefault( 'intf', self.intf )
# Set default MAC - this should probably be in Link
options.setdefault( 'addr1', self.randMac() )
options.setdefault( 'addr2', self.randMac() )
@@ -391,7 +404,7 @@ class Mininet( object ):
if not isinstance( classes, list ):
classes = [ classes ]
for i, cls in enumerate( classes ):
# Allow Controller objects because nobody understands currying
# Allow Controller objects because nobody understands partial()
if isinstance( cls, Controller ):
self.addController( cls )
else:
@@ -404,7 +417,12 @@ class Mininet( object ):
info( '\n*** Adding switches:\n' )
for switchName in topo.switches():
self.addSwitch( switchName, **topo.nodeInfo( switchName) )
# A bit ugly: add batch parameter if appropriate
params = topo.nodeInfo( switchName)
cls = params.get( 'cls', self.switch )
if hasattr( cls, 'batchStartup' ):
params.setdefault( 'batch', True )
self.addSwitch( switchName, **params )
info( switchName + ' ' )
info( '\n*** Adding links:\n' )
@@ -448,7 +466,7 @@ class Mininet( object ):
def stopXterms( self ):
"Kill each xterm."
for term in self.terms:
os.kill( term.pid, signal.SIGKILL )
term.send_signal( signal.SIGKILL )
cleanUpScreens()
def staticArp( self ):
@@ -471,6 +489,13 @@ class Mininet( object ):
for switch in self.switches:
info( switch.name + ' ')
switch.start( self.controllers )
started = {}
for swclass, switches in groupby(
sorted( self.switches, key=type ), type ):
switches = tuple( switches )
if hasattr( swclass, 'batchStartup' ):
success = swclass.batchStartup( switches )
started.update( { s: s for s in success } )
info( '\n' )
if self.waitConn:
self.waitConnected()
@@ -485,19 +510,25 @@ class Mininet( object ):
if self.terms:
info( '*** Stopping %i terms\n' % len( self.terms ) )
self.stopXterms()
info( '*** Stopping %i switches\n' % len( self.switches ) )
for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
if hasattr( swclass, 'batchShutdown' ):
swclass.batchShutdown( switches )
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:
info( '.' )
link.stop()
info( '\n' )
info( '*** Stopping %i switches\n' % len( self.switches ) )
stopped = {}
for swclass, switches in groupby(
sorted( self.switches, key=type ), type ):
switches = tuple( switches )
if hasattr( swclass, 'batchShutdown' ):
success = swclass.batchShutdown( switches )
stopped.update( { s: s for s in success } )
for switch in self.switches:
info( switch.name + ' ' )
if switch not in stopped:
switch.stop()
switch.terminate()
info( '\n' )
info( '*** Stopping %i hosts\n' % len( self.hosts ) )
for host in self.hosts:
info( host.name + ' ' )
@@ -521,13 +552,13 @@ class Mininet( object ):
if hosts is None:
hosts = self.hosts
poller = select.poll()
Node = hosts[ 0 ] # so we can call class method fdToNode
h1 = hosts[ 0 ] # so we can call class method fdToNode
for host in hosts:
poller.register( host.stdout )
while True:
ready = poller.poll( timeoutms )
for fd, event in ready:
host = Node.fdToNode( fd )
host = h1.fdToNode( fd )
if event & select.POLLIN:
line = host.readline()
if line is not None:
@@ -574,7 +605,8 @@ class Mininet( object ):
if timeout:
opts = '-W %s' % timeout
if dest.intfs:
result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
result = node.cmd( 'ping -c1 %s %s' %
(opts, dest.IP()) )
sent, received = self._parsePing( result )
else:
sent, received = 0, 0
@@ -699,52 +731,44 @@ class Mininet( object ):
# XXX This should be cleaned up
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None,
seconds=5):
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
seconds=5, port=5001):
"""Run iperf between two hosts.
hosts: list of hosts; if None, uses opposite hosts
hosts: list of hosts; if None, uses first and last hosts
l4Type: string, one of [ TCP, UDP ]
udpBw: bandwidth target for UDP test
format: iperf format argument if any
fmt: iperf format argument if any
seconds: iperf time to transmit
port: iperf port
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
if not hosts:
hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
else:
assert len( hosts ) == 2
hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
assert len( hosts ) == 2
client, server = hosts
output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
output( "%s and %s\n" % ( client.name, server.name ) )
output( '*** Iperf: testing', l4Type, 'bandwidth between',
client, 'and', server, '\n' )
server.cmd( 'killall -9 iperf' )
iperfArgs = 'iperf '
iperfArgs = 'iperf -p %d ' % port
bwArgs = ''
if l4Type == 'UDP':
iperfArgs += '-u '
bwArgs = '-b ' + udpBw + ' '
elif l4Type != 'TCP':
raise Exception( 'Unexpected l4 type: %s' % l4Type )
if format:
iperfArgs += '-f %s ' %format
server.sendCmd( iperfArgs + '-s', printPid=True )
servout = ''
while server.lastPid is None:
servout += server.monitor()
if fmt:
iperfArgs += '-f %s ' % fmt
server.sendCmd( iperfArgs + '-s' )
if l4Type == 'TCP':
while 'Connected' not in client.cmd(
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
info( 'Waiting for iperf to start up...' )
sleep(.5)
if not waitListening( client, server.IP(), port ):
raise Exception( 'Could not connect to iperf on port %d'
% port )
cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
server.IP() + ' ' + bwArgs )
debug( 'Client output: %s\n' % cliout )
server.sendInt()
servout += server.waitOutput()
servout = server.waitOutput()
debug( 'Server output: %s\n' % servout )
result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
if l4Type == 'UDP':
@@ -755,7 +779,7 @@ class Mininet( object ):
def runCpuLimitTest( self, cpu, duration=5 ):
"""run CPU limit test with 'while true' processes.
cpu: desired CPU fraction of each host
duration: test duration in seconds
duration: test duration in seconds (integer)
returns a single list of measured CPU fractions as floats.
"""
cores = int( quietRun( 'nproc' ) )
@@ -776,12 +800,14 @@ class Mininet( object ):
# get the initial cpu time for each host
for host in hosts:
outputs[ host ] = []
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
host, 'r' ) as f:
time[ host ] = float( f.read() )
for _ in range( 5 ):
for _ in range( duration ):
sleep( 1 )
for host in hosts:
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % host, 'r' ) as f:
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
host, 'r' ) as f:
readTime = float( f.read() )
outputs[ host ].append( ( ( readTime - time[ host ] )
/ 1000000000 ) / cores * 100 )
+365 -229
View File
@@ -23,20 +23,26 @@ Switch: superclass for switch nodes.
UserSwitch: a switch using the user-space switch from the OpenFlow
reference implementation.
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
implementation.
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
OVSSwitch: a switch using the Open vSwitch OpenFlow-compatible switch
implementation (openvswitch.org).
OVSBridge: an Ethernet bridge implemented using Open vSwitch.
Supports STP.
IVSSwitch: OpenFlow switch using the Indigo Virtual Switch.
Controller: superclass for OpenFlow controllers. The default controller
is controller(8) from the reference implementation.
OVSController: The test controller from Open vSwitch.
NOXController: a controller node using NOX (noxrepo.org).
Ryu: The Ryu controller (https://osrg.github.io/ryu/)
RemoteController: a remote controller node, which may use any
arbitrary OpenFlow-compatible controller, and which is not
created or managed by mininet.
created or managed by Mininet.
Future enhancements:
@@ -52,14 +58,13 @@ import re
import signal
import select
from subprocess import Popen, PIPE
from operator import or_
from time import sleep
from mininet.log import info, error, warn, debug
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
numCores, retry, mountCgroups )
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
from mininet.link import Link, Intf, TCIntf
from mininet.moduledeps import moduleDeps, pathCheck, TUN
from mininet.link import Link, Intf, TCIntf, OVSIntf
from re import findall
from distutils.version import StrictVersion
@@ -69,9 +74,9 @@ class Node( object ):
portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
def __init__( self, name, inNamespace=True, **params ):
def __init__( self, name, **params ):
"""name: name of node
inNamespace: in network namespace?
ns: private namespaces to use ['net','mnt']
privateDirs: list of private directory strings or tuples
params: Node parameters (see config() for details)"""
@@ -80,7 +85,14 @@ class Node( object ):
self.name = params.get( 'name', name )
self.privateDirs = params.get( 'privateDirs', [] )
self.inNamespace = params.get( 'inNamespace', inNamespace )
self.overlayDirs = params.get( 'overlayDirs', [] )
# Support old inNamespace param
self.ns = params.get( 'ns', ( 'net', 'mnt' ) )
inNamespace = params.get( 'inNamespace', True )
if not inNamespace:
self.ns = []
self.inNamespace = 'net' in self.ns
# Stash configuration parameters for future reference
self.params = params
@@ -97,8 +109,9 @@ class Node( object ):
self.waiting = False
self.readbuf = ''
# Start command interpreter shell
# Start command interpreter shell and mount any local dirs
self.startShell()
self.mountOverlayDirs()
self.mountPrivateDirs()
# File descriptor to node mapping support
@@ -115,22 +128,26 @@ class Node( object ):
node = cls.outToNode.get( fd )
return node or cls.inToNode.get( fd )
_marker = re.compile( chr( 1 ) + r'(\d+)\r\n' )
# Command support via shell process in namespace
def startShell( self, mnopts=None ):
"Start a shell process for running commands"
if self.shell:
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' if mnopts is None else mnopts
if self.inNamespace:
opts += 'n'
# bash -m: enable job control, i: force interactive
# mnexec: (c)lose descriptors
# (p)rint pid, and run in (n)etwork and (m)ount namespace
opts = '-cdp' if mnopts is None else mnopts
# Handle additional namespaces if specified
nsmap = { 'pid': 'P', 'mnt': 'm', 'net': 'n', 'uts': 'u' }
chars = [ nsmap.get( ns, '' ) for ns in self.ns ]
opts += ''.join( chars )
# bash -i: force interactive
# -s: pass $* to shell, and make process easy to find in ps
# prompt is set to sentinel chr( 127 )
cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ),
'bash', '--norc', '-mis', 'mininet:' + self.name ]
cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ),
'bash', '--norc', '-is', 'mininet:' + self.name ]
# Spawn a shell subprocess in a pseudo-tty, to disable buffering
# in the subprocess and insulate it from signals (e.g. SIGINT)
# received by the parent
@@ -152,17 +169,18 @@ class Node( object ):
self.lastPid = None
self.readbuf = ''
# Wait for prompt
while True:
data = self.read( 1024 )
if data[ -1 ] == chr( 127 ):
break
self.pollOut.poll()
self.waiting = False
self.cmd( 'stty -echo' )
self.cmd( 'set +m' )
self.waiting = True
self.waitOutput()
if 'P' in opts:
assert self.lastPid is not None
self.pid = self.lastPid
# +m: disable job control notification
self.cmd( 'unset HISTFILE; stty -echo; set +m' )
def mountPrivateDirs( self ):
"mount private directories"
"Mount private directories"
# Avoid expanding a string into a list of chars
assert not isinstance( self.privateDirs, basestring )
for directory in self.privateDirs:
if isinstance( directory, tuple ):
# mount given private directory
@@ -178,25 +196,76 @@ class Node( object ):
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
def unmountPrivateDirs( self ):
"mount private directories"
"Unmount private and overlay directories"
for directory in self.privateDirs:
if isinstance( directory, tuple ):
self.cmd( 'umount ', directory[ 0 ] )
else:
self.cmd( 'umount ', directory )
# XXX We should make overlayDirs as consistent as possible
# with privateDirs.
def _overlayFrom( self, entry ):
"Helper function: return mountpaint, overlay, tmpfs from entry"
if type( entry ) is str:
# '/mountpoint'
mountpoint, overlay = entry, None
elif len( entry ) is 1:
# [ '/mountpoint' ]
mountpoint, overlay = entry[ 0 ], None
else:
# [ '/mountpoint', '/overlay' ]
mountpoint, overlay = entry
tmpfs = None if overlay else '/tmp/%s/%s' % ( self, mountpoint )
return mountpoint, overlay, tmpfs
def mountOverlayDirs( self ):
"""Mount overlay directories. Overlay directories are similar
to private directories except they are copy-on-write copies
of directories in the host file system.
overlayDirs is of the form ((mountpoint,overlaydir), ...)
much like privateDirs. If overlaydir doesn't exist, we
mount a tmpfs at the specified mount point."""
# Avoid expanding a string into a list of chars
assert not isinstance( self.overlayDirs, basestring )
for entry in self.overlayDirs:
mountpoint, overlay, tmpfs = self._overlayFrom( entry )
# Create tmpfs if overlay dir is not specified
if not overlay:
overlay = tmpfs
self.cmd( 'mkdir -p', overlay )
self.cmd( 'mount -t tmpfs tmpfs', overlay )
# Mount overlay dir at mount point
self.cmd( ( 'mount -t overlayfs -o upperdir=%s,lowerdir=%s'
' overlayfs %s' ) % ( overlay, mountpoint, mountpoint ) )
def unmountOverlayDirs( self ):
"Unmount overlay directories"
for entry in self.overlayDirs:
mountpoint, overlay, tmpfs = self._overlayFrom( entry )
# Unfortunately these umounts can fail if the mount point
# is in use, possibly leaving tmpfs garbage in the root
# mount namespace / file system
self.cmd( 'umount', mountpoint )
if not overlay:
self.cmd( 'umount', tmpfs )
def _popen( self, cmd, **params ):
"""Internal method: spawn and return a process
cmd: command to run (list)
params: parameters to Popen()"""
# Leave this is as an instance method for now
assert self
return Popen( cmd, **params )
def cleanup( self ):
"Help python collect its garbage."
# We used to do this, but it slows us down:
# Intfs may end up in root NS
for intfName in self.intfNames():
if self.name in intfName:
quietRun( 'ip link del ' + intfName )
# for intfName in self.intfNames():
# if self.name in intfName:
# quietRun( 'ip link del ' + intfName )
self.shell = None
# Subshell I/O, commands and control
@@ -235,13 +304,17 @@ class Node( object ):
def terminate( self ):
"Send kill signal to Node and clean up after it."
self.unmountPrivateDirs()
self.unmountOverlayDirs()
if self.shell:
if self.shell.poll() is None:
os.killpg( self.shell.pid, signal.SIGHUP )
self.cleanup()
def stop( self ):
"Stop node."
def stop( self, deleteIntfs=False ):
"""Stop node.
deleteIntfs: delete interfaces? (False)"""
if deleteIntfs:
self.deleteIntfs()
self.terminate()
def waitReadable( self, timeoutms=None ):
@@ -254,9 +327,9 @@ class Node( object ):
"""Send a command, followed by a command to echo a sentinel,
and return without waiting for the command to complete.
args: command and arguments, or string
printPid: print command's PID?"""
assert not self.waiting
printPid = kwargs.get( 'printPid', True )
printPid: print command's PID? (False)"""
assert self.shell and not self.waiting
printPid = kwargs.get( 'printPid', False )
# Allow sendCmd( [ list ] )
if len( args ) == 1 and isinstance( args[ 0 ], list ):
cmd = args[ 0 ]
@@ -280,10 +353,10 @@ class Node( object ):
self.lastPid = None
self.waiting = True
def sendInt( self, intr=chr( 3 ) ):
def sendInt( self, signal=signal.SIGINT ):
"Interrupt running command."
debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
self.write( intr )
debug( "sending signal %d to pgrp %d" % ( signal, self.pid ) )
os.killpg( self.pid, signal )
def monitor( self, timeoutms=None, findPid=True ):
"""Monitor and return the output of a command.
@@ -294,18 +367,18 @@ class Node( object ):
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 ):
while True:
markers = self._marker.findall( data )
if markers:
self.lastPid = int( markers[ -1 ] )
data = self._marker.sub( '', data )
break
data += self.read( 1024 )
markers = re.findall( marker, data )
if markers:
self.lastPid = int( markers[ 0 ][ 1: ] )
data = re.sub( marker, '', data )
# Look for sentinel/EOF
if len( data ) > 0 and data[ -1 ] == chr( 127 ):
self.waiting = False
@@ -324,7 +397,7 @@ class Node( object ):
log = info if verbose else debug
output = ''
while self.waiting:
data = self.monitor()
data = self.monitor( findPid=findPid )
output += data
log( data )
return output
@@ -335,8 +408,11 @@ class Node( object ):
verbose = kwargs.get( 'verbose', False )
log = info if verbose else debug
log( '*** %s : %s\n' % ( self.name, args ) )
self.sendCmd( *args, **kwargs )
return self.waitOutput( verbose )
if self.shell:
self.sendCmd( *args, **kwargs )
return self.waitOutput( verbose )
else:
warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) )
def cmdPrint( self, *args):
"""Call cmd and printing its output
@@ -368,6 +444,7 @@ class Node( object ):
# Shell requires a string, not a list!
if defaults.get( 'shell', False ):
cmd = ' '.join( cmd )
debug( cmd, defaults )
popen = self._popen( cmd, **defaults )
return popen
@@ -375,7 +452,7 @@ class Node( object ):
"""Execute a command using popen
returns: out, err, exitcode"""
popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
**kwargs )
**kwargs )
# Warning: this can fail with large numbers of fds!
out, err = popen.communicate()
exitcode = popen.wait()
@@ -421,7 +498,7 @@ class Node( object ):
warn( '*** defaultIntf: warning:', self.name,
'has no interfaces\n' )
def intf( self, intf='' ):
def intf( self, intf=None ):
"""Return our interface object with given string name,
default intf if name is falsy (None, empty string, etc).
or the input intf arg.
@@ -488,8 +565,8 @@ class Node( object ):
params = intf
else:
params = 'dev %s' % intf
self.cmd( 'ip route del default' )
return self.cmd( 'ip route add default', params )
# Do this in one line in case we're messing with the root namespace
self.cmd( 'ip route del default; ip route add default', params )
# Convenience and configuration methods
@@ -499,15 +576,13 @@ class Node( object ):
mac: MAC address as string"""
return self.intf( intf ).setMAC( mac )
def setIP( self, ip, prefixLen=8, intf=None ):
def setIP( self, ip, prefixLen=8, intf=None, **kwargs ):
"""Set the IP address for an interface.
intf: intf or intf name
ip: IP address as a string
prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
# This should probably be rethought
if '/' not in ip:
ip = '%s/%s' % ( ip, prefixLen )
return self.intf( intf ).setIP( ip )
prefixLen: prefix length, e.g. 8 for /8 or 16M addrs
kwargs: any additional arguments for intf.setIP"""
return self.intf( intf ).setIP( ip, prefixLen, **kwargs )
def IP( self, intf=None ):
"Return IP address of a node or specific interface."
@@ -667,7 +742,7 @@ class CPULimitedHost( Host ):
"Clean up our cgroup"
# info( '*** deleting cgroup', self.cgroup, '\n' )
_out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
return exitcode != 0
return exitcode == 0 # success condition
def popen( self, *args, **kwargs ):
"""Return a Popen() object in node's namespace
@@ -682,8 +757,8 @@ class CPULimitedHost( Host ):
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' )
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 ):
@@ -692,15 +767,17 @@ class CPULimitedHost( Host ):
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 )
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' )
error( '\n*** error: please enable RT_GROUP_SCHED '
'in your kernel\n' )
exit( 1 )
cls._rtGroupSched = True
@@ -749,14 +826,14 @@ class CPULimitedHost( Host ):
"""Set overall CPU fraction for this host
f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited)
sched: 'rt' or 'cfs'
Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
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
raise Exception( 'Please set a positive CPU fraction'
' for sched=rt\n' )
pstr, qstr, period, quota = self.rtInfo( f )
elif sched == 'cfs':
pstr, qstr, period, quota = self.cfsInfo( f )
@@ -842,7 +919,7 @@ class Switch( Node ):
self.dpid = self.defaultDpid( dpid )
self.opts = opts
self.listenPort = listenPort
if not self.inNamespace:
if 'net' not in self.ns:
self.controlIntf = Intf( 'lo', self, port=0 )
def defaultDpid( self, dpid=None ):
@@ -881,7 +958,17 @@ class Switch( Node ):
def connected( self ):
"Is the switch connected to a controller? (override this method)"
return False and self # satisfy pylint
# Assume that we are connected by default to whatever we need to
# be connected to. This should be overridden by any OpenFlow
# switch, but not by a standalone bridge.
debug( 'Assuming', repr( self ), 'is connected to a controller\n' )
return True
def stop( self, deleteIntfs=True ):
"""Stop switch
deleteIntfs: delete interfaces? (True)"""
if deleteIntfs:
self.deleteIntfs()
def __repr__( self ):
"More informative string representation"
@@ -890,6 +977,7 @@ class Switch( Node ):
return '<%s %s: %s pid=%s> ' % (
self.__class__.__name__, self.name, intfs, self.pid )
class UserSwitch( Switch ):
"User-space switch."
@@ -938,12 +1026,12 @@ class UserSwitch( Switch ):
we re-create the user switch's configuration, but as a
leaf of the TCIntf-created configuration."""
if isinstance( intf, TCIntf ):
ifspeed = 10000000000 # 10 Gbps
ifspeed = 10000000000 # 10 Gbps
minspeed = ifspeed * 0.001
res = intf.config( **intf.params )
if res is None: # link may not have TC parameters
if res is None: # link may not have TC parameters
return
# Re-add qdisc, root, and default classes user switch created, but
@@ -976,85 +1064,44 @@ class UserSwitch( Switch ):
' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
if "no-slicing" not in self.dpopts:
# Only TCReapply if slicing is enable
sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
for intf in self.intfList():
if not intf.IP():
self.TCReapply( intf )
def stop( self, deleteIntfs=True ):
"Stop OpenFlow reference user datapath."
"""Stop OpenFlow reference user datapath.
deleteIntfs: delete interfaces? (True)"""
self.cmd( 'kill %ofdatapath' )
self.cmd( 'kill %ofprotocol' )
if deleteIntfs:
self.deleteIntfs()
class OVSLegacyKernelSwitch( Switch ):
"""Open VSwitch legacy kernel-space switch using ovs-openflowd.
Currently only works in the root namespace."""
def __init__( self, name, dp=None, **kwargs ):
"""Init.
name: name for switch
dp: netlink id (0, 1, 2, ...)
defaultMAC: default MAC as unsigned int; random value if None"""
Switch.__init__( self, name, **kwargs )
self.dp = dp if dp else self.name
self.intf = self.dp
if self.inNamespace:
error( "OVSKernelSwitch currently only works"
" in the root namespace.\n" )
exit( 1 )
@classmethod
def setup( cls ):
"Ensure any dependencies are loaded; if not, try to load them."
pathCheck( 'ovs-dpctl', 'ovs-openflowd',
moduleName='Open vSwitch (openvswitch.org)')
moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
def start( self, controllers ):
"Start up kernel datapath."
ofplog = '/tmp/' + self.name + '-ofp.log'
# Delete local datapath if it exists;
# then create a new one monitoring the given interfaces
self.cmd( 'ovs-dpctl del-dp ' + self.dp )
self.cmd( 'ovs-dpctl add-dp ' + self.dp )
intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
# Run protocol daemon
clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
for c in controllers ] )
self.cmd( 'ovs-openflowd ' + self.dp +
' ' + clist +
' --fail=secure ' + self.opts +
' --datapath-id=' + self.dpid +
' 1>' + ofplog + ' 2>' + ofplog + '&' )
self.execed = False
def stop( self, deleteIntfs=True ):
"Terminate kernel datapath."
quietRun( 'ovs-dpctl del-dp ' + self.dp )
self.cmd( 'kill %ovs-openflowd' )
if deleteIntfs:
self.deleteIntfs()
super( UserSwitch, self ).stop( deleteIntfs )
class OVSSwitch( Switch ):
"Open vSwitch switch. Depends on ovs-vsctl."
def __init__( self, name, failMode='secure', datapath='kernel',
inband=False, protocols=None, **params ):
"""Init.
name: name for switch
inband=False, protocols=None,
reconnectms=1000, stp=False, batch=False, **params ):
"""name: name for switch
failMode: controller loss behavior (secure|open)
datapath: userspace or kernel mode (kernel|user)
inband: use in-band control (False)"""
inband: use in-band control (False)
protocols: use specific OpenFlow version(s) (e.g. OpenFlow13)
Unspecified (or old OVS version) uses OVS default
reconnectms: max reconnect timeout in ms (0/None for default)
stp: enable STP (False, requires failMode=standalone)
batch: enable batch startup (False)"""
Switch.__init__( self, name, **params )
self.failMode = failMode
self.datapath = datapath
self.inband = inband
self.protocols = protocols
self.reconnectms = reconnectms
self.stp = stp
self._uuids = [] # controller UUIDs
self.batch = batch
self.commands = [] # saved commands for batch startup
@classmethod
def setup( cls ):
@@ -1075,25 +1122,26 @@ class OVSSwitch( Switch ):
'You may wish to try '
'"service openvswitch-switch start".\n' )
exit( 1 )
info = quietRun( 'ovs-vsctl --version' )
cls.OVSVersion = findall( '\d+\.\d+', info )[ 0 ]
version = quietRun( 'ovs-vsctl --version' )
cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
@classmethod
def isOldOVS( cls ):
return ( StrictVersion( cls.OVSVersion ) <
StrictVersion( '1.10' ) )
@classmethod
def batchShutdown( cls, switches ):
"Call ovs-vsctl del-br on all OVSSwitches in a list"
quietRun( 'ovs-vsctl ' +
' -- '.join( '--if-exists del-br %s' % s
for s in switches ) )
def isOldOVS( self ):
"Is OVS ersion < 1.10?"
return ( StrictVersion( self.OVSVersion ) <
StrictVersion( '1.10' ) )
def dpctl( self, *args ):
"Run ovs-ofctl command"
return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
def vsctl( self, *args, **kwargs ):
"Run ovs-vsctl command (or queue for later execution)"
if self.batch:
cmd = ' '.join( str( arg ).strip() for arg in args )
self.commands.append( cmd )
else:
return self.cmd( 'ovs-vsctl', *args, **kwargs )
@staticmethod
def TCReapply( intf ):
"""Unfortunately OVS and Mininet are fighting
@@ -1104,94 +1152,157 @@ class OVSSwitch( Switch ):
def attach( self, intf ):
"Connect a data port"
self.cmd( 'ovs-vsctl add-port', self, intf )
self.vsctl( 'add-port', self, intf )
self.cmd( 'ifconfig', intf, 'up' )
self.TCReapply( intf )
def detach( self, intf ):
"Disconnect a data port"
self.cmd( 'ovs-vsctl del-port', self, intf )
self.vsctl( 'del-port', self, intf )
def controllerUUIDs( self ):
"Return ovsdb UUIDs for our controllers"
uuids = []
controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
'Controller' ).strip()
if controllers.startswith( '[' ) and controllers.endswith( ']' ):
controllers = controllers[ 1 : -1 ]
uuids = [ c.strip() for c in controllers.split( ',' ) ]
return uuids
def controllerUUIDs( self, update=False ):
"""Return ovsdb UUIDs for our controllers
update: update cached value"""
if not self._uuids or update:
controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
'Controller' ).strip()
if controllers.startswith( '[' ) and controllers.endswith( ']' ):
controllers = controllers[ 1 : -1 ]
if controllers:
self._uuids = [ c.strip()
for c in controllers.split( ',' ) ]
return self._uuids
def connected( self ):
"Are we connected to at least one of our controllers?"
results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
uuid, 'is_connected' )
for uuid in self.controllerUUIDs() ]
return reduce( or_, results, False )
for uuid in self.controllerUUIDs():
if 'true' in self.vsctl( '-- get Controller',
uuid, 'is_connected' ):
return True
return self.failMode == 'standalone'
def intfOpts( self, intf ):
"Return OVS interface options for intf"
opts = ''
if not self.isOldOVS():
# ofport_request is not supported on old OVS
opts += ' ofport_request=%s' % self.ports[ intf ]
# Patch ports don't work well with old OVS
if isinstance( intf, OVSIntf ):
intf1, intf2 = intf.link.intf1, intf.link.intf2
peer = intf1 if intf1 != intf else intf2
opts += ' type=patch options:peer=%s' % peer
return '' if not opts else ' -- set Interface %s' % intf + opts
def bridgeOpts( self ):
"Return OVS bridge options"
opts = ( ' other_config:datapath-id=%s' % self.dpid +
' fail_mode=%s' % self.failMode )
if not self.inband:
opts += ' other-config:disable-in-band=true'
if self.datapath == 'user':
opts += ' datapath_type=netdev'
if self.protocols and not self.isOldOVS():
opts += ' protocols=%s' % self.protocols
if self.stp and self.failMode == 'standalone':
opts += ' stp_enable=true'
return opts
def start( self, controllers ):
"Start up a new OVS OpenFlow switch using ovs-vsctl"
if self.inNamespace:
raise Exception(
'OVS kernel switch does not work in a namespace' )
# Annoyingly, --if-exists option seems not to work
self.cmd( 'ovs-vsctl del-br', self )
int( self.dpid, 16 ) # DPID must be a hex string
# Interfaces and controllers
intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
'-- set Interface %s ' % intf +
'ofport_request=%s ' % self.ports[ intf ]
'OVS kernel switch does not work in a network namespace' )
int( self.dpid, 16 ) # DPID must be a hex string
# Command to add interfaces
intfs = ''.join( ' -- add-port %s %s' % ( self, intf ) +
self.intfOpts( intf )
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 )
# Command to create controller entries
clist = [ ( self.name + c.name, '%s:%s:%d' %
( c.protocol, c.IP(), c.port ) )
for c in controllers ]
if self.listenPort:
clist += ' ptcp:%s' % self.listenPort
# Construct big ovs-vsctl command for new versions of OVS
clist.append( ( self.name + '-listen',
'ptcp:%s' % self.listenPort ) )
ccmd = '-- --id=@%s create Controller target=\\"%s\\"'
if self.reconnectms:
ccmd += ' max_backoff=%d' % self.reconnectms
cargs = ' '.join( ccmd % ( name, target )
for name, target in clist )
# Controller ID list
cids = ','.join( '@%s' % name for name, _target in clist )
# Try to delete any existing bridges with the same name
if not self.isOldOVS():
cmd = ( 'ovs-vsctl add-br %s ' % self +
'-- set Bridge %s ' % self +
'other_config:datapath-id=%s ' % self.dpid +
'-- set-fail-mode %s %s ' % ( self, self.failMode ) +
intfs +
'-- set-controller %s %s ' % ( self, clist ) )
# Construct ovs-vsctl commands for old versions of OVS
else:
self.cmd( 'ovs-vsctl add-br', self )
cargs += ' -- --if-exists del-br %s' % self
# One ovs-vsctl command to rule them all!
self.vsctl( cargs +
' -- add-br %s' % self +
' -- set bridge %s controller=[%s]' % ( self, cids ) +
self.bridgeOpts() +
intfs )
# If necessary, restore TC config overwritten by OVS
if not self.batch:
for intf in self.intfList():
if not intf.IP():
self.cmd( 'ovs-vsctl add-port', self, intf )
cmd = ( 'ovs-vsctl set Bridge %s ' % self +
'other_config:datapath-id=%s ' % self.dpid +
'-- set-fail-mode %s %s ' % ( self, self.failMode ) +
'-- set-controller %s %s ' % ( self, clist ) )
if not self.inband:
cmd += ( '-- set bridge %s '
'other-config:disable-in-band=true ' % self )
if self.datapath == 'user':
cmd += '-- set bridge %s datapath_type=netdev ' % self
if self.protocols:
cmd += '-- set bridge %s protocols=%s' % ( self, self.protocols )
# Reconnect quickly to controllers (1s vs. 15s max_backoff)
for uuid in self.controllerUUIDs():
if uuid.count( '-' ) != 4:
# Doesn't look like a UUID
continue
uuid = uuid.strip()
cmd += '-- set Controller %smax_backoff=1000 ' % uuid
# Do it!!
self.cmd( cmd )
for intf in self.intfList():
self.TCReapply( intf )
self.TCReapply( intf )
# This should be ~ int( quietRun( 'getconf ARG_MAX' ) ),
# but the real limit seems to be much lower
argmax = 128000
@classmethod
def batchStartup( cls, switches, run=errRun ):
"""Batch startup for OVS
switches: switches to start up
run: function to run commands (errRun)"""
info( '...' )
cmds = 'ovs-vsctl'
for switch in switches:
if switch.isOldOVS():
# Ideally we'd optimize this also
run( 'ovs-vsctl del-br %s' % switch )
for cmd in switch.commands:
cmd = cmd.strip()
# Don't exceed ARG_MAX
if len( cmds ) + len( cmd ) >= cls.argmax:
run( cmds, shell=True )
cmds = 'ovs-vsctl'
cmds += ' ' + cmd
switch.cmds = []
switch.batch = False
if cmds:
run( cmds, shell=True )
# Reapply link config if necessary...
for switch in switches:
for intf in switch.intfs.itervalues():
if isinstance( intf, TCIntf ):
intf.config( **intf.params )
return switches
def stop( self, deleteIntfs=True ):
"Terminate OVS switch."
"""Terminate OVS switch.
deleteIntfs: delete interfaces? (True)"""
self.cmd( 'ovs-vsctl del-br', self )
if self.datapath == 'user':
self.cmd( 'ip link del', self )
if deleteIntfs:
self.deleteIntfs()
super( OVSSwitch, self ).stop( deleteIntfs )
@classmethod
def batchShutdown( cls, switches, run=errRun ):
"Shut down a list of OVS switches"
delcmd = 'del-br %s'
if switches and not switches[ 0 ].isOldOVS():
delcmd = '--if-exists ' + delcmd
# First, delete them all from ovsdb
run( 'ovs-vsctl ' +
' -- '.join( delcmd % s for s in switches ) )
# Next, shut down all of the processes
pids = ' '.join( str( switch.pid ) for switch in switches )
run( 'kill -HUP ' + pids )
for switch in switches:
switch.shell = None
return switches
OVSKernelSwitch = OVSSwitch
@@ -1199,17 +1310,28 @@ OVSKernelSwitch = OVSSwitch
class OVSBridge( OVSSwitch ):
"OVSBridge is an OVSSwitch in standalone/bridge mode"
def __init__( self, args, **kwargs ):
def __init__( self, *args, **kwargs ):
"""stp: enable Spanning Tree Protocol (False)
see OVSSwitch for other options"""
kwargs.update( failMode='standalone' )
OVSSwitch.__init__( self, args, **kwargs )
OVSSwitch.__init__( self, *args, **kwargs )
def start( self, controllers ):
"Start bridge, ignoring controllers argument"
OVSSwitch.start( self, controllers=[] )
def connected( self ):
"Are we forwarding yet?"
if self.stp:
status = self.dpctl( 'show' )
return 'STP_FORWARD' in status and not 'STP_LEARN' in status
else:
return True
class IVSSwitch( Switch ):
"""IVS virtual switch"""
"Indigo Virtual Switch"
def __init__( self, name, verbose=False, **kwargs ):
Switch.__init__( self, name, **kwargs )
@@ -1233,6 +1355,7 @@ class IVSSwitch( Switch ):
"Kill each IVS switch, to be waited on later in stop()"
for switch in switches:
switch.cmd( 'kill %ivs' )
return switches
def start( self, controllers ):
"Start up a new IVS switch"
@@ -1255,11 +1378,11 @@ class IVSSwitch( Switch ):
self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
def stop( self, deleteIntfs=True ):
"Terminate IVS switch."
"""Terminate IVS switch.
deleteIntfs: delete interfaces? (True)"""
self.cmd( 'kill %ivs' )
self.cmd( 'wait' )
if deleteIntfs:
self.deleteIntfs()
super( IVSSwitch, self ).stop( deleteIntfs )
def attach( self, intf ):
"Connect a data port"
@@ -1282,11 +1405,15 @@ class Controller( Node ):
OpenFlow controller."""
def __init__( self, name, inNamespace=False, command='controller',
cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
cargs='ptcp:%d', cdir=None, ip="127.0.0.1",
port=6633, protocol='tcp', **params ):
self.command = command
self.cargs = cargs
self.cdir = cdir
# Accept 'ip:port' syntax as shorthand
if ':' in ip:
ip, port = ip.split( ':' )
port = int( port )
self.ip = ip
self.port = port
self.protocol = protocol
@@ -1323,11 +1450,11 @@ class Controller( Node ):
' 1>' + cout + ' 2>' + cout + ' &' )
self.execed = False
def stop( self ):
def stop( self, *args, **kwargs ):
"Stop controller."
self.cmd( 'kill %' + self.command )
self.cmd( 'wait %' + self.command )
self.terminate()
super( Controller, self ).stop( *args, **kwargs )
def IP( self, intf=None ):
"Return IP address of the Controller"
@@ -1342,19 +1469,24 @@ class Controller( Node ):
return '<%s %s: %s:%s pid=%s> ' % (
self.__class__.__name__, self.name,
self.IP(), self.port, self.pid )
@classmethod
def isAvailable( self ):
def isAvailable( cls ):
"Is controller available?"
return quietRun( 'which controller' )
class OVSController( Controller ):
"Open vSwitch controller"
def __init__( self, name, command='ovs-controller', **kwargs ):
if quietRun( 'which test-controller' ):
command = 'test-controller'
Controller.__init__( self, name, command=command, **kwargs )
@classmethod
def isAvailable( self ):
return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
def isAvailable( cls ):
return ( quietRun( 'which ovs-controller' ) or
quietRun( 'which test-controller' ) )
class NOX( Controller ):
"Controller to run a NOX application."
@@ -1381,7 +1513,7 @@ class NOX( Controller ):
cdir=noxCoreDir,
**kwargs )
class RYU( Controller ):
class Ryu( Controller ):
"Controller to run Ryu application"
def __init__( self, name, *ryuArgs, **kwargs ):
"""Init.
@@ -1397,11 +1529,12 @@ class RYU( Controller ):
ryuArgs = [ ryuArgs ]
Controller.__init__( self, name,
command='ryu-manager',
cargs='--ofp-tcp-listen-port %s ' +
' '.join( ryuArgs ),
cdir=ryuCoreDir,
**kwargs )
command='ryu-manager',
cargs='--ofp-tcp-listen-port %s ' +
' '.join( ryuArgs ),
cdir=ryuCoreDir,
**kwargs )
class RemoteController( Controller ):
"Controller running outside of Mininet's control."
@@ -1432,7 +1565,7 @@ class RemoteController( Controller ):
" at %s:%d\n" % ( self.ip, self.port ) )
DefaultControllers = [ Controller, OVSController ]
DefaultControllers = ( Controller, OVSController )
def findController( controllers=DefaultControllers ):
"Return first available controller from list, if any"
@@ -1447,3 +1580,6 @@ def DefaultController( name, controllers=DefaultControllers, **kwargs ):
raise Exception( 'Could not find a default OpenFlow controller' )
return controller( name, **kwargs )
def NullController( *_args, **_kwargs ):
"Nonexistent controller - simply returns None"
return None
+137 -48
View File
@@ -4,11 +4,13 @@ Node Library for Mininet
This contains additional Node types which you may find to be useful.
"""
from mininet.node import Node, Switch
from mininet.node import Node, Host, Switch
from mininet.log import info, warn
from mininet.moduledeps import pathCheck
from mininet.util import quietRun
import re
from tempfile import NamedTemporaryFile
class LinuxBridge( Switch ):
"Linux Bridge (with optional spanning tree)"
@@ -32,8 +34,8 @@ class LinuxBridge( Switch ):
return 'forwarding' in self.cmd( 'brctl showstp', self )
else:
return True
def start( self, controllers ):
def start( self, _controllers ):
"Start Linux bridge"
self.cmd( 'ifconfig', self, 'down' )
self.cmd( 'brctl delbr', self )
@@ -46,10 +48,12 @@ class LinuxBridge( Switch ):
self.cmd( 'brctl addif', self, i )
self.cmd( 'ifconfig', self, 'up' )
def stop( self ):
"Stop Linux bridge"
def stop( self, deleteIntfs=True ):
"""Stop Linux bridge
deleteIntfs: delete interfaces? (True)"""
self.cmd( 'ifconfig', self, 'down' )
self.cmd( 'brctl delbr', self )
super( LinuxBridge, self ).stop( deleteIntfs )
def dpctl( self, *args ):
"Run brctl command"
@@ -57,47 +61,56 @@ class LinuxBridge( Switch ):
@classmethod
def setup( cls ):
"Make sure our class dependencies are available"
"Check dependencies and warn about firewalling"
pathCheck( 'brctl', moduleName='bridge-utils' )
# Disable Linux bridge firewalling so that traffic can flow!
for table in 'arp', 'ip', 'ip6':
cmd = 'sysctl net.bridge.bridge-nf-call-%stables' % table
out = quietRun( cmd ).strip()
if out.endswith( '1' ):
warn( 'Warning: Linux bridge may not work with', out, '\n' )
class NAT( Node ):
"""NAT: Provides connectivity to external network"""
"NAT: Provides connectivity to external network"
def __init__( self, name, inetIntf=None, subnet='10.0/8', localIntf=None, **params):
def __init__( self, name, subnet='10.0/8',
localIntf=None, flush=False, **params):
"""Start NAT/forwarding between Mininet and external network
subnet: Mininet subnet (default 10.0/8)
flush: flush iptables before installing NAT rules"""
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 if inetIntf else self.getGatewayIntf()
self.subnet = subnet
self.localIntf = localIntf
self.flush = flush
self.forwardState = self.cmd( 'sysctl -n net.ipv4.ip_forward' ).strip()
def config( self, **params ):
super( NAT, self).config( **params )
"""Configure the NAT and iptables"""
super( NAT, self).config( **params )
if not self.localIntf:
self.localIntf = self.defaultIntf()
self.localIntf = self.defaultIntf()
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
if self.flush:
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
self.cmd( 'iptables -F' )
self.cmd( 'iptables -t nat -F' )
# Create default entries for unmatched traffic
self.cmd( 'iptables -P INPUT ACCEPT' )
self.cmd( 'iptables -P OUTPUT ACCEPT' )
self.cmd( 'iptables -P FORWARD DROP' )
# Flush any currently active rules
# TODO: is this safe?
self.cmd( 'iptables -F' )
self.cmd( 'iptables -t nat -F' )
# Create default entries for unmatched traffic
self.cmd( 'iptables -P INPUT ACCEPT' )
self.cmd( 'iptables -P OUTPUT ACCEPT' )
self.cmd( 'iptables -P FORWARD DROP' )
# Configure NAT
self.cmd( 'iptables -I FORWARD -i', self.localIntf, '-d', self.subnet, '-j DROP' )
self.cmd( 'iptables -A FORWARD -i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
self.cmd( 'iptables -A FORWARD -i', self.inetIntf, '-d', self.subnet, '-j ACCEPT' )
self.cmd( 'iptables -t nat -A POSTROUTING -o ', self.inetIntf, '-s', self.subnet, '-j MASQUERADE' )
# Install NAT rules
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',
'-o', self.localIntf, '-d', self.subnet,'-j ACCEPT' )
self.cmd( 'iptables -t nat -A POSTROUTING',
'-s', self.subnet, "'!'", '-d', self.subnet, '-j MASQUERADE' )
# Instruct the kernel to perform forwarding
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
@@ -116,24 +129,100 @@ class NAT( Node ):
# 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
# TODO: is this safe?
self.cmd( 'iptables -F' )
self.cmd( 'iptables -t nat -F' )
# Instruct the kernel to stop forwarding
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
"Stop NAT/forwarding between Mininet and external network"
# Remote NAT rules
self.cmd( 'iptables -D FORWARD',
'-i', self.localIntf, '-d', self.subnet, '-j DROP' )
self.cmd( 'iptables -D FORWARD',
'-i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
self.cmd( 'iptables -D FORWARD',
'-o', self.localIntf, '-d', self.subnet,'-j ACCEPT' )
self.cmd( 'iptables -t nat -D POSTROUTING',
'-s', self.subnet, '\'!\'', '-d', self.subnet, '-j MASQUERADE' )
# Put the forwarding state back to what it was
self.cmd( 'sysctl net.ipv4.ip_forward=%s' % self.forwardState )
super( NAT, self ).terminate()
class Server( Host ):
"""Run sshd in a net/mnt/pid/uts namespace, with private /etc/hosts
WARNING!!! KNOWN ISSUES:
- control-c does not work in Mininet CLI with pid namespace
- xterm does not work from Mininet CLI
We may be able to address these issues in the future."""
ns = [ 'net', 'mnt', 'pid', 'uts' ]
overlayDirs = [ '/etc', '/var/run', '/var/log' ]
privateDirs = [ '/var/run/sshd', ]
def __init__( self, *args, **kwargs ):
"""Add overlay dirs and private dirs, and change permissions
ssh: run sshd? (True)"""
kwargs.setdefault( 'inNamespace', True )
kwargs.setdefault( 'ns', self.ns )
kwargs.setdefault( 'privateDirs', self.privateDirs )
kwargs.setdefault( 'overlayDirs', self.overlayDirs )
kwargs.setdefault( 'ssh', True )
super( Server, self ).__init__( *args, **kwargs )
# Change permissions, mainly for ssh
for pdir in self.privateDirs:
self.cmd( 'chown root:root', pdir )
self.cmd( 'chmod 755', pdir )
@staticmethod
def updateHostsFiles( servers, tmpdir='/tmp' ):
"""Update local hosts files on a list of servers
servers: list of servers
tmpdir: tmp dir shared between mn and servers"""
# This scales as n^2, so for a large configuration it's
# more efficient to use a DNS server
for s in servers:
dirs = ( getattr( s, 'overlayDirs', [] ) +
getattr( s, 'privateDirs', [] ))
if '/etc' in dirs:
with NamedTemporaryFile( dir=tmpdir ) as tmpfile:
tmpfile.write( '# Mininet hosts file\n' )
tmpfile.write( '127.0.0.1 localhost %s\n' % s )
for t in servers:
tmpfile.write( '%s %s\n' % ( t.IP(), t ) )
tmpfile.flush()
s.cmd( 'cp', tmpfile.name, '/etc/hosts' )
else:
warn( 'not updating hosts file on %s\n' % s )
def service( self, cmd ):
"""Start or stop a service
usage: service( 'ssh stop' )"""
self.cmd( '/etc/init.d/%s' % cmd )
def motd( self ):
"Return login message as a string"
return 'Welcome to Mininet host %s at %s' % ( self, self.IP() )
def startSSH( self, motdPath='/var/run/motd.dynamic' ):
"Update motd, clear out utmp/wtmp/btmp, and start sshd"
# Note: /var/run and /var/log must be overlays!
assert ( '/var/run' in ( self.overlayDirs + self.privateDirs ) and
'/var/log' in ( self.overlayDirs + self.privateDirs ) )
self.cmd( "echo '%s' > %s" % ( self.motd(), motdPath ) )
self.cmd( 'truncate -s0 /var/run/utmp /var/log/wtmp* /var/log/btmp*' )
# sshd.pid should really be in /var/run/sshd instead of /var/run
self.cmd( 'rm /var/run/sshd.pid' )
self.cmd( '/etc/init.d/ssh start' )
def config( self, **kwargs ):
"""Configure/start sshd and other stuff
ssh: start sshd? (True )"""
super( Server, self ).config( **kwargs )
self.ssh = kwargs.get( 'ssh' )
if self.ssh:
self.startSSH()
if 'uts' in self.ns:
self.cmd( 'hostname', self )
def terminate( self, *args, **kwargs ):
"Shut down services and terminate server"
if self.ssh:
self.service( 'ssh stop' )
super( Server, self ).terminate( *args, **kwargs )
+62 -9
View File
@@ -5,12 +5,13 @@ Utility functions to run a terminal (connected via socat(1)) on each host.
Requires socat(1) and xterm(1).
Optionally uses gnome-terminal.
"""
from os import environ
from mininet.log import error
from mininet.util import quietRun, errRun
from os import environ, getpid, path
from subprocess import Popen
from tempfile import NamedTemporaryFile
def tunnelX11( node, display=None):
"""Create an X11 tunnel from node:6000 to the root host
display: display on root host (optional)
@@ -28,14 +29,65 @@ def tunnelX11( node, display=None):
quietRun( 'xhost +si:localuser:root' )
return display, None
else:
# Create a tunnel for the TCP connection
# XXX Need to handle case where node is in UTS namespace
# in this case, we need to set XAUTHORITY to a private
# xauth file
if False and 'uts' in node.inNamespace and not hasattr( node, 'xauthFile' ):
node.xauthFile = NamedTemporaryFile()
quietRun( 'xauth extract $DISPLAY | xauth -f %s merge' % node.xauthFile.name )
port = 6000 + int( float( screen ) )
connection = r'TCP\:%s\:%s' % ( host, port )
cmd = [ "socat", "TCP-LISTEN:%d,fork,reuseaddr" % port,
"EXEC:'mnexec -a 1 socat STDIO %s'" % connection ]
# This can conflict if we are running nested Mininet
# in a pid namespace
socket = '/tmp/mininet.x11.%d' % getpid()
if not path.exists( socket ):
cmd = 'socat unix-listen:%s,fork tcp:localhost:%d' % ( socket, port )
# Should be shut down when mn shuts down
tunnelX11.socket = Popen( cmd, shell=True )
# Create a tunnel for the TCP connection
cmd = 'socat tcp-listen:%d,fork,reuseaddr unix:%s' % ( port, socket )
return 'localhost:' + screen, node.popen( cmd )
def makeTerm( node, title='Node', term='xterm', display=None ):
"""
With pid namespaces, we can't easily escape the pid jail using mnexec
(or can we?)
What we can do, however, is create a unix socket in /tmp which connects
to our x server, and a tcp listener in the host that connects to our
unix socket!
We just have to make sure that we clean up our processes and sockets
when we quit.
If we're using UTS namespaces, then xauth will get confused because
our hostname has changed. So, we use a private XAUTHORITY file per
host. We need to initialize this file with the key of our X server;
this is a bit hard to figure out because the usual xauth list $DISPLAY
may fail even if xlib can figure out a backup cookie to use.
But what about namespace conflicts? This could certainly be very
annoying for nested mininet!! In this case, our nested mininet servers
should have a private /tmp that they can use.... except that conflicts
with the shared unix domain socket in /tmp! ;-p
We could also use a random name for the socket, to avoid the namespace
conflict, although it's not clear what to do for cleanup to avoid
blasting this....
Perhaps the best idea is to use a canonical name (mininet.x11) and if the
first name fails, try a second name??
Another idea is to create a tmp dir for Mininet based on the pid of the
mn process.....
recommendation: for now, use mininet.x11.1234 as the socket.
"""
def makeTerm( node, title='Node', term='xterm', display=None, cmd='bash'):
"""Create an X11 tunnel to the node and start up a terminal.
node: Node object
title: base title
@@ -54,7 +106,8 @@ def makeTerm( node, title='Node', term='xterm', display=None ):
display, tunnel = tunnelX11( node, display )
if display is None:
return []
term = node.popen( cmds[ term ] + [ display, '-e', 'env TERM=ansi bash'] )
term = node.popen( cmds[ term ] +
[ display, '-e', 'env TERM=ansi %s' % cmd ] )
return [ tunnel, term ] if tunnel else [ term ]
def runX11( node, cmd ):
+6 -6
View File
@@ -6,7 +6,7 @@ Run all mininet core tests
-quick : skip tests that take more than ~30 seconds
"""
import unittest
from unittest import defaultTestLoader, TextTestRunner
import os
import sys
from mininet.util import ensureRoot
@@ -19,13 +19,13 @@ def runTests( testDir, verbosity=1 ):
ensureRoot()
cleanup()
# discover all tests in testDir
testSuite = unittest.defaultTestLoader.discover( testDir )
testSuite = defaultTestLoader.discover( testDir )
# run tests
unittest.TextTestRunner( verbosity=verbosity ).run( testSuite )
TextTestRunner( verbosity=verbosity ).run( testSuite )
if __name__ == '__main__':
setLogLevel( 'warning' )
# get the directory containing example tests
testDir = os.path.dirname( os.path.realpath( __file__ ) )
verbosity = 2 if '-v' in sys.argv else 1
runTests( testDir, verbosity )
thisdir = os.path.dirname( os.path.realpath( __file__ ) )
vlevel = 2 if '-v' in sys.argv else 1
runTests( testDir=thisdir, verbosity=vlevel )
+26 -14
View File
@@ -4,6 +4,7 @@
Test creation and pings for topologies with link and/or CPU options."""
import unittest
import sys
from functools import partial
from mininet.net import Mininet
@@ -13,6 +14,7 @@ from mininet.link import TCLink
from mininet.topo import Topo
from mininet.log import setLogLevel
from mininet.util import quietRun
from mininet.clean import cleanup
# Number of hosts for each test
N = 2
@@ -38,7 +40,13 @@ class testOptionsTopoCommon( object ):
"""Verify ability to create networks with host and link options
(common code)."""
switchClass = None # overridden in subclasses
switchClass = None # overridden in subclasses
@staticmethod
def tearDown():
"Clean up if necessary"
if sys.exc_info != ( None, None, None ):
cleanup()
def runOptionsTopoTest( self, n, msg, hopts=None, lopts=None ):
"Generic topology-with-options test runner."
@@ -79,7 +87,7 @@ class testOptionsTopoCommon( object ):
upperBound, lowerBound ) )
msg += info
self.assertGreaterEqual( float( measured ),lowerBound, msg=msg )
self.assertGreaterEqual( float( measured ), lowerBound, msg=msg )
self.assertLessEqual( float( measured ), upperBound, msg=msg )
def testCPULimits( self ):
@@ -96,7 +104,8 @@ class testOptionsTopoCommon( object ):
results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
mn.stop()
hostUsage = '\n'.join( 'h%s: %s' %
( n + 1, results[ ( n - 1 ) * 5: ( n * 5 ) - 1 ] )
( n + 1,
results[ (n - 1) * 5 : (n * 5) - 1 ] )
for n in range( N ) )
hoptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in hopts.items() )
@@ -106,7 +115,8 @@ class testOptionsTopoCommon( object ):
'hopts = %s\n'
'host = CPULimitedHost\n'
'Switch = %s\n'
% ( CPU_FRACTION * 100, hostUsage, N, hoptsStr, self.switchClass ) )
% ( CPU_FRACTION * 100, hostUsage, N, hoptsStr,
self.switchClass ) )
for pct in results:
#divide cpu by 100 to convert from percentage to fraction
self.assertWithinTolerance( pct/100, CPU_FRACTION,
@@ -115,7 +125,8 @@ class testOptionsTopoCommon( object ):
def testLinkBandwidth( self ):
"Verify that link bandwidths are accurate within a bound."
if self.switchClass is UserSwitch:
self.skipTest ( 'UserSwitch has very poor performance, so skip for now' )
self.skipTest( 'UserSwitch has very poor performance -'
' skipping for now' )
BW = 5 # Mbps
BW_TOLERANCE = 0.8 # BW fraction below which test should fail
# Verify ability to create limited-link topo first;
@@ -124,7 +135,7 @@ class testOptionsTopoCommon( object ):
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
link=TCLink, switch=self.switchClass,
waitConnected=True )
bw_strs = mn.run( mn.iperf, format='m' )
bw_strs = mn.run( mn.iperf, fmt='m' )
loptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in lopts.items() )
msg = ( '\nTesting link bandwidth limited to %d Mbps per link\n'
@@ -141,7 +152,7 @@ class testOptionsTopoCommon( object ):
# As long as the kernel doesn't wait a long time before
# delivering bytes to the iperf server, its reported data rate
# should be close to the actual receive rate.
serverRate, clientRate = bw_strs
serverRate, _clientRate = bw_strs
bw = float( serverRate.split(' ')[0] )
self.assertWithinTolerance( bw, BW, BW_TOLERANCE, msg )
@@ -160,12 +171,12 @@ class testOptionsTopoCommon( object ):
mn.stop()
test_outputs = ping_delays[0]
# Ignore unused variables below
# pylint: disable-msg=W0612
# pylint: disable=W0612
node, dest, ping_outputs = test_outputs
sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
pingFailMsg = 'sent %s pings, only received %s' % ( sent, received )
self.assertEqual( sent, received, msg=pingFailMsg )
# pylint: enable-msg=W0612
# pylint: enable=W0612
loptsStr = ', '.join( '%s: %s' % ( opt, value )
for opt, value in lopts.items() )
msg = ( '\nTesting Link Delay of %s ms\n'
@@ -181,10 +192,9 @@ class testOptionsTopoCommon( object ):
for rttval in [rttmin, rttavg, rttmax]:
# Multiply delay by 4 to cover there & back on two links
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
DELAY_TOLERANCE, msg )
def testLinkLoss( self ):
"Verify that we see packet drops with a high configured loss rate."
LOSS_PERCENT = 99
@@ -212,7 +222,8 @@ class testOptionsTopoCommon( object ):
'lopts = %s\n'
'host = default\n'
'switch = %s\n'
% ( LOSS_PERCENT, dropped_total, N, loptsStr, self.switchClass ) )
% ( LOSS_PERCENT, dropped_total, N, loptsStr,
self.switchClass ) )
self.assertGreater( dropped_total, 0, msg )
@@ -245,9 +256,10 @@ class testOptionsTopoIVS( testOptionsTopoCommon, unittest.TestCase ):
switchClass = IVSSwitch
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
'Reference user switch is not installed' )
'Reference user switch is not installed' )
class testOptionsTopoUserspace( testOptionsTopoCommon, unittest.TestCase ):
"Verify ability to create networks with host and link options (UserSwitch)."
"""Verify ability to create networks with host and link options
(UserSwitch)."""
longMessage = True
switchClass = UserSwitch
+13 -4
View File
@@ -4,6 +4,7 @@
Test creation and all-pairs ping for each included mininet topo type."""
import unittest
import sys
from functools import partial
from mininet.net import Mininet
@@ -12,6 +13,7 @@ from mininet.node import UserSwitch, OVSSwitch, IVSSwitch
from mininet.topo import SingleSwitchTopo, LinearTopo
from mininet.log import setLogLevel
from mininet.util import quietRun
from mininet.clean import cleanup
# Tell pylint not to complain about calls to other class
# pylint: disable=E1101
@@ -19,7 +21,13 @@ from mininet.util import quietRun
class testSingleSwitchCommon( object ):
"Test ping with single switch topology (common code)."
switchClass = None # overridden in subclasses
switchClass = None # overridden in subclasses
@staticmethod
def tearDown():
"Clean up if necessary"
if sys.exc_info != ( None, None, None ):
cleanup()
def testMinimal( self ):
"Ping test on minimal topology"
@@ -51,7 +59,7 @@ class testSingleSwitchIVS( testSingleSwitchCommon, unittest.TestCase ):
switchClass = IVSSwitch
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
'Reference user switch is not installed' )
'Reference user switch is not installed' )
class testSingleSwitchUserspace( testSingleSwitchCommon, unittest.TestCase ):
"Test ping with single switch topology (Userspace switch)."
switchClass = UserSwitch
@@ -63,11 +71,12 @@ class testSingleSwitchUserspace( testSingleSwitchCommon, unittest.TestCase ):
class testLinearCommon( object ):
"Test all-pairs ping with LinearNet (common code)."
switchClass = None # overridden in subclasses
switchClass = None # overridden in subclasses
def testLinear5( self ):
"Ping test on a 5-switch topology"
mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller, waitConnected=True )
mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host,
Controller, waitConnected=True )
dropped = mn.run( mn.ping )
self.assertEqual( dropped, 0 )
+53 -45
View File
@@ -4,40 +4,58 @@
Regression tests for switch dpid assignment."""
import unittest
from functools import partial
import re
import sys
from mininet.net import Mininet
from mininet.node import Host, Controller
from mininet.node import UserSwitch, OVSSwitch, OVSLegacyKernelSwitch, IVSSwitch
from mininet.node import ( UserSwitch, OVSSwitch, IVSSwitch )
from mininet.topo import Topo
from mininet.log import setLogLevel
from mininet.util import quietRun
from mininet.clean import cleanup
class testSwitchDpidAssignmentCommon ( object ):
"""Verify Switch dpid assignment."""
class TestSwitchDpidAssignmentOVS( unittest.TestCase ):
"Verify Switch dpid assignment."
switchClass = None # overridden in subclasses
switchClass = OVSSwitch # overridden in subclasses
def testDefaultDpid ( self ):
def tearDown( self ):
"Clean up if necessary"
# satisfy pylint
assert self
if sys.exc_info != ( None, None, None ):
cleanup()
def testDefaultDpid( self ):
"""Verify that the default dpid is assigned using a valid provided
canonical switchname if no dpid is passed in switch creation."""
switch = Mininet( Topo(), self.switchClass, Host, Controller ).addSwitch( 's1' )
switch = Mininet( Topo(),
self.switchClass,
Host, Controller ).addSwitch( 's1' )
self.assertEqual( switch.defaultDpid(), switch.dpid )
def dpidFrom( self, num ):
"Compute default dpid from number"
fmt = ( '%0' + str( self.switchClass.dpidLen ) + 'x' )
return fmt % num
def testActualDpidAssignment( self ):
"""Verify that Switch dpid is the actual dpid assigned if dpid is
"""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' )
dpid = self.dpidFrom( 0xABCD )
switch = Mininet( Topo(), self.switchClass,
Host, Controller ).addSwitch(
's1', dpid=dpid )
self.assertEqual( switch.dpid, dpid )
def testDefaultDpidAssignmentFailure( self ):
"""Verify that Default dpid assignment raises an Exception if the
name of the switch does not contin a digit. Also verify the
"""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' )
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.')
@@ -47,43 +65,33 @@ class testSwitchDpidAssignmentCommon ( object ):
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) )
switch = Mininet( Topo(), self.switchClass,
Host, Controller ).addSwitch( 's123' )
class testSwitchOVSKernel( testSwitchDpidAssignmentCommon, unittest.TestCase ):
"""Test dpid assignnment of OVS Kernel Switch."""
switchClass = OVSSwitch
self.assertEqual( switch.dpid, self.dpidFrom( 123 ) )
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."""
class OVSUser( OVSSwitch):
"OVS User Switch convenience class"
def __init__( self, *args, **kwargs ):
kwargs.update( datapath='user' )
OVSSwitch.__init__( self, *args, **kwargs )
class testSwitchOVSUser( TestSwitchDpidAssignmentOVS ):
"Test dpid assignnment of OVS User Switch."
switchClass = OVSUser
@unittest.skipUnless( quietRun( 'which ivs-ctl' ),
'IVS switch is not installed' )
class testSwitchIVS( TestSwitchDpidAssignmentOVS ):
"Test dpid assignment of IVS switch."
switchClass = IVSSwitch
@unittest.skipUnless( quietRun( 'which ofprotocol' ), 'Reference user switch is not installed' )
class testSwitchUserspace( testSwitchDpidAssignmentCommon, unittest.TestCase ):
"""Test dpid assignment of Userspace switch."""
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
'Reference user switch is not installed' )
class testSwitchUserspace( TestSwitchDpidAssignmentOVS ):
"Test dpid assignment of Userspace switch."
switchClass = UserSwitch
if __name__ == '__main__':
setLogLevel( 'warning' )
unittest.main()
+31 -15
View File
@@ -14,11 +14,16 @@ from mininet.util import quietRun
from distutils.version import StrictVersion
def tsharkVersion():
"Return tshark version"
versionStr = quietRun( 'tshark -v' )
versionMatch = re.findall( 'TShark \d+.\d+.\d+', versionStr )[0]
versionMatch = re.findall( r'TShark \d+.\d+.\d+', versionStr )[0]
return versionMatch.split()[ 1 ]
# pylint doesn't understand pexpect.match, unfortunately!
# pylint:disable=maybe-no-member
class testWalkthrough( unittest.TestCase ):
"Test Mininet walkthrough"
prompt = 'mininet>'
@@ -31,6 +36,8 @@ class testWalkthrough( unittest.TestCase ):
def testWireshark( self ):
"Use tshark to test the of dissector"
# Satisfy pylint
assert self
if StrictVersion( tsharkVersion() ) < StrictVersion( '1.12.0' ):
tshark = pexpect.spawn( 'tshark -i lo -R of' )
else:
@@ -51,7 +58,7 @@ class testWalkthrough( unittest.TestCase ):
self.assertEqual( index, 0, 'No output for "help" command')
# nodes command
p.sendline( 'nodes' )
p.expect( '([chs]\d ?){4}' )
p.expect( r'([chs]\d ?){4}' )
nodes = p.match.group( 0 ).split()
self.assertEqual( len( nodes ), 4, 'No nodes in "nodes" command')
p.expect( self.prompt )
@@ -67,14 +74,15 @@ class testWalkthrough( unittest.TestCase ):
p.expect( self.prompt )
# dump command
p.sendline( 'dump' )
expected = [ '<\w+ (%s)' % n for n in nodes ]
expected = [ r'<\w+ (%s)' % n for n in nodes ]
actual = []
for _ in nodes:
index = p.expect( expected )
node = p.match.group( 1 )
actual.append( node )
p.expect( '\n' )
self.assertEqual( actual.sort(), nodes.sort(), '"nodes" and "dump" differ' )
self.assertEqual( actual.sort(), nodes.sort(),
'"nodes" and "dump" differ' )
p.expect( self.prompt )
p.sendline( 'exit' )
p.wait()
@@ -161,7 +169,7 @@ class testWalkthrough( unittest.TestCase ):
p.expect( pexpect.EOF )
# test iperf
p = pexpect.spawn( 'mn --test iperf' )
p.expect( "Results: \['([\d\.]+) .bits/sec'," )
p.expect( r"Results: \['([\d\.]+) .bits/sec'," )
bw = float( p.match.group( 1 ) )
self.assertTrue( bw > 0 )
p.expect( pexpect.EOF )
@@ -170,7 +178,7 @@ class testWalkthrough( unittest.TestCase ):
"Test pingall on single,3 and linear,4 topos"
# testing single,3
p = pexpect.spawn( 'mn --test pingall --topo single,3' )
p.expect( '(\d+)/(\d+) received')
p.expect( r'(\d+)/(\d+) received')
received = int( p.match.group( 1 ) )
sent = int( p.match.group( 2 ) )
self.assertEqual( sent, 6, 'Wrong number of pings sent in single,3' )
@@ -178,7 +186,7 @@ class testWalkthrough( unittest.TestCase ):
p.expect( pexpect.EOF )
# testing linear,4
p = pexpect.spawn( 'mn --test pingall --topo linear,4' )
p.expect( '(\d+)/(\d+) received')
p.expect( r'(\d+)/(\d+) received')
received = int( p.match.group( 1 ) )
sent = int( p.match.group( 2 ) )
self.assertEqual( sent, 12, 'Wrong number of pings sent in linear,4' )
@@ -191,14 +199,15 @@ class testWalkthrough( unittest.TestCase ):
# test bw
p.expect( self.prompt )
p.sendline( 'iperf' )
p.expect( "Results: \['([\d\.]+) Mbits/sec'," )
p.expect( r"Results: \['([\d\.]+) Mbits/sec'," )
bw = float( p.match.group( 1 ) )
self.assertTrue( bw < 10.1, 'Bandwidth > 10 Mb/s')
self.assertTrue( bw > 9.0, 'Bandwidth < 9 Mb/s')
p.expect( self.prompt )
# test delay
p.sendline( 'h1 ping -c 4 h2' )
p.expect( 'rtt min/avg/max/mdev = ([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' )
p.expect( r'rtt min/avg/max/mdev = '
r'([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' )
delay = float( p.match.group( 2 ) )
self.assertTrue( delay > 40, 'Delay < 40ms' )
self.assertTrue( delay < 45, 'Delay > 40ms' )
@@ -222,10 +231,13 @@ class testWalkthrough( unittest.TestCase ):
def testCustomTopo( self ):
"Start Mininet using a custom topo, then run pingall"
# Satisfy pylint
assert self
custom = os.path.dirname( os.path.realpath( __file__ ) )
custom = os.path.join( custom, '../../custom/topo-2sw-2host.py' )
custom = os.path.normpath( custom )
p = pexpect.spawn( 'mn --custom %s --topo mytopo --test pingall' % custom )
p = pexpect.spawn(
'mn --custom %s --topo mytopo --test pingall' % custom )
p.expect( '0% dropped' )
p.expect( pexpect.EOF )
@@ -243,7 +255,7 @@ class testWalkthrough( unittest.TestCase ):
switches = [ 'user', 'ovsk' ]
for sw in switches:
p = pexpect.spawn( 'mn --switch %s --test iperf' % sw )
p.expect( "Results: \['([\d\.]+) .bits/sec'," )
p.expect( r"Results: \['([\d\.]+) .bits/sec'," )
bw = float( p.match.group( 1 ) )
self.assertTrue( bw > 0 )
p.expect( pexpect.EOF )
@@ -251,7 +263,7 @@ class testWalkthrough( unittest.TestCase ):
def testBenchmark( self ):
"Run benchmark and verify that it takes less than 2 seconds"
p = pexpect.spawn( 'mn --test none' )
p.expect( 'completed in ([\d\.]+) seconds' )
p.expect( r'completed in ([\d\.]+) seconds' )
time = float( p.match.group( 1 ) )
self.assertTrue( time < 2, 'Benchmark takes more than 2 seconds' )
@@ -275,7 +287,7 @@ class testWalkthrough( unittest.TestCase ):
self.assertEqual( ifcount, 2, 'Missing interfaces on s1' )
# verify that all hosts a reachable
p.sendline( 'pingall' )
p.expect( '(\d+)% dropped' )
p.expect( r'(\d+)% dropped' )
dropped = int( p.match.group( 1 ) )
self.assertEqual( dropped, 0, 'pingall failed')
p.expect( self.prompt )
@@ -327,11 +339,15 @@ class testWalkthrough( unittest.TestCase ):
'Github is not reachable; cannot download Pox' )
def testRemoteController( self ):
"Test Mininet using Pox controller"
# Satisfy pylint
assert self
if not os.path.exists( '/tmp/pox' ):
p = pexpect.spawn( 'git clone https://github.com/noxrepo/pox.git /tmp/pox' )
p = pexpect.spawn(
'git clone https://github.com/noxrepo/pox.git /tmp/pox' )
p.expect( pexpect.EOF )
pox = pexpect.spawn( '/tmp/pox/pox.py forwarding.l2_learning' )
net = pexpect.spawn( 'mn --controller=remote,ip=127.0.0.1,port=6633 --test pingall' )
net = pexpect.spawn(
'mn --controller=remote,ip=127.0.0.1,port=6633 --test pingall' )
net.expect( '0% dropped' )
net.expect( pexpect.EOF )
pox.sendintr()
+22 -10
View File
@@ -78,7 +78,6 @@ class MultiGraph( object ):
"Return list of graph edges"
return list( self.edges_iter( data=data, keys=keys ) )
def __getitem__( self, node ):
"Return link dict for given src node"
return self.edge[ node ]
@@ -101,7 +100,7 @@ class Topo( object ):
"Data center network representation for structured multi-trees."
def __init__( self, *args, **params ):
"""Topo object.
"""Topo object.
Optional named parameters:
hinfo: default host options
sopts: default switch options
@@ -111,7 +110,8 @@ class Topo( object ):
self.hopts = params.pop( 'hopts', {} )
self.sopts = params.pop( 'sopts', {} )
self.lopts = params.pop( 'lopts', {} )
self.ports = {} # ports[src][dst][sport] is port on dst that connects to src
# ports[src][dst][sport] is port on dst that connects to src
self.ports = {}
self.build( *args, **params )
def build( self, *args, **params ):
@@ -146,7 +146,7 @@ class Topo( object ):
return result
def addLink( self, node1, node2, port1=None, port2=None,
key=None, **opts ):
key=None, **opts ):
"""node1, node2: nodes to link together
port1, port2: ports (optional)
opts: link options (optional)
@@ -187,7 +187,7 @@ class Topo( object ):
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 ):
for _src, _dst, key, info in self.g.edges_iter( data=True, keys=True ):
node1, node2 = info[ 'node1' ], info[ 'node2' ]
if withKeys:
if withInfo:
@@ -207,7 +207,7 @@ class Topo( object ):
withInfo: return link info
returns: list of ( src, dst [,key, info ] )"""
links = list( self.iterLinks( withKeys, withInfo ) )
if not sorted:
if not sort:
return links
# Ignore info when sorting
tupleSize = 3 if withKeys else 2
@@ -256,7 +256,7 @@ class Topo( object ):
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 )
@@ -287,10 +287,13 @@ class Topo( object ):
return sorted( items, key=natural )
# Our idiom defines additional parameters in build(param...)
# pylint: disable=arguments-differ
class SingleSwitchTopo( Topo ):
"Single switch connected to k hosts."
def build( self, k=2, **opts ):
def build( self, k=2, **_opts ):
"k: number of hosts"
self.k = k
switch = self.addSwitch( 's1' )
@@ -302,7 +305,8 @@ class SingleSwitchTopo( Topo ):
class SingleSwitchReversedTopo( Topo ):
"""Single switch connected to k hosts, with reversed ports.
The lowest-numbered host is connected to the highest-numbered port.
Useful to verify that Mininet properly handles custom port numberings."""
Useful to verify that Mininet properly handles custom port
numberings."""
def build( self, k=2 ):
"k: number of hosts"
@@ -314,10 +318,16 @@ class SingleSwitchReversedTopo( Topo ):
port1=0, port2=( k - h + 1 ) )
class MinimalTopo( SingleSwitchTopo ):
"Minimal topology with two hosts and one switch"
def build( self ):
return SingleSwitchTopo.build( self, k=2 )
class LinearTopo( Topo ):
"Linear topology of k switches, with n hosts per switch."
def build( self, k=2, n=1, **opts):
def build( self, k=2, n=1, **_opts):
"""k: number of switches
n: number of hosts per switch"""
self.k = k
@@ -340,3 +350,5 @@ class LinearTopo( Topo ):
if lastSwitch:
self.addLink( switch, lastSwitch )
lastSwitch = switch
# pylint: enable=arguments-differ
+20 -8
View File
@@ -3,6 +3,9 @@
from mininet.topo import Topo
from mininet.net import Mininet
# The build() method is expected to do this:
# pylint: disable=arguments-differ
class TreeTopo( Topo ):
"Topology for a tree network with a given depth and fanout."
@@ -41,11 +44,19 @@ class TorusTopo( Topo ):
with the default controller or any Ethernet bridge
without STP turned on! It can be used with STP, e.g.:
# mn --topo torus,3,3 --switch lxbr,stp=1 --test pingall"""
def build( self, x, y ):
def build( self, x, y, n=1 ):
"""x: dimension of torus in x-direction
y: dimension of torus in y-direction
n: number of hosts per switch"""
if x < 3 or y < 3:
raise Exception( 'Please use 3x3 or greater for compatibility '
'with 2.1' )
'with 2.1' )
if n == 1:
genHostName = lambda loc, k: 'h%s' % ( loc )
else:
genHostName = lambda loc, k: 'h%sx%d' % ( loc, k )
hosts, switches, dpid = {}, {}, 0
# Create and wire interior
for i in range( 0, x ):
@@ -53,9 +64,11 @@ class TorusTopo( Topo ):
loc = '%dx%d' % ( i + 1, j + 1 )
# dpid cannot be zero for OVS
dpid = ( i + 1 ) * 256 + ( j + 1 )
switch = switches[ i, j ] = self.addSwitch( 's' + loc, dpid='%016x' % dpid )
host = hosts[ i, j ] = self.addHost( 'h' + loc )
self.addLink( host, switch )
switch = switches[ i, j ] = self.addSwitch(
's' + loc, dpid='%x' % dpid )
for k in range( 0, n ):
host = hosts[ i, j, k ] = self.addHost( genHostName( loc, k + 1 ) )
self.addLink( host, switch )
# Connect switches
for i in range( 0, x ):
for j in range( 0, y ):
@@ -65,5 +78,4 @@ class TorusTopo( Topo ):
self.addLink( sw1, sw2 )
self.addLink( sw1, sw3 )
# pylint: enable=arguments-differ
+156 -82
View File
@@ -25,7 +25,7 @@ def checkRun( cmd ):
return check_call( cmd.split( ' ' ) )
# pylint doesn't understand explicit type checking
# pylint: disable-msg=E1103
# pylint: disable=maybe-no-member
def oldQuietRun( *cmd ):
"""Run a command, routing stderr to stdout, and return the output.
@@ -56,6 +56,7 @@ def oldQuietRun( *cmd ):
# This is a bit complicated, but it enables us to
# monitor command output as it is happening
# pylint: disable=too-many-branches
def errRun( *cmd, **kwargs ):
"""Run a command and return stdout, stderr and return code
cmd: string or list of command and args
@@ -77,6 +78,7 @@ def errRun( *cmd, **kwargs ):
cmd = [ str( arg ) for arg in cmd ]
elif isinstance( cmd, list ) and shell:
cmd = " ".join( arg for arg in cmd )
debug( '*** errRun:', cmd, '\n' )
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
@@ -91,21 +93,31 @@ def errRun( *cmd, **kwargs ):
errDone = False
while not outDone or not errDone:
readable = poller.poll()
for fd, _event in readable:
for fd, event in readable:
f = fdtofile[ fd ]
data = f.read( 1024 )
if echo:
output( data )
if f == popen.stdout:
out += data
if data == '':
if event & POLLIN:
data = f.read( 1024 )
if echo:
output( data )
if f == popen.stdout:
out += data
if data == '':
outDone = True
elif f == popen.stderr:
err += data
if data == '':
errDone = True
else: # POLLHUP or something unexpected
if f == popen.stdout:
outDone = True
elif f == popen.stderr:
err += data
if data == '':
elif f == popen.stderr:
errDone = True
poller.unregister( fd )
returncode = popen.wait()
debug( out, err, returncode )
return out, err, returncode
# pylint: enable=too-many-branches
def errFail( *cmd, **kwargs ):
"Run a command using errRun and raise exception on nonzero exit"
@@ -119,8 +131,7 @@ def quietRun( cmd, **kwargs ):
"Run a command and return merged stdout and stderr"
return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
# pylint: enable-msg=E1103
# pylint: disable-msg=E1101
# pylint: enable=maybe-no-member
def isShellBuiltin( cmd ):
"Return True if cmd is a bash builtin."
@@ -133,8 +144,6 @@ def isShellBuiltin( cmd ):
isShellBuiltin.builtIns = None
# pylint: enable-msg=E1101
# Interface management
#
# Interfaces are managed as strings which are simply the
@@ -148,27 +157,42 @@ isShellBuiltin.builtIns = None
# live in the root namespace and thus do not have to be
# explicitly moved.
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, run=quietRun ):
"""Make a veth pair connecting intf1 and intf2.
intf1: string, interface
intf2: string, interface
node: node to run on or None (default)
returns: ip link add result"""
# Delete any old interfaces with the same names
run( 'ip link del ' + intf1 )
run( 'ip link del ' + intf2 )
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, node1=None, node2=None,
deleteIntfs=True, runCmd=None ):
"""Make a veth pair connnecting new interfaces intf1 and intf2
intf1: name for interface 1
intf2: name for interface 2
addr1: MAC address for interface 1 (optional)
addr2: MAC address for interface 2 (optional)
node1: home node for interface 1 (optional)
node2: home node for interface 2 (optional)
deleteIntfs: delete intfs before creating them
runCmd: function to run shell commands (quietRun)
raises Exception on failure"""
if not runCmd:
runCmd = quietRun if not node1 else node1.cmd
runCmd2 = quietRun if not node2 else node2.cmd
if deleteIntfs:
# Delete any old interfaces with the same names
runCmd( 'ip link del ' + intf1 )
runCmd2( 'ip link del ' + intf2 )
# Create new pair
ns1 = 1 if not node1 else node1.pid
ns2 = 1 if not node2 else node2.pid
if addr1 is None and addr2 is None:
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
cmdOutput = runCmd( 'ip link add name %s netns %s '
'type veth peer name %s '
'netns %s' % ( intf1, ns1, intf2, ns2 ) )
else:
cmd = ( 'ip link add name ' + intf1 + ' address ' + addr1 +
' type veth peer name ' + intf2 + ' address ' + addr2 )
cmdOutput = run( cmd )
if cmdOutput == '':
return True
else:
error( "Error creating interface pair: %s " % cmdOutput )
return False
cmdOutput = runCmd( 'ip link add name %s '
'address %s netns %s '
'type veth peer name %s '
'address %s '
'netns %s' %
( intf1, addr1, ns1, intf2, addr2, ns2 ) )
if cmdOutput:
raise Exception( "Error creating interface pair (%s,%s): %s " %
( intf1, intf2, cmdOutput ) )
def retry( retries, delaySecs, fn, *args, **keywords ):
"""Try something several times before giving up.
@@ -202,8 +226,8 @@ def moveIntfNoRetry( intf, dstNode, printError=False ):
return False
return True
def moveIntf( intf, dstNode, srcNode=None, printError=True,
retries=3, delaySecs=0.001 ):
def moveIntf( intf, dstNode, printError=True,
retries=3, delaySecs=0.001 ):
"""Move interface to node, retrying on failure.
intf: string, interface
dstNode: destination Node
@@ -296,7 +320,7 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
def ipParse( ip ):
"Parse an IP address and return an unsigned int."
args = [ int( arg ) for arg in ip.split( '.' ) ]
while ( len(args) < 4 ):
while len(args) < 4:
args.append( 0 )
return ipNum( *args )
@@ -427,9 +451,12 @@ def fixLimits():
sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 )
#Increase number of PTYs for nodes
sysctlTestAndSet( 'kernel.pty.max', 20000 )
except:
# pylint: disable=broad-except
except Exception:
warn( "*** Error setting resource limits. "
"Mininet's performance may be affected.\n" )
# pylint: enable=broad-except
def mountCgroups():
"Make sure cgroups file system is mounted"
@@ -481,47 +508,91 @@ def custom( cls, **params ):
customized.__name__ = 'custom(%s,%s)' % ( cls, params )
return customized
def splitArgs( argstr ):
"""Split argument string into usable python arguments
argstr: argument string with format fn,arg2,kw1=arg3...
returns: fn, args, kwargs"""
split = argstr.split( ',' )
fn = split[ 0 ]
params = split[ 1: ]
def parseArgs( argstr ):
"""Parse argument string
returns args, kwargs"""
# One step at a time: support param=[a,b,c]
paramre = r'\[[^\]]*\]|[^,\[\]]+'
paramre = r'\w+=\[[^\]]*\]|\w+\[^,\[\]]+' + paramre
params = re.findall( paramre, argstr )
# Parse lists
for i, arg in enumerate( params ):
if arg.startswith( '[' ) and arg.endswith( ']' ):
arg = arg[ 1 : -1 ]
print "recurse on", arg
params[ i ] = parseArgs( args )
# Convert int and float args; removes the need for function
# to be flexible with input arg formats.
args = [ makeNumeric( s ) for s in params if '=' not in s ]
kwargs = {}
for s in [ p for p in params if '=' in p ]:
key, val = s.split( '=', 1 )
kwargs[ key ] = makeNumeric( val )
return fn, args, kwargs
key, arg = s.split( '=', 1 )
if arg.startswith( '[' ) and arg.endswith( ']' ):
arg = arg[ 1 : -1 ]
print 'recurse on', arg
arg = parseArgs( arg )[ 0 ]
else:
arg = makeNumeric( arg )
kwargs[ key ] = arg
return args, kwargs
def customConstructor( constructors, argStr ):
"""Return custom constructor based on argStr
The args and key/val pairs in argsStr will be automatically applied
when the generated constructor is later used.
def splitArgs( argstr ):
"""Split argument string into usable python arguments
argstr: argument string with format fn,arg2,kw1=arg3...
returns: fn, args, kwargs"""
args, kwargs = parseArgs( argstr )
return args[ 0 ], args[ 1: ], kwargs
def customClass( classes, argStr ):
"""Return customized class based on argStr
The args and key/val pairs in argStr will be automatically applied
when the generated class is later used.
"""
cname, newargs, kwargs = splitArgs( argStr )
constructor = constructors.get( cname, None )
if not constructor:
cname, args, kwargs = splitArgs( argStr )
cls = classes.get( cname, None )
if not cls:
raise Exception( "error: %s is unknown - please specify one of %s" %
( cname, constructors.keys() ) )
( cname, classes.keys() ) )
if not args and not kwargs:
return cls
def customized( name, *args, **params ):
"Customized constructor, useful for Node, Link, and other classes"
params = params.copy()
params.update( kwargs )
if not newargs:
return constructor( name, *args, **params )
if args:
warn( 'warning: %s replacing %s with %s\n' % (
constructor, args, newargs ) )
return constructor( name, *newargs, **params )
return specialClass( cls, append=args, defaults=kwargs )
def specialClass( cls, prepend=None, append=None,
defaults=None, override=None ):
"""Like functools.partial, but it returns a class
prepend: arguments to prepend to argument list
append: arguments to append to argument list
defaults: default values for keyword arguments
override: keyword arguments to override"""
if prepend is None:
prepend = []
if append is None:
append = []
if defaults is None:
defaults = {}
if override is None:
override = {}
class CustomClass( cls ):
"Customized subclass with preset args/params"
def __init__( self, *args, **params ):
newparams = defaults.copy()
newparams.update( params )
newparams.update( override )
cls.__init__( self, *( list( prepend ) + list( args ) +
list( append ) ),
**newparams )
CustomClass.__name__ = '%s%s' % ( cls.__name__, defaults )
return CustomClass
customized.__name__ = 'customConstructor(%s)' % argStr
return customized
def buildTopo( topos, topoStr ):
"""Create topology from string with format (object, arg1, arg2,...).
@@ -545,23 +616,26 @@ def ensureRoot():
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
"""Wait until server is listening on port.
returns True if server is listening"""
run = ( client.cmd if client else
partial( quietRun, shell=True ) )
if not run( 'which telnet' ):
runCmd = ( client.cmd if client else
partial( quietRun, shell=True ) )
if not runCmd( 'which telnet' ):
raise Exception('Could not find telnet' )
# pylint: disable=maybe-no-member
serverIP = server if isinstance( server, basestring ) else server.IP()
cmd = ( 'sh -c "echo A | telnet -e A %s %s"' %
( serverIP, port ) )
cmd = ( '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')
result = runCmd( cmd )
while 'Connected' not in result:
if 'No route' in result:
rtable = runCmd( 'route' )
error( 'no route to %s:\n%s' % ( server, rtable ) )
return False
if timeout and time >= timeout:
error( 'could not connect to %s on port %d\n' % ( server, port ) )
return False
debug( 'waiting for', server, 'to listen on port', port, '\n' )
info( '.' )
sleep( .5 )
time += .5
result = runCmd( cmd )
return True
+179 -98
View File
@@ -5,9 +5,9 @@
*
* - closing all file descriptors except stdin/out/error
* - detaching from a controlling tty using setsid
* - running in network and mount namespaces
* - running in network and other namespaces
* - printing out the pid of a process so we can identify it later
* - attaching to a namespace and cgroup
* - attaching to namespace(s) and cgroup
* - setting RT scheduling
*
* Partially based on public domain setsid(1)
@@ -24,21 +24,26 @@
#include <sched.h>
#include <ctype.h>
#include <sys/mount.h>
#include <sys/wait.h>
#if !defined(VERSION)
#define VERSION "(devel)"
#endif
void usage(char *name)
{
printf("Execution utility for Mininet\n\n"
"Usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...\n\n"
"Usage: %s [-cdmnPpu] [-a pid] [-g group] [-r rtprio] cmd args...\n\n"
"Options:\n"
" -c: close all file descriptors except stdin/out/error\n"
" -d: detach from tty by calling setsid()\n"
" -n: run in new network and mount namespaces\n"
" -m: run in a new mount namespace\n"
" -n: run in a new network namespace\n"
" -P: run in a new pid namespace\n"
" -u: run in a new UTS (ipc, hostname) namespace\n"
" -p: print ^A + pid\n"
" -a pid: attach to pid's network and mount namespaces\n"
" -a pid: attach to pid's namespaces\n"
" -g group: add to cgroup\n"
" -r rtprio: run with SCHED_RR (usually requires -g)\n"
" -v: print version\n",
@@ -51,6 +56,7 @@ int setns(int fd, int nstype)
return syscall(__NR_setns, fd, nstype);
}
/* Validate alphanumeric path foo1/bar2/baz */
void validate(char *path)
{
@@ -63,6 +69,7 @@ void validate(char *path)
}
}
/* Add our pid to cgroup */
void cgroup(char *gname)
{
@@ -92,107 +99,181 @@ void cgroup(char *gname)
}
}
/* Attach to ns 'name' if present */
int attachns( pid_t pid, char *name ) {
char path[PATH_MAX];
int nsid;
sprintf(path, "/proc/%d/ns/%s", pid, name) ;
if ((nsid = open(path, O_RDONLY)) < 0)
return nsid;
if (setns(nsid, 0) != 0) {
perror("setns");
return 1;
}
return 0;
}
/* Attach to pid's namespaces - returns true if pidns */
int attach(int pid) {
char *cwd = get_current_dir_name();
char path[PATH_MAX];
int pidns = 0;
attachns(pid, "net");
attachns(pid, "uts");
if ( attachns(pid, "pid") == 0 )
pidns = 1;
if (attachns(pid, "mnt") != 0) {
/* Plan B: chroot into pid's root file system */
sprintf(path, "/proc/%d/root", pid);
if (chroot(path) < 0) {
perror(path);
}
}
/* chdir to correct working directory */
if (chdir(cwd) != 0) {
perror(cwd);
}
return pidns;
}
int main(int argc, char *argv[])
{
int c;
int fd;
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)
/* Argument flags */
int flags = 0;
int closefds = 0;
int attachpid = 0;
char *cgrouparg = NULL;
int detachtty = 0;
int printpid = 0;
int rtprio = 0;
int pidns = 0;
while ((c = getopt(argc, argv, "+cdmnPpa:g:r:uvh")) != -1)
switch(c) {
case 'c':
/* close file descriptors except stdin/out/error */
for (fd = getdtablesize(); fd > 2; fd--)
close(fd);
break;
case 'd':
/* detach from tty */
if (getpgrp() == getpid()) {
switch(fork()) {
case -1:
perror("fork");
return 1;
case 0: /* child */
break;
default: /* parent */
return 0;
}
}
setsid();
break;
case 'n':
/* run in network and mount namespaces */
if (unshare(CLONE_NEWNET|CLONE_NEWNS) == -1) {
perror("unshare");
case 'c': closefds = 1; break;
case 'd': detachtty = 1; break;
case 'm': flags |= CLONE_NEWNS; break;
case 'n': flags |= CLONE_NEWNET; break;
case 'p': printpid = 1; break;
case 'P': flags |= CLONE_NEWPID; break;
case 'a': attachpid = atoi(optarg);break;
case 'g': cgrouparg = optarg ; break;
case 'r': rtprio = atoi(optarg); break;
case 'u': flags |= CLONE_NEWUTS; break;
case 'v': printf("%s\n", VERSION); exit(0);
case 'h': usage(argv[0]); exit(0);
default: usage(argv[0]); exit(1);
}
if (closefds) {
/* close file descriptors except stdin/out/error */
int fd;
for (fd = getdtablesize(); fd > 2; fd--) close(fd);
}
/* XXX We should not fork twice if we don't need to!! */
if (detachtty) {
/* detach from tty */
if (getpgrp() == getpid()) {
switch(fork()) {
case -1:
perror("fork");
return 1;
case 0: /* child */
break;
default: /* parent */
return 0;
}
/* mount sysfs to pick up the new network namespace */
if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) {
perror("mount");
return 1;
}
break;
case 'p':
/* print pid */
printf("\001%d\n", getpid());
fflush(stdout);
break;
case 'a':
/* Attach to pid's network namespace and mount namespace */
pid = atoi(optarg);
sprintf(path, "/proc/%d/ns/net", pid);
nsid = open(path, O_RDONLY);
if (nsid < 0) {
perror(path);
return 1;
}
if (setns(nsid, 0) != 0) {
perror("setns");
return 1;
}
/* Plan A: call setns() to attach to mount namespace */
sprintf(path, "/proc/%d/ns/mnt", pid);
nsid = open(path, O_RDONLY);
if (nsid < 0 || setns(nsid, 0) != 0) {
/* 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 */
cgroup(optarg);
break;
case 'r':
/* Set RT scheduling priority */
sp.sched_priority = atoi(optarg);
if (sched_setscheduler(getpid(), SCHED_RR, &sp) < 0) {
perror("sched_setscheduler");
return 1;
}
break;
case 'v':
printf("%s\n", VERSION);
exit(0);
case 'h':
usage(argv[0]);
exit(0);
default:
usage(argv[0]);
exit(1);
}
setsid();
}
if (attachpid) {
/* Attach to existing namespace(s) */
pidns = attach(attachpid);
}
else {
/* Create new namespace(s) */
if (unshare(flags) == -1) {
perror("unshare");
return 1;
}
}
/* Use a new process group so we can use killpg */
setpgid( 0, 0 );
if ( flags & CLONE_NEWPID || pidns ) {
/* For pid namespace, we need to fork and wait for child ;-( */
pid_t pid = fork();
switch(pid) {
int status;
case -1:
perror("fork");
return 1;
case 0:
/* child continues below */
break;
default:
/* We print the *child pid* if needed */
if (printpid) {
printf("\001%d\n", pid);
fflush(stdout);
}
/* Parent needs to wait for child and exit */
wait(&status);
return 0;
}
}
else if (printpid) {
/* If we're in a pid namespace, parent prints our pid instead */
printf("\001%d\n", getpid());
fflush(stdout);
}
if (flags & CLONE_NEWNS && !attachpid) {
/* Child remounts /proc for ps */
if (mount("proc", "/proc", "proc", MS_MGC_VAL, NULL) == -1) {
perror("mountproc");
}
}
if (cgrouparg) {
/* Attach to cgroup */
cgroup(cgrouparg);
}
if (flags & CLONE_NEWNET & CLONE_NEWNS) {
/* Mount sysfs to pick up the new network namespace */
if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) {
perror("mount");
return 1;
}
}
if (rtprio != 0) {
/* Set RT scheduling priority */
static struct sched_param sp;
sp.sched_priority = atoi(optarg);
if (sched_setscheduler(getpid(), SCHED_RR, &sp) < 0) {
perror("sched_setscheduler");
return 1;
}
}
if (optind < argc) {
execvp(argv[optind], &argv[optind]);
+56 -38
View File
@@ -25,28 +25,40 @@ clean=false
declare -a hosts=()
user=$(whoami)
SSHDIR=/tmp/mn/ssh
USERDIR=/home/$user/.ssh
usage=$'./clustersetup.sh [ -p|h|c ] [ host1 ] [ host2 ] ...\n
USERDIR=$HOME/.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.
$USERDIR 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
$USERDIR 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
if ! ssh-keygen -t rsa -C "Cluster_Edition_Key" -f $USERDIR/cluster_key -N ''; then
echo "key generation returned error" $?
fi
echo "***adding to authorized_keys"
if ! grep -f $USERDIR/cluster_key.pub $USERDIR/authorized_keys > /dev/null; then
cat $USERDIR/cluster_key.pub >> $USERDIR/authorized_keys
else
echo "$USERDIR/cluster_key.pub already in $USERDIR/authorized_keys"
fi
echo "***configuring ssh"
echo "IdentityFile $USERDIR/cluster_key" >> $USERDIR/config
echo "IdentityFile $USERDIR/id_rsa" >> $USERDIR/config
if ! grep cluster_key $USERDIR/config; then
echo "IdentityFile $USERDIR/cluster_key" >> $USERDIR/config
echo "IdentityFile $USERDIR/id_rsa" >> $USERDIR/config
fi
for host in $hosts; do
echo "***copying public key to $host"
@@ -56,58 +68,65 @@ persistentSetup() {
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"
if ! grep cluster_key $USERDIR/config > /dev/null; then
echo 'IdentityFile $USERDIR/cluster_key' >> $USERDIR/config
echo 'IdentityFile $USERDIR/id_rsa' >> $USERDIR/config;
fi"
done
for host in $hosts; do
echo "***copying known_hosts to $host"
scp $USERDIR/known_hosts $user@$host:$USERDIR/cluster_known_hosts
echo "***appending"
ssh $user@$host "
cat $USERDIR/cluster_known_hosts >> $USERDIR/known_hosts
if ! grep -f $USERDIR/cluster_known_hosts $USERDIR/known_hosts > /dev/null; then
cat $USERDIR/cluster_known_hosts >> $USERDIR/known_hosts;
fi;
rm $USERDIR/cluster_known_hosts"
done
echo "***done with persistent setup"
}
tempSetup() {
echo "***creating temporary ssh directory"
mkdir -p $SSHDIR
mkdir -p $SSHDIR
echo "***creating key pair"
ssh-keygen -t rsa -C "Cluster_Edition_Key" -f /tmp/mn/ssh/id_rsa -N '' &> /dev/null
ssh-keygen -t rsa -C "Cluster_Edition_Key" -f $SSHDIR/id_rsa -N '' &> /dev/null
echo "***mounting temporary ssh directory"
sudo mount --bind $SSHDIR /home/$user/.ssh
sudo mount --bind $SSHDIR $USERDIR
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 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 $SSHDIR
cp $USERDIR/authorized_keys $SSHDIR/authorized_keys
sudo mount --bind $SSHDIR $USERDIR"
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
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
ssh $user@$host "sudo umount $USERDIR
sudo rm -rf $SSHDIR"
done
echo "**unmounting local directories"
sudo umount /home/$user/.ssh
sudo umount $USERDIR
echo "***removing temporary ssh directory"
sudo rm -rf $SSHDIR
echo "done!"
@@ -145,9 +164,8 @@ if $showHelp; then
fi
for i in "$@"; do
output=$(getent ahostsv4 "$i")
if [ -z "$output" ]; then
echo '***WARNING: could not find hostname "$i"'
if ! getent ahostsv4 $i > /dev/null; then
echo "***WARNING: could not find hostname $i"
echo ""
else
hosts+="$i "
+3 -3
View File
@@ -30,7 +30,7 @@ def fixParam( line ):
def fixReturns( line ):
"Change returns: foo to @return foo"
return re.sub( 'returns:', r'@returns', line )
def fixLine( line ):
global comment
match = spaces.match( line )
@@ -69,7 +69,7 @@ def funTest():
).splitlines( True )
fixLines( testFun )
def fixLines( lines, fid ):
for line in lines:
os.write( fid, fixLine( line ) )
@@ -86,4 +86,4 @@ if __name__ == '__main__':
+91 -61
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Mininet install script for Ubuntu (and Debian Lenny)
# Mininet install script for Ubuntu (and Debian Wheezy+)
# Brandon Heller (brandonh@stanford.edu)
# Fail on error
@@ -102,7 +102,10 @@ OF13_SWITCH_REV=${OF13_SWITCH_REV:-""}
function kernel {
echo "Install Mininet-compatible kernel if necessary"
sudo apt-get update
$install linux-image-$KERNEL_NAME
if ! $install linux-image-$KERNEL_NAME; then
echo "Could not install linux-image-$KERNEL_NAME"
echo "Skipping - assuming installed kernel is OK."
fi
}
function kernel_clean {
@@ -189,17 +192,24 @@ function of13 {
fi
# Install netbee
NBEESRC="nbeesrc-jan-10-2013"
if [ "$DIST" = "Ubuntu" ] && version_ge $RELEASE 14.04; then
NBEESRC="nbeesrc-feb-24-2015"
NBEEDIR="netbee"
else
NBEESRC="nbeesrc-jan-10-2013"
NBEEDIR="nbeesrc-jan-10-2013"
fi
NBEEURL=${NBEEURL:-http://www.nbee.org/download/}
wget -nc ${NBEEURL}${NBEESRC}.zip
unzip ${NBEESRC}.zip
cd ${NBEESRC}/src
cd ${NBEEDIR}/src
cmake .
make
cd $BUILD_DIR/
sudo cp ${NBEESRC}/bin/libn*.so /usr/local/lib
sudo cp ${NBEEDIR}/bin/libn*.so /usr/local/lib
sudo ldconfig
sudo cp -R ${NBEESRC}/include/ /usr/
sudo cp -R ${NBEEDIR}/include/ /usr/
# Resume the install:
cd $BUILD_DIR/ofsoftswitch13
@@ -258,53 +268,63 @@ function ubuntuOvs {
OVS_SRC=$BUILD_DIR/openvswitch
OVS_TARBALL_LOC=http://openvswitch.org/releases
if [ "$DIST" = "Ubuntu" ] && version_ge $RELEASE 12.04; then
rm -rf $OVS_SRC
mkdir -p $OVS_SRC
cd $OVS_SRC
if ! echo "$DIST" | egrep "Ubuntu|Debian" > /dev/null; then
echo "OS must be Ubuntu or Debian"
$cd BUILD_DIR
return
fi
if [ "$DIST" = "Ubuntu" ] && ! version_ge $RELEASE 12.04; then
echo "Ubuntu version must be >= 12.04"
cd $BUILD_DIR
return
fi
if [ "$DIST" = "Debian" ] && ! version_ge $RELEASE 7.0; then
echo "Debian version must be >= 7.0"
cd $BUILD_DIR
return
fi
if wget $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz 2> /dev/null; then
tar xzf openvswitch-$OVS_RELEASE.tar.gz
else
echo "Failed to find OVS at $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz"
cd $BUILD_DIR
return
fi
rm -rf $OVS_SRC
mkdir -p $OVS_SRC
cd $OVS_SRC
# Remove any old packages
$remove openvswitch-common openvswitch-datapath-dkms openvswitch-controller \
openvswitch-pki openvswitch-switch
# Get build deps
$install build-essential fakeroot debhelper autoconf automake libssl-dev \
pkg-config bzip2 openssl python-all procps python-qt4 \
python-zopeinterface python-twisted-conch dkms
# Build OVS
cd $BUILD_DIR/openvswitch/openvswitch-$OVS_RELEASE
DEB_BUILD_OPTIONS='parallel=2 nocheck' fakeroot debian/rules binary
cd ..
$pkginst openvswitch-common_$OVS_RELEASE*.deb openvswitch-datapath-dkms_$OVS_RELEASE*.deb \
openvswitch-pki_$OVS_RELEASE*.deb openvswitch-switch_$OVS_RELEASE*.deb
if $pkginst openvswitch-controller_$OVS_RELEASE*.deb; then
echo "Ignoring error installing openvswitch-controller"
fi
modinfo openvswitch
sudo ovs-vsctl show
# 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
if wget $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz 2> /dev/null; then
tar xzf openvswitch-$OVS_RELEASE.tar.gz
else
echo "Failed to install Open vSwitch. OS must be Ubuntu >= 12.04"
cd $BUILD_DIR
return
echo "Failed to find OVS at $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz"
cd $BUILD_DIR
return
fi
# Remove any old packages
$remove openvswitch-common openvswitch-datapath-dkms openvswitch-controller \
openvswitch-pki openvswitch-switch
# Get build deps
$install build-essential fakeroot debhelper autoconf automake libssl-dev \
pkg-config bzip2 openssl python-all procps python-qt4 \
python-zopeinterface python-twisted-conch dkms
# Build OVS
cd $BUILD_DIR/openvswitch/openvswitch-$OVS_RELEASE
DEB_BUILD_OPTIONS='parallel=2 nocheck' fakeroot debian/rules binary
cd ..
$pkginst openvswitch-common_$OVS_RELEASE*.deb openvswitch-datapath-dkms_$OVS_RELEASE*.deb \
openvswitch-pki_$OVS_RELEASE*.deb openvswitch-switch_$OVS_RELEASE*.deb
if $pkginst openvswitch-controller_$OVS_RELEASE*.deb; then
echo "Ignoring error installing openvswitch-controller"
fi
modinfo openvswitch
sudo ovs-vsctl show
# 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
}
@@ -319,14 +339,17 @@ function ovs {
return
fi
# Manually installing openvswitch-datapath may be necessary
# for manually built kernel .debs using Debian's defective kernel
# packaging, which doesn't yield usable headers.
if ! dpkg --get-selections | grep openvswitch-datapath; then
# If you've already installed a datapath, assume you
# know what you're doing and don't need dkms datapath.
# Otherwise, install it.
$install openvswitch-datapath-dkms
if [ "$DIST" = "Ubuntu" ] && ! version_ge $RELEASE 14.04; then
# Older Ubuntu versions need openvswitch-datapath/-dkms
# Manually installing openvswitch-datapath may be necessary
# for manually built kernel .debs using Debian's defective kernel
# packaging, which doesn't yield usable headers.
if ! dpkg --get-selections | grep openvswitch-datapath; then
# If you've already installed a datapath, assume you
# know what you're doing and don't need dkms datapath.
# Otherwise, install it.
$install openvswitch-datapath-dkms
fi
fi
$install openvswitch-switch
@@ -343,7 +366,7 @@ function ovs {
else
echo "Attempting to install openvswitch-testcontroller"
if ! $install openvswitch-testcontroller; then
echo "Failed - giving up"
echo "Failed - skipping openvswitch-testcontroller"
fi
fi
@@ -554,9 +577,9 @@ function vm_other {
# BLACKLIST=/etc/modprobe.d/blacklist
#fi
#sudo sh -c "echo 'blacklist net-pf-10\nblacklist ipv6' >> $BLACKLIST"
echo "Disabling IPv6"
# Disable IPv6
if ! grep 'disable IPv6' /etc/sysctl.conf; then
if ! grep 'disable_ipv6' /etc/sysctl.conf; then
echo 'Disabling IPv6'
echo '
# Mininet: disable IPv6
@@ -564,6 +587,13 @@ net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf > /dev/null
fi
# Since the above doesn't disable neighbor discovery, also do this:
if ! grep 'ipv6.disable' /etc/default/grub; then
sudo sed -i -e \
's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 /' \
/etc/default/grub
sudo update-grub
fi
# Disabling IPv6 breaks X11 forwarding via ssh
line='AddressFamily inet'
file='/etc/ssh/sshd_config'
+1 -1
View File
@@ -40,5 +40,5 @@ if [ -d $rootdir -a -x $rootdir/bin/bash ]; then
cmd="chroot $rootdir /bin/bash -c $cmd"
fi
cmd="exec sudo mnexec $cg -a $pid $cmd"
cmd="exec sudo -E 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( "egrep -or 'Mininet [0-9\.]+\w*' *", shell=True )
lines = co( "egrep -or 'Mininet [0-9\.\+]+\w*' *", shell=True )
error = False
+2 -2
View File
@@ -323,7 +323,7 @@ zerombr yes
clearpart --all --initlabel
#Automatic partitioning
autopart
#System authorization infomation
#System authorization information
auth --useshadow --enablemd5
#Firewall configuration
firewall --disabled
@@ -881,7 +881,7 @@ def getMininetVersion( vm ):
return version
def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None,
def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None,
runFunction=None, **runArgs ):
"""Boot and test VM
tests: list of tests to run
+5 -2
View File
@@ -11,7 +11,8 @@ 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 sed -i -e 's/splash//' /etc/default/grub
sudo sed -i -e 's/quiet/text/' /etc/default/grub
sudo update-grub
# Update from official archive
sudo apt-get update
@@ -34,7 +35,9 @@ git clone git://github.com/mininet/mininet
# Optionally check out branch
if [ "$1" != "" ]; then
pushd mininet
git checkout -b $1 $1
#git checkout -b $1 $1
# TODO branch will in detached HEAD state if it is not master
git checkout $1
popd
fi
# Install Mininet