blob: 7756b59d0488e73ea8b3822570b2c0c7fd2e0053 [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
11We use single Zookeeper and Cassandra instances for now.
12
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
27from mininet.node import Controller, OVSSwitch
28from 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 Lantzc7c05402013-12-16 21:22:12 -080032from mininet.util import quietRun
33
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 Lantzc7c05402013-12-16 21:22:12 -080040
41class ONOS( Controller ):
42 "Custom controller class for ONOS"
43
44 # Directories and configuration templates
45 home = environ[ 'HOME' ]
46 onosDir = home + "/ONOS"
47 zookeeperDir = home + "/zookeeper-3.4.5"
48 dirBase = '/tmp'
49 logDir = dirBase + '/onos-%s.logs'
Bob Lantz4ed6cff2013-12-17 21:43:36 -080050 # cassDir = dirBase + '/onos-%s.cassandra'
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',
58 'net.onrc.onos.ofcontroller.floodlightlistener.NetworkGraphPublisher',
59 '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',
64 'net.onrc.onos.registry.controller.ZookeeperRegistry'
65 )
66
67 # Additions for reactive forwarding
68 reactiveModules = (
69 'net.onrc.onos.ofcontroller.proxyarp.ProxyArpManager',
70 'net.onrc.onos.ofcontroller.core.config.DefaultConfiguration',
71 'net.onrc.onos.ofcontroller.forwarding.Forwarding'
72 )
73
74 # Module parameters
Bob Lantzc7c05402013-12-16 21:22:12 -080075 ofbase = 6633
Bob Lantz7ead0532013-12-17 20:41:20 -080076 restbase = 8080
77 jmxbase = 7189
Bob Lantzc7c05402013-12-16 21:22:12 -080078
Bob Lantzc7c05402013-12-16 21:22:12 -080079 fc = 'net.floodlightcontroller.'
Bob Lantzc7c05402013-12-16 21:22:12 -080080
Bob Lantz21b41f62013-12-18 18:16:38 -080081 # Things that vary per ONOS id
Bob Lantz7ead0532013-12-17 20:41:20 -080082 perNodeConfigBase = {
83 fc + 'core.FloodlightProvider.openflowport': ofbase,
84 fc + 'restserver.RestApiServer.port': restbase,
85 fc + 'core.FloodlightProvider.controllerid': 0
86 }
87
Bob Lantz21b41f62013-12-18 18:16:38 -080088 # Things that are static
Bob Lantz7ead0532013-12-17 20:41:20 -080089 staticConfig = {
90 'net.onrc.onos.ofcontroller.floodlightlistener.NetworkGraphPublisher.dbconf':
91 '/tmp/cassandra.titan',
Bob Lantz7ead0532013-12-17 20:41:20 -080092 'net.floodlightcontroller.core.FloodlightProvider.workerthreads': 16,
93 'net.floodlightcontroller.forwarding.Forwarding.idletimeout': 5,
94 'net.floodlightcontroller.forwarding.Forwarding.hardtimeout': 0
95 }
96
Bob Lantz21b41f62013-12-18 18:16:38 -080097 # Things that are based on onosDir
98 dirConfig = {
99 'net.onrc.onos.datagrid.HazelcastDatagrid.datagridConfig':
100 '%s/conf/hazelcast.xml',
101 }
102
Bob Lantz7ead0532013-12-17 20:41:20 -0800103 proctag = 'mn-onos-id'
Bob Lantzc7c05402013-12-16 21:22:12 -0800104
Bob Lantz7a4d6102013-12-17 00:34:55 -0800105 # For maven debugging
106 # mvn = 'mvn -o -e -X'
Bob Lantzc7c05402013-12-16 21:22:12 -0800107
Bob Lantz7ead0532013-12-17 20:41:20 -0800108 def __init__( self, name, n=1, reactive=True, runAsRoot=False, **params):
Bob Lantzc7c05402013-12-16 21:22:12 -0800109 """n: number of ONOS instances to run (1)
Bob Lantz7ead0532013-12-17 20:41:20 -0800110 reactive: run in reactive mode (True)
111 runAsRoot: run ONOS as root (False)"""
Bob Lantzc7c05402013-12-16 21:22:12 -0800112 self.check()
Bob Lantzc7c05402013-12-16 21:22:12 -0800113 self.count = n
Bob Lantz7ead0532013-12-17 20:41:20 -0800114 self.reactive = reactive
115 self.runAsRoot = runAsRoot
Bob Lantzc7c05402013-12-16 21:22:12 -0800116 self.ids = range( 0, self.count )
117 Controller.__init__( self, name, **params )
118 # We don't need to run as root, and it can interfere
119 # with starting Zookeeper manually
Bob Lantz7ead0532013-12-17 20:41:20 -0800120 self.user = None
121 if not self.runAsRoot:
122 try:
123 self.user = quietRun( 'who am i' ).split()[ 0 ]
124 self.sendCmd( 'su', self.user )
125 self.waiting = False
126 except:
127 warn( '__init__: failed to drop privileges\n' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800128 # Need to run commands from ONOS dir
129 self.cmd( 'cd', self.onosDir )
130 self.cmd( 'export PATH=$PATH:%s' % self.onosDir )
Bob Lantz7a4d6102013-12-17 00:34:55 -0800131 if hasattr( self, 'mvn' ):
132 self.cmd( 'export MVN="%s"' % self.mvn )
Bob Lantzc7c05402013-12-16 21:22:12 -0800133
134 def check( self ):
Bob Lantz21b41f62013-12-18 18:16:38 -0800135 "Set onosDir and check for ONOS prerequisites"
Bob Lantzc7c05402013-12-16 21:22:12 -0800136 if not quietRun( 'which java' ):
137 raise Exception( 'java not found -'
138 ' make sure it is installed and in $PATH' )
139 if not quietRun( 'which mvn' ):
140 raise Exception( 'Maven (mvn) not found -'
141 ' make sure it is installed and in $PATH' )
Bob Lantz21b41f62013-12-18 18:16:38 -0800142 if 'ONOS_HOME' in environ:
143 self.onosDir = environ[ 'ONOS_HOME' ]
144 else:
145 warn( '* $ONOS_HOME is not set - assuming %s\n' % self.onosDir )
146 for script in 'start-zk.sh', 'start-cassandra.sh', 'start-onos.sh':
147 script = path.join( self.onosDir, script )
148 if not path.exists( script ):
149 msg = '%s not found' % script
150 if 'ONOS_HOME' not in environ:
151 msg += ' (try setting $ONOS_HOME and/or sudo -E)'
152 raise Exception( msg )
Bob Lantz7ead0532013-12-17 20:41:20 -0800153
154 def waitNetstat( self, pid ):
155 """Wait for pid to show up in netstat
156 We assume that once a process is listening on some
157 port, it is ready to go!"""
158 while True:
159 output = self.cmd( 'sudo netstat -natp | grep %s/' % pid )
160 if output:
161 return output
162 info( '.' )
Bob Lantza0930822013-12-17 21:00:41 -0800163 time.sleep( 1 )
Bob Lantz7ead0532013-12-17 20:41:20 -0800164
165 def waitStart( self, procname, pattern ):
166 "Wait for at least one of procname to show up in netstat"
Bob Lantz21b41f62013-12-18 18:16:38 -0800167 # Check script exit code
168 exitCode = int( self.cmd( 'echo $?' ) )
169 if exitCode != 0:
170 raise Exception( '%s startup failed with code %d' %
171 ( procname, exitCode ) )
Bob Lantz7ead0532013-12-17 20:41:20 -0800172 info( '* Waiting for %s startup' % procname )
173 result = self.cmd( 'pgrep -f %s' % pattern ).split()[ 0 ]
174 pid = int( result )
175 output = self.waitNetstat( pid )
176 info( '\n* %s process %d is listening\n' % ( procname, pid ) )
177 info( output )
178
Bob Lantzc7c05402013-12-16 21:22:12 -0800179 def startCassandra( self ):
180 "Start Cassandra"
181 self.cmd( 'start-cassandra.sh start' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800182 self.waitStart( 'Cassandra', 'apache-cassandra' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800183 status = self.cmd( 'start-cassandra.sh status' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800184 if 'running' not in status:
Bob Lantzc7c05402013-12-16 21:22:12 -0800185 raise Exception( 'Cassandra startup failed: ' + status )
186
187 def stopCassandra( self ):
188 "Stop Cassandra"
189 self.cmd( 'start-cassandra.sh stop' )
190
191 def startZookeeper( self, initcfg=True ):
192 "Start Zookeeper"
193 # Reinitialize configuration file
194 if initcfg:
195 cfg = self.zookeeperDir + '/conf/zoo.cfg'
196 template = self.zookeeperDir + '/conf/zoo_sample.cfg'
197 copyfile( template, cfg )
198 self.cmd( 'start-zk.sh restart' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800199 self.waitStart( 'Zookeeper', 'zookeeper' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800200 status = self.cmd( 'start-zk.sh status' )
201 if 'Error' in status:
202 raise Exception( 'Zookeeper startup failed: ' + status )
203
204 def stopZookeeper( self ):
205 "Stop Zookeeper"
206 self.cmd( 'start-zk.sh stop' )
207
Bob Lantz7ead0532013-12-17 20:41:20 -0800208 def genProperties( self, id, path='/tmp' ):
209 "Generate ONOS properties file"
210 filename = path + '/onos-%s.properties' % id
211 with open( filename, 'w' ) as f:
212 # Write modules list
213 modules = list( self.baseModules )
214 if self.reactive:
215 modules += list( self.reactiveModules )
216 f.write( 'floodlight.modules = %s\n' %
217 ',\\\n'.join( modules ) )
218 # Write other parameters
219 for var, val in self.perNodeConfigBase.iteritems():
220 if type( val ) is int:
221 val += id
222 f.write( '%s = %s\n' % ( var, val ) )
223 for var, val in self.staticConfig.iteritems():
224 f.write( '%s = %s\n' % ( var, val ) )
Bob Lantz21b41f62013-12-18 18:16:38 -0800225 for var, val in self.dirConfig.iteritems():
226 f.write( '%s = %s\n' % ( var, val % self.onosDir) )
Bob Lantz7ead0532013-12-17 20:41:20 -0800227 return filename
228
229 def setVars( self, id, propsFile ):
230 """Set and return environment vars
231 id: ONOS instance number
232 propsFile: properties file name"""
Bob Lantzc7c05402013-12-16 21:22:12 -0800233 # ONOS directories and files
234 logdir = self.logDir % id
Bob Lantz4ed6cff2013-12-17 21:43:36 -0800235 # cassdir = self.cassDir % id
Bob Lantzc7c05402013-12-16 21:22:12 -0800236 logback = self.logbackFile % id
237 jmxport = self.jmxbase + id
Bob Lantz4ed6cff2013-12-17 21:43:36 -0800238 self.cmd( 'mkdir -p', logdir ) # , cassdir
Bob Lantzc7c05402013-12-16 21:22:12 -0800239 self.cmd( 'export ONOS_LOGDIR="%s"' % logdir )
240 self.cmd( 'export ZOO_LOG_DIR="%s"' % logdir )
Bob Lantz4ed6cff2013-12-17 21:43:36 -0800241 # self.cmd( 'export CASS_DIR="%s"' % cassdir )
Bob Lantzc7c05402013-12-16 21:22:12 -0800242 self.cmd( 'export ONOS_LOGBACK="%s"' % logback )
243 self.cmd( 'export JMX_PORT=%s' % jmxport )
Bob Lantz7ead0532013-12-17 20:41:20 -0800244 self.cmd( 'export JVM_OPTS="-D%s=%s"' % (
245 self.proctag, id ) )
246 self.cmd( 'export ONOS_PROPS="%s"' % propsFile )
Bob Lantzc7c05402013-12-16 21:22:12 -0800247
248 def startONOS( self, id ):
249 """Start ONOS
Bob Lantz7ead0532013-12-17 20:41:20 -0800250 id: new instance number"""
Bob Lantza0930822013-12-17 21:00:41 -0800251 start = time.time()
Bob Lantz7ead0532013-12-17 20:41:20 -0800252 self.stopONOS( id )
253 propsFile = self.genProperties( id )
254 self.setVars( id, propsFile )
Bob Lantzc7c05402013-12-16 21:22:12 -0800255 self.cmdPrint( 'start-onos.sh startnokill' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800256 # start-onos.sh waits for ONOS startup
Bob Lantza0930822013-12-17 21:00:41 -0800257 elapsed = time.time() - start
Bob Lantz7ead0532013-12-17 20:41:20 -0800258 info( '* ONOS %s started in %.2f seconds\n' % ( id, elapsed ) )
Bob Lantzc7c05402013-12-16 21:22:12 -0800259
260 def stopONOS( self, id ):
261 """Shut down ONOS
262 id: identifier for instance"""
263 pid = self.cmd( "jps -v | grep %s=%s | awk '{print $1}'" %
264 ( self.proctag, id ) ).strip()
265 if pid:
266 self.cmdPrint( 'kill', pid )
267
268 def start( self, *args ):
269 "Start ONOS instances"
270 info( '* Starting Cassandra\n' )
271 self.startCassandra()
272 info( '* Starting Zookeeper\n' )
273 self.startZookeeper()
274 for id in self.ids:
275 info( '* Starting ONOS %s\n' % id )
276 self.startONOS( id )
277
278 def stop( self, *args ):
279 "Stop ONOS instances"
280 for id in self.ids:
281 info( '* Stopping ONOS %s\n' % id )
282 self.stopONOS( id )
Bob Lantz7ead0532013-12-17 20:41:20 -0800283 info( '* Stopping Zookeeper\n' )
Bob Lantzc7c05402013-12-16 21:22:12 -0800284 self.stopZookeeper()
285 info( '* Stopping Cassandra\n' )
286 self.stopCassandra()
287
288 def clist( self ):
289 "Return list of controller specifiers (proto:ip:port)"
290 return [ 'tcp:127.0.0.1:%s' % ( self.ofbase + id )
291 for id in range( 0, self.count ) ]
292
293
294class OVSSwitchONOS( OVSSwitch ):
295 "OVS switch which connects to multiple controllers"
296 def start( self, controllers ):
297 OVSSwitch.start( self, controllers )
298 assert len( controllers ) == 1
299 c0 = controllers[ 0 ]
300 assert type( c0 ) == ONOS
301 clist = ','.join( c0.clist() )
302 self.cmd( 'ovs-vsctl set-controller', self, clist)
303 # Reconnect quickly to controllers (1s vs. 15s max_backoff)
304 for uuid in self.controllerUUIDs():
305 if uuid.count( '-' ) != 4:
306 # Doesn't look like a UUID
307 continue
308 uuid = uuid.strip()
309 self.cmd( 'ovs-vsctl set Controller', uuid,
310 'max_backoff=1000' )
311
312
Bob Lantz7ead0532013-12-17 20:41:20 -0800313def waitConnected( switches ):
314 "Wait until all switches connect to controllers"
Bob Lantza0930822013-12-17 21:00:41 -0800315 start = time.time()
Bob Lantz7ead0532013-12-17 20:41:20 -0800316 info( '* Waiting for switches to connect...\n' )
317 for s in switches:
318 info( s )
319 while not s.connected():
320 info( '.' )
Bob Lantza0930822013-12-17 21:00:41 -0800321 time.sleep( 1 )
Bob Lantz7ead0532013-12-17 20:41:20 -0800322 info( ' ' )
Bob Lantza0930822013-12-17 21:00:41 -0800323 elapsed = time.time() - start
Bob Lantz7ead0532013-12-17 20:41:20 -0800324 info( '\n* Connected in %.2f seconds\n' % elapsed )
325
326
Bob Lantzc7c05402013-12-16 21:22:12 -0800327controllers = { 'onos': ONOS }
328switches = { 'ovso': OVSSwitchONOS }
329
330
331if __name__ == '__main__':
Bob Lantz7ead0532013-12-17 20:41:20 -0800332 # Simple test for ONOS() controller class
Bob Lantzc7c05402013-12-16 21:22:12 -0800333 setLogLevel( 'info' )
Bob Lantz7ead0532013-12-17 20:41:20 -0800334 size = 2 if len( argv ) != 2 else int( argv[ 1 ] )
335 net = Mininet( topo=LinearTopo( size ),
Bob Lantzc7c05402013-12-16 21:22:12 -0800336 controller=partial( ONOS, n=2 ),
337 switch=OVSSwitchONOS )
338 net.start()
Bob Lantz7ead0532013-12-17 20:41:20 -0800339 waitConnected( net.switches )
Bob Lantzc7c05402013-12-16 21:22:12 -0800340 CLI( net )
341 net.stop()