blob: 5093cfe97d6acba15d576cb79319276f9328d94e [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,
Jon Hall4173b242017-09-12 17:04:38 -070042 self.ipAddress,
43 self.CLI,
44 self.REST,
45 self.Bench )
Jon Hallca319892017-06-15 15:25:22 -070046
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
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 ):
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 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
134 self.home = self.checkOptions(self.home, "~/onos")
135 self.karafUser = self.checkOptions(self.karafUser, self.user_name)
136 self.karafPass = self.checkOptions(self.karafPass, self.pwd )
137 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 """
249 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
Jon Hallca319892017-06-15 15:25:22 -0700253 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] )
256
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 """
289 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] )
301
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 """
334 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] )
340
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
370 def setServerOptions( self, name, ipAddress ):
371 """
372 Parse the cluster options to create an ONOS "server" component with the given name
373
374 Arguments:
375 name - The name of the server componet
376 ipAddress - The ip address of the server
377 """
378 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
379 main.componentDictionary[name]['type'] = "OnosDriver"
380 main.componentDictionary[name]['host'] = ipAddress
381 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
382 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
383 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
384 main.log.debug( main.componentDictionary[name] )
385
386
387 def createServerComponent( self, name, ipAddress ):
388 """
389 Creates a new onos "server" component. This will be connected to the
390 node ONOS is running on.
391
392 Arguments:
393 name - The string of the name of this component. The new component
394 will be assigned to main.<name> .
395 In addition, main.<name>.name = str( name )
396 ipAddress - The ip address of the server
397 """
398 try:
399 # look to see if this component already exists
400 getattr( main, name )
401 except AttributeError:
402 # namespace is clear, creating component
403 self.setServerOptions( name, ipAddress )
404 return main.componentInit( name )
405 except pexpect.EOF:
406 main.log.error( self.name + ": EOF exception found" )
407 main.log.error( self.name + ": " + self.handle.before )
408 main.cleanAndExit()
409 except Exception:
410 main.log.exception( self.name + ": Uncaught exception!" )
411 main.cleanAndExit()
412 else:
413 # namespace is not clear!
414 main.log.error( name + " component already exists!" )
415 main.cleanAndExit()
416
417
418 def createComponents( self, prefix='', createServer=True ):
Jon Hallca319892017-06-15 15:25:22 -0700419 """
420 Creates a CLI and REST component for each nodes in the cluster
421 """
422 # TODO: This needs work to support starting two seperate clusters in one test
423 cliPrefix = prefix + "cli"
424 restPrefix = prefix + "rest"
425 benchPrefix = prefix + "bench"
Jon Hall4173b242017-09-12 17:04:38 -0700426 serverPrefix = prefix + "server"
Jon Hallca319892017-06-15 15:25:22 -0700427 for i in xrange( 1, self.maxNodes + 1 ):
428 cliName = cliPrefix + str( i )
429 restName = restPrefix + str( i )
430 benchName = benchPrefix + str( i )
Jon Hall4173b242017-09-12 17:04:38 -0700431 serverName = serverPrefix + str( i )
Jon Hallca319892017-06-15 15:25:22 -0700432
433 # Unfortunately this means we need to have a cell set beofre running TestON,
434 # Even if it is just the entire possible cluster size
435 ip = self.onosIps[ 'OC' + str( i ) ]
436
Devin Lim142b5342017-07-20 15:22:39 -0700437 cli = self.createCliComponent( cliName, ip )
Jon Hallca319892017-06-15 15:25:22 -0700438 rest = self.createRestComponent( restName, ip )
439 bench = self.createBenchComponent( benchName )
Jon Hall4173b242017-09-12 17:04:38 -0700440 server = self.createServerComponent( serverName, ip ) if createServer else None
441 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1, self.user_name, server=server ) )