Merge branch 'master' into nat

Conflicts:
	mininet/net.py
	mininet/node.py
This commit is contained in:
Brian O'Connor
2014-08-07 13:40:14 -07:00
34 changed files with 3594 additions and 646 deletions
+27 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+51
View File
@@ -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 )
+14 -9
View File
@@ -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."
+1 -1
View File
@@ -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 )
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 ):