blob: 63bee6049874fd738707eaeabf597aff00f12559 [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 )
Devin Lim44075962017-08-11 10:56:37 -0700217 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700218 except Exception:
219 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700220 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700221
222 def disconnect( self ):
223 """
224 Called when Test is complete to disconnect the ONOS handle.
225 """
226 response = main.TRUE
227 try:
228 if self.handle:
229 self.handle.sendline( "" )
230 self.handle.expect( "\$" )
231 self.handle.sendline( "exit" )
232 self.handle.expect( "closed" )
233 except pexpect.EOF:
234 main.log.error( self.name + ": EOF exception found" )
235 main.log.error( self.name + ": " + self.handle.before )
236 except ValueError:
237 main.log.exception( "Exception in disconnect of " + self.name )
238 response = main.TRUE
239 except Exception:
240 main.log.exception( self.name + ": Connection failed to the host" )
241 response = main.FALSE
242 return response
243
Devin Lim142b5342017-07-20 15:22:39 -0700244 def setCliOptions( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700245 """
246 Parse the cluster options to create an ONOS cli component with the given name
247 """
248 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
Devin Lim142b5342017-07-20 15:22:39 -0700249 clihost = main.componentDictionary[ name ][ 'COMPONENTS' ].get( "diff_clihost", "" )
250 if clihost == "True":
251 main.componentDictionary[ name ][ 'host' ] = host
Jon Hallca319892017-06-15 15:25:22 -0700252 main.componentDictionary[name]['type'] = "OnosCliDriver"
253 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
254 main.log.debug( main.componentDictionary[name] )
255
Devin Lim142b5342017-07-20 15:22:39 -0700256 def createCliComponent( self, name, host ):
Jon Hallca319892017-06-15 15:25:22 -0700257 """
258 Creates a new onos cli component.
259
260 Arguments:
261 name - The string of the name of this component. The new component
262 will be assigned to main.<name> .
263 In addition, main.<name>.name = str( name )
264 """
265 try:
266 # look to see if this component already exists
267 getattr( main, name )
268 except AttributeError:
269 # namespace is clear, creating component
Devin Lim142b5342017-07-20 15:22:39 -0700270 self.setCliOptions( name, host )
Jon Hallca319892017-06-15 15:25:22 -0700271 return main.componentInit( name )
272 except pexpect.EOF:
273 main.log.error( self.name + ": EOF exception found" )
274 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700275 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700276 except Exception:
277 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700278 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700279 else:
280 # namespace is not clear!
281 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700282 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700283
284 def setRestOptions( self, name, host ):
285 """
286 Parse the cluster options to create an ONOS cli component with the given name
287 """
288 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
289 main.log.debug( main.componentDictionary[name] )
290 user = main.componentDictionary[name]['COMPONENTS'].get( "web_user", "onos" )
291 main.componentDictionary[name]['user'] = self.checkOptions( user, "onos" )
292 password = main.componentDictionary[name]['COMPONENTS'].get( "web_pass", "rocks" )
293 main.componentDictionary[name]['pass'] = self.checkOptions( password, "rocks" )
294 main.componentDictionary[name]['host'] = host
295 port = main.componentDictionary[name]['COMPONENTS'].get( "rest_port", "8181" )
296 main.componentDictionary[name]['port'] = self.checkOptions( port, "8181" )
297 main.componentDictionary[name]['type'] = "OnosRestDriver"
298 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
299 main.log.debug( main.componentDictionary[name] )
300
301 def createRestComponent( self, name, ipAddress ):
302 """
303 Creates a new onos rest component.
304
305 Arguments:
306 name - The string of the name of this component. The new component
307 will be assigned to main.<name> .
308 In addition, main.<name>.name = str( name )
309 """
310 try:
311 # look to see if this component already exists
312 getattr( main, name )
313 except AttributeError:
314 # namespace is clear, creating component
315 self.setRestOptions( name, ipAddress )
316 return main.componentInit( name )
317 except pexpect.EOF:
318 main.log.error( self.name + ": EOF exception found" )
319 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700320 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700321 except Exception:
322 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700323 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700324 else:
325 # namespace is not clear!
326 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700327 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700328
329 def setBenchOptions( self, name ):
330 """
331 Parse the cluster options to create an ONOS "bench" component with the given name
332 """
333 main.componentDictionary[name] = main.componentDictionary[self.name].copy()
334 main.componentDictionary[name]['type'] = "OnosDriver"
335 home = main.componentDictionary[name]['COMPONENTS'].get( "onos_home", None )
336 main.componentDictionary[name]['home'] = self.checkOptions( home, None )
337 main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
338 main.log.debug( main.componentDictionary[name] )
339
340 def createBenchComponent( self, name ):
341 """
342 Creates a new onos "bench" component.
343
344 Arguments:
345 name - The string of the name of this component. The new component
346 will be assigned to main.<name> .
347 In addition, main.<name>.name = str( name )
348 """
349 try:
350 # look to see if this component already exists
351 getattr( main, name )
352 except AttributeError:
353 # namespace is clear, creating component
354 self.setBenchOptions( name )
355 return main.componentInit( name )
356 except pexpect.EOF:
357 main.log.error( self.name + ": EOF exception found" )
358 main.log.error( self.name + ": " + self.handle.before )
Devin Lim44075962017-08-11 10:56:37 -0700359 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700360 except Exception:
361 main.log.exception( self.name + ": Uncaught exception!" )
Devin Lim44075962017-08-11 10:56:37 -0700362 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700363 else:
364 # namespace is not clear!
365 main.log.error( name + " component already exists!" )
Devin Lim44075962017-08-11 10:56:37 -0700366 main.cleanAndExit()
Jon Hallca319892017-06-15 15:25:22 -0700367
368 def createComponents( self, prefix='' ):
369 """
370 Creates a CLI and REST component for each nodes in the cluster
371 """
372 # TODO: This needs work to support starting two seperate clusters in one test
373 cliPrefix = prefix + "cli"
374 restPrefix = prefix + "rest"
375 benchPrefix = prefix + "bench"
376 #self.nodes = []
377 for i in xrange( 1, self.maxNodes + 1 ):
378 cliName = cliPrefix + str( i )
379 restName = restPrefix + str( i )
380 benchName = benchPrefix + str( i )
381
382 # Unfortunately this means we need to have a cell set beofre running TestON,
383 # Even if it is just the entire possible cluster size
384 ip = self.onosIps[ 'OC' + str( i ) ]
385
Devin Lim142b5342017-07-20 15:22:39 -0700386 cli = self.createCliComponent( cliName, ip )
Jon Hallca319892017-06-15 15:25:22 -0700387 rest = self.createRestComponent( restName, ip )
388 bench = self.createBenchComponent( benchName )
Devin Lim6b6ec902017-08-04 16:02:27 -0700389 self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1, self.user_name ) )