cluster: add batchStartup/Shutdown, cleanup

This commit is contained in:
Bob Lantz
2015-01-27 15:27:26 -08:00
parent c702840a0a
commit acdcf9b6ae
4 changed files with 145 additions and 83 deletions
+8 -2
View File
@@ -41,7 +41,8 @@ from functools import partial
# Experimental! cluster edition prototype # Experimental! cluster edition prototype
from mininet.examples.cluster import ( MininetCluster, RemoteHost, from mininet.examples.cluster import ( MininetCluster, RemoteHost,
RemoteOVSSwitch, RemoteLink, RemoteOVSSwitch, RemoteLink,
SwitchBinPlacer, RandomPlacer ) SwitchBinPlacer, RandomPlacer,
ClusterCleanup )
from mininet.examples.clustercli import ClusterCLI from mininet.examples.clustercli import ClusterCLI
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer } PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
@@ -281,6 +282,11 @@ class MininetRunner( object ):
def begin( self ): def begin( self ):
"Create and run mininet." "Create and run mininet."
if self.options.cluster:
servers = self.options.cluster.split( ',' )
for server in servers:
ClusterCleanup.add( server )
if self.options.clean: if self.options.clean:
cleanup() cleanup()
exit() exit()
@@ -334,7 +340,7 @@ class MininetRunner( object ):
warn( '*** WARNING: Experimental cluster mode!\n' warn( '*** WARNING: Experimental cluster mode!\n'
'*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' ) '*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
Net = partial( MininetCluster, servers=cluster.split( ',' ), Net = partial( MininetCluster, servers=servers,
placement=PLACEMENT[ self.options.placement ] ) placement=PLACEMENT[ self.options.placement ] )
mn = Net( topo=topo, mn = Net( topo=topo,
+67 -30
View File
@@ -82,6 +82,7 @@ from mininet.topolib import TreeTopo
from mininet.util import quietRun, errRun, retry from mininet.util import quietRun, errRun, retry
from mininet.examples.clustercli import CLI from mininet.examples.clustercli import CLI
from mininet.log import setLogLevel, debug, info, error from mininet.log import setLogLevel, debug, info, error
from mininet.clean import addCleanupCallback
from signal import signal, SIGINT, SIG_IGN from signal import signal, SIGINT, SIG_IGN
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
@@ -89,9 +90,51 @@ import os
from random import randrange from random import randrange
import sys import sys
import re import re
from itertools import groupby
from operator import attrgetter
from distutils.version import StrictVersion from distutils.version import StrictVersion
def findUser():
"Try to return logged-in (usually non-root) user"
return (
# If we're running sudo
os.environ.get( 'SUDO_USER', False ) or
# Logged-in user (if we have a tty)
( quietRun( 'who am i' ).split() or [ False ] )[ 0 ] or
# Give up and return effective user
quietRun( 'whoami' ) )
class ClusterCleanup( object ):
"Cleanup callback"
inited = False
serveruser = {}
@classmethod
def add( cls, server, user='' ):
"Add an entry to server: user dict"
if not cls.inited:
addCleanupCallback( cls.cleanup )
if not user:
user = findUser()
cls.serveruser[ server ] = user
@classmethod
def cleanup( cls ):
"Clean up"
info( '*** Cleaning up cluster\n' )
for server, user in cls.serveruser.iteritems():
if server == 'localhost':
# Handled by mininet.clean.cleanup()
continue
else:
cmd = [ 'su', user, '-c',
'ssh %s@%s sudo mn -c' % ( user, server ) ]
info( cmd, '\n' )
info( quietRun( cmd ) )
# BL note: so little code is required for remote nodes, # BL note: so little code is required for remote nodes,
# we will probably just want to update the main Node() # we will probably just want to update the main Node()
# class to enable it for remote access! However, there # class to enable it for remote access! However, there
@@ -125,7 +168,8 @@ class RemoteMixin( object ):
self.server = server if server else 'localhost' self.server = server if server else 'localhost'
self.serverIP = ( serverIP if serverIP self.serverIP = ( serverIP if serverIP
else self.findServerIP( self.server ) ) else self.findServerIP( self.server ) )
self.user = user if user else self.findUser() self.user = user if user else findUser()
ClusterCleanup.add( server=server, user=user )
if controlPath is True: if controlPath is True:
# Set a default control path for shared SSH connections # Set a default control path for shared SSH connections
controlPath = '/tmp/mn-%r@%h:%p' controlPath = '/tmp/mn-%r@%h:%p'
@@ -148,17 +192,6 @@ class RemoteMixin( object ):
self.shell, self.pid = None, None self.shell, self.pid = None, None
super( RemoteMixin, self ).__init__( name, **kwargs ) super( RemoteMixin, self ).__init__( name, **kwargs )
@staticmethod
def findUser():
"Try to return logged-in (usually non-root) user"
return (
# If we're running sudo
os.environ.get( 'SUDO_USER', False ) or
# Logged-in user (if we have a tty)
( quietRun( 'who am i' ).split() or [ False ] )[ 0 ] or
# Give up and return effective user
quietRun( 'whoami' ) )
# Determine IP address of local host # Determine IP address of local host
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' ) _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
@@ -244,7 +277,7 @@ class RemoteMixin( object ):
# Drop privileges # Drop privileges
cmd = [ 'sudo', '-E', '-u', self.user ] + cmd cmd = [ 'sudo', '-E', '-u', self.user ] + cmd
params.update( preexec_fn=self._ignoreSignal ) params.update( preexec_fn=self._ignoreSignal )
debug( '_popen', ' '.join(cmd), params ) debug( '_popen', cmd, '\n' )
popen = super( RemoteMixin, self )._popen( cmd, **params ) popen = super( RemoteMixin, self )._popen( cmd, **params )
return popen return popen
@@ -257,13 +290,6 @@ class RemoteMixin( object ):
kwargs.update( moveIntfFn=RemoteLink.moveIntf ) kwargs.update( moveIntfFn=RemoteLink.moveIntf )
return super( RemoteMixin, self).addIntf( *args, **kwargs ) return super( RemoteMixin, self).addIntf( *args, **kwargs )
def cleanup( self ):
"Help python collect its garbage."
# Intfs may end up in root NS
for intfName in self.intfNames():
if self.name in intfName:
self.rcmd( 'ip link del ' + intfName )
self.shell = None
class RemoteNode( RemoteMixin, Node ): class RemoteNode( RemoteMixin, Node ):
"A node on a remote server" "A node on a remote server"
@@ -282,7 +308,7 @@ class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
def __init__( self, *args, **kwargs ): def __init__( self, *args, **kwargs ):
# No batch startup yet # No batch startup yet
kwargs.update( batch=False ) kwargs.update( batch=True )
super( RemoteOVSSwitch, self ).__init__( *args, **kwargs ) super( RemoteOVSSwitch, self ).__init__( *args, **kwargs )
def isOldOVS( self ): def isOldOVS( self ):
@@ -298,14 +324,24 @@ class RemoteOVSSwitch( RemoteMixin, OVSSwitch ):
StrictVersion( '1.10' ) ) StrictVersion( '1.10' ) )
@classmethod @classmethod
def batchStartup( cls, *_args, **_kwargs ): def batchStartup( cls, switches, **_kwargs ):
"Not implemented yet" "Start up switches in per-server batches"
return [] # no switches started for server, switchGroup in groupby( switches, attrgetter( 'server' ) ):
info( '(%s)' % server )
group = tuple( switchGroup )
switch = group[ 0 ]
OVSSwitch.batchStartup( group, run=switch.cmd )
return switches
@classmethod @classmethod
def batchShutdown( cls, *_args, **_kwargs ): def batchShutdown( cls, switches, **_kwargs ):
"Not implemented yet" "Stop switches in per-server batches"
return [] # no switchest stopped for server, switchGroup in groupby( switches, attrgetter( 'server' ) ):
info( '(%s)' % server )
group = tuple( switchGroup )
switch = group[ 0 ]
OVSSwitch.batchShutdown( group, run=switch.rcmd )
return switches
class RemoteLink( Link ): class RemoteLink( Link ):
@@ -325,6 +361,7 @@ class RemoteLink( Link ):
def stop( self ): def stop( self ):
"Stop this link" "Stop this link"
Link.stop( self )
if self.tunnel: if self.tunnel:
self.tunnel.terminate() self.tunnel.terminate()
self.tunnel = None self.tunnel = None
@@ -636,7 +673,7 @@ class MininetCluster( Mininet ):
if not self.serverIP: if not self.serverIP:
self.serverIP = { server: RemoteMixin.findServerIP( server ) self.serverIP = { server: RemoteMixin.findServerIP( server )
for server in self.servers } for server in self.servers }
self.user = params.pop( 'user', RemoteMixin.findUser() ) self.user = params.pop( 'user', findUser() )
if params.pop( 'precheck' ): if params.pop( 'precheck' ):
self.precheck() self.precheck()
self.connections = {} self.connections = {}
+67 -47
View File
@@ -38,60 +38,80 @@ def killprocs( pattern ):
else: else:
break break
def cleanup(): class Cleanup( object ):
"""Clean up junk which might be left over from old runs; "Wrapper for cleanup()"
do fast stuff before slow dp and link removal!"""
info("*** Removing excess controllers/ofprotocols/ofdatapaths/pings/noxes" callbacks = []
"\n")
zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
zombies += 'ovs-openflowd ovs-controller udpbwtest mnexec ivs'
# Note: real zombie processes can't actually be killed, since they
# are already (un)dead. Then again,
# you can't connect to them either, so they're mostly harmless.
# Send SIGTERM first to give processes a chance to shutdown cleanly.
sh( 'killall ' + zombies + ' 2> /dev/null' )
time.sleep( 1 )
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
# And kill off sudo mnexec @classmethod
sh( 'pkill -9 -f "sudo mnexec"') def cleanup( cls):
"""Clean up junk which might be left over from old runs;
do fast stuff before slow dp and link removal!"""
info( "*** Removing junk from /tmp\n" ) info("*** Removing excess controllers/ofprotocols/ofdatapaths/pings/noxes"
sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' ) "\n")
zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
zombies += 'ovs-openflowd ovs-controller udpbwtest mnexec ivs'
# Note: real zombie processes can't actually be killed, since they
# are already (un)dead. Then again,
# you can't connect to them either, so they're mostly harmless.
# Send SIGTERM first to give processes a chance to shutdown cleanly.
sh( 'killall ' + zombies + ' 2> /dev/null' )
time.sleep( 1 )
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
info( "*** Removing old X11 tunnels\n" ) # And kill off sudo mnexec
cleanUpScreens() sh( 'pkill -9 -f "sudo mnexec"')
info( "*** Removing excess kernel datapaths\n" ) info( "*** Removing junk from /tmp\n" )
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines() sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' )
for dp in dps:
if dp:
sh( 'dpctl deldp ' + dp )
info( "*** Removing OVS datapaths" ) info( "*** Removing old X11 tunnels\n" )
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines() cleanUpScreens()
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:
sh( 'ovs-vsctl del-br ' + dp )
info( "*** Removing all links of the pattern foo-ethX\n" ) info( "*** Removing excess kernel datapaths\n" )
links = sh( "ip link show | " dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" ).splitlines()
"egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines() for dp in dps:
for link in links: if dp:
if link: sh( 'dpctl deldp ' + dp )
sh( "ip link del " + link )
info( "*** Killing stale mininet node processes\n" ) info( "*** Removing OVS datapaths" )
killprocs( 'mininet:' ) 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:
sh( 'ovs-vsctl del-br ' + dp )
info( "*** Shutting down stale tunnels\n" ) info( "*** Removing all links of the pattern foo-ethX\n" )
killprocs( 'Tunnel=Ethernet' ) links = sh( "ip link show | "
killprocs( '.ssh/mn') "egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'" ).splitlines()
sh( 'rm -f ~/.ssh/mn/*' ) for link in links:
if link:
sh( "ip link del " + link )
info( "*** Cleanup complete.\n" ) info( "*** Killing stale mininet node processes\n" )
killprocs( 'mininet:' )
info( "*** Shutting down stale tunnels\n" )
killprocs( 'Tunnel=Ethernet' )
killprocs( '.ssh/mn')
sh( 'rm -f ~/.ssh/mn/*' )
# Call any additional cleanup code if necessary
for callback in cls.callbacks:
callback()
info( "*** Cleanup complete.\n" )
@classmethod
def addCleanupCallback( cls, callback ):
"Add cleanup callback"
if callback not in cls.callbacks:
cls.callbacks.append( callback )
cleanup = Cleanup.cleanup
addCleanupCallback = Cleanup.addCleanupCallback
+3 -4
View File
@@ -157,8 +157,7 @@ class Node( object ):
break break
self.pollOut.poll() self.pollOut.poll()
self.waiting = False self.waiting = False
self.cmd( 'stty -echo' ) self.cmd( 'stty -echo; set +m' )
self.cmd( 'set +m' )
def mountPrivateDirs( self ): def mountPrivateDirs( self ):
"mount private directories" "mount private directories"
@@ -1270,7 +1269,7 @@ class OVSSwitch( Switch ):
def batchShutdown( cls, switches, run=errRun ): def batchShutdown( cls, switches, run=errRun ):
"Shut down a list of OVS switches" "Shut down a list of OVS switches"
delcmd = 'del-br %s' delcmd = 'del-br %s'
if not cls.isOldOVS(): if switches and not switches[ 0 ].isOldOVS():
delcmd = '--if-exists ' + delcmd delcmd = '--if-exists ' + delcmd
# First, delete them all from ovsdb # First, delete them all from ovsdb
run( 'ovs-vsctl ' + run( 'ovs-vsctl ' +
@@ -1549,4 +1548,4 @@ def DefaultController( name, controllers=DefaultControllers, **kwargs ):
controller = findController( controllers ) controller = findController( controllers )
if not controller: if not controller:
raise Exception( 'Could not find a default OpenFlow controller' ) raise Exception( 'Could not find a default OpenFlow controller' )
return contr return controller( name, **kwargs )