blob: cf448bbd0ab8d6111fa30535bd5e1f87dbb630f8 [file] [log] [blame]
Bob Lantz087b5d92016-05-06 11:39:04 -07001#!/usr/bin/python
2
3"""
4onos.py: ONOS cluster and control network in Mininet
5
6With onos.py, you can use Mininet to create a complete
7ONOS network, including an ONOS cluster with a modeled
8control network as well as the usual data nework.
9
10This is intended to be useful for distributed ONOS
11development and testing in the case that you require
12a modeled control network.
13
14Invocation (using OVS as default switch):
15
16mn --custom onos.py --controller onos,3 --topo torus,4,4
17
18Or with the user switch (or CPqD if installed):
19
20mn --custom onos.py --controller onos,3 \
21 --switch onosuser --topo torus,4,4
22
Bob Lantzbb37d872016-05-16 16:26:13 -070023Currently you meed to use a custom switch class
Bob Lantz087b5d92016-05-06 11:39:04 -070024because Mininet's Switch() class does't (yet?) handle
25controllers with multiple IP addresses directly.
26
27The classes may also be imported and used via Mininet's
28python API.
29
30Bugs/Gripes:
31- We need --switch onosuser for the user switch because
32 Switch() doesn't currently handle Controller objects
33 with multiple IP addresses.
34- ONOS startup and configuration is painful/undocumented.
35- Too many ONOS environment vars - do we need them all?
36- ONOS cluster startup is very, very slow. If Linux can
37 boot in 4 seconds, why can't ONOS?
38- It's a pain to mess with the control network from the
39 CLI
40- Setting a default controller for Mininet should be easier
41"""
42
43from mininet.node import Controller, OVSSwitch, UserSwitch
44from mininet.nodelib import LinuxBridge
45from mininet.net import Mininet
46from mininet.topo import SingleSwitchTopo, Topo
Bob Lantz64382422016-06-03 22:51:39 -070047from mininet.log import setLogLevel, info, warn, error, debug
Bob Lantz087b5d92016-05-06 11:39:04 -070048from mininet.cli import CLI
Bob Lantz64382422016-06-03 22:51:39 -070049from mininet.util import quietRun, specialClass
Bob Lantz087b5d92016-05-06 11:39:04 -070050from mininet.examples.controlnet import MininetFacade
51
52from os import environ
53from os.path import dirname, join, isfile
54from sys import argv
55from glob import glob
56import time
Bob Lantz64382422016-06-03 22:51:39 -070057from functools import partial
Bob Lantz087b5d92016-05-06 11:39:04 -070058
59### ONOS Environment
60
Bob Lantzbb37d872016-05-16 16:26:13 -070061KarafPort = 8101 # ssh port indicating karaf is running
62GUIPort = 8181 # GUI/REST port
63OpenFlowPort = 6653 # OpenFlow port
Bob Lantz087b5d92016-05-06 11:39:04 -070064
65def defaultUser():
66 "Return a reasonable default user"
67 if 'SUDO_USER' in environ:
68 return environ[ 'SUDO_USER' ]
69 try:
70 user = quietRun( 'who am i' ).split()[ 0 ]
71 except:
72 user = 'nobody'
73 return user
74
Bob Lantz087b5d92016-05-06 11:39:04 -070075# Module vars, initialized below
Bob Lantz4b51d5c2016-05-27 14:47:38 -070076HOME = ONOS_ROOT = ONOS_USER = None
Bob Lantz087b5d92016-05-06 11:39:04 -070077ONOS_APPS = ONOS_WEB_USER = ONOS_WEB_PASS = ONOS_TAR = None
78
79def initONOSEnv():
80 """Initialize ONOS environment (and module) variables
81 This is ugly and painful, but they have to be set correctly
82 in order for the onos-setup-karaf script to work.
83 nodes: list of ONOS nodes
84 returns: ONOS environment variable dict"""
85 # pylint: disable=global-statement
Bob Lantz4b51d5c2016-05-27 14:47:38 -070086 global HOME, ONOS_ROOT, ONOS_USER
Bob Lantz087b5d92016-05-06 11:39:04 -070087 global ONOS_APPS, ONOS_WEB_USER, ONOS_WEB_PASS
88 env = {}
89 def sd( var, val ):
90 "Set default value for environment variable"
91 env[ var ] = environ.setdefault( var, val )
92 return env[ var ]
Bob Lantz4b51d5c2016-05-27 14:47:38 -070093 assert environ[ 'HOME' ]
Bob Lantz087b5d92016-05-06 11:39:04 -070094 HOME = sd( 'HOME', environ[ 'HOME' ] )
Bob Lantz087b5d92016-05-06 11:39:04 -070095 ONOS_ROOT = sd( 'ONOS_ROOT', join( HOME, 'onos' ) )
Bob Lantz087b5d92016-05-06 11:39:04 -070096 environ[ 'ONOS_USER' ] = defaultUser()
97 ONOS_USER = sd( 'ONOS_USER', defaultUser() )
98 ONOS_APPS = sd( 'ONOS_APPS',
99 'drivers,openflow,fwd,proxyarp,mobility' )
100 # ONOS_WEB_{USER,PASS} isn't respected by onos-karaf:
101 environ.update( ONOS_WEB_USER='karaf', ONOS_WEB_PASS='karaf' )
102 ONOS_WEB_USER = sd( 'ONOS_WEB_USER', 'karaf' )
103 ONOS_WEB_PASS = sd( 'ONOS_WEB_PASS', 'karaf' )
104 return env
105
106
107def updateNodeIPs( env, nodes ):
108 "Update env dict and environ with node IPs"
109 # Get rid of stale junk
110 for var in 'ONOS_NIC', 'ONOS_CELL', 'ONOS_INSTANCES':
111 env[ var ] = ''
112 for var in environ.keys():
113 if var.startswith( 'OC' ):
114 env[ var ] = ''
115 for index, node in enumerate( nodes, 1 ):
116 var = 'OC%d' % index
117 env[ var ] = node.IP()
118 env[ 'OCI' ] = env[ 'OCN' ] = env[ 'OC1' ]
119 env[ 'ONOS_INSTANCES' ] = '\n'.join(
120 node.IP() for node in nodes )
121 environ.update( env )
122 return env
123
124
125tarDefaultPath = 'buck-out/gen/tools/package/onos-package/onos.tar.gz'
126
Bob Lantz1451d722016-05-17 14:40:07 -0700127def unpackONOS( destDir='/tmp', run=quietRun ):
Bob Lantz087b5d92016-05-06 11:39:04 -0700128 "Unpack ONOS and return its location"
129 global ONOS_TAR
130 environ.setdefault( 'ONOS_TAR', join( ONOS_ROOT, tarDefaultPath ) )
131 ONOS_TAR = environ[ 'ONOS_TAR' ]
132 tarPath = ONOS_TAR
133 if not isfile( tarPath ):
134 raise Exception( 'Missing ONOS tarball %s - run buck build onos?'
135 % tarPath )
136 info( '(unpacking %s)' % destDir)
Bob Lantz1451d722016-05-17 14:40:07 -0700137 cmds = ( 'mkdir -p "%s" && cd "%s" && tar xzf "%s"'
Bob Lantz087b5d92016-05-06 11:39:04 -0700138 % ( destDir, destDir, tarPath) )
Bob Lantz1451d722016-05-17 14:40:07 -0700139 run( cmds, shell=True, verbose=True )
140 # We can use quietRun for this usually
141 tarOutput = quietRun( 'tar tzf "%s" | head -1' % tarPath, shell=True)
142 tarOutput = tarOutput.split()[ 0 ].strip()
143 assert '/' in tarOutput
144 onosDir = join( destDir, dirname( tarOutput ) )
Bob Lantz087b5d92016-05-06 11:39:04 -0700145 # Add symlink to log file
Bob Lantz1451d722016-05-17 14:40:07 -0700146 run( 'cd %s; ln -s onos*/apache* karaf;'
147 'ln -s karaf/data/log/karaf.log log' % destDir,
148 shell=True )
Bob Lantz087b5d92016-05-06 11:39:04 -0700149 return onosDir
150
151
Bob Lantz9ba19dc2016-06-13 20:22:07 -0700152def waitListening( server, port=80, callback=None, sleepSecs=.5,
153 proc='java' ):
154 "Simplified netstat version of waitListening"
155 while True:
156 lines = server.cmd( 'netstat -natp' ).strip().split( '\n' )
157 entries = [ line.split() for line in lines ]
158 portstr = ':%s' % port
159 listening = [ entry for entry in entries
160 if len( entry ) > 6 and portstr in entry[ 3 ]
161 and proc in entry[ 6 ] ]
162 if listening:
163 break
Bob Lantz64382422016-06-03 22:51:39 -0700164 info( '.' )
165 if callback:
166 callback()
167 time.sleep( sleepSecs )
Bob Lantz64382422016-06-03 22:51:39 -0700168
169
Bob Lantz087b5d92016-05-06 11:39:04 -0700170### Mininet classes
171
172def RenamedTopo( topo, *args, **kwargs ):
173 """Return specialized topo with renamed hosts
174 topo: topo class/class name to specialize
175 args, kwargs: topo args
176 sold: old switch name prefix (default 's')
177 snew: new switch name prefix
178 hold: old host name prefix (default 'h')
179 hnew: new host name prefix
180 This may be used from the mn command, e.g.
181 mn --topo renamed,single,spref=sw,hpref=host"""
182 sold = kwargs.pop( 'sold', 's' )
183 hold = kwargs.pop( 'hold', 'h' )
184 snew = kwargs.pop( 'snew', 'cs' )
185 hnew = kwargs.pop( 'hnew' ,'ch' )
186 topos = {} # TODO: use global TOPOS dict
187 if isinstance( topo, str ):
188 # Look up in topo directory - this allows us to
189 # use RenamedTopo from the command line!
190 if topo in topos:
191 topo = topos.get( topo )
192 else:
193 raise Exception( 'Unknown topo name: %s' % topo )
194 # pylint: disable=no-init
195 class RenamedTopoCls( topo ):
196 "Topo subclass with renamed nodes"
197 def addNode( self, name, *args, **kwargs ):
198 "Add a node, renaming if necessary"
199 if name.startswith( sold ):
200 name = snew + name[ len( sold ): ]
201 elif name.startswith( hold ):
202 name = hnew + name[ len( hold ): ]
203 return topo.addNode( self, name, *args, **kwargs )
204 return RenamedTopoCls( *args, **kwargs )
205
206
207class ONOSNode( Controller ):
208 "ONOS cluster node"
209
Bob Lantz087b5d92016-05-06 11:39:04 -0700210 def __init__( self, name, **kwargs ):
Bob Lantz64382422016-06-03 22:51:39 -0700211 "alertAction: exception|ignore|warn|exit (exception)"
Bob Lantz087b5d92016-05-06 11:39:04 -0700212 kwargs.update( inNamespace=True )
Bob Lantz64382422016-06-03 22:51:39 -0700213 self.alertAction = kwargs.pop( 'alertAction', 'exception' )
Bob Lantz087b5d92016-05-06 11:39:04 -0700214 Controller.__init__( self, name, **kwargs )
215 self.dir = '/tmp/%s' % self.name
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700216 self.client = self.dir + '/karaf/bin/client'
Bob Lantz087b5d92016-05-06 11:39:04 -0700217 self.ONOS_HOME = '/tmp'
218
219 # pylint: disable=arguments-differ
220
Bob Lantz569bbec2016-06-03 18:51:16 -0700221 def start( self, env, nodes=() ):
Bob Lantz087b5d92016-05-06 11:39:04 -0700222 """Start ONOS on node
Bob Lantz569bbec2016-06-03 18:51:16 -0700223 env: environment var dict
224 nodes: all nodes in cluster"""
Bob Lantz087b5d92016-05-06 11:39:04 -0700225 env = dict( env )
226 self.cmd( 'rm -rf', self.dir )
Bob Lantz1451d722016-05-17 14:40:07 -0700227 self.ONOS_HOME = unpackONOS( self.dir, run=self.ucmd )
Bob Lantz087b5d92016-05-06 11:39:04 -0700228 env.update( ONOS_HOME=self.ONOS_HOME )
229 self.updateEnv( env )
230 karafbin = glob( '%s/apache*/bin' % self.ONOS_HOME )[ 0 ]
231 onosbin = join( ONOS_ROOT, 'tools/test/bin' )
232 self.cmd( 'export PATH=%s:%s:$PATH' % ( onosbin, karafbin ) )
233 self.cmd( 'cd', self.ONOS_HOME )
Bob Lantz1451d722016-05-17 14:40:07 -0700234 self.ucmd( 'mkdir -p config && '
Bob Lantz569bbec2016-06-03 18:51:16 -0700235 'onos-gen-partitions config/cluster.json',
236 ' '.join( node.IP() for node in nodes ) )
Bob Lantz087b5d92016-05-06 11:39:04 -0700237 info( '(starting %s)' % self )
238 service = join( self.ONOS_HOME, 'bin/onos-service' )
Bob Lantz1451d722016-05-17 14:40:07 -0700239 self.ucmd( service, 'server 1>../onos.log 2>../onos.log'
240 ' & echo $! > onos.pid; ln -s `pwd`/onos.pid ..' )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700241 self.onosPid = int( self.cmd( 'cat onos.pid' ).strip() )
Bob Lantz64382422016-06-03 22:51:39 -0700242 self.warningCount = 0
Bob Lantz087b5d92016-05-06 11:39:04 -0700243
244 # pylint: enable=arguments-differ
245
246 def stop( self ):
247 # XXX This will kill all karafs - too bad!
248 self.cmd( 'pkill -HUP -f karaf.jar && wait' )
249 self.cmd( 'rm -rf', self.dir )
250
Bob Lantz64382422016-06-03 22:51:39 -0700251 def sanityAlert( self, *args ):
252 "Alert to raise on sanityCheck failure"
253 info( '\n' )
254 if self.alertAction == 'exception':
255 raise Exception( *args )
256 if self.alertAction == 'warn':
257 warn( *args + ( '\n', ) )
258 elif self.alertAction == 'exit':
259 error( '***', *args +
260 ( '\nExiting. Run "sudo mn -c" to clean up.\n', ) )
261 exit( 1 )
262
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700263 def isRunning( self ):
264 "Is our ONOS process still running?"
Bob Lantz64382422016-06-03 22:51:39 -0700265 cmd = ( 'ps -p %d >/dev/null 2>&1 && echo "running" ||'
266 'echo "not running"' )
267 return self.cmd( cmd % self.onosPid ).strip() == 'running'
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700268
Bob Lantz64382422016-06-03 22:51:39 -0700269 def checkLog( self ):
270 "Return log file errors and warnings"
271 log = join( self.dir, 'log' )
Bob Lantzc96e2582016-06-13 18:57:04 -0700272 errors, warnings = [], []
Bob Lantz64382422016-06-03 22:51:39 -0700273 if isfile( log ):
274 lines = open( log ).read().split( '\n' )
275 errors = [ line for line in lines if 'ERROR' in line ]
276 warnings = [ line for line in lines if 'WARN'in line ]
277 return errors, warnings
278
279 def memAvailable( self ):
280 "Return available memory in KB (or -1 if we can't tell)"
281 lines = open( '/proc/meminfo' ).read().strip().split( '\n' )
282 entries = map( str.split, lines )
283 index = { entry[ 0 ]: entry for entry in entries }
284 # Check MemAvailable if present
285 default = ( None, '-1', 'kB' )
286 _name, count, unit = index.get( 'MemAvailable:', default )
287 if unit.lower() == 'kb':
288 return int( count )
289 return -1
290
291 def sanityCheck( self, lowMem=100000 ):
292 """Check whether we've quit or are running out of memory
293 lowMem: low memory threshold in KB (100000)"""
294 # Are we still running?
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700295 if not self.isRunning():
Bob Lantz64382422016-06-03 22:51:39 -0700296 self.sanityAlert( 'ONOS node %s has died' % self.name )
297 # Are there errors in the log file?
298 errors, warnings = self.checkLog()
299 if errors:
300 self.sanityAlert( 'ONOS startup errors:\n<<%s>>' %
301 '\n'.join( errors ) )
302 warningCount = len( warnings )
303 if warnings and warningCount > self.warningCount:
304 warn( '(%d warnings)' % len( warnings ) )
305 self.warningCount = warningCount
306 # Are we running out of memory?
307 mem = self.memAvailable()
308 if mem > 0 and mem < lowMem:
309 self.sanityAlert( 'Running out of memory (only %d KB available)'
310 % mem )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700311
Bob Lantz087b5d92016-05-06 11:39:04 -0700312 def waitStarted( self ):
313 "Wait until we've really started"
314 info( '(checking: karaf' )
315 while True:
Bob Lantz1451d722016-05-17 14:40:07 -0700316 status = self.ucmd( 'karaf status' ).lower()
Bob Lantz087b5d92016-05-06 11:39:04 -0700317 if 'running' in status and 'not running' not in status:
318 break
319 info( '.' )
Bob Lantz64382422016-06-03 22:51:39 -0700320 self.sanityCheck()
Bob Lantz087b5d92016-05-06 11:39:04 -0700321 time.sleep( 1 )
322 info( ' ssh-port' )
Bob Lantz64382422016-06-03 22:51:39 -0700323 waitListening( server=self, port=KarafPort, callback=self.sanityCheck )
Bob Lantz087b5d92016-05-06 11:39:04 -0700324 info( ' openflow-port' )
Bob Lantz64382422016-06-03 22:51:39 -0700325 waitListening( server=self, port=OpenFlowPort,
326 callback=self.sanityCheck )
Bob Lantz087b5d92016-05-06 11:39:04 -0700327 info( ' client' )
328 while True:
Bob Lantz9ba19dc2016-06-13 20:22:07 -0700329 result = quietRun( '%s -h %s "apps -a"' %
Bob Lantzbb37d872016-05-16 16:26:13 -0700330 ( self.client, self.IP() ), shell=True )
Bob Lantz087b5d92016-05-06 11:39:04 -0700331 if 'openflow' in result:
332 break
333 info( '.' )
Bob Lantz64382422016-06-03 22:51:39 -0700334 self.sanityCheck()
Bob Lantz087b5d92016-05-06 11:39:04 -0700335 time.sleep( 1 )
336 info( ')\n' )
337
338 def updateEnv( self, envDict ):
339 "Update environment variables"
Bob Lantz569bbec2016-06-03 18:51:16 -0700340 cmd = ';'.join( ( 'export %s="%s"' % ( var, val )
341 if val else 'unset %s' % var )
Bob Lantz087b5d92016-05-06 11:39:04 -0700342 for var, val in envDict.iteritems() )
343 self.cmd( cmd )
344
Bob Lantz1451d722016-05-17 14:40:07 -0700345 def ucmd( self, *args, **_kwargs ):
346 "Run command as $ONOS_USER using sudo -E -u"
347 if ONOS_USER != 'root': # don't bother with sudo
348 args = [ "sudo -E -u $ONOS_USER PATH=$PATH "
349 "bash -c '%s'" % ' '.join( args ) ]
350 return self.cmd( *args )
351
Bob Lantz087b5d92016-05-06 11:39:04 -0700352
353class ONOSCluster( Controller ):
354 "ONOS Cluster"
355 def __init__( self, *args, **kwargs ):
356 """name: (first parameter)
357 *args: topology class parameters
358 ipBase: IP range for ONOS nodes
Bob Lantzbb37d872016-05-16 16:26:13 -0700359 forward: default port forwarding list,
Bob Lantz087b5d92016-05-06 11:39:04 -0700360 topo: topology class or instance
Bob Lantz64382422016-06-03 22:51:39 -0700361 nodeOpts: ONOSNode options
Bob Lantz087b5d92016-05-06 11:39:04 -0700362 **kwargs: additional topology parameters"""
363 args = list( args )
364 name = args.pop( 0 )
365 topo = kwargs.pop( 'topo', None )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700366 nat = kwargs.pop( 'nat', 'nat0' )
Bob Lantz64382422016-06-03 22:51:39 -0700367 nodeOpts = kwargs.pop( 'nodeOpts', {} )
Bob Lantz087b5d92016-05-06 11:39:04 -0700368 # Default: single switch with 1 ONOS node
369 if not topo:
370 topo = SingleSwitchTopo
371 if not args:
372 args = ( 1, )
373 if not isinstance( topo, Topo ):
374 topo = RenamedTopo( topo, *args, hnew='onos', **kwargs )
Bob Lantzbb37d872016-05-16 16:26:13 -0700375 self.ipBase = kwargs.pop( 'ipBase', '192.168.123.0/24' )
376 self.forward = kwargs.pop( 'forward',
377 [ KarafPort, GUIPort, OpenFlowPort ] )
Bob Lantz087b5d92016-05-06 11:39:04 -0700378 super( ONOSCluster, self ).__init__( name, inNamespace=False )
379 fixIPTables()
380 self.env = initONOSEnv()
Bob Lantzbb37d872016-05-16 16:26:13 -0700381 self.net = Mininet( topo=topo, ipBase=self.ipBase,
Bob Lantz64382422016-06-03 22:51:39 -0700382 host=partial( ONOSNode, **nodeOpts ),
383 switch=LinuxBridge,
Bob Lantz087b5d92016-05-06 11:39:04 -0700384 controller=None )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700385 if nat:
386 self.net.addNAT( nat ).configDefault()
Bob Lantz087b5d92016-05-06 11:39:04 -0700387 updateNodeIPs( self.env, self.nodes() )
388 self._remoteControllers = []
389
390 def start( self ):
391 "Start up ONOS cluster"
Bob Lantz087b5d92016-05-06 11:39:04 -0700392 info( '*** ONOS_APPS = %s\n' % ONOS_APPS )
393 self.net.start()
394 for node in self.nodes():
Bob Lantz569bbec2016-06-03 18:51:16 -0700395 node.start( self.env, self.nodes() )
Bob Lantz087b5d92016-05-06 11:39:04 -0700396 info( '\n' )
Bob Lantzbb37d872016-05-16 16:26:13 -0700397 self.configPortForwarding( ports=self.forward, action='A' )
Bob Lantz087b5d92016-05-06 11:39:04 -0700398 self.waitStarted()
399 return
400
401 def waitStarted( self ):
402 "Wait until all nodes have started"
403 startTime = time.time()
404 for node in self.nodes():
405 info( node )
406 node.waitStarted()
Bob Lantzbb37d872016-05-16 16:26:13 -0700407 info( '*** Waited %.2f seconds for ONOS startup' %
408 ( time.time() - startTime ) )
Bob Lantz087b5d92016-05-06 11:39:04 -0700409
410 def stop( self ):
411 "Shut down ONOS cluster"
Bob Lantzbb37d872016-05-16 16:26:13 -0700412 self.configPortForwarding( ports=self.forward, action='D' )
Bob Lantz087b5d92016-05-06 11:39:04 -0700413 for node in self.nodes():
414 node.stop()
415 self.net.stop()
416
417 def nodes( self ):
418 "Return list of ONOS nodes"
419 return [ h for h in self.net.hosts if isinstance( h, ONOSNode ) ]
420
Bob Lantzbb37d872016-05-16 16:26:13 -0700421 def configPortForwarding( self, ports=[], intf='eth0', action='A' ):
422 """Start or stop ports on intf to all nodes
423 action: A=add/start, D=delete/stop (default: A)"""
424 for port in ports:
425 for index, node in enumerate( self.nodes() ):
426 ip, inport = node.IP(), port + index
427 # Configure a destination NAT rule
428 cmd = ( 'iptables -t nat -{action} PREROUTING -t nat '
429 '-i {intf} -p tcp --dport {inport} '
430 '-j DNAT --to-destination {ip}:{port}' )
431 self.cmd( cmd.format( **locals() ) )
432
Bob Lantz087b5d92016-05-06 11:39:04 -0700433
434class ONOSSwitchMixin( object ):
435 "Mixin for switches that connect to an ONOSCluster"
436 def start( self, controllers ):
437 "Connect to ONOSCluster"
438 self.controllers = controllers
439 assert ( len( controllers ) is 1 and
440 isinstance( controllers[ 0 ], ONOSCluster ) )
441 clist = controllers[ 0 ].nodes()
442 return super( ONOSSwitchMixin, self ).start( clist )
443
444class ONOSOVSSwitch( ONOSSwitchMixin, OVSSwitch ):
445 "OVSSwitch that can connect to an ONOSCluster"
446 pass
447
448class ONOSUserSwitch( ONOSSwitchMixin, UserSwitch):
449 "UserSwitch that can connect to an ONOSCluster"
450 pass
451
452
453### Ugly utility routines
454
455def fixIPTables():
456 "Fix LinuxBridge warning"
457 for s in 'arp', 'ip', 'ip6':
458 quietRun( 'sysctl net.bridge.bridge-nf-call-%stables=0' % s )
459
460
461### Test code
462
463def test( serverCount ):
464 "Test this setup"
465 setLogLevel( 'info' )
466 net = Mininet( topo=SingleSwitchTopo( 3 ),
467 controller=[ ONOSCluster( 'c0', serverCount ) ],
468 switch=ONOSOVSSwitch )
469 net.start()
470 net.waitConnected()
471 CLI( net )
472 net.stop()
473
474
475### CLI Extensions
476
477OldCLI = CLI
478
479class ONOSCLI( OldCLI ):
480 "CLI Extensions for ONOS"
481
482 prompt = 'mininet-onos> '
483
484 def __init__( self, net, **kwargs ):
485 c0 = net.controllers[ 0 ]
486 if isinstance( c0, ONOSCluster ):
487 net = MininetFacade( net, cnet=c0.net )
488 OldCLI.__init__( self, net, **kwargs )
489
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700490 def onos1( self ):
491 "Helper function: return default ONOS node"
492 return self.mn.controllers[ 0 ].net.hosts[ 0 ]
493
Bob Lantz087b5d92016-05-06 11:39:04 -0700494 def do_onos( self, line ):
495 "Send command to ONOS CLI"
496 c0 = self.mn.controllers[ 0 ]
497 if isinstance( c0, ONOSCluster ):
498 # cmdLoop strips off command name 'onos'
499 if line.startswith( ':' ):
500 line = 'onos' + line
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700501 onos1 = self.onos1().name
502 if line:
503 line = '"%s"' % line
504 cmd = '%s client -h %s %s' % ( onos1, onos1, line )
Bob Lantz087b5d92016-05-06 11:39:04 -0700505 quietRun( 'stty -echo' )
506 self.default( cmd )
507 quietRun( 'stty echo' )
508
509 def do_wait( self, line ):
510 "Wait for switches to connect"
511 self.mn.waitConnected()
512
513 def do_balance( self, line ):
514 "Balance switch mastership"
515 self.do_onos( ':balance-masters' )
516
517 def do_log( self, line ):
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700518 "Run tail -f /tmp/onos1/log; press control-C to stop"
Bob Lantz9ba19dc2016-06-13 20:22:07 -0700519 self.default( '%s tail -f /tmp/%s/log' %
520 ( self.onos1(), self.onos1() ) )
Bob Lantz087b5d92016-05-06 11:39:04 -0700521
Bob Lantz64382422016-06-03 22:51:39 -0700522 def do_status( self, line ):
523 "Return status of ONOS cluster(s)"
524 for c in self.mn.controllers:
525 if isinstance( c, ONOSCluster ):
526 for node in c.net.hosts:
527 if isinstance( node, ONOSNode ):
528 errors, warnings = node.checkLog()
529 running = ( 'Running' if node.isRunning()
530 else 'Exited' )
531 status = ''
532 if errors:
533 status += '%d ERRORS ' % len( errors )
534 if warnings:
535 status += '%d warnings' % len( warnings )
536 status = status if status else 'OK'
537 info( node, '\t', running, '\t', status, '\n' )
538
Bob Lantzc96e2582016-06-13 18:57:04 -0700539 def do_arp( self, line ):
540 "Send gratuitous arps from all data network hosts"
541 startTime = time.time()
542 try:
543 count = int( line )
544 except:
545 count = 1
546 # Technically this check should be on the host
547 if '-U' not in quietRun( 'arping -h' ):
548 warn( 'Please install iputils-arping' )
549 return
550 # This is much faster if we do it in parallel
551 for host in self.mn.net.hosts:
552 intf = host.defaultIntf()
553 # -b: keep using broadcasts; -f: quit after 1 reply
554 # -U: gratuitous ARP update
555 host.sendCmd( 'arping -bf -c', count, '-U -I',
556 intf.name, intf.IP() )
557 for host in self.mn.net.hosts:
558 # We could check the output here if desired
559 host.waitOutput()
560 info( '.' )
561 info( '\n' )
562 elapsed = time.time() - startTime
563 debug( 'Completed in %.2f seconds\n' % elapsed )
564
Bob Lantz64382422016-06-03 22:51:39 -0700565
566# For interactive use, exit on error
567exitOnError = dict( nodeOpts={ 'alertAction': 'exit' } )
568ONOSClusterInteractive = specialClass( ONOSCluster, defaults=exitOnError )
569
Bob Lantz087b5d92016-05-06 11:39:04 -0700570
571### Exports for bin/mn
572
573CLI = ONOSCLI
Bob Lantz64382422016-06-03 22:51:39 -0700574controllers = { 'onos': ONOSClusterInteractive,
575 'default': ONOSClusterInteractive }
Bob Lantz087b5d92016-05-06 11:39:04 -0700576
577# XXX Hack to change default controller as above doesn't work
Bob Lantz64382422016-06-03 22:51:39 -0700578findController = lambda: controllers[ 'default' ]
Bob Lantz087b5d92016-05-06 11:39:04 -0700579
580switches = { 'onos': ONOSOVSSwitch,
581 'onosovs': ONOSOVSSwitch,
582 'onosuser': ONOSUserSwitch,
583 'default': ONOSOVSSwitch }
584
Bob Lantzbb37d872016-05-16 16:26:13 -0700585# Null topology so we can control an external/hardware network
586topos = { 'none': Topo }
587
Bob Lantz087b5d92016-05-06 11:39:04 -0700588if __name__ == '__main__':
589 if len( argv ) != 2:
590 test( 3 )
591 else:
592 test( int( argv[ 1 ] ) )