blob: af59a60d530a793294a4295b0b36501bd625b8aa [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 main.log.warn( "Rest driver has attribute '%s'" % ( name ) )
62 if not usedDriver:
63 usedDriver = True
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000064 main.log.debug("Using Rest driver's attribute for '%s'" % (name))
65 f = getattr( self.REST, name)
Jon Hallca319892017-06-15 15:25:22 -070066 if hasattr( self.CLI, name ):
Devin Lim142b5342017-07-20 15:22:39 -070067 main.log.warn( "CLI driver has attribute '%s'" % ( name ) )
68 if not usedDriver:
69 usedDriver = True
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000070 main.log.debug("Using CLI driver's attribute for '%s'" % (name))
71 f = getattr( self.CLI, name)
Jon Hallca319892017-06-15 15:25:22 -070072 if hasattr( self.Bench, name ):
Devin Lim142b5342017-07-20 15:22:39 -070073 main.log.warn( "Bench driver has attribute '%s'" % ( name ) )
74 if not usedDriver:
75 usedDriver = True
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +000076 main.log.debug("Using Bench driver's attribute for '%s'" % (name))
77 f = getattr( self.Bench, name)
Devin Lim142b5342017-07-20 15:22:39 -070078 if usedDriver:
79 return f
80 raise AttributeError( "Could not find the attribute %s in %r or it's component handles" % ( name, self ) )
Jon Hallca319892017-06-15 15:25:22 -070081
Jon Hall4173b242017-09-12 17:04:38 -070082 def __init__( self, name, ipAddress, CLI=None, REST=None, Bench=None, pos=None, userName=None, server=None ):
Jeremy Ronquillo82705492017-10-18 14:19:55 -070083 # TODO: validate these arguments
Jon Hallca319892017-06-15 15:25:22 -070084 self.name = str( name )
85 self.ipAddress = ipAddress
86 self.CLI = CLI
87 self.REST = REST
88 self.Bench = Bench
89 self.active = False
Devin Lim142b5342017-07-20 15:22:39 -070090 self.pos = pos
91 self.ip_address = ipAddress
92 self.user_name = userName
Jon Hall4173b242017-09-12 17:04:38 -070093 self.server = server
Jon Hallca319892017-06-15 15:25:22 -070094
95class OnosClusterDriver( CLI ):
96
97 def __init__( self ):
98 """
99 Initialize client
100 """
101 self.name = None
102 self.home = None
103 self.handle = None
104 self.nodes = []
105 super( OnosClusterDriver, self ).__init__()
106
107 def checkOptions( self, var, defaultVar ):
108 if var is None or var == "":
109 return defaultVar
110 return var
111
112 def connect( self, **connectargs ):
113 """
114 Creates ssh handle for ONOS "bench".
115 NOTE:
116 The ip_address would come from the topo file using the host tag, the
117 value can be an environment variable as well as a "localhost" to get
118 the ip address needed to ssh to the "bench"
119 """
120 try:
121 for key in connectargs:
122 vars( self )[ key ] = connectargs[ key ]
123 self.home = "~/onos"
124 for key in self.options:
125 if key == "home":
126 self.home = self.options[ 'home' ]
127 elif key == "karaf_username":
128 self.karafUser = self.options[ key ]
129 elif key == "karaf_password":
130 self.karafPass = self.options[ key ]
131 elif key == "cluster_name":
132 prefix = self.options[ key ]
133
Jeremy Ronquillo82705492017-10-18 14:19:55 -0700134 self.home = self.checkOptions(self.home, "~/onos" )
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000135 self.karafUser = self.checkOptions(self.karafUser, self.user_name)
136 self.karafPass = self.checkOptions(self.karafPass, self.pwd )
Jon Hallca319892017-06-15 15:25:22 -0700137 prefix = self.checkOptions( prefix, "ONOS" )
138
139 self.name = self.options[ 'name' ]
140
141 # The 'nodes' tag is optional and it is not required in .topo file
142 for key in self.options:
143 if key == "nodes":
144 # Maximum number of ONOS nodes to run, if there is any
145 self.maxNodes = int( self.options[ 'nodes' ] )
146 break
147 self.maxNodes = None
148
149 if self.maxNodes is None or self.maxNodes == "":
150 self.maxNodes = 100
151
152 # Grabs all OC environment variables based on max number of nodes
153 # TODO: Also support giving an ip range as a compononet option
154 self.onosIps = {} # Dictionary of all possible ONOS ip
155
156 try:
157 if self.maxNodes:
158 for i in range( self.maxNodes ):
159 envString = "OC" + str( i + 1 )
160 # If there is no more OC# then break the loop
161 if os.getenv( envString ):
162 self.onosIps[ envString ] = os.getenv( envString )
163 else:
164 self.maxNodes = len( self.onosIps )
165 main.log.info( self.name +
166 ": Created cluster data with " +
167 str( self.maxNodes ) +
168 " maximum number" +
169 " of nodes" )
170 break
171
172 if not self.onosIps:
173 main.log.info( "Could not read any environment variable"
174 + " please load a cell file with all" +
175 " onos IP" )
176 self.maxNodes = None
177 else:
178 main.log.info( self.name + ": Found " +
179 str( self.onosIps.values() ) +
180 " ONOS IPs" )
181 except KeyError:
182 main.log.info( "Invalid environment variable" )
183 except Exception as inst:
184 main.log.error( "Uncaught exception: " + str( inst ) )
185
186 try:
187 if os.getenv( str( self.ip_address ) ) is not None:
188 self.ip_address = os.getenv( str( self.ip_address ) )
189 else:
190 main.log.info( self.name +
191 ": Trying to connect to " +
192 self.ip_address )
193 except KeyError:
194 main.log.info( "Invalid host name," +
195 " connecting to local host instead" )
196 self.ip_address = 'localhost'
197 except Exception as inst:
198 main.log.error( "Uncaught exception: " + str( inst ) )
199
200 self.handle = super( OnosClusterDriver, self ).connect(
201 user_name=self.user_name,
202 ip_address=self.ip_address,
203 port=self.port,
204 pwd=self.pwd,
205 home=self.home )
206
207 if self.handle:
208 self.handle.sendline( "cd " + self.home )
209 self.handle.expect( "\$" )
210 self.createComponents( prefix=prefix )
211 return self.handle
212 else:
213 main.log.info( "Failed to create ONOS handle" )
214 return main.FALSE
215 except pexpect.EOF:
216 main.log.error( self.name + ": EOF exception found" )
217 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700218 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700219 except Exception:
220 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700221 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700222
223 def disconnect( self ):
224 """
225 Called when Test is complete to disconnect the ONOS handle.
226 """
227 response = main.TRUE
228 try:
229 if self.handle:
230 self.handle.sendline( "" )
231 self.handle.expect( "\$" )
232 self.handle.sendline( "exit" )
233 self.handle.expect( "closed" )
234 except pexpect.EOF:
235 main.log.error( self.name + ": EOF exception found" )
236 main.log.error( self.name + ": " + self.handle.before )
237 except ValueError:
238 main.log.exception( "Exception in disconnect of " + self.name )
239 response = main.TRUE
240 except Exception:
241 main.log.exception( self.name + ": Connection failed to the host" )
242 response = main.FALSE
243 return response
244
Devin Lim142b5342017-07-20 15:22:39 -0700245 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700246 """
247 Parse the cluster options to create an ONOS cli component with the given name
248 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000249 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700250 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
251 if clihost == "True":
252 main.componentDictionary[ name ][ 'host' ] = host
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000253 main.componentDictionary[name]['type'] = "OnosCliDriver"
254 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
255 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700256
Devin Lim142b5342017-07-20 15:22:39 -0700257 def createCliComponent( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700258 """
259 Creates a new onos cli component.
260
261 Arguments:
262 name - The string of the name of this component. The new component
263 will be assigned to main.<name> .
264 In addition, main.<name>.name = str( name )
265 """
266 try:
267 # look to see if this component already exists
268 getattr( main, name )
269 except AttributeError:
270 # namespace is clear, creating component
Devin Lim142b5342017-07-20 15:22:39 -0700271 self.setCliOptions( name, host )
Jon Hallca319892017-06-15 15:25:22 -0700272 return main.componentInit( name )
273 except pexpect.EOF:
274 main.log.error( self.name + ": EOF exception found" )
275 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700276 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700277 except Exception:
278 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700279 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700280 else:
281 # namespace is not clear!
282 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700283 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700284
285 def setRestOptions( self, name, host ):
286 """
287 Parse the cluster options to create an ONOS cli component with the given name
288 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000289 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
290 main.log.debug( main.componentDictionary[name] )
291 user = main.componentDictionary[name]['COMPONENTS'].get( "web_user", "onos" )
292 main.componentDictionary[name]['user'] = self.checkOptions( user, "onos" )
293 password = main.componentDictionary[name]['COMPONENTS'].get( "web_pass", "rocks" )
294 main.componentDictionary[name]['pass'] = self.checkOptions( password, "rocks" )
295 main.componentDictionary[name]['host'] = host
296 port = main.componentDictionary[name]['COMPONENTS'].get( "rest_port", "8181" )
297 main.componentDictionary[name]['port'] = self.checkOptions( port, "8181" )
298 main.componentDictionary[name]['type'] = "OnosRestDriver"
299 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
300 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700301
302 def createRestComponent( self, name, ipAddress ):
303 """
304 Creates a new onos rest component.
305
306 Arguments:
307 name - The string of the name of this component. The new component
308 will be assigned to main.<name> .
309 In addition, main.<name>.name = str( name )
310 """
311 try:
312 # look to see if this component already exists
313 getattr( main, name )
314 except AttributeError:
315 # namespace is clear, creating component
316 self.setRestOptions( name, ipAddress )
317 return main.componentInit( name )
318 except pexpect.EOF:
319 main.log.error( self.name + ": EOF exception found" )
320 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700321 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700322 except Exception:
323 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700324 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700325 else:
326 # namespace is not clear!
327 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700328 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700329
330 def setBenchOptions( self, name ):
331 """
332 Parse the cluster options to create an ONOS "bench" component with the given name
333 """
Jeremy Ronquillo4d5f1d02017-10-13 20:23:57 +0000334 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
335 main.componentDictionary[name]['type'] = "OnosDriver"
336 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
337 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
338 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
339 main.log.debug( main.componentDictionary[name] )
Jon Hallca319892017-06-15 15:25:22 -0700340
341 def createBenchComponent( self, name ):
342 """
343 Creates a new onos "bench" component.
344
345 Arguments:
346 name - The string of the name of this component. The new component
347 will be assigned to main.<name> .
348 In addition, main.<name>.name = str( name )
349 """
350 try:
351 # look to see if this component already exists
352 getattr( main, name )
353 except AttributeError:
354 # namespace is clear, creating component
355 self.setBenchOptions( name )
356 return main.componentInit( name )
357 except pexpect.EOF:
358 main.log.error( self.name + ": EOF exception found" )
359 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700360 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700361 except Exception:
362 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700363 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700364 else:
365 # namespace is not clear!
366 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700367 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700368
Jon Hall4173b242017-09-12 17:04:38 -0700369 def setServerOptions( self, name, ipAddress ):
370 """
371 Parse the cluster options to create an ONOS "server" component with the given name
372
373 Arguments:
374 name - The name of the server componet
375 ipAddress - The ip address of the server
376 """
377 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
378 main.componentDictionary[name]['type'] = "OnosDriver"
379 main.componentDictionary[name]['host'] = ipAddress
380 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
381 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
382 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 ) )