blob: 4be6191d9123669e2fabedebf484e786311a33fe [file] [log] [blame]
Jon Hallca319892017-06-15 15:25:22 -07001#!/usr/bin/env python
2"""
3Copyright 2017 Open Networking Foundation (ONF)
4
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
12 (at your option) any later version.
13
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():
37 def __str__( self ):
38 return self.name
39 def __repr__( self ):
40 #TODO use repr() for components?
41 return "%s<IP=%s, CLI=%s, REST=%s, Bench=%s >" % ( self.name,
42 self.ipAddress,
43 self.CLI,
44 self.REST,
45 self.Bench )
46
47 def __getattr__( self, name ):
48 """
49 Called when an attribute lookup has not found the attribute
50 in the usual places (i.e. it is not an instance attribute nor
51 is it found in the class tree for self). name is the attribute
52 name. This method should return the (computed) attribute value
53 or raise an AttributeError exception.
54
55 We will look into each of the node's component handles to try to find the attreibute, looking at REST first
56 """
Devin Lim142b5342017-07-20 15:22:39 -070057 usedDriver = False
Jon Hallca319892017-06-15 15:25:22 -070058 if hasattr( self.REST, name ):
Devin Lim142b5342017-07-20 15:22:39 -070059 main.log.warn( "Rest driver has attribute '%s'" % ( name ) )
60 if not usedDriver:
61 usedDriver = True
62 main.log.debug("Using Rest driver's attribute for '%s'" % (name))
63 f = getattr( self.REST, name)
Jon Hallca319892017-06-15 15:25:22 -070064 if hasattr( self.CLI, name ):
Devin Lim142b5342017-07-20 15:22:39 -070065 main.log.warn( "CLI driver has attribute '%s'" % ( name ) )
66 if not usedDriver:
67 usedDriver = True
68 main.log.debug("Using CLI driver's attribute for '%s'" % (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 main.log.warn( "Bench driver has attribute '%s'" % ( name ) )
72 if not usedDriver:
73 usedDriver = True
74 main.log.debug("Using Bench driver's attribute for '%s'" % (name))
75 f = getattr( self.Bench, name)
76 if usedDriver:
77 return f
78 raise AttributeError( "Could not find the attribute %s in %r or it's component handles" % ( name, self ) )
Jon Hallca319892017-06-15 15:25:22 -070079
80
81
Devin Lim142b5342017-07-20 15:22:39 -070082 def __init__( self, name, ipAddress, CLI=None, REST=None, Bench=None, pos=None, userName=None ):
Jon Hallca319892017-06-15 15:25:22 -070083 #TODO: validate these arguments
84 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 Hallca319892017-06-15 15:25:22 -070093
94class OnosClusterDriver( CLI ):
95
96 def __init__( self ):
97 """
98 Initialize client
99 """
100 self.name = None
101 self.home = None
102 self.handle = None
103 self.nodes = []
104 super( OnosClusterDriver, self ).__init__()
105
106 def checkOptions( self, var, defaultVar ):
107 if var is None or var == "":
108 return defaultVar
109 return var
110
111 def connect( self, **connectargs ):
112 """
113 Creates ssh handle for ONOS "bench".
114 NOTE:
115 The ip_address would come from the topo file using the host tag, the
116 value can be an environment variable as well as a "localhost" to get
117 the ip address needed to ssh to the "bench"
118 """
119 try:
120 for key in connectargs:
121 vars( self )[ key ] = connectargs[ key ]
122 self.home = "~/onos"
123 for key in self.options:
124 if key == "home":
125 self.home = self.options[ 'home' ]
126 elif key == "karaf_username":
127 self.karafUser = self.options[ key ]
128 elif key == "karaf_password":
129 self.karafPass = self.options[ key ]
130 elif key == "cluster_name":
131 prefix = self.options[ key ]
132
133 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 )
136 prefix = self.checkOptions( prefix, "ONOS" )
137
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 )
217 main.cleanup()
218 main.exit()
219 except Exception:
220 main.log.exception( self.name + ": Uncaught exception!" )
221 main.cleanup()
222 main.exit()
223
224 def disconnect( self ):
225 """
226 Called when Test is complete to disconnect the ONOS handle.
227 """
228 response = main.TRUE
229 try:
230 if self.handle:
231 self.handle.sendline( "" )
232 self.handle.expect( "\$" )
233 self.handle.sendline( "exit" )
234 self.handle.expect( "closed" )
235 except pexpect.EOF:
236 main.log.error( self.name + ": EOF exception found" )
237 main.log.error( self.name + ": " + self.handle.before )
238 except ValueError:
239 main.log.exception( "Exception in disconnect of " + self.name )
240 response = main.TRUE
241 except Exception:
242 main.log.exception( self.name + ": Connection failed to the host" )
243 response = main.FALSE
244 return response
245
Devin Lim142b5342017-07-20 15:22:39 -0700246 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700247 """
248 Parse the cluster options to create an ONOS cli component with the given name
249 """
250 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700251 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
252 if clihost == "True":
253 main.componentDictionary[ name ][ 'host' ] = host
Jon Hallca319892017-06-15 15:25:22 -0700254 main.componentDictionary[name]['type'] = "OnosCliDriver"
255 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
256 main.log.debug( main.componentDictionary[name] )
257
Devin Lim142b5342017-07-20 15:22:39 -0700258 def createCliComponent( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700259 """
260 Creates a new onos cli component.
261
262 Arguments:
263 name - The string of the name of this component. The new component
264 will be assigned to main.<name> .
265 In addition, main.<name>.name = str( name )
266 """
267 try:
268 # look to see if this component already exists
269 getattr( main, name )
270 except AttributeError:
271 # namespace is clear, creating component
Devin Lim142b5342017-07-20 15:22:39 -0700272 self.setCliOptions( name, host )
Jon Hallca319892017-06-15 15:25:22 -0700273 return main.componentInit( name )
274 except pexpect.EOF:
275 main.log.error( self.name + ": EOF exception found" )
276 main.log.error( self.name + ": " + self.handle.before )
277 main.cleanup()
278 main.exit()
279 except Exception:
280 main.log.exception( self.name + ": Uncaught exception!" )
281 main.cleanup()
282 main.exit()
283 else:
284 # namespace is not clear!
285 main.log.error( name + " component already exists!" )
286 main.cleanup()
287 main.exit()
288
289 def setRestOptions( self, name, host ):
290 """
291 Parse the cluster options to create an ONOS cli component with the given name
292 """
293 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
294 main.log.debug( main.componentDictionary[name] )
295 user = main.componentDictionary[name]['COMPONENTS'].get( "web_user", "onos" )
296 main.componentDictionary[name]['user'] = self.checkOptions( user, "onos" )
297 password = main.componentDictionary[name]['COMPONENTS'].get( "web_pass", "rocks" )
298 main.componentDictionary[name]['pass'] = self.checkOptions( password, "rocks" )
299 main.componentDictionary[name]['host'] = host
300 port = main.componentDictionary[name]['COMPONENTS'].get( "rest_port", "8181" )
301 main.componentDictionary[name]['port'] = self.checkOptions( port, "8181" )
302 main.componentDictionary[name]['type'] = "OnosRestDriver"
303 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
304 main.log.debug( main.componentDictionary[name] )
305
306 def createRestComponent( self, name, ipAddress ):
307 """
308 Creates a new onos rest component.
309
310 Arguments:
311 name - The string of the name of this component. The new component
312 will be assigned to main.<name> .
313 In addition, main.<name>.name = str( name )
314 """
315 try:
316 # look to see if this component already exists
317 getattr( main, name )
318 except AttributeError:
319 # namespace is clear, creating component
320 self.setRestOptions( name, ipAddress )
321 return main.componentInit( name )
322 except pexpect.EOF:
323 main.log.error( self.name + ": EOF exception found" )
324 main.log.error( self.name + ": " + self.handle.before )
325 main.cleanup()
326 main.exit()
327 except Exception:
328 main.log.exception( self.name + ": Uncaught exception!" )
329 main.cleanup()
330 main.exit()
331 else:
332 # namespace is not clear!
333 main.log.error( name + " component already exists!" )
334 main.cleanup()
335 main.exit()
336
337 def setBenchOptions( self, name ):
338 """
339 Parse the cluster options to create an ONOS "bench" component with the given name
340 """
341 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
342 main.componentDictionary[name]['type'] = "OnosDriver"
343 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
344 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
345 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
346 main.log.debug( main.componentDictionary[name] )
347
348 def createBenchComponent( self, name ):
349 """
350 Creates a new onos "bench" component.
351
352 Arguments:
353 name - The string of the name of this component. The new component
354 will be assigned to main.<name> .
355 In addition, main.<name>.name = str( name )
356 """
357 try:
358 # look to see if this component already exists
359 getattr( main, name )
360 except AttributeError:
361 # namespace is clear, creating component
362 self.setBenchOptions( name )
363 return main.componentInit( name )
364 except pexpect.EOF:
365 main.log.error( self.name + ": EOF exception found" )
366 main.log.error( self.name + ": " + self.handle.before )
367 main.cleanup()
368 main.exit()
369 except Exception:
370 main.log.exception( self.name + ": Uncaught exception!" )
371 main.cleanup()
372 main.exit()
373 else:
374 # namespace is not clear!
375 main.log.error( name + " component already exists!" )
376 main.cleanup()
377 main.exit()
378
379 def createComponents( self, prefix='' ):
380 """
381 Creates a CLI and REST component for each nodes in the cluster
382 """
383 # TODO: This needs work to support starting two seperate clusters in one test
384 cliPrefix = prefix + "cli"
385 restPrefix = prefix + "rest"
386 benchPrefix = prefix + "bench"
387 #self.nodes = []
388 for i in xrange( 1, self.maxNodes + 1 ):
389 cliName = cliPrefix + str( i )
390 restName = restPrefix + str( i )
391 benchName = benchPrefix + str( i )
392
393 # Unfortunately this means we need to have a cell set beofre running TestON,
394 # Even if it is just the entire possible cluster size
395 ip = self.onosIps[ 'OC' + str( i ) ]
396
Devin Lim142b5342017-07-20 15:22:39 -0700397 cli = self.createCliComponent( cliName, ip )
Jon Hallca319892017-06-15 15:25:22 -0700398 rest = self.createRestComponent( restName, ip )
399 bench = self.createBenchComponent( benchName )
Devin Lim6b6ec902017-08-04 16:02:27 -0700400 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1, self.user_name ) )