blob: 71a4e5d10ca9b83e9206ae110dd64303b54873e1 [file] [log] [blame]
Bob Lantzc7c05402013-12-16 21:22:12 -08001#!/usr/bin/env python
2
3"""
Bob Lantz7ead0532013-12-17 20:41:20 -08004onos.py: A basic (?) ONOS Controller() subclass for Mininet
Bob Lantzc7c05402013-12-16 21:22:12 -08005
6We implement the following classes:
7
8ONOSController: a custom Controller() subclass to start ONOS
9OVSSwitchONOS: a custom OVSSwitch() switch that connects to multiple controllers.
10
Bob Lantz63bbe4c2014-02-06 19:29:55 -080011We use single Zookeeper and Ramcloud instances for now.
Bob Lantzc7c05402013-12-16 21:22:12 -080012
13As a custom file, exports:
14
15--controller onos
16--switch ovso
17
18Usage:
19
Bob Lantz21b41f62013-12-18 18:16:38 -080020$ sudo -E ./onos.py
Bob Lantzc7c05402013-12-16 21:22:12 -080021
22This will start up a simple 2-host, 2 ONOS network
23
Bob Lantz21b41f62013-12-18 18:16:38 -080024$ sudo -E mn --custom onos.py --controller onos,2 --switch ovso
Bob Lantzc7c05402013-12-16 21:22:12 -080025"""
26
Bob Lantz63bbe4c2014-02-06 19:29:55 -080027from mininet.node import Controller, OVSSwitch, CPULimitedHost, RemoteController
Bob Lantzc7c05402013-12-16 21:22:12 -080028from mininet.net import Mininet
29from mininet.cli import CLI
Bob Lantz7ead0532013-12-17 20:41:20 -080030from mininet.topo import LinearTopo
31from mininet.log import setLogLevel, info, warn
Bob Lantz63bbe4c2014-02-06 19:29:55 -080032from mininet.util import quietRun, numCores
Bob Lantzc7c05402013-12-16 21:22:12 -080033
Bob Lantza0930822013-12-17 21:00:41 -080034# This should be cleaned up to avoid interfering with mn
Bob Lantzc7c05402013-12-16 21:22:12 -080035from shutil import copyfile
Bob Lantz21b41f62013-12-18 18:16:38 -080036from os import environ, path
Bob Lantzc7c05402013-12-16 21:22:12 -080037from functools import partial
Bob Lantza0930822013-12-17 21:00:41 -080038import time
Bob Lantz7ead0532013-12-17 20:41:20 -080039from sys import argv
Bob Lantz63bbe4c2014-02-06 19:29:55 -080040from time import sleep
Bob Lantzc7c05402013-12-16 21:22:12 -080041
42class ONOS( Controller ):
43 "Custom controller class for ONOS"
44
45 # Directories and configuration templates
46 home = environ[ 'HOME' ]
47 onosDir = home + "/ONOS"
48 zookeeperDir = home + "/zookeeper-3.4.5"
49 dirBase = '/tmp'
Bob Lantz63bbe4c2014-02-06 19:29:55 -080050 logDir = dirBase + '/onos-logs'
Bob Lantzc7c05402013-12-16 21:22:12 -080051 configFile = dirBase + '/onos-%s.properties'
Bob Lantz4ed6cff2013-12-17 21:43:36 -080052 logbackFile = dirBase + '/onos-%s.logback.xml'
Bob Lantz7ead0532013-12-17 20:41:20 -080053
54 # Base ONOS modules
55 baseModules = (
56 'net.floodlightcontroller.core.FloodlightProvider',
57 'net.floodlightcontroller.threadpool.ThreadPool',
Bob Lantz63bbe4c2014-02-06 19:29:55 -080058 'net.onrc.onos.ofcontroller.floodlightlistener.RCNetworkGraphPublisher',
Bob Lantz7ead0532013-12-17 20:41:20 -080059 'net.floodlightcontroller.ui.web.StaticWebRoutable',
60 'net.onrc.onos.datagrid.HazelcastDatagrid',
61 'net.onrc.onos.ofcontroller.flowmanager.FlowManager',
62 'net.onrc.onos.ofcontroller.flowprogrammer.FlowProgrammer',
63 'net.onrc.onos.ofcontroller.topology.TopologyManager',
Bob Lantz63bbe4c2014-02-06 19:29:55 -080064 'net.onrc.onos.intent.runtime.PathCalcRuntimeModule',
65 'net.onrc.onos.intent.runtime.PlanInstallModule',
Bob Lantz7ead0532013-12-17 20:41:20 -080066 'net.onrc.onos.registry.controller.ZookeeperRegistry'
67 )
68
69 # Additions for reactive forwarding
70 reactiveModules = (
71 'net.onrc.onos.ofcontroller.proxyarp.ProxyArpManager',
72 'net.onrc.onos.ofcontroller.core.config.DefaultConfiguration',
73 'net.onrc.onos.ofcontroller.forwarding.Forwarding'
74 )
75
76 # Module parameters
Bob Lantzc7c05402013-12-16 21:22:12 -080077 ofbase = 6633
Bob Lantz7ead0532013-12-17 20:41:20 -080078 restbase = 8080
79 jmxbase = 7189
Bob Lantzc7c05402013-12-16 21:22:12 -080080
Bob Lantzc7c05402013-12-16 21:22:12 -080081 fc = 'net.floodlightcontroller.'
Bob Lantzc7c05402013-12-16 21:22:12 -080082
Bob Lantz21b41f62013-12-18 18:16:38 -080083 # Things that vary per ONOS id
Bob Lantz7ead0532013-12-17 20:41:20 -080084 perNodeConfigBase = {
85 fc + 'core.FloodlightProvider.openflowport': ofbase,
86 fc + 'restserver.RestApiServer.port': restbase,
87 fc + 'core.FloodlightProvider.controllerid': 0
88 }
89
Bob Lantz21b41f62013-12-18 18:16:38 -080090 # Things that are static
Bob Lantz7ead0532013-12-17 20:41:20 -080091 staticConfig = {
Bob Lantz63bbe4c2014-02-06 19:29:55 -080092 'net.onrc.onos.ofcontroller.floodlightlistener.NetworkGraphPublisher.graph_db_store':
93 'ramcloud',
Bob Lantz7ead0532013-12-17 20:41:20 -080094 'net.floodlightcontroller.core.FloodlightProvider.workerthreads': 16,
95 'net.floodlightcontroller.forwarding.Forwarding.idletimeout': 5,
96 'net.floodlightcontroller.forwarding.Forwarding.hardtimeout': 0
97 }
98
Bob Lantz21b41f62013-12-18 18:16:38 -080099 # Things that are based on onosDir
100 dirConfig = {
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800101 'net.onrc.onos.ofcontroller.floodlightlistener.NetworkGraphPublisher.dbconf':
102 '%s/conf/ramcloud.conf',
Bob Lantz21b41f62013-12-18 18:16:38 -0800103 'net.onrc.onos.datagrid.HazelcastDatagrid.datagridConfig':
104 '%s/conf/hazelcast.xml',
105 }
106
Bob Lantz7ead0532013-12-17 20:41:20 -0800107 proctag = 'mn-onos-id'
Bob Lantzc7c05402013-12-16 21:22:12 -0800108
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800109 # List of scripts that we need/use
110 scripts = ( 'start-zk.sh', 'start-ramcloud-coordinator.sh',
111 'start-ramcloud-server.sh', 'start-onos.sh', 'start-rest.sh' )
112
Bob Lantz7a4d6102013-12-17 00:34:55 -0800113 # For maven debugging
114 # mvn = 'mvn -o -e -X'
Bob Lantzc7c05402013-12-16 21:22:12 -0800115
Bob Lantz7ead0532013-12-17 20:41:20 -0800116 def __init__( self, name, n=1, reactive=True, runAsRoot=False, **params):
Bob Lantzc7c05402013-12-16 21:22:12 -0800117 """n: number of ONOS instances to run (1)
Bob Lantz7ead0532013-12-17 20:41:20 -0800118 reactive: run in reactive mode (True)
119 runAsRoot: run ONOS as root (False)"""
Bob Lantzc7c05402013-12-16 21:22:12 -0800120 self.check()
Bob Lantzc7c05402013-12-16 21:22:12 -0800121 self.count = n
Bob Lantz7ead0532013-12-17 20:41:20 -0800122 self.reactive = reactive
123 self.runAsRoot = runAsRoot
Bob Lantzc7c05402013-12-16 21:22:12 -0800124 self.ids = range( 0, self.count )
125 Controller.__init__( self, name, **params )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800126 self.proxies = []
Bob Lantzc7c05402013-12-16 21:22:12 -0800127 # We don't need to run as root, and it can interfere
128 # with starting Zookeeper manually
Bob Lantz7ead0532013-12-17 20:41:20 -0800129 self.user = None
130 if not self.runAsRoot:
131 try:
132 self.user = quietRun( 'who am i' ).split()[ 0 ]
133 self.sendCmd( 'su', self.user )
134 self.waiting = False
135 except:
136 warn( '__init__: failed to drop privileges\n' )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800137 self.cmd( 'mkdir -p', self.logDir )
Bob Lantzc7c05402013-12-16 21:22:12 -0800138 # Need to run commands from ONOS dir
139 self.cmd( 'cd', self.onosDir )
140 self.cmd( 'export PATH=$PATH:%s' % self.onosDir )
Bob Lantz7a4d6102013-12-17 00:34:55 -0800141 if hasattr( self, 'mvn' ):
142 self.cmd( 'export MVN="%s"' % self.mvn )
Bob Lantzc7c05402013-12-16 21:22:12 -0800143
144 def check( self ):
Bob Lantz21b41f62013-12-18 18:16:38 -0800145 "Set onosDir and check for ONOS prerequisites"
Bob Lantzc7c05402013-12-16 21:22:12 -0800146 if not quietRun( 'which java' ):
147 raise Exception( 'java not found -'
148 ' make sure it is installed and in $PATH' )
149 if not quietRun( 'which mvn' ):
150 raise Exception( 'Maven (mvn) not found -'
151 ' make sure it is installed and in $PATH' )
Bob Lantz21b41f62013-12-18 18:16:38 -0800152 if 'ONOS_HOME' in environ:
153 self.onosDir = environ[ 'ONOS_HOME' ]
154 else:
155 warn( '* $ONOS_HOME is not set - assuming %s\n' % self.onosDir )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800156 for script in self.scripts:
Bob Lantz21b41f62013-12-18 18:16:38 -0800157 script = path.join( self.onosDir, script )
158 if not path.exists( script ):
159 msg = '%s not found' % script
160 if 'ONOS_HOME' not in environ:
161 msg += ' (try setting $ONOS_HOME and/or sudo -E)'
162 raise Exception( msg )
Bob Lantz7ead0532013-12-17 20:41:20 -0800163
164 def waitNetstat( self, pid ):
165 """Wait for pid to show up in netstat
166 We assume that once a process is listening on some
167 port, it is ready to go!"""
168 while True:
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800169 output = self.cmd( 'sudo netstat -natup | grep %s/' % pid )
Bob Lantz7ead0532013-12-17 20:41:20 -0800170 if output:
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800171 break
Bob Lantz7ead0532013-12-17 20:41:20 -0800172 info( '.' )
Bob Lantza0930822013-12-17 21:00:41 -0800173 time.sleep( 1 )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800174 info( '\n* Process %d is listening\n' % pid )
Bob Lantz7ead0532013-12-17 20:41:20 -0800175
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800176 def waitStart( self, procname, pattern, maxWait=10 ):
177 "Wait for proces to start up and be visible to pgrep"
Bob Lantz21b41f62013-12-18 18:16:38 -0800178 # Check script exit code
179 exitCode = int( self.cmd( 'echo $?' ) )
180 if exitCode != 0:
181 raise Exception( '%s startup failed with code %d' %
182 ( procname, exitCode ) )
Bob Lantz7ead0532013-12-17 20:41:20 -0800183 info( '* Waiting for %s startup' % procname )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800184 while True:
185 result = self.cmd( 'pgrep -f %s' % pattern )
186 if result:
187 break
188 info( '.' )
189 sleep( 1 )
190 pid = int( result.split()[ 0 ] )
191 return pid
Bob Lantz7ead0532013-12-17 20:41:20 -0800192
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800193 def startRamcloud( self, cpu=.6 ):
194 """Start Ramcloud
195 cpu: CPU usage limit (in seconds/s)"""
196 # Edit configuration file
197 self.cmd( "sed -ibak -e 's/host=.*/host=127.0.0.1/' %s/conf/ramcloud.conf" %
198 self.onosDir)
199 # Create a cgroup so Ramcloud doesn't eat all of our CPU
200 ramcloud = CPULimitedHost( 'ramcloud', inNamespace=False, period_us=5000 )
201 ramcloud.setCPUFrac( cpu / numCores() )
202 ramcloud.cmd( 'export PATH=%s:$PATH' % self.onosDir )
203 ramcloud.cmd( 'export ONOS_LOGDIR=%s' % self.logDir )
204 for daemon in 'coordinator', 'server':
205 ramcloud.cmd( 'start-ramcloud-%s.sh start' % daemon )
206 pid = self.waitStart( 'Ramcloud %s' % daemon, 'obj.master/' + daemon )
207 self.waitNetstat( pid )
208 status = self.cmd( 'start-ramcloud-%s.sh status' % daemon )
209 if 'running' not in status:
210 raise Exception( 'Ramcloud %s startup failed: ' % daemon + status )
211 self.ramcloud = ramcloud
Bob Lantzc7c05402013-12-16 21:22:12 -0800212
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800213 def stopRamcloud( self ):
214 "Stop Ramcloud"
215 for daemon in 'coordinator', 'server':
216 self.ramcloud.cmd( './start-ramcloud-%s.sh stop' % daemon )
217 self.ramcloud.terminate()
Bob Lantzc7c05402013-12-16 21:22:12 -0800218
219 def startZookeeper( self, initcfg=True ):
220 "Start Zookeeper"
221 # Reinitialize configuration file
222 if initcfg:
223 cfg = self.zookeeperDir + '/conf/zoo.cfg'
224 template = self.zookeeperDir + '/conf/zoo_sample.cfg'
225 copyfile( template, cfg )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800226 self.cmd( 'start-zk.sh stop' )
227 self.cmd( 'start-zk.sh start' )
228 pid = self.waitStart( 'Zookeeper', 'zookeeper' )
229 self.waitNetstat( pid )
Bob Lantzc7c05402013-12-16 21:22:12 -0800230 status = self.cmd( 'start-zk.sh status' )
231 if 'Error' in status:
232 raise Exception( 'Zookeeper startup failed: ' + status )
233
234 def stopZookeeper( self ):
235 "Stop Zookeeper"
236 self.cmd( 'start-zk.sh stop' )
237
Bob Lantz7ead0532013-12-17 20:41:20 -0800238 def genProperties( self, id, path='/tmp' ):
239 "Generate ONOS properties file"
240 filename = path + '/onos-%s.properties' % id
241 with open( filename, 'w' ) as f:
242 # Write modules list
243 modules = list( self.baseModules )
244 if self.reactive:
245 modules += list( self.reactiveModules )
246 f.write( 'floodlight.modules = %s\n' %
247 ',\\\n'.join( modules ) )
248 # Write other parameters
249 for var, val in self.perNodeConfigBase.iteritems():
250 if type( val ) is int:
251 val += id
252 f.write( '%s = %s\n' % ( var, val ) )
253 for var, val in self.staticConfig.iteritems():
254 f.write( '%s = %s\n' % ( var, val ) )
Bob Lantz21b41f62013-12-18 18:16:38 -0800255 for var, val in self.dirConfig.iteritems():
256 f.write( '%s = %s\n' % ( var, val % self.onosDir) )
Bob Lantz7ead0532013-12-17 20:41:20 -0800257 return filename
258
259 def setVars( self, id, propsFile ):
260 """Set and return environment vars
261 id: ONOS instance number
262 propsFile: properties file name"""
Bob Lantz4ed6cff2013-12-17 21:43:36 -0800263 # cassdir = self.cassDir % id
Bob Lantzc7c05402013-12-16 21:22:12 -0800264 logback = self.logbackFile % id
265 jmxport = self.jmxbase + id
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800266 logdir = self.logDir
267 self.cmd( 'export ONOS_LOGDIR=%s' % logdir )
268 self.cmd( 'export ONOS_LOGBASE=onos-%d.`hostname`' % id)
Bob Lantzc7c05402013-12-16 21:22:12 -0800269 self.cmd( 'export ZOO_LOG_DIR="%s"' % logdir )
Bob Lantzc7c05402013-12-16 21:22:12 -0800270 self.cmd( 'export ONOS_LOGBACK="%s"' % logback )
271 self.cmd( 'export JMX_PORT=%s' % jmxport )
Bob Lantz7ead0532013-12-17 20:41:20 -0800272 self.cmd( 'export JVM_OPTS="-D%s=%s"' % (
273 self.proctag, id ) )
274 self.cmd( 'export ONOS_PROPS="%s"' % propsFile )
Bob Lantzc7c05402013-12-16 21:22:12 -0800275
276 def startONOS( self, id ):
277 """Start ONOS
Bob Lantz7ead0532013-12-17 20:41:20 -0800278 id: new instance number"""
Bob Lantza0930822013-12-17 21:00:41 -0800279 start = time.time()
Bob Lantz7ead0532013-12-17 20:41:20 -0800280 self.stopONOS( id )
281 propsFile = self.genProperties( id )
282 self.setVars( id, propsFile )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800283 self.cmd( 'start-onos.sh startnokill' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800284 # start-onos.sh waits for ONOS startup
Bob Lantza0930822013-12-17 21:00:41 -0800285 elapsed = time.time() - start
Bob Lantz7ead0532013-12-17 20:41:20 -0800286 info( '* ONOS %s started in %.2f seconds\n' % ( id, elapsed ) )
Bob Lantzc7c05402013-12-16 21:22:12 -0800287
288 def stopONOS( self, id ):
289 """Shut down ONOS
290 id: identifier for instance"""
291 pid = self.cmd( "jps -v | grep %s=%s | awk '{print $1}'" %
292 ( self.proctag, id ) ).strip()
293 if pid:
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800294 self.cmd( 'kill', pid )
Bob Lantzc7c05402013-12-16 21:22:12 -0800295
296 def start( self, *args ):
297 "Start ONOS instances"
Bob Lantzc7c05402013-12-16 21:22:12 -0800298 info( '* Starting Zookeeper\n' )
299 self.startZookeeper()
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800300 info( '* Starting Ramcloud\n' )
301 self.startRamcloud()
Bob Lantzc7c05402013-12-16 21:22:12 -0800302 for id in self.ids:
303 info( '* Starting ONOS %s\n' % id )
304 self.startONOS( id )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800305 self.cmd( 'start-rest.sh start' )
306 # Initialize proxies for clist()
307 self.proxies = [ RemoteController( 'onos-%d' % id, port=(self.ofbase + id ) )
308 for id in range( 0, self.count ) ]
Bob Lantzc7c05402013-12-16 21:22:12 -0800309
310 def stop( self, *args ):
311 "Stop ONOS instances"
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800312 self.cmd( 'start-rest.sh stop' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800313 for id in self.ids:
314 info( '* Stopping ONOS %s\n' % id )
315 self.stopONOS( id )
Bob Lantz7ead0532013-12-17 20:41:20 -0800316 info( '* Stopping Zookeeper\n' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800317 self.stopZookeeper()
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800318 info( '* Stopping Ramcloud\n' )
319 self.stopRamcloud()
320 for p in self.proxies:
321 p.stop()
322 p.proxies = []
Bob Lantzc7c05402013-12-16 21:22:12 -0800323
324 def clist( self ):
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800325 "Return list of Controller proxies for this ONOS cluster"
326 return self.proxies
Bob Lantzc7c05402013-12-16 21:22:12 -0800327
328
329class OVSSwitchONOS( OVSSwitch ):
330 "OVS switch which connects to multiple controllers"
331 def start( self, controllers ):
Bob Lantzc7c05402013-12-16 21:22:12 -0800332 assert len( controllers ) == 1
333 c0 = controllers[ 0 ]
334 assert type( c0 ) == ONOS
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800335 controllers = c0.clist()
336 OVSSwitch.start( self, controllers )
Bob Lantzc7c05402013-12-16 21:22:12 -0800337
338
Bob Lantz7ead0532013-12-17 20:41:20 -0800339def waitConnected( switches ):
340 "Wait until all switches connect to controllers"
Bob Lantza0930822013-12-17 21:00:41 -0800341 start = time.time()
Bob Lantz7ead0532013-12-17 20:41:20 -0800342 info( '* Waiting for switches to connect...\n' )
343 for s in switches:
344 info( s )
345 while not s.connected():
346 info( '.' )
Bob Lantza0930822013-12-17 21:00:41 -0800347 time.sleep( 1 )
Bob Lantz7ead0532013-12-17 20:41:20 -0800348 info( ' ' )
Bob Lantza0930822013-12-17 21:00:41 -0800349 elapsed = time.time() - start
Bob Lantz7ead0532013-12-17 20:41:20 -0800350 info( '\n* Connected in %.2f seconds\n' % elapsed )
351
352
Bob Lantzc7c05402013-12-16 21:22:12 -0800353controllers = { 'onos': ONOS }
354switches = { 'ovso': OVSSwitchONOS }
355
356
357if __name__ == '__main__':
Bob Lantz7ead0532013-12-17 20:41:20 -0800358 # Simple test for ONOS() controller class
Bob Lantzc7c05402013-12-16 21:22:12 -0800359 setLogLevel( 'info' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800360 size = 2 if len( argv ) != 2 else int( argv[ 1 ] )
361 net = Mininet( topo=LinearTopo( size ),
Bob Lantzc7c05402013-12-16 21:22:12 -0800362 controller=partial( ONOS, n=2 ),
363 switch=OVSSwitchONOS )
364 net.start()
Bob Lantz7ead0532013-12-17 20:41:20 -0800365 waitConnected( net.switches )
Bob Lantzc7c05402013-12-16 21:22:12 -0800366 CLI( net )
367 net.stop()