Merge branch 'master' into nat

Conflicts:
	mininet/net.py
	mininet/node.py
This commit is contained in:
Brian O'Connor
2014-08-07 13:40:14 -07:00
34 changed files with 3594 additions and 646 deletions
+8 -1
View File
@@ -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 -1
View File
@@ -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
+2
View File
@@ -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:
+4 -4
View File
@@ -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
+15 -6
View File
@@ -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:
+5
View File
@@ -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
View File
@@ -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')
+1 -1
View File
@@ -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 )
+2 -2
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -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
+76
View File
@@ -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
View File
@@ -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 -3
View File
@@ -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 ):
+52
View File
@@ -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()
+2 -2
View File
@@ -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' )
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -16,6 +16,11 @@ Host: a virtual host. By default, a host is simply a shell; commands
CPULimitedHost: a virtual host whose CPU bandwidth is limited by 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()
+51
View File
@@ -0,0 +1,51 @@
"""
Node Library for Mininet
This contains additional Node types which you may find to be useful
"""
from mininet.net import Mininet
from mininet.topo import Topo
from mininet.node import Switch
from mininet.log import setLogLevel, info
class LinuxBridge( Switch ):
"Linux Bridge (with optional spanning tree)"
nextPrio = 100 # next bridge priority for spanning tree
def __init__( self, name, stp=False, prio=None, **kwargs ):
"""stp: use spanning tree protocol? (default False)
prio: optional explicit bridge priority for STP"""
self.stp = stp
if prio:
self.prio = prio
else:
self.prio = LinuxBridge.nextPrio
LinuxBridge.nextPrio += 1
Switch.__init__( self, name, **kwargs )
def connected( self ):
"Are we forwarding yet?"
if self.stp:
return 'forwarding' in self.cmd( 'brctl showstp', self )
else:
return True
def start( self, controllers ):
self.cmd( 'ifconfig', self, 'down' )
self.cmd( 'brctl delbr', self )
self.cmd( 'brctl addbr', self )
if self.stp:
self.cmd( 'brctl setbridgeprio', self.prio )
self.cmd( 'brctl stp', self, 'on' )
for i in self.intfList():
if self.name in i.name:
self.cmd( 'brctl addif', self, i )
self.cmd( 'ifconfig', self, 'up' )
def stop( self ):
self.cmd( 'ifconfig', self, 'down' )
self.cmd( 'brctl delbr', self )
+14 -9
View File
@@ -53,8 +53,11 @@ class testOptionsTopoCommon( object ):
"""Check that a given value is within a tolerance of expected """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."
+1 -1
View File
@@ -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 )
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 ):
+27 -8
View File
@@ -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
View File
@@ -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;;
+1 -1
View File
@@ -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
View File
@@ -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()
+10 -4
View File
@@ -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."