Compare commits
197
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f649fa2456 | ||
|
|
9da9ebe085 | ||
|
|
a17ec6dd9f | ||
|
|
65cc3eab1a | ||
|
|
bc8875c2d6 | ||
|
|
75cbb67203 | ||
|
|
c41d88fb8a | ||
|
|
dfa400d7fd | ||
|
|
49a6480e85 | ||
|
|
e9c207ea7b | ||
|
|
122141c6e4 | ||
|
|
d826d03643 | ||
|
|
0efa65ef08 | ||
|
|
8c41029817 | ||
|
|
994e8efe3e | ||
|
|
5c882419f7 | ||
|
|
50faa927d0 | ||
|
|
9535291882 | ||
|
|
9f7f9b8594 | ||
|
|
abd39c9dd1 | ||
|
|
d6a2909f8c | ||
|
|
4e7bf368fa | ||
|
|
8dfc3bf091 | ||
|
|
24769aba40 | ||
|
|
97a7fc9d0a | ||
|
|
e088c85a04 | ||
|
|
9cc2255569 | ||
|
|
2d5627677a | ||
|
|
073021973b | ||
|
|
bad5adc773 | ||
|
|
03a8b01273 | ||
|
|
08d3c86c9a | ||
|
|
e332a7f49d | ||
|
|
a0a35c1093 | ||
|
|
8d6043df94 | ||
|
|
f190c7d8e3 | ||
|
|
a8a2799174 | ||
|
|
b3efefc561 | ||
|
|
d32ab759a5 | ||
|
|
1819730925 | ||
|
|
47fdafe93e | ||
|
|
bd0f710078 | ||
|
|
31b29cc8bc | ||
|
|
6b06431e86 | ||
|
|
721c6a3ace | ||
|
|
615f63f8f7 | ||
|
|
9b45382bdc | ||
|
|
240278bb22 | ||
|
|
872be9a796 | ||
|
|
587997dc20 | ||
|
|
1268bed091 | ||
|
|
cfb2518ecf | ||
|
|
19c03c3842 | ||
|
|
55ceed2ab0 | ||
|
|
7245babab6 | ||
|
|
9d578e5550 | ||
|
|
f3a82b9b88 | ||
|
|
c65997ac7d | ||
|
|
ab65aabc32 | ||
|
|
0183e29765 | ||
|
|
f44559651f | ||
|
|
cac9dd0090 | ||
|
|
5fd74d5d8f | ||
|
|
16a2f1bfc3 | ||
|
|
9fe29fafdf | ||
|
|
c13170add4 | ||
|
|
844fe4bd41 | ||
|
|
1bb3784bf4 | ||
|
|
dfdbea029c | ||
|
|
2727444ede | ||
|
|
9537107063 | ||
|
|
be9cddbcff | ||
|
|
2cb17590c2 | ||
|
|
f7601da006 | ||
|
|
65a0e5f3b6 | ||
|
|
d5d66f1276 | ||
|
|
9ed14fa0f4 | ||
|
|
f24ebc438e | ||
|
|
90d50dcbc4 | ||
|
|
5f68be2273 | ||
|
|
68ae67dc58 | ||
|
|
c4a85ab1d1 | ||
|
|
ef3c885630 | ||
|
|
bb35d04102 | ||
|
|
ba05fd363b | ||
|
|
e988b0f660 | ||
|
|
b27cce08af | ||
|
|
a38896c254 | ||
|
|
63ae13fcf9 | ||
|
|
c5f6d0ff17 | ||
|
|
ab8c4e9198 | ||
|
|
8daa4193c7 | ||
|
|
5c895eaad8 | ||
|
|
435d0d68b3 | ||
|
|
66e9845f05 | ||
|
|
fe3340077c | ||
|
|
c589660e17 | ||
|
|
d9117c77e1 | ||
|
|
6cb68f26c9 | ||
|
|
c60764c34f | ||
|
|
ba723826b7 | ||
|
|
0165d7bdff | ||
|
|
1df1f9a1c5 | ||
|
|
2c916acc06 | ||
|
|
0a810b226c | ||
|
|
57c8f5934f | ||
|
|
125e669723 | ||
|
|
a2486a6d66 | ||
|
|
15f2898f26 | ||
|
|
1c4adde1b6 | ||
|
|
4d22991245 | ||
|
|
90ea6c6bd1 | ||
|
|
269cecd3ee | ||
|
|
c2be20f09e | ||
|
|
df56fa2716 | ||
|
|
f49e2539b7 | ||
|
|
4da7b3b7f0 | ||
|
|
e5a5cd0070 | ||
|
|
c5779deeb6 | ||
|
|
79c944aef4 | ||
|
|
a23c6a2871 | ||
|
|
7acc693277 | ||
|
|
889698f7bd | ||
|
|
f6f6d9282b | ||
|
|
5224884e5e | ||
|
|
f063b023bd | ||
|
|
a1edb167b1 | ||
|
|
613fac4bab | ||
|
|
e0cd11ab21 | ||
|
|
5b818ad75c | ||
|
|
17ba6a7c4d | ||
|
|
f77a8b9e17 | ||
|
|
3dd8c2cda6 | ||
|
|
48a8ed857e | ||
|
|
74c3511d5c | ||
|
|
5ac113cfaa | ||
|
|
cd02954c91 | ||
|
|
93be1d0401 | ||
|
|
6a38811f1a | ||
|
|
4ac45a3967 | ||
|
|
d7e01bb821 | ||
|
|
340bf3cb7a | ||
|
|
5f8547a5e0 | ||
|
|
09e9c0550a | ||
|
|
c1dc80571a | ||
|
|
ec9b23ba9c | ||
|
|
7c0b56f9ba | ||
|
|
d90a45514f | ||
|
|
b2fe0778dc | ||
|
|
3e4f254573 | ||
|
|
2e4dd13482 | ||
|
|
19331ca287 | ||
|
|
9483f6378f | ||
|
|
a4e933688a | ||
|
|
c11e9f3316 | ||
|
|
acdcf9b6ae | ||
|
|
c702840a0a | ||
|
|
254fae2dc9 | ||
|
|
bdad3e8c8e | ||
|
|
574d634fc2 | ||
|
|
eafbd2a597 | ||
|
|
7485b035af | ||
|
|
8014a7023c | ||
|
|
bec34e7227 | ||
|
|
9ca6322603 | ||
|
|
957fe1db93 | ||
|
|
3b4738c2ca | ||
|
|
30ebb852a3 | ||
|
|
959586bc8f | ||
|
|
9bda98486d | ||
|
|
c68e4e76f4 | ||
|
|
98a8231cef | ||
|
|
28ce13d18e | ||
|
|
ef59cd88dc | ||
|
|
9db6cdc261 | ||
|
|
f7b29333f5 | ||
|
|
24520fc982 | ||
|
|
b93cc989f4 | ||
|
|
a8cc243aa7 | ||
|
|
e65dc4c6d6 | ||
|
|
d4be92713a | ||
|
|
79f5d39db5 | ||
|
|
6da3fcdef1 | ||
|
|
026130bd5f | ||
|
|
c1b48fb5c8 | ||
|
|
b1983548aa | ||
|
|
c62812a944 | ||
|
|
d66b96260a | ||
|
|
d7e9c3bbfd | ||
|
|
7a4a865bdb | ||
|
|
da4dcf3753 | ||
|
|
5383b0e603 | ||
|
|
9d2e6404b3 | ||
|
|
91a73bd191 | ||
|
|
c069542c5c | ||
|
|
127f35a9bc | ||
|
|
171e815122 |
No files matched your search
+4
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
Executable
+918
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
|
||||
|
||||
@@ -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"""
|
||||
|
||||
Executable
+89
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 ):
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user