blob: 11c44ae8cd5d116a5a19e545c9bed219009937ae [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 Halle37bd1f2020-09-10 12:16:41 -070096 self.maxNodes = None
Jon Hallca319892017-06-15 15:25:22 -070097 self.nodes = []
98 super( OnosClusterDriver, self ).__init__()
99
Jon Hallca319892017-06-15 15:25:22 -0700100 def connect( self, **connectargs ):
101 """
102 Creates ssh handle for ONOS "bench".
103 NOTE:
104 The ip_address would come from the topo file using the host tag, the
105 value can be an environment variable as well as a "localhost" to get
106 the ip address needed to ssh to the "bench"
107 """
108 try:
109 for key in connectargs:
110 vars( self )[ key ] = connectargs[ key ]
111 self.home = "~/onos"
112 for key in self.options:
113 if key == "home":
Jon Halle37bd1f2020-09-10 12:16:41 -0700114 self.home = self.options[ key ]
Jon Hallca319892017-06-15 15:25:22 -0700115 elif key == "karaf_username":
116 self.karafUser = self.options[ key ]
117 elif key == "karaf_password":
118 self.karafPass = self.options[ key ]
119 elif key == "cluster_name":
120 prefix = self.options[ key ]
Jon Hall3c0114c2020-08-11 15:07:42 -0700121 elif key == "useDocker":
122 self.useDocker = "True" == self.options[ key ]
123 elif key == "docker_prompt":
124 self.dockerPrompt = self.options[ key ]
Jon Hall9b0de1f2020-08-24 15:38:04 -0700125 elif key == "web_user":
126 self.webUser = self.options[ key ]
127 elif key == "web_pass":
128 self.webPass = self.options[ key ]
Jon Halle37bd1f2020-09-10 12:16:41 -0700129 elif key == "nodes":
130 # Maximum number of ONOS nodes to run, if there is any
131 self.maxNodes = self.options[ key ]
Jon Hallca319892017-06-15 15:25:22 -0700132
Jon Hall0e240372018-05-02 11:21:57 -0700133 self.home = self.checkOptions( self.home, "~/onos" )
134 self.karafUser = self.checkOptions( self.karafUser, self.user_name )
135 self.karafPass = self.checkOptions( self.karafPass, self.pwd )
Jon Hall9b0de1f2020-08-24 15:38:04 -0700136 self.webUser = self.checkOptions( self.webUser, "onos" )
137 self.webPass = self.checkOptions( self.webPass, "rocks" )
Jon Hallca319892017-06-15 15:25:22 -0700138 prefix = self.checkOptions( prefix, "ONOS" )
Jon Hall3c0114c2020-08-11 15:07:42 -0700139 self.useDocker = self.checkOptions( self.useDocker, False )
140 self.dockerPrompt = self.checkOptions( self.dockerPrompt, "~/onos#" )
Jon Halle37bd1f2020-09-10 12:16:41 -0700141 self.maxNodes = int( self.checkOptions( self.maxNodes, 100 ) )
Jon Hallca319892017-06-15 15:25:22 -0700142
143 self.name = self.options[ 'name' ]
144
Jon Hallca319892017-06-15 15:25:22 -0700145 # Grabs all OC environment variables based on max number of nodes
146 # TODO: Also support giving an ip range as a compononet option
147 self.onosIps = {} # Dictionary of all possible ONOS ip
148
149 try:
150 if self.maxNodes:
151 for i in range( self.maxNodes ):
152 envString = "OC" + str( i + 1 )
153 # If there is no more OC# then break the loop
154 if os.getenv( envString ):
155 self.onosIps[ envString ] = os.getenv( envString )
156 else:
157 self.maxNodes = len( self.onosIps )
158 main.log.info( self.name +
159 ": Created cluster data with " +
160 str( self.maxNodes ) +
161 " maximum number" +
162 " of nodes" )
163 break
164
165 if not self.onosIps:
166 main.log.info( "Could not read any environment variable"
167 + " please load a cell file with all" +
168 " onos IP" )
169 self.maxNodes = None
170 else:
171 main.log.info( self.name + ": Found " +
172 str( self.onosIps.values() ) +
173 " ONOS IPs" )
174 except KeyError:
175 main.log.info( "Invalid environment variable" )
176 except Exception as inst:
177 main.log.error( "Uncaught exception: " + str( inst ) )
178
179 try:
180 if os.getenv( str( self.ip_address ) ) is not None:
181 self.ip_address = os.getenv( str( self.ip_address ) )
182 else:
183 main.log.info( self.name +
184 ": Trying to connect to " +
185 self.ip_address )
186 except KeyError:
187 main.log.info( "Invalid host name," +
188 " connecting to local host instead" )
189 self.ip_address = 'localhost'
190 except Exception as inst:
191 main.log.error( "Uncaught exception: " + str( inst ) )
192
193 self.handle = super( OnosClusterDriver, self ).connect(
194 user_name=self.user_name,
195 ip_address=self.ip_address,
196 port=self.port,
197 pwd=self.pwd,
198 home=self.home )
199
200 if self.handle:
201 self.handle.sendline( "cd " + self.home )
202 self.handle.expect( "\$" )
203 self.createComponents( prefix=prefix )
204 return self.handle
205 else:
206 main.log.info( "Failed to create ONOS handle" )
207 return main.FALSE
208 except pexpect.EOF:
209 main.log.error( self.name + ": EOF exception found" )
210 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700211 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700212 except Exception:
213 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700214 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700215
216 def disconnect( self ):
217 """
218 Called when Test is complete to disconnect the ONOS handle.
219 """
220 response = main.TRUE
221 try:
222 if self.handle:
223 self.handle.sendline( "" )
224 self.handle.expect( "\$" )
225 self.handle.sendline( "exit" )
226 self.handle.expect( "closed" )
227 except pexpect.EOF:
228 main.log.error( self.name + ": EOF exception found" )
229 main.log.error( self.name + ": " + self.handle.before )
230 except ValueError:
231 main.log.exception( "Exception in disconnect of " + self.name )
232 response = main.TRUE
233 except Exception:
234 main.log.exception( self.name + ": Connection failed to the host" )
235 response = main.FALSE
236 return response
237
Devin Lim142b5342017-07-20 15:22:39 -0700238 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700239 """
240 Parse the cluster options to create an ONOS cli component with the given name
241 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000242 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700243 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
244 if clihost == "True":
245 main.componentDictionary[ name ][ 'host' ] = host
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000246 main.componentDictionary[name]['type'] = "OnosCliDriver"
247 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
Jon Hallca319892017-06-15 15:25:22 -0700248
Devin Lim142b5342017-07-20 15:22:39 -0700249 def createCliComponent( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700250 """
251 Creates a new onos cli component.
252
253 Arguments:
254 name - The string of the name of this component. The new component
255 will be assigned to main.<name> .
256 In addition, main.<name>.name = str( name )
257 """
258 try:
259 # look to see if this component already exists
260 getattr( main, name )
261 except AttributeError:
262 # namespace is clear, creating component
Devin Lim142b5342017-07-20 15:22:39 -0700263 self.setCliOptions( name, host )
Jon Hallca319892017-06-15 15:25:22 -0700264 return main.componentInit( name )
265 except pexpect.EOF:
266 main.log.error( self.name + ": EOF exception found" )
267 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700268 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700269 except Exception:
270 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700271 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700272 else:
273 # namespace is not clear!
274 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700275 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700276
277 def setRestOptions( self, name, host ):
278 """
279 Parse the cluster options to create an ONOS cli component with the given name
280 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000281 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
282 main.log.debug( main.componentDictionary[name] )
283 user = main.componentDictionary[name]['COMPONENTS'].get( "web_user", "onos" )
284 main.componentDictionary[name]['user'] = self.checkOptions( user, "onos" )
285 password = main.componentDictionary[name]['COMPONENTS'].get( "web_pass", "rocks" )
You Wangbef7ea12018-09-21 13:15:12 -0700286 main.componentDictionary[name]['password'] = self.checkOptions( password, "rocks" )
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000287 main.componentDictionary[name]['host'] = host
288 port = main.componentDictionary[name]['COMPONENTS'].get( "rest_port", "8181" )
289 main.componentDictionary[name]['port'] = self.checkOptions( port, "8181" )
290 main.componentDictionary[name]['type'] = "OnosRestDriver"
291 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
292 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700293
294 def createRestComponent( self, name, ipAddress ):
295 """
296 Creates a new onos rest component.
297
298 Arguments:
299 name - The string of the name of this component. The new component
300 will be assigned to main.<name> .
301 In addition, main.<name>.name = str( name )
302 """
303 try:
304 # look to see if this component already exists
305 getattr( main, name )
306 except AttributeError:
307 # namespace is clear, creating component
308 self.setRestOptions( name, ipAddress )
309 return main.componentInit( name )
310 except pexpect.EOF:
311 main.log.error( self.name + ": EOF exception found" )
312 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700313 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700314 except Exception:
315 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700316 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700317 else:
318 # namespace is not clear!
319 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700320 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700321
322 def setBenchOptions( self, name ):
323 """
324 Parse the cluster options to create an ONOS "bench" component with the given name
325 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000326 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
327 main.componentDictionary[name]['type'] = "OnosDriver"
328 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
329 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
330 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
331 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700332
333 def createBenchComponent( self, name ):
334 """
335 Creates a new onos "bench" component.
336
337 Arguments:
338 name - The string of the name of this component. The new component
339 will be assigned to main.<name> .
340 In addition, main.<name>.name = str( name )
341 """
342 try:
343 # look to see if this component already exists
344 getattr( main, name )
345 except AttributeError:
346 # namespace is clear, creating component
347 self.setBenchOptions( name )
348 return main.componentInit( name )
349 except pexpect.EOF:
350 main.log.error( self.name + ": EOF exception found" )
351 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700352 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700353 except Exception:
354 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700355 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700356 else:
357 # namespace is not clear!
358 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700359 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700360
Jon Hall4173b242017-09-12 17:04:38 -0700361 def setServerOptions( self, name, ipAddress ):
362 """
363 Parse the cluster options to create an ONOS "server" component with the given name
364
365 Arguments:
366 name - The name of the server componet
367 ipAddress - The ip address of the server
368 """
369 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
370 main.componentDictionary[name]['type'] = "OnosDriver"
371 main.componentDictionary[name]['host'] = ipAddress
372 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
373 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
You Wang4cc61912018-08-28 10:10:58 -0700374 # TODO: for now we use karaf user name and password also for logging to the onos nodes
375 main.componentDictionary[name]['user'] = self.karafUser
376 main.componentDictionary[name]['password'] = self.karafPass
Jon Hall4173b242017-09-12 17:04:38 -0700377 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
378 main.log.debug( main.componentDictionary[name] )
379
Jon Hall4173b242017-09-12 17:04:38 -0700380 def createServerComponent( self, name, ipAddress ):
381 """
382 Creates a new onos "server" component. This will be connected to the
383 node ONOS is running on.
384
385 Arguments:
386 name - The string of the name of this component. The new component
387 will be assigned to main.<name> .
388 In addition, main.<name>.name = str( name )
389 ipAddress - The ip address of the server
390 """
391 try:
392 # look to see if this component already exists
393 getattr( main, name )
394 except AttributeError:
395 # namespace is clear, creating component
396 self.setServerOptions( name, ipAddress )
397 return main.componentInit( name )
398 except pexpect.EOF:
399 main.log.error( self.name + ": EOF exception found" )
400 main.log.error( self.name + ": " + self.handle.before )
401 main.cleanAndExit()
402 except Exception:
403 main.log.exception( self.name + ": Uncaught exception!" )
404 main.cleanAndExit()
405 else:
406 # namespace is not clear!
407 main.log.error( name + " component already exists!" )
408 main.cleanAndExit()
409
Jon Hall4173b242017-09-12 17:04:38 -0700410 def createComponents( self, prefix='', createServer=True ):
Jon Hallca319892017-06-15 15:25:22 -0700411 """
412 Creates a CLI and REST component for each nodes in the cluster
413 """
414 # TODO: This needs work to support starting two seperate clusters in one test
415 cliPrefix = prefix + "cli"
416 restPrefix = prefix + "rest"
417 benchPrefix = prefix + "bench"
Jon Hall4173b242017-09-12 17:04:38 -0700418 serverPrefix = prefix + "server"
Jon Hallca319892017-06-15 15:25:22 -0700419 for i in xrange( 1, self.maxNodes + 1 ):
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000420 cliName = cliPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700421 restName = restPrefix + str( i )
422 benchName = benchPrefix + str( i )
Jon Hall4173b242017-09-12 17:04:38 -0700423 serverName = serverPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700424
425 # Unfortunately this means we need to have a cell set beofre running TestON,
426 # Even if it is just the entire possible cluster size
427 ip = self.onosIps[ 'OC' + str( i ) ]
428
Devin Lim142b5342017-07-20 15:22:39 -0700429 cli = self.createCliComponent( cliName, ip )
Jon Hallca319892017-06-15 15:25:22 -0700430 rest = self.createRestComponent( restName, ip )
431 bench = self.createBenchComponent( benchName )
Jon Hall4173b242017-09-12 17:04:38 -0700432 server = self.createServerComponent( serverName, ip ) if createServer else None
Jon Hall3c0114c2020-08-11 15:07:42 -0700433 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1,
434 self.user_name, server=server,
435 dockerPrompt=self.dockerPrompt ) )