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