mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-15 17:31:31 +02:00
741 lines
26 KiB
Python
Executable File
741 lines
26 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
"""
|
|
onos.py: ONOS cluster and control network in Mininet
|
|
|
|
With onos.py, you can use Mininet to create a complete
|
|
ONOS network, including an ONOS cluster with a modeled
|
|
control network as well as the usual data nework.
|
|
|
|
This is intended to be useful for distributed ONOS
|
|
development and testing in the case that you require
|
|
a modeled control network.
|
|
|
|
Invocation (using OVS as default switch):
|
|
|
|
mn --custom onos.py --controller onos,3 --topo torus,4,4
|
|
|
|
Or with the user switch (or CPqD if installed):
|
|
|
|
mn --custom onos.py --controller onos,3 \
|
|
--switch onosuser --topo torus,4,4
|
|
|
|
Currently you meed to use a custom switch class
|
|
because Mininet's Switch() class does't (yet?) handle
|
|
controllers with multiple IP addresses directly.
|
|
|
|
The classes may also be imported and used via Mininet's
|
|
python API.
|
|
|
|
Bugs/Gripes:
|
|
- We need --switch onosuser for the user switch because
|
|
Switch() doesn't currently handle Controller objects
|
|
with multiple IP addresses.
|
|
- ONOS startup and configuration is painful/undocumented.
|
|
- Too many ONOS environment vars - do we need them all?
|
|
- ONOS cluster startup is very, very slow. If Linux can
|
|
boot in 4 seconds, why can't ONOS?
|
|
- It's a pain to mess with the control network from the
|
|
CLI
|
|
- Setting a default controller for Mininet should be easier
|
|
"""
|
|
|
|
from mininet.node import Controller, OVSSwitch, UserSwitch
|
|
from mininet.nodelib import LinuxBridge
|
|
from mininet.net import Mininet
|
|
from mininet.topo import SingleSwitchTopo, Topo
|
|
from mininet.log import setLogLevel, info, warn, error, debug
|
|
from mininet.cli import CLI
|
|
from mininet.util import quietRun, specialClass
|
|
from mininet.examples.controlnet import MininetFacade
|
|
|
|
from os import environ
|
|
from os.path import dirname, join, isfile
|
|
from sys import argv
|
|
from glob import glob
|
|
import time
|
|
from functools import partial
|
|
|
|
|
|
### ONOS Environment
|
|
|
|
KarafPort = 8101 # ssh port indicating karaf is running
|
|
GUIPort = 8181 # GUI/REST port
|
|
OpenFlowPort = 6653 # OpenFlow port
|
|
CopycatPort = 9876 # Copycat port
|
|
DebugPort = 5005 # JVM debug port
|
|
|
|
def defaultUser():
|
|
"Return a reasonable default user"
|
|
if 'SUDO_USER' in environ:
|
|
return environ[ 'SUDO_USER' ]
|
|
try:
|
|
user = quietRun( 'who am i' ).split()[ 0 ]
|
|
except:
|
|
user = 'nobody'
|
|
return user
|
|
|
|
# Module vars, initialized below
|
|
HOME = ONOS_ROOT = ONOS_USER = None
|
|
ONOS_APPS = ONOS_WEB_USER = ONOS_WEB_PASS = ONOS_TAR = JAVA_OPTS = None
|
|
|
|
def initONOSEnv():
|
|
"""Initialize ONOS environment (and module) variables
|
|
This is ugly and painful, but they have to be set correctly
|
|
in order for the onos-setup-karaf script to work.
|
|
nodes: list of ONOS nodes
|
|
returns: ONOS environment variable dict"""
|
|
# pylint: disable=global-statement
|
|
global HOME, ONOS_ROOT, ONOS_USER
|
|
global ONOS_APPS, ONOS_WEB_USER, ONOS_WEB_PASS
|
|
env = {}
|
|
def sd( var, val ):
|
|
"Set default value for environment variable"
|
|
env[ var ] = environ.setdefault( var, val )
|
|
return env[ var ]
|
|
assert environ[ 'HOME' ]
|
|
HOME = sd( 'HOME', environ[ 'HOME' ] )
|
|
ONOS_ROOT = sd( 'ONOS_ROOT', join( HOME, 'onos' ) )
|
|
environ[ 'ONOS_USER' ] = defaultUser()
|
|
ONOS_USER = sd( 'ONOS_USER', defaultUser() )
|
|
ONOS_APPS = sd( 'ONOS_APPS',
|
|
'drivers,openflow,fwd,proxyarp,mobility' )
|
|
JAVA_OPTS = sd( 'JAVA_OPTS', '-Xms128m -Xmx512m' )
|
|
# ONOS_WEB_{USER,PASS} isn't respected by onos-karaf:
|
|
environ.update( ONOS_WEB_USER='karaf', ONOS_WEB_PASS='karaf' )
|
|
ONOS_WEB_USER = sd( 'ONOS_WEB_USER', 'karaf' )
|
|
ONOS_WEB_PASS = sd( 'ONOS_WEB_PASS', 'karaf' )
|
|
return env
|
|
|
|
|
|
def updateNodeIPs( env, nodes ):
|
|
"Update env dict and environ with node IPs"
|
|
# Get rid of stale junk
|
|
for var in 'ONOS_NIC', 'ONOS_CELL', 'ONOS_INSTANCES':
|
|
env[ var ] = ''
|
|
for var in environ.keys():
|
|
if var.startswith( 'OC' ):
|
|
env[ var ] = ''
|
|
for index, node in enumerate( nodes, 1 ):
|
|
var = 'OC%d' % index
|
|
env[ var ] = node.IP()
|
|
if nodes:
|
|
env[ 'OCI' ] = env[ 'OCN' ] = env[ 'OC1' ]
|
|
env[ 'ONOS_INSTANCES' ] = '\n'.join(
|
|
node.IP() for node in nodes )
|
|
environ.update( env )
|
|
return env
|
|
|
|
|
|
tarDefaultPath = "bazel-out/k8-fastbuild/bin/onos.tar.gz"
|
|
|
|
def unpackONOS( destDir='/tmp', run=quietRun ):
|
|
"Unpack ONOS and return its location"
|
|
global ONOS_TAR
|
|
environ.setdefault( 'ONOS_TAR', join( ONOS_ROOT, tarDefaultPath ) )
|
|
ONOS_TAR = environ[ 'ONOS_TAR' ]
|
|
tarPath = ONOS_TAR
|
|
if not isfile( tarPath ):
|
|
raise Exception( 'Missing ONOS tarball %s - run buck build onos?'
|
|
% tarPath )
|
|
info( '(unpacking %s)' % destDir)
|
|
success = '*** SUCCESS ***'
|
|
cmds = ( 'mkdir -p "%s" && cd "%s" && tar xzf "%s" && echo "%s"'
|
|
% ( destDir, destDir, tarPath, success ) )
|
|
result = run( cmds, shell=True, verbose=True )
|
|
if success not in result:
|
|
raise Exception( 'Failed to unpack ONOS archive %s in %s:\n%s\n' %
|
|
( tarPath, destDir, result ) )
|
|
# We can use quietRun for this usually
|
|
tarOutput = quietRun( 'tar tzf "%s" | head -1' % tarPath, shell=True)
|
|
tarOutput = tarOutput.split()[ 0 ].strip()
|
|
assert '/' in tarOutput
|
|
onosDir = join( destDir, dirname( tarOutput ) )
|
|
# Add symlink to log file
|
|
run( 'cd %s; ln -s onos*/apache* karaf;'
|
|
'ln -s karaf/data/log/karaf.log log' % destDir,
|
|
shell=True )
|
|
return onosDir
|
|
|
|
|
|
def waitListening( server, port=80, callback=None, sleepSecs=.5,
|
|
proc='java' ):
|
|
"Simplified netstat version of waitListening"
|
|
while True:
|
|
lines = server.cmd( 'netstat -natp' ).strip().split( '\n' )
|
|
entries = [ line.split() for line in lines ]
|
|
portstr = ':%s' % port
|
|
listening = [ entry for entry in entries
|
|
if len( entry ) > 6 and portstr in entry[ 3 ]
|
|
and proc in entry[ 6 ] ]
|
|
if listening:
|
|
break
|
|
info( '.' )
|
|
if callback:
|
|
callback()
|
|
time.sleep( sleepSecs )
|
|
|
|
|
|
### Mininet classes
|
|
|
|
def RenamedTopo( topo, *args, **kwargs ):
|
|
"""Return specialized topo with renamed hosts
|
|
topo: topo class/class name to specialize
|
|
args, kwargs: topo args
|
|
sold: old switch name prefix (default 's')
|
|
snew: new switch name prefix
|
|
hold: old host name prefix (default 'h')
|
|
hnew: new host name prefix
|
|
This may be used from the mn command, e.g.
|
|
mn --topo renamed,single,spref=sw,hpref=host"""
|
|
sold = kwargs.pop( 'sold', 's' )
|
|
hold = kwargs.pop( 'hold', 'h' )
|
|
snew = kwargs.pop( 'snew', 'cs' )
|
|
hnew = kwargs.pop( 'hnew' ,'ch' )
|
|
topos = {} # TODO: use global TOPOS dict
|
|
if isinstance( topo, str ):
|
|
# Look up in topo directory - this allows us to
|
|
# use RenamedTopo from the command line!
|
|
if topo in topos:
|
|
topo = topos.get( topo )
|
|
else:
|
|
raise Exception( 'Unknown topo name: %s' % topo )
|
|
# pylint: disable=no-init
|
|
class RenamedTopoCls( topo ):
|
|
"Topo subclass with renamed nodes"
|
|
def addNode( self, name, *args, **kwargs ):
|
|
"Add a node, renaming if necessary"
|
|
if name.startswith( sold ):
|
|
name = snew + name[ len( sold ): ]
|
|
elif name.startswith( hold ):
|
|
name = hnew + name[ len( hold ): ]
|
|
return topo.addNode( self, name, *args, **kwargs )
|
|
return RenamedTopoCls( *args, **kwargs )
|
|
|
|
|
|
# We accept objects that "claim" to be a particular class,
|
|
# since the class definitions can be both execed (--custom) and
|
|
# imported (in another custom file), which breaks isinstance().
|
|
# In order for this to work properly, a class should not be
|
|
# renamed so as to inappropriately omit or include the class
|
|
# name text. Note that mininet.util.specialClass renames classes
|
|
# by adding the specialized parameter names and values.
|
|
|
|
def isONOSNode( obj ):
|
|
"Does obj claim to be some kind of ONOSNode?"
|
|
return ( isinstance( obj, ONOSNode) or
|
|
'ONOSNode' in type( obj ).__name__ )
|
|
|
|
def isONOSCluster( obj ):
|
|
"Does obj claim to be some kind of ONOSCluster?"
|
|
return ( isinstance( obj, ONOSCluster ) or
|
|
'ONOSCluster' in type( obj ).__name__ )
|
|
|
|
|
|
class ONOSNode( Controller ):
|
|
"ONOS cluster node"
|
|
|
|
def __init__( self, name, **kwargs ):
|
|
"alertAction: exception|ignore|warn|exit (exception)"
|
|
kwargs.update( inNamespace=True )
|
|
self.alertAction = kwargs.pop( 'alertAction', 'exception' )
|
|
Controller.__init__( self, name, **kwargs )
|
|
self.dir = '/tmp/%s' % self.name
|
|
self.client = self.dir + '/karaf/bin/client'
|
|
self.ONOS_HOME = '/tmp'
|
|
self.cmd( 'rm -rf', self.dir )
|
|
self.ONOS_HOME = unpackONOS( self.dir, run=self.ucmd )
|
|
self.ONOS_ROOT = ONOS_ROOT
|
|
|
|
# pylint: disable=arguments-differ
|
|
|
|
|
|
def start( self, env=None, nodes=(), debug=False ):
|
|
"""Start ONOS on node
|
|
env: environment var dict
|
|
nodes: all nodes in cluster"""
|
|
if env is not None:
|
|
self.genStartConfig(env, nodes)
|
|
self.cmd( 'cd', self.ONOS_HOME )
|
|
info( '(starting %s debug: %s)' % ( self, debug ) )
|
|
service = join( self.ONOS_HOME, 'bin/onos-service' )
|
|
if debug:
|
|
self.ucmd( service, 'server debug 1>../onos.log 2>../onos.log'
|
|
' & echo $! > onos.pid; ln -s `pwd`/onos.pid ..' )
|
|
else:
|
|
self.ucmd( service, 'server 1>../onos.log 2>../onos.log'
|
|
' & echo $! > onos.pid; ln -s `pwd`/onos.pid ..' )
|
|
self.onosPid = int( self.cmd( 'cat onos.pid' ).strip() )
|
|
self.warningCount = 0
|
|
|
|
# pylint: disable=arguments-differ
|
|
|
|
def genStartConfig( self, env, nodes=() ):
|
|
"""generate a start config"""
|
|
env = dict( env )
|
|
env.update( ONOS_HOME=self.ONOS_HOME )
|
|
self.updateEnv( env )
|
|
karafbin = glob( '%s/apache*/bin' % self.ONOS_HOME )[ 0 ]
|
|
onosbin = join( ONOS_ROOT, 'tools/test/bin' )
|
|
self.cmd( 'export PATH=%s:%s:$PATH' % ( onosbin, karafbin ) )
|
|
self.cmd( 'cd', self.ONOS_HOME )
|
|
self.ucmd( 'mkdir -p config && '
|
|
'onos-gen-default-cluster config/cluster.json --nodes ',
|
|
' '.join( node.IP() for node in nodes ) )
|
|
|
|
def intfsDown( self ):
|
|
"""Bring all interfaces down"""
|
|
for intf in self.intfs.values():
|
|
cmdOutput = intf.ifconfig( 'down' )
|
|
# no output indicates success
|
|
if cmdOutput:
|
|
error( "Error setting %s down: %s " % ( intf.name, cmdOutput ) )
|
|
|
|
def intfsUp( self ):
|
|
"""Bring all interfaces up"""
|
|
for intf in self.intfs.values():
|
|
cmdOutput = intf.ifconfig( 'up' )
|
|
if cmdOutput:
|
|
error( "Error setting %s up: %s " % ( intf.name, cmdOutput ) )
|
|
|
|
def kill( self ):
|
|
"""Kill ONOS process"""
|
|
self.cmd( 'kill %d && wait' % ( self.onosPid ) )
|
|
|
|
def stop( self ):
|
|
# XXX This will kill all karafs - too bad!
|
|
self.cmd( 'pkill -HUP -f karaf.jar && wait' )
|
|
self.cmd( 'rm -rf', self.dir )
|
|
|
|
def sanityAlert( self, *args ):
|
|
"Alert to raise on sanityCheck failure"
|
|
info( '\n' )
|
|
if self.alertAction == 'exception':
|
|
raise Exception( *args )
|
|
if self.alertAction == 'warn':
|
|
warn( *args + ( '\n', ) )
|
|
elif self.alertAction == 'exit':
|
|
error( '***', *args +
|
|
( '\nExiting. Run "sudo mn -c" to clean up.\n', ) )
|
|
exit( 1 )
|
|
|
|
def isRunning( self ):
|
|
"Is our ONOS process still running?"
|
|
cmd = ( 'ps -p %d >/dev/null 2>&1 && echo "running" ||'
|
|
'echo "not running"' )
|
|
return self.cmd( cmd % self.onosPid ).strip() == 'running'
|
|
|
|
def checkLog( self ):
|
|
"Return log file errors and warnings"
|
|
log = join( self.dir, 'log' )
|
|
errors, warnings = [], []
|
|
if isfile( log ):
|
|
lines = open( log ).read().split( '\n' )
|
|
errors = [ line for line in lines if 'ERROR' in line ]
|
|
warnings = [ line for line in lines if 'WARN'in line ]
|
|
return errors, warnings
|
|
|
|
def memAvailable( self ):
|
|
"Return available memory in KB (or -1 if we can't tell)"
|
|
lines = open( '/proc/meminfo' ).read().strip().split( '\n' )
|
|
entries = map( str.split, lines )
|
|
index = { entry[ 0 ]: entry for entry in entries }
|
|
# Check MemAvailable if present
|
|
default = ( None, '-1', 'kB' )
|
|
_name, count, unit = index.get( 'MemAvailable:', default )
|
|
if unit.lower() == 'kb':
|
|
return int( count )
|
|
return -1
|
|
|
|
def sanityCheck( self, lowMem=100000 ):
|
|
"""Check whether we've quit or are running out of memory
|
|
lowMem: low memory threshold in KB (100000)"""
|
|
# Are we still running?
|
|
if not self.isRunning():
|
|
self.sanityAlert( 'ONOS node %s has died' % self.name )
|
|
# Are there errors in the log file?
|
|
errors, warnings = self.checkLog()
|
|
if errors:
|
|
self.sanityAlert( 'ONOS startup errors:\n<<%s>>' %
|
|
'\n'.join( errors ) )
|
|
warningCount = len( warnings )
|
|
if warnings and warningCount > self.warningCount:
|
|
warn( '(%d warnings)' % len( warnings ) )
|
|
self.warningCount = warningCount
|
|
# Are we running out of memory?
|
|
mem = self.memAvailable()
|
|
if mem > 0 and mem < lowMem:
|
|
self.sanityAlert( 'Running out of memory (only %d KB available)'
|
|
% mem )
|
|
|
|
def waitStarted( self ):
|
|
"Wait until we've really started"
|
|
info( '(checking: karaf' )
|
|
while True:
|
|
status = self.ucmd( 'karaf status' ).lower()
|
|
if 'running' in status and 'not running' not in status:
|
|
break
|
|
info( '.' )
|
|
self.sanityCheck()
|
|
time.sleep( 1 )
|
|
info( ' ssh-port' )
|
|
waitListening( server=self, port=KarafPort, callback=self.sanityCheck )
|
|
info( ' protocol' )
|
|
while True:
|
|
result = quietRun( '%s -h %s "apps -a"' %
|
|
( self.client, self.IP() ), shell=True )
|
|
if 'openflow' in result or 'p4runtime' in result:
|
|
break
|
|
info( '.' )
|
|
self.sanityCheck()
|
|
time.sleep( 1 )
|
|
info( ' node-status' )
|
|
while True:
|
|
result = quietRun( '%s -h %s "nodes"' %
|
|
( self.client, self.IP() ), shell=True )
|
|
nodeStr = 'id=%s, address=%s:%s, state=READY' %\
|
|
( self.IP(), self.IP(), CopycatPort )
|
|
if nodeStr in result:
|
|
break
|
|
|
|
# just break if state is active
|
|
if "state=ACTIVE" in result:
|
|
break
|
|
|
|
info( '.' )
|
|
self.sanityCheck()
|
|
time.sleep( 1 )
|
|
info( ')\n' )
|
|
|
|
def updateEnv( self, envDict ):
|
|
"Update environment variables"
|
|
cmd = ';'.join( ( 'export %s="%s"' % ( var, val )
|
|
if val else 'unset %s' % var )
|
|
for var, val in envDict.iteritems() )
|
|
self.cmd( cmd )
|
|
|
|
def ucmd( self, *args, **_kwargs ):
|
|
"Run command as $ONOS_USER using sudo -E -u"
|
|
if ONOS_USER != 'root': # don't bother with sudo
|
|
args = [ "sudo -E -u $ONOS_USER PATH=$PATH "
|
|
"bash -c '%s'" % ' '.join( args ) ]
|
|
return self.cmd( *args )
|
|
|
|
|
|
class ONOSCluster( Controller ):
|
|
"ONOS Cluster"
|
|
# Offset for port forwarding
|
|
portOffset = 0
|
|
def __init__( self, *args, **kwargs ):
|
|
"""name: (first parameter)
|
|
*args: topology class parameters
|
|
ipBase: IP range for ONOS nodes
|
|
forward: default port forwarding list
|
|
portOffset: offset to port base (optional)
|
|
topo: topology class or instance
|
|
nodeOpts: ONOSNode options
|
|
debug: enabling debug mode or not
|
|
**kwargs: additional topology parameters
|
|
By default, multiple ONOSClusters will increment
|
|
the portOffset automatically; alternately, it can
|
|
be specified explicitly.
|
|
"""
|
|
args = list( args )
|
|
name = args.pop( 0 )
|
|
topo = kwargs.pop( 'topo', None )
|
|
self.nat = kwargs.pop( 'nat', 'nat0' )
|
|
nodeOpts = kwargs.pop( 'nodeOpts', {} )
|
|
self.portOffset = kwargs.pop( 'portOffset', ONOSCluster.portOffset )
|
|
# Pass in kwargs to the ONOSNodes instead of the cluster
|
|
"alertAction: exception|ignore|warn|exit (exception)"
|
|
alertAction = kwargs.pop( 'alertAction', None )
|
|
if alertAction:
|
|
nodeOpts[ 'alertAction'] = alertAction
|
|
# Default: single switch with 1 ONOS node
|
|
if not topo:
|
|
topo = SingleSwitchTopo
|
|
if not args:
|
|
args = ( 1, )
|
|
if not isinstance( topo, Topo ):
|
|
topo = RenamedTopo( topo, *args, hnew='onos', **kwargs )
|
|
self.ipBase = kwargs.pop( 'ipBase', '192.168.123.0/24' )
|
|
self.forward = kwargs.pop( 'forward',
|
|
[ KarafPort, GUIPort, OpenFlowPort, DebugPort ] )
|
|
self.debug = kwargs.pop('debug', 'False') == 'True'
|
|
|
|
super( ONOSCluster, self ).__init__( name, inNamespace=False )
|
|
fixIPTables()
|
|
self.env = initONOSEnv()
|
|
self.net = Mininet( topo=topo, ipBase=self.ipBase,
|
|
host=partial( ONOSNode, **nodeOpts ),
|
|
switch=LinuxBridge,
|
|
controller=None )
|
|
if self.nat:
|
|
self.net.addNAT( self.nat ).configDefault()
|
|
updateNodeIPs( self.env, self.nodes() )
|
|
self._remoteControllers = []
|
|
# Update port offset for more ONOS clusters
|
|
ONOSCluster.portOffset += len( self.nodes() )
|
|
|
|
def start( self ):
|
|
"Start up ONOS cluster"
|
|
info( '*** ONOS_APPS = %s\n' % ONOS_APPS )
|
|
self.net.start()
|
|
for node in self.nodes():
|
|
node.start( self.env, self.nodes(), self.debug )
|
|
info( '\n' )
|
|
self.configPortForwarding( ports=self.forward, action='A' )
|
|
self.waitStarted()
|
|
return
|
|
|
|
def waitStarted( self ):
|
|
"Wait until all nodes have started"
|
|
startTime = time.time()
|
|
for node in self.nodes():
|
|
info( node )
|
|
node.waitStarted()
|
|
info( '*** Waited %.2f seconds for ONOS startup' %
|
|
( time.time() - startTime ) )
|
|
|
|
def stop( self ):
|
|
"Shut down ONOS cluster"
|
|
self.configPortForwarding( ports=self.forward, action='D' )
|
|
for node in self.nodes():
|
|
node.stop()
|
|
self.net.stop()
|
|
|
|
def nodes( self ):
|
|
"Return list of ONOS nodes"
|
|
return [ h for h in self.net.hosts if isONOSNode( h ) ]
|
|
|
|
def configPortForwarding( self, ports=[], action='A' ):
|
|
"""Start or stop port forwarding (any intf) for all nodes
|
|
ports: list of ports to forward
|
|
action: A=add/start, D=delete/stop (default: A)"""
|
|
self.cmd( 'iptables -' + action, 'FORWARD -d', self.ipBase,
|
|
'-j ACCEPT' )
|
|
for port in ports:
|
|
for index, node in enumerate( self.nodes() ):
|
|
ip, inport = node.IP(), port + self.portOffset + index
|
|
# Configure a destination NAT rule
|
|
self.cmd( 'iptables -t nat -' + action,
|
|
'PREROUTING -t nat -p tcp --dport', inport,
|
|
'-j DNAT --to-destination %s:%s' % ( ip, port ) )
|
|
|
|
def getONOSNode( self, instance ):
|
|
"""Return ONOS node which name is 'instance'
|
|
instance: ONOS instance name"""
|
|
try:
|
|
onos = self.net.getNodeByName( instance )
|
|
if isONOSNode( onos ):
|
|
return onos
|
|
else:
|
|
info( 'instance %s is not ONOS.\n' % instance )
|
|
except KeyError:
|
|
info( 'No such ONOS instance %s.\n' % instance )
|
|
|
|
class ONOSSwitchMixin( object ):
|
|
"Mixin for switches that connect to an ONOSCluster"
|
|
def start( self, controllers ):
|
|
"Connect to ONOSCluster"
|
|
self.controllers = controllers
|
|
assert ( len( controllers ) is 1 and
|
|
isONOSCluster( controllers[ 0 ] ) )
|
|
clist = controllers[ 0 ].nodes()
|
|
return super( ONOSSwitchMixin, self ).start( clist )
|
|
|
|
class ONOSOVSSwitch( ONOSSwitchMixin, OVSSwitch ):
|
|
"OVSSwitch that can connect to an ONOSCluster"
|
|
pass
|
|
|
|
class ONOSUserSwitch( ONOSSwitchMixin, UserSwitch):
|
|
"UserSwitch that can connect to an ONOSCluster"
|
|
pass
|
|
|
|
|
|
### Ugly utility routines
|
|
|
|
def fixIPTables():
|
|
"Fix LinuxBridge warning"
|
|
for s in 'arp', 'ip', 'ip6':
|
|
quietRun( 'sysctl net.bridge.bridge-nf-call-%stables=0' % s )
|
|
|
|
|
|
### Test code
|
|
|
|
def test( serverCount ):
|
|
"Test this setup"
|
|
setLogLevel( 'info' )
|
|
net = Mininet( topo=SingleSwitchTopo( 3 ),
|
|
controller=[ ONOSCluster( 'c0', serverCount ) ],
|
|
switch=ONOSOVSSwitch )
|
|
net.start()
|
|
net.waitConnected()
|
|
CLI( net )
|
|
net.stop()
|
|
|
|
|
|
### CLI Extensions
|
|
|
|
OldCLI = CLI
|
|
|
|
class ONOSCLI( OldCLI ):
|
|
"CLI Extensions for ONOS"
|
|
|
|
prompt = 'mininet-onos> '
|
|
|
|
def __init__( self, net, **kwargs ):
|
|
clusters = [ c.net for c in net.controllers
|
|
if isONOSCluster( c ) ]
|
|
net = MininetFacade( net, *clusters )
|
|
OldCLI.__init__( self, net, **kwargs )
|
|
|
|
def onos1( self ):
|
|
"Helper function: return default ONOS node"
|
|
return self.mn.controllers[ 0 ].net.hosts[ 0 ]
|
|
|
|
def do_onos( self, line ):
|
|
"Send command to ONOS CLI"
|
|
c0 = self.mn.controllers[ 0 ]
|
|
if isONOSCluster( c0 ):
|
|
# cmdLoop strips off command name 'onos'
|
|
if line.startswith( ':' ):
|
|
line = 'onos' + line
|
|
onos1 = self.onos1().name
|
|
if line:
|
|
line = '"%s"' % line
|
|
cmd = '%s client -h %s %s' % ( onos1, onos1, line )
|
|
quietRun( 'stty -echo' )
|
|
self.default( cmd )
|
|
quietRun( 'stty echo' )
|
|
|
|
def do_wait( self, line ):
|
|
"Wait for switches to connect"
|
|
self.mn.waitConnected()
|
|
|
|
def do_balance( self, line ):
|
|
"Balance switch mastership"
|
|
self.do_onos( ':balance-masters' )
|
|
|
|
def do_log( self, line ):
|
|
"Run tail -f /tmp/onos1/log; press control-C to stop"
|
|
self.default( '%s tail -f /tmp/%s/log' %
|
|
( self.onos1(), self.onos1() ) )
|
|
|
|
def do_status( self, line ):
|
|
"Return status of ONOS cluster(s)"
|
|
for c in self.mn.controllers:
|
|
if isONOSCluster( c ):
|
|
for node in c.net.hosts:
|
|
if isONOSNode( node ):
|
|
errors, warnings = node.checkLog()
|
|
running = ( 'Running' if node.isRunning()
|
|
else 'Exited' )
|
|
status = ''
|
|
if errors:
|
|
status += '%d ERRORS ' % len( errors )
|
|
if warnings:
|
|
status += '%d warnings' % len( warnings )
|
|
status = status if status else 'OK'
|
|
info( node, '\t', running, '\t', status, '\n' )
|
|
|
|
def do_arp( self, line ):
|
|
"Send gratuitous arps from all data network hosts"
|
|
startTime = time.time()
|
|
try:
|
|
count = int( line )
|
|
except:
|
|
count = 1
|
|
# Technically this check should be on the host
|
|
if '-U' not in quietRun( 'arping -h', shell=True ):
|
|
warn( 'Please install iputils-arping.\n' )
|
|
return
|
|
# This is much faster if we do it in parallel
|
|
for host in self.mn.net.hosts:
|
|
intf = host.defaultIntf()
|
|
# -b: keep using broadcasts; -f: quit after 1 reply
|
|
# -U: gratuitous ARP update
|
|
host.sendCmd( 'arping -bf -c', count, '-U -I',
|
|
intf.name, intf.IP() )
|
|
for host in self.mn.net.hosts:
|
|
# We could check the output here if desired
|
|
host.waitOutput()
|
|
info( '.' )
|
|
info( '\n' )
|
|
elapsed = time.time() - startTime
|
|
debug( 'Completed in %.2f seconds\n' % elapsed )
|
|
|
|
def onosupdown( self, cmd, instance ):
|
|
if not instance:
|
|
info( 'Provide the name of an ONOS instance.\n' )
|
|
return
|
|
c0 = self.mn.controllers[ 0 ]
|
|
if isONOSCluster( c0 ):
|
|
onos = c0.getONOSNode( instance )
|
|
if onos:
|
|
info('Bringing %s %s...\n' % ( instance, cmd ) )
|
|
if cmd == 'up':
|
|
onos.intfsUp()
|
|
else:
|
|
onos.intfsDown()
|
|
|
|
def do_onosdown( self, instance=None ):
|
|
"""Disconnects an ONOS instance from the network"""
|
|
self.onosupdown( 'down', instance )
|
|
|
|
def do_onosup( self, instance=None ):
|
|
""""Connects an ONOS instance to the network"""
|
|
self.onosupdown( 'up', instance )
|
|
|
|
def do_start( self, instance=None ):
|
|
"""Start ONOS instance"""
|
|
if not instance:
|
|
info( 'Provide the name of an ONOS instance.\n' )
|
|
return
|
|
c0 = self.mn.controllers[ 0 ]
|
|
if isONOSCluster( c0 ):
|
|
onos = c0.getONOSNode( instance )
|
|
if onos:
|
|
info('Starting %s...\n' % ( instance ) )
|
|
onos.start( debug=c0.debug )
|
|
|
|
def do_kill( self, instance=None ):
|
|
"""Kill ONOS instance"""
|
|
if not instance:
|
|
info( 'Provide the name of an ONOS instance.\n' )
|
|
return
|
|
c0 = self.mn.controllers[ 0 ]
|
|
if isONOSCluster( c0 ):
|
|
onos = c0.getONOSNode( instance )
|
|
if onos:
|
|
info('Killing %s...\n' % ( instance ) )
|
|
onos.kill()
|
|
|
|
# For interactive use, exit on error
|
|
exitOnError = dict( nodeOpts={ 'alertAction': 'exit' } )
|
|
ONOSClusterInteractive = specialClass( ONOSCluster, defaults=exitOnError )
|
|
|
|
|
|
### Exports for bin/mn
|
|
|
|
CLI = ONOSCLI
|
|
controllers = { 'onos': ONOSClusterInteractive,
|
|
'default': ONOSClusterInteractive }
|
|
|
|
# XXX Hack to change default controller as above doesn't work
|
|
findController = lambda: controllers[ 'default' ]
|
|
|
|
switches = { 'onos': ONOSOVSSwitch,
|
|
'onosovs': ONOSOVSSwitch,
|
|
'onosuser': ONOSUserSwitch,
|
|
'default': ONOSOVSSwitch }
|
|
|
|
# Null topology so we can control an external/hardware network
|
|
topos = { 'none': Topo }
|
|
|
|
if __name__ == '__main__':
|
|
if len( argv ) != 2:
|
|
test( 3 )
|
|
else:
|
|
test( int( argv[ 1 ] ) )
|