blob: 947a330f0615fbb3f785b86628a4525e9b5b0294 [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 Lantz64382422016-06-03 22:51:39 -0700152def waitListening( client=None, server='127.0.0.1', port=80, timeout=None,
153 callback=None, sleepSecs=.5 ):
154 "Modified mininet.util.waitListening with callback, sleepSecs"
155 runCmd = ( client.cmd if client else
156 partial( quietRun, shell=True ) )
157 if not runCmd( 'which telnet' ):
158 raise Exception('Could not find telnet' )
159 # pylint: disable=maybe-no-member
160 serverIP = server if isinstance( server, basestring ) else server.IP()
161 cmd = ( 'echo A | telnet -e A %s %s' % ( serverIP, port ) )
162 elapsed = 0
163 result = runCmd( cmd )
164 while 'Connected' not in result:
165 if 'No route' in result:
166 rtable = runCmd( 'route' )
167 error( 'no route to %s:\n%s' % ( server, rtable ) )
168 return False
169 if timeout and elapsed >= timeout:
170 error( 'could not connect to %s on port %d\n' % ( server, port ) )
171 return False
172 debug( 'waiting for', server, 'to listen on port', port, '\n' )
173 info( '.' )
174 if callback:
175 callback()
176 time.sleep( sleepSecs )
177 elapsed += sleepSecs
178 result = runCmd( cmd )
179 return True
180
181
Bob Lantz087b5d92016-05-06 11:39:04 -0700182### Mininet classes
183
184def RenamedTopo( topo, *args, **kwargs ):
185 """Return specialized topo with renamed hosts
186 topo: topo class/class name to specialize
187 args, kwargs: topo args
188 sold: old switch name prefix (default 's')
189 snew: new switch name prefix
190 hold: old host name prefix (default 'h')
191 hnew: new host name prefix
192 This may be used from the mn command, e.g.
193 mn --topo renamed,single,spref=sw,hpref=host"""
194 sold = kwargs.pop( 'sold', 's' )
195 hold = kwargs.pop( 'hold', 'h' )
196 snew = kwargs.pop( 'snew', 'cs' )
197 hnew = kwargs.pop( 'hnew' ,'ch' )
198 topos = {} # TODO: use global TOPOS dict
199 if isinstance( topo, str ):
200 # Look up in topo directory - this allows us to
201 # use RenamedTopo from the command line!
202 if topo in topos:
203 topo = topos.get( topo )
204 else:
205 raise Exception( 'Unknown topo name: %s' % topo )
206 # pylint: disable=no-init
207 class RenamedTopoCls( topo ):
208 "Topo subclass with renamed nodes"
209 def addNode( self, name, *args, **kwargs ):
210 "Add a node, renaming if necessary"
211 if name.startswith( sold ):
212 name = snew + name[ len( sold ): ]
213 elif name.startswith( hold ):
214 name = hnew + name[ len( hold ): ]
215 return topo.addNode( self, name, *args, **kwargs )
216 return RenamedTopoCls( *args, **kwargs )
217
218
219class ONOSNode( Controller ):
220 "ONOS cluster node"
221
Bob Lantz087b5d92016-05-06 11:39:04 -0700222 def __init__( self, name, **kwargs ):
Bob Lantz64382422016-06-03 22:51:39 -0700223 "alertAction: exception|ignore|warn|exit (exception)"
Bob Lantz087b5d92016-05-06 11:39:04 -0700224 kwargs.update( inNamespace=True )
Bob Lantz64382422016-06-03 22:51:39 -0700225 self.alertAction = kwargs.pop( 'alertAction', 'exception' )
Bob Lantz087b5d92016-05-06 11:39:04 -0700226 Controller.__init__( self, name, **kwargs )
227 self.dir = '/tmp/%s' % self.name
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700228 self.client = self.dir + '/karaf/bin/client'
Bob Lantz087b5d92016-05-06 11:39:04 -0700229 self.ONOS_HOME = '/tmp'
230
231 # pylint: disable=arguments-differ
232
Bob Lantz569bbec2016-06-03 18:51:16 -0700233 def start( self, env, nodes=() ):
Bob Lantz087b5d92016-05-06 11:39:04 -0700234 """Start ONOS on node
Bob Lantz569bbec2016-06-03 18:51:16 -0700235 env: environment var dict
236 nodes: all nodes in cluster"""
Bob Lantz087b5d92016-05-06 11:39:04 -0700237 env = dict( env )
238 self.cmd( 'rm -rf', self.dir )
Bob Lantz1451d722016-05-17 14:40:07 -0700239 self.ONOS_HOME = unpackONOS( self.dir, run=self.ucmd )
Bob Lantz087b5d92016-05-06 11:39:04 -0700240 env.update( ONOS_HOME=self.ONOS_HOME )
241 self.updateEnv( env )
242 karafbin = glob( '%s/apache*/bin' % self.ONOS_HOME )[ 0 ]
243 onosbin = join( ONOS_ROOT, 'tools/test/bin' )
244 self.cmd( 'export PATH=%s:%s:$PATH' % ( onosbin, karafbin ) )
245 self.cmd( 'cd', self.ONOS_HOME )
Bob Lantz1451d722016-05-17 14:40:07 -0700246 self.ucmd( 'mkdir -p config && '
Bob Lantz569bbec2016-06-03 18:51:16 -0700247 'onos-gen-partitions config/cluster.json',
248 ' '.join( node.IP() for node in nodes ) )
Bob Lantz087b5d92016-05-06 11:39:04 -0700249 info( '(starting %s)' % self )
250 service = join( self.ONOS_HOME, 'bin/onos-service' )
Bob Lantz1451d722016-05-17 14:40:07 -0700251 self.ucmd( service, 'server 1>../onos.log 2>../onos.log'
252 ' & echo $! > onos.pid; ln -s `pwd`/onos.pid ..' )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700253 self.onosPid = int( self.cmd( 'cat onos.pid' ).strip() )
Bob Lantz64382422016-06-03 22:51:39 -0700254 self.warningCount = 0
Bob Lantz087b5d92016-05-06 11:39:04 -0700255
256 # pylint: enable=arguments-differ
257
258 def stop( self ):
259 # XXX This will kill all karafs - too bad!
260 self.cmd( 'pkill -HUP -f karaf.jar && wait' )
261 self.cmd( 'rm -rf', self.dir )
262
Bob Lantz64382422016-06-03 22:51:39 -0700263 def sanityAlert( self, *args ):
264 "Alert to raise on sanityCheck failure"
265 info( '\n' )
266 if self.alertAction == 'exception':
267 raise Exception( *args )
268 if self.alertAction == 'warn':
269 warn( *args + ( '\n', ) )
270 elif self.alertAction == 'exit':
271 error( '***', *args +
272 ( '\nExiting. Run "sudo mn -c" to clean up.\n', ) )
273 exit( 1 )
274
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700275 def isRunning( self ):
276 "Is our ONOS process still running?"
Bob Lantz64382422016-06-03 22:51:39 -0700277 cmd = ( 'ps -p %d >/dev/null 2>&1 && echo "running" ||'
278 'echo "not running"' )
279 return self.cmd( cmd % self.onosPid ).strip() == 'running'
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700280
Bob Lantz64382422016-06-03 22:51:39 -0700281 def checkLog( self ):
282 "Return log file errors and warnings"
283 log = join( self.dir, 'log' )
Bob Lantzc96e2582016-06-13 18:57:04 -0700284 errors, warnings = [], []
Bob Lantz64382422016-06-03 22:51:39 -0700285 if isfile( log ):
286 lines = open( log ).read().split( '\n' )
287 errors = [ line for line in lines if 'ERROR' in line ]
288 warnings = [ line for line in lines if 'WARN'in line ]
289 return errors, warnings
290
291 def memAvailable( self ):
292 "Return available memory in KB (or -1 if we can't tell)"
293 lines = open( '/proc/meminfo' ).read().strip().split( '\n' )
294 entries = map( str.split, lines )
295 index = { entry[ 0 ]: entry for entry in entries }
296 # Check MemAvailable if present
297 default = ( None, '-1', 'kB' )
298 _name, count, unit = index.get( 'MemAvailable:', default )
299 if unit.lower() == 'kb':
300 return int( count )
301 return -1
302
303 def sanityCheck( self, lowMem=100000 ):
304 """Check whether we've quit or are running out of memory
305 lowMem: low memory threshold in KB (100000)"""
306 # Are we still running?
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700307 if not self.isRunning():
Bob Lantz64382422016-06-03 22:51:39 -0700308 self.sanityAlert( 'ONOS node %s has died' % self.name )
309 # Are there errors in the log file?
310 errors, warnings = self.checkLog()
311 if errors:
312 self.sanityAlert( 'ONOS startup errors:\n<<%s>>' %
313 '\n'.join( errors ) )
314 warningCount = len( warnings )
315 if warnings and warningCount > self.warningCount:
316 warn( '(%d warnings)' % len( warnings ) )
317 self.warningCount = warningCount
318 # Are we running out of memory?
319 mem = self.memAvailable()
320 if mem > 0 and mem < lowMem:
321 self.sanityAlert( 'Running out of memory (only %d KB available)'
322 % mem )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700323
Bob Lantz087b5d92016-05-06 11:39:04 -0700324 def waitStarted( self ):
325 "Wait until we've really started"
326 info( '(checking: karaf' )
327 while True:
Bob Lantz1451d722016-05-17 14:40:07 -0700328 status = self.ucmd( 'karaf status' ).lower()
Bob Lantz087b5d92016-05-06 11:39:04 -0700329 if 'running' in status and 'not running' not in status:
330 break
331 info( '.' )
Bob Lantz64382422016-06-03 22:51:39 -0700332 self.sanityCheck()
Bob Lantz087b5d92016-05-06 11:39:04 -0700333 time.sleep( 1 )
334 info( ' ssh-port' )
Bob Lantz64382422016-06-03 22:51:39 -0700335 waitListening( server=self, port=KarafPort, callback=self.sanityCheck )
Bob Lantz087b5d92016-05-06 11:39:04 -0700336 info( ' openflow-port' )
Bob Lantz64382422016-06-03 22:51:39 -0700337 waitListening( server=self, port=OpenFlowPort,
338 callback=self.sanityCheck )
Bob Lantz087b5d92016-05-06 11:39:04 -0700339 info( ' client' )
340 while True:
Bob Lantzbb37d872016-05-16 16:26:13 -0700341 result = quietRun( 'echo apps -a | %s -h %s' %
342 ( self.client, self.IP() ), shell=True )
Bob Lantz087b5d92016-05-06 11:39:04 -0700343 if 'openflow' in result:
344 break
345 info( '.' )
Bob Lantz64382422016-06-03 22:51:39 -0700346 self.sanityCheck()
Bob Lantz087b5d92016-05-06 11:39:04 -0700347 time.sleep( 1 )
348 info( ')\n' )
349
350 def updateEnv( self, envDict ):
351 "Update environment variables"
Bob Lantz569bbec2016-06-03 18:51:16 -0700352 cmd = ';'.join( ( 'export %s="%s"' % ( var, val )
353 if val else 'unset %s' % var )
Bob Lantz087b5d92016-05-06 11:39:04 -0700354 for var, val in envDict.iteritems() )
355 self.cmd( cmd )
356
Bob Lantz1451d722016-05-17 14:40:07 -0700357 def ucmd( self, *args, **_kwargs ):
358 "Run command as $ONOS_USER using sudo -E -u"
359 if ONOS_USER != 'root': # don't bother with sudo
360 args = [ "sudo -E -u $ONOS_USER PATH=$PATH "
361 "bash -c '%s'" % ' '.join( args ) ]
362 return self.cmd( *args )
363
Bob Lantz087b5d92016-05-06 11:39:04 -0700364
365class ONOSCluster( Controller ):
366 "ONOS Cluster"
367 def __init__( self, *args, **kwargs ):
368 """name: (first parameter)
369 *args: topology class parameters
370 ipBase: IP range for ONOS nodes
Bob Lantzbb37d872016-05-16 16:26:13 -0700371 forward: default port forwarding list,
Bob Lantz087b5d92016-05-06 11:39:04 -0700372 topo: topology class or instance
Bob Lantz64382422016-06-03 22:51:39 -0700373 nodeOpts: ONOSNode options
Bob Lantz087b5d92016-05-06 11:39:04 -0700374 **kwargs: additional topology parameters"""
375 args = list( args )
376 name = args.pop( 0 )
377 topo = kwargs.pop( 'topo', None )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700378 nat = kwargs.pop( 'nat', 'nat0' )
Bob Lantz64382422016-06-03 22:51:39 -0700379 nodeOpts = kwargs.pop( 'nodeOpts', {} )
Bob Lantz087b5d92016-05-06 11:39:04 -0700380 # Default: single switch with 1 ONOS node
381 if not topo:
382 topo = SingleSwitchTopo
383 if not args:
384 args = ( 1, )
385 if not isinstance( topo, Topo ):
386 topo = RenamedTopo( topo, *args, hnew='onos', **kwargs )
Bob Lantzbb37d872016-05-16 16:26:13 -0700387 self.ipBase = kwargs.pop( 'ipBase', '192.168.123.0/24' )
388 self.forward = kwargs.pop( 'forward',
389 [ KarafPort, GUIPort, OpenFlowPort ] )
Bob Lantz087b5d92016-05-06 11:39:04 -0700390 super( ONOSCluster, self ).__init__( name, inNamespace=False )
391 fixIPTables()
392 self.env = initONOSEnv()
Bob Lantzbb37d872016-05-16 16:26:13 -0700393 self.net = Mininet( topo=topo, ipBase=self.ipBase,
Bob Lantz64382422016-06-03 22:51:39 -0700394 host=partial( ONOSNode, **nodeOpts ),
395 switch=LinuxBridge,
Bob Lantz087b5d92016-05-06 11:39:04 -0700396 controller=None )
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700397 if nat:
398 self.net.addNAT( nat ).configDefault()
Bob Lantz087b5d92016-05-06 11:39:04 -0700399 updateNodeIPs( self.env, self.nodes() )
400 self._remoteControllers = []
401
402 def start( self ):
403 "Start up ONOS cluster"
Bob Lantz087b5d92016-05-06 11:39:04 -0700404 info( '*** ONOS_APPS = %s\n' % ONOS_APPS )
405 self.net.start()
406 for node in self.nodes():
Bob Lantz569bbec2016-06-03 18:51:16 -0700407 node.start( self.env, self.nodes() )
Bob Lantz087b5d92016-05-06 11:39:04 -0700408 info( '\n' )
Bob Lantzbb37d872016-05-16 16:26:13 -0700409 self.configPortForwarding( ports=self.forward, action='A' )
Bob Lantz087b5d92016-05-06 11:39:04 -0700410 self.waitStarted()
411 return
412
413 def waitStarted( self ):
414 "Wait until all nodes have started"
415 startTime = time.time()
416 for node in self.nodes():
417 info( node )
418 node.waitStarted()
Bob Lantzbb37d872016-05-16 16:26:13 -0700419 info( '*** Waited %.2f seconds for ONOS startup' %
420 ( time.time() - startTime ) )
Bob Lantz087b5d92016-05-06 11:39:04 -0700421
422 def stop( self ):
423 "Shut down ONOS cluster"
Bob Lantzbb37d872016-05-16 16:26:13 -0700424 self.configPortForwarding( ports=self.forward, action='D' )
Bob Lantz087b5d92016-05-06 11:39:04 -0700425 for node in self.nodes():
426 node.stop()
427 self.net.stop()
428
429 def nodes( self ):
430 "Return list of ONOS nodes"
431 return [ h for h in self.net.hosts if isinstance( h, ONOSNode ) ]
432
Bob Lantzbb37d872016-05-16 16:26:13 -0700433 def configPortForwarding( self, ports=[], intf='eth0', action='A' ):
434 """Start or stop ports on intf to all nodes
435 action: A=add/start, D=delete/stop (default: A)"""
436 for port in ports:
437 for index, node in enumerate( self.nodes() ):
438 ip, inport = node.IP(), port + index
439 # Configure a destination NAT rule
440 cmd = ( 'iptables -t nat -{action} PREROUTING -t nat '
441 '-i {intf} -p tcp --dport {inport} '
442 '-j DNAT --to-destination {ip}:{port}' )
443 self.cmd( cmd.format( **locals() ) )
444
Bob Lantz087b5d92016-05-06 11:39:04 -0700445
446class ONOSSwitchMixin( object ):
447 "Mixin for switches that connect to an ONOSCluster"
448 def start( self, controllers ):
449 "Connect to ONOSCluster"
450 self.controllers = controllers
451 assert ( len( controllers ) is 1 and
452 isinstance( controllers[ 0 ], ONOSCluster ) )
453 clist = controllers[ 0 ].nodes()
454 return super( ONOSSwitchMixin, self ).start( clist )
455
456class ONOSOVSSwitch( ONOSSwitchMixin, OVSSwitch ):
457 "OVSSwitch that can connect to an ONOSCluster"
458 pass
459
460class ONOSUserSwitch( ONOSSwitchMixin, UserSwitch):
461 "UserSwitch that can connect to an ONOSCluster"
462 pass
463
464
465### Ugly utility routines
466
467def fixIPTables():
468 "Fix LinuxBridge warning"
469 for s in 'arp', 'ip', 'ip6':
470 quietRun( 'sysctl net.bridge.bridge-nf-call-%stables=0' % s )
471
472
473### Test code
474
475def test( serverCount ):
476 "Test this setup"
477 setLogLevel( 'info' )
478 net = Mininet( topo=SingleSwitchTopo( 3 ),
479 controller=[ ONOSCluster( 'c0', serverCount ) ],
480 switch=ONOSOVSSwitch )
481 net.start()
482 net.waitConnected()
483 CLI( net )
484 net.stop()
485
486
487### CLI Extensions
488
489OldCLI = CLI
490
491class ONOSCLI( OldCLI ):
492 "CLI Extensions for ONOS"
493
494 prompt = 'mininet-onos> '
495
496 def __init__( self, net, **kwargs ):
497 c0 = net.controllers[ 0 ]
498 if isinstance( c0, ONOSCluster ):
499 net = MininetFacade( net, cnet=c0.net )
500 OldCLI.__init__( self, net, **kwargs )
501
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700502 def onos1( self ):
503 "Helper function: return default ONOS node"
504 return self.mn.controllers[ 0 ].net.hosts[ 0 ]
505
Bob Lantz087b5d92016-05-06 11:39:04 -0700506 def do_onos( self, line ):
507 "Send command to ONOS CLI"
508 c0 = self.mn.controllers[ 0 ]
509 if isinstance( c0, ONOSCluster ):
510 # cmdLoop strips off command name 'onos'
511 if line.startswith( ':' ):
512 line = 'onos' + line
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700513 onos1 = self.onos1().name
514 if line:
515 line = '"%s"' % line
516 cmd = '%s client -h %s %s' % ( onos1, onos1, line )
Bob Lantz087b5d92016-05-06 11:39:04 -0700517 quietRun( 'stty -echo' )
518 self.default( cmd )
519 quietRun( 'stty echo' )
520
521 def do_wait( self, line ):
522 "Wait for switches to connect"
523 self.mn.waitConnected()
524
525 def do_balance( self, line ):
526 "Balance switch mastership"
527 self.do_onos( ':balance-masters' )
528
529 def do_log( self, line ):
Bob Lantz4b51d5c2016-05-27 14:47:38 -0700530 "Run tail -f /tmp/onos1/log; press control-C to stop"
531 self.default( self.onos1().name, 'tail -f /tmp/%s/log' % self.onos1() )
Bob Lantz087b5d92016-05-06 11:39:04 -0700532
Bob Lantz64382422016-06-03 22:51:39 -0700533 def do_status( self, line ):
534 "Return status of ONOS cluster(s)"
535 for c in self.mn.controllers:
536 if isinstance( c, ONOSCluster ):
537 for node in c.net.hosts:
538 if isinstance( node, ONOSNode ):
539 errors, warnings = node.checkLog()
540 running = ( 'Running' if node.isRunning()
541 else 'Exited' )
542 status = ''
543 if errors:
544 status += '%d ERRORS ' % len( errors )
545 if warnings:
546 status += '%d warnings' % len( warnings )
547 status = status if status else 'OK'
548 info( node, '\t', running, '\t', status, '\n' )
549
Bob Lantzc96e2582016-06-13 18:57:04 -0700550 def do_arp( self, line ):
551 "Send gratuitous arps from all data network hosts"
552 startTime = time.time()
553 try:
554 count = int( line )
555 except:
556 count = 1
557 # Technically this check should be on the host
558 if '-U' not in quietRun( 'arping -h' ):
559 warn( 'Please install iputils-arping' )
560 return
561 # This is much faster if we do it in parallel
562 for host in self.mn.net.hosts:
563 intf = host.defaultIntf()
564 # -b: keep using broadcasts; -f: quit after 1 reply
565 # -U: gratuitous ARP update
566 host.sendCmd( 'arping -bf -c', count, '-U -I',
567 intf.name, intf.IP() )
568 for host in self.mn.net.hosts:
569 # We could check the output here if desired
570 host.waitOutput()
571 info( '.' )
572 info( '\n' )
573 elapsed = time.time() - startTime
574 debug( 'Completed in %.2f seconds\n' % elapsed )
575
Bob Lantz64382422016-06-03 22:51:39 -0700576
577# For interactive use, exit on error
578exitOnError = dict( nodeOpts={ 'alertAction': 'exit' } )
579ONOSClusterInteractive = specialClass( ONOSCluster, defaults=exitOnError )
580
Bob Lantz087b5d92016-05-06 11:39:04 -0700581
582### Exports for bin/mn
583
584CLI = ONOSCLI
Bob Lantz64382422016-06-03 22:51:39 -0700585controllers = { 'onos': ONOSClusterInteractive,
586 'default': ONOSClusterInteractive }
Bob Lantz087b5d92016-05-06 11:39:04 -0700587
588# XXX Hack to change default controller as above doesn't work
Bob Lantz64382422016-06-03 22:51:39 -0700589findController = lambda: controllers[ 'default' ]
Bob Lantz087b5d92016-05-06 11:39:04 -0700590
591switches = { 'onos': ONOSOVSSwitch,
592 'onosovs': ONOSOVSSwitch,
593 'onosuser': ONOSUserSwitch,
594 'default': ONOSOVSSwitch }
595
Bob Lantzbb37d872016-05-16 16:26:13 -0700596# Null topology so we can control an external/hardware network
597topos = { 'none': Topo }
598
Bob Lantz087b5d92016-05-06 11:39:04 -0700599if __name__ == '__main__':
600 if len( argv ) != 2:
601 test( 3 )
602 else:
603 test( int( argv[ 1 ] ) )