blob: 6ca91b713b860f098eaf05de5e21006f4fd60843 [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
Jon Hallca319892017-06-15 15:25:22 -070099 def connect( self, **connectargs ):
100 """
101 Creates ssh handle for ONOS "bench".
102 NOTE:
103 The ip_address would come from the topo file using the host tag, the
104 value can be an environment variable as well as a "localhost" to get
105 the ip address needed to ssh to the "bench"
106 """
107 try:
108 for key in connectargs:
109 vars( self )[ key ] = connectargs[ key ]
110 self.home = "~/onos"
111 for key in self.options:
112 if key == "home":
113 self.home = self.options[ 'home' ]
114 elif key == "karaf_username":
115 self.karafUser = self.options[ key ]
116 elif key == "karaf_password":
117 self.karafPass = self.options[ key ]
118 elif key == "cluster_name":
119 prefix = self.options[ key ]
Jon Hall3c0114c2020-08-11 15:07:42 -0700120 elif key == "useDocker":
121 self.useDocker = "True" == self.options[ key ]
122 elif key == "docker_prompt":
123 self.dockerPrompt = self.options[ key ]
Jon Hall9b0de1f2020-08-24 15:38:04 -0700124 elif key == "web_user":
125 self.webUser = self.options[ key ]
126 elif key == "web_pass":
127 self.webPass = self.options[ key ]
Jon Hallca319892017-06-15 15:25:22 -0700128
Jon Hall0e240372018-05-02 11:21:57 -0700129 self.home = self.checkOptions( self.home, "~/onos" )
130 self.karafUser = self.checkOptions( self.karafUser, self.user_name )
131 self.karafPass = self.checkOptions( self.karafPass, self.pwd )
Jon Hall9b0de1f2020-08-24 15:38:04 -0700132 self.webUser = self.checkOptions( self.webUser, "onos" )
133 self.webPass = self.checkOptions( self.webPass, "rocks" )
Jon Hallca319892017-06-15 15:25:22 -0700134 prefix = self.checkOptions( prefix, "ONOS" )
Jon Hall3c0114c2020-08-11 15:07:42 -0700135 self.useDocker = self.checkOptions( self.useDocker, False )
136 self.dockerPrompt = self.checkOptions( self.dockerPrompt, "~/onos#" )
Jon Hallca319892017-06-15 15:25:22 -0700137
138 self.name = self.options[ 'name' ]
139
140 # The 'nodes' tag is optional and it is not required in .topo file
141 for key in self.options:
142 if key == "nodes":
143 # Maximum number of ONOS nodes to run, if there is any
144 self.maxNodes = int( self.options[ 'nodes' ] )
145 break
146 self.maxNodes = None
147
148 if self.maxNodes is None or self.maxNodes == "":
149 self.maxNodes = 100
150
151 # Grabs all OC environment variables based on max number of nodes
152 # TODO: Also support giving an ip range as a compononet option
153 self.onosIps = {} # Dictionary of all possible ONOS ip
154
155 try:
156 if self.maxNodes:
157 for i in range( self.maxNodes ):
158 envString = "OC" + str( i + 1 )
159 # If there is no more OC# then break the loop
160 if os.getenv( envString ):
161 self.onosIps[ envString ] = os.getenv( envString )
162 else:
163 self.maxNodes = len( self.onosIps )
164 main.log.info( self.name +
165 ": Created cluster data with " +
166 str( self.maxNodes ) +
167 " maximum number" +
168 " of nodes" )
169 break
170
171 if not self.onosIps:
172 main.log.info( "Could not read any environment variable"
173 + " please load a cell file with all" +
174 " onos IP" )
175 self.maxNodes = None
176 else:
177 main.log.info( self.name + ": Found " +
178 str( self.onosIps.values() ) +
179 " ONOS IPs" )
180 except KeyError:
181 main.log.info( "Invalid environment variable" )
182 except Exception as inst:
183 main.log.error( "Uncaught exception: " + str( inst ) )
184
185 try:
186 if os.getenv( str( self.ip_address ) ) is not None:
187 self.ip_address = os.getenv( str( self.ip_address ) )
188 else:
189 main.log.info( self.name +
190 ": Trying to connect to " +
191 self.ip_address )
192 except KeyError:
193 main.log.info( "Invalid host name," +
194 " connecting to local host instead" )
195 self.ip_address = 'localhost'
196 except Exception as inst:
197 main.log.error( "Uncaught exception: " + str( inst ) )
198
199 self.handle = super( OnosClusterDriver, self ).connect(
200 user_name=self.user_name,
201 ip_address=self.ip_address,
202 port=self.port,
203 pwd=self.pwd,
204 home=self.home )
205
206 if self.handle:
207 self.handle.sendline( "cd " + self.home )
208 self.handle.expect( "\$" )
209 self.createComponents( prefix=prefix )
210 return self.handle
211 else:
212 main.log.info( "Failed to create ONOS handle" )
213 return main.FALSE
214 except pexpect.EOF:
215 main.log.error( self.name + ": EOF exception found" )
216 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700217 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700218 except Exception:
219 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700220 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700221
222 def disconnect( self ):
223 """
224 Called when Test is complete to disconnect the ONOS handle.
225 """
226 response = main.TRUE
227 try:
228 if self.handle:
229 self.handle.sendline( "" )
230 self.handle.expect( "\$" )
231 self.handle.sendline( "exit" )
232 self.handle.expect( "closed" )
233 except pexpect.EOF:
234 main.log.error( self.name + ": EOF exception found" )
235 main.log.error( self.name + ": " + self.handle.before )
236 except ValueError:
237 main.log.exception( "Exception in disconnect of " + self.name )
238 response = main.TRUE
239 except Exception:
240 main.log.exception( self.name + ": Connection failed to the host" )
241 response = main.FALSE
242 return response
243
Devin Lim142b5342017-07-20 15:22:39 -0700244 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700245 """
246 Parse the cluster options to create an ONOS cli component with the given name
247 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000248 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700249 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
250 if clihost == "True":
251 main.componentDictionary[ name ][ 'host' ] = host
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000252 main.componentDictionary[name]['type'] = "OnosCliDriver"
253 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
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 ) )