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
32 changed files with 3058 additions and 1080 deletions

No files matched your search

+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.0
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.0 License
Mininet 2.2.1 License
Copyright (c) 2013-2014 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
+19 -43
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.0
Mininet 2.2.1
### What is Mininet?
@@ -68,44 +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 a 1.3-compatible Wireshark dissector using
Loxigen
- `install.sh -y` installs the 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` (see `examples/`)
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.
* A `--nat` option which connects a Mininet network to your LAN using NAT
(For this to work correctly, Mininet's `--ipbase` subnet should not
overlap with any external or internet IP addresses you wish to use)
- You can now easily install Mininet on a Raspberry Pi ;-)
* An improved MiniEdit GUI (`examples/miniedit.py`) - thanks to
Gregory Gee
* Support for multiple `--custom` arguments to `mn`
* Experimental cluster support - consult the
[documentation](http://docs.mininet.org) for details -
as well as `examples/cluster.py` and an experimental `--cluster`
option for topologies built with the default `Host` and `OVSSwitch`
classes:
`mn --cluster localhost,server1,server2`
Note that examples contain experimental features which might
"graduate" into mainline Mininet in the future, but they should
not be considered a stable part of the Mininet API!
A number of bugs have also been fixed, most notably multiple link
support in `Topo()`. See github issues and the release notes on
the Mininet wiki for additional information.
- Additional information for this release and previous releases
may be found in the release notes on docs.mininet.org
### Installation
@@ -129,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,
@@ -142,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
+50 -47
View File
@@ -25,30 +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,
@@ -61,28 +62,30 @@ SWITCHES = { 'user': UserSwitch,
'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
@@ -95,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"
@@ -158,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 )
@@ -197,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' )
@@ -238,7 +238,7 @@ 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"
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"
@@ -280,34 +280,39 @@ class MininetRunner( object ):
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':
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:
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 )
@@ -330,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,
+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()
+719 -249
View File
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -19,8 +19,7 @@ class ClusterCLI( CLI ):
'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
+8 -1
View File
@@ -27,11 +27,18 @@ 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"""
+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 )
+36 -21
View File
@@ -6,20 +6,25 @@ 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,29 +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 ):
router = self.addNode( 'r0', cls=LinuxRouter, ip='192.168.1.1/24' )
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' )
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' } )
for h, s in [ (h1, s1), (h2, s2), (h3, s3) ]:
self.addLink( h, s )
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()
+2 -2
View File
@@ -49,7 +49,7 @@ from mininet.log import info, setLogLevel
from mininet.net import Mininet, VERSION
from mininet.util import netParse, ipAdd, quietRun
from mininet.util import buildTopo
from mininet.util import custom, customConstructor
from mininet.util import custom, customClass
from mininet.term import makeTerm, cleanUpScreens
from mininet.node import Controller, RemoteController, NOX, OVSController
from mininet.node import CPULimitedHost, Host, Node
@@ -3222,7 +3222,7 @@ class MiniEdit( Frame ):
return
self.newTopology()
topo = buildTopo( TOPOS, self.options.topo )
link = customConstructor( LINKS, self.options.link )
link = customClass( LINKS, self.options.link )
importNet = Mininet(topo=topo, build=False, link=link)
importNet.build()
+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()
+1 -2
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()
+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 )
+81 -52
View File
@@ -25,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', pattern ] )
pids = co( [ 'pgrep', '-f', pattern ] ).split( '\n' )
except CalledProcessError:
pids = ''
pids = []
# Don't kill init
pids = [ pid for pid in pids if pid and pid != '1' ]
if pids:
sh( 'pkill -9 -f %s' % pattern )
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( "*** 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 )
info( "*** Cleanup complete.\n" )
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
+38 -13
View File
@@ -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."
+83 -15
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 ):
@@ -49,7 +50,11 @@ class Intf( object ):
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 )
@@ -192,9 +197,10 @@ 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"
@@ -216,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
@@ -367,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
@@ -405,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
@@ -419,6 +437,12 @@ 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."
@@ -427,24 +451,33 @@ class Link( object ):
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)"""
# Leave this as a class method for now
assert cls
return makeIntfPair( intfname1, intfname2, addr1, addr2 )
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"
@@ -453,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,
+46 -32
View File
@@ -102,12 +102,13 @@ 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.0"
VERSION = "2.2.1"
class Mininet( object ):
"Network emulation with hosts spawned in network namespaces."
@@ -356,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() )
@@ -401,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:
@@ -414,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' )
@@ -458,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 ):
@@ -481,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()
@@ -495,20 +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 + ' ' )
@@ -712,27 +732,25 @@ class Mininet( object ):
# XXX This should be cleaned up
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
seconds=5):
seconds=5, port=5001):
"""Run iperf between two hosts.
hosts: list of hosts; if None, uses first and last hosts
l4Type: string, one of [ TCP, UDP ]
udpBw: bandwidth target for UDP test
fmt: iperf format argument if any
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
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 '
@@ -741,20 +759,16 @@ class Mininet( object ):
raise Exception( 'Unexpected l4 type: %s' % l4Type )
if fmt:
iperfArgs += '-f %s ' % fmt
server.sendCmd( iperfArgs + '-s', printPid=True )
servout = ''
while server.lastPid is None:
servout += server.monitor()
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':
+288 -170
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:
@@ -57,8 +63,8 @@ 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
@@ -68,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)"""
@@ -79,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
@@ -96,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
@@ -114,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 ]
'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
@@ -151,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
@@ -177,13 +196,61 @@ 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)
@@ -194,10 +261,11 @@ class Node( object ):
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
@@ -236,6 +304,7 @@ 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 )
@@ -258,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 ]
@@ -284,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.
@@ -298,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
@@ -339,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
@@ -372,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
@@ -503,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."
@@ -671,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
@@ -848,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 ):
@@ -893,6 +964,12 @@ class Switch( Node ):
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"
intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
@@ -999,73 +1076,32 @@ class UserSwitch( Switch ):
self.cmd( 'kill %ofprotocol' )
super( UserSwitch, self ).stop( 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."
deleteIntfs: delete interfaces? (True)"""
quietRun( 'ovs-dpctl del-dp ' + self.dp )
self.cmd( 'kill %ovs-openflowd' )
super( OVSLegacyKernelSwitch, 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 ):
@@ -1089,23 +1125,23 @@ class OVSSwitch( Switch ):
version = quietRun( 'ovs-vsctl --version' )
cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ]
@classmethod
def isOldOVS( cls ):
def isOldOVS( self ):
"Is OVS ersion < 1.10?"
return ( StrictVersion( cls.OVSVersion ) <
return ( StrictVersion( self.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 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
@@ -1116,13 +1152,13 @@ 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, update=False ):
"""Return ovsdb UUIDs for our controllers
@@ -1140,64 +1176,109 @@ class OVSSwitch( Switch ):
def connected( self ):
"Are we connected to at least one of our controllers?"
for uuid in self.controllerUUIDs():
if 'true' in self.cmd( 'ovs-vsctl -- get Controller',
uuid, 'is_connected' ):
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 )
'OVS kernel switch does not work in a network namespace' )
int( self.dpid, 16 ) # DPID must be a hex string
# Interfaces and controllers
intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
'-- set Interface %s ' % intf +
'ofport_request=%s ' % self.ports[ intf ]
for intf in self.intfList()
if self.ports[ intf ] and not intf.IP() )
clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
for c in controllers )
# 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() )
# 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 )
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 )
# Do it!!
self.cmd( cmd )
# Reconnect quickly to controllers (1s vs. 15s max_backoff)
uuids = [ '-- set Controller %s max_backoff=1000' % uuid
for uuid in self.controllerUUIDs() ]
if uuids:
self.cmd( 'ovs-vsctl', *uuids )
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
for intf in self.intfList():
self.TCReapply( intf )
if not self.batch:
for intf in self.intfList():
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.
@@ -1207,6 +1288,22 @@ class OVSSwitch( Switch ):
self.cmd( 'ip link del', self )
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
@@ -1214,13 +1311,24 @@ 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 ):
"Indigo Virtual Switch"
@@ -1247,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"
@@ -1296,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
@@ -1400,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.
@@ -1422,6 +1535,7 @@ class RYU( Controller ):
cdir=ryuCoreDir,
**kwargs )
class RemoteController( Controller ):
"Controller running outside of Mininet's control."
@@ -1465,3 +1579,7 @@ def DefaultController( name, controllers=DefaultControllers, **kwargs ):
if not controller:
raise Exception( 'Could not find a default OpenFlow controller' )
return controller( name, **kwargs )
def NullController( *_args, **_kwargs ):
"Nonexistent controller - simply returns None"
return None
+122 -42
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)"
@@ -59,23 +61,30 @@ 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"
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
inetIntf: interface for internet access
subnet: Mininet subnet (default 10.0/8)="""
subnet: Mininet subnet (default 10.0/8)
flush: flush iptables before installing NAT rules"""
super( NAT, self ).__init__( name, **params )
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 ):
"""Configure the NAT and iptables"""
@@ -84,27 +93,24 @@ class NAT( Node ):
if not self.localIntf:
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
# 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',
'-i', self.inetIntf, '-d', self.subnet, '-j ACCEPT' )
'-o', self.localIntf, '-d', self.subnet,'-j ACCEPT' )
self.cmd( 'iptables -t nat -A POSTROUTING',
'-o', self.inetIntf, '-s', self.subnet, '-j MASQUERADE' )
'-s', self.subnet, "'!'", '-d', self.subnet, '-j MASQUERADE' )
# Instruct the kernel to perform forwarding
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
@@ -123,26 +129,100 @@ class NAT( Node ):
# hopefully this won't disconnect you
self.cmd( 'service network-manager restart' )
def getGatewayIntf( self, fallback='eth0' ):
"""Return gateway interface name
fallback: default device to fall back to"""
routes = self.cmd( 'ip route show' )
match = re.search( r'default via \S+ dev (\S+)', routes )
if match:
return match.group( 1 )
else:
warn( 'There is no default route set.',
'Using', fallback, 'as gateway interface...\n' )
return fallback
def terminate( self ):
"""Stop NAT/forwarding between Mininet and external network"""
# Flush any currently active rules
# 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 ):
+1 -8
View File
@@ -8,8 +8,7 @@ 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
@@ -81,12 +80,6 @@ class testSwitchOVSUser( TestSwitchDpidAssignmentOVS ):
"Test dpid assignnment of OVS User Switch."
switchClass = OVSUser
@unittest.skipUnless( quietRun( 'which ovs-openflowd' ),
'OVS Legacy Kernel switch is not installed' )
class testSwitchOVSLegacyKernel( TestSwitchDpidAssignmentOVS ):
"Test dpid assignnment of OVS Legacy Kernel Switch."
switchClass = OVSLegacyKernelSwitch
@unittest.skipUnless( quietRun( 'which ivs-ctl' ),
'IVS switch is not installed' )
class testSwitchIVS( TestSwitchDpidAssignmentOVS ):
+6
View File
@@ -318,6 +318,12 @@ 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."
+13 -4
View File
@@ -45,10 +45,18 @@ class TorusTopo( Topo ):
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' )
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 ):
@@ -57,9 +65,10 @@ class TorusTopo( Topo ):
# 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 )
'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 ):
+142 -69
View File
@@ -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"
@@ -145,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, runCmd=quietRun ):
"""Make a veth pair connecting intf1 and intf2.
intf1: string, interface
intf2: string, interface
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)
returns: ip link add result"""
# Delete any old interfaces with the same names
runCmd( 'ip link del ' + intf1 )
runCmd( 'ip link del ' + intf2 )
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 = runCmd( 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.
@@ -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,...).
@@ -551,18 +622,20 @@ def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
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 runCmd( 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 "
+83 -60
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
@@ -567,7 +590,7 @@ net.ipv6.conf.lo.disable_ipv6 = 1' | sudo tee -a /etc/sysctl.conf > /dev/null
# 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/' \
's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 /' \
/etc/default/grub
sudo update-grub
fi
+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
+1 -1
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