Merge lp:~javier.collado/utah/returncodes into lp:utah

Proposed by Javier Collado
Status: Merged
Approved by: Javier Collado
Approved revision: 838
Merged at revision: 830
Proposed branch: lp:~javier.collado/utah/returncodes
Merge into: lp:utah
Diff against target: 391 lines (+75/-35)
11 files modified
debian/changelog (+3/-0)
examples/run_install_test.py (+4/-3)
examples/run_test_bamboo_feeder.py (+4/-3)
examples/run_test_cobbler.py (+4/-3)
examples/run_test_vm.py (+4/-3)
examples/run_utah_tests.py (+5/-4)
utah/client/common.py (+15/-8)
utah/client/result.py (+1/-4)
utah/client/runner.py (+4/-4)
utah/client/tests/test_runner.py (+2/-2)
utah/run.py (+29/-1)
To merge this branch: bzr merge lp:~javier.collado/utah/returncodes
Reviewer Review Type Date Requested Status
UTAH Dev Pending
Review via email: mp+152447@code.launchpad.net

Description of the change

This branch intend is to clarify the client/server return codes and make it
possible to know from the server return code if a failure happened either at
the client or at the server.

The set of possible return codes is as follows:

- Client
PASS (0) - All test cases where executed and passed
FAIL (1) - All test cases where executed, but at least one of them failed
ERROR (2) - At least one error was detected that prevented a test case from
being executed.

- Server
SUCCESS (0) - No problems found
UTAH_EXCEPTION_ERROR (1) - UTAH exception caught
TIMEOUT_ERROR (2) - Unable to complete before config.jobtimeout seconds
GROUP_ERROR (3) - User isn't in UTAH group
FAIL (101) - All test cases where executed, but at least one of them failed
ERROR (102) - At least one error was detected that prevented a test case from
being executed.

Note that the convention followed in the server is that when the client returns
an error 100 is added to that code to make clear what errors came from the
server and what errors came from the client. This might be useful in jenkins
jobs in which the actions following the test run depend on the test execution
results.

To test this I've used different variations of the pass.run (and its testsuite)
to make sure that the codes above work as expected. Anyway, I haven't tested
some error codes in the server (1-3) as only the return code number might have
changed, but the code is almost the same as before the change.

To post a comment you must log in.
Revision history for this message
Andy Doan (doanac) wrote :

Looks pretty good. It will be nice to consolidate run_* stuff to make changes like this easier :)

One thing I was wondering about: rather than using dictionaries for return codes would it be better to do something like:

class ReturnCode:
     SUCCESS = 0
     UTAH_EXCEPTION_ERROR = 1
...

    @staticmethod
    def client_error(rc):
        return 100 + rc

then there aren't hard-coded strings.

lp:~javier.collado/utah/returncodes updated
837. By Javier Collado

Refactored server return codes to use a class instead of a dictionary

Suggested by Andy

838. By Javier Collado

Refactored client return codes to use a class instead of a dictionary

Suggested by Andy

Revision history for this message
Javier Collado (javier.collado) wrote :

@Andy

I've updated the dictionaries to use classes instead. Thanks for the suggestion.

Revision history for this message
Andy Doan (doanac) wrote :

