blob: 0a99d0571eca426588434b693096413a9744da51 [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 """
57 if hasattr( self.REST, name ):
58 main.log.debug( "Using Rest driver's attribute for '%s'" % ( name ) )
59 return getattr( self.REST, name)
60 if hasattr( self.CLI, name ):
61 main.log.debug( "Using CLI driver's attribute for '%s'" % ( name ) )
62 return getattr( self.CLI, name)
63 if hasattr( self.Bench, name ):
64 main.log.debug( "Using Bench driver's attribute for '%s'" % ( name ) )
65 return getattr( self.Bench, name)
66 raise AttributeError( "Could not find the attribute %s in %s or it's component handles" % ( name, self ) )
67
68
69
70 def __init__( self, name, ipAddress, CLI=None, REST=None, Bench=None ):
71 #TODO: validate these arguments
72 self.name = str( name )
73 self.ipAddress = ipAddress
74 self.CLI = CLI
75 self.REST = REST
76 self.Bench = Bench
77 self.active = False
78
79
80class OnosClusterDriver( CLI ):
81
82 def __init__( self ):
83 """
84 Initialize client
85 """
86 self.name = None
87 self.home = None
88 self.handle = None
89 self.nodes = []
90 super( OnosClusterDriver, self ).__init__()
91
92 def checkOptions( self, var, defaultVar ):
93 if var is None or var == "":
94 return defaultVar
95 return var
96
97 def connect( self, **connectargs ):
98 """
99 Creates ssh handle for ONOS "bench".
100 NOTE:
101 The ip_address would come from the topo file using the host tag, the
102 value can be an environment variable as well as a "localhost" to get
103 the ip address needed to ssh to the "bench"
104 """
105 try:
106 for key in connectargs:
107 vars( self )[ key ] = connectargs[ key ]
108 self.home = "~/onos"
109 for key in self.options:
110 if key == "home":
111 self.home = self.options[ 'home' ]
112 elif key == "karaf_username":
113 self.karafUser = self.options[ key ]
114 elif key == "karaf_password":
115 self.karafPass = self.options[ key ]
116 elif key == "cluster_name":
117 prefix = self.options[ key ]
118
119 self.home = self.checkOptions(self.home, "~/onos")
120 self.karafUser = self.checkOptions(self.karafUser, self.user_name)
121 self.karafPass = self.checkOptions(self.karafPass, self.pwd )
122 prefix = self.checkOptions( prefix, "ONOS" )
123
124 self.name = self.options[ 'name' ]
125
126 # The 'nodes' tag is optional and it is not required in .topo file
127 for key in self.options:
128 if key == "nodes":
129 # Maximum number of ONOS nodes to run, if there is any
130 self.maxNodes = int( self.options[ 'nodes' ] )
131 break
132 self.maxNodes = None
133
134 if self.maxNodes is None or self.maxNodes == "":
135 self.maxNodes = 100
136
137 # Grabs all OC environment variables based on max number of nodes
138 # TODO: Also support giving an ip range as a compononet option
139 self.onosIps = {} # Dictionary of all possible ONOS ip
140
141 try:
142 if self.maxNodes:
143 for i in range( self.maxNodes ):
144 envString = "OC" + str( i + 1 )
145 # If there is no more OC# then break the loop
146 if os.getenv( envString ):
147 self.onosIps[ envString ] = os.getenv( envString )
148 else:
149 self.maxNodes = len( self.onosIps )
150 main.log.info( self.name +
151 ": Created cluster data with " +
152 str( self.maxNodes ) +
153 " maximum number" +
154 " of nodes" )
155 break
156
157 if not self.onosIps:
158 main.log.info( "Could not read any environment variable"
159 + " please load a cell file with all" +
160 " onos IP" )
161 self.maxNodes = None
162 else:
163 main.log.info( self.name + ": Found " +
164 str( self.onosIps.values() ) +
165 " ONOS IPs" )
166 except KeyError:
167 main.log.info( "Invalid environment variable" )
168 except Exception as inst:
169 main.log.error( "Uncaught exception: " + str( inst ) )
170
171 try:
172 if os.getenv( str( self.ip_address ) ) is not None:
173 self.ip_address = os.getenv( str( self.ip_address ) )
174 else:
175 main.log.info( self.name +
176 ": Trying to connect to " +
177 self.ip_address )
178 except KeyError:
179 main.log.info( "Invalid host name," +
180 " connecting to local host instead" )
181 self.ip_address = 'localhost'
182 except Exception as inst:
183 main.log.error( "Uncaught exception: " + str( inst ) )
184
185 self.handle = super( OnosClusterDriver, self ).connect(
186 user_name=self.user_name,
187 ip_address=self.ip_address,
188 port=self.port,
189 pwd=self.pwd,
190 home=self.home )
191
192 if self.handle:
193 self.handle.sendline( "cd " + self.home )
194 self.handle.expect( "\$" )
195 self.createComponents( prefix=prefix )
196 return self.handle
197 else:
198 main.log.info( "Failed to create ONOS handle" )
199 return main.FALSE
200 except pexpect.EOF:
201 main.log.error( self.name + ": EOF exception found" )
202 main.log.error( self.name + ": " + self.handle.before )
203 main.cleanup()
204 main.exit()
205 except Exception:
206 main.log.exception( self.name + ": Uncaught exception!" )
207 main.cleanup()
208 main.exit()
209
210 def disconnect( self ):
211 """
212 Called when Test is complete to disconnect the ONOS handle.
213 """
214 response = main.TRUE
215 try:
216 if self.handle:
217 self.handle.sendline( "" )
218 self.handle.expect( "\$" )
219 self.handle.sendline( "exit" )
220 self.handle.expect( "closed" )
221 except pexpect.EOF:
222 main.log.error( self.name + ": EOF exception found" )
223 main.log.error( self.name + ": " + self.handle.before )
224 except ValueError:
225 main.log.exception( "Exception in disconnect of " + self.name )
226 response = main.TRUE
227 except Exception:
228 main.log.exception( self.name + ": Connection failed to the host" )
229 response = main.FALSE
230 return response
231
232 def setCliOptions( self, name ):
233 """
234 Parse the cluster options to create an ONOS cli component with the given name
235 """
236 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
237 main.componentDictionary[name]['type'] = "OnosCliDriver"
238 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
239 main.log.debug( main.componentDictionary[name] )
240
241 def createCliComponent( self, name ):
242 """
243 Creates a new onos cli component.
244
245 Arguments:
246 name - The string of the name of this component. The new component
247 will be assigned to main.<name> .
248 In addition, main.<name>.name = str( name )
249 """
250 try:
251 # look to see if this component already exists
252 getattr( main, name )
253 except AttributeError:
254 # namespace is clear, creating component
255 self.setCliOptions( name )
256 return main.componentInit( name )
257 except pexpect.EOF:
258 main.log.error( self.name + ": EOF exception found" )
259 main.log.error( self.name + ": " + self.handle.before )
260 main.cleanup()
261 main.exit()
262 except Exception:
263 main.log.exception( self.name + ": Uncaught exception!" )
264 main.cleanup()
265 main.exit()
266 else:
267 # namespace is not clear!
268 main.log.error( name + " component already exists!" )
269 main.cleanup()
270 main.exit()
271
272 def setRestOptions( self, name, host ):
273 """
274 Parse the cluster options to create an ONOS cli component with the given name
275 """
276 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
277 main.log.debug( main.componentDictionary[name] )
278 user = main.componentDictionary[name]['COMPONENTS'].get( "web_user", "onos" )
279 main.componentDictionary[name]['user'] = self.checkOptions( user, "onos" )
280 password = main.componentDictionary[name]['COMPONENTS'].get( "web_pass", "rocks" )
281 main.componentDictionary[name]['pass'] = self.checkOptions( password, "rocks" )
282 main.componentDictionary[name]['host'] = host
283 port = main.componentDictionary[name]['COMPONENTS'].get( "rest_port", "8181" )
284 main.componentDictionary[name]['port'] = self.checkOptions( port, "8181" )
285 main.componentDictionary[name]['type'] = "OnosRestDriver"
286 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
287 main.log.debug( main.componentDictionary[name] )
288
289 def createRestComponent( self, name, ipAddress ):
290 """
291 Creates a new onos rest component.
292
293 Arguments:
294 name - The string of the name of this component. The new component
295 will be assigned to main.<name> .
296 In addition, main.<name>.name = str( name )
297 """
298 try:
299 # look to see if this component already exists
300 getattr( main, name )
301 except AttributeError:
302 # namespace is clear, creating component
303 self.setRestOptions( name, ipAddress )
304 return main.componentInit( name )
305 except pexpect.EOF:
306 main.log.error( self.name + ": EOF exception found" )
307 main.log.error( self.name + ": " + self.handle.before )
308 main.cleanup()
309 main.exit()
310 except Exception:
311 main.log.exception( self.name + ": Uncaught exception!" )
312 main.cleanup()
313 main.exit()
314 else:
315 # namespace is not clear!
316 main.log.error( name + " component already exists!" )
317 main.cleanup()
318 main.exit()
319
320 def setBenchOptions( self, name ):
321 """
322 Parse the cluster options to create an ONOS "bench" component with the given name
323 """
324 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
325 main.componentDictionary[name]['type'] = "OnosDriver"
326 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
327 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
328 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
329 main.log.debug( main.componentDictionary[name] )
330
331 def createBenchComponent( self, name ):
332 """
333 Creates a new onos "bench" component.
334
335 Arguments:
336 name - The string of the name of this component. The new component
337 will be assigned to main.<name> .
338 In addition, main.<name>.name = str( name )
339 """
340 try:
341 # look to see if this component already exists
342 getattr( main, name )
343 except AttributeError:
344 # namespace is clear, creating component
345 self.setBenchOptions( name )
346 return main.componentInit( name )
347 except pexpect.EOF:
348 main.log.error( self.name + ": EOF exception found" )
349 main.log.error( self.name + ": " + self.handle.before )
350 main.cleanup()
351 main.exit()
352 except Exception:
353 main.log.exception( self.name + ": Uncaught exception!" )
354 main.cleanup()
355 main.exit()
356 else:
357 # namespace is not clear!
358 main.log.error( name + " component already exists!" )
359 main.cleanup()
360 main.exit()
361
362 def createComponents( self, prefix='' ):
363 """
364 Creates a CLI and REST component for each nodes in the cluster
365 """
366 # TODO: This needs work to support starting two seperate clusters in one test
367 cliPrefix = prefix + "cli"
368 restPrefix = prefix + "rest"
369 benchPrefix = prefix + "bench"
370 #self.nodes = []
371 for i in xrange( 1, self.maxNodes + 1 ):
372 cliName = cliPrefix + str( i )
373 restName = restPrefix + str( i )
374 benchName = benchPrefix + str( i )
375
376 # Unfortunately this means we need to have a cell set beofre running TestON,
377 # Even if it is just the entire possible cluster size
378 ip = self.onosIps[ 'OC' + str( i ) ]
379
380 cli = self.createCliComponent( cliName )
381 rest = self.createRestComponent( restName, ip )
382 bench = self.createBenchComponent( benchName )
383 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench ) )
384
385 ## DEBUG ########################################################################
386 print "Prininting NODES::"
387 try:
388 print self.nodes
389 for node in self.nodes:
390 main.log.error( repr(node ))
391 main.log.warn( node )
392 except Exception as e:
393 print repr(e)
394 ## END debug ########################################################################