blob: 83d429523090c24ee4d305b0922d3c34d186e477 [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 """
Devin Lim142b5342017-07-20 15:22:39 -070059 usedDriver = False
Jon Hallca319892017-06-15 15:25:22 -070060 if hasattr( self.REST, name ):
Devin Lim142b5342017-07-20 15:22:39 -070061 if not usedDriver:
62 usedDriver = True
Jon Hall0e240372018-05-02 11:21:57 -070063 main.log.debug( "%s: Using Rest driver's attribute for '%s'" % ( self.name, name ) )
64 f = getattr( self.REST, name )
Jon Hallca319892017-06-15 15:25:22 -070065 if hasattr( self.CLI, name ):
Devin Lim142b5342017-07-20 15:22:39 -070066 if not usedDriver:
67 usedDriver = True
Jon Hall0e240372018-05-02 11:21:57 -070068 main.log.debug( "%s: Using CLI driver's attribute for '%s'" % ( self.name, name ) )
69 f = getattr( self.CLI, name )
Jon Hallca319892017-06-15 15:25:22 -070070 if hasattr( self.Bench, name ):
Devin Lim142b5342017-07-20 15:22:39 -070071 if not usedDriver:
72 usedDriver = True
Jon Hall0e240372018-05-02 11:21:57 -070073 main.log.debug( "%s: Using Bench driver's attribute for '%s'" % ( self.name, name ) )
74 f = getattr( self.Bench, name )
Devin Lim142b5342017-07-20 15:22:39 -070075 if usedDriver:
76 return f
77 raise AttributeError( "Could not find the attribute %s in %r or it's component handles" % ( name, self ) )
Jon Hallca319892017-06-15 15:25:22 -070078
Jon Hall4173b242017-09-12 17:04:38 -070079 def __init__( self, name, ipAddress, CLI=None, REST=None, Bench=None, pos=None, userName=None, server=None ):
Jeremy Ronquillo82705492017-10-18 14:19:55 -070080 # TODO: validate these arguments
Jon Hallca319892017-06-15 15:25:22 -070081 self.name = str( name )
82 self.ipAddress = ipAddress
83 self.CLI = CLI
84 self.REST = REST
85 self.Bench = Bench
86 self.active = False
Devin Lim142b5342017-07-20 15:22:39 -070087 self.pos = pos
88 self.ip_address = ipAddress
89 self.user_name = userName
Jon Hall4173b242017-09-12 17:04:38 -070090 self.server = server
Jon Hallca319892017-06-15 15:25:22 -070091
92class OnosClusterDriver( CLI ):
93
94 def __init__( self ):
95 """
96 Initialize client
97 """
98 self.name = None
99 self.home = None
100 self.handle = None
101 self.nodes = []
102 super( OnosClusterDriver, self ).__init__()
103
104 def checkOptions( self, var, defaultVar ):
105 if var is None or var == "":
106 return defaultVar
107 return var
108
109 def connect( self, **connectargs ):
110 """
111 Creates ssh handle for ONOS "bench".
112 NOTE:
113 The ip_address would come from the topo file using the host tag, the
114 value can be an environment variable as well as a "localhost" to get
115 the ip address needed to ssh to the "bench"
116 """
117 try:
118 for key in connectargs:
119 vars( self )[ key ] = connectargs[ key ]
120 self.home = "~/onos"
121 for key in self.options:
122 if key == "home":
123 self.home = self.options[ 'home' ]
124 elif key == "karaf_username":
125 self.karafUser = self.options[ key ]
126 elif key == "karaf_password":
127 self.karafPass = self.options[ key ]
128 elif key == "cluster_name":
129 prefix = self.options[ key ]
130
Jon Hall0e240372018-05-02 11:21:57 -0700131 self.home = self.checkOptions( self.home, "~/onos" )
132 self.karafUser = self.checkOptions( self.karafUser, self.user_name )
133 self.karafPass = self.checkOptions( self.karafPass, self.pwd )
Jon Hallca319892017-06-15 15:25:22 -0700134 prefix = self.checkOptions( prefix, "ONOS" )
135
136 self.name = self.options[ 'name' ]
137
138 # The 'nodes' tag is optional and it is not required in .topo file
139 for key in self.options:
140 if key == "nodes":
141 # Maximum number of ONOS nodes to run, if there is any
142 self.maxNodes = int( self.options[ 'nodes' ] )
143 break
144 self.maxNodes = None
145
146 if self.maxNodes is None or self.maxNodes == "":
147 self.maxNodes = 100
148
149 # Grabs all OC environment variables based on max number of nodes
150 # TODO: Also support giving an ip range as a compononet option
151 self.onosIps = {} # Dictionary of all possible ONOS ip
152
153 try:
154 if self.maxNodes:
155 for i in range( self.maxNodes ):
156 envString = "OC" + str( i + 1 )
157 # If there is no more OC# then break the loop
158 if os.getenv( envString ):
159 self.onosIps[ envString ] = os.getenv( envString )
160 else:
161 self.maxNodes = len( self.onosIps )
162 main.log.info( self.name +
163 ": Created cluster data with " +
164 str( self.maxNodes ) +
165 " maximum number" +
166 " of nodes" )
167 break
168
169 if not self.onosIps:
170 main.log.info( "Could not read any environment variable"
171 + " please load a cell file with all" +
172 " onos IP" )
173 self.maxNodes = None
174 else:
175 main.log.info( self.name + ": Found " +
176 str( self.onosIps.values() ) +
177 " ONOS IPs" )
178 except KeyError:
179 main.log.info( "Invalid environment variable" )
180 except Exception as inst:
181 main.log.error( "Uncaught exception: " + str( inst ) )
182
183 try:
184 if os.getenv( str( self.ip_address ) ) is not None:
185 self.ip_address = os.getenv( str( self.ip_address ) )
186 else:
187 main.log.info( self.name +
188 ": Trying to connect to " +
189 self.ip_address )
190 except KeyError:
191 main.log.info( "Invalid host name," +
192 " connecting to local host instead" )
193 self.ip_address = 'localhost'
194 except Exception as inst:
195 main.log.error( "Uncaught exception: " + str( inst ) )
196
197 self.handle = super( OnosClusterDriver, self ).connect(
198 user_name=self.user_name,
199 ip_address=self.ip_address,
200 port=self.port,
201 pwd=self.pwd,
202 home=self.home )
203
204 if self.handle:
205 self.handle.sendline( "cd " + self.home )
206 self.handle.expect( "\$" )
207 self.createComponents( prefix=prefix )
208 return self.handle
209 else:
210 main.log.info( "Failed to create ONOS handle" )
211 return main.FALSE
212 except pexpect.EOF:
213 main.log.error( self.name + ": EOF exception found" )
214 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700215 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700216 except Exception:
217 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700218 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700219
220 def disconnect( self ):
221 """
222 Called when Test is complete to disconnect the ONOS handle.
223 """
224 response = main.TRUE
225 try:
226 if self.handle:
227 self.handle.sendline( "" )
228 self.handle.expect( "\$" )
229 self.handle.sendline( "exit" )
230 self.handle.expect( "closed" )
231 except pexpect.EOF:
232 main.log.error( self.name + ": EOF exception found" )
233 main.log.error( self.name + ": " + self.handle.before )
234 except ValueError:
235 main.log.exception( "Exception in disconnect of " + self.name )
236 response = main.TRUE
237 except Exception:
238 main.log.exception( self.name + ": Connection failed to the host" )
239 response = main.FALSE
240 return response
241
Devin Lim142b5342017-07-20 15:22:39 -0700242 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700243 """
244 Parse the cluster options to create an ONOS cli component with the given name
245 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000246 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700247 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
248 if clihost == "True":
249 main.componentDictionary[ name ][ 'host' ] = host
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000250 main.componentDictionary[name]['type'] = "OnosCliDriver"
251 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
252 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700253
Devin Lim142b5342017-07-20 15:22:39 -0700254 def createCliComponent( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700255 """
256 Creates a new onos cli component.
257
258 Arguments:
259 name - The string of the name of this component. The new component
260 will be assigned to main.<name> .
261 In addition, main.<name>.name = str( name )
262 """
263 try:
264 # look to see if this component already exists
265 getattr( main, name )
266 except AttributeError:
267 # namespace is clear, creating component
Devin Lim142b5342017-07-20 15:22:39 -0700268 self.setCliOptions( name, host )
Jon Hallca319892017-06-15 15:25:22 -0700269 return main.componentInit( name )
270 except pexpect.EOF:
271 main.log.error( self.name + ": EOF exception found" )
272 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700273 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700274 except Exception:
275 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700276 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700277 else:
278 # namespace is not clear!
279 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700280 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700281
282 def setRestOptions( self, name, host ):
283 """
284 Parse the cluster options to create an ONOS cli component with the given name
285 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000286 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
287 main.log.debug( main.componentDictionary[name] )
288 user = main.componentDictionary[name]['COMPONENTS'].get( "web_user", "onos" )
289 main.componentDictionary[name]['user'] = self.checkOptions( user, "onos" )
290 password = main.componentDictionary[name]['COMPONENTS'].get( "web_pass", "rocks" )
You Wangbef7ea12018-09-21 13:15:12 -0700291 main.componentDictionary[name]['password'] = self.checkOptions( password, "rocks" )
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000292 main.componentDictionary[name]['host'] = host
293 port = main.componentDictionary[name]['COMPONENTS'].get( "rest_port", "8181" )
294 main.componentDictionary[name]['port'] = self.checkOptions( port, "8181" )
295 main.componentDictionary[name]['type'] = "OnosRestDriver"
296 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
297 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700298
299 def createRestComponent( self, name, ipAddress ):
300 """
301 Creates a new onos rest component.
302
303 Arguments:
304 name - The string of the name of this component. The new component
305 will be assigned to main.<name> .
306 In addition, main.<name>.name = str( name )
307 """
308 try:
309 # look to see if this component already exists
310 getattr( main, name )
311 except AttributeError:
312 # namespace is clear, creating component
313 self.setRestOptions( name, ipAddress )
314 return main.componentInit( name )
315 except pexpect.EOF:
316 main.log.error( self.name + ": EOF exception found" )
317 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700318 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700319 except Exception:
320 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700321 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700322 else:
323 # namespace is not clear!
324 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700325 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700326
327 def setBenchOptions( self, name ):
328 """
329 Parse the cluster options to create an ONOS "bench" component with the given name
330 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000331 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
332 main.componentDictionary[name]['type'] = "OnosDriver"
333 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
334 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
335 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
336 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700337
338 def createBenchComponent( self, name ):
339 """
340 Creates a new onos "bench" component.
341
342 Arguments:
343 name - The string of the name of this component. The new component
344 will be assigned to main.<name> .
345 In addition, main.<name>.name = str( name )
346 """
347 try:
348 # look to see if this component already exists
349 getattr( main, name )
350 except AttributeError:
351 # namespace is clear, creating component
352 self.setBenchOptions( name )
353 return main.componentInit( name )
354 except pexpect.EOF:
355 main.log.error( self.name + ": EOF exception found" )
356 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700357 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700358 except Exception:
359 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700360 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700361 else:
362 # namespace is not clear!
363 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700364 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700365
Jon Hall4173b242017-09-12 17:04:38 -0700366 def setServerOptions( self, name, ipAddress ):
367 """
368 Parse the cluster options to create an ONOS "server" component with the given name
369
370 Arguments:
371 name - The name of the server componet
372 ipAddress - The ip address of the server
373 """
374 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
375 main.componentDictionary[name]['type'] = "OnosDriver"
376 main.componentDictionary[name]['host'] = ipAddress
377 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
378 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
You Wang4cc61912018-08-28 10:10:58 -0700379 # TODO: for now we use karaf user name and password also for logging to the onos nodes
380 main.componentDictionary[name]['user'] = self.karafUser
381 main.componentDictionary[name]['password'] = self.karafPass
Jon Hall4173b242017-09-12 17:04:38 -0700382 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
383 main.log.debug( main.componentDictionary[name] )
384
Jon Hall4173b242017-09-12 17:04:38 -0700385 def createServerComponent( self, name, ipAddress ):
386 """
387 Creates a new onos "server" component. This will be connected to the
388 node ONOS is running on.
389
390 Arguments:
391 name - The string of the name of this component. The new component
392 will be assigned to main.<name> .
393 In addition, main.<name>.name = str( name )
394 ipAddress - The ip address of the server
395 """
396 try:
397 # look to see if this component already exists
398 getattr( main, name )
399 except AttributeError:
400 # namespace is clear, creating component
401 self.setServerOptions( name, ipAddress )
402 return main.componentInit( name )
403 except pexpect.EOF:
404 main.log.error( self.name + ": EOF exception found" )
405 main.log.error( self.name + ": " + self.handle.before )
406 main.cleanAndExit()
407 except Exception:
408 main.log.exception( self.name + ": Uncaught exception!" )
409 main.cleanAndExit()
410 else:
411 # namespace is not clear!
412 main.log.error( name + " component already exists!" )
413 main.cleanAndExit()
414
Jon Hall4173b242017-09-12 17:04:38 -0700415 def createComponents( self, prefix='', createServer=True ):
Jon Hallca319892017-06-15 15:25:22 -0700416 """
417 Creates a CLI and REST component for each nodes in the cluster
418 """
419 # TODO: This needs work to support starting two seperate clusters in one test
420 cliPrefix = prefix + "cli"
421 restPrefix = prefix + "rest"
422 benchPrefix = prefix + "bench"
Jon Hall4173b242017-09-12 17:04:38 -0700423 serverPrefix = prefix + "server"
Jon Hallca319892017-06-15 15:25:22 -0700424 for i in xrange( 1, self.maxNodes + 1 ):
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000425 cliName = cliPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700426 restName = restPrefix + str( i )
427 benchName = benchPrefix + str( i )
Jon Hall4173b242017-09-12 17:04:38 -0700428 serverName = serverPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700429
430 # Unfortunately this means we need to have a cell set beofre running TestON,
431 # Even if it is just the entire possible cluster size
432 ip = self.onosIps[ 'OC' + str( i ) ]
433
Devin Lim142b5342017-07-20 15:22:39 -0700434 cli = self.createCliComponent( cliName, ip )
Jon Hallca319892017-06-15 15:25:22 -0700435 rest = self.createRestComponent( restName, ip )
436 bench = self.createBenchComponent( benchName )
Jon Hall4173b242017-09-12 17:04:38 -0700437 server = self.createServerComponent( serverName, ip ) if createServer else None
438 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1, self.user_name, server=server ) )