Merge branch 'master' into nat
Conflicts: mininet/net.py mininet/node.py
This commit is contained in:
+27
-8
@@ -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
|
||||
@@ -47,21 +47,40 @@ def cleanup():
|
||||
cleanUpScreens()
|
||||
|
||||
info( "*** Removing excess kernel datapaths\n" )
|
||||
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).split( '\n' )
|
||||
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines()
|
||||
for dp in dps:
|
||||
if dp != '':
|
||||
if dp:
|
||||
sh( 'dpctl deldp ' + dp )
|
||||
|
||||
info( "*** Removing OVS datapaths" )
|
||||
dps = sh("ovs-vsctl --timeout=1 list-br").split( '\n' )
|
||||
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
|
||||
if dps:
|
||||
sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp
|
||||
for dp in dps if dp ) )
|
||||
# And in case the above didn't work...
|
||||
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
|
||||
for dp in dps:
|
||||
if dp:
|
||||
sh( 'ovs-vsctl del-br ' + dp )
|
||||
sh( 'ovs-vsctl del-br ' + dp )
|
||||
|
||||
info( "*** Removing all links of the pattern foo-ethX\n" )
|
||||
links = sh( r"ip link show | egrep -o '(\w+-eth\w+)'" ).split( '\n' )
|
||||
links = sh( "ip link show | "
|
||||
"egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines()
|
||||
for link in links:
|
||||
if link != '':
|
||||
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" )
|
||||
|
||||
+29
-10
@@ -31,6 +31,8 @@ from os import isatty
|
||||
from select import poll, POLLIN
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import atexit
|
||||
|
||||
from mininet.log import info, output, error
|
||||
from mininet.term import makeTerms, runX11
|
||||
@@ -52,6 +54,18 @@ class CLI( Cmd ):
|
||||
self.inputFile = script
|
||||
Cmd.__init__( self )
|
||||
info( '*** Starting CLI:\n' )
|
||||
|
||||
# Set up history if readline is available
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
history_path = os.path.expanduser('~/.mininet_history')
|
||||
if os.path.isfile(history_path):
|
||||
readline.read_history_file(history_path)
|
||||
atexit.register(lambda: readline.write_history_file(history_path))
|
||||
|
||||
if self.inputFile:
|
||||
self.do_source( self.inputFile )
|
||||
return
|
||||
@@ -63,7 +77,7 @@ class CLI( Cmd ):
|
||||
node.sendInt()
|
||||
node.monitor()
|
||||
if self.isatty():
|
||||
quietRun( 'stty sane' )
|
||||
quietRun( 'stty echo sane intr "^C"' )
|
||||
self.cmdloop()
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
@@ -151,16 +165,16 @@ class CLI( Cmd ):
|
||||
|
||||
# pylint: enable-msg=W0703,W0122
|
||||
|
||||
def do_pingall( self, _line ):
|
||||
def do_pingall( self, line ):
|
||||
"Ping between all hosts."
|
||||
self.mn.pingAll()
|
||||
self.mn.pingAll( line )
|
||||
|
||||
def do_pingpair( self, _line ):
|
||||
"Ping between first two hosts, useful for testing."
|
||||
self.mn.pingPair()
|
||||
|
||||
def do_pingallfull( self, _line ):
|
||||
"Ping between first two hosts, returns all ping results."
|
||||
"Ping between all hosts, returns all ping results."
|
||||
self.mn.pingAllFull()
|
||||
|
||||
def do_pingpairfull( self, _line ):
|
||||
@@ -187,7 +201,7 @@ class CLI( Cmd ):
|
||||
error( 'invalid number of args: iperf src dst\n' )
|
||||
|
||||
def do_iperfudp( self, line ):
|
||||
"Simple iperf TCP test between two (optionally specified) hosts."
|
||||
"Simple iperf UDP test between two (optionally specified) hosts."
|
||||
args = line.split()
|
||||
if not args:
|
||||
self.mn.iperf( l4Type='UDP' )
|
||||
@@ -297,6 +311,7 @@ class CLI( Cmd ):
|
||||
break
|
||||
except IOError:
|
||||
error( 'error reading file %s\n' % args[ 0 ] )
|
||||
self.inputFile.close()
|
||||
self.inputFile = None
|
||||
|
||||
def do_dpctl( self, line ):
|
||||
@@ -331,13 +346,13 @@ class CLI( Cmd ):
|
||||
node = self.mn[ first ]
|
||||
rest = args.split( ' ' )
|
||||
# Substitute IP addresses for node names in command
|
||||
rest = [ self.mn[ arg ].defaultIntf().updateIP()
|
||||
# If updateIP() returns None, then use node name
|
||||
rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
|
||||
if arg in self.mn else arg
|
||||
for arg in rest ]
|
||||
rest = ' '.join( rest )
|
||||
# Run cmd on node:
|
||||
builtin = isShellBuiltin( first )
|
||||
node.sendCmd( rest, printPid=( not builtin ) )
|
||||
node.sendCmd( rest )
|
||||
self.waitForNode( node )
|
||||
else:
|
||||
error( '*** Unknown command: %s\n' % line )
|
||||
@@ -345,7 +360,7 @@ class CLI( Cmd ):
|
||||
# pylint: enable-msg=R0201
|
||||
|
||||
def waitForNode( self, node ):
|
||||
"Wait for a node to finish, and print its output."
|
||||
"Wait for a node to finish, and print its output."
|
||||
# Pollers
|
||||
nodePoller = poll()
|
||||
nodePoller.register( node.stdout )
|
||||
@@ -363,7 +378,7 @@ class CLI( Cmd ):
|
||||
if False and self.inputFile:
|
||||
key = self.inputFile.read( 1 )
|
||||
if key is not '':
|
||||
node.write(key)
|
||||
node.write( key )
|
||||
else:
|
||||
self.inputFile = None
|
||||
if isReadable( self.inPoller ):
|
||||
@@ -375,8 +390,12 @@ class CLI( Cmd ):
|
||||
if not node.waiting:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
# There is an at least one race condition here, since
|
||||
# it's possible to interrupt ourselves after we've
|
||||
# read data but before it has been printed.
|
||||
node.sendInt()
|
||||
|
||||
|
||||
# Helper functions
|
||||
|
||||
def isReadable( poller ):
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ class StreamHandlerNoNewline( logging.StreamHandler ):
|
||||
|
||||
class Singleton( type ):
|
||||
"""Singleton pattern from Wikipedia
|
||||
See http://en.wikipedia.org/wiki/SingletonPattern#Python
|
||||
See http://en.wikipedia.org/wiki/Singleton_Pattern
|
||||
|
||||
Intended to be used as a __metaclass_ param, as shown for the class
|
||||
below."""
|
||||
@@ -69,7 +69,7 @@ class Singleton( type ):
|
||||
def __call__( cls, *args, **kw ):
|
||||
if cls.instance is None:
|
||||
cls.instance = super( Singleton, cls ).__call__( *args, **kw )
|
||||
return cls.instance
|
||||
return cls.instance
|
||||
|
||||
|
||||
class MininetLogger( Logger, object ):
|
||||
|
||||
+66
-22
@@ -90,29 +90,30 @@ import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
import copy
|
||||
from time import sleep
|
||||
from itertools import chain
|
||||
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, NAT
|
||||
from mininet.log import info, error, debug, output, warn
|
||||
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller, NAT
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
|
||||
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||
from mininet.term import cleanUpScreens, makeTerms
|
||||
|
||||
# Mininet version: should be consistent with README and LICENSE
|
||||
VERSION = "2.1.0"
|
||||
VERSION = "2.1.0+"
|
||||
|
||||
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
|
||||
@@ -338,7 +371,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():
|
||||
@@ -369,7 +406,7 @@ class Mininet( object ):
|
||||
"Build mininet."
|
||||
if self.topo:
|
||||
self.buildFromTopo( self.topo )
|
||||
if ( self.inNamespace ):
|
||||
if self.inNamespace:
|
||||
self.configureControlNetwork()
|
||||
info( '*** Configuring hosts\n' )
|
||||
self.configHosts()
|
||||
@@ -415,13 +452,23 @@ 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()
|
||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
||||
for swclass, switches in groupby( sorted( self.switches, key=type ), type ):
|
||||
if hasattr( swclass, 'batchShutdown' ):
|
||||
swclass.batchShutdown( switches )
|
||||
for switch in self.switches:
|
||||
info( switch.name + ' ' )
|
||||
switch.stop()
|
||||
@@ -430,11 +477,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 ):
|
||||
@@ -477,13 +519,13 @@ class Mininet( object ):
|
||||
"Parse ping output and return packets sent, received."
|
||||
# Check for downed link
|
||||
if 'connect: Network is unreachable' in pingOutput:
|
||||
return (1, 0)
|
||||
return 1, 0
|
||||
r = r'(\d+) packets transmitted, (\d+) received'
|
||||
m = re.search( r, pingOutput )
|
||||
if m is None:
|
||||
error( '*** Error: could not parse ping output: %s\n' %
|
||||
pingOutput )
|
||||
return (1, 0)
|
||||
return 1, 0
|
||||
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
|
||||
return sent, received
|
||||
|
||||
@@ -518,7 +560,7 @@ class Mininet( object ):
|
||||
output( ( '%s ' % dest.name ) if received else 'X ' )
|
||||
output( '\n' )
|
||||
if packets > 0:
|
||||
ploss = 100 * lost / packets
|
||||
ploss = 100.0 * lost / packets
|
||||
received = packets - lost
|
||||
output( "*** Results: %i%% dropped (%d/%d received)\n" %
|
||||
( ploss, received, packets ) )
|
||||
@@ -589,10 +631,10 @@ class Mininet( object ):
|
||||
(rttmin, rttavg, rttmax, rttdev) )
|
||||
return all_outputs
|
||||
|
||||
def pingAll( self ):
|
||||
def pingAll( self, timeout=None ):
|
||||
"""Ping between all hosts.
|
||||
returns: ploss packet loss percentage"""
|
||||
return self.ping()
|
||||
return self.ping( timeout=timeout )
|
||||
|
||||
def pingPair( self ):
|
||||
"""Ping between first two hosts, useful for testing.
|
||||
@@ -627,7 +669,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 ]
|
||||
@@ -650,6 +692,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:
|
||||
@@ -657,7 +701,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 )
|
||||
|
||||
+180
-64
@@ -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
|
||||
@@ -47,6 +52,7 @@ Future enhancements:
|
||||
"""
|
||||
|
||||
import os
|
||||
import pty
|
||||
import re
|
||||
import signal
|
||||
import select
|
||||
@@ -59,6 +65,8 @@ from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
|
||||
numCores, retry, mountCgroups )
|
||||
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
|
||||
from mininet.link import Link, Intf, TCIntf
|
||||
from re import findall
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
class Node( object ):
|
||||
"""A virtual network node is simply a shell in a network namespace.
|
||||
@@ -118,16 +126,22 @@ class Node( object ):
|
||||
return
|
||||
# mnexec: (c)lose descriptors, (d)etach from tty,
|
||||
# (p)rint pid, and run in (n)amespace
|
||||
opts = '-cdp'
|
||||
opts = '-cd'
|
||||
if self.inNamespace:
|
||||
opts += 'n'
|
||||
# bash -m: enable job control
|
||||
# bash -m: enable job control, i: force interactive
|
||||
# -s: pass $* to shell, and make process easy to find in ps
|
||||
cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
|
||||
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
|
||||
close_fds=True )
|
||||
self.stdin = self.shell.stdin
|
||||
self.stdout = self.shell.stdout
|
||||
# prompt is set to sentinel chr( 127 )
|
||||
os.environ[ 'PS1' ] = chr( 127 )
|
||||
cmd = [ 'mnexec', opts, '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,
|
||||
close_fds=False )
|
||||
self.stdin = os.fdopen( master )
|
||||
self.stdout = self.stdin
|
||||
self.pid = self.shell.pid
|
||||
self.pollOut = select.poll()
|
||||
self.pollOut.register( self.stdout )
|
||||
@@ -140,7 +154,14 @@ class Node( object ):
|
||||
self.lastCmd = None
|
||||
self.lastPid = None
|
||||
self.readbuf = ''
|
||||
# Wait for prompt
|
||||
while True:
|
||||
data = self.read( 1024 )
|
||||
if data[ -1 ] == chr( 127 ):
|
||||
break
|
||||
self.pollOut.poll()
|
||||
self.waiting = False
|
||||
self.cmd( 'stty -echo' )
|
||||
|
||||
def cleanup( self ):
|
||||
"Help python collect its garbage."
|
||||
@@ -186,7 +207,7 @@ class Node( object ):
|
||||
def terminate( self ):
|
||||
"Send kill signal to Node and clean up after it."
|
||||
if self.shell:
|
||||
os.kill( self.pid, signal.SIGKILL )
|
||||
os.killpg( self.pid, signal.SIGKILL )
|
||||
self.cleanup()
|
||||
|
||||
def stop( self ):
|
||||
@@ -219,36 +240,29 @@ class Node( object ):
|
||||
# Replace empty commands with something harmless
|
||||
cmd = 'echo -n'
|
||||
self.lastCmd = cmd
|
||||
printPid = printPid and not isShellBuiltin( cmd )
|
||||
if len( cmd ) > 0 and cmd[ -1 ] == '&':
|
||||
# print ^A{pid}\n{sentinel}
|
||||
cmd += ' printf "\\001%d\n\\177" $! \n'
|
||||
else:
|
||||
# print sentinel
|
||||
cmd += '; printf "\\177"'
|
||||
if printPid and not isShellBuiltin( 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
|
||||
self.write( cmd + '\n' )
|
||||
self.lastPid = None
|
||||
self.waiting = True
|
||||
|
||||
def sendInt( self, sig=signal.SIGINT ):
|
||||
def sendInt( self, intr=chr( 3 ) ):
|
||||
"Interrupt running command."
|
||||
if self.lastPid:
|
||||
try:
|
||||
os.kill( self.lastPid, sig )
|
||||
except OSError:
|
||||
pass
|
||||
self.write( intr )
|
||||
|
||||
def monitor( self, timeoutms=None ):
|
||||
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."""
|
||||
self.waitReadable( timeoutms )
|
||||
data = self.read( 1024 )
|
||||
# Look for PID
|
||||
marker = chr( 1 ) + r'\d+\n'
|
||||
if chr( 1 ) in data:
|
||||
marker = chr( 1 ) + r'\d+\r\n'
|
||||
if findPid and chr( 1 ) in data:
|
||||
markers = re.findall( marker, data )
|
||||
if markers:
|
||||
self.lastPid = int( markers[ 0 ][ 1: ] )
|
||||
@@ -725,6 +739,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:
|
||||
#
|
||||
@@ -754,27 +795,32 @@ class Switch( Node ):
|
||||
dpidLen = 16 # digits in dpid passed to switch
|
||||
|
||||
def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
|
||||
"""dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
|
||||
"""dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
|
||||
opts: additional switch options
|
||||
listenPort: port to listen on for dpctl connections"""
|
||||
Node.__init__( self, name, **params )
|
||||
self.dpid = dpid if dpid else self.defaultDpid()
|
||||
self.dpid = self.defaultDpid( dpid )
|
||||
self.opts = opts
|
||||
self.listenPort = listenPort
|
||||
if not self.inNamespace:
|
||||
self.controlIntf = Intf( 'lo', self, port=0 )
|
||||
|
||||
def defaultDpid( self ):
|
||||
"Derive dpid from switch name, s1 -> 1"
|
||||
try:
|
||||
dpid = int( re.findall( r'\d+', self.name )[ 0 ] )
|
||||
dpid = hex( dpid )[ 2: ]
|
||||
dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
|
||||
return dpid
|
||||
except IndexError:
|
||||
raise Exception( 'Unable to derive default datapath ID - '
|
||||
'please either specify a dpid or use a '
|
||||
'canonical switch name such as s23.' )
|
||||
def defaultDpid( self, dpid=None ):
|
||||
"Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
|
||||
if dpid:
|
||||
# Remove any colons and make sure it's a good hex number
|
||||
dpid = dpid.translate( None, ':' )
|
||||
assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
|
||||
else:
|
||||
# Use hex of the first number in the switch name
|
||||
nums = re.findall( r'\d+', self.name )
|
||||
if nums:
|
||||
dpid = hex( int( nums[ 0 ] ) )[ 2: ]
|
||||
else:
|
||||
raise Exception( 'Unable to derive default datapath ID - '
|
||||
'please either specify a dpid or use a '
|
||||
'canonical switch name such as s23.' )
|
||||
return '0' * ( self.dpidLen - len( dpid ) ) + dpid
|
||||
|
||||
def defaultIntf( self ):
|
||||
"Return control interface"
|
||||
@@ -819,6 +865,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
|
||||
@@ -829,10 +877,13 @@ class UserSwitch( Switch ):
|
||||
|
||||
def dpctl( self, *args ):
|
||||
"Run dpctl command"
|
||||
listenAddr = None
|
||||
if not self.listenPort:
|
||||
return "can't run dpctl without passive listening port"
|
||||
listenAddr = 'unix:/tmp/%s.listen' % self.name
|
||||
else:
|
||||
listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
|
||||
return self.cmd( 'dpctl ' + ' '.join( args ) +
|
||||
' tcp:127.0.0.1:%i' % self.listenPort )
|
||||
' ' + listenAddr )
|
||||
|
||||
def connected( self ):
|
||||
"Is the switch connected to a controller?"
|
||||
@@ -849,10 +900,13 @@ class UserSwitch( Switch ):
|
||||
minspeed = ifspeed * 0.001
|
||||
|
||||
res = intf.config( **intf.params )
|
||||
parent = res['parent']
|
||||
|
||||
if res is None: # link may not have TC parameters
|
||||
return
|
||||
|
||||
# Re-add qdisc, root, and default classes user switch created, but
|
||||
# with new parent, as setup by Mininet's TCIntf
|
||||
parent = res['parent']
|
||||
intf.tc( "%s qdisc add dev %s " + parent +
|
||||
" handle 1: htb default 0xfffe" )
|
||||
intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
|
||||
@@ -947,14 +1001,17 @@ class OVSLegacyKernelSwitch( Switch ):
|
||||
class OVSSwitch( Switch ):
|
||||
"Open vSwitch switch. Depends on ovs-vsctl."
|
||||
|
||||
def __init__( self, name, failMode='secure', datapath='kernel', **params ):
|
||||
def __init__( self, name, failMode='secure', datapath='kernel',
|
||||
inband=False, **params ):
|
||||
"""Init.
|
||||
name: name for switch
|
||||
failMode: controller loss behavior (secure|open)
|
||||
datapath: userspace or kernel mode (kernel|user)"""
|
||||
datapath: userspace or kernel mode (kernel|user)
|
||||
inband: use in-band control (False)"""
|
||||
Switch.__init__( self, name, **params )
|
||||
self.failMode = failMode
|
||||
self.datapath = datapath
|
||||
self.inband = inband
|
||||
|
||||
@classmethod
|
||||
def setup( cls ):
|
||||
@@ -975,6 +1032,20 @@ class OVSSwitch( Switch ):
|
||||
'You may wish to try '
|
||||
'"service openvswitch-switch start".\n' )
|
||||
exit( 1 )
|
||||
info = quietRun( 'ovs-vsctl --version' )
|
||||
cls.OVSVersion = findall( '\d+\.\d+', info )[ 0 ]
|
||||
|
||||
@classmethod
|
||||
def isOldOVS( cls ):
|
||||
return ( StrictVersion( cls.OVSVersion ) <
|
||||
StrictVersion( '1.10' ) )
|
||||
|
||||
@classmethod
|
||||
def batchShutdown( cls, switches ):
|
||||
"Call ovs-vsctl del-br on all OVSSwitches in a list"
|
||||
quietRun( 'ovs-vsctl ' +
|
||||
' -- '.join( '--if-exists del-br %s' % s
|
||||
for s in switches ) )
|
||||
|
||||
def dpctl( self, *args ):
|
||||
"Run ovs-ofctl command"
|
||||
@@ -1025,30 +1096,51 @@ class OVSSwitch( Switch ):
|
||||
self.cmd( 'ifconfig lo up' )
|
||||
# Annoyingly, --if-exists option seems not to work
|
||||
self.cmd( 'ovs-vsctl del-br', self )
|
||||
self.cmd( 'ovs-vsctl add-br', self )
|
||||
if self.datapath == 'user':
|
||||
self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' )
|
||||
int( self.dpid, 16 ) # DPID must be a hex string
|
||||
self.cmd( 'ovs-vsctl -- set Bridge', self,
|
||||
'other_config:datapath-id=' + self.dpid )
|
||||
self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
|
||||
for intf in self.intfList():
|
||||
if not intf.IP():
|
||||
self.attach( intf )
|
||||
# Add controllers
|
||||
clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
|
||||
for c in controllers ] )
|
||||
# Interfaces and controllers
|
||||
intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
|
||||
'-- set Interface %s ' % intf +
|
||||
'ofport_request=%s ' % self.ports[ intf ]
|
||||
for intf in self.intfList() if not intf.IP() )
|
||||
clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
|
||||
for c in controllers )
|
||||
if self.listenPort:
|
||||
clist += ' ptcp:%s' % self.listenPort
|
||||
self.cmd( 'ovs-vsctl set-controller', self, clist )
|
||||
# Construct big ovs-vsctl command for new versions of OVS
|
||||
if not self.isOldOVS():
|
||||
cmd = ( 'ovs-vsctl add-br %s ' % self +
|
||||
'-- set Bridge %s ' % self +
|
||||
'other_config:datapath-id=%s ' % self.dpid +
|
||||
'-- set-fail-mode %s %s ' % ( self, self.failMode ) +
|
||||
intfs +
|
||||
'-- set-controller %s %s ' % ( self, clist ) )
|
||||
# Construct ovs-vsctl commands for old versions of OVS
|
||||
else:
|
||||
self.cmd( 'ovs-vsctl add-br', self )
|
||||
for intf in self.intfList():
|
||||
if not intf.IP():
|
||||
self.cmd( 'ovs-vsctl add-port', self, intf )
|
||||
cmd = ( 'ovs-vsctl set Bridge %s ' % self +
|
||||
'other_config:datapath-id=%s ' % self.dpid +
|
||||
'-- set-fail-mode %s %s ' % ( self, self.failMode ) +
|
||||
'-- set-controller %s %s ' % ( self, clist ) )
|
||||
if not self.inband:
|
||||
cmd += ( '-- set bridge %s '
|
||||
'other-config:disable-in-band=true ' % self )
|
||||
if self.datapath == 'user':
|
||||
cmd += '-- set bridge %s datapath_type=netdev ' % self
|
||||
# Reconnect quickly to controllers (1s vs. 15s max_backoff)
|
||||
for uuid in self.controllerUUIDs():
|
||||
if uuid.count( '-' ) != 4:
|
||||
# Doesn't look like a UUID
|
||||
continue
|
||||
uuid = uuid.strip()
|
||||
self.cmd( 'ovs-vsctl set Controller', uuid,
|
||||
'max_backoff=1000' )
|
||||
cmd += '-- set Controller %smax_backoff=1000 ' % uuid
|
||||
# Do it!!
|
||||
self.cmd( cmd )
|
||||
for intf in self.intfList():
|
||||
self.TCReapply( intf )
|
||||
|
||||
|
||||
def stop( self ):
|
||||
"Terminate OVS switch."
|
||||
@@ -1063,8 +1155,9 @@ OVSKernelSwitch = OVSSwitch
|
||||
class IVSSwitch(Switch):
|
||||
"""IVS virtual switch"""
|
||||
|
||||
def __init__( self, name, **kwargs ):
|
||||
def __init__( self, name, verbose=True, **kwargs ):
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
self.verbose = verbose
|
||||
|
||||
@classmethod
|
||||
def setup( cls ):
|
||||
@@ -1079,12 +1172,19 @@ class IVSSwitch(Switch):
|
||||
'not be loaded. Try modprobe openvswitch.\n' )
|
||||
exit( 1 )
|
||||
|
||||
@classmethod
|
||||
def batchShutdown( cls, switches ):
|
||||
"Kill each IVS switch, to be waited on later in stop()"
|
||||
for switch in switches:
|
||||
switch.cmd( 'kill %ivs' )
|
||||
|
||||
def start( self, controllers ):
|
||||
"Start up a new IVS switch"
|
||||
args = ['ivs']
|
||||
args.extend( ['--name', self.name] )
|
||||
args.extend( ['--dpid', self.dpid] )
|
||||
args.extend( ['--verbose'] )
|
||||
if self.verbose:
|
||||
args.extend( ['--verbose'] )
|
||||
for intf in self.intfs.values():
|
||||
if not intf.IP():
|
||||
args.extend( ['-i', intf.name] )
|
||||
@@ -1096,11 +1196,13 @@ 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 ):
|
||||
"Terminate IVS switch."
|
||||
self.cmd( 'kill %ivs' )
|
||||
self.cmd( 'wait' )
|
||||
self.deleteIntfs()
|
||||
|
||||
def attach( self, intf ):
|
||||
@@ -1125,12 +1227,13 @@ class Controller( Node ):
|
||||
|
||||
def __init__( self, name, inNamespace=False, command='controller',
|
||||
cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
|
||||
port=6633, **params ):
|
||||
port=6633, protocol='tcp', **params ):
|
||||
self.command = command
|
||||
self.cargs = cargs
|
||||
self.cdir = cdir
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.protocol = protocol
|
||||
Node.__init__( self, name, inNamespace=inNamespace,
|
||||
ip=ip, **params )
|
||||
self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
|
||||
@@ -1147,7 +1250,7 @@ class Controller( Node ):
|
||||
listening = self.cmd( "echo A | telnet -e A %s %d" %
|
||||
( self.ip, self.port ) )
|
||||
if 'Connected' in listening:
|
||||
servers = self.cmd( 'netstat -atp' ).split( '\n' )
|
||||
servers = self.cmd( 'netstat -natp' ).split( '\n' )
|
||||
pstr = ':%d ' % self.port
|
||||
clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
|
||||
raise Exception( "Please shut down the controller which is"
|
||||
@@ -1183,13 +1286,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."
|
||||
@@ -1245,6 +1354,12 @@ class RemoteController( Controller ):
|
||||
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 )
|
||||
|
||||
class NAT( Node ):
|
||||
"""NAT: Provides connectivity to external network"""
|
||||
|
||||
@@ -1311,3 +1426,4 @@ class NAT( Node ):
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
||||
|
||||
super( NAT, self ).terminate()
|
||||
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -53,8 +53,11 @@ class testOptionsTopoCommon( object ):
|
||||
"""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.assertTrue( float(measured) >= float(expected) * tolerance_frac )
|
||||
self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
|
||||
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
|
||||
@@ -120,7 +125,7 @@ class testOptionsTopoCommon( object ):
|
||||
for _ in range(REPS):
|
||||
dropped_total += mn.ping(timeout='1')
|
||||
mn.stop()
|
||||
self.assertTrue(dropped_total > 0)
|
||||
self.assertGreater( dropped_total, 0 )
|
||||
|
||||
def testMostOptions( self ):
|
||||
"Verify topology creation with most link options and CPU limits."
|
||||
|
||||
@@ -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 )
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ TODO: missing xterm test
|
||||
import unittest
|
||||
import pexpect
|
||||
import os
|
||||
from time import sleep
|
||||
from mininet.util import quietRun
|
||||
|
||||
class testWalkthrough( unittest.TestCase ):
|
||||
|
||||
+44
-57
@@ -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.
|
||||
@@ -168,7 +175,7 @@ class Topo(object):
|
||||
'''
|
||||
if src in self.ports and dst in self.ports[src]:
|
||||
assert dst in self.ports and src in self.ports[dst]
|
||||
return (self.ports[src][dst], self.ports[dst][src])
|
||||
return self.ports[src][dst], self.ports[dst][src]
|
||||
|
||||
def linkInfo( self, src, dst ):
|
||||
"Return link metadata"
|
||||
@@ -194,76 +201,56 @@ class Topo(object):
|
||||
"Items sorted in natural (i.e. alphabetical) order"
|
||||
return sorted(items, key=natural)
|
||||
|
||||
class SingleSwitchTopo(Topo):
|
||||
'''Single switch connected to k hosts.'''
|
||||
|
||||
def __init__(self, k=2, **opts):
|
||||
'''Init.
|
||||
|
||||
@param k number of hosts
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(SingleSwitchTopo, self).__init__(**opts)
|
||||
class SingleSwitchTopo( Topo ):
|
||||
"Single switch connected to k hosts."
|
||||
|
||||
def build( self, k=2, **opts ):
|
||||
"k: number of hosts"
|
||||
self.k = k
|
||||
|
||||
switch = self.addSwitch('s1')
|
||||
for h in irange(1, k):
|
||||
host = self.addHost('h%s' % h)
|
||||
self.addLink(host, switch)
|
||||
switch = self.addSwitch( 's1' )
|
||||
for h in irange( 1, k ):
|
||||
host = self.addHost( 'h%s' % h )
|
||||
self.addLink( host, switch )
|
||||
|
||||
|
||||
class SingleSwitchReversedTopo(Topo):
|
||||
'''Single switch connected to k hosts, with reversed ports.
|
||||
class SingleSwitchReversedTopo( Topo ):
|
||||
"""Single switch connected to k hosts, with reversed ports.
|
||||
The lowest-numbered host is connected to the highest-numbered port.
|
||||
Useful to verify that Mininet properly handles custom port numberings."""
|
||||
|
||||
The lowest-numbered host is connected to the highest-numbered port.
|
||||
|
||||
Useful to verify that Mininet properly handles custom port numberings.
|
||||
'''
|
||||
def __init__(self, k=2, **opts):
|
||||
'''Init.
|
||||
|
||||
@param k number of hosts
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(SingleSwitchReversedTopo, self).__init__(**opts)
|
||||
def build( self, k=2 ):
|
||||
"k: number of hosts"
|
||||
self.k = k
|
||||
switch = self.addSwitch('s1')
|
||||
for h in irange(1, k):
|
||||
host = self.addHost('h%s' % h)
|
||||
self.addLink(host, switch,
|
||||
port1=0, port2=(k - h + 1))
|
||||
switch = self.addSwitch( 's1' )
|
||||
for h in irange( 1, k ):
|
||||
host = self.addHost( 'h%s' % h )
|
||||
self.addLink( host, switch,
|
||||
port1=0, port2=( k - h + 1 ) )
|
||||
|
||||
class LinearTopo(Topo):
|
||||
class LinearTopo( Topo ):
|
||||
"Linear topology of k switches, with n hosts per switch."
|
||||
|
||||
def __init__(self, k=2, n=1, **opts):
|
||||
"""Init.
|
||||
k: number of switches
|
||||
n: number of hosts per switch
|
||||
hconf: host configuration options
|
||||
lconf: link configuration options"""
|
||||
|
||||
super(LinearTopo, self).__init__(**opts)
|
||||
|
||||
def build( self, k=2, n=1, **opts):
|
||||
"""k: number of switches
|
||||
n: number of hosts per switch"""
|
||||
self.k = k
|
||||
self.n = n
|
||||
|
||||
if n == 1:
|
||||
genHostName = lambda i, j: 'h%s' % i
|
||||
else:
|
||||
genHostName = lambda i, j: 'h%ss%d' % (j, i)
|
||||
|
||||
genHostName = lambda i, j: 'h%ss%d' % ( j, i )
|
||||
|
||||
lastSwitch = None
|
||||
for i in irange(1, k):
|
||||
for i in irange( 1, k ):
|
||||
# Add switch
|
||||
switch = self.addSwitch('s%s' % i)
|
||||
switch = self.addSwitch( 's%s' % i )
|
||||
# Add hosts to switch
|
||||
for j in irange(1, n):
|
||||
host = self.addHost(genHostName(i, j))
|
||||
self.addLink(host, switch)
|
||||
for j in irange( 1, n ):
|
||||
host = self.addHost( genHostName( i, j ) )
|
||||
self.addLink( host, switch )
|
||||
# Connect switch to previous
|
||||
if lastSwitch:
|
||||
self.addLink(switch, lastSwitch)
|
||||
self.addLink( switch, lastSwitch )
|
||||
lastSwitch = switch
|
||||
|
||||
+35
-2
@@ -6,8 +6,7 @@ from mininet.net import Mininet
|
||||
class TreeTopo( Topo ):
|
||||
"Topology for a tree network with a given depth and fanout."
|
||||
|
||||
def __init__( self, depth=1, fanout=2 ):
|
||||
super( TreeTopo, self ).__init__()
|
||||
def build( self, depth=1, fanout=2 ):
|
||||
# Numbering: h1..N, s1..M
|
||||
self.hostNum = 1
|
||||
self.switchNum = 1
|
||||
@@ -34,3 +33,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 build( self, x, y ):
|
||||
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 )
|
||||
|
||||
|
||||
|
||||
|
||||
+14
-4
@@ -155,7 +155,12 @@ def makeIntfPair( intf1, intf2 ):
|
||||
quietRun( 'ip link del ' + intf2 )
|
||||
# Create new pair
|
||||
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
|
||||
return quietRun( cmd )
|
||||
cmdOutput = quietRun( cmd )
|
||||
if cmdOutput == '':
|
||||
return True
|
||||
else:
|
||||
error( "Error creating interface pair: %s " % cmdOutput )
|
||||
return False
|
||||
|
||||
def retry( retries, delaySecs, fn, *args, **keywords ):
|
||||
"""Try something several times before giving up.
|
||||
@@ -183,8 +188,7 @@ def moveIntfNoRetry( intf, dstNode, srcNode=None, printError=False ):
|
||||
srcNode.cmd( cmd )
|
||||
else:
|
||||
quietRun( cmd )
|
||||
links = dstNode.cmd( 'ip link show' )
|
||||
if not ( ' %s:' % intf ) in links:
|
||||
if ( ' %s:' % intf ) not in dstNode.cmd( 'ip link show', intf ):
|
||||
if printError:
|
||||
error( '*** Error: moveIntf: ' + intf +
|
||||
' not successfully moved to ' + dstNode.name + '\n' )
|
||||
@@ -269,7 +273,7 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
|
||||
ipBaseNum: option base IP address as int
|
||||
returns IP address as string"""
|
||||
imax = 0xffffffff >> prefixLen
|
||||
assert i <= imax
|
||||
assert i <= imax, 'Not enough IP addresses in the subnet'
|
||||
mask = 0xffffffff ^ imax
|
||||
ipnum = ( ipBaseNum & mask ) + i
|
||||
return ipStr( ipnum )
|
||||
@@ -277,6 +281,8 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
|
||||
def ipParse( ip ):
|
||||
"Parse an IP address and return an unsigned int."
|
||||
args = [ int( arg ) for arg in ip.split( '.' ) ]
|
||||
while ( len(args) < 4 ):
|
||||
args.append( 0 )
|
||||
return ipNum( *args )
|
||||
|
||||
def netParse( ipstr ):
|
||||
@@ -286,6 +292,10 @@ def netParse( ipstr ):
|
||||
if '/' in ipstr:
|
||||
ip, pf = ipstr.split( '/' )
|
||||
prefixLen = int( pf )
|
||||
#if no prefix is specified, set the prefix to 24
|
||||
else:
|
||||
ip = ipstr
|
||||
prefixLen = 24
|
||||
return ipParse( ip ), prefixLen
|
||||
|
||||
def checkInt( s ):
|
||||
|
||||
Reference in New Issue
Block a user