looks good to me.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2013-03-08 16:14:06 +0000
3+++ debian/changelog 2013-03-11 10:10:26 +0000
4@@ -6,10 +6,13 @@
5 * Added installation failure log message (LP: #1126361)
6 * Added check to make sure user is in the password database
7 (LP: #1071020)
8+ * Clarified return code values in server and client
9+ (LP: #1025633, LP: #1133227)
10
11 [ Max Brustkern ]
12 * Fixed ProvisionedMachine template support (LP: #1147306)
13
14+
15 -- Max Brustkern <max@canonical.com> Thu, 07 Mar 2013 17:11:12 -0500
16
17 utah (0.8ubuntu1) quantal; urgency=low
18
19=== modified file 'examples/run_install_test.py'
20--- examples/run_install_test.py 2013-02-15 08:36:32 +0000
21+++ examples/run_install_test.py 2013-03-11 10:10:26 +0000
22@@ -28,6 +28,7 @@
23 run_tests,
24 virtual_arguments,
25 configure_logging,
26+ ReturnCodes,
27 )
28
29
30@@ -52,13 +53,13 @@
31 def run_install_test(args=None):
32 if not check_user_group():
33 print_group_error_message(__file__)
34- sys.exit(3)
35+ sys.exit(ReturnCodes.GROUP_ERROR)
36
37 if args is None:
38 args = get_parser().parse_args()
39
40 locallogs = []
41- exitstatus = 0
42+ exitstatus = ReturnCodes.SUCCESS
43 machine = None
44
45 configure_logging(args.debug)
46@@ -78,7 +79,7 @@
47
48 except UTAHException as error:
49 sys.stderr.write('Exception: ' + str(error))
50- exitstatus = 2
51+ exitstatus = ReturnCodes.UTAH_EXCEPTION_ERROR
52
53 finally:
54 if not args.no_destroy and machine is not None:
55
56=== modified file 'examples/run_test_bamboo_feeder.py'
57--- examples/run_test_bamboo_feeder.py 2013-02-15 08:36:32 +0000
58+++ examples/run_test_bamboo_feeder.py 2013-03-11 10:10:26 +0000
59@@ -31,6 +31,7 @@
60 name_argument,
61 run_tests,
62 configure_logging,
63+ ReturnCodes,
64 )
65
66
67@@ -54,13 +55,13 @@
68 def run_test_bamboo_feeder(args=None):
69 if not check_user_group():
70 print_group_error_message(__file__)
71- sys.exit(3)
72+ sys.exit(ReturnCodes.GROUP_ERROR)
73
74 if args is None:
75 args = get_parser().parse_args()
76
77 locallogs = []
78- exitstatus = 0
79+ exitstatus = ReturnCodes.SUCCESS
80 machine = None
81
82 configure_logging(args.debug)
83@@ -86,7 +87,7 @@
84 logging.error(mesg)
85 except (AttributeError, NameError):
86 sys.stderr.write(mesg)
87- exitstatus = 2
88+ exitstatus = ReturnCodes.UTAH_EXCEPTION_ERROR
89
90 finally:
91 if machine is not None:
92
93=== modified file 'examples/run_test_cobbler.py'
94--- examples/run_test_cobbler.py 2013-02-15 08:36:32 +0000
95+++ examples/run_test_cobbler.py 2013-03-11 10:10:26 +0000
96@@ -30,6 +30,7 @@
97 name_argument,
98 run_tests,
99 configure_logging,
100+ ReturnCodes,
101 )
102
103
104@@ -53,13 +54,13 @@
105 def run_test_cobbler(args=None):
106 if not check_user_group():
107 print_group_error_message(__file__)
108- sys.exit(3)
109+ sys.exit(ReturnCodes.GROUP_ERROR)
110
111 if args is None:
112 args = get_parser().parse_args()
113
114 locallogs = []
115- exitstatus = 0
116+ exitstatus = ReturnCodes.SUCCESS
117 machine = None
118
119 configure_logging(args.debug)
120@@ -91,7 +92,7 @@
121 logging.error(mesg)
122 except (AttributeError, NameError):
123 sys.stderr.write(mesg)
124- exitstatus = 2
125+ exitstatus = ReturnCodes.UTAH_EXCEPTION_ERROR
126
127 finally:
128 if machine is not None:
129
130=== modified file 'examples/run_test_vm.py'
131--- examples/run_test_vm.py 2013-02-15 08:36:32 +0000
132+++ examples/run_test_vm.py 2013-03-11 10:10:26 +0000
133@@ -25,6 +25,7 @@
134 common_arguments,
135 run_tests,
136 configure_logging,
137+ ReturnCodes,
138 )
139
140
141@@ -45,13 +46,13 @@
142 def run_test_vm(args=None):
143 if not check_user_group():
144 print_group_error_message(__file__)
145- sys.exit(3)
146+ sys.exit(ReturnCodes.GROUP_ERROR)
147
148 if args is None:
149 args = get_parser().parse_args()
150
151 locallogs = []
152- exitstatus = 0
153+ exitstatus = ReturnCodes.SUCCESS
154 machine = None
155
156 configure_logging(args.debug)
157@@ -72,7 +73,7 @@
158
159 except UTAHException as error:
160 sys.stderr.write('Exception: ' + str(error))
161- exitstatus = 2
162+ exitstatus = ReturnCodes.UTAH_EXCEPTION_ERROR
163
164 finally:
165 if not args.no_destroy and machine is not None:
166
167=== modified file 'examples/run_utah_tests.py'
168--- examples/run_utah_tests.py 2013-02-15 08:36:32 +0000
169+++ examples/run_utah_tests.py 2013-03-11 10:10:26 +0000
170@@ -28,6 +28,7 @@
171 virtual_arguments,
172 configure_logging,
173 run_tests,
174+ ReturnCodes,
175 )
176 from utah.timeout import timeout, UTAHTimeout
177 from run_install_test import run_install_test
178@@ -67,7 +68,7 @@
179 def run_utah_tests(args=None):
180 if not check_user_group():
181 print_group_error_message(__file__)
182- sys.exit(3)
183+ sys.exit(ReturnCodes.GROUP_ERROR)
184
185 if args is None:
186 args = get_parser().parse_args()
187@@ -86,7 +87,7 @@
188 def run_provisioned_tests(args):
189 """Run test cases in a provisioned machine."""
190 locallogs = []
191- exitstatus = 0
192+ exitstatus = ReturnCodes.SUCCESS
193 try:
194 # TBD: Inventory should be used to verify machine
195 # is not running other tests
196@@ -94,7 +95,7 @@
197 exitstatus, locallogs = run_tests(args, machine)
198 except UTAHException as error:
199 sys.stderr.write('Exception: ' + str(error))
200- exitstatus = 2
201+ exitstatus = ReturnCodes.UTAH_EXCEPTION_ERROR
202 finally:
203 if len(locallogs) != 0:
204 print('Test logs copied to the following files:')
205@@ -121,7 +122,7 @@
206 except UTAHTimeout:
207 sys.stderr.write('Job failed to complete after '
208 + str(config.jobtimeout) + ' seconds.')
209- sys.exit(3)
210+ sys.exit(ReturnCodes.TIMEOUT_ERROR)
211 else:
212 run_utah_tests()
213 except AttributeError:
214
215=== modified file 'utah/client/common.py'
216--- utah/client/common.py 2013-03-04 14:31:13 +0000
217+++ utah/client/common.py 2013-03-11 10:10:26 +0000
218@@ -35,11 +35,6 @@
219
220 from utah.client.battery import battery
221
222-
223-PASS = 0
224-FAIL = 1
225-ERROR_TIMEOUT = -9
226-
227 CONFIG = {
228 'DEBUG': False,
229 'TEST_DIR': '/var/lib/utah',
230@@ -57,7 +52,18 @@
231 CLIENT_CONFIG = os.path.join(UTAH_DIR, 'config', 'client.json')
232 DEFAULT_STATE_FILE = os.path.join(UTAH_DIR, "state.yaml")
233
234-RETURN_CODES = {'PASS': 0, 'ERROR': -1, 'FAIL': 1}
235+
236+# UTAH client return codes
237+# PASS: All test cases where executed and passed
238+# FAIL: All test cases where executed, but at least one of them failed
239+# ERROR: At least one error was detected that prevented a test case from being
240+# executed. Examples of situations that are considered an error are:
241+# - Fetch command failure
242+# - Setup command failure
243+class ReturnCodes:
244+ PASS = 0
245+ FAIL = 1
246+ ERROR = 2
247
248 CMD_TC_BUILD = 'testcase_build'
249 CMD_TC_SETUP = 'testcase_setup'
250@@ -114,7 +120,7 @@
251 except KeyError:
252 return make_result(
253 command,
254- FAIL,
255+ 1, # sudo return value for configuration/permission problem
256 stderr=('{!r} user not found in the password database'
257 .format(run_as)))
258
259@@ -164,13 +170,14 @@
260 # so wrap to avoid OSError: no such process
261 try:
262 os.kill(pid, signal.SIGKILL)
263+ p.communicate()
264 except OSError:
265 pass
266
267 time_delta = datetime.datetime.now() - start_time
268
269 return make_result(command=command,
270- retcode=ERROR_TIMEOUT,
271+ retcode=p.returncode,
272 start_time=start_time.strftime(DATE_FORMAT),
273 time_delta=str(time_delta),
274 cmd_type=cmd_type,
275
276=== modified file 'utah/client/result.py'
277--- utah/client/result.py 2013-02-06 17:42:00 +0000
278+++ utah/client/result.py 2013-03-11 10:10:26 +0000
279@@ -112,12 +112,9 @@
280
281 self.results.append(result)
282
283- if result['returncode'] == 1 and self.status != 'ERROR':
284+ if result['returncode'] != 0 and self.status != 'ERROR':
285 self.status = 'FAIL'
286
287- if result['returncode'] not in [0, 1]:
288- self.status = 'ERROR'
289-
290 def result(self, verbose=False):
291 """
292 Output a text based result.
293
294=== modified file 'utah/client/runner.py'
295--- utah/client/runner.py 2013-02-27 08:32:22 +0000
296+++ utah/client/runner.py 2013-03-11 10:10:26 +0000
297@@ -37,7 +37,7 @@
298 MASTER_RUNLIST,
299 UTAH_DIR,
300 DEFAULT_STATE_FILE,
301- RETURN_CODES,
302+ ReturnCodes,
303 parse_yaml_file,
304 BzrHandler,
305 DevHandler,
306@@ -557,11 +557,11 @@
307
308 def returncode(self):
309 if self.errors > 0 or self.fetch_errors > 0:
310- return RETURN_CODES['ERROR']
311+ return ReturnCodes.ERROR
312 elif self.failures > 0:
313- return RETURN_CODES['FAIL']
314+ return ReturnCodes.FAIL
315 else:
316- return RETURN_CODES['PASS']
317+ return ReturnCodes.PASS
318
319 def report(self):
320 tests_run = self.passes + self.errors + self.failures
321
322=== modified file 'utah/client/tests/test_runner.py'
323--- utah/client/tests/test_runner.py 2012-12-11 09:47:51 +0000
324+++ utah/client/tests/test_runner.py 2013-03-11 10:10:26 +0000
325@@ -21,7 +21,7 @@
326 from utah.client.result import ResultYAML
327 from utah.client.state_agent import StateAgentYAML
328 from utah.client.runner import Runner
329-from utah.client.common import RETURN_CODES
330+from utah.client.common import ReturnCodes
331 from utah.client import exceptions
332
333 from .common import ( # NOQA
334@@ -99,7 +99,7 @@
335 for suite in self.runner.suites:
336 self.assertTrue(suite.is_done())
337
338- self.assertEqual(retcode, RETURN_CODES['ERROR'])
339+ self.assertEqual(retcode, ReturnCodes.ERROR)
340
341 def test_missing_testdir(self):
342 """
343
344=== modified file 'utah/run.py'
345--- utah/run.py 2013-02-15 08:36:32 +0000
346+++ utah/run.py 2013-03-11 10:10:26 +0000
347@@ -30,6 +30,30 @@
348 from utah.provisioning.ssh import ProvisionedMachine
349
350
351+# Return codes for the server
352+class ReturnCodes:
353+ SUCCESS = 0 # No problems found
354+ UTAH_EXCEPTION_ERROR = 1 # UTAH exception caught
355+ TIMEOUT_ERROR = 2 # Unable to complete before config.jobtimeout seconds
356+ GROUP_ERROR = 3 # User isn't in UTAH group
357+
358+ @staticmethod
359+ def client_error(returncode):
360+ """Add offset to client error to avoid overlapping.
361+
362+ This is useful to be able to know when a failure happened in the server
363+ or in the client at the shell level.
364+
365+ :param returncode: The code returned by the client
366+ :type returncode: int
367+ :returns: The code to be returned by the server
368+ :rtype: int
369+
370+ """
371+ offset = 100
372+ return returncode + offset
373+
374+
375 def common_arguments(parser):
376 parser.add_argument('runlist', metavar='runlist',
377 type=url_argument,
378@@ -137,9 +161,13 @@
379 report_remote_path)
380 utah_command = 'utah {} {}'.format(extraopts, options)
381 exitstatus, _stdout, _stderr = machine.run(utah_command, root=True)
382+ # Make sure that it's possible to know when the server failed
383+ # and when the client failed just checking the return code
384+ if exitstatus != 0:
385+ exitstatus = ReturnCodes.client_error(exitstatus)
386 except UTAHException as error:
387 logging.error('Failed to run test: ' + str(error))
388- exitstatus = 1
389+ exitstatus = ReturnCodes.UTAH_EXCEPTION_ERROR
390 else:
391 _write(runlist, report_remote_path)
392

Subscribers

People subscribed via source and target branches