blob: 520250b71440cfe2851d121f83d6e1ca2f4b99e2 [file] [log] [blame]
Jon Hallca319892017-06-15 15:25:22 -07001#!/usr/bin/env python
2"""
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +00003Copyright 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 Ronquillo4d5f1d02017-10-13 20:23:57 +000012 (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?
36class Controller():
Jeremy Ronquillo82705492017-10-18 14:19:55 -070037
Jon Hallca319892017-06-15 15:25:22 -070038 def __str__( self ):
39 return self.name
Jeremy Ronquillo82705492017-10-18 14:19:55 -070040
Jon Hallca319892017-06-15 15:25:22 -070041 def __repr__( self ):
Jeremy Ronquillo82705492017-10-18 14:19:55 -070042 # TODO use repr() for components?
Jon Hallca319892017-06-15 15:25:22 -070043 return "%s<IP=%s, CLI=%s, REST=%s, Bench=%s >" % ( self.name,
Jon Hall4173b242017-09-12 17:04:38 -070044 self.ipAddress,
45 self.CLI,
46 self.REST,
47 self.Bench )
Jon Hallca319892017-06-15 15:25:22 -070048
49 def __getattr__( self, name ):
50 """
51 Called when an attribute lookup has not found the attribute
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000052 in the usual places (i.e. it is not an instance attribute nor
53 is it found in the class tree for self). name is the attribute
54 name. This method should return the (computed) attribute value
Jon Hallca319892017-06-15 15:25:22 -070055 or raise an AttributeError exception.
56
57 We will look into each of the node's component handles to try to find the attreibute, looking at REST first
58 """
59 if hasattr( self.REST, name ):
Jon Hall3c0114c2020-08-11 15:07:42 -070060 main.log.debug( "%s: Using Rest driver's attribute for '%s'" % ( self.name, name ) )
61 return getattr( self.REST, name )
Jon Hallca319892017-06-15 15:25:22 -070062 if hasattr( self.CLI, name ):
Jon Hall3c0114c2020-08-11 15:07:42 -070063 main.log.debug( "%s: Using CLI driver's attribute for '%s'" % ( self.name, name ) )
64 return getattr( self.CLI, name )
Jon Hallca319892017-06-15 15:25:22 -070065 if hasattr( self.Bench, name ):
Jon Hall3c0114c2020-08-11 15:07:42 -070066 main.log.debug( "%s: Using Bench driver's attribute for '%s'" % ( self.name, name ) )
67 return getattr( self.Bench, name )
Devin Lim142b5342017-07-20 15:22:39 -070068 raise AttributeError( "Could not find the attribute %s in %r or it's component handles" % ( name, self ) )
Jon Hallca319892017-06-15 15:25:22 -070069
Jon Hall3c0114c2020-08-11 15:07:42 -070070 def __init__( self, name, ipAddress, CLI=None, REST=None, Bench=None, pos=None,
71 userName=None, server=None, dockerPrompt=None ):
Jeremy Ronquillo82705492017-10-18 14:19:55 -070072 # TODO: validate these arguments
Jon Hallca319892017-06-15 15:25:22 -070073 self.name = str( name )
74 self.ipAddress = ipAddress
75 self.CLI = CLI
76 self.REST = REST
77 self.Bench = Bench
78 self.active = False
Devin Lim142b5342017-07-20 15:22:39 -070079 self.pos = pos
80 self.ip_address = ipAddress
81 self.user_name = userName
Jon Hall4173b242017-09-12 17:04:38 -070082 self.server = server
Jon Hall3c0114c2020-08-11 15:07:42 -070083 self.dockerPrompt = dockerPrompt
Jon Hallca319892017-06-15 15:25:22 -070084
85class OnosClusterDriver( CLI ):
86
87 def __init__( self ):
88 """
89 Initialize client
90 """
91 self.name = None
92 self.home = None
93 self.handle = None
Jon Hall3c0114c2020-08-11 15:07:42 -070094 self.useDocker = False
95 self.dockerPrompt = None
Jon Hallca319892017-06-15 15:25:22 -070096 self.nodes = []
97 super( OnosClusterDriver, self ).__init__()
98
99 def checkOptions( self, var, defaultVar ):
100 if var is None or var == "":
101 return defaultVar
102 return var
103
104 def connect( self, **connectargs ):
105 """
106 Creates ssh handle for ONOS "bench".
107 NOTE:
108 The ip_address would come from the topo file using the host tag, the
109 value can be an environment variable as well as a "localhost" to get
110 the ip address needed to ssh to the "bench"
111 """
112 try:
113 for key in connectargs:
114 vars( self )[ key ] = connectargs[ key ]
115 self.home = "~/onos"
116 for key in self.options:
117 if key == "home":
118 self.home = self.options[ 'home' ]
119 elif key == "karaf_username":
120 self.karafUser = self.options[ key ]
121 elif key == "karaf_password":
122 self.karafPass = self.options[ key ]
123 elif key == "cluster_name":
124 prefix = self.options[ key ]
Jon Hall3c0114c2020-08-11 15:07:42 -0700125 elif key == "useDocker":
126 self.useDocker = "True" == self.options[ key ]
127 elif key == "docker_prompt":
128 self.dockerPrompt = self.options[ key ]
Jon Hallca319892017-06-15 15:25:22 -0700129
Jon Hall0e240372018-05-02 11:21:57 -0700130 self.home = self.checkOptions( self.home, "~/onos" )
131 self.karafUser = self.checkOptions( self.karafUser, self.user_name )
132 self.karafPass = self.checkOptions( self.karafPass, self.pwd )
Jon Hallca319892017-06-15 15:25:22 -0700133 prefix = self.checkOptions( prefix, "ONOS" )
Jon Hall3c0114c2020-08-11 15:07:42 -0700134 self.useDocker = self.checkOptions( self.useDocker, False )
135 self.dockerPrompt = self.checkOptions( self.dockerPrompt, "~/onos#" )
Jon Hallca319892017-06-15 15:25:22 -0700136
137 self.name = self.options[ 'name' ]
138
139 # The 'nodes' tag is optional and it is not required in .topo file
140 for key in self.options:
141 if key == "nodes":
142 # Maximum number of ONOS nodes to run, if there is any
143 self.maxNodes = int( self.options[ 'nodes' ] )
144 break
145 self.maxNodes = None
146
147 if self.maxNodes is None or self.maxNodes == "":
148 self.maxNodes = 100
149
150 # Grabs all OC environment variables based on max number of nodes
151 # TODO: Also support giving an ip range as a compononet option
152 self.onosIps = {} # Dictionary of all possible ONOS ip
153
154 try:
155 if self.maxNodes:
156 for i in range( self.maxNodes ):
157 envString = "OC" + str( i + 1 )
158 # If there is no more OC# then break the loop
159 if os.getenv( envString ):
160 self.onosIps[ envString ] = os.getenv( envString )
161 else:
162 self.maxNodes = len( self.onosIps )
163 main.log.info( self.name +
164 ": Created cluster data with " +
165 str( self.maxNodes ) +
166 " maximum number" +
167 " of nodes" )
168 break
169
170 if not self.onosIps:
171 main.log.info( "Could not read any environment variable"
172 + " please load a cell file with all" +
173 " onos IP" )
174 self.maxNodes = None
175 else:
176 main.log.info( self.name + ": Found " +
177 str( self.onosIps.values() ) +
178 " ONOS IPs" )
179 except KeyError:
180 main.log.info( "Invalid environment variable" )
181 except Exception as inst:
182 main.log.error( "Uncaught exception: " + str( inst ) )
183
184 try:
185 if os.getenv( str( self.ip_address ) ) is not None:
186 self.ip_address = os.getenv( str( self.ip_address ) )
187 else:
188 main.log.info( self.name +
189 ": Trying to connect to " +
190 self.ip_address )
191 except KeyError:
192 main.log.info( "Invalid host name," +
193 " connecting to local host instead" )
194 self.ip_address = 'localhost'
195 except Exception as inst:
196 main.log.error( "Uncaught exception: " + str( inst ) )
197
198 self.handle = super( OnosClusterDriver, self ).connect(
199 user_name=self.user_name,
200 ip_address=self.ip_address,
201 port=self.port,
202 pwd=self.pwd,
203 home=self.home )
204
205 if self.handle:
206 self.handle.sendline( "cd " + self.home )
207 self.handle.expect( "\$" )
208 self.createComponents( prefix=prefix )
209 return self.handle
210 else:
211 main.log.info( "Failed to create ONOS handle" )
212 return main.FALSE
213 except pexpect.EOF:
214 main.log.error( self.name + ": EOF exception found" )
215 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700216 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700217 except Exception:
218 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700219 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700220
221 def disconnect( self ):
222 """
223 Called when Test is complete to disconnect the ONOS handle.
224 """
225 response = main.TRUE
226 try:
227 if self.handle:
228 self.handle.sendline( "" )
229 self.handle.expect( "\$" )
230 self.handle.sendline( "exit" )
231 self.handle.expect( "closed" )
232 except pexpect.EOF:
233 main.log.error( self.name + ": EOF exception found" )
234 main.log.error( self.name + ": " + self.handle.before )
235 except ValueError:
236 main.log.exception( "Exception in disconnect of " + self.name )
237 response = main.TRUE
238 except Exception:
239 main.log.exception( self.name + ": Connection failed to the host" )
240 response = main.FALSE
241 return response
242
Devin Lim142b5342017-07-20 15:22:39 -0700243 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700244 """
245 Parse the cluster options to create an ONOS cli component with the given name
246 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000247 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700248 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
249 if clihost == "True":
250 main.componentDictionary[ name ][ 'host' ] = host
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000251 main.componentDictionary[name]['type'] = "OnosCliDriver"
252 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
253 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700254
Devin Lim142b5342017-07-20 15:22:39 -0700255 def createCliComponent( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700256 """
257 Creates a new onos cli component.
258
259 Arguments:
260 name - The string of the name of this component. The new component
261 will be assigned to main.<name> .
262 In addition, main.<name>.name = str( name )
263 """
264 try:
265 # look to see if this component already exists
266 getattr( main, name )
267 except AttributeError:
268 # namespace is clear, creating component
Devin Lim142b5342017-07-20 15:22:39 -0700269 self.setCliOptions( name, host )
Jon Hallca319892017-06-15 15:25:22 -0700270 return main.componentInit( name )
271 except pexpect.EOF:
272 main.log.error( self.name + ": EOF exception found" )
273 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700274 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700275 except Exception:
276 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700277 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700278 else:
279 # namespace is not clear!
280 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700281 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700282
283 def setRestOptions( self, name, host ):
284 """
285 Parse the cluster options to create an ONOS cli component with the given name
286 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000287 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
288 main.log.debug( main.componentDictionary[name] )
289 user = main.componentDictionary[name]['COMPONENTS'].get( "web_user", "onos" )
290 main.componentDictionary[name]['user'] = self.checkOptions( user, "onos" )
291 password = main.componentDictionary[name]['COMPONENTS'].get( "web_pass", "rocks" )
You Wangbef7ea12018-09-21 13:15:12 -0700292 main.componentDictionary[name]['password'] = self.checkOptions( password, "rocks" )
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000293 main.componentDictionary[name]['host'] = host
294 port = main.componentDictionary[name]['COMPONENTS'].get( "rest_port", "8181" )
295 main.componentDictionary[name]['port'] = self.checkOptions( port, "8181" )
296 main.componentDictionary[name]['type'] = "OnosRestDriver"
297 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
298 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700299
300 def createRestComponent( self, name, ipAddress ):
301 """
302 Creates a new onos rest component.
303
304 Arguments:
305 name - The string of the name of this component. The new component
306 will be assigned to main.<name> .
307 In addition, main.<name>.name = str( name )
308 """
309 try:
310 # look to see if this component already exists
311 getattr( main, name )
312 except AttributeError:
313 # namespace is clear, creating component
314 self.setRestOptions( name, ipAddress )
315 return main.componentInit( name )
316 except pexpect.EOF:
317 main.log.error( self.name + ": EOF exception found" )
318 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700319 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700320 except Exception:
321 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700322 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700323 else:
324 # namespace is not clear!
325 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700326 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700327
328 def setBenchOptions( self, name ):
329 """
330 Parse the cluster options to create an ONOS "bench" component with the given name
331 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000332 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
333 main.componentDictionary[name]['type'] = "OnosDriver"
334 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
335 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
336 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
337 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700338
339 def createBenchComponent( self, name ):
340 """
341 Creates a new onos "bench" component.
342
343 Arguments:
344 name - The string of the name of this component. The new component
345 will be assigned to main.<name> .
346 In addition, main.<name>.name = str( name )
347 """
348 try:
349 # look to see if this component already exists
350 getattr( main, name )
351 except AttributeError:
352 # namespace is clear, creating component
353 self.setBenchOptions( name )
354 return main.componentInit( name )
355 except pexpect.EOF:
356 main.log.error( self.name + ": EOF exception found" )
357 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700358 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700359 except Exception:
360 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700361 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700362 else:
363 # namespace is not clear!
364 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700365 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700366
Jon Hall4173b242017-09-12 17:04:38 -0700367 def setServerOptions( self, name, ipAddress ):
368 """
369 Parse the cluster options to create an ONOS "server" component with the given name
370
371 Arguments:
372 name - The name of the server componet
373 ipAddress - The ip address of the server
374 """
375 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
376 main.componentDictionary[name]['type'] = "OnosDriver"
377 main.componentDictionary[name]['host'] = ipAddress
378 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
379 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
You Wang4cc61912018-08-28 10:10:58 -0700380 # TODO: for now we use karaf user name and password also for logging to the onos nodes
381 main.componentDictionary[name]['user'] = self.karafUser
382 main.componentDictionary[name]['password'] = self.karafPass
Jon Hall4173b242017-09-12 17:04:38 -0700383 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
384 main.log.debug( main.componentDictionary[name] )
385
Jon Hall4173b242017-09-12 17:04:38 -0700386 def createServerComponent( self, name, ipAddress ):
387 """
388 Creates a new onos "server" component. This will be connected to the
389 node ONOS is running on.
390
391 Arguments:
392 name - The string of the name of this component. The new component
393 will be assigned to main.<name> .
394 In addition, main.<name>.name = str( name )
395 ipAddress - The ip address of the server
396 """
397 try:
398 # look to see if this component already exists
399 getattr( main, name )
400 except AttributeError:
401 # namespace is clear, creating component
402 self.setServerOptions( name, ipAddress )
403 return main.componentInit( name )
404 except pexpect.EOF:
405 main.log.error( self.name + ": EOF exception found" )
406 main.log.error( self.name + ": " + self.handle.before )
407 main.cleanAndExit()
408 except Exception:
409 main.log.exception( self.name + ": Uncaught exception!" )
410 main.cleanAndExit()
411 else:
412 # namespace is not clear!
413 main.log.error( name + " component already exists!" )
414 main.cleanAndExit()
415
Jon Hall4173b242017-09-12 17:04:38 -0700416 def createComponents( self, prefix='', createServer=True ):
Jon Hallca319892017-06-15 15:25:22 -0700417 """
418 Creates a CLI and REST component for each nodes in the cluster
419 """
420 # TODO: This needs work to support starting two seperate clusters in one test
421 cliPrefix = prefix + "cli"
422 restPrefix = prefix + "rest"
423 benchPrefix = prefix + "bench"
Jon Hall4173b242017-09-12 17:04:38 -0700424 serverPrefix = prefix + "server"
Jon Hallca319892017-06-15 15:25:22 -0700425 for i in xrange( 1, self.maxNodes + 1 ):
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000426 cliName = cliPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700427 restName = restPrefix + str( i )
428 benchName = benchPrefix + str( i )
Jon Hall4173b242017-09-12 17:04:38 -0700429 serverName = serverPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700430
431 # Unfortunately this means we need to have a cell set beofre running TestON,
432 # Even if it is just the entire possible cluster size
433 ip = self.onosIps[ 'OC' + str( i ) ]
434
Devin Lim142b5342017-07-20 15:22:39 -0700435 cli = self.createCliComponent( cliName, ip )
Jon Hallca319892017-06-15 15:25:22 -0700436 rest = self.createRestComponent( restName, ip )
437 bench = self.createBenchComponent( benchName )
Jon Hall4173b242017-09-12 17:04:38 -0700438 server = self.createServerComponent( serverName, ip ) if createServer else None
Jon Hall3c0114c2020-08-11 15:07:42 -0700439 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1,
440 self.user_name, server=server,
441 dockerPrompt=self.dockerPrompt ) )