Merge branch 'master' into nat
Conflicts: mininet/net.py mininet/node.py
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
Mininet Installation/Configuration Notes
|
Mininet Installation/Configuration Notes
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
Mininet 2.1.0
|
Mininet 2.1.0+
|
||||||
---
|
---
|
||||||
|
|
||||||
The supported installation methods for Mininet are 1) using a
|
The supported installation methods for Mininet are 1) using a
|
||||||
@@ -51,6 +51,13 @@ like to contribute an installation script, we would welcome it!)
|
|||||||
|
|
||||||
git clone git://github.com/mininet/mininet.git
|
git clone git://github.com/mininet/mininet.git
|
||||||
|
|
||||||
|
Note that the above git command will check out the latest and greatest
|
||||||
|
Mininet (which we recommend!) If you want to run the last tagged/released
|
||||||
|
version of Mininet, use:
|
||||||
|
|
||||||
|
git clone git://github.com/mininet/mininet
|
||||||
|
git checkout -b 2.1.0 2.1.0
|
||||||
|
|
||||||
If you are running Ubuntu, you may be able to use our handy
|
If you are running Ubuntu, you may be able to use our handy
|
||||||
`install.sh` script, which is in `mininet/util`.
|
`install.sh` script, which is in `mininet/util`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Mininet 2.1.0 License
|
Mininet 2.1.0+ License
|
||||||
|
|
||||||
Copyright (c) 2013 Open Networking Laboratory
|
Copyright (c) 2013 Open Networking Laboratory
|
||||||
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
|
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ MANDIR = /usr/share/man/man1
|
|||||||
DOCDIRS = doc/html doc/latex
|
DOCDIRS = doc/html doc/latex
|
||||||
PDF = doc/latex/refman.pdf
|
PDF = doc/latex/refman.pdf
|
||||||
|
|
||||||
|
CFLAGS += -Wall -Wextra
|
||||||
|
|
||||||
all: codecheck test
|
all: codecheck test
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Mininet: Rapid Prototyping for Software Defined Networks
|
|||||||
|
|
||||||
*The best way to emulate almost any network on your laptop!*
|
*The best way to emulate almost any network on your laptop!*
|
||||||
|
|
||||||
Version 2.1.0
|
Version 2.1.0+
|
||||||
|
|
||||||
### What is Mininet?
|
### What is Mininet?
|
||||||
|
|
||||||
@@ -66,9 +66,9 @@ Mininet includes:
|
|||||||
|
|
||||||
`mn -c`
|
`mn -c`
|
||||||
|
|
||||||
### New features in 2.1.0
|
### New features in 2.1.0+
|
||||||
|
|
||||||
Mininet 2.1.0 provides a number of bug fixes as well as
|
Mininet 2.1.0+ provides a number of bug fixes as well as
|
||||||
several new features, including:
|
several new features, including:
|
||||||
|
|
||||||
* Convenient access to `Mininet()` as a dict of nodes
|
* Convenient access to `Mininet()` as a dict of nodes
|
||||||
@@ -127,7 +127,7 @@ Mininet to change the networking world!
|
|||||||
|
|
||||||
### Credits
|
### Credits
|
||||||
|
|
||||||
The Mininet 2.1.0 Team:
|
The Mininet 2.1.0+ Team:
|
||||||
|
|
||||||
* Bob Lantz
|
* Bob Lantz
|
||||||
* Brian O'Connor
|
* Brian O'Connor
|
||||||
|
|||||||
@@ -25,11 +25,13 @@ from mininet.cli import CLI
|
|||||||
from mininet.log import lg, LEVELS, info, debug, error
|
from mininet.log import lg, LEVELS, info, debug, error
|
||||||
from mininet.net import Mininet, MininetWithControlNet, VERSION
|
from mininet.net import Mininet, MininetWithControlNet, VERSION
|
||||||
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
||||||
NOX, RemoteController, UserSwitch, OVSKernelSwitch,
|
NOX, RemoteController, DefaultController,
|
||||||
|
UserSwitch, OVSSwitch,
|
||||||
OVSLegacyKernelSwitch, IVSSwitch )
|
OVSLegacyKernelSwitch, IVSSwitch )
|
||||||
|
from mininet.nodelib import LinuxBridge
|
||||||
from mininet.link import Link, TCLink
|
from mininet.link import Link, TCLink
|
||||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
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 custom, customConstructor
|
||||||
from mininet.util import buildTopo
|
from mininet.util import buildTopo
|
||||||
|
|
||||||
@@ -40,24 +42,29 @@ TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
|||||||
'linear': LinearTopo,
|
'linear': LinearTopo,
|
||||||
'reversed': SingleSwitchReversedTopo,
|
'reversed': SingleSwitchReversedTopo,
|
||||||
'single': SingleSwitchTopo,
|
'single': SingleSwitchTopo,
|
||||||
'tree': TreeTopo }
|
'tree': TreeTopo,
|
||||||
|
'torus': TorusTopo }
|
||||||
|
|
||||||
SWITCHDEF = 'ovsk'
|
SWITCHDEF = 'ovsk'
|
||||||
SWITCHES = { 'user': UserSwitch,
|
SWITCHES = { 'user': UserSwitch,
|
||||||
'ovsk': OVSKernelSwitch,
|
'ovs': OVSSwitch,
|
||||||
|
# Keep ovsk for compatibility with 2.0
|
||||||
|
'ovsk': OVSSwitch,
|
||||||
'ovsl': OVSLegacyKernelSwitch,
|
'ovsl': OVSLegacyKernelSwitch,
|
||||||
'ivs': IVSSwitch }
|
'ivs': IVSSwitch,
|
||||||
|
'lxbr': LinuxBridge }
|
||||||
|
|
||||||
HOSTDEF = 'proc'
|
HOSTDEF = 'proc'
|
||||||
HOSTS = { 'proc': Host,
|
HOSTS = { 'proc': Host,
|
||||||
'rt': custom( CPULimitedHost, sched='rt' ),
|
'rt': custom( CPULimitedHost, sched='rt' ),
|
||||||
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
||||||
|
|
||||||
CONTROLLERDEF = 'ovsc'
|
CONTROLLERDEF = 'default'
|
||||||
CONTROLLERS = { 'ref': Controller,
|
CONTROLLERS = { 'ref': Controller,
|
||||||
'ovsc': OVSController,
|
'ovsc': OVSController,
|
||||||
'nox': NOX,
|
'nox': NOX,
|
||||||
'remote': RemoteController,
|
'remote': RemoteController,
|
||||||
|
'default': DefaultController,
|
||||||
'none': lambda name: None }
|
'none': lambda name: None }
|
||||||
|
|
||||||
LINKDEF = 'default'
|
LINKDEF = 'default'
|
||||||
@@ -268,12 +275,14 @@ class MininetRunner( object ):
|
|||||||
if test == 'none':
|
if test == 'none':
|
||||||
pass
|
pass
|
||||||
elif test == 'all':
|
elif test == 'all':
|
||||||
|
mn.waitConnected()
|
||||||
mn.start()
|
mn.start()
|
||||||
mn.ping()
|
mn.ping()
|
||||||
mn.iperf()
|
mn.iperf()
|
||||||
elif test == 'cli':
|
elif test == 'cli':
|
||||||
CLI( mn )
|
CLI( mn )
|
||||||
elif test != 'build':
|
elif test != 'build':
|
||||||
|
mn.waitConnected()
|
||||||
getattr( mn, test )()
|
getattr( mn, test )()
|
||||||
|
|
||||||
if self.options.post:
|
if self.options.post:
|
||||||
|
|||||||
@@ -117,3 +117,8 @@ memory and `sysctl` configuration (see `INSTALL`.)
|
|||||||
|
|
||||||
This example creates a 64-host tree network, and attempts to check full
|
This example creates a 64-host tree network, and attempts to check full
|
||||||
connectivity using `ping`, for different switch/datapath types.
|
connectivity using `ping`, for different switch/datapath types.
|
||||||
|
|
||||||
|
#### numberedports.py
|
||||||
|
|
||||||
|
This example verifies the mininet ofport numbers match up to the ovs port numbers.
|
||||||
|
It also verifies that the port numbers match up to the interface numbers
|
||||||
|
|||||||
+42
-167
@@ -1,197 +1,72 @@
|
|||||||
#!/usr/bin/python
|
#!/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.net import Mininet
|
||||||
from mininet.node import Host
|
from mininet.node import Host, HostWithPrivateDirs
|
||||||
from mininet.cli import CLI
|
from mininet.cli import CLI
|
||||||
from mininet.util import errFail, quietRun, errRun
|
|
||||||
from mininet.topo import SingleSwitchTopo
|
from mininet.topo import SingleSwitchTopo
|
||||||
from mininet.log import setLogLevel, info, debug
|
from mininet.log import setLogLevel, info, debug
|
||||||
|
|
||||||
from os.path import realpath
|
|
||||||
from functools import partial
|
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
|
# Sample usage
|
||||||
|
|
||||||
def testHostWithPrivateDirs():
|
def testHostWithPrivateDirs():
|
||||||
"Test bind mounts"
|
"Test bind mounts"
|
||||||
topo = SingleSwitchTopo( 10 )
|
topo = SingleSwitchTopo( 10 )
|
||||||
remounts = findRemounts( fstypes=[ 'nfs' ] )
|
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
|
||||||
privateDirs = [ '/var/log', '/var/run' ]
|
( '/var/run', '/tmp/%(name)s/var/run' ),
|
||||||
host = partial( HostWithPrivateDirs, remounts=remounts,
|
'/var/mn' ]
|
||||||
privateDirs=privateDirs, unmount=False )
|
host = partial( HostWithPrivateDirs,
|
||||||
|
privateDirs=privateDirs )
|
||||||
net = Mininet( topo=topo, host=host )
|
net = Mininet( topo=topo, host=host )
|
||||||
net.start()
|
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 )
|
CLI( net )
|
||||||
net.stop()
|
net.stop()
|
||||||
# We do this all at once to save a bit of time
|
|
||||||
info( 'Unmounting host bind mounts...\n' )
|
|
||||||
unmountAll()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unmountAll()
|
|
||||||
setLogLevel( 'info' )
|
setLogLevel( 'info' )
|
||||||
testHostWithPrivateDirs()
|
testHostWithPrivateDirs()
|
||||||
info( 'Done.\n')
|
info( 'Done.\n')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from mininet.log import setLogLevel
|
|||||||
def multiControllerNet():
|
def multiControllerNet():
|
||||||
"Create a network from semi-scratch with multiple controllers."
|
"Create a network from semi-scratch with multiple controllers."
|
||||||
|
|
||||||
net = Mininet( controller=Controller, switch=OVSSwitch, build=False )
|
net = Mininet( controller=Controller, switch=OVSSwitch )
|
||||||
|
|
||||||
print "*** Creating (reference) controllers"
|
print "*** Creating (reference) controllers"
|
||||||
c1 = net.addController( 'c1', port=6633 )
|
c1 = net.addController( 'c1', port=6633 )
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ of switches, this example demonstrates:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from mininet.net import Mininet
|
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.topo import Topo
|
||||||
from mininet.log import lg
|
from mininet.log import lg
|
||||||
from mininet.util import irange
|
from mininet.util import irange
|
||||||
@@ -76,7 +76,7 @@ def linearBandwidthTest( lengths ):
|
|||||||
print "*** testing", datapath, "datapath"
|
print "*** testing", datapath, "datapath"
|
||||||
Switch = switches[ datapath ]
|
Switch = switches[ datapath ]
|
||||||
results[ datapath ] = []
|
results[ datapath ] = []
|
||||||
net = Mininet( topo=topo, switch=Switch )
|
net = Mininet( topo=topo, switch=Switch, controller=Controller, waitConnected=True )
|
||||||
net.start()
|
net.start()
|
||||||
print "*** testing basic connectivity"
|
print "*** testing basic connectivity"
|
||||||
for n in lengths:
|
for n in lengths:
|
||||||
|
|||||||
+2684
-85
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -59,13 +59,13 @@ def fixNetworkManager( root, intf ):
|
|||||||
cfile = '/etc/network/interfaces'
|
cfile = '/etc/network/interfaces'
|
||||||
line = '\niface %s inet manual\n' % intf
|
line = '\niface %s inet manual\n' % intf
|
||||||
config = open( cfile ).read()
|
config = open( cfile ).read()
|
||||||
if ( line ) not in config:
|
if line not in config:
|
||||||
print '*** Adding', line.strip(), 'to', cfile
|
print '*** Adding', line.strip(), 'to', cfile
|
||||||
with open( cfile, 'a' ) as f:
|
with open( cfile, 'a' ) as f:
|
||||||
f.write( line )
|
f.write( line )
|
||||||
# Probably need to restart network-manager to be safe -
|
# Probably need to restart network-manager to be safe -
|
||||||
# hopefully this won't disconnect you
|
# hopefully this won't disconnect you
|
||||||
root.cmd( 'service network-manager restart' )
|
root.cmd( 'service network-manager restart' )
|
||||||
|
|
||||||
def connectToInternet( network, switch='s1', rootip='10.254', subnet='10.0/8'):
|
def connectToInternet( network, switch='s1', rootip='10.254', subnet='10.0/8'):
|
||||||
"""Connect the network to the internet
|
"""Connect the network to the internet
|
||||||
|
|||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Create a network with 5 hosts, numbered 1-4 and 9.
|
||||||
|
Validate that the port numbers match to the interface name,
|
||||||
|
and that the ovs ports match the mininet ports.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from mininet.net import Mininet
|
||||||
|
from mininet.node import Controller
|
||||||
|
from mininet.log import setLogLevel, info, warn
|
||||||
|
from mininet.node import Node
|
||||||
|
|
||||||
|
def validatePort( switch, intf ):
|
||||||
|
"Validate intf's OF port number"
|
||||||
|
ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf,
|
||||||
|
'ofport' ) )
|
||||||
|
if ofport != switch.ports[ intf ]:
|
||||||
|
warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' )
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def net():
|
||||||
|
|
||||||
|
"Create a network with 5 hosts."
|
||||||
|
|
||||||
|
net = Mininet( controller=Controller )
|
||||||
|
|
||||||
|
info( '*** Adding controller\n' )
|
||||||
|
net.addController( 'c0' )
|
||||||
|
|
||||||
|
info( '*** Adding hosts\n' )
|
||||||
|
h1 = net.addHost( 'h1', ip='10.0.0.1' )
|
||||||
|
h2 = net.addHost( 'h2', ip='10.0.0.2' )
|
||||||
|
h3 = net.addHost( 'h3', ip='10.0.0.3' )
|
||||||
|
h4 = net.addHost( 'h4', ip='10.0.0.4' )
|
||||||
|
h5 = net.addHost( 'h5', ip='10.0.0.5' )
|
||||||
|
|
||||||
|
info( '*** Adding switch\n' )
|
||||||
|
s1 = net.addSwitch( 's1' )
|
||||||
|
|
||||||
|
info( '*** Creating links\n' )
|
||||||
|
# host 1-4 connect to ports 1-4 on the switch
|
||||||
|
net.addLink( h1, s1 )
|
||||||
|
net.addLink( h2, s1 )
|
||||||
|
net.addLink( h3, s1 )
|
||||||
|
net.addLink( h4, s1 )
|
||||||
|
net.addLink( h5, s1, port1 = 1, port2 = 9 ) # specify a different port to connect host 5 to on the switch.
|
||||||
|
|
||||||
|
root = Node( 'root', inNamespace=False )
|
||||||
|
info( '*** Starting network\n' )
|
||||||
|
net.start()
|
||||||
|
|
||||||
|
# print the interfaces and their port numbers
|
||||||
|
info( '\n*** printing and validating the ports running on each interface\n' )
|
||||||
|
for intfs in s1.intfList():
|
||||||
|
if not intfs.name == "lo":
|
||||||
|
info( intfs, ': ', s1.ports[intfs],
|
||||||
|
'\n' )
|
||||||
|
info ( 'Validating that', intfs, 'is actually on port', s1.ports[intfs], '... ' )
|
||||||
|
if validatePort( s1, intfs ):
|
||||||
|
info( 'Validated.\n' )
|
||||||
|
print '\n'
|
||||||
|
|
||||||
|
# test the network with pingall
|
||||||
|
net.pingAll()
|
||||||
|
print '\n'
|
||||||
|
|
||||||
|
info( '*** Stopping network' )
|
||||||
|
net.stop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
setLogLevel( 'info' )
|
||||||
|
net()
|
||||||
|
|
||||||
+1
-1
@@ -55,7 +55,7 @@ def sshd( network, cmd='/usr/sbin/sshd', opts='-D',
|
|||||||
if not switch:
|
if not switch:
|
||||||
switch = network[ 's1' ] # switch to use
|
switch = network[ 's1' ] # switch to use
|
||||||
if not routes:
|
if not routes:
|
||||||
routes = [ '10.0.0.0/24' ]
|
routes = [ '10.0.0.0/24' ]
|
||||||
connectToRootNS( network, switch, ip, routes )
|
connectToRootNS( network, switch, ip, routes )
|
||||||
for host in network.hosts:
|
for host in network.hosts:
|
||||||
host.cmd( cmd + ' ' + opts + '&' )
|
host.cmd( cmd + ' ' + opts + '&' )
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ Test for hwintf.py
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import pexpect
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import pexpect
|
||||||
|
|
||||||
from mininet.log import setLogLevel
|
from mininet.log import setLogLevel
|
||||||
from mininet.net import Mininet
|
|
||||||
from mininet.node import Node
|
from mininet.node import Node
|
||||||
from mininet.link import Link, Intf
|
from mininet.link import Link
|
||||||
|
|
||||||
|
|
||||||
class testHwintf( unittest.TestCase ):
|
class testHwintf( unittest.TestCase ):
|
||||||
|
|
||||||
|
|||||||
Executable
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test for numberedports.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import pexpect
|
||||||
|
from collections import defaultdict
|
||||||
|
from mininet.node import OVSSwitch
|
||||||
|
|
||||||
|
class testNumberedports( unittest.TestCase ):
|
||||||
|
|
||||||
|
@unittest.skipIf( OVSSwitch.setup() or OVSSwitch.isOldOVS(), "old version of OVS" )
|
||||||
|
def testConsistency( self ):
|
||||||
|
"""verify consistency between mininet and ovs ports"""
|
||||||
|
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
|
||||||
|
opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.',
|
||||||
|
'Validating that s1-eth\d is actually on port \d ... WARNING',
|
||||||
|
pexpect.EOF ]
|
||||||
|
correct_ports = True
|
||||||
|
count = 0
|
||||||
|
while True:
|
||||||
|
index = p.expect( opts )
|
||||||
|
if index == 0:
|
||||||
|
count += 1
|
||||||
|
elif index == 1:
|
||||||
|
correct_ports = False
|
||||||
|
elif index == 2:
|
||||||
|
self.assertNotEqual( 0, count )
|
||||||
|
break
|
||||||
|
self.assertTrue( correct_ports )
|
||||||
|
|
||||||
|
def testNumbering( self ):
|
||||||
|
"""verify that all of the port numbers are printed correctly and consistent with their interface"""
|
||||||
|
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
|
||||||
|
opts = [ 's1-eth(\d+) : (\d+)',
|
||||||
|
pexpect.EOF ]
|
||||||
|
count_intfs = 0
|
||||||
|
while True:
|
||||||
|
index = p.expect( opts )
|
||||||
|
if index == 0:
|
||||||
|
count_intfs += 1
|
||||||
|
intfport = p.match.group( 1 )
|
||||||
|
ofport = p.match.group( 2 )
|
||||||
|
self.assertEqual( intfport, ofport )
|
||||||
|
elif index == 1:
|
||||||
|
break
|
||||||
|
self.assertNotEqual( 0, count_intfs )
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -24,7 +24,7 @@ class testSimplePerf( unittest.TestCase ):
|
|||||||
# check ping results
|
# check ping results
|
||||||
p.expect( "Results: (\d+)% dropped", timeout=120 )
|
p.expect( "Results: (\d+)% dropped", timeout=120 )
|
||||||
loss = int( p.match.group( 1 ) )
|
loss = int( p.match.group( 1 ) )
|
||||||
self.assertTrue( loss > 0 and loss < 100 )
|
self.assertTrue( 0 < loss < 100 )
|
||||||
# check iperf results
|
# check iperf results
|
||||||
p.expect( "Results: \['([\d\.]+) .bits/sec", timeout=480 )
|
p.expect( "Results: \['([\d\.]+) .bits/sec", timeout=480 )
|
||||||
bw = float( p.match.group( 1 ) )
|
bw = float( p.match.group( 1 ) )
|
||||||
@@ -46,7 +46,7 @@ class testSimplePerf( unittest.TestCase ):
|
|||||||
m = re.search( expectStr, output )
|
m = re.search( expectStr, output )
|
||||||
loss = int( m.group( 3 ) )
|
loss = int( m.group( 3 ) )
|
||||||
net.stop()
|
net.stop()
|
||||||
self.assertTrue( loss > 0 and loss < 100 )
|
self.assertTrue( 0 < loss < 100 )
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
setLogLevel( 'warning' )
|
setLogLevel( 'warning' )
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ Test for sshd.py
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import pexpect
|
import pexpect
|
||||||
from time import sleep
|
|
||||||
from mininet.clean import sh
|
from mininet.clean import sh
|
||||||
|
|
||||||
class testSSHD( unittest.TestCase ):
|
class testSSHD( unittest.TestCase ):
|
||||||
|
|||||||
+27
-8
@@ -10,7 +10,7 @@ It may also get rid of 'false positives', but hopefully
|
|||||||
nothing irreplaceable!
|
nothing irreplaceable!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE, check_output as co
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from mininet.log import info
|
from mininet.log import info
|
||||||
@@ -47,21 +47,40 @@ def cleanup():
|
|||||||
cleanUpScreens()
|
cleanUpScreens()
|
||||||
|
|
||||||
info( "*** Removing excess kernel datapaths\n" )
|
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:
|
for dp in dps:
|
||||||
if dp != '':
|
if dp:
|
||||||
sh( 'dpctl deldp ' + dp )
|
sh( 'dpctl deldp ' + dp )
|
||||||
|
|
||||||
info( "*** Removing OVS datapaths" )
|
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:
|
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" )
|
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:
|
for link in links:
|
||||||
if link != '':
|
if link:
|
||||||
sh( "ip link del " + 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" )
|
info( "*** Cleanup complete.\n" )
|
||||||
|
|||||||
+29
-10
@@ -31,6 +31,8 @@ from os import isatty
|
|||||||
from select import poll, POLLIN
|
from select import poll, POLLIN
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
|
||||||
from mininet.log import info, output, error
|
from mininet.log import info, output, error
|
||||||
from mininet.term import makeTerms, runX11
|
from mininet.term import makeTerms, runX11
|
||||||
@@ -52,6 +54,18 @@ class CLI( Cmd ):
|
|||||||
self.inputFile = script
|
self.inputFile = script
|
||||||
Cmd.__init__( self )
|
Cmd.__init__( self )
|
||||||
info( '*** Starting CLI:\n' )
|
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:
|
if self.inputFile:
|
||||||
self.do_source( self.inputFile )
|
self.do_source( self.inputFile )
|
||||||
return
|
return
|
||||||
@@ -63,7 +77,7 @@ class CLI( Cmd ):
|
|||||||
node.sendInt()
|
node.sendInt()
|
||||||
node.monitor()
|
node.monitor()
|
||||||
if self.isatty():
|
if self.isatty():
|
||||||
quietRun( 'stty sane' )
|
quietRun( 'stty echo sane intr "^C"' )
|
||||||
self.cmdloop()
|
self.cmdloop()
|
||||||
break
|
break
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
@@ -151,16 +165,16 @@ class CLI( Cmd ):
|
|||||||
|
|
||||||
# pylint: enable-msg=W0703,W0122
|
# pylint: enable-msg=W0703,W0122
|
||||||
|
|
||||||
def do_pingall( self, _line ):
|
def do_pingall( self, line ):
|
||||||
"Ping between all hosts."
|
"Ping between all hosts."
|
||||||
self.mn.pingAll()
|
self.mn.pingAll( line )
|
||||||
|
|
||||||
def do_pingpair( self, _line ):
|
def do_pingpair( self, _line ):
|
||||||
"Ping between first two hosts, useful for testing."
|
"Ping between first two hosts, useful for testing."
|
||||||
self.mn.pingPair()
|
self.mn.pingPair()
|
||||||
|
|
||||||
def do_pingallfull( self, _line ):
|
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()
|
self.mn.pingAllFull()
|
||||||
|
|
||||||
def do_pingpairfull( self, _line ):
|
def do_pingpairfull( self, _line ):
|
||||||
@@ -187,7 +201,7 @@ class CLI( Cmd ):
|
|||||||
error( 'invalid number of args: iperf src dst\n' )
|
error( 'invalid number of args: iperf src dst\n' )
|
||||||
|
|
||||||
def do_iperfudp( self, line ):
|
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()
|
args = line.split()
|
||||||
if not args:
|
if not args:
|
||||||
self.mn.iperf( l4Type='UDP' )
|
self.mn.iperf( l4Type='UDP' )
|
||||||
@@ -297,6 +311,7 @@ class CLI( Cmd ):
|
|||||||
break
|
break
|
||||||
except IOError:
|
except IOError:
|
||||||
error( 'error reading file %s\n' % args[ 0 ] )
|
error( 'error reading file %s\n' % args[ 0 ] )
|
||||||
|
self.inputFile.close()
|
||||||
self.inputFile = None
|
self.inputFile = None
|
||||||
|
|
||||||
def do_dpctl( self, line ):
|
def do_dpctl( self, line ):
|
||||||
@@ -331,13 +346,13 @@ class CLI( Cmd ):
|
|||||||
node = self.mn[ first ]
|
node = self.mn[ first ]
|
||||||
rest = args.split( ' ' )
|
rest = args.split( ' ' )
|
||||||
# Substitute IP addresses for node names in command
|
# 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
|
if arg in self.mn else arg
|
||||||
for arg in rest ]
|
for arg in rest ]
|
||||||
rest = ' '.join( rest )
|
rest = ' '.join( rest )
|
||||||
# Run cmd on node:
|
# Run cmd on node:
|
||||||
builtin = isShellBuiltin( first )
|
node.sendCmd( rest )
|
||||||
node.sendCmd( rest, printPid=( not builtin ) )
|
|
||||||
self.waitForNode( node )
|
self.waitForNode( node )
|
||||||
else:
|
else:
|
||||||
error( '*** Unknown command: %s\n' % line )
|
error( '*** Unknown command: %s\n' % line )
|
||||||
@@ -345,7 +360,7 @@ class CLI( Cmd ):
|
|||||||
# pylint: enable-msg=R0201
|
# pylint: enable-msg=R0201
|
||||||
|
|
||||||
def waitForNode( self, node ):
|
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
|
# Pollers
|
||||||
nodePoller = poll()
|
nodePoller = poll()
|
||||||
nodePoller.register( node.stdout )
|
nodePoller.register( node.stdout )
|
||||||
@@ -363,7 +378,7 @@ class CLI( Cmd ):
|
|||||||
if False and self.inputFile:
|
if False and self.inputFile:
|
||||||
key = self.inputFile.read( 1 )
|
key = self.inputFile.read( 1 )
|
||||||
if key is not '':
|
if key is not '':
|
||||||
node.write(key)
|
node.write( key )
|
||||||
else:
|
else:
|
||||||
self.inputFile = None
|
self.inputFile = None
|
||||||
if isReadable( self.inPoller ):
|
if isReadable( self.inPoller ):
|
||||||
@@ -375,8 +390,12 @@ class CLI( Cmd ):
|
|||||||
if not node.waiting:
|
if not node.waiting:
|
||||||
break
|
break
|
||||||
except KeyboardInterrupt:
|
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()
|
node.sendInt()
|
||||||
|
|
||||||
|
|
||||||
# Helper functions
|
# Helper functions
|
||||||
|
|
||||||
def isReadable( poller ):
|
def isReadable( poller ):
|
||||||
|
|||||||
+2
-2
@@ -57,7 +57,7 @@ class StreamHandlerNoNewline( logging.StreamHandler ):
|
|||||||
|
|
||||||
class Singleton( type ):
|
class Singleton( type ):
|
||||||
"""Singleton pattern from Wikipedia
|
"""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
|
Intended to be used as a __metaclass_ param, as shown for the class
|
||||||
below."""
|
below."""
|
||||||
@@ -69,7 +69,7 @@ class Singleton( type ):
|
|||||||
def __call__( cls, *args, **kw ):
|
def __call__( cls, *args, **kw ):
|
||||||
if cls.instance is None:
|
if cls.instance is None:
|
||||||
cls.instance = super( Singleton, cls ).__call__( *args, **kw )
|
cls.instance = super( Singleton, cls ).__call__( *args, **kw )
|
||||||
return cls.instance
|
return cls.instance
|
||||||
|
|
||||||
|
|
||||||
class MininetLogger( Logger, object ):
|
class MininetLogger( Logger, object ):
|
||||||
|
|||||||
+66
-22
@@ -90,29 +90,30 @@ import os
|
|||||||
import re
|
import re
|
||||||
import select
|
import select
|
||||||
import signal
|
import signal
|
||||||
|
import copy
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from itertools import chain
|
from itertools import chain, groupby
|
||||||
|
|
||||||
from mininet.cli import CLI
|
from mininet.cli import CLI
|
||||||
from mininet.log import info, error, debug, output
|
from mininet.log import info, error, debug, output, warn
|
||||||
from mininet.node import Host, OVSKernelSwitch, Controller, NAT
|
from mininet.node import Host, OVSKernelSwitch, DefaultController, Controller, NAT
|
||||||
from mininet.link import Link, Intf
|
from mininet.link import Link, Intf
|
||||||
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
|
from mininet.util import quietRun, fixLimits, numCores, ensureRoot
|
||||||
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||||
from mininet.term import cleanUpScreens, makeTerms
|
from mininet.term import cleanUpScreens, makeTerms
|
||||||
|
|
||||||
# Mininet version: should be consistent with README and LICENSE
|
# Mininet version: should be consistent with README and LICENSE
|
||||||
VERSION = "2.1.0"
|
VERSION = "2.1.0+"
|
||||||
|
|
||||||
class Mininet( object ):
|
class Mininet( object ):
|
||||||
"Network emulation with hosts spawned in network namespaces."
|
"Network emulation with hosts spawned in network namespaces."
|
||||||
|
|
||||||
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
|
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',
|
build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
|
||||||
inNamespace=False,
|
inNamespace=False,
|
||||||
autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
|
autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
|
||||||
listenPort=None ):
|
listenPort=None, waitConnected=False ):
|
||||||
"""Create Mininet object.
|
"""Create Mininet object.
|
||||||
topo: Topo (topology) object or None
|
topo: Topo (topology) object or None
|
||||||
switch: default Switch class
|
switch: default Switch class
|
||||||
@@ -148,6 +149,7 @@ class Mininet( object ):
|
|||||||
self.numCores = numCores()
|
self.numCores = numCores()
|
||||||
self.nextCore = 0 # next core for pinning hosts to CPUs
|
self.nextCore = 0 # next core for pinning hosts to CPUs
|
||||||
self.listenPort = listenPort
|
self.listenPort = listenPort
|
||||||
|
self.waitConn = waitConnected
|
||||||
|
|
||||||
self.hosts = []
|
self.hosts = []
|
||||||
self.switches = []
|
self.switches = []
|
||||||
@@ -163,6 +165,37 @@ class Mininet( object ):
|
|||||||
if topo and build:
|
if topo and build:
|
||||||
self.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 ):
|
def addHost( self, name, cls=None, **params ):
|
||||||
"""Add host.
|
"""Add host.
|
||||||
name: name of host to add
|
name: name of host to add
|
||||||
@@ -213,7 +246,7 @@ class Mininet( object ):
|
|||||||
if not controller:
|
if not controller:
|
||||||
controller = self.controller
|
controller = self.controller
|
||||||
# Construct new controller if one is not given
|
# Construct new controller if one is not given
|
||||||
if isinstance(name, Controller):
|
if isinstance( name, Controller ):
|
||||||
controller_new = name
|
controller_new = name
|
||||||
# Pylint thinks controller is a str()
|
# Pylint thinks controller is a str()
|
||||||
# pylint: disable=E1103
|
# pylint: disable=E1103
|
||||||
@@ -222,7 +255,7 @@ class Mininet( object ):
|
|||||||
else:
|
else:
|
||||||
controller_new = controller( name, **params )
|
controller_new = controller( name, **params )
|
||||||
# Add new controller to net
|
# 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.controllers.append( controller_new )
|
||||||
self.nameToNode[ name ] = controller_new
|
self.nameToNode[ name ] = controller_new
|
||||||
return controller_new
|
return controller_new
|
||||||
@@ -338,7 +371,11 @@ class Mininet( object ):
|
|||||||
if type( classes ) is not list:
|
if type( classes ) is not list:
|
||||||
classes = [ classes ]
|
classes = [ classes ]
|
||||||
for i, cls in enumerate( 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' )
|
info( '*** Adding hosts:\n' )
|
||||||
for hostName in topo.hosts():
|
for hostName in topo.hosts():
|
||||||
@@ -369,7 +406,7 @@ class Mininet( object ):
|
|||||||
"Build mininet."
|
"Build mininet."
|
||||||
if self.topo:
|
if self.topo:
|
||||||
self.buildFromTopo( self.topo )
|
self.buildFromTopo( self.topo )
|
||||||
if ( self.inNamespace ):
|
if self.inNamespace:
|
||||||
self.configureControlNetwork()
|
self.configureControlNetwork()
|
||||||
info( '*** Configuring hosts\n' )
|
info( '*** Configuring hosts\n' )
|
||||||
self.configHosts()
|
self.configHosts()
|
||||||
@@ -415,13 +452,23 @@ class Mininet( object ):
|
|||||||
info( switch.name + ' ')
|
info( switch.name + ' ')
|
||||||
switch.start( self.controllers )
|
switch.start( self.controllers )
|
||||||
info( '\n' )
|
info( '\n' )
|
||||||
|
if self.waitConn:
|
||||||
|
self.waitConnected()
|
||||||
|
|
||||||
def stop( self ):
|
def stop( self ):
|
||||||
"Stop the controller(s), switches and hosts"
|
"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:
|
if self.terms:
|
||||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||||
self.stopXterms()
|
self.stopXterms()
|
||||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
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:
|
for switch in self.switches:
|
||||||
info( switch.name + ' ' )
|
info( switch.name + ' ' )
|
||||||
switch.stop()
|
switch.stop()
|
||||||
@@ -430,11 +477,6 @@ class Mininet( object ):
|
|||||||
for host in self.hosts:
|
for host in self.hosts:
|
||||||
info( host.name + ' ' )
|
info( host.name + ' ' )
|
||||||
host.terminate()
|
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' )
|
info( '\n*** Done\n' )
|
||||||
|
|
||||||
def run( self, test, *args, **kwargs ):
|
def run( self, test, *args, **kwargs ):
|
||||||
@@ -477,13 +519,13 @@ class Mininet( object ):
|
|||||||
"Parse ping output and return packets sent, received."
|
"Parse ping output and return packets sent, received."
|
||||||
# Check for downed link
|
# Check for downed link
|
||||||
if 'connect: Network is unreachable' in pingOutput:
|
if 'connect: Network is unreachable' in pingOutput:
|
||||||
return (1, 0)
|
return 1, 0
|
||||||
r = r'(\d+) packets transmitted, (\d+) received'
|
r = r'(\d+) packets transmitted, (\d+) received'
|
||||||
m = re.search( r, pingOutput )
|
m = re.search( r, pingOutput )
|
||||||
if m is None:
|
if m is None:
|
||||||
error( '*** Error: could not parse ping output: %s\n' %
|
error( '*** Error: could not parse ping output: %s\n' %
|
||||||
pingOutput )
|
pingOutput )
|
||||||
return (1, 0)
|
return 1, 0
|
||||||
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
|
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
|
||||||
return sent, received
|
return sent, received
|
||||||
|
|
||||||
@@ -518,7 +560,7 @@ class Mininet( object ):
|
|||||||
output( ( '%s ' % dest.name ) if received else 'X ' )
|
output( ( '%s ' % dest.name ) if received else 'X ' )
|
||||||
output( '\n' )
|
output( '\n' )
|
||||||
if packets > 0:
|
if packets > 0:
|
||||||
ploss = 100 * lost / packets
|
ploss = 100.0 * lost / packets
|
||||||
received = packets - lost
|
received = packets - lost
|
||||||
output( "*** Results: %i%% dropped (%d/%d received)\n" %
|
output( "*** Results: %i%% dropped (%d/%d received)\n" %
|
||||||
( ploss, received, packets ) )
|
( ploss, received, packets ) )
|
||||||
@@ -589,10 +631,10 @@ class Mininet( object ):
|
|||||||
(rttmin, rttavg, rttmax, rttdev) )
|
(rttmin, rttavg, rttmax, rttdev) )
|
||||||
return all_outputs
|
return all_outputs
|
||||||
|
|
||||||
def pingAll( self ):
|
def pingAll( self, timeout=None ):
|
||||||
"""Ping between all hosts.
|
"""Ping between all hosts.
|
||||||
returns: ploss packet loss percentage"""
|
returns: ploss packet loss percentage"""
|
||||||
return self.ping()
|
return self.ping( timeout=timeout )
|
||||||
|
|
||||||
def pingPair( self ):
|
def pingPair( self ):
|
||||||
"""Ping between first two hosts, useful for testing.
|
"""Ping between first two hosts, useful for testing.
|
||||||
@@ -627,7 +669,7 @@ class Mininet( object ):
|
|||||||
|
|
||||||
# XXX This should be cleaned up
|
# 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.
|
"""Run iperf between two hosts.
|
||||||
hosts: list of hosts; if None, uses opposite hosts
|
hosts: list of hosts; if None, uses opposite hosts
|
||||||
l4Type: string, one of [ TCP, UDP ]
|
l4Type: string, one of [ TCP, UDP ]
|
||||||
@@ -650,6 +692,8 @@ class Mininet( object ):
|
|||||||
bwArgs = '-b ' + udpBw + ' '
|
bwArgs = '-b ' + udpBw + ' '
|
||||||
elif l4Type != 'TCP':
|
elif l4Type != 'TCP':
|
||||||
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
||||||
|
if format:
|
||||||
|
iperfArgs += '-f %s ' %format
|
||||||
server.sendCmd( iperfArgs + '-s', printPid=True )
|
server.sendCmd( iperfArgs + '-s', printPid=True )
|
||||||
servout = ''
|
servout = ''
|
||||||
while server.lastPid is None:
|
while server.lastPid is None:
|
||||||
@@ -657,7 +701,7 @@ class Mininet( object ):
|
|||||||
if l4Type == 'TCP':
|
if l4Type == 'TCP':
|
||||||
while 'Connected' not in client.cmd(
|
while 'Connected' not in client.cmd(
|
||||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
'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)
|
sleep(.5)
|
||||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||||
bwArgs )
|
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
|
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
|
||||||
RT or CFS bandwidth limiting.
|
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.
|
Switch: superclass for switch nodes.
|
||||||
|
|
||||||
UserSwitch: a switch using the user-space switch from the OpenFlow
|
UserSwitch: a switch using the user-space switch from the OpenFlow
|
||||||
@@ -47,6 +52,7 @@ Future enhancements:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import pty
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
import select
|
import select
|
||||||
@@ -59,6 +65,8 @@ from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
|
|||||||
numCores, retry, mountCgroups )
|
numCores, retry, mountCgroups )
|
||||||
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
|
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
|
||||||
from mininet.link import Link, Intf, TCIntf
|
from mininet.link import Link, Intf, TCIntf
|
||||||
|
from re import findall
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
|
||||||
class Node( object ):
|
class Node( object ):
|
||||||
"""A virtual network node is simply a shell in a network namespace.
|
"""A virtual network node is simply a shell in a network namespace.
|
||||||
@@ -118,16 +126,22 @@ class Node( object ):
|
|||||||
return
|
return
|
||||||
# mnexec: (c)lose descriptors, (d)etach from tty,
|
# mnexec: (c)lose descriptors, (d)etach from tty,
|
||||||
# (p)rint pid, and run in (n)amespace
|
# (p)rint pid, and run in (n)amespace
|
||||||
opts = '-cdp'
|
opts = '-cd'
|
||||||
if self.inNamespace:
|
if self.inNamespace:
|
||||||
opts += 'n'
|
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
|
# -s: pass $* to shell, and make process easy to find in ps
|
||||||
cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
|
# prompt is set to sentinel chr( 127 )
|
||||||
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
|
os.environ[ 'PS1' ] = chr( 127 )
|
||||||
close_fds=True )
|
cmd = [ 'mnexec', opts, 'bash', '--norc', '-mis', 'mininet:' + self.name ]
|
||||||
self.stdin = self.shell.stdin
|
# Spawn a shell subprocess in a pseudo-tty, to disable buffering
|
||||||
self.stdout = self.shell.stdout
|
# 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.pid = self.shell.pid
|
||||||
self.pollOut = select.poll()
|
self.pollOut = select.poll()
|
||||||
self.pollOut.register( self.stdout )
|
self.pollOut.register( self.stdout )
|
||||||
@@ -140,7 +154,14 @@ class Node( object ):
|
|||||||
self.lastCmd = None
|
self.lastCmd = None
|
||||||
self.lastPid = None
|
self.lastPid = None
|
||||||
self.readbuf = ''
|
self.readbuf = ''
|
||||||
|
# Wait for prompt
|
||||||
|
while True:
|
||||||
|
data = self.read( 1024 )
|
||||||
|
if data[ -1 ] == chr( 127 ):
|
||||||
|
break
|
||||||
|
self.pollOut.poll()
|
||||||
self.waiting = False
|
self.waiting = False
|
||||||
|
self.cmd( 'stty -echo' )
|
||||||
|
|
||||||
def cleanup( self ):
|
def cleanup( self ):
|
||||||
"Help python collect its garbage."
|
"Help python collect its garbage."
|
||||||
@@ -186,7 +207,7 @@ class Node( object ):
|
|||||||
def terminate( self ):
|
def terminate( self ):
|
||||||
"Send kill signal to Node and clean up after it."
|
"Send kill signal to Node and clean up after it."
|
||||||
if self.shell:
|
if self.shell:
|
||||||
os.kill( self.pid, signal.SIGKILL )
|
os.killpg( self.pid, signal.SIGKILL )
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
|
||||||
def stop( self ):
|
def stop( self ):
|
||||||
@@ -219,36 +240,29 @@ class Node( object ):
|
|||||||
# Replace empty commands with something harmless
|
# Replace empty commands with something harmless
|
||||||
cmd = 'echo -n'
|
cmd = 'echo -n'
|
||||||
self.lastCmd = cmd
|
self.lastCmd = cmd
|
||||||
printPid = printPid and not isShellBuiltin( cmd )
|
if printPid and not isShellBuiltin( cmd ):
|
||||||
if len( cmd ) > 0 and cmd[ -1 ] == '&':
|
if len( cmd ) > 0 and cmd[ -1 ] == '&':
|
||||||
# print ^A{pid}\n{sentinel}
|
# print ^A{pid}\n so monitor() can set lastPid
|
||||||
cmd += ' printf "\\001%d\n\\177" $! \n'
|
cmd += ' printf "\\001%d\n" $! \n'
|
||||||
else:
|
else:
|
||||||
# print sentinel
|
|
||||||
cmd += '; printf "\\177"'
|
|
||||||
if printPid and not isShellBuiltin( cmd ):
|
|
||||||
cmd = 'mnexec -p ' + cmd
|
cmd = 'mnexec -p ' + cmd
|
||||||
self.write( cmd + '\n' )
|
self.write( cmd + '\n' )
|
||||||
self.lastPid = None
|
self.lastPid = None
|
||||||
self.waiting = True
|
self.waiting = True
|
||||||
|
|
||||||
def sendInt( self, sig=signal.SIGINT ):
|
def sendInt( self, intr=chr( 3 ) ):
|
||||||
"Interrupt running command."
|
"Interrupt running command."
|
||||||
if self.lastPid:
|
self.write( intr )
|
||||||
try:
|
|
||||||
os.kill( self.lastPid, sig )
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def monitor( self, timeoutms=None ):
|
def monitor( self, timeoutms=None, findPid=True ):
|
||||||
"""Monitor and return the output of a command.
|
"""Monitor and return the output of a command.
|
||||||
Set self.waiting to False if command has completed.
|
Set self.waiting to False if command has completed.
|
||||||
timeoutms: timeout in ms or None to wait indefinitely."""
|
timeoutms: timeout in ms or None to wait indefinitely."""
|
||||||
self.waitReadable( timeoutms )
|
self.waitReadable( timeoutms )
|
||||||
data = self.read( 1024 )
|
data = self.read( 1024 )
|
||||||
# Look for PID
|
# Look for PID
|
||||||
marker = chr( 1 ) + r'\d+\n'
|
marker = chr( 1 ) + r'\d+\r\n'
|
||||||
if chr( 1 ) in data:
|
if findPid and chr( 1 ) in data:
|
||||||
markers = re.findall( marker, data )
|
markers = re.findall( marker, data )
|
||||||
if markers:
|
if markers:
|
||||||
self.lastPid = int( markers[ 0 ][ 1: ] )
|
self.lastPid = int( markers[ 0 ][ 1: ] )
|
||||||
@@ -725,6 +739,33 @@ class CPULimitedHost( Host ):
|
|||||||
mountCgroups()
|
mountCgroups()
|
||||||
cls.inited = True
|
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:
|
# Some important things to note:
|
||||||
#
|
#
|
||||||
@@ -754,27 +795,32 @@ class Switch( Node ):
|
|||||||
dpidLen = 16 # digits in dpid passed to switch
|
dpidLen = 16 # digits in dpid passed to switch
|
||||||
|
|
||||||
def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
|
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
|
opts: additional switch options
|
||||||
listenPort: port to listen on for dpctl connections"""
|
listenPort: port to listen on for dpctl connections"""
|
||||||
Node.__init__( self, name, **params )
|
Node.__init__( self, name, **params )
|
||||||
self.dpid = dpid if dpid else self.defaultDpid()
|
self.dpid = self.defaultDpid( dpid )
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.listenPort = listenPort
|
self.listenPort = listenPort
|
||||||
if not self.inNamespace:
|
if not self.inNamespace:
|
||||||
self.controlIntf = Intf( 'lo', self, port=0 )
|
self.controlIntf = Intf( 'lo', self, port=0 )
|
||||||
|
|
||||||
def defaultDpid( self ):
|
def defaultDpid( self, dpid=None ):
|
||||||
"Derive dpid from switch name, s1 -> 1"
|
"Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
|
||||||
try:
|
if dpid:
|
||||||
dpid = int( re.findall( r'\d+', self.name )[ 0 ] )
|
# Remove any colons and make sure it's a good hex number
|
||||||
dpid = hex( dpid )[ 2: ]
|
dpid = dpid.translate( None, ':' )
|
||||||
dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
|
assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
|
||||||
return dpid
|
else:
|
||||||
except IndexError:
|
# Use hex of the first number in the switch name
|
||||||
raise Exception( 'Unable to derive default datapath ID - '
|
nums = re.findall( r'\d+', self.name )
|
||||||
'please either specify a dpid or use a '
|
if nums:
|
||||||
'canonical switch name such as s23.' )
|
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 ):
|
def defaultIntf( self ):
|
||||||
"Return control interface"
|
"Return control interface"
|
||||||
@@ -819,6 +865,8 @@ class UserSwitch( Switch ):
|
|||||||
'(openflow.org)' )
|
'(openflow.org)' )
|
||||||
if self.listenPort:
|
if self.listenPort:
|
||||||
self.opts += ' --listen=ptcp:%i ' % self.listenPort
|
self.opts += ' --listen=ptcp:%i ' % self.listenPort
|
||||||
|
else:
|
||||||
|
self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
|
||||||
self.dpopts = dpopts
|
self.dpopts = dpopts
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -829,10 +877,13 @@ class UserSwitch( Switch ):
|
|||||||
|
|
||||||
def dpctl( self, *args ):
|
def dpctl( self, *args ):
|
||||||
"Run dpctl command"
|
"Run dpctl command"
|
||||||
|
listenAddr = None
|
||||||
if not self.listenPort:
|
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 ) +
|
return self.cmd( 'dpctl ' + ' '.join( args ) +
|
||||||
' tcp:127.0.0.1:%i' % self.listenPort )
|
' ' + listenAddr )
|
||||||
|
|
||||||
def connected( self ):
|
def connected( self ):
|
||||||
"Is the switch connected to a controller?"
|
"Is the switch connected to a controller?"
|
||||||
@@ -849,10 +900,13 @@ class UserSwitch( Switch ):
|
|||||||
minspeed = ifspeed * 0.001
|
minspeed = ifspeed * 0.001
|
||||||
|
|
||||||
res = intf.config( **intf.params )
|
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
|
# Re-add qdisc, root, and default classes user switch created, but
|
||||||
# with new parent, as setup by Mininet's TCIntf
|
# with new parent, as setup by Mininet's TCIntf
|
||||||
|
parent = res['parent']
|
||||||
intf.tc( "%s qdisc add dev %s " + parent +
|
intf.tc( "%s qdisc add dev %s " + parent +
|
||||||
" handle 1: htb default 0xfffe" )
|
" handle 1: htb default 0xfffe" )
|
||||||
intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
|
intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
|
||||||
@@ -947,14 +1001,17 @@ class OVSLegacyKernelSwitch( Switch ):
|
|||||||
class OVSSwitch( Switch ):
|
class OVSSwitch( Switch ):
|
||||||
"Open vSwitch switch. Depends on ovs-vsctl."
|
"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.
|
"""Init.
|
||||||
name: name for switch
|
name: name for switch
|
||||||
failMode: controller loss behavior (secure|open)
|
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 )
|
Switch.__init__( self, name, **params )
|
||||||
self.failMode = failMode
|
self.failMode = failMode
|
||||||
self.datapath = datapath
|
self.datapath = datapath
|
||||||
|
self.inband = inband
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup( cls ):
|
def setup( cls ):
|
||||||
@@ -975,6 +1032,20 @@ class OVSSwitch( Switch ):
|
|||||||
'You may wish to try '
|
'You may wish to try '
|
||||||
'"service openvswitch-switch start".\n' )
|
'"service openvswitch-switch start".\n' )
|
||||||
exit( 1 )
|
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 ):
|
def dpctl( self, *args ):
|
||||||
"Run ovs-ofctl command"
|
"Run ovs-ofctl command"
|
||||||
@@ -1025,30 +1096,51 @@ class OVSSwitch( Switch ):
|
|||||||
self.cmd( 'ifconfig lo up' )
|
self.cmd( 'ifconfig lo up' )
|
||||||
# Annoyingly, --if-exists option seems not to work
|
# Annoyingly, --if-exists option seems not to work
|
||||||
self.cmd( 'ovs-vsctl del-br', self )
|
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
|
int( self.dpid, 16 ) # DPID must be a hex string
|
||||||
self.cmd( 'ovs-vsctl -- set Bridge', self,
|
# Interfaces and controllers
|
||||||
'other_config:datapath-id=' + self.dpid )
|
intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
|
||||||
self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
|
'-- set Interface %s ' % intf +
|
||||||
for intf in self.intfList():
|
'ofport_request=%s ' % self.ports[ intf ]
|
||||||
if not intf.IP():
|
for intf in self.intfList() if not intf.IP() )
|
||||||
self.attach( intf )
|
clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
|
||||||
# Add controllers
|
for c in controllers )
|
||||||
clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
|
|
||||||
for c in controllers ] )
|
|
||||||
if self.listenPort:
|
if self.listenPort:
|
||||||
clist += ' ptcp:%s' % 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)
|
# Reconnect quickly to controllers (1s vs. 15s max_backoff)
|
||||||
for uuid in self.controllerUUIDs():
|
for uuid in self.controllerUUIDs():
|
||||||
if uuid.count( '-' ) != 4:
|
if uuid.count( '-' ) != 4:
|
||||||
# Doesn't look like a UUID
|
# Doesn't look like a UUID
|
||||||
continue
|
continue
|
||||||
uuid = uuid.strip()
|
uuid = uuid.strip()
|
||||||
self.cmd( 'ovs-vsctl set Controller', uuid,
|
cmd += '-- set Controller %smax_backoff=1000 ' % uuid
|
||||||
'max_backoff=1000' )
|
# Do it!!
|
||||||
|
self.cmd( cmd )
|
||||||
|
for intf in self.intfList():
|
||||||
|
self.TCReapply( intf )
|
||||||
|
|
||||||
|
|
||||||
def stop( self ):
|
def stop( self ):
|
||||||
"Terminate OVS switch."
|
"Terminate OVS switch."
|
||||||
@@ -1063,8 +1155,9 @@ OVSKernelSwitch = OVSSwitch
|
|||||||
class IVSSwitch(Switch):
|
class IVSSwitch(Switch):
|
||||||
"""IVS virtual switch"""
|
"""IVS virtual switch"""
|
||||||
|
|
||||||
def __init__( self, name, **kwargs ):
|
def __init__( self, name, verbose=True, **kwargs ):
|
||||||
Switch.__init__( self, name, **kwargs )
|
Switch.__init__( self, name, **kwargs )
|
||||||
|
self.verbose = verbose
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup( cls ):
|
def setup( cls ):
|
||||||
@@ -1079,12 +1172,19 @@ class IVSSwitch(Switch):
|
|||||||
'not be loaded. Try modprobe openvswitch.\n' )
|
'not be loaded. Try modprobe openvswitch.\n' )
|
||||||
exit( 1 )
|
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 ):
|
def start( self, controllers ):
|
||||||
"Start up a new IVS switch"
|
"Start up a new IVS switch"
|
||||||
args = ['ivs']
|
args = ['ivs']
|
||||||
args.extend( ['--name', self.name] )
|
args.extend( ['--name', self.name] )
|
||||||
args.extend( ['--dpid', self.dpid] )
|
args.extend( ['--dpid', self.dpid] )
|
||||||
args.extend( ['--verbose'] )
|
if self.verbose:
|
||||||
|
args.extend( ['--verbose'] )
|
||||||
for intf in self.intfs.values():
|
for intf in self.intfs.values():
|
||||||
if not intf.IP():
|
if not intf.IP():
|
||||||
args.extend( ['-i', intf.name] )
|
args.extend( ['-i', intf.name] )
|
||||||
@@ -1096,11 +1196,13 @@ class IVSSwitch(Switch):
|
|||||||
|
|
||||||
logfile = '/tmp/ivs.%s.log' % self.name
|
logfile = '/tmp/ivs.%s.log' % self.name
|
||||||
|
|
||||||
|
self.cmd( 'ifconfig lo up' )
|
||||||
self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
|
self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
|
||||||
|
|
||||||
def stop( self ):
|
def stop( self ):
|
||||||
"Terminate IVS switch."
|
"Terminate IVS switch."
|
||||||
self.cmd( 'kill %ivs' )
|
self.cmd( 'kill %ivs' )
|
||||||
|
self.cmd( 'wait' )
|
||||||
self.deleteIntfs()
|
self.deleteIntfs()
|
||||||
|
|
||||||
def attach( self, intf ):
|
def attach( self, intf ):
|
||||||
@@ -1125,12 +1227,13 @@ class Controller( Node ):
|
|||||||
|
|
||||||
def __init__( self, name, inNamespace=False, command='controller',
|
def __init__( self, name, inNamespace=False, command='controller',
|
||||||
cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
|
cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
|
||||||
port=6633, **params ):
|
port=6633, protocol='tcp', **params ):
|
||||||
self.command = command
|
self.command = command
|
||||||
self.cargs = cargs
|
self.cargs = cargs
|
||||||
self.cdir = cdir
|
self.cdir = cdir
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.protocol = protocol
|
||||||
Node.__init__( self, name, inNamespace=inNamespace,
|
Node.__init__( self, name, inNamespace=inNamespace,
|
||||||
ip=ip, **params )
|
ip=ip, **params )
|
||||||
self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
|
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" %
|
listening = self.cmd( "echo A | telnet -e A %s %d" %
|
||||||
( self.ip, self.port ) )
|
( self.ip, self.port ) )
|
||||||
if 'Connected' in listening:
|
if 'Connected' in listening:
|
||||||
servers = self.cmd( 'netstat -atp' ).split( '\n' )
|
servers = self.cmd( 'netstat -natp' ).split( '\n' )
|
||||||
pstr = ':%d ' % self.port
|
pstr = ':%d ' % self.port
|
||||||
clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
|
clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
|
||||||
raise Exception( "Please shut down the controller which is"
|
raise Exception( "Please shut down the controller which is"
|
||||||
@@ -1183,13 +1286,19 @@ class Controller( Node ):
|
|||||||
return '<%s %s: %s:%s pid=%s> ' % (
|
return '<%s %s: %s:%s pid=%s> ' % (
|
||||||
self.__class__.__name__, self.name,
|
self.__class__.__name__, self.name,
|
||||||
self.IP(), self.port, self.pid )
|
self.IP(), self.port, self.pid )
|
||||||
|
@classmethod
|
||||||
|
def isAvailable( self ):
|
||||||
|
return quietRun( 'which controller' )
|
||||||
|
|
||||||
class OVSController( Controller ):
|
class OVSController( Controller ):
|
||||||
"Open vSwitch controller"
|
"Open vSwitch controller"
|
||||||
def __init__( self, name, command='ovs-controller', **kwargs ):
|
def __init__( self, name, command='ovs-controller', **kwargs ):
|
||||||
|
if quietRun( 'which test-controller' ):
|
||||||
|
command = 'test-controller'
|
||||||
Controller.__init__( self, name, command=command, **kwargs )
|
Controller.__init__( self, name, command=command, **kwargs )
|
||||||
|
@classmethod
|
||||||
|
def isAvailable( self ):
|
||||||
|
return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
|
||||||
|
|
||||||
class NOX( Controller ):
|
class NOX( Controller ):
|
||||||
"Controller to run a NOX application."
|
"Controller to run a NOX application."
|
||||||
@@ -1245,6 +1354,12 @@ class RemoteController( Controller ):
|
|||||||
warn( "Unable to contact the remote controller"
|
warn( "Unable to contact the remote controller"
|
||||||
" at %s:%d\n" % ( self.ip, self.port ) )
|
" 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 ):
|
class NAT( Node ):
|
||||||
"""NAT: Provides connectivity to external network"""
|
"""NAT: Provides connectivity to external network"""
|
||||||
|
|
||||||
@@ -1311,3 +1426,4 @@ class NAT( Node ):
|
|||||||
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
||||||
|
|
||||||
super( NAT, self ).terminate()
|
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
|
"""Check that a given value is within a tolerance of expected
|
||||||
tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance.
|
tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance.
|
||||||
"""
|
"""
|
||||||
self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
|
self.assertGreaterEqual( float(measured),
|
||||||
self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
|
float(expected) * tolerance_frac )
|
||||||
|
self.assertLessEqual( float( measured ),
|
||||||
|
float(expected) + (1-tolerance_frac)
|
||||||
|
* float( expected ) )
|
||||||
|
|
||||||
def testCPULimits( self ):
|
def testCPULimits( self ):
|
||||||
"Verify topology creation with CPU limits set for both schedulers."
|
"Verify topology creation with CPU limits set for both schedulers."
|
||||||
@@ -68,19 +71,20 @@ class testOptionsTopoCommon( object ):
|
|||||||
mn.start()
|
mn.start()
|
||||||
results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
|
results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
|
||||||
mn.stop()
|
mn.stop()
|
||||||
for cpu in results:
|
for pct in results:
|
||||||
self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE )
|
#divide cpu by 100 to convert from percentage to fraction
|
||||||
|
self.assertWithinTolerance( pct/100, CPU_FRACTION, CPU_TOLERANCE )
|
||||||
|
|
||||||
def testLinkBandwidth( self ):
|
def testLinkBandwidth( self ):
|
||||||
"Verify that link bandwidths are accurate within a bound."
|
"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
|
BW_TOLERANCE = 0.8 # BW fraction below which test should fail
|
||||||
# Verify ability to create limited-link topo first;
|
# Verify ability to create limited-link topo first;
|
||||||
lopts = { 'bw': BW, 'use_htb': True }
|
lopts = { 'bw': BW, 'use_htb': True }
|
||||||
# Also verify correctness of limit limitng within a bound.
|
# Also verify correctness of limit limitng within a bound.
|
||||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
||||||
link=TCLink, switch=self.switchClass )
|
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:
|
for bw_str in bw_strs:
|
||||||
bw = float( bw_str.split(' ')[0] )
|
bw = float( bw_str.split(' ')[0] )
|
||||||
self.assertWithinTolerance( bw, BW, BW_TOLERANCE )
|
self.assertWithinTolerance( bw, BW, BW_TOLERANCE )
|
||||||
@@ -91,7 +95,7 @@ class testOptionsTopoCommon( object ):
|
|||||||
DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail
|
DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail
|
||||||
lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True }
|
lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True }
|
||||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
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 )
|
ping_delays = mn.run( mn.pingFull )
|
||||||
test_outputs = ping_delays[0]
|
test_outputs = ping_delays[0]
|
||||||
# Ignore unused variables below
|
# Ignore unused variables below
|
||||||
@@ -102,9 +106,10 @@ class testOptionsTopoCommon( object ):
|
|||||||
# pylint: enable-msg=W0612
|
# pylint: enable-msg=W0612
|
||||||
for rttval in [rttmin, rttavg, rttmax]:
|
for rttval in [rttmin, rttavg, rttmax]:
|
||||||
# Multiply delay by 4 to cover there & back on two links
|
# 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)
|
DELAY_TOLERANCE)
|
||||||
|
|
||||||
|
|
||||||
def testLinkLoss( self ):
|
def testLinkLoss( self ):
|
||||||
"Verify that we see packet drops with a high configured loss rate."
|
"Verify that we see packet drops with a high configured loss rate."
|
||||||
LOSS_PERCENT = 99
|
LOSS_PERCENT = 99
|
||||||
@@ -120,7 +125,7 @@ class testOptionsTopoCommon( object ):
|
|||||||
for _ in range(REPS):
|
for _ in range(REPS):
|
||||||
dropped_total += mn.ping(timeout='1')
|
dropped_total += mn.ping(timeout='1')
|
||||||
mn.stop()
|
mn.stop()
|
||||||
self.assertTrue(dropped_total > 0)
|
self.assertGreater( dropped_total, 0 )
|
||||||
|
|
||||||
def testMostOptions( self ):
|
def testMostOptions( self ):
|
||||||
"Verify topology creation with most link options and CPU limits."
|
"Verify topology creation with most link options and CPU limits."
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class testLinearCommon( object ):
|
|||||||
|
|
||||||
def testLinear5( self ):
|
def testLinear5( self ):
|
||||||
"Ping test on a 5-switch topology"
|
"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 )
|
dropped = mn.run( mn.ping )
|
||||||
self.assertEqual( dropped, 0 )
|
self.assertEqual( dropped, 0 )
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ TODO: missing xterm test
|
|||||||
import unittest
|
import unittest
|
||||||
import pexpect
|
import pexpect
|
||||||
import os
|
import os
|
||||||
from time import sleep
|
|
||||||
from mininet.util import quietRun
|
from mininet.util import quietRun
|
||||||
|
|
||||||
class testWalkthrough( unittest.TestCase ):
|
class testWalkthrough( unittest.TestCase ):
|
||||||
|
|||||||
+44
-57
@@ -48,18 +48,25 @@ class MultiGraph( object ):
|
|||||||
class Topo(object):
|
class Topo(object):
|
||||||
"Data center network representation for structured multi-trees."
|
"Data center network representation for structured multi-trees."
|
||||||
|
|
||||||
def __init__(self, hopts=None, sopts=None, lopts=None):
|
def __init__(self, *args, **params):
|
||||||
"""Topo object:
|
"""Topo object.
|
||||||
|
Optional named parameters:
|
||||||
hinfo: default host options
|
hinfo: default host options
|
||||||
sopts: default switch options
|
sopts: default switch options
|
||||||
lopts: default link options"""
|
lopts: default link options
|
||||||
|
calls build()"""
|
||||||
self.g = MultiGraph()
|
self.g = MultiGraph()
|
||||||
self.node_info = {}
|
self.node_info = {}
|
||||||
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
|
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
|
||||||
self.hopts = {} if hopts is None else hopts
|
self.hopts = params.pop( 'hopts', {} )
|
||||||
self.sopts = {} if sopts is None else sopts
|
self.sopts = params.pop( 'sopts', {} )
|
||||||
self.lopts = {} if lopts is None else lopts
|
self.lopts = params.pop( 'lopts', {} )
|
||||||
self.ports = {} # ports[src][dst] is port on src that connects to dst
|
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):
|
def addNode(self, name, **opts):
|
||||||
"""Add Node to graph.
|
"""Add Node to graph.
|
||||||
@@ -168,7 +175,7 @@ class Topo(object):
|
|||||||
'''
|
'''
|
||||||
if src in self.ports and dst in self.ports[src]:
|
if src in self.ports and dst in self.ports[src]:
|
||||||
assert dst in self.ports and src in self.ports[dst]
|
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 ):
|
def linkInfo( self, src, dst ):
|
||||||
"Return link metadata"
|
"Return link metadata"
|
||||||
@@ -194,76 +201,56 @@ class Topo(object):
|
|||||||
"Items sorted in natural (i.e. alphabetical) order"
|
"Items sorted in natural (i.e. alphabetical) order"
|
||||||
return sorted(items, key=natural)
|
return sorted(items, key=natural)
|
||||||
|
|
||||||
class SingleSwitchTopo(Topo):
|
|
||||||
'''Single switch connected to k hosts.'''
|
|
||||||
|
|
||||||
def __init__(self, k=2, **opts):
|
class SingleSwitchTopo( Topo ):
|
||||||
'''Init.
|
"Single switch connected to k hosts."
|
||||||
|
|
||||||
@param k number of hosts
|
|
||||||
@param enable_all enables all nodes and switches?
|
|
||||||
'''
|
|
||||||
super(SingleSwitchTopo, self).__init__(**opts)
|
|
||||||
|
|
||||||
|
def build( self, k=2, **opts ):
|
||||||
|
"k: number of hosts"
|
||||||
self.k = k
|
self.k = k
|
||||||
|
switch = self.addSwitch( 's1' )
|
||||||
switch = self.addSwitch('s1')
|
for h in irange( 1, k ):
|
||||||
for h in irange(1, k):
|
host = self.addHost( 'h%s' % h )
|
||||||
host = self.addHost('h%s' % h)
|
self.addLink( host, switch )
|
||||||
self.addLink(host, switch)
|
|
||||||
|
|
||||||
|
|
||||||
class SingleSwitchReversedTopo(Topo):
|
class SingleSwitchReversedTopo( Topo ):
|
||||||
'''Single switch connected to k hosts, with reversed ports.
|
"""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.
|
def build( self, k=2 ):
|
||||||
|
"k: number of hosts"
|
||||||
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)
|
|
||||||
self.k = k
|
self.k = k
|
||||||
switch = self.addSwitch('s1')
|
switch = self.addSwitch( 's1' )
|
||||||
for h in irange(1, k):
|
for h in irange( 1, k ):
|
||||||
host = self.addHost('h%s' % h)
|
host = self.addHost( 'h%s' % h )
|
||||||
self.addLink(host, switch,
|
self.addLink( host, switch,
|
||||||
port1=0, port2=(k - h + 1))
|
port1=0, port2=( k - h + 1 ) )
|
||||||
|
|
||||||
class LinearTopo(Topo):
|
class LinearTopo( Topo ):
|
||||||
"Linear topology of k switches, with n hosts per switch."
|
"Linear topology of k switches, with n hosts per switch."
|
||||||
|
|
||||||
def __init__(self, k=2, n=1, **opts):
|
def build( self, k=2, n=1, **opts):
|
||||||
"""Init.
|
"""k: number of switches
|
||||||
k: number of switches
|
n: number of hosts per switch"""
|
||||||
n: number of hosts per switch
|
|
||||||
hconf: host configuration options
|
|
||||||
lconf: link configuration options"""
|
|
||||||
|
|
||||||
super(LinearTopo, self).__init__(**opts)
|
|
||||||
|
|
||||||
self.k = k
|
self.k = k
|
||||||
self.n = n
|
self.n = n
|
||||||
|
|
||||||
if n == 1:
|
if n == 1:
|
||||||
genHostName = lambda i, j: 'h%s' % i
|
genHostName = lambda i, j: 'h%s' % i
|
||||||
else:
|
else:
|
||||||
genHostName = lambda i, j: 'h%ss%d' % (j, i)
|
genHostName = lambda i, j: 'h%ss%d' % ( j, i )
|
||||||
|
|
||||||
|
|
||||||
lastSwitch = None
|
lastSwitch = None
|
||||||
for i in irange(1, k):
|
for i in irange( 1, k ):
|
||||||
# Add switch
|
# Add switch
|
||||||
switch = self.addSwitch('s%s' % i)
|
switch = self.addSwitch( 's%s' % i )
|
||||||
# Add hosts to switch
|
# Add hosts to switch
|
||||||
for j in irange(1, n):
|
for j in irange( 1, n ):
|
||||||
host = self.addHost(genHostName(i, j))
|
host = self.addHost( genHostName( i, j ) )
|
||||||
self.addLink(host, switch)
|
self.addLink( host, switch )
|
||||||
# Connect switch to previous
|
# Connect switch to previous
|
||||||
if lastSwitch:
|
if lastSwitch:
|
||||||
self.addLink(switch, lastSwitch)
|
self.addLink( switch, lastSwitch )
|
||||||
lastSwitch = switch
|
lastSwitch = switch
|
||||||
|
|||||||
+35
-2
@@ -6,8 +6,7 @@ from mininet.net import Mininet
|
|||||||
class TreeTopo( Topo ):
|
class TreeTopo( Topo ):
|
||||||
"Topology for a tree network with a given depth and fanout."
|
"Topology for a tree network with a given depth and fanout."
|
||||||
|
|
||||||
def __init__( self, depth=1, fanout=2 ):
|
def build( self, depth=1, fanout=2 ):
|
||||||
super( TreeTopo, self ).__init__()
|
|
||||||
# Numbering: h1..N, s1..M
|
# Numbering: h1..N, s1..M
|
||||||
self.hostNum = 1
|
self.hostNum = 1
|
||||||
self.switchNum = 1
|
self.switchNum = 1
|
||||||
@@ -34,3 +33,37 @@ def TreeNet( depth=1, fanout=2, **kwargs ):
|
|||||||
"Convenience function for creating tree networks."
|
"Convenience function for creating tree networks."
|
||||||
topo = TreeTopo( depth, fanout )
|
topo = TreeTopo( depth, fanout )
|
||||||
return Mininet( topo, **kwargs )
|
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 )
|
quietRun( 'ip link del ' + intf2 )
|
||||||
# Create new pair
|
# Create new pair
|
||||||
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
|
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 ):
|
def retry( retries, delaySecs, fn, *args, **keywords ):
|
||||||
"""Try something several times before giving up.
|
"""Try something several times before giving up.
|
||||||
@@ -183,8 +188,7 @@ def moveIntfNoRetry( intf, dstNode, srcNode=None, printError=False ):
|
|||||||
srcNode.cmd( cmd )
|
srcNode.cmd( cmd )
|
||||||
else:
|
else:
|
||||||
quietRun( cmd )
|
quietRun( cmd )
|
||||||
links = dstNode.cmd( 'ip link show' )
|
if ( ' %s:' % intf ) not in dstNode.cmd( 'ip link show', intf ):
|
||||||
if not ( ' %s:' % intf ) in links:
|
|
||||||
if printError:
|
if printError:
|
||||||
error( '*** Error: moveIntf: ' + intf +
|
error( '*** Error: moveIntf: ' + intf +
|
||||||
' not successfully moved to ' + dstNode.name + '\n' )
|
' 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
|
ipBaseNum: option base IP address as int
|
||||||
returns IP address as string"""
|
returns IP address as string"""
|
||||||
imax = 0xffffffff >> prefixLen
|
imax = 0xffffffff >> prefixLen
|
||||||
assert i <= imax
|
assert i <= imax, 'Not enough IP addresses in the subnet'
|
||||||
mask = 0xffffffff ^ imax
|
mask = 0xffffffff ^ imax
|
||||||
ipnum = ( ipBaseNum & mask ) + i
|
ipnum = ( ipBaseNum & mask ) + i
|
||||||
return ipStr( ipnum )
|
return ipStr( ipnum )
|
||||||
@@ -277,6 +281,8 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
|
|||||||
def ipParse( ip ):
|
def ipParse( ip ):
|
||||||
"Parse an IP address and return an unsigned int."
|
"Parse an IP address and return an unsigned int."
|
||||||
args = [ int( arg ) for arg in ip.split( '.' ) ]
|
args = [ int( arg ) for arg in ip.split( '.' ) ]
|
||||||
|
while ( len(args) < 4 ):
|
||||||
|
args.append( 0 )
|
||||||
return ipNum( *args )
|
return ipNum( *args )
|
||||||
|
|
||||||
def netParse( ipstr ):
|
def netParse( ipstr ):
|
||||||
@@ -286,6 +292,10 @@ def netParse( ipstr ):
|
|||||||
if '/' in ipstr:
|
if '/' in ipstr:
|
||||||
ip, pf = ipstr.split( '/' )
|
ip, pf = ipstr.split( '/' )
|
||||||
prefixLen = int( pf )
|
prefixLen = int( pf )
|
||||||
|
#if no prefix is specified, set the prefix to 24
|
||||||
|
else:
|
||||||
|
ip = ipstr
|
||||||
|
prefixLen = 24
|
||||||
return ipParse( ip ), prefixLen
|
return ipParse( ip ), prefixLen
|
||||||
|
|
||||||
def checkInt( s ):
|
def checkInt( s ):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*
|
*
|
||||||
* - closing all file descriptors except stdin/out/error
|
* - closing all file descriptors except stdin/out/error
|
||||||
* - detaching from a controlling tty using setsid
|
* - detaching from a controlling tty using setsid
|
||||||
* - running in a network namespace
|
* - running in network and mount namespaces
|
||||||
* - printing out the pid of a process so we can identify it later
|
* - printing out the pid of a process so we can identify it later
|
||||||
* - attaching to a namespace and cgroup
|
* - attaching to a namespace and cgroup
|
||||||
* - setting RT scheduling
|
* - setting RT scheduling
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
* Partially based on public domain setsid(1)
|
* Partially based on public domain setsid(1)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -20,8 +21,9 @@
|
|||||||
#include <syscall.h>
|
#include <syscall.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <limits.h>
|
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
|
||||||
#if !defined(VERSION)
|
#if !defined(VERSION)
|
||||||
#define VERSION "(devel)"
|
#define VERSION "(devel)"
|
||||||
@@ -34,9 +36,9 @@ void usage(char *name)
|
|||||||
"Options:\n"
|
"Options:\n"
|
||||||
" -c: close all file descriptors except stdin/out/error\n"
|
" -c: close all file descriptors except stdin/out/error\n"
|
||||||
" -d: detach from tty by calling setsid()\n"
|
" -d: detach from tty by calling setsid()\n"
|
||||||
" -n: run in new network namespace\n"
|
" -n: run in new network and mount namespaces\n"
|
||||||
" -p: print ^A + pid\n"
|
" -p: print ^A + pid\n"
|
||||||
" -a pid: attach to pid's network namespace\n"
|
" -a pid: attach to pid's network and mount namespaces\n"
|
||||||
" -g group: add to cgroup\n"
|
" -g group: add to cgroup\n"
|
||||||
" -r rtprio: run with SCHED_RR (usually requires -g)\n"
|
" -r rtprio: run with SCHED_RR (usually requires -g)\n"
|
||||||
" -v: print version\n",
|
" -v: print version\n",
|
||||||
@@ -62,7 +64,7 @@ void validate(char *path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Add our pid to cgroup */
|
/* Add our pid to cgroup */
|
||||||
int cgroup(char *gname)
|
void cgroup(char *gname)
|
||||||
{
|
{
|
||||||
static char path[PATH_MAX];
|
static char path[PATH_MAX];
|
||||||
static char *groups[] = {
|
static char *groups[] = {
|
||||||
@@ -121,11 +123,16 @@ int main(int argc, char *argv[])
|
|||||||
setsid();
|
setsid();
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
/* run in network namespace */
|
/* run in network and mount namespaces */
|
||||||
if (unshare(CLONE_NEWNET) == -1) {
|
if (unshare(CLONE_NEWNET|CLONE_NEWNS) == -1) {
|
||||||
perror("unshare");
|
perror("unshare");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
/* mount sysfs to pick up the new network namespace */
|
||||||
|
if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) {
|
||||||
|
perror("mount");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
/* print pid */
|
/* print pid */
|
||||||
@@ -133,7 +140,7 @@ int main(int argc, char *argv[])
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
/* Attach to pid's network namespace */
|
/* Attach to pid's network namespace and mount namespace*/
|
||||||
pid = atoi(optarg);
|
pid = atoi(optarg);
|
||||||
sprintf(path, "/proc/%d/ns/net", pid );
|
sprintf(path, "/proc/%d/ns/net", pid );
|
||||||
nsid = open(path, O_RDONLY);
|
nsid = open(path, O_RDONLY);
|
||||||
@@ -145,6 +152,16 @@ int main(int argc, char *argv[])
|
|||||||
perror("setns");
|
perror("setns");
|
||||||
return 1;
|
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;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
/* Attach to cgroup */
|
/* Attach to cgroup */
|
||||||
@@ -176,4 +193,6 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-137
@@ -63,22 +63,11 @@ echo "Detected Linux distribution: $DIST $RELEASE $CODENAME $ARCH"
|
|||||||
|
|
||||||
# Kernel params
|
# Kernel params
|
||||||
|
|
||||||
if [ "$DIST" = "Ubuntu" ]; then
|
KERNEL_NAME=`uname -r`
|
||||||
if [ "$RELEASE" = "10.04" ]; then
|
KERNEL_HEADERS=kernel-headers-${KERNEL_NAME}
|
||||||
KERNEL_NAME='3.0.0-15-generic'
|
|
||||||
else
|
if ! echo $DIST | egrep 'Ubuntu|Debian|Fedora'; then
|
||||||
KERNEL_NAME=`uname -r`
|
echo "Install.sh currently only supports Ubuntu, Debian and Fedora."
|
||||||
fi
|
|
||||||
KERNEL_HEADERS=linux-headers-${KERNEL_NAME}
|
|
||||||
elif [ "$DIST" = "Debian" ] && [ "$ARCH" = "i386" ] && [ "$CODENAME" = "lenny" ]; then
|
|
||||||
KERNEL_NAME=2.6.33.1-mininet
|
|
||||||
KERNEL_HEADERS=linux-headers-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
|
|
||||||
KERNEL_IMAGE=linux-image-${KERNEL_NAME}_${KERNEL_NAME}-10.00.Custom_i386.deb
|
|
||||||
elif [ "$DIST" = "Fedora" ]; then
|
|
||||||
KERNEL_NAME=`uname -r`
|
|
||||||
KERNEL_HEADERS=kernel-headers-${KERNEL_NAME}
|
|
||||||
else
|
|
||||||
echo "Install.sh currently only supports Ubuntu, Debian Lenny i386 and Fedora."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -96,8 +85,6 @@ OVS_BUILDSUFFIX=-ignore # was -2
|
|||||||
OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar
|
OVS_PACKAGE_NAME=ovs-$OVS_RELEASE-core-$DIST_LC-$RELEASE-$ARCH$OVS_BUILDSUFFIX.tar
|
||||||
OVS_TAG=v$OVS_RELEASE
|
OVS_TAG=v$OVS_RELEASE
|
||||||
|
|
||||||
IVS_TAG=v0.3
|
|
||||||
|
|
||||||
# Command-line versions overrides that simplify custom VM creation
|
# Command-line versions overrides that simplify custom VM creation
|
||||||
# To use, pass in the vars on the cmd line before install.sh, e.g.
|
# To use, pass in the vars on the cmd line before install.sh, e.g.
|
||||||
# WS_DISSECTOR_REV=pre-ws-1.10.0 install.sh -w
|
# WS_DISSECTOR_REV=pre-ws-1.10.0 install.sh -w
|
||||||
@@ -108,31 +95,7 @@ OF13_SWITCH_REV=${OF13_SWITCH_REV:-""}
|
|||||||
function kernel {
|
function kernel {
|
||||||
echo "Install Mininet-compatible kernel if necessary"
|
echo "Install Mininet-compatible kernel if necessary"
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
if [ "$DIST" = "Ubuntu" ] && [ "$RELEASE" = "10.04" ]; then
|
$install linux-image-$KERNEL_NAME
|
||||||
$install linux-image-$KERNEL_NAME
|
|
||||||
elif [ "$DIST" = "Debian" ]; then
|
|
||||||
# The easy approach: download pre-built linux-image and linux-headers packages:
|
|
||||||
wget -c $KERNEL_LOC/$KERNEL_HEADERS
|
|
||||||
wget -c $KERNEL_LOC/$KERNEL_IMAGE
|
|
||||||
|
|
||||||
# Install custom linux headers and image:
|
|
||||||
$pkginst $KERNEL_IMAGE $KERNEL_HEADERS
|
|
||||||
|
|
||||||
# The next two steps are to work around a bug in newer versions of
|
|
||||||
# kernel-package, which fails to add initrd images with the latest kernels.
|
|
||||||
# See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=525032
|
|
||||||
# Generate initrd image if the .deb didn't install it:
|
|
||||||
if ! test -e /boot/initrd.img-${KERNEL_NAME}; then
|
|
||||||
sudo update-initramfs -c -k ${KERNEL_NAME}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure /boot/grub/menu.lst boots with initrd image:
|
|
||||||
sudo update-grub
|
|
||||||
|
|
||||||
# The default should be the new kernel. Otherwise, you may need to modify
|
|
||||||
# /boot/grub/menu.lst to set the default to the entry corresponding to the
|
|
||||||
# kernel you just installed.
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function kernel_clean {
|
function kernel_clean {
|
||||||
@@ -176,10 +139,9 @@ function mn_dev {
|
|||||||
# -user switch
|
# -user switch
|
||||||
# The instructions below are an abbreviated version from
|
# The instructions below are an abbreviated version from
|
||||||
# http://www.openflowswitch.org/wk/index.php/Debian_Install
|
# http://www.openflowswitch.org/wk/index.php/Debian_Install
|
||||||
# ... modified to use Debian Lenny rather than unstable.
|
|
||||||
function of {
|
function of {
|
||||||
echo "Installing OpenFlow reference implementation..."
|
echo "Installing OpenFlow reference implementation..."
|
||||||
cd $BUILD_DIR/
|
cd $BUILD_DIR
|
||||||
$install autoconf automake libtool make gcc
|
$install autoconf automake libtool make gcc
|
||||||
if [ "$DIST" = "Fedora" ]; then
|
if [ "$DIST" = "Fedora" ]; then
|
||||||
$install git pkgconfig glibc-devel
|
$install git pkgconfig glibc-devel
|
||||||
@@ -300,111 +262,95 @@ function wireshark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Install Open vSwitch specific version Ubuntu package
|
||||||
|
function ubuntuOvs {
|
||||||
|
echo "Creating and Installing Open vSwitch packages..."
|
||||||
|
|
||||||
|
OVS_SRC=$BUILD_DIR/openvswitch
|
||||||
|
OVS_TARBALL_LOC=http://openvswitch.org/releases
|
||||||
|
|
||||||
|
if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 12.04` = 1 ]; then
|
||||||
|
rm -rf $OVS_SRC
|
||||||
|
mkdir -p $OVS_SRC
|
||||||
|
cd $OVS_SRC
|
||||||
|
|
||||||
|
if wget $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz 2> /dev/null; then
|
||||||
|
tar xzf openvswitch-$OVS_RELEASE.tar.gz
|
||||||
|
else
|
||||||
|
echo "Failed to find OVS at $OVS_TARBALL_LOC/openvswitch-$OVS_RELEASE.tar.gz"
|
||||||
|
cd $BUILD_DIR
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove any old packages
|
||||||
|
$remove openvswitch-common openvswitch-datapath-dkms openvswitch-controller \
|
||||||
|
openvswitch-pki openvswitch-switch
|
||||||
|
|
||||||
|
# Get build deps
|
||||||
|
$install build-essential fakeroot debhelper autoconf automake libssl-dev \
|
||||||
|
pkg-config bzip2 openssl python-all procps python-qt4 \
|
||||||
|
python-zopeinterface python-twisted-conch dkms
|
||||||
|
|
||||||
|
# Build OVS
|
||||||
|
cd $BUILD_DIR/openvswitch/openvswitch-$OVS_RELEASE
|
||||||
|
DEB_BUILD_OPTIONS='parallel=2 nocheck' fakeroot debian/rules binary
|
||||||
|
cd ..
|
||||||
|
$pkginst openvswitch-common_$OVS_RELEASE*.deb openvswitch-datapath-dkms_$OVS_RELEASE*.deb \
|
||||||
|
openvswitch-pki_$OVS_RELEASE*.deb openvswitch-switch_$OVS_RELEASE*.deb
|
||||||
|
if $pkginst openvswitch-controller_$OVS_RELEASE*.deb; then
|
||||||
|
echo "Ignoring error installing openvswitch-controller"
|
||||||
|
fi
|
||||||
|
|
||||||
|
modinfo openvswitch
|
||||||
|
sudo ovs-vsctl show
|
||||||
|
# Switch can run on its own, but
|
||||||
|
# Mininet should control the controller
|
||||||
|
# This appears to only be an issue on Ubuntu/Debian
|
||||||
|
if sudo service openvswitch-controller stop; then
|
||||||
|
echo "Stopped running controller"
|
||||||
|
fi
|
||||||
|
if [ -e /etc/init.d/openvswitch-controller ]; then
|
||||||
|
sudo update-rc.d openvswitch-controller disable
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Failed to install Open vSwitch. OS must be Ubuntu >= 12.04"
|
||||||
|
cd $BUILD_DIR
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Install Open vSwitch
|
# Install Open vSwitch
|
||||||
# Instructions derived from OVS INSTALL, INSTALL.OpenFlow and README files.
|
|
||||||
|
|
||||||
function ovs {
|
function ovs {
|
||||||
echo "Installing Open vSwitch..."
|
echo "Installing Open vSwitch..."
|
||||||
|
|
||||||
if [ "$DIST" = "Fedora" ]; then
|
if [ "$DIST" == "Fedora" ]; then
|
||||||
# Just install Fedora's openvswitch RPMS
|
|
||||||
$install openvswitch openvswitch-controller
|
$install openvswitch openvswitch-controller
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OVS_SRC=$BUILD_DIR/openvswitch
|
# Manually installing openvswitch-datapath may be necessary
|
||||||
OVS_BUILD=$OVS_SRC/build-$KERNEL_NAME
|
# for manually built kernel .debs using Debian's defective kernel
|
||||||
OVS_KMODS=($OVS_BUILD/datapath/linux/{openvswitch_mod.ko,brcompat_mod.ko})
|
# packaging, which doesn't yield usable headers.
|
||||||
|
if ! dpkg --get-selections | grep openvswitch-datapath; then
|
||||||
# Required for module build/dkms install
|
# If you've already installed a datapath, assume you
|
||||||
$install $KERNEL_HEADERS
|
# know what you're doing and don't need dkms datapath.
|
||||||
|
# Otherwise, install it.
|
||||||
ovspresent=0
|
$install openvswitch-datapath-dkms
|
||||||
|
|
||||||
# First see if we have packages
|
|
||||||
# XXX wget -c seems to fail from github/amazon s3
|
|
||||||
cd /tmp
|
|
||||||
if wget $OVS_PACKAGE_LOC/$OVS_PACKAGE_NAME 2> /dev/null; then
|
|
||||||
$install patch dkms fakeroot python-argparse
|
|
||||||
tar xf $OVS_PACKAGE_NAME
|
|
||||||
orig=`tar tf $OVS_PACKAGE_NAME`
|
|
||||||
# Now install packages in reasonable dependency order
|
|
||||||
order='dkms common pki openvswitch-switch brcompat controller'
|
|
||||||
pkgs=""
|
|
||||||
for p in $order; do
|
|
||||||
pkg=`echo "$orig" | grep $p`
|
|
||||||
# Annoyingly, things seem to be missing without this flag
|
|
||||||
$pkginst --force-confmiss $pkg
|
|
||||||
done
|
|
||||||
ovspresent=1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Otherwise try distribution's OVS packages
|
$install openvswitch-switch openvswitch-controller
|
||||||
if [ "$DIST" = "Ubuntu" ] && [ `expr $RELEASE '>=' 11.10` = 1 ]; then
|
|
||||||
if ! dpkg --get-selections | grep openvswitch-datapath; then
|
|
||||||
# If you've already installed a datapath, assume you
|
|
||||||
# know what you're doing and don't need dkms datapath.
|
|
||||||
# Otherwise, install it.
|
|
||||||
$install openvswitch-datapath-dkms
|
|
||||||
fi
|
|
||||||
if $install openvswitch-switch openvswitch-controller; then
|
|
||||||
echo "Ignoring error installing openvswitch-controller"
|
|
||||||
fi
|
|
||||||
ovspresent=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Switch can run on its own, but
|
# Switch can run on its own, but
|
||||||
# Mininet should control the controller
|
# Mininet should control the controller
|
||||||
|
# This appears to only be an issue on Ubuntu/Debian
|
||||||
|
if sudo service openvswitch-controller stop; then
|
||||||
|
echo "Stopped running controller"
|
||||||
|
fi
|
||||||
if [ -e /etc/init.d/openvswitch-controller ]; then
|
if [ -e /etc/init.d/openvswitch-controller ]; then
|
||||||
if sudo service openvswitch-controller stop; then
|
|
||||||
echo "Stopped running controller"
|
|
||||||
fi
|
|
||||||
sudo update-rc.d openvswitch-controller disable
|
sudo update-rc.d openvswitch-controller disable
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $ovspresent = 1 ]; then
|
|
||||||
echo "Done (hopefully) installing packages"
|
|
||||||
cd $BUILD_DIR
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Otherwise attempt to install from source
|
|
||||||
|
|
||||||
$install pkg-config gcc make python-dev libssl-dev libtool
|
|
||||||
|
|
||||||
if [ "$DIST" = "Debian" ]; then
|
|
||||||
if [ "$CODENAME" = "lenny" ]; then
|
|
||||||
$install git-core
|
|
||||||
# Install Autoconf 2.63+ backport from Debian Backports repo:
|
|
||||||
# Instructions from http://backports.org/dokuwiki/doku.php?id=instructions
|
|
||||||
sudo su -c "echo 'deb http://www.backports.org/debian lenny-backports main contrib non-free' >> /etc/apt/sources.list"
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y --force-yes install debian-backports-keyring
|
|
||||||
sudo apt-get -y --force-yes -t lenny-backports install autoconf
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
$install git
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install OVS from release
|
|
||||||
cd $BUILD_DIR/
|
|
||||||
git clone git://openvswitch.org/openvswitch $OVS_SRC
|
|
||||||
cd $OVS_SRC
|
|
||||||
git checkout $OVS_TAG
|
|
||||||
./boot.sh
|
|
||||||
BUILDDIR=/lib/modules/${KERNEL_NAME}/build
|
|
||||||
if [ ! -e $BUILDDIR ]; then
|
|
||||||
echo "Creating build sdirectory $BUILDDIR"
|
|
||||||
sudo mkdir -p $BUILDDIR
|
|
||||||
fi
|
|
||||||
opts="--with-linux=$BUILDDIR"
|
|
||||||
mkdir -p $OVS_BUILD
|
|
||||||
cd $OVS_BUILD
|
|
||||||
../configure $opts
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
|
|
||||||
modprobe
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove_ovs {
|
function remove_ovs {
|
||||||
@@ -438,7 +384,7 @@ function ivs {
|
|||||||
|
|
||||||
# Install IVS from source
|
# Install IVS from source
|
||||||
cd $BUILD_DIR
|
cd $BUILD_DIR
|
||||||
git clone git://github.com/floodlight/ivs $IVS_SRC -b $IVS_TAG --recursive
|
git clone git://github.com/floodlight/ivs $IVS_SRC --recursive
|
||||||
cd $IVS_SRC
|
cd $IVS_SRC
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
@@ -555,7 +501,7 @@ function cbench {
|
|||||||
$install libsnmp-dev libpcap-dev libconfig-dev
|
$install libsnmp-dev libpcap-dev libconfig-dev
|
||||||
fi
|
fi
|
||||||
cd $BUILD_DIR/
|
cd $BUILD_DIR/
|
||||||
git clone git://openflow.org/oflops.git
|
git clone git://gitosis.stanford.edu/oflops.git
|
||||||
cd oflops
|
cd oflops
|
||||||
sh boot.sh || true # possible error in autoreconf, so run twice
|
sh boot.sh || true # possible error in autoreconf, so run twice
|
||||||
sh boot.sh
|
sh boot.sh
|
||||||
@@ -706,7 +652,7 @@ function vm_clean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function usage {
|
function usage {
|
||||||
printf '\nUsage: %s [-abcdfhikmnprtvwx03]\n\n' $(basename $0) >&2
|
printf '\nUsage: %s [-abcdfhikmnprtvVwx03]\n\n' $(basename $0) >&2
|
||||||
|
|
||||||
printf 'This install script attempts to install useful packages\n' >&2
|
printf 'This install script attempts to install useful packages\n' >&2
|
||||||
printf 'for Mininet. It should (hopefully) work on Ubuntu 11.10+\n' >&2
|
printf 'for Mininet. It should (hopefully) work on Ubuntu 11.10+\n' >&2
|
||||||
@@ -731,6 +677,7 @@ function usage {
|
|||||||
printf -- ' -s <dir>: place dependency (S)ource/build trees in <dir>\n' >&2
|
printf -- ' -s <dir>: place dependency (S)ource/build trees in <dir>\n' >&2
|
||||||
printf -- ' -t: complete o(T)her Mininet VM setup tasks\n' >&2
|
printf -- ' -t: complete o(T)her Mininet VM setup tasks\n' >&2
|
||||||
printf -- ' -v: install Open (V)switch\n' >&2
|
printf -- ' -v: install Open (V)switch\n' >&2
|
||||||
|
printf -- ' -V <version>: install a particular version of Open (V)switch on Ubuntu\n' >&2
|
||||||
printf -- ' -w: install OpenFlow (W)ireshark dissector\n' >&2
|
printf -- ' -w: install OpenFlow (W)ireshark dissector\n' >&2
|
||||||
printf -- ' -x: install NO(X) Classic OpenFlow controller\n' >&2
|
printf -- ' -x: install NO(X) Classic OpenFlow controller\n' >&2
|
||||||
printf -- ' -0: (default) -0[fx] installs OpenFlow 1.0 versions\n' >&2
|
printf -- ' -0: (default) -0[fx] installs OpenFlow 1.0 versions\n' >&2
|
||||||
@@ -744,7 +691,7 @@ if [ $# -eq 0 ]
|
|||||||
then
|
then
|
||||||
all
|
all
|
||||||
else
|
else
|
||||||
while getopts 'abcdefhikmnprs:tvwx03' OPTION
|
while getopts 'abcdefhikmnprs:tvV:wx03' OPTION
|
||||||
do
|
do
|
||||||
case $OPTION in
|
case $OPTION in
|
||||||
a) all;;
|
a) all;;
|
||||||
@@ -769,6 +716,8 @@ else
|
|||||||
echo "Dependency installation directory: $BUILD_DIR";;
|
echo "Dependency installation directory: $BUILD_DIR";;
|
||||||
t) vm_other;;
|
t) vm_other;;
|
||||||
v) ovs;;
|
v) ovs;;
|
||||||
|
V) OVS_RELEASE=$OPTARG;
|
||||||
|
ubuntuOvs;;
|
||||||
w) wireshark;;
|
w) wireshark;;
|
||||||
x) case $OF_VERSION in
|
x) case $OF_VERSION in
|
||||||
1.0) nox;;
|
1.0) nox;;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ version = 'Mininet ' + co( 'PYTHONPATH=. bin/mn --version', shell=True )
|
|||||||
version = version.strip()
|
version = version.strip()
|
||||||
|
|
||||||
# Find all Mininet path references
|
# Find all Mininet path references
|
||||||
lines = co( "grep -or 'Mininet \w\.\w\.\w\w*' *", shell=True )
|
lines = co( "grep -or 'Mininet \w\+\.\w\+\.\w\+[+]*' *", shell=True )
|
||||||
|
|
||||||
error = False
|
error = False
|
||||||
|
|
||||||
|
|||||||
+103
-38
@@ -83,6 +83,18 @@ isoURLs = {
|
|||||||
'saucy64server':
|
'saucy64server':
|
||||||
'http://mirrors.kernel.org/ubuntu-releases/13.10/'
|
'http://mirrors.kernel.org/ubuntu-releases/13.10/'
|
||||||
'ubuntu-13.10-server-amd64.iso',
|
'ubuntu-13.10-server-amd64.iso',
|
||||||
|
'trusty32server':
|
||||||
|
'http://mirrors.kernel.org/ubuntu-releases/14.04/'
|
||||||
|
'ubuntu-14.04-server-i386.iso',
|
||||||
|
'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',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -91,6 +103,18 @@ def OSVersion( flavor ):
|
|||||||
urlbase = path.basename( isoURLs.get( flavor, 'unknown' ) )
|
urlbase = path.basename( isoURLs.get( flavor, 'unknown' ) )
|
||||||
return path.splitext( urlbase )[ 0 ]
|
return path.splitext( urlbase )[ 0 ]
|
||||||
|
|
||||||
|
def OVFOSNameID( flavor ):
|
||||||
|
"Return OVF-specified ( OS Name, ID ) for flavor"
|
||||||
|
version = OSVersion( flavor )
|
||||||
|
arch = archFor( flavor )
|
||||||
|
if 'ubuntu' in version:
|
||||||
|
map = { 'i386': ( 'Ubuntu', 93 ),
|
||||||
|
'x86_64': ( 'Ubuntu 64-bit', 94 ) }
|
||||||
|
else:
|
||||||
|
map = { 'i386': ( 'Linux', 36 ),
|
||||||
|
'x86_64': ( 'Linux 64-bit', 101 ) }
|
||||||
|
osname, osid = map[ arch ]
|
||||||
|
return osname, osid
|
||||||
|
|
||||||
LogStartTime = time()
|
LogStartTime = time()
|
||||||
LogFile = None
|
LogFile = None
|
||||||
@@ -140,8 +164,7 @@ def depend():
|
|||||||
run( 'sudo apt-get -y update' )
|
run( 'sudo apt-get -y update' )
|
||||||
run( 'sudo apt-get install -y'
|
run( 'sudo apt-get install -y'
|
||||||
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
||||||
' e2fsprogs '
|
' e2fsprogs dnsmasq curl'
|
||||||
' landscape-client'
|
|
||||||
' python-setuptools mtools zip' )
|
' python-setuptools mtools zip' )
|
||||||
run( 'sudo easy_install pexpect' )
|
run( 'sudo easy_install pexpect' )
|
||||||
|
|
||||||
@@ -440,16 +463,16 @@ def boot( cow, kernel, initrd, logfile, memory=1024 ):
|
|||||||
return vm
|
return vm
|
||||||
|
|
||||||
|
|
||||||
def login( vm ):
|
def login( vm, user='mininet', password='mininet' ):
|
||||||
"Log in to vm (pexpect object)"
|
"Log in to vm (pexpect object)"
|
||||||
log( '* Waiting for login prompt' )
|
log( '* Waiting for login prompt' )
|
||||||
vm.expect( 'login: ' )
|
vm.expect( 'login: ' )
|
||||||
log( '* Logging in' )
|
log( '* Logging in' )
|
||||||
vm.sendline( 'mininet' )
|
vm.sendline( user )
|
||||||
log( '* Waiting for password prompt' )
|
log( '* Waiting for password prompt' )
|
||||||
vm.expect( 'Password: ' )
|
vm.expect( 'Password: ' )
|
||||||
log( '* Sending password' )
|
log( '* Sending password' )
|
||||||
vm.sendline( 'mininet' )
|
vm.sendline( password )
|
||||||
log( '* Waiting for login...' )
|
log( '* Waiting for login...' )
|
||||||
|
|
||||||
|
|
||||||
@@ -485,6 +508,10 @@ def coreTest( vm, prompt=Prompt ):
|
|||||||
log( '* Test', test, 'output:' )
|
log( '* Test', test, 'output:' )
|
||||||
log( vm.before )
|
log( vm.before )
|
||||||
|
|
||||||
|
def noneTest( vm ):
|
||||||
|
"This test does nothing"
|
||||||
|
vm.sendline( 'echo' )
|
||||||
|
|
||||||
def examplesquickTest( vm, prompt=Prompt ):
|
def examplesquickTest( vm, prompt=Prompt ):
|
||||||
"Quick test of mininet examples"
|
"Quick test of mininet examples"
|
||||||
vm.sendline( 'sudo apt-get install python-pexpect' )
|
vm.sendline( 'sudo apt-get install python-pexpect' )
|
||||||
@@ -507,8 +534,13 @@ def walkthroughTest( vm, prompt=Prompt ):
|
|||||||
|
|
||||||
|
|
||||||
def checkOutBranch( vm, branch, prompt=Prompt ):
|
def checkOutBranch( vm, branch, prompt=Prompt ):
|
||||||
vm.sendline( 'cd ~/mininet; git fetch; git pull --rebase; git checkout '
|
# This is a bit subtle; it will check out an existing branch (e.g. master)
|
||||||
+ branch )
|
# if it exists; otherwise it will create a detached branch.
|
||||||
|
# The branch will be rebased to its parent on origin.
|
||||||
|
# This probably doesn't matter since we're running on a COW disk
|
||||||
|
# anyway.
|
||||||
|
vm.sendline( 'cd ~/mininet; git fetch --all; git checkout '
|
||||||
|
+ branch + '; git pull --rebase origin ' + branch )
|
||||||
vm.expect( prompt )
|
vm.expect( prompt )
|
||||||
vm.sendline( 'sudo make install' )
|
vm.sendline( 'sudo make install' )
|
||||||
|
|
||||||
@@ -523,12 +555,16 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ):
|
|||||||
log( '* Waiting for output' )
|
log( '* Waiting for output' )
|
||||||
vm.expect( prompt )
|
vm.expect( prompt )
|
||||||
log( '* Fetching Mininet VM install script' )
|
log( '* Fetching Mininet VM install script' )
|
||||||
|
branch = Branch if Branch else 'master'
|
||||||
vm.sendline( 'wget '
|
vm.sendline( 'wget '
|
||||||
'https://raw.github.com/mininet/mininet/master/util/vm/'
|
'https://raw.github.com/mininet/mininet/%s/util/vm/'
|
||||||
'install-mininet-vm.sh' )
|
'install-mininet-vm.sh' % branch )
|
||||||
vm.expect( prompt )
|
vm.expect( prompt )
|
||||||
log( '* Running VM install script' )
|
log( '* Running VM install script' )
|
||||||
vm.sendline( 'bash install-mininet-vm.sh' )
|
installcmd = 'bash install-mininet-vm.sh'
|
||||||
|
if Branch:
|
||||||
|
installcmd += ' ' + Branch
|
||||||
|
vm.sendline( installcmd )
|
||||||
vm.expect ( 'password for mininet: ' )
|
vm.expect ( 'password for mininet: ' )
|
||||||
vm.sendline( 'mininet' )
|
vm.sendline( 'mininet' )
|
||||||
log( '* Waiting for script to complete... ' )
|
log( '* Waiting for script to complete... ' )
|
||||||
@@ -541,6 +577,13 @@ def interact( vm, tests, pre='', post='', prompt=Prompt ):
|
|||||||
log( '* Mininet version: ', version )
|
log( '* Mininet version: ', version )
|
||||||
log( '* Testing Mininet' )
|
log( '* Testing Mininet' )
|
||||||
runTests( vm, tests=tests, pre=pre, post=post )
|
runTests( vm, tests=tests, pre=pre, post=post )
|
||||||
|
# Ubuntu adds this because we install via a serial console,
|
||||||
|
# but we want the VM to boot via the VM console. Otherwise
|
||||||
|
# we get the message 'error: terminal "serial" not found'
|
||||||
|
log( '* Disabling serial console' )
|
||||||
|
vm.sendline( "sudo sed -i -e 's/^GRUB_TERMINAL=serial/#GRUB_TERMINAL=serial/' "
|
||||||
|
"/etc/default/grub; sudo update-grub" )
|
||||||
|
vm.expect( prompt )
|
||||||
log( '* Shutting down' )
|
log( '* Shutting down' )
|
||||||
vm.sendline( 'sync; sudo shutdown -h now' )
|
vm.sendline( 'sync; sudo shutdown -h now' )
|
||||||
log( '* Waiting for EOF/shutdown' )
|
log( '* Waiting for EOF/shutdown' )
|
||||||
@@ -577,13 +620,13 @@ OVFTemplate = """<?xml version="1.0"?>
|
|||||||
xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
|
xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<References>
|
<References>
|
||||||
<File ovf:href="%s" ovf:id="file1" ovf:size="%d"/>
|
<File ovf:href="%(diskname)s" ovf:id="file1" ovf:size="%(filesize)d"/>
|
||||||
</References>
|
</References>
|
||||||
<DiskSection>
|
<DiskSection>
|
||||||
<Info>Virtual disk information</Info>
|
<Info>Virtual disk information</Info>
|
||||||
<Disk ovf:capacity="%d" ovf:capacityAllocationUnits="byte"
|
<Disk ovf:capacity="%(disksize)d" ovf:capacityAllocationUnits="byte"
|
||||||
ovf:diskId="vmdisk1" ovf:fileRef="file1"
|
ovf:diskId="vmdisk1" ovf:fileRef="file1"
|
||||||
ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html"/>
|
ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"/>
|
||||||
</DiskSection>
|
</DiskSection>
|
||||||
<NetworkSection>
|
<NetworkSection>
|
||||||
<Info>The list of logical networks</Info>
|
<Info>The list of logical networks</Info>
|
||||||
@@ -591,26 +634,30 @@ OVFTemplate = """<?xml version="1.0"?>
|
|||||||
<Description>The nat network</Description>
|
<Description>The nat network</Description>
|
||||||
</Network>
|
</Network>
|
||||||
</NetworkSection>
|
</NetworkSection>
|
||||||
<VirtualSystem ovf:id="Mininet-VM">
|
<VirtualSystem ovf:id="%(vmname)s">
|
||||||
<Info>A Mininet Virtual Machine (%s)</Info>
|
<Info>%(vminfo)s (%(name)s)</Info>
|
||||||
<Name>mininet-vm</Name>
|
<Name>%(vmname)s</Name>
|
||||||
|
<OperatingSystemSection ovf:id="%(osid)d">
|
||||||
|
<Info>The kind of installed guest operating system</Info>
|
||||||
|
<Description>%(osname)s</Description>
|
||||||
|
</OperatingSystemSection>
|
||||||
<VirtualHardwareSection>
|
<VirtualHardwareSection>
|
||||||
<Info>Virtual hardware requirements</Info>
|
<Info>Virtual hardware requirements</Info>
|
||||||
<Item>
|
<Item>
|
||||||
<rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
|
<rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
|
||||||
<rasd:Description>Number of Virtual CPUs</rasd:Description>
|
<rasd:Description>Number of Virtual CPUs</rasd:Description>
|
||||||
<rasd:ElementName>1 virtual CPU(s)</rasd:ElementName>
|
<rasd:ElementName>%(cpus)s virtual CPU(s)</rasd:ElementName>
|
||||||
<rasd:InstanceID>1</rasd:InstanceID>
|
<rasd:InstanceID>1</rasd:InstanceID>
|
||||||
<rasd:ResourceType>3</rasd:ResourceType>
|
<rasd:ResourceType>3</rasd:ResourceType>
|
||||||
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
|
<rasd:VirtualQuantity>%(cpus)s</rasd:VirtualQuantity>
|
||||||
</Item>
|
</Item>
|
||||||
<Item>
|
<Item>
|
||||||
<rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
|
<rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
|
||||||
<rasd:Description>Memory Size</rasd:Description>
|
<rasd:Description>Memory Size</rasd:Description>
|
||||||
<rasd:ElementName>%dMB of memory</rasd:ElementName>
|
<rasd:ElementName>%(mem)dMB of memory</rasd:ElementName>
|
||||||
<rasd:InstanceID>2</rasd:InstanceID>
|
<rasd:InstanceID>2</rasd:InstanceID>
|
||||||
<rasd:ResourceType>4</rasd:ResourceType>
|
<rasd:ResourceType>4</rasd:ResourceType>
|
||||||
<rasd:VirtualQuantity>%d</rasd:VirtualQuantity>
|
<rasd:VirtualQuantity>%(mem)d</rasd:VirtualQuantity>
|
||||||
</Item>
|
</Item>
|
||||||
<Item>
|
<Item>
|
||||||
<rasd:Address>0</rasd:Address>
|
<rasd:Address>0</rasd:Address>
|
||||||
@@ -653,16 +700,24 @@ OVFTemplate = """<?xml version="1.0"?>
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def generateOVF( name, diskname, disksize, mem=1024 ):
|
def generateOVF( name, osname, osid, diskname, disksize, mem=1024, cpus=1,
|
||||||
|
vmname='Mininet-VM', vminfo='A Mininet Virtual Machine' ):
|
||||||
"""Generate (and return) OVF file "name.ovf"
|
"""Generate (and return) OVF file "name.ovf"
|
||||||
name: root name of OVF file to generate
|
name: root name of OVF file to generate
|
||||||
|
osname: OS name for OVF (Ubuntu | Ubuntu 64-bit)
|
||||||
|
osid: OS ID for OVF (93 | 94 )
|
||||||
diskname: name of disk file
|
diskname: name of disk file
|
||||||
disksize: size of virtual disk in bytes
|
disksize: size of virtual disk in bytes
|
||||||
mem: VM memory size in MB"""
|
mem: VM memory size in MB
|
||||||
|
cpus: # of virtual CPUs
|
||||||
|
vmname: Name for VM (default name when importing)
|
||||||
|
vmimfo: Brief description of VM for OVF"""
|
||||||
ovf = name + '.ovf'
|
ovf = name + '.ovf'
|
||||||
filesize = stat( diskname )[ ST_SIZE ]
|
filesize = stat( diskname )[ ST_SIZE ]
|
||||||
# OVFTemplate uses the memory size twice in a row
|
params = dict( osname=osname, osid=osid, diskname=diskname,
|
||||||
xmltext = OVFTemplate % ( diskname, filesize, disksize, name, mem, mem )
|
filesize=filesize, disksize=disksize, name=name,
|
||||||
|
mem=mem, cpus=cpus, vmname=vmname, vminfo=vminfo )
|
||||||
|
xmltext = OVFTemplate % params
|
||||||
with open( ovf, 'w+' ) as f:
|
with open( ovf, 'w+' ) as f:
|
||||||
f.write( xmltext )
|
f.write( xmltext )
|
||||||
return ovf
|
return ovf
|
||||||
@@ -689,6 +744,8 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
|
|||||||
date = strftime( '%y%m%d-%H-%M-%S', lstart)
|
date = strftime( '%y%m%d-%H-%M-%S', lstart)
|
||||||
ovfdate = strftime( '%y%m%d', lstart )
|
ovfdate = strftime( '%y%m%d', lstart )
|
||||||
dir = 'mn-%s-%s' % ( flavor, date )
|
dir = 'mn-%s-%s' % ( flavor, date )
|
||||||
|
if Branch:
|
||||||
|
dir = 'mn-%s-%s-%s' % ( Branch, flavor, date )
|
||||||
try:
|
try:
|
||||||
os.mkdir( dir )
|
os.mkdir( dir )
|
||||||
except:
|
except:
|
||||||
@@ -710,13 +767,16 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
|
|||||||
vm = boot( volume, kernel, initrd, logfile, memory=memory )
|
vm = boot( volume, kernel, initrd, logfile, memory=memory )
|
||||||
version = interact( vm, tests=tests, pre=pre, post=post )
|
version = interact( vm, tests=tests, pre=pre, post=post )
|
||||||
size = qcow2size( volume )
|
size = qcow2size( volume )
|
||||||
vmdk = convert( volume, basename='mininet-vm-' + archFor( flavor ) )
|
arch = archFor( flavor )
|
||||||
|
vmdk = convert( volume, basename='mininet-vm-' + arch )
|
||||||
if not SaveQCOW2:
|
if not SaveQCOW2:
|
||||||
log( '* Removing qcow2 volume', volume )
|
log( '* Removing qcow2 volume', volume )
|
||||||
os.remove( volume )
|
os.remove( volume )
|
||||||
log( '* Converted VM image stored as', abspath( vmdk ) )
|
log( '* Converted VM image stored as', abspath( vmdk ) )
|
||||||
ovfname = 'mininet-%s-%s-%s' % ( version, ovfdate, OSVersion( flavor ) )
|
ovfname = 'mininet-%s-%s-%s' % ( version, ovfdate, OSVersion( flavor ) )
|
||||||
ovf = generateOVF( diskname=vmdk, disksize=size, name=ovfname )
|
osname, osid = OVFOSNameID( flavor )
|
||||||
|
ovf = generateOVF( name=ovfname, osname=osname, osid=osid,
|
||||||
|
diskname=vmdk, disksize=size )
|
||||||
log( '* Generated OVF descriptor file', ovf )
|
log( '* Generated OVF descriptor file', ovf )
|
||||||
if Zip:
|
if Zip:
|
||||||
log( '* Generating .zip file' )
|
log( '* Generating .zip file' )
|
||||||
@@ -731,6 +791,9 @@ def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
|
|||||||
|
|
||||||
def runTests( vm, tests=None, pre='', post='', prompt=Prompt ):
|
def runTests( vm, tests=None, pre='', post='', prompt=Prompt ):
|
||||||
"Run tests (list) in vm (pexpect object)"
|
"Run tests (list) in vm (pexpect object)"
|
||||||
|
if Branch:
|
||||||
|
checkOutBranch( vm, branch=Branch )
|
||||||
|
vm.expect( prompt )
|
||||||
if not tests:
|
if not tests:
|
||||||
tests = []
|
tests = []
|
||||||
if pre:
|
if pre:
|
||||||
@@ -761,8 +824,8 @@ def getMininetVersion( vm ):
|
|||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt,
|
def bootAndRun( image, prompt=Prompt, memory=1024, outputFile=None,
|
||||||
memory=1024 ):
|
runFunction=None, **runArgs ):
|
||||||
"""Boot and test VM
|
"""Boot and test VM
|
||||||
tests: list of tests to run
|
tests: list of tests to run
|
||||||
pre: command line to run in VM before tests
|
pre: command line to run in VM before tests
|
||||||
@@ -790,15 +853,16 @@ def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt,
|
|||||||
login( vm )
|
login( vm )
|
||||||
log( '* Waiting for prompt after login' )
|
log( '* Waiting for prompt after login' )
|
||||||
vm.expect( prompt )
|
vm.expect( prompt )
|
||||||
if Branch:
|
# runFunction should begin with sendline and should eat its last prompt
|
||||||
checkOutBranch( vm, branch=Branch )
|
if runFunction:
|
||||||
vm.expect( prompt )
|
runFunction( vm, **runArgs )
|
||||||
runTests( vm, tests=tests, pre=pre, post=post )
|
|
||||||
# runTests eats its last prompt, but maybe it shouldn't...
|
|
||||||
log( '* Shutting down' )
|
log( '* Shutting down' )
|
||||||
vm.sendline( 'sudo shutdown -h now ' )
|
vm.sendline( 'sudo shutdown -h now ' )
|
||||||
log( '* Waiting for shutdown' )
|
log( '* Waiting for shutdown' )
|
||||||
vm.wait()
|
vm.wait()
|
||||||
|
if outputFile:
|
||||||
|
log( '* Saving temporary image to %s' % outputFile )
|
||||||
|
convert( cow, outputFile )
|
||||||
log( '* Removing temporary dir', tmpdir )
|
log( '* Removing temporary dir', tmpdir )
|
||||||
srun( 'rm -rf ' + tmpdir )
|
srun( 'rm -rf ' + tmpdir )
|
||||||
elapsed = time() - bootTestStart
|
elapsed = time() - bootTestStart
|
||||||
@@ -859,12 +923,13 @@ def parseArgs():
|
|||||||
parser.add_argument( '-p', '--post', metavar='cmd', default='',
|
parser.add_argument( '-p', '--post', metavar='cmd', default='',
|
||||||
help='specify a command line to run after tests' )
|
help='specify a command line to run after tests' )
|
||||||
parser.add_argument( '-b', '--branch', metavar='branch',
|
parser.add_argument( '-b', '--branch', metavar='branch',
|
||||||
help='For an existing VM image, check out and install'
|
help='branch to install and/or check out and test' )
|
||||||
' this branch before testing' )
|
|
||||||
parser.add_argument( 'flavor', nargs='*',
|
parser.add_argument( 'flavor', nargs='*',
|
||||||
help='VM flavor(s) to build (e.g. raring32server)' )
|
help='VM flavor(s) to build (e.g. raring32server)' )
|
||||||
parser.add_argument( '-z', '--zip', action='store_true',
|
parser.add_argument( '-z', '--zip', action='store_true',
|
||||||
help='archive .ovf and .vmdk into .zip file' )
|
help='archive .ovf and .vmdk into .zip file' )
|
||||||
|
parser.add_argument( '-o', '--out',
|
||||||
|
help='output file for test image (vmdk)' )
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.depend:
|
if args.depend:
|
||||||
depend()
|
depend()
|
||||||
@@ -896,8 +961,8 @@ def parseArgs():
|
|||||||
log( '* BUILD FAILED with exception: ', e )
|
log( '* BUILD FAILED with exception: ', e )
|
||||||
exit( 1 )
|
exit( 1 )
|
||||||
for image in args.image:
|
for image in args.image:
|
||||||
bootAndRunTests( image, tests=args.test, pre=args.run,
|
bootAndRun( image, runFunction=runTests, tests=args.test, pre=args.run,
|
||||||
post=args.post, memory=args.memory)
|
post=args.post, memory=args.memory, outputFile=args.out )
|
||||||
if not ( args.depend or args.list or args.clean or args.flavor
|
if not ( args.depend or args.list or args.clean or args.flavor
|
||||||
or args.image ):
|
or args.image ):
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
# This script is intended to install Mininet into
|
# This script is intended to install Mininet into
|
||||||
# a brand-new Ubuntu virtual machine,
|
# a brand-new Ubuntu virtual machine,
|
||||||
# to create a fully usable "tutorial" VM.
|
# to create a fully usable "tutorial" VM.
|
||||||
|
#
|
||||||
|
# optional argument: Mininet branch to install
|
||||||
set -e
|
set -e
|
||||||
echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers > /dev/null
|
echo `whoami` ALL=NOPASSWD: ALL | sudo tee -a /etc/sudoers > /dev/null
|
||||||
sudo sed -i -e 's/Default/#Default/' /etc/sudoers
|
sudo sed -i -e 's/Default/#Default/' /etc/sudoers
|
||||||
@@ -25,11 +27,16 @@ fi
|
|||||||
if [ -e /etc/rc.local.backup ]; then
|
if [ -e /etc/rc.local.backup ]; then
|
||||||
sudo mv /etc/rc.local.backup /etc/rc.local
|
sudo mv /etc/rc.local.backup /etc/rc.local
|
||||||
fi
|
fi
|
||||||
# Install Mininet
|
# Fetch Mininet
|
||||||
sudo apt-get -y install git-core openssh-server
|
sudo apt-get -y install git-core openssh-server
|
||||||
git clone git://github.com/mininet/mininet
|
git clone git://github.com/mininet/mininet
|
||||||
cd mininet
|
# Optionally check out branch
|
||||||
cd
|
if [ "$1" != "" ]; then
|
||||||
|
pushd mininet
|
||||||
|
git checkout -b $1 $1
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
# Install Mininet
|
||||||
time mininet/util/install.sh
|
time mininet/util/install.sh
|
||||||
# Finalize VM
|
# Finalize VM
|
||||||
time mininet/util/install.sh -tc
|
time mininet/util/install.sh -tc
|
||||||
@@ -38,4 +45,3 @@ time mininet/util/install.sh -tc
|
|||||||
# echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc
|
# echo "export NOX_CORE_DIR=~/noxcore/build/src/" >> .bashrc
|
||||||
#fi
|
#fi
|
||||||
echo "Done preparing Mininet VM."
|
echo "Done preparing Mininet VM."
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user