blob: 9a34c5bbc2c961db03899906fbb158d568763b57 [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 Lantz4ed6cff2013-12-17 21:43:36 -080051 logbackFile = dirBase + '/onos-%s.logback.xml'
Bob Lantz7ead0532013-12-17 20:41:20 -080052
Bob Lantz7ead0532013-12-17 20:41:20 -080053 # Additions for reactive forwarding
54 reactiveModules = (
Jonathan Hart0961fe82014-04-03 09:56:25 -070055 'net.onrc.onos.apps.proxyarp.ProxyArpManager',
Jonathan Hart51f6f5b2014-04-03 10:32:10 -070056 'net.onrc.onos.core.main.config.DefaultConfiguration',
Jonathan Hart0961fe82014-04-03 09:56:25 -070057 'net.onrc.onos.apps.forwarding.Forwarding'
Bob Lantz7ead0532013-12-17 20:41:20 -080058 )
59
60 # Module parameters
Bob Lantzc7c05402013-12-16 21:22:12 -080061 ofbase = 6633
Bob Lantz7ead0532013-12-17 20:41:20 -080062 restbase = 8080
63 jmxbase = 7189
Naoki Shiotad8ea71a2014-04-23 17:04:51 -070064 hcbase = 5701
Bob Lantzc7c05402013-12-16 21:22:12 -080065
Bob Lantzc7c05402013-12-16 21:22:12 -080066 fc = 'net.floodlightcontroller.'
Bob Lantzc7c05402013-12-16 21:22:12 -080067
Bob Lantz21b41f62013-12-18 18:16:38 -080068 # Things that vary per ONOS id
Bob Lantz7ead0532013-12-17 20:41:20 -080069 perNodeConfigBase = {
70 fc + 'core.FloodlightProvider.openflowport': ofbase,
71 fc + 'restserver.RestApiServer.port': restbase,
72 fc + 'core.FloodlightProvider.controllerid': 0
73 }
74
Bob Lantz7ead0532013-12-17 20:41:20 -080075 proctag = 'mn-onos-id'
Bob Lantzc7c05402013-12-16 21:22:12 -080076
Bob Lantz63bbe4c2014-02-06 19:29:55 -080077 # List of scripts that we need/use
Naoki Shiotabe433a12014-04-07 12:03:49 -070078# scripts = ( 'start-zk.sh', 'start-ramcloud-coordinator.sh',
79# 'start-ramcloud-server.sh', 'start-onos.sh', 'start-rest.sh' )
80 scripts = ( 'onos.sh', 'start-rest.sh' )
Bob Lantz63bbe4c2014-02-06 19:29:55 -080081
Bob Lantz7ead0532013-12-17 20:41:20 -080082 def __init__( self, name, n=1, reactive=True, runAsRoot=False, **params):
Bob Lantzc7c05402013-12-16 21:22:12 -080083 """n: number of ONOS instances to run (1)
Bob Lantz7ead0532013-12-17 20:41:20 -080084 reactive: run in reactive mode (True)
85 runAsRoot: run ONOS as root (False)"""
Bob Lantzc7c05402013-12-16 21:22:12 -080086 self.check()
Bob Lantzc7c05402013-12-16 21:22:12 -080087 self.count = n
Bob Lantz7ead0532013-12-17 20:41:20 -080088 self.reactive = reactive
89 self.runAsRoot = runAsRoot
Bob Lantzc7c05402013-12-16 21:22:12 -080090 self.ids = range( 0, self.count )
91 Controller.__init__( self, name, **params )
Bob Lantz63bbe4c2014-02-06 19:29:55 -080092 self.proxies = []
Bob Lantzc7c05402013-12-16 21:22:12 -080093 # We don't need to run as root, and it can interfere
94 # with starting Zookeeper manually
Bob Lantz7ead0532013-12-17 20:41:20 -080095 self.user = None
96 if not self.runAsRoot:
97 try:
98 self.user = quietRun( 'who am i' ).split()[ 0 ]
99 self.sendCmd( 'su', self.user )
100 self.waiting = False
101 except:
102 warn( '__init__: failed to drop privileges\n' )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800103 self.cmd( 'mkdir -p', self.logDir )
Bob Lantzc7c05402013-12-16 21:22:12 -0800104 # Need to run commands from ONOS dir
105 self.cmd( 'cd', self.onosDir )
106 self.cmd( 'export PATH=$PATH:%s' % self.onosDir )
Bob Lantzc7c05402013-12-16 21:22:12 -0800107
108 def check( self ):
Bob Lantz21b41f62013-12-18 18:16:38 -0800109 "Set onosDir and check for ONOS prerequisites"
Bob Lantzc7c05402013-12-16 21:22:12 -0800110 if not quietRun( 'which java' ):
111 raise Exception( 'java not found -'
112 ' make sure it is installed and in $PATH' )
Bob Lantz21b41f62013-12-18 18:16:38 -0800113 if 'ONOS_HOME' in environ:
114 self.onosDir = environ[ 'ONOS_HOME' ]
115 else:
116 warn( '* $ONOS_HOME is not set - assuming %s\n' % self.onosDir )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800117 for script in self.scripts:
Bob Lantz21b41f62013-12-18 18:16:38 -0800118 script = path.join( self.onosDir, script )
119 if not path.exists( script ):
120 msg = '%s not found' % script
121 if 'ONOS_HOME' not in environ:
122 msg += ' (try setting $ONOS_HOME and/or sudo -E)'
123 raise Exception( msg )
Bob Lantz7ead0532013-12-17 20:41:20 -0800124
125 def waitNetstat( self, pid ):
126 """Wait for pid to show up in netstat
127 We assume that once a process is listening on some
128 port, it is ready to go!"""
129 while True:
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800130 output = self.cmd( 'sudo netstat -natup | grep %s/' % pid )
Bob Lantz7ead0532013-12-17 20:41:20 -0800131 if output:
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800132 break
Bob Lantz7ead0532013-12-17 20:41:20 -0800133 info( '.' )
Bob Lantza0930822013-12-17 21:00:41 -0800134 time.sleep( 1 )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800135 info( '\n* Process %d is listening\n' % pid )
Bob Lantz7ead0532013-12-17 20:41:20 -0800136
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800137 def waitStart( self, procname, pattern, maxWait=10 ):
138 "Wait for proces to start up and be visible to pgrep"
Bob Lantz21b41f62013-12-18 18:16:38 -0800139 # Check script exit code
140 exitCode = int( self.cmd( 'echo $?' ) )
141 if exitCode != 0:
142 raise Exception( '%s startup failed with code %d' %
143 ( procname, exitCode ) )
Bob Lantz7ead0532013-12-17 20:41:20 -0800144 info( '* Waiting for %s startup' % procname )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800145 while True:
146 result = self.cmd( 'pgrep -f %s' % pattern )
147 if result:
148 break
149 info( '.' )
150 sleep( 1 )
151 pid = int( result.split()[ 0 ] )
152 return pid
Bob Lantz7ead0532013-12-17 20:41:20 -0800153
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800154 def startRamcloud( self, cpu=.6 ):
155 """Start Ramcloud
156 cpu: CPU usage limit (in seconds/s)"""
157 # Edit configuration file
Naoki Shiotabe433a12014-04-07 12:03:49 -0700158# self.cmd( "sed -ibak -e 's/host=.*/host=127.0.0.1/' %s/conf/ramcloud.conf" %
159# self.onosDir)
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800160 # Create a cgroup so Ramcloud doesn't eat all of our CPU
161 ramcloud = CPULimitedHost( 'ramcloud', inNamespace=False, period_us=5000 )
162 ramcloud.setCPUFrac( cpu / numCores() )
163 ramcloud.cmd( 'export PATH=%s:$PATH' % self.onosDir )
164 ramcloud.cmd( 'export ONOS_LOGDIR=%s' % self.logDir )
Naoki Shiotabe433a12014-04-07 12:03:49 -0700165 for daemon in 'coord', 'server':
166 ramcloud.cmd( 'onos.sh rc-%s start' % daemon )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800167 pid = self.waitStart( 'Ramcloud %s' % daemon, 'obj.master/' + daemon )
168 self.waitNetstat( pid )
Naoki Shiotabe433a12014-04-07 12:03:49 -0700169 status = self.cmd( 'onos.sh rc-%s.sh status' % daemon )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800170 if 'running' not in status:
171 raise Exception( 'Ramcloud %s startup failed: ' % daemon + status )
172 self.ramcloud = ramcloud
Bob Lantzc7c05402013-12-16 21:22:12 -0800173
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800174 def stopRamcloud( self ):
175 "Stop Ramcloud"
Naoki Shiotabe433a12014-04-07 12:03:49 -0700176 for daemon in 'coord', 'server':
177 self.ramcloud.cmd( './onos.sh rc-%s stop' % daemon )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800178 self.ramcloud.terminate()
Bob Lantzc7c05402013-12-16 21:22:12 -0800179
180 def startZookeeper( self, initcfg=True ):
181 "Start Zookeeper"
Naoki Shiotabe433a12014-04-07 12:03:49 -0700182# # Reinitialize configuration file
183# if initcfg:
184# cfg = self.zookeeperDir + '/conf/zoo.cfg'
185# template = self.zookeeperDir + '/conf/zoo_sample.cfg'
186# copyfile( template, cfg )
187 self.cmd( 'onos.sh zk start' )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800188 pid = self.waitStart( 'Zookeeper', 'zookeeper' )
189 self.waitNetstat( pid )
Naoki Shiotabe433a12014-04-07 12:03:49 -0700190 status = self.cmd( 'onos.sh zk status' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800191 if 'Error' in status:
192 raise Exception( 'Zookeeper startup failed: ' + status )
193
194 def stopZookeeper( self ):
195 "Stop Zookeeper"
Naoki Shiotabe433a12014-04-07 12:03:49 -0700196 self.cmd( 'onos.sh zk stop' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800197
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700198 def getPropsFilename( self, id, path ):
199 return path + '/onos-%s.properties' % id
200
Bob Lantz7ead0532013-12-17 20:41:20 -0800201 def genProperties( self, id, path='/tmp' ):
Bob Lantz42543db2014-02-24 16:07:34 -0800202 "Generate ONOS properties file and return its full pathname"
203 defaultProps = self.onosDir + '/conf/onos.properties'
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700204 propsFile = self.getPropsFilename( id, path )
Bob Lantz42543db2014-02-24 16:07:34 -0800205 with open( propsFile, 'w' ) as f:
206 with open( defaultProps ) as d:
207 for line in d.readlines():
208 prop = line.split( ' ' )[ 0 ]
209 val = self.perNodeConfigBase.get( prop, None )
210 if val:
211 # Write updated property
212 f.write( '%s = %s\n' % ( prop, val + id) )
213 else:
214 # Write original property
215 f.write( line )
216 if prop == 'floodlight.modules' and ',\\' in line:
217 if self.reactive:
218 # Insert reactive modules into list
219 for module in self.reactiveModules:
220 f.write( '%s,\\\n' % module )
221 return propsFile
Bob Lantz7ead0532013-12-17 20:41:20 -0800222
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700223 def getConfsFilename( self, id, path ):
224 return path + '/onos-%s.conf' % id
225
226 def genConfig( self, id, path='/tmp' ):
227 "Generate ONOS node config file and return its full pathname"
228 confsFile = self.getConfsFilename( id, path )
229 with open( confsFile, 'w' ) as f:
230 f.write( 'host.ip = 127.0.0.1\n' )
231 f.write( 'host.backend = ramcloud\n' )
232 f.write( 'hazelcast.host.port = %s\n' % ( self.hcbase + 10 * id ) )
233 return confsFile
234
235 def setVarsGlobal( self, path='/tmp'):
236 logdir = self.logDir
237 self.cmd( 'export ONOS_LOGDIR=%s' % logdir )
238 self.cmd( 'export ZK_LOG_DIR=%s' % logdir )
239
240 def setVarsLocal( self, id, path='/tmp' ):
Bob Lantz7ead0532013-12-17 20:41:20 -0800241 """Set and return environment vars
242 id: ONOS instance number
243 propsFile: properties file name"""
Bob Lantz4ed6cff2013-12-17 21:43:36 -0800244 # cassdir = self.cassDir % id
Bob Lantzc7c05402013-12-16 21:22:12 -0800245 logback = self.logbackFile % id
246 jmxport = self.jmxbase + id
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800247 logdir = self.logDir
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800248 self.cmd( 'export ONOS_LOGBASE=onos-%d.`hostname`' % id)
Bob Lantzc7c05402013-12-16 21:22:12 -0800249 self.cmd( 'export ONOS_LOGBACK="%s"' % logback )
250 self.cmd( 'export JMX_PORT=%s' % jmxport )
Bob Lantz7ead0532013-12-17 20:41:20 -0800251 self.cmd( 'export JVM_OPTS="-D%s=%s"' % (
252 self.proctag, id ) )
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700253 propsFile = self.getPropsFilename( id, path )
Bob Lantz7ead0532013-12-17 20:41:20 -0800254 self.cmd( 'export ONOS_PROPS="%s"' % propsFile )
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700255 confsFile = self.getConfsFilename( id, path )
256 self.cmd( 'export ONOS_CONF="%s"' % confsFile )
257 self.cmd( 'export HC_CONF="%s/hazelcast.%s.conf"' % ( path, id ) )
Bob Lantzc7c05402013-12-16 21:22:12 -0800258
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700259 def setupONOS (self, id):
260 propsFile = self.genProperties( id )
261 confFile = self.genConfig( id )
262 self.setVarsLocal( id )
263 self.cmd( 'onos.sh setup -f' )
264
Bob Lantzc7c05402013-12-16 21:22:12 -0800265 def startONOS( self, id ):
266 """Start ONOS
Bob Lantz7ead0532013-12-17 20:41:20 -0800267 id: new instance number"""
Bob Lantza0930822013-12-17 21:00:41 -0800268 start = time.time()
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700269 self.setVarsLocal( id )
Bob Lantz7ead0532013-12-17 20:41:20 -0800270 self.stopONOS( id )
Naoki Shiotabe433a12014-04-07 12:03:49 -0700271 self.cmd( 'onos.sh core startnokill' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800272 # start-onos.sh waits for ONOS startup
Bob Lantza0930822013-12-17 21:00:41 -0800273 elapsed = time.time() - start
Bob Lantz7ead0532013-12-17 20:41:20 -0800274 info( '* ONOS %s started in %.2f seconds\n' % ( id, elapsed ) )
Bob Lantzc7c05402013-12-16 21:22:12 -0800275
276 def stopONOS( self, id ):
277 """Shut down ONOS
278 id: identifier for instance"""
279 pid = self.cmd( "jps -v | grep %s=%s | awk '{print $1}'" %
280 ( self.proctag, id ) ).strip()
281 if pid:
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800282 self.cmd( 'kill', pid )
Bob Lantzc7c05402013-12-16 21:22:12 -0800283
284 def start( self, *args ):
285 "Start ONOS instances"
Naoki Shiotad8ea71a2014-04-23 17:04:51 -0700286 self.setVarsGlobal()
287 # TODO: use onos-cluster.sh to setup/start/stop ONOS cluster
288 for id in self.ids:
289 info( '* Setting up ONOS %s\n' % id )
290 self.setupONOS( id )
Bob Lantzc7c05402013-12-16 21:22:12 -0800291 info( '* Starting Zookeeper\n' )
292 self.startZookeeper()
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800293 info( '* Starting Ramcloud\n' )
294 self.startRamcloud()
Bob Lantzc7c05402013-12-16 21:22:12 -0800295 for id in self.ids:
296 info( '* Starting ONOS %s\n' % id )
297 self.startONOS( id )
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800298 self.cmd( 'start-rest.sh start' )
299 # Initialize proxies for clist()
300 self.proxies = [ RemoteController( 'onos-%d' % id, port=(self.ofbase + id ) )
301 for id in range( 0, self.count ) ]
Bob Lantzc7c05402013-12-16 21:22:12 -0800302
303 def stop( self, *args ):
304 "Stop ONOS instances"
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800305 self.cmd( 'start-rest.sh stop' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800306 for id in self.ids:
307 info( '* Stopping ONOS %s\n' % id )
308 self.stopONOS( id )
Bob Lantz7ead0532013-12-17 20:41:20 -0800309 info( '* Stopping Zookeeper\n' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800310 self.stopZookeeper()
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800311 info( '* Stopping Ramcloud\n' )
312 self.stopRamcloud()
313 for p in self.proxies:
314 p.stop()
315 p.proxies = []
Bob Lantzc7c05402013-12-16 21:22:12 -0800316
317 def clist( self ):
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800318 "Return list of Controller proxies for this ONOS cluster"
319 return self.proxies
Bob Lantzc7c05402013-12-16 21:22:12 -0800320
321
322class OVSSwitchONOS( OVSSwitch ):
323 "OVS switch which connects to multiple controllers"
324 def start( self, controllers ):
Bob Lantzc7c05402013-12-16 21:22:12 -0800325 assert len( controllers ) == 1
326 c0 = controllers[ 0 ]
327 assert type( c0 ) == ONOS
Bob Lantz63bbe4c2014-02-06 19:29:55 -0800328 controllers = c0.clist()
329 OVSSwitch.start( self, controllers )
Bob Lantzc7c05402013-12-16 21:22:12 -0800330
331
Bob Lantz7ead0532013-12-17 20:41:20 -0800332def waitConnected( switches ):
333 "Wait until all switches connect to controllers"
Bob Lantza0930822013-12-17 21:00:41 -0800334 start = time.time()
Bob Lantz7ead0532013-12-17 20:41:20 -0800335 info( '* Waiting for switches to connect...\n' )
336 for s in switches:
337 info( s )
338 while not s.connected():
339 info( '.' )
Bob Lantza0930822013-12-17 21:00:41 -0800340 time.sleep( 1 )
Bob Lantz7ead0532013-12-17 20:41:20 -0800341 info( ' ' )
Bob Lantza0930822013-12-17 21:00:41 -0800342 elapsed = time.time() - start
Bob Lantz7ead0532013-12-17 20:41:20 -0800343 info( '\n* Connected in %.2f seconds\n' % elapsed )
344
345
Bob Lantzc7c05402013-12-16 21:22:12 -0800346controllers = { 'onos': ONOS }
347switches = { 'ovso': OVSSwitchONOS }
348
349
350if __name__ == '__main__':
Bob Lantz7ead0532013-12-17 20:41:20 -0800351 # Simple test for ONOS() controller class
Bob Lantzc7c05402013-12-16 21:22:12 -0800352 setLogLevel( 'info' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800353 size = 2 if len( argv ) != 2 else int( argv[ 1 ] )
354 net = Mininet( topo=LinearTopo( size ),
Bob Lantzc7c05402013-12-16 21:22:12 -0800355 controller=partial( ONOS, n=2 ),
356 switch=OVSSwitchONOS )
357 net.start()
Bob Lantz7ead0532013-12-17 20:41:20 -0800358 waitConnected( net.switches )
Bob Lantzc7c05402013-12-16 21:22:12 -0800359 CLI( net )
360 net.stop()