blob: cc32b94d51599f4da510c6bd1053af4b2f972333 [file] [log] [blame]
Jon Hallca319892017-06-15 15:25:22 -07001#!/usr/bin/env python
2"""
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -07003Copyright 2017 Open Networking Foundation ( ONF )
Jon Hallca319892017-06-15 15:25:22 -07004
5Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
6the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
7or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
8
9 TestON is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 2 of the License, or
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070012 ( at your option ) any later version.
Jon Hallca319892017-06-15 15:25:22 -070013
14 TestON is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with TestON. If not, see <http://www.gnu.org/licenses/>.
21
22
23This driver is used to interact with an ONOS cluster. It should
24handle creating the necessary components to interact with each specific ONOS nodes.
25
26Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
27the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
28or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
29
30"""
31import pexpect
32import os
33from drivers.common.clidriver import CLI
34
35# FIXME: Move this to it's own file?
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070036
37
Jon Hallca319892017-06-15 15:25:22 -070038class Controller():
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070039
Jon Hallca319892017-06-15 15:25:22 -070040 def __str__( self ):
41 return self.name
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070042
Jon Hallca319892017-06-15 15:25:22 -070043 def __repr__( self ):
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070044 # TODO: use repr() for components?
Jon Hallca319892017-06-15 15:25:22 -070045 return "%s<IP=%s, CLI=%s, REST=%s, Bench=%s >" % ( self.name,
Jon Hall4173b242017-09-12 17:04:38 -070046 self.ipAddress,
47 self.CLI,
48 self.REST,
49 self.Bench )
Jon Hallca319892017-06-15 15:25:22 -070050
51 def __getattr__( self, name ):
52 """
53 Called when an attribute lookup has not found the attribute
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070054 in the usual places ( i.e. it is not an instance attribute nor
55 is it found in the class tree for self ). name is the attribute
56 name. This method should return the ( computed ) attribute value
Jon Hallca319892017-06-15 15:25:22 -070057 or raise an AttributeError exception.
58
59 We will look into each of the node's component handles to try to find the attreibute, looking at REST first
60 """
Devin Lim142b5342017-07-20 15:22:39 -070061 usedDriver = False
Jon Hallca319892017-06-15 15:25:22 -070062 if hasattr( self.REST, name ):
Devin Lim142b5342017-07-20 15:22:39 -070063 main.log.warn( "Rest driver has attribute '%s'" % ( name ) )
64 if not usedDriver:
65 usedDriver = True
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070066 main.log.debug( "Using Rest driver's attribute for '%s'" % ( name ) )
67 f = getattr( self.REST, name )
Jon Hallca319892017-06-15 15:25:22 -070068 if hasattr( self.CLI, name ):
Devin Lim142b5342017-07-20 15:22:39 -070069 main.log.warn( "CLI driver has attribute '%s'" % ( name ) )
70 if not usedDriver:
71 usedDriver = True
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070072 main.log.debug( "Using CLI driver's attribute for '%s'" % ( name ) )
73 f = getattr( self.CLI, name )
Jon Hallca319892017-06-15 15:25:22 -070074 if hasattr( self.Bench, name ):
Devin Lim142b5342017-07-20 15:22:39 -070075 main.log.warn( "Bench driver has attribute '%s'" % ( name ) )
76 if not usedDriver:
77 usedDriver = True
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070078 main.log.debug( "Using Bench driver's attribute for '%s'" % ( name ) )
79 f = getattr( self.Bench, name )
Devin Lim142b5342017-07-20 15:22:39 -070080 if usedDriver:
81 return f
82 raise AttributeError( "Could not find the attribute %s in %r or it's component handles" % ( name, self ) )
Jon Hallca319892017-06-15 15:25:22 -070083
Jon Hall4173b242017-09-12 17:04:38 -070084 def __init__( self, name, ipAddress, CLI=None, REST=None, Bench=None, pos=None, userName=None, server=None ):
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070085 # TODO: validate these arguments
Jon Hallca319892017-06-15 15:25:22 -070086 self.name = str( name )
87 self.ipAddress = ipAddress
88 self.CLI = CLI
89 self.REST = REST
90 self.Bench = Bench
91 self.active = False
Devin Lim142b5342017-07-20 15:22:39 -070092 self.pos = pos
93 self.ip_address = ipAddress
94 self.user_name = userName
Jon Hall4173b242017-09-12 17:04:38 -070095 self.server = server
Jon Hallca319892017-06-15 15:25:22 -070096
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -070097
Jon Hallca319892017-06-15 15:25:22 -070098class OnosClusterDriver( CLI ):
99
100 def __init__( self ):
101 """
102 Initialize client
103 """
104 self.name = None
105 self.home = None
106 self.handle = None
107 self.nodes = []
108 super( OnosClusterDriver, self ).__init__()
109
110 def checkOptions( self, var, defaultVar ):
111 if var is None or var == "":
112 return defaultVar
113 return var
114
115 def connect( self, **connectargs ):
116 """
117 Creates ssh handle for ONOS "bench".
118 NOTE:
119 The ip_address would come from the topo file using the host tag, the
120 value can be an environment variable as well as a "localhost" to get
121 the ip address needed to ssh to the "bench"
122 """
123 try:
124 for key in connectargs:
125 vars( self )[ key ] = connectargs[ key ]
126 self.home = "~/onos"
127 for key in self.options:
128 if key == "home":
129 self.home = self.options[ 'home' ]
130 elif key == "karaf_username":
131 self.karafUser = self.options[ key ]
132 elif key == "karaf_password":
133 self.karafPass = self.options[ key ]
134 elif key == "cluster_name":
135 prefix = self.options[ key ]
136
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700137 self.home = self.checkOptions( self.home, "~/onos" )
138 self.karafUser = self.checkOptions( self.karafUser, self.user_name )
139 self.karafPass = self.checkOptions( self.karafPass, self.pwd )
Jon Hallca319892017-06-15 15:25:22 -0700140 prefix = self.checkOptions( prefix, "ONOS" )
141
142 self.name = self.options[ 'name' ]
143
144 # The 'nodes' tag is optional and it is not required in .topo file
145 for key in self.options:
146 if key == "nodes":
147 # Maximum number of ONOS nodes to run, if there is any
148 self.maxNodes = int( self.options[ 'nodes' ] )
149 break
150 self.maxNodes = None
151
152 if self.maxNodes is None or self.maxNodes == "":
153 self.maxNodes = 100
154
155 # Grabs all OC environment variables based on max number of nodes
156 # TODO: Also support giving an ip range as a compononet option
157 self.onosIps = {} # Dictionary of all possible ONOS ip
158
159 try:
160 if self.maxNodes:
161 for i in range( self.maxNodes ):
162 envString = "OC" + str( i + 1 )
163 # If there is no more OC# then break the loop
164 if os.getenv( envString ):
165 self.onosIps[ envString ] = os.getenv( envString )
166 else:
167 self.maxNodes = len( self.onosIps )
168 main.log.info( self.name +
169 ": Created cluster data with " +
170 str( self.maxNodes ) +
171 " maximum number" +
172 " of nodes" )
173 break
174
175 if not self.onosIps:
176 main.log.info( "Could not read any environment variable"
177 + " please load a cell file with all" +
178 " onos IP" )
179 self.maxNodes = None
180 else:
181 main.log.info( self.name + ": Found " +
182 str( self.onosIps.values() ) +
183 " ONOS IPs" )
184 except KeyError:
185 main.log.info( "Invalid environment variable" )
186 except Exception as inst:
187 main.log.error( "Uncaught exception: " + str( inst ) )
188
189 try:
190 if os.getenv( str( self.ip_address ) ) is not None:
191 self.ip_address = os.getenv( str( self.ip_address ) )
192 else:
193 main.log.info( self.name +
194 ": Trying to connect to " +
195 self.ip_address )
196 except KeyError:
197 main.log.info( "Invalid host name," +
198 " connecting to local host instead" )
199 self.ip_address = 'localhost'
200 except Exception as inst:
201 main.log.error( "Uncaught exception: " + str( inst ) )
202
203 self.handle = super( OnosClusterDriver, self ).connect(
204 user_name=self.user_name,
205 ip_address=self.ip_address,
206 port=self.port,
207 pwd=self.pwd,
208 home=self.home )
209
210 if self.handle:
211 self.handle.sendline( "cd " + self.home )
212 self.handle.expect( "\$" )
213 self.createComponents( prefix=prefix )
214 return self.handle
215 else:
216 main.log.info( "Failed to create ONOS handle" )
217 return main.FALSE
218 except pexpect.EOF:
219 main.log.error( self.name + ": EOF exception found" )
220 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700221 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700222 except Exception:
223 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700224 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700225
226 def disconnect( self ):
227 """
228 Called when Test is complete to disconnect the ONOS handle.
229 """
230 response = main.TRUE
231 try:
232 if self.handle:
233 self.handle.sendline( "" )
234 self.handle.expect( "\$" )
235 self.handle.sendline( "exit" )
236 self.handle.expect( "closed" )
237 except pexpect.EOF:
238 main.log.error( self.name + ": EOF exception found" )
239 main.log.error( self.name + ": " + self.handle.before )
240 except ValueError:
241 main.log.exception( "Exception in disconnect of " + self.name )
242 response = main.TRUE
243 except Exception:
244 main.log.exception( self.name + ": Connection failed to the host" )
245 response = main.FALSE
246 return response
247
Devin Lim142b5342017-07-20 15:22:39 -0700248 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700249 """
250 Parse the cluster options to create an ONOS cli component with the given name
251 """
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700252 main.componentDictionary[ name ] = main.componentDictionary[ self.name ].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700253 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
254 if clihost == "True":
255 main.componentDictionary[ name ][ 'host' ] = host
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700256 main.componentDictionary[ name ][ 'type' ] = "OnosCliDriver"
257 main.componentDictionary[ name ][ 'connect_order' ] = str( int( main.componentDictionary[ name ][ 'connect_order' ] ) + 1 )
258 main.log.debug( main.componentDictionary[ name ] )
Jon Hallca319892017-06-15 15:25:22 -0700259
Devin Lim142b5342017-07-20 15:22:39 -0700260 def createCliComponent( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700261 """
262 Creates a new onos cli component.
263
264 Arguments:
265 name - The string of the name of this component. The new component
266 will be assigned to main.<name> .
267 In addition, main.<name>.name = str( name )
268 """
269 try:
270 # look to see if this component already exists
271 getattr( main, name )
272 except AttributeError:
273 # namespace is clear, creating component
Devin Lim142b5342017-07-20 15:22:39 -0700274 self.setCliOptions( name, host )
Jon Hallca319892017-06-15 15:25:22 -0700275 return main.componentInit( name )
276 except pexpect.EOF:
277 main.log.error( self.name + ": EOF exception found" )
278 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700279 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700280 except Exception:
281 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700282 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700283 else:
284 # namespace is not clear!
285 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700286 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700287
288 def setRestOptions( self, name, host ):
289 """
290 Parse the cluster options to create an ONOS cli component with the given name
291 """
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700292 main.componentDictionary[ name ] = main.componentDictionary[ self.name ].copy()
293 main.log.debug( main.componentDictionary[ name ] )
294 user = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "web_user", "onos" )
295 main.componentDictionary[ name ][ 'user' ] = self.checkOptions( user, "onos" )
296 password = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "web_pass", "rocks" )
297 main.componentDictionary[ name ][ 'pass' ] = self.checkOptions( password, "rocks" )
298 main.componentDictionary[ name ][ 'host' ] = host
299 port = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "rest_port", "8181" )
300 main.componentDictionary[ name ][ 'port' ] = self.checkOptions( port, "8181" )
301 main.componentDictionary[ name ][ 'type' ] = "OnosRestDriver"
302 main.componentDictionary[ name ][ 'connect_order' ] = str( int( main.componentDictionary[ name ][ 'connect_order' ] ) + 1 )
303 main.log.debug( main.componentDictionary[ name ] )
Jon Hallca319892017-06-15 15:25:22 -0700304
305 def createRestComponent( self, name, ipAddress ):
306 """
307 Creates a new onos rest component.
308
309 Arguments:
310 name - The string of the name of this component. The new component
311 will be assigned to main.<name> .
312 In addition, main.<name>.name = str( name )
313 """
314 try:
315 # look to see if this component already exists
316 getattr( main, name )
317 except AttributeError:
318 # namespace is clear, creating component
319 self.setRestOptions( name, ipAddress )
320 return main.componentInit( name )
321 except pexpect.EOF:
322 main.log.error( self.name + ": EOF exception found" )
323 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700324 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700325 except Exception:
326 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700327 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700328 else:
329 # namespace is not clear!
330 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700331 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700332
333 def setBenchOptions( self, name ):
334 """
335 Parse the cluster options to create an ONOS "bench" component with the given name
336 """
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700337 main.componentDictionary[ name ] = main.componentDictionary[ self.name ].copy()
338 main.componentDictionary[ name ][ 'type' ] = "OnosDriver"
339 home = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "onos_home", None )
340 main.componentDictionary[ name ][ 'home' ] = self.checkOptions( home, None )
341 main.componentDictionary[ name ][ 'connect_order' ] = str( int( main.componentDictionary[ name ][ 'connect_order' ] ) + 1 )
342 main.log.debug( main.componentDictionary[ name ] )
Jon Hallca319892017-06-15 15:25:22 -0700343
344 def createBenchComponent( self, name ):
345 """
346 Creates a new onos "bench" component.
347
348 Arguments:
349 name - The string of the name of this component. The new component
350 will be assigned to main.<name> .
351 In addition, main.<name>.name = str( name )
352 """
353 try:
354 # look to see if this component already exists
355 getattr( main, name )
356 except AttributeError:
357 # namespace is clear, creating component
358 self.setBenchOptions( name )
359 return main.componentInit( name )
360 except pexpect.EOF:
361 main.log.error( self.name + ": EOF exception found" )
362 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700363 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700364 except Exception:
365 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700366 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700367 else:
368 # namespace is not clear!
369 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700370 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700371
Jon Hall4173b242017-09-12 17:04:38 -0700372 def setServerOptions( self, name, ipAddress ):
373 """
374 Parse the cluster options to create an ONOS "server" component with the given name
375
376 Arguments:
377 name - The name of the server componet
378 ipAddress - The ip address of the server
379 """
380 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
381 main.componentDictionary[name]['type'] = "OnosDriver"
382 main.componentDictionary[name]['host'] = ipAddress
383 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
384 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
385 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
386 main.log.debug( main.componentDictionary[name] )
387
Jon Hall4173b242017-09-12 17:04:38 -0700388 def createServerComponent( self, name, ipAddress ):
389 """
390 Creates a new onos "server" component. This will be connected to the
391 node ONOS is running on.
392
393 Arguments:
394 name - The string of the name of this component. The new component
395 will be assigned to main.<name> .
396 In addition, main.<name>.name = str( name )
397 ipAddress - The ip address of the server
398 """
399 try:
400 # look to see if this component already exists
401 getattr( main, name )
402 except AttributeError:
403 # namespace is clear, creating component
404 self.setServerOptions( name, ipAddress )
405 return main.componentInit( name )
406 except pexpect.EOF:
407 main.log.error( self.name + ": EOF exception found" )
408 main.log.error( self.name + ": " + self.handle.before )
409 main.cleanAndExit()
410 except Exception:
411 main.log.exception( self.name + ": Uncaught exception!" )
412 main.cleanAndExit()
413 else:
414 # namespace is not clear!
415 main.log.error( name + " component already exists!" )
416 main.cleanAndExit()
417
Jon Hall4173b242017-09-12 17:04:38 -0700418 def createComponents( self, prefix='', createServer=True ):
Jon Hallca319892017-06-15 15:25:22 -0700419 """
420 Creates a CLI and REST component for each nodes in the cluster
421 """
422 # TODO: This needs work to support starting two seperate clusters in one test
423 cliPrefix = prefix + "cli"
424 restPrefix = prefix + "rest"
425 benchPrefix = prefix + "bench"
Jon Hall4173b242017-09-12 17:04:38 -0700426 serverPrefix = prefix + "server"
Jon Hallca319892017-06-15 15:25:22 -0700427 for i in xrange( 1, self.maxNodes + 1 ):
Jeremy Ronquillo23fb2162017-09-15 14:59:57 -0700428 cliName = cliPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700429 restName = restPrefix + str( i )
430 benchName = benchPrefix + str( i )
Jon Hall4173b242017-09-12 17:04:38 -0700431 serverName = serverPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700432
433 # Unfortunately this means we need to have a cell set beofre running TestON,
434 # Even if it is just the entire possible cluster size
435 ip = self.onosIps[ 'OC' + str( i ) ]
436
Devin Lim142b5342017-07-20 15:22:39 -0700437 cli = self.createCliComponent( cliName, ip )
Jon Hallca319892017-06-15 15:25:22 -0700438 rest = self.createRestComponent( restName, ip )
439 bench = self.createBenchComponent( benchName )
Jon Hall4173b242017-09-12 17:04:38 -0700440 server = self.createServerComponent( serverName, ip ) if createServer else None
441 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1, self.user_name, server=server ) )