Merge branch 'master' into devel/pty
This commit is contained in:
@@ -25,11 +25,13 @@ from mininet.cli import CLI
|
||||
from mininet.log import lg, LEVELS, info, debug, error
|
||||
from mininet.net import Mininet, MininetWithControlNet, VERSION
|
||||
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
||||
NOX, RemoteController, UserSwitch, OVSKernelSwitch,
|
||||
NOX, RemoteController, DefaultController,
|
||||
UserSwitch, OVSSwitch,
|
||||
OVSLegacyKernelSwitch, IVSSwitch )
|
||||
from mininet.nodelib import LinuxBridge
|
||||
from mininet.link import Link, TCLink
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.topolib import TreeTopo, TorusTopo
|
||||
from mininet.util import custom, customConstructor
|
||||
from mininet.util import buildTopo
|
||||
|
||||
@@ -40,24 +42,29 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
||||
'linear': LinearTopo,
|
||||
'reversed': SingleSwitchReversedTopo,
|
||||
'single': SingleSwitchTopo,
|
||||
'tree': TreeTopo }
|
||||
'tree': TreeTopo,
|
||||
'torus': TorusTopo }
|
||||
|
||||
SWITCHDEF = 'ovsk'
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch,
|
||||
'ovs': OVSSwitch,
|
||||
# Keep ovsk for compatibility with 2.0
|
||||
'ovsk': OVSSwitch,
|
||||
'ovsl': OVSLegacyKernelSwitch,
|
||||
'ivs': IVSSwitch }
|
||||
'ivs': IVSSwitch,
|
||||
'lxbr': LinuxBridge }
|
||||
|
||||
HOSTDEF = 'proc'
|
||||
HOSTS = { 'proc': Host,
|
||||
'rt': custom( CPULimitedHost, sched='rt' ),
|
||||
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
||||
|
||||
CONTROLLERDEF = 'ovsc'
|
||||
CONTROLLERDEF = 'default'
|
||||
CONTROLLERS = { 'ref': Controller,
|
||||
'ovsc': OVSController,
|
||||
'nox': NOX,
|
||||
'remote': RemoteController,
|
||||
'default': DefaultController,
|
||||
'none': lambda name: None }
|
||||
|
||||
LINKDEF = 'default'
|
||||
@@ -261,12 +268,14 @@ class MininetRunner( object ):
|
||||
if test == 'none':
|
||||
pass
|
||||
elif test == 'all':
|
||||
mn.waitConnected()
|
||||
mn.start()
|
||||
mn.ping()
|
||||
mn.iperf()
|
||||
elif test == 'cli':
|
||||
CLI( mn )
|
||||
elif test != 'build':
|
||||
mn.waitConnected()
|
||||
getattr( mn, test )()
|
||||
|
||||
if self.options.post:
|
||||
|
||||
+42
-167
@@ -1,197 +1,72 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
bind.py: Bind mount prototype
|
||||
bind.py: Bind mount example
|
||||
|
||||
This creates hosts with private directories as desired.
|
||||
This creates hosts with private directories that the user specifies.
|
||||
These hosts may have persistent directories that will be available
|
||||
across multiple mininet session, or temporary directories that will
|
||||
only last for one mininet session. To specify a persistent
|
||||
directory, add a tuple to a list of private directories:
|
||||
|
||||
[ ( 'directory to be mounted on', 'directory to be mounted' ) ]
|
||||
|
||||
String expansion may be used to create a directory template for
|
||||
each host. To do this, add a %(name)s in place of the host name
|
||||
when creating your list of directories:
|
||||
|
||||
[ ( '/var/run', '/tmp/%(name)s/var/run' ) ]
|
||||
|
||||
If no persistent directory is specified, the directories will default
|
||||
to temporary private directories. To do this, simply create a list of
|
||||
directories to be made private. A tmpfs will then be mounted on them.
|
||||
|
||||
You may use both temporary and persistent directories at the same
|
||||
time. In the following privateDirs string, each host will have a
|
||||
persistent directory in the root filesystem at
|
||||
"/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also
|
||||
have a temporary private directory mounted on "/var/log".
|
||||
|
||||
[ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ]
|
||||
|
||||
This example has both persistent directories mounted on '/var/log'
|
||||
and '/var/run'. It also has a temporary private directory mounted
|
||||
on '/var/mn'
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host
|
||||
from mininet.node import Host, HostWithPrivateDirs
|
||||
from mininet.cli import CLI
|
||||
from mininet.util import errFail, quietRun, errRun
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel, info, debug
|
||||
|
||||
from os.path import realpath
|
||||
from functools import partial
|
||||
|
||||
|
||||
# Utility functions for unmounting a tree
|
||||
|
||||
MNRUNDIR = realpath( '/var/run/mn' )
|
||||
|
||||
def mountPoints():
|
||||
"Return list of mounted file systems"
|
||||
mtab, _err, _ret = errFail( 'cat /proc/mounts' )
|
||||
lines = mtab.split( '\n' )
|
||||
mounts = []
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
fields = line.split( ' ')
|
||||
mount = fields[ 1 ]
|
||||
mounts.append( mount )
|
||||
return mounts
|
||||
|
||||
def unmountAll( rootdir=MNRUNDIR ):
|
||||
"Unmount all mounts under a directory tree"
|
||||
rootdir = realpath( rootdir )
|
||||
# Find all mounts below rootdir
|
||||
# This is subtle because /foo is not
|
||||
# a parent of /foot
|
||||
dirslash = rootdir + '/'
|
||||
mounts = [ m for m in mountPoints()
|
||||
if m == dir or m.find( dirslash ) == 0 ]
|
||||
# Unmount them from bottom to top
|
||||
mounts.sort( reverse=True )
|
||||
for mount in mounts:
|
||||
debug( 'Unmounting', mount, '\n' )
|
||||
_out, err, code = errRun( 'umount', mount )
|
||||
if code != 0:
|
||||
info( '*** Warning: failed to umount', mount, '\n' )
|
||||
info( err )
|
||||
|
||||
|
||||
class HostWithPrivateDirs( Host ):
|
||||
"Host with private directories"
|
||||
|
||||
mnRunDir = MNRUNDIR
|
||||
|
||||
def __init__(self, name, *args, **kwargs ):
|
||||
"""privateDirs: list of private directories
|
||||
remounts: dirs to remount
|
||||
unmount: unmount dirs in cleanup? (True)
|
||||
Note: if unmount is False, you must call unmountAll()
|
||||
manually."""
|
||||
self.privateDirs = kwargs.pop( 'privateDirs', [] )
|
||||
self.remounts = kwargs.pop( 'remounts', [] )
|
||||
self.unmount = kwargs.pop( 'unmount', True )
|
||||
Host.__init__( self, name, *args, **kwargs )
|
||||
self.rundir = '%s/%s' % ( self.mnRunDir, name )
|
||||
self.root, self.private = None, None # set in createBindMounts
|
||||
if self.privateDirs:
|
||||
self.privateDirs = [ realpath( d ) for d in self.privateDirs ]
|
||||
self.createBindMounts()
|
||||
# These should run in the namespace before we chroot,
|
||||
# in order to put the right entries in /etc/mtab
|
||||
# Eventually this will allow a local pid space
|
||||
# Now we chroot and cd to wherever we were before.
|
||||
pwd = self.cmd( 'pwd' ).strip()
|
||||
self.sendCmd( 'exec chroot', self.root, 'bash -ms mininet:'
|
||||
+ self.name )
|
||||
self.waiting = False
|
||||
self.cmd( 'cd', pwd )
|
||||
# In order for many utilities to work,
|
||||
# we need to remount /proc and /sys
|
||||
self.cmd( 'mount /proc' )
|
||||
self.cmd( 'mount /sys' )
|
||||
|
||||
def mountPrivateDirs( self ):
|
||||
"Create and bind mount private dirs"
|
||||
for dir_ in self.privateDirs:
|
||||
privateDir = self.private + dir_
|
||||
errFail( 'mkdir -p ' + privateDir )
|
||||
mountPoint = self.root + dir_
|
||||
errFail( 'mount -B %s %s' %
|
||||
( privateDir, mountPoint) )
|
||||
|
||||
def mountDirs( self, dirs ):
|
||||
"Mount a list of directories"
|
||||
for dir_ in dirs:
|
||||
mountpoint = self.root + dir_
|
||||
errFail( 'mount -B %s %s' %
|
||||
( dir_, mountpoint ) )
|
||||
|
||||
@classmethod
|
||||
def findRemounts( cls, fstypes=None ):
|
||||
"""Identify mount points in /proc/mounts to remount
|
||||
fstypes: file system types to match"""
|
||||
if fstypes is None:
|
||||
fstypes = [ 'nfs' ]
|
||||
dirs = quietRun( 'cat /proc/mounts' ).strip().split( '\n' )
|
||||
remounts = []
|
||||
for dir_ in dirs:
|
||||
line = dir_.split()
|
||||
mountpoint, fstype = line[ 1 ], line[ 2 ]
|
||||
# Don't re-remount directories!!!
|
||||
if mountpoint.find( cls.mnRunDir ) == 0:
|
||||
continue
|
||||
if fstype in fstypes:
|
||||
remounts.append( mountpoint )
|
||||
return remounts
|
||||
|
||||
def createBindMounts( self ):
|
||||
"""Create a chroot directory structure,
|
||||
with self.privateDirs as private dirs"""
|
||||
errFail( 'mkdir -p '+ self.rundir )
|
||||
unmountAll( self.rundir )
|
||||
# Create /root and /private directories
|
||||
self.root = self.rundir + '/root'
|
||||
self.private = self.rundir + '/private'
|
||||
errFail( 'mkdir -p ' + self.root )
|
||||
errFail( 'mkdir -p ' + self.private )
|
||||
# Recursively mount / in private doort
|
||||
# note we'll remount /sys and /proc later
|
||||
errFail( 'mount -B / ' + self.root )
|
||||
self.mountDirs( self.remounts )
|
||||
self.mountPrivateDirs()
|
||||
|
||||
def unmountBindMounts( self ):
|
||||
"Unmount all of our bind mounts"
|
||||
unmountAll( self.rundir )
|
||||
|
||||
def popen( self, *args, **kwargs ):
|
||||
"Popen with chroot support"
|
||||
chroot = kwargs.pop( 'chroot', True )
|
||||
mncmd = kwargs.get( 'mncmd',
|
||||
[ 'mnexec', '-a', str( self.pid ) ] )
|
||||
if chroot:
|
||||
mncmd = [ 'chroot', self.root ] + mncmd
|
||||
kwargs[ 'mncmd' ] = mncmd
|
||||
return Host.popen( self, *args, **kwargs )
|
||||
|
||||
def cleanup( self ):
|
||||
"""Clean up, then unmount bind mounts
|
||||
unmount: actually unmount bind mounts?"""
|
||||
# Wait for process to actually terminate
|
||||
self.shell.wait()
|
||||
Host.cleanup( self )
|
||||
if self.unmount:
|
||||
self.unmountBindMounts()
|
||||
errFail( 'rmdir ' + self.root )
|
||||
|
||||
|
||||
# Convenience aliases
|
||||
|
||||
findRemounts = HostWithPrivateDirs.findRemounts
|
||||
|
||||
|
||||
# Sample usage
|
||||
|
||||
def testHostWithPrivateDirs():
|
||||
"Test bind mounts"
|
||||
topo = SingleSwitchTopo( 10 )
|
||||
remounts = findRemounts( fstypes=[ 'nfs' ] )
|
||||
privateDirs = [ '/var/log', '/var/run' ]
|
||||
host = partial( HostWithPrivateDirs, remounts=remounts,
|
||||
privateDirs=privateDirs, unmount=False )
|
||||
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
|
||||
( '/var/run', '/tmp/%(name)s/var/run' ),
|
||||
'/var/mn' ]
|
||||
host = partial( HostWithPrivateDirs,
|
||||
privateDirs=privateDirs )
|
||||
net = Mininet( topo=topo, host=host )
|
||||
net.start()
|
||||
info( 'Private Directories:', privateDirs, '\n' )
|
||||
directories = []
|
||||
for directory in privateDirs:
|
||||
directories.append( directory[ 0 ]
|
||||
if isinstance( directory, tuple )
|
||||
else directory )
|
||||
info( 'Private Directories:', directories, '\n' )
|
||||
CLI( net )
|
||||
net.stop()
|
||||
# We do this all at once to save a bit of time
|
||||
info( 'Unmounting host bind mounts...\n' )
|
||||
unmountAll()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unmountAll()
|
||||
setLogLevel( 'info' )
|
||||
testHostWithPrivateDirs()
|
||||
info( 'Done.\n')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ of switches, this example demonstrates:
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch, Controller
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import lg
|
||||
from mininet.util import irange
|
||||
@@ -76,7 +76,7 @@ def linearBandwidthTest( lengths ):
|
||||
print "*** testing", datapath, "datapath"
|
||||
Switch = switches[ datapath ]
|
||||
results[ datapath ] = []
|
||||
net = Mininet( topo=topo, switch=Switch )
|
||||
net = Mininet( topo=topo, switch=Switch, controller=Controller, waitConnected=True )
|
||||
net.start()
|
||||
print "*** testing basic connectivity"
|
||||
for n in lengths:
|
||||
|
||||
+15
-1
@@ -10,7 +10,7 @@ It may also get rid of 'false positives', but hopefully
|
||||
nothing irreplaceable!
|
||||
"""
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import Popen, PIPE, check_output as co
|
||||
import time
|
||||
|
||||
from mininet.log import info
|
||||
@@ -69,4 +69,18 @@ def cleanup():
|
||||
if link:
|
||||
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
|
||||
|
||||
info( "*** Cleanup complete.\n" )
|
||||
|
||||
+55
-14
@@ -90,12 +90,13 @@ import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
import copy
|
||||
from time import sleep
|
||||
from itertools import chain, groupby
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import info, error, debug, output
|
||||
from mininet.node import Host, OVSKernelSwitch, Controller
|
||||
from mininet.log import info, error, debug, output, warn
|
||||
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
|
||||
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||
@@ -108,11 +109,11 @@ class Mininet( object ):
|
||||
"Network emulation with hosts spawned in network namespaces."
|
||||
|
||||
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
|
||||
controller=Controller, link=Link, intf=Intf,
|
||||
controller=DefaultController, link=Link, intf=Intf,
|
||||
build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
|
||||
inNamespace=False,
|
||||
autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
|
||||
listenPort=None ):
|
||||
listenPort=None, waitConnected=False ):
|
||||
"""Create Mininet object.
|
||||
topo: Topo (topology) object or None
|
||||
switch: default Switch class
|
||||
@@ -148,6 +149,7 @@ class Mininet( object ):
|
||||
self.numCores = numCores()
|
||||
self.nextCore = 0 # next core for pinning hosts to CPUs
|
||||
self.listenPort = listenPort
|
||||
self.waitConn = waitConnected
|
||||
|
||||
self.hosts = []
|
||||
self.switches = []
|
||||
@@ -163,6 +165,37 @@ class Mininet( object ):
|
||||
if topo and build:
|
||||
self.build()
|
||||
|
||||
|
||||
def waitConnected( self, timeout=None, delay=.5 ):
|
||||
"""wait for each switch to connect to a controller,
|
||||
up to 5 seconds
|
||||
timeout: time to wait, or None to wait indefinitely
|
||||
delay: seconds to sleep per iteration
|
||||
returns: True if all switches are connected"""
|
||||
info( '*** Waiting for switches to connect\n' )
|
||||
time = 0
|
||||
remaining = list( self.switches )
|
||||
while True:
|
||||
for switch in tuple( remaining ):
|
||||
if switch.connected():
|
||||
info( '%s ' % switch )
|
||||
remaining.remove( switch )
|
||||
if not remaining:
|
||||
info( '\n' )
|
||||
return True
|
||||
if time > timeout and timeout is not None:
|
||||
break
|
||||
sleep( delay )
|
||||
time += delay
|
||||
warn( 'Timed out after %d seconds\n' % time )
|
||||
for switch in remaining:
|
||||
if not switch.connected():
|
||||
warn( 'Warning: %s is not connected to a controller\n'
|
||||
% switch.name )
|
||||
else:
|
||||
remaining.remove( switch )
|
||||
return not remaining
|
||||
|
||||
def addHost( self, name, cls=None, **params ):
|
||||
"""Add host.
|
||||
name: name of host to add
|
||||
@@ -213,7 +246,7 @@ class Mininet( object ):
|
||||
if not controller:
|
||||
controller = self.controller
|
||||
# Construct new controller if one is not given
|
||||
if isinstance(name, Controller):
|
||||
if isinstance( name, Controller ):
|
||||
controller_new = name
|
||||
# Pylint thinks controller is a str()
|
||||
# pylint: disable=E1103
|
||||
@@ -222,7 +255,7 @@ class Mininet( object ):
|
||||
else:
|
||||
controller_new = controller( name, **params )
|
||||
# Add new controller to net
|
||||
if controller_new: # allow controller-less setups
|
||||
if controller_new: # allow controller-less setups
|
||||
self.controllers.append( controller_new )
|
||||
self.nameToNode[ name ] = controller_new
|
||||
return controller_new
|
||||
@@ -324,7 +357,11 @@ class Mininet( object ):
|
||||
if type( classes ) is not list:
|
||||
classes = [ classes ]
|
||||
for i, cls in enumerate( classes ):
|
||||
self.addController( 'c%d' % i, cls )
|
||||
# Allow Controller objects because nobody understands currying
|
||||
if isinstance( cls, Controller ):
|
||||
self.addController( cls )
|
||||
else:
|
||||
self.addController( 'c%d' % i, cls )
|
||||
|
||||
info( '*** Adding hosts:\n' )
|
||||
for hostName in topo.hosts():
|
||||
@@ -401,9 +438,16 @@ class Mininet( object ):
|
||||
info( switch.name + ' ')
|
||||
switch.start( self.controllers )
|
||||
info( '\n' )
|
||||
if self.waitConn:
|
||||
self.waitConnected()
|
||||
|
||||
def stop( self ):
|
||||
"Stop the controller(s), switches and hosts"
|
||||
info( '*** Stopping %i controllers\n' % len( self.controllers ) )
|
||||
for controller in self.controllers:
|
||||
info( controller.name + ' ' )
|
||||
controller.stop()
|
||||
info( '\n' )
|
||||
if self.terms:
|
||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||
self.stopXterms()
|
||||
@@ -419,11 +463,6 @@ class Mininet( object ):
|
||||
for host in self.hosts:
|
||||
info( host.name + ' ' )
|
||||
host.terminate()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i controllers\n' % len( self.controllers ) )
|
||||
for controller in self.controllers:
|
||||
info( controller.name + ' ' )
|
||||
controller.stop()
|
||||
info( '\n*** Done\n' )
|
||||
|
||||
def run( self, test, *args, **kwargs ):
|
||||
@@ -616,7 +655,7 @@ class Mininet( object ):
|
||||
|
||||
# XXX This should be cleaned up
|
||||
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', format=None ):
|
||||
"""Run iperf between two hosts.
|
||||
hosts: list of hosts; if None, uses opposite hosts
|
||||
l4Type: string, one of [ TCP, UDP ]
|
||||
@@ -639,6 +678,8 @@ class Mininet( object ):
|
||||
bwArgs = '-b ' + udpBw + ' '
|
||||
elif l4Type != 'TCP':
|
||||
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
||||
if format:
|
||||
iperfArgs += '-f %s ' %format
|
||||
server.sendCmd( iperfArgs + '-s', printPid=True )
|
||||
servout = ''
|
||||
while server.lastPid is None:
|
||||
@@ -646,7 +687,7 @@ class Mininet( object ):
|
||||
if l4Type == 'TCP':
|
||||
while 'Connected' not in client.cmd(
|
||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
||||
output('waiting for iperf to start up...')
|
||||
info( 'Waiting for iperf to start up...' )
|
||||
sleep(.5)
|
||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||
bwArgs )
|
||||
|
||||
+50
-3
@@ -16,6 +16,11 @@ Host: a virtual host. By default, a host is simply a shell; commands
|
||||
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
|
||||
RT or CFS bandwidth limiting.
|
||||
|
||||
HostWithPrivateDirs: a virtual host that has user-specified private
|
||||
directories. These may be temporary directories stored as a tmpfs,
|
||||
or persistent directories that are mounted from another directory in
|
||||
the root filesystem.
|
||||
|
||||
Switch: superclass for switch nodes.
|
||||
|
||||
UserSwitch: a switch using the user-space switch from the OpenFlow
|
||||
@@ -735,6 +740,33 @@ class CPULimitedHost( Host ):
|
||||
mountCgroups()
|
||||
cls.inited = True
|
||||
|
||||
class HostWithPrivateDirs( Host ):
|
||||
"Host with private directories"
|
||||
|
||||
def __init__( self, name, *args, **kwargs ):
|
||||
"privateDirs: list of private directory strings or tuples"
|
||||
self.name = name
|
||||
self.privateDirs = kwargs.pop( 'privateDirs', [] )
|
||||
Host.__init__( self, name, *args, **kwargs )
|
||||
self.mountPrivateDirs()
|
||||
|
||||
def mountPrivateDirs( self ):
|
||||
"mount private directories"
|
||||
for directory in self.privateDirs:
|
||||
if isinstance( directory, tuple ):
|
||||
# mount given private directory
|
||||
privateDir = directory[ 1 ] % self.__dict__
|
||||
mountPoint = directory[ 0 ]
|
||||
self.cmd( 'mkdir -p %s' % privateDir )
|
||||
self.cmd( 'mkdir -p %s' % mountPoint )
|
||||
self.cmd( 'mount --bind %s %s' %
|
||||
( privateDir, mountPoint ) )
|
||||
else:
|
||||
# mount temporary filesystem on directory
|
||||
self.cmd( 'mkdir -p %s' % directory )
|
||||
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
|
||||
|
||||
|
||||
|
||||
# Some important things to note:
|
||||
#
|
||||
@@ -834,6 +866,8 @@ class UserSwitch( Switch ):
|
||||
'(openflow.org)' )
|
||||
if self.listenPort:
|
||||
self.opts += ' --listen=ptcp:%i ' % self.listenPort
|
||||
else:
|
||||
self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
|
||||
self.dpopts = dpopts
|
||||
|
||||
@classmethod
|
||||
@@ -846,7 +880,7 @@ class UserSwitch( Switch ):
|
||||
"Run dpctl command"
|
||||
listenAddr = None
|
||||
if not self.listenPort:
|
||||
listenAddr = 'unix:/tmp/' + self.name
|
||||
listenAddr = 'unix:/tmp/%s.listen' % self.name
|
||||
else:
|
||||
listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
|
||||
return self.cmd( 'dpctl ' + ' '.join( args ) +
|
||||
@@ -1253,13 +1287,19 @@ class Controller( Node ):
|
||||
return '<%s %s: %s:%s pid=%s> ' % (
|
||||
self.__class__.__name__, self.name,
|
||||
self.IP(), self.port, self.pid )
|
||||
|
||||
@classmethod
|
||||
def isAvailable( self ):
|
||||
return quietRun( 'which controller' )
|
||||
|
||||
class OVSController( Controller ):
|
||||
"Open vSwitch controller"
|
||||
def __init__( self, name, command='ovs-controller', **kwargs ):
|
||||
if quietRun( 'which test-controller' ):
|
||||
command = 'test-controller'
|
||||
Controller.__init__( self, name, command=command, **kwargs )
|
||||
|
||||
@classmethod
|
||||
def isAvailable( self ):
|
||||
return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
|
||||
|
||||
class NOX( Controller ):
|
||||
"Controller to run a NOX application."
|
||||
@@ -1314,3 +1354,10 @@ class RemoteController( Controller ):
|
||||
if 'Connected' not in listening:
|
||||
warn( "Unable to contact the remote controller"
|
||||
" at %s:%d\n" % ( self.ip, self.port ) )
|
||||
|
||||
|
||||
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
|
||||
"find any controller that is available and run it"
|
||||
for controller in order:
|
||||
if controller.isAvailable():
|
||||
return controller( name, **kwargs )
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Node Library for Mininet
|
||||
|
||||
This contains additional Node types which you may find to be useful
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import Topo
|
||||
from mininet.node import Switch
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
|
||||
class LinuxBridge( Switch ):
|
||||
"Linux Bridge (with optional spanning tree)"
|
||||
|
||||
nextPrio = 100 # next bridge priority for spanning tree
|
||||
|
||||
def __init__( self, name, stp=False, prio=None, **kwargs ):
|
||||
"""stp: use spanning tree protocol? (default False)
|
||||
prio: optional explicit bridge priority for STP"""
|
||||
self.stp = stp
|
||||
if prio:
|
||||
self.prio = prio
|
||||
else:
|
||||
self.prio = LinuxBridge.nextPrio
|
||||
LinuxBridge.nextPrio += 1
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
|
||||
def connected( self ):
|
||||
"Are we forwarding yet?"
|
||||
if self.stp:
|
||||
return 'forwarding' in self.cmd( 'brctl showstp', self )
|
||||
else:
|
||||
return True
|
||||
|
||||
def start( self, controllers ):
|
||||
self.cmd( 'ifconfig', self, 'down' )
|
||||
self.cmd( 'brctl delbr', self )
|
||||
self.cmd( 'brctl addbr', self )
|
||||
if self.stp:
|
||||
self.cmd( 'brctl setbridgeprio', self.prio )
|
||||
self.cmd( 'brctl stp', self, 'on' )
|
||||
for i in self.intfList():
|
||||
if self.name in i.name:
|
||||
self.cmd( 'brctl addif', self, i )
|
||||
self.cmd( 'ifconfig', self, 'up' )
|
||||
|
||||
def stop( self ):
|
||||
self.cmd( 'ifconfig', self, 'down' )
|
||||
self.cmd( 'brctl delbr', self )
|
||||
|
||||
@@ -55,6 +55,9 @@ class testOptionsTopoCommon( object ):
|
||||
"""
|
||||
self.assertGreaterEqual( float(measured),
|
||||
float(expected) * tolerance_frac )
|
||||
self.assertLessEqual( float( measured ),
|
||||
float(expected) + (1-tolerance_frac)
|
||||
* float( expected ) )
|
||||
|
||||
def testCPULimits( self ):
|
||||
"Verify topology creation with CPU limits set for both schedulers."
|
||||
@@ -68,19 +71,20 @@ class testOptionsTopoCommon( object ):
|
||||
mn.start()
|
||||
results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
|
||||
mn.stop()
|
||||
for cpu in results:
|
||||
self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE )
|
||||
for pct in results:
|
||||
#divide cpu by 100 to convert from percentage to fraction
|
||||
self.assertWithinTolerance( pct/100, CPU_FRACTION, CPU_TOLERANCE )
|
||||
|
||||
def testLinkBandwidth( self ):
|
||||
"Verify that link bandwidths are accurate within a bound."
|
||||
BW = 5 # Mbps
|
||||
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 )
|
||||
bw_strs = mn.run( mn.iperf )
|
||||
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 )
|
||||
@@ -91,7 +95,7 @@ class testOptionsTopoCommon( object ):
|
||||
DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail
|
||||
lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True }
|
||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
||||
link=TCLink, switch=self.switchClass )
|
||||
link=TCLink, switch=self.switchClass, autoStaticArp=True )
|
||||
ping_delays = mn.run( mn.pingFull )
|
||||
test_outputs = ping_delays[0]
|
||||
# Ignore unused variables below
|
||||
@@ -102,9 +106,10 @@ class testOptionsTopoCommon( object ):
|
||||
# pylint: enable-msg=W0612
|
||||
for rttval in [rttmin, rttavg, rttmax]:
|
||||
# Multiply delay by 4 to cover there & back on two links
|
||||
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
|
||||
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
|
||||
DELAY_TOLERANCE)
|
||||
|
||||
|
||||
def testLinkLoss( self ):
|
||||
"Verify that we see packet drops with a high configured loss rate."
|
||||
LOSS_PERCENT = 99
|
||||
|
||||
@@ -66,7 +66,7 @@ class testLinearCommon( object ):
|
||||
|
||||
def testLinear5( self ):
|
||||
"Ping test on a 5-switch topology"
|
||||
mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller )
|
||||
mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host, Controller, waitConnected=True )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
|
||||
+13
-6
@@ -48,18 +48,25 @@ class MultiGraph( object ):
|
||||
class Topo(object):
|
||||
"Data center network representation for structured multi-trees."
|
||||
|
||||
def __init__(self, hopts=None, sopts=None, lopts=None):
|
||||
"""Topo object:
|
||||
def __init__(self, *args, **params):
|
||||
"""Topo object.
|
||||
Optional named parameters:
|
||||
hinfo: default host options
|
||||
sopts: default switch options
|
||||
lopts: default link options"""
|
||||
lopts: default link options
|
||||
calls build()"""
|
||||
self.g = MultiGraph()
|
||||
self.node_info = {}
|
||||
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
|
||||
self.hopts = {} if hopts is None else hopts
|
||||
self.sopts = {} if sopts is None else sopts
|
||||
self.lopts = {} if lopts is None else lopts
|
||||
self.hopts = params.pop( 'hopts', {} )
|
||||
self.sopts = params.pop( 'sopts', {} )
|
||||
self.lopts = params.pop( 'lopts', {} )
|
||||
self.ports = {} # ports[src][dst] is port on src that connects to dst
|
||||
self.build( *args, **params )
|
||||
|
||||
def build( self, *args, **params ):
|
||||
"Override this method to build your topology."
|
||||
pass
|
||||
|
||||
def addNode(self, name, **opts):
|
||||
"""Add Node to graph.
|
||||
|
||||
@@ -34,3 +34,37 @@ def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
topo = TreeTopo( depth, fanout )
|
||||
return Mininet( topo, **kwargs )
|
||||
|
||||
|
||||
class TorusTopo( Topo ):
|
||||
"""2-D Torus topology
|
||||
WARNING: this topology has LOOPS and WILL NOT WORK
|
||||
with the default controller or any Ethernet bridge
|
||||
without STP turned on! It can be used with STP, e.g.:
|
||||
# mn --topo torus,3,3 --switch lxbr,stp=1 --test pingall"""
|
||||
def __init__( self, x, y, *args, **kwargs ):
|
||||
Topo.__init__( self, *args, **kwargs )
|
||||
if x < 3 or y < 3:
|
||||
raise Exception( 'Please use 3x3 or greater for compatibility '
|
||||
'with 2.1' )
|
||||
hosts, switches, dpid = {}, {}, 0
|
||||
# Create and wire interior
|
||||
for i in range( 0, x ):
|
||||
for j in range( 0, y ):
|
||||
loc = '%dx%d' % ( i + 1, j + 1 )
|
||||
# dpid cannot be zero for OVS
|
||||
dpid = ( i + 1 ) * 256 + ( j + 1 )
|
||||
switch = switches[ i, j ] = self.addSwitch( 's' + loc, dpid='%016x' % dpid )
|
||||
host = hosts[ i, j ] = self.addHost( 'h' + loc )
|
||||
self.addLink( host, switch )
|
||||
# Connect switches
|
||||
for i in range( 0, x ):
|
||||
for j in range( 0, y ):
|
||||
sw1 = switches[ i, j ]
|
||||
sw2 = switches[ i, ( j + 1 ) % y ]
|
||||
sw3 = switches[ ( i + 1 ) % x, j ]
|
||||
self.addLink( sw1, sw2 )
|
||||
self.addLink( sw1, sw3 )
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ int main(int argc, char *argv[])
|
||||
fflush(stdout);
|
||||
break;
|
||||
case 'a':
|
||||
/* Attach to pid's network namespace */
|
||||
/* Attach to pid's network namespace and mount namespace*/
|
||||
pid = atoi(optarg);
|
||||
sprintf(path, "/proc/%d/ns/net", pid );
|
||||
nsid = open(path, O_RDONLY);
|
||||
@@ -152,6 +152,16 @@ int main(int argc, char *argv[])
|
||||
perror("setns");
|
||||
return 1;
|
||||
}
|
||||
sprintf(path, "/proc/%d/ns/mnt", pid );
|
||||
nsid = open(path, O_RDONLY);
|
||||
if (nsid < 0) {
|
||||
perror(path);
|
||||
return 1;
|
||||
}
|
||||
if (setns(nsid, 0) != 0) {
|
||||
perror("setns");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
/* Attach to cgroup */
|
||||
|
||||
@@ -89,6 +89,12 @@ isoURLs = {
|
||||
'trusty64server':
|
||||
'http://mirrors.kernel.org/ubuntu-releases/14.04/'
|
||||
'ubuntu-14.04-server-amd64.iso',
|
||||
'utopic32server':
|
||||
'http://mirrors.kernel.org/ubuntu-releases/14.10/'
|
||||
'ubuntu-14.10-server-i386.iso',
|
||||
'utopic64server':
|
||||
'http://mirrors.kernel.org/ubuntu-releases/14.10/'
|
||||
'ubuntu-14.10-server-amd64.iso',
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user