Merge lp:ubuntu-automation-test-harness/dev into lp:ubuntu-automation-test-harness

Proposed by Max Brustkern
Status: Merged
Approved by: Max Brustkern
Approved revision: 249
Merged at revision: 121
Proposed branch: lp:ubuntu-automation-test-harness/dev
Merge into: lp:ubuntu-automation-test-harness
Diff against target: 5744 lines (+3299/-1710)
48 files modified
.profile (+0/-2)
Makefile (+11/-0)
TODO (+3/-3)
client.py (+91/-87)
debian/changelog (+825/-0)
debian/conffiles (+0/-1)
debian/control (+1/-7)
debian/dirs (+1/-0)
debian/install (+3/-1)
debian/postinst (+9/-8)
debian/rules (+5/-1)
debian/source/format (+1/-0)
debian/ubuntu-automation-test-harness-client.install (+0/-1)
examples/provision.py (+0/-45)
examples/run_test_vm.py (+105/-0)
examples/run_uath_tests.py (+71/-0)
shell-profile (+1/-0)
uath/client/README (+1/-1)
uath/client/common.py (+129/-113)
uath/client/examples/master.run (+1/-1)
uath/client/examples/uath_tests/test_one/tc_control (+1/-1)
uath/client/exceptions.py (+7/-0)
uath/client/result.py (+102/-79)
uath/client/runner.py (+305/-267)
uath/client/self_test.py (+496/-461)
uath/client/state_agent.py (+70/-70)
uath/client/testcase.py (+195/-192)
uath/client/testsuite.py (+263/-250)
uath/exceptions.py (+0/-6)
uath/provisioning/catalog/__init__.py (+1/-1)
uath/provisioning/catalog/base.py (+51/-14)
uath/provisioning/catalog/sqlite.py (+13/-6)
uath/provisioning/provisioning.py (+192/-18)
uath/provisioning/vm/__init__.py (+2/-1)
uath/provisioning/vm/kvm.py (+186/-42)
uath/provisioning/vm/vm.py (+6/-19)
uath_howto.txt (+21/-9)
ubuntu-automation-test-harness-client/Makefile (+25/-0)
ubuntu-automation-test-harness-client/debian/compat (+1/-0)
ubuntu-automation-test-harness-client/debian/control (+15/-0)
ubuntu-automation-test-harness-client/debian/copyright (+60/-0)
ubuntu-automation-test-harness-client/debian/dirs (+2/-0)
ubuntu-automation-test-harness-client/debian/install (+2/-0)
ubuntu-automation-test-harness-client/debian/postinst (+4/-0)
ubuntu-automation-test-harness-client/debian/rules (+9/-0)
ubuntu-automation-test-harness-client/debian/source/format (+1/-0)
ubuntu-automation-test-harness-client/getroot (+8/-0)
uqt-vm-tools.conf (+3/-3)
To merge this branch: bzr merge lp:ubuntu-automation-test-harness/dev
Reviewer Review Type Date Requested Status
Max Brustkern (community) Approve
Review via email: mp+105151@code.launchpad.net

Description of the change

Merging working version into trunk for use in kernel backport testing.

To post a comment you must log in.
Revision history for this message
Max Brustkern (nuclearbob) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file '.profile'
2--- .profile 2012-04-06 20:13:15 +0000
3+++ .profile 1970-01-01 00:00:00 +0000
4@@ -1,2 +0,0 @@
5-export UQT_VM_TOOLS=/opt/canonical.com/ubuntu-qa-tools/vm-tools
6-export DISPLAY=""
7
8=== modified file 'Makefile'
9--- Makefile 2012-03-29 20:14:50 +0000
10+++ Makefile 2012-05-09 00:33:18 +0000
11@@ -1,4 +1,15 @@
12 PYC_PATTERN?=*.pyc
13+all: client uath_sudoers
14+
15+client:
16+ cd ubuntu-automation-test-harness-client ; make
17+
18+uath_sudoers:
19+ ls examples | grep -v "~$$" | grep -v "\.pyc$$" | sed 's/^/ALL ALL = (uath) NOPASSWD: \/usr\/share\/uath\/examples\//' > uath_sudoers
20
21 clean:
22 find . -name "$(PYC_PATTERN)" -delete
23+ cd ubuntu-automation-test-harness-client ; make clean
24+ rm uath_sudoers
25+
26+.PHONY: client clean
27
28=== modified file 'TODO'
29--- TODO 2012-04-09 19:43:57 +0000
30+++ TODO 2012-05-09 00:33:18 +0000
31@@ -1,6 +1,7 @@
32 # Mon 26 Mar 2012 01:48:25 PM EDT Joe Talbott <joe.talbott@canonical.com>
33
34 1. Write/Implement
35+ * make sure a new Result object is created for each suite.
36 * add support for 'run_as'.
37 * add proper support for testcase types as we currently only support
38 userland.
39@@ -18,9 +19,8 @@
40 # proposed layout of UATH directory
41 /var/uath/
42 master.run
43- suite_one/ # to be created by runner
44- # fetch suite_one into here (currently this is done one level up.
45- suite_two/ # to be created by runner
46+ suite_one/ # the Runner will create this and run the fetch_cmd in here
47+ suite_two/ # the Runner will create this and run the fetch_cmd in here
48 suite_two/ # fetched
49 test_one/
50 test_one.py
51
52=== renamed file 'main.py' => 'client.py'
53--- main.py 2012-04-10 20:42:15 +0000
54+++ client.py 2012-05-09 00:33:18 +0000
55@@ -1,109 +1,113 @@
56 #!/usr/bin/env python
57
58 import logging
59+import platform
60 import sys
61
62-from uath.runner import Runner
63-from uath.state_agent import StateAgentYAML
64-from uath.result import Result, ResultYAML
65+from uath.client.runner import Runner
66+from uath.client.state_agent import StateAgentYAML
67+from uath.client.result import Result, ResultYAML, ResultJSON
68
69-from uath.common import DEFAULT_STATE_FILE
70-from uath import exceptions
71+from uath.client.common import DEFAULT_STATE_FILE
72+from uath.client import exceptions
73
74 def process_cmdline_argparse():
75- """
76- Process the command line arguments.
77- """
78-
79- # requires Python2.7+
80- import argparse
81-
82- parser = argparse.ArgumentParser(description='Ubuntu Automation Testing Harness')
83- parser.add_argument('--resume', action='store_true', help='Continue a previous run. Used after a reboot')
84- parser.add_argument('-s', '--state-file', help='File to use for storing state (default "%s"' % DEFAULT_STATE_FILE)
85- parser.add_argument('-f', '--format', choices=['text', 'yaml'], default='yaml', help='Output format (default "yaml")')
86- parser.add_argument('-t', '--testdir', default='/var/uath', help='Main test directory')
87- parser.add_argument('-r', '--runlist', default='master.run', help='runlist file name')
88- parser.add_argument('-o', '--output', help='write output to this file')
89- parser.add_argument('-a', '--append', action='store_true', help='append to output')
90- parser.add_argument('-d', '--debug', action='store_true', help='Print debugging output')
91-
92- args = parser.parse_args()
93-
94- return args
95+ """
96+ Process the command line arguments.
97+ """
98+
99+ # requires Python2.7+
100+ import argparse
101+
102+ parser = argparse.ArgumentParser(description='Ubuntu Automation Testing Harness')
103+ parser.add_argument('--resume', action='store_true', help='Continue a previous run. Used after a reboot')
104+ parser.add_argument('-s', '--state-file', help='File to use for storing state (default "%s"' % DEFAULT_STATE_FILE)
105+ parser.add_argument('-f', '--format', choices=['text', 'yaml', 'json'], default='yaml', help='Output format (default "yaml")')
106+ parser.add_argument('-t', '--testdir', default='/var/lib/uath', help='Main test directory')
107+ parser.add_argument('-r', '--runlist', default='master.run', help='runlist file name')
108+ parser.add_argument('-o', '--output', help='write output to this file')
109+ parser.add_argument('-a', '--append', action='store_true', help='append to output')
110+ parser.add_argument('-d', '--debug', action='store_true', help='Print debugging output')
111+
112+ args = parser.parse_args()
113+
114+ return args
115
116 # TODO: write <2.7 optparse version and set based on version of python
117 # being used.
118 process_cmdline = process_cmdline_argparse
119
120 def exit(returncode, msg):
121- print msg
122- sys.exit(returncode)
123+ print msg
124+ sys.exit(returncode)
125
126 def setup_logging(debug=False):
127- if debug:
128- level = logging.DEBUG
129- else:
130- level = logging.WARNING
131+ if debug:
132+ level = logging.DEBUG
133+ else:
134+ level = logging.WARNING
135
136- logging.basicConfig(level=level,
137- format='%(asctime)s - %(levelname)s - %(message)s')
138+ logging.basicConfig(level=level,
139+ format='%(asctime)s - %(levelname)s - %(message)s')
140
141 def main():
142- """
143- The main driver for the 'uath' application.
144- """
145-
146- args = process_cmdline()
147-
148- setup_logging(args.debug)
149-
150- logging.debug(args)
151-
152- """
153- print args
154- import sys
155- sys.exit(0)
156- """
157- resume = args.resume
158- state_file = args.state_file
159- testdir = args.testdir
160- runlist = args.runlist
161- output = args.output
162-
163- if args.append:
164- open_flags = 'a'
165- else:
166- open_flags = 'w'
167-
168- # Redirect stdout to a file if requested.
169- if output:
170+ """
171+ The main driver for the 'uath' application.
172+ """
173+
174+ args = process_cmdline()
175+
176+ setup_logging(args.debug)
177+
178+ logging.debug(args)
179+ logging.info(platform.uname())
180+
181+ """
182+ print args
183+ import sys
184+ sys.exit(0)
185+ """
186+ resume = args.resume
187+ state_file = args.state_file
188+ testdir = args.testdir
189+ runlist = args.runlist
190+ output = args.output
191+
192+ if args.append:
193+ open_flags = 'a'
194+ else:
195+ open_flags = 'w'
196+
197+ # Redirect stdout to a file if requested.
198+ if output:
199+ try:
200+ sys.stdout = open(output, open_flags)
201+ except IOError as e:
202+ exit(e.errno, e)
203+
204+ if args.format == 'text':
205+ result_class = Result
206+ elif args.format == 'json':
207+ result_class = ResultJSON
208+ else:
209+ result_class = ResultYAML
210+
211+ state_agent = StateAgentYAML(state_file=state_file)
212 try:
213- sys.stdout = open(output, open_flags)
214- except IOError as e:
215- exit(e.errno, e)
216-
217- if args.format == 'text':
218- result_class = Result
219- else:
220- result_class = ResultYAML
221-
222- state_agent = StateAgentYAML(state_file=state_file)
223- try:
224- runner = Runner(state_agent=state_agent, result_class=result_class,
225- runlist=runlist, resume=resume, testdir=testdir)
226- except (exceptions.BadDir, exceptions.MissingFile) as e:
227- errno = 1
228-
229- if hasattr(e, 'errno'):
230- errno = e.errno
231-
232- exit(errno, e)
233-
234- if resume:
235- runner.load_state()
236-
237- runner.run()
238+ runner = Runner(state_agent=state_agent, result_class=result_class,
239+ runlist=runlist, resume=resume, testdir=testdir)
240+ except (exceptions.BadDir, exceptions.MissingFile) as e:
241+ errno = 1
242+
243+ if hasattr(e, 'errno'):
244+ errno = e.errno
245+
246+ exit(errno, e)
247+
248+ if resume:
249+ runner.load_state()
250+
251+ runner.run()
252
253 if __name__ == "__main__":
254- main()
255+ main()
256
257=== modified file 'debian/changelog'
258--- debian/changelog 2012-04-11 13:22:05 +0000
259+++ debian/changelog 2012-05-09 00:33:18 +0000
260@@ -1,3 +1,828 @@
261+ubuntu-automation-test-harness (0.1ubuntu266) precise; urgency=low
262+
263+ * vm-start runs fine on a started vm, so always run it as part of
264+ activecheck debcommit
265+
266+ -- Max Brustkern <max@canonical.com> Thu, 03 May 2012 10:38:56 -0400
267+
268+ubuntu-automation-test-harness (0.1ubuntu265) precise; urgency=low
269+
270+ * Changed vm-wait to run all the time to make sure ssh is up
271+
272+ -- Max Brustkern <max@canonical.com> Thu, 03 May 2012 09:28:44 -0400
273+
274+ubuntu-automation-test-harness (0.1ubuntu264) precise; urgency=low
275+
276+ * Removed permissions change from postinstall since it breaks SSH key
277+
278+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 17:51:54 -0400
279+
280+ubuntu-automation-test-harness (0.1ubuntu263) precise; urgency=low
281+
282+ * Fixed typo
283+
284+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 14:38:20 -0400
285+
286+ubuntu-automation-test-harness (0.1ubuntu262) precise; urgency=low
287+
288+ * Added file this time
289+
290+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 14:35:38 -0400
291+
292+ubuntu-automation-test-harness (0.1ubuntu261) precise; urgency=low
293+
294+ * Fixed exception import
295+
296+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 14:30:17 -0400
297+
298+ubuntu-automation-test-harness (0.1ubuntu260) precise; urgency=low
299+
300+ * Updated permissions to facilitate non-uath user use
301+
302+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 12:33:45 -0400
303+
304+ubuntu-automation-test-harness (0.1ubuntu259) precise; urgency=low
305+
306+ * Corrected exceptions
307+
308+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 10:29:46 -0400
309+
310+ubuntu-automation-test-harness (0.1ubuntu258) precise; urgency=low
311+
312+ * Resolved changes
313+
314+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 09:53:20 -0400
315+
316+ubuntu-automation-test-harness (0.1ubuntu257) precise; urgency=low
317+
318+ * Actually saved changes this time
319+
320+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 09:24:50 -0400
321+
322+ubuntu-automation-test-harness (0.1ubuntu256) precise; urgency=low
323+
324+ * Changed runargs to _runargs
325+
326+ -- Max Brustkern <max@canonical.com> Wed, 02 May 2012 08:56:51 -0400
327+
328+ubuntu-automation-test-harness (0.1ubuntu255) precise; urgency=low
329+
330+ * Better handle missing tc_control files
331+
332+ -- Joe Talbott <joe.talbott@canonical.com> Tue, 01 May 2012 17:30:58 -0400
333+
334+ubuntu-automation-test-harness (0.1ubuntu254) precise; urgency=low
335+
336+ * Updated client dependencies
337+
338+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 16:46:40 -0400
339+
340+ubuntu-automation-test-harness (0.1ubuntu253) precise; urgency=low
341+
342+ * Running client as root
343+
344+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 16:14:22 -0400
345+
346+ubuntu-automation-test-harness (0.1ubuntu252) precise; urgency=low
347+
348+ * Updated documentation a lot, got some of it into correct column
349+ limit; changed iso to image in test runner; added variant to test
350+ runner
351+
352+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 15:16:50 -0400
353+
354+ubuntu-automation-test-harness (0.1ubuntu251) precise; urgency=low
355+
356+ * Updated Catalog documentation
357+
358+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 13:18:30 -0400
359+
360+ubuntu-automation-test-harness (0.1ubuntu250) precise; urgency=low
361+
362+ * Updated documentation further and corrected classes to use private
363+ methods
364+
365+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 13:07:29 -0400
366+
367+ubuntu-automation-test-harness (0.1ubuntu249) precise; urgency=low
368+
369+ * Updated documentation further and corrected classes to use private
370+ methods
371+
372+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 13:06:44 -0400
373+
374+ubuntu-automation-test-harness (0.1ubuntu248) precise; urgency=low
375+
376+ * Updated documentation further and corrected classes to use private
377+ methods
378+
379+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 13:03:44 -0400
380+
381+ubuntu-automation-test-harness (0.1ubuntu247) precise; urgency=low
382+
383+ * Updated documentation further and corrected classes to use private
384+ methods
385+
386+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 13:00:06 -0400
387+
388+ubuntu-automation-test-harness (0.1ubuntu246) precise; urgency=low
389+
390+ * Updated documentation and moved methods around
391+
392+ -- Max Brustkern <max@canonical.com> Tue, 01 May 2012 11:24:09 -0400
393+
394+ubuntu-automation-test-harness (0.1ubuntu245) precise; urgency=low
395+
396+ * Added exception when vm-wait times out
397+
398+ -- Max Brustkern <max@canonical.com> Mon, 30 Apr 2012 18:50:36 -0400
399+
400+ubuntu-automation-test-harness (0.1ubuntu244) precise; urgency=low
401+
402+ * Added vm-wait check to VMToolsKVM startup
403+
404+ -- Max Brustkern <max@canonical.com> Mon, 30 Apr 2012 17:38:10 -0400
405+
406+ubuntu-automation-test-harness (0.1ubuntu243) precise; urgency=low
407+
408+ * Made changes to update things to precise
409+
410+ -- Max Brustkern <max@canonical.com> Fri, 27 Apr 2012 12:32:27 -0400
411+
412+ubuntu-automation-test-harness (0.1ubuntu242) precise; urgency=low
413+
414+ * Updated current example scripts and removed outdated ones
415+
416+ -- Max Brustkern <max@canonical.com> Fri, 27 Apr 2012 09:29:30 -0400
417+
418+ubuntu-automation-test-harness (0.1ubuntu241) precise; urgency=low
419+
420+ * Corrected suggested command for no vm-tools config file
421+
422+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 17:03:07 -0400
423+
424+ubuntu-automation-test-harness (0.1ubuntu240) precise; urgency=low
425+
426+ * Corrected error handling for no hvm support
427+
428+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:58:36 -0400
429+
430+ubuntu-automation-test-harness (0.1ubuntu239) precise; urgency=low
431+
432+ * Updated vm-tools config to handle non-standard uath user home
433+ directory better
434+
435+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:56:26 -0400
436+
437+ubuntu-automation-test-harness (0.1ubuntu238) precise; urgency=low
438+
439+ * Fixed script to remove debug argument print
440+
441+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:46:58 -0400
442+
443+ubuntu-automation-test-harness (0.1ubuntu237) precise; urgency=low
444+
445+ * Updated postinst to handle non-standard uath user home directory
446+ better
447+
448+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:43:15 -0400
449+
450+ubuntu-automation-test-harness (0.1ubuntu236) precise; urgency=low
451+
452+ * Throw an exception when no vm-tools config file exists
453+
454+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:37:31 -0400
455+
456+ubuntu-automation-test-harness (0.1ubuntu235) precise; urgency=low
457+
458+ * Updated postinst to handle non-standard uath user home directory
459+ better
460+
461+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:18:44 -0400
462+
463+ubuntu-automation-test-harness (0.1ubuntu234) precise; urgency=low
464+
465+ * Updated script to better deal with failure to create virtual machine
466+
467+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:12:57 -0400
468+
469+ubuntu-automation-test-harness (0.1ubuntu233) precise; urgency=low
470+
471+ * Fixed no-destroy to no_destroy
472+
473+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 16:04:54 -0400
474+
475+ubuntu-automation-test-harness (0.1ubuntu232) precise; urgency=low
476+
477+ * Added check for no HVM support
478+
479+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 15:34:01 -0400
480+
481+ubuntu-automation-test-harness (0.1ubuntu231) precise; urgency=low
482+
483+ * Made server default install type
484+
485+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 14:58:38 -0400
486+
487+ubuntu-automation-test-harness (0.1ubuntu230) precise; urgency=low
488+
489+ * Added run_uath_tests.py master script
490+
491+ -- Max Brustkern <max@canonical.com> Thu, 26 Apr 2012 14:52:11 -0400
492+
493+ubuntu-automation-test-harness (0.1ubuntu229) precise; urgency=low
494+
495+ * Initial draft of output processing for vm creation
496+
497+ -- Max Brustkern <max@canonical.com> Wed, 25 Apr 2012 19:14:57 -0400
498+
499+ubuntu-automation-test-harness (0.1ubuntu228) precise; urgency=low
500+
501+ * Updated output handling
502+
503+ -- Max Brustkern <max@canonical.com> Wed, 25 Apr 2012 16:47:53 -0400
504+
505+ubuntu-automation-test-harness (0.1ubuntu227) precise; urgency=low
506+
507+ * Removed shell=True
508+
509+ -- Max Brustkern <max@canonical.com> Wed, 25 Apr 2012 13:13:29 -0400
510+
511+ubuntu-automation-test-harness (0.1ubuntu226) precise; urgency=low
512+
513+ * Updated package to use new vm-tools package which places all
514+ necessary scripts in /usr/bin
515+
516+ -- Max Brustkern <max@canonical.com> Tue, 24 Apr 2012 17:59:21 -0400
517+
518+ubuntu-automation-test-harness (0.1ubuntu225) precise; urgency=low
519+
520+ * Removed DISPLAY clearing from .profile since vm-new has the -v
521+ option now
522+
523+ -- Max Brustkern <max@canonical.com> Tue, 24 Apr 2012 17:31:33 -0400
524+
525+ubuntu-automation-test-harness (0.1ubuntu224) precise; urgency=low
526+
527+ * Added -v option to vm-new call
528+
529+ -- Max Brustkern <max@canonical.com> Tue, 24 Apr 2012 17:27:24 -0400
530+
531+ubuntu-automation-test-harness (0.1ubuntu223) precise; urgency=low
532+
533+ * Changed exit status conditions for client
534+
535+ -- Max Brustkern <max@canonical.com> Tue, 24 Apr 2012 16:43:45 -0400
536+
537+ubuntu-automation-test-harness (0.1ubuntu222) precise; urgency=low
538+
539+ * Added machine info to client script
540+
541+ -- Max Brustkern <max@canonical.com> Tue, 24 Apr 2012 16:10:35 -0400
542+
543+ubuntu-automation-test-harness (0.1ubuntu221) precise; urgency=low
544+
545+ * Added machine info to client script
546+
547+ -- Max Brustkern <max@canonical.com> Tue, 24 Apr 2012 16:10:27 -0400
548+
549+ubuntu-automation-test-harness (0.1ubuntu220) oneiric; urgency=low
550+
551+ * Adding source format files
552+
553+ -- Max Brustkern <max@canonical.com> Mon, 23 Apr 2012 10:13:00 -0400
554+
555+ubuntu-automation-test-harness (0.1ubuntu219) oneiric; urgency=low
556+
557+ * Fixed argument order error when calling client
558+
559+ -- Max Brustkern <max@canonical.com> Sat, 21 Apr 2012 01:05:54 -0400
560+
561+ubuntu-automation-test-harness (0.1ubuntu218) oneiric; urgency=low
562+
563+ * Changed indention in client script and added json output support
564+
565+ -- Max Brustkern <max@canonical.com> Fri, 20 Apr 2012 17:59:23 -0400
566+
567+ubuntu-automation-test-harness (0.1ubuntu217) oneiric; urgency=low
568+
569+ * Removed sudoers file from install pending further testing
570+
571+ -- Max Brustkern <max@canonical.com> Thu, 19 Apr 2012 17:54:20 -0400
572+
573+ubuntu-automation-test-harness (0.1ubuntu216) oneiric; urgency=low
574+
575+ * Updated example syntax
576+
577+ -- Max Brustkern <max@canonical.com> Thu, 19 Apr 2012 17:51:39 -0400
578+
579+ubuntu-automation-test-harness (0.1ubuntu215) oneiric; urgency=low
580+
581+ * Fixed sudoers syntax
582+
583+ -- Max Brustkern <max@canonical.com> Thu, 19 Apr 2012 17:45:37 -0400
584+
585+ubuntu-automation-test-harness (0.1ubuntu214) oneiric; urgency=low
586+
587+ * Implemented initial logging and updated example
588+
589+ -- Max Brustkern <max@canonical.com> Thu, 19 Apr 2012 17:31:05 -0400
590+
591+ubuntu-automation-test-harness (0.1ubuntu213) oneiric; urgency=low
592+
593+ * Added sudoers file
594+
595+ -- Max Brustkern <max@canonical.com> Thu, 19 Apr 2012 15:59:08 -0400
596+
597+ubuntu-automation-test-harness (0.1ubuntu212) oneiric; urgency=low
598+
599+ * Changed default iso type to mini since desktop seems to not run
600+ latecommand until a user logs in
601+
602+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 21:38:57 -0400
603+
604+ubuntu-automation-test-harness (0.1ubuntu211) oneiric; urgency=low
605+
606+ * Fixed error in test runner
607+
608+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 19:35:31 -0400
609+
610+ubuntu-automation-test-harness (0.1ubuntu210) oneiric; urgency=low
611+
612+ * Added new example script
613+
614+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 18:18:02 -0400
615+
616+ubuntu-automation-test-harness (0.1ubuntu209) oneiric; urgency=low
617+
618+ * Added timeout to example test case since it is now mandatory
619+
620+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 17:55:47 -0400
621+
622+ubuntu-automation-test-harness (0.1ubuntu208) oneiric; urgency=low
623+
624+ * Improved command handling in run function
625+
626+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 17:51:35 -0400
627+
628+ubuntu-automation-test-harness (0.1ubuntu207) oneiric; urgency=low
629+
630+ * Fixed indentation typos and added /var/log/uath directory to client
631+ installation
632+
633+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 17:19:06 -0400
634+
635+ubuntu-automation-test-harness (0.1ubuntu206) oneiric; urgency=low
636+
637+ * Updated example to not use precise mini iso
638+
639+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 16:06:31 -0400
640+
641+ubuntu-automation-test-harness (0.1ubuntu205) oneiric; urgency=low
642+
643+ * Updated includes and module init files
644+
645+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 16:05:52 -0400
646+
647+ubuntu-automation-test-harness (0.1ubuntu204) oneiric; urgency=low
648+
649+ * Fixed a typo and added creation of /var/log/uath to packaging
650+
651+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 14:56:37 -0400
652+
653+ubuntu-automation-test-harness (0.1ubuntu203) oneiric; urgency=low
654+
655+ * Made action requires in testcase
656+
657+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 10:54:43 -0400
658+
659+ubuntu-automation-test-harness (0.1ubuntu202) oneiric; urgency=low
660+
661+ * Cleaned up named arguments, removed env from command running,
662+ removed relative imports
663+
664+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 10:36:07 -0400
665+
666+ubuntu-automation-test-harness (0.1ubuntu201) oneiric; urgency=low
667+
668+ * Changed SUCCESS to PASS and FAILUE to FAIL
669+
670+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 10:16:52 -0400
671+
672+ubuntu-automation-test-harness (0.1ubuntu200) oneiric; urgency=low
673+
674+ * Made timeout for testcase mandatory
675+
676+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 09:51:43 -0400
677+
678+ubuntu-automation-test-harness (0.1ubuntu199) oneiric; urgency=low
679+
680+ * Updated indentation: Changed default spacing from 2 to 4 spaces,
681+ continued multiline function arguments after parentheses, spaces
682+ multiline lists after opening bracket, used 4 space to indent
683+ multiline dictionaries and tuples, 80 column limit exceed in some
684+ cases (may fix in another pass,) if you see anything you don't agree
685+ with, let me know and I'll keep it in mind in the future
686+
687+ -- Max Brustkern <max@canonical.com> Wed, 18 Apr 2012 09:47:24 -0400
688+
689+ubuntu-automation-test-harness (0.1ubuntu198) oneiric; urgency=low
690+
691+ * Put response back in
692+
693+ -- Max Brustkern <max@canonical.com> Tue, 17 Apr 2012 10:05:03 -0400
694+
695+ubuntu-automation-test-harness (0.1ubuntu197) precise; urgency=low
696+
697+ * Collapse client package installation and post-installation to one
698+ command
699+
700+ -- Max Brustkern <max@canonical.com> Mon, 16 Apr 2012 19:50:27 -0400
701+
702+ubuntu-automation-test-harness (0.1ubuntu196) precise; urgency=low
703+
704+ * Set TinySQLiteCatalog to autocommit
705+
706+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 19:49:59 -0400
707+
708+ubuntu-automation-test-harness (0.1ubuntu195) precise; urgency=low
709+
710+ * Added batch mode to scp commands
711+
712+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 19:15:59 -0400
713+
714+ubuntu-automation-test-harness (0.1ubuntu194) precise; urgency=low
715+
716+ * Updated example and code based on testing
717+
718+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 18:18:46 -0400
719+
720+ubuntu-automation-test-harness (0.1ubuntu193) precise; urgency=low
721+
722+ * Updated example and code based on testing
723+
724+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 18:17:53 -0400
725+
726+ubuntu-automation-test-harness (0.1ubuntu192) precise; urgency=low
727+
728+ * Updated example and code based on testing
729+
730+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 17:45:10 -0400
731+
732+ubuntu-automation-test-harness (0.1ubuntu191) precise; urgency=low
733+
734+ * Cleaned up based on testing
735+
736+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 15:27:22 -0400
737+
738+ubuntu-automation-test-harness (0.1ubuntu190) precise; urgency=low
739+
740+ * Cleaned up syntax
741+
742+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 14:47:23 -0400
743+
744+ubuntu-automation-test-harness (0.1ubuntu189) precise; urgency=low
745+
746+ * Made some changes so most methods that aren't returning an object
747+ should be returning a boolean value and modified the example
748+
749+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 14:44:35 -0400
750+
751+ubuntu-automation-test-harness (0.1ubuntu188) precise; urgency=low
752+
753+ * Updated example and client package
754+
755+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 14:27:09 -0400
756+
757+ubuntu-automation-test-harness (0.1ubuntu187) precise; urgency=low
758+
759+ * Updated postinstall files
760+
761+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 14:17:30 -0400
762+
763+ubuntu-automation-test-harness (0.1ubuntu186) precise; urgency=low
764+
765+ * Updated example and setup vm-tools VMs to install python-yaml by
766+ default
767+
768+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 14:05:12 -0400
769+
770+ubuntu-automation-test-harness (0.1ubuntu185) precise; urgency=low
771+
772+ * Added example for provisioning a VM and running a test
773+
774+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 11:50:42 -0400
775+
776+ubuntu-automation-test-harness (0.1ubuntu184) precise; urgency=low
777+
778+ * Updated Makefile to create python modules properly with __init__.py
779+
780+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 11:37:00 -0400
781+
782+ubuntu-automation-test-harness (0.1ubuntu183) precise; urgency=low
783+
784+ * Changing make file to build uath module properly for client
785+
786+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 11:25:50 -0400
787+
788+ubuntu-automation-test-harness (0.1ubuntu182) precise; urgency=low
789+
790+ * Adjusting build to fix error when using pbuilder
791+
792+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 10:48:28 -0400
793+
794+ubuntu-automation-test-harness (0.1ubuntu181) precise; urgency=low
795+
796+ * Adjusting build to fix error when using pbuilder
797+
798+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 10:46:37 -0400
799+
800+ubuntu-automation-test-harness (0.1ubuntu180) precise; urgency=low
801+
802+ * Adjusting build to fix error when using pbuilder
803+
804+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 10:45:19 -0400
805+
806+ubuntu-automation-test-harness (0.1ubuntu179) precise; urgency=low
807+
808+ * Updated client packaging
809+
810+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 10:32:12 -0400
811+
812+ubuntu-automation-test-harness (0.1ubuntu178) precise; urgency=low
813+
814+ * Updating client packaging
815+
816+ -- Max Brustkern <max@canonical.com> Fri, 13 Apr 2012 10:01:29 -0400
817+
818+ubuntu-automation-test-harness (0.1ubuntu177) precise; urgency=low
819+
820+ * Changed copy command for client examples
821+
822+ -- Max Brustkern <max@canonical.com> Thu, 12 Apr 2012 18:01:06 -0400
823+
824+ubuntu-automation-test-harness (0.1ubuntu176) precise; urgency=low
825+
826+ * Updated examples in client deb
827+
828+ -- Max Brustkern <max@canonical.com> Thu, 12 Apr 2012 17:57:43 -0400
829+
830+ubuntu-automation-test-harness (0.1ubuntu175) precise; urgency=low
831+
832+ * Made additional changes to downloadfiles, still broken
833+
834+ -- Max Brustkern <max@canonical.com> Thu, 12 Apr 2012 17:43:51 -0400
835+
836+ubuntu-automation-test-harness (0.1ubuntu174) precise; urgency=low
837+
838+ * Changed client Makefile to install uath.client instead of just uath
839+
840+ -- Max Brustkern <max@canonical.com> Thu, 12 Apr 2012 17:22:21 -0400
841+
842+ubuntu-automation-test-harness (0.1ubuntu173) precise; urgency=low
843+
844+ * Renamed copyfile to uploadfiles, began implementation of
845+ downloadfiles
846+
847+ -- Max Brustkern <max@canonical.com> Thu, 12 Apr 2012 17:21:07 -0400
848+
849+ubuntu-automation-test-harness (0.1ubuntu172) precise; urgency=low
850+
851+ * Implemented installclient for VMToolsKVM
852+
853+ -- Max Brustkern <max@canonical.com> Thu, 12 Apr 2012 16:58:39 -0400
854+
855+ubuntu-automation-test-harness (0.1ubuntu171) precise; urgency=low
856+
857+ * Add start_time and time_delta to results
858+
859+ -- Joe Talbott <joe.talbott@canonical.com> Thu, 12 Apr 2012 16:55:48 -0400
860+
861+ubuntu-automation-test-harness (0.1ubuntu170) precise; urgency=low
862+
863+ * Update uath_howto.txt
864+
865+ -- Joe Talbott <joe.talbott@canonical.com> Thu, 12 Apr 2012 16:38:31 -0400
866+
867+ubuntu-automation-test-harness (0.1ubuntu169) precise; urgency=low
868+
869+ * Move /var/uath to /var/lib/uath and fix client examples
870+
871+ -- Joe Talbott <joe.talbott@canonical.com> Thu, 12 Apr 2012 16:30:09 -0400
872+
873+ubuntu-automation-test-harness (0.1ubuntu168) precise; urgency=low
874+
875+ * move client code into its own subdirectory
876+
877+ -- Joe Talbott <joe.talbott@canonical.com> Thu, 12 Apr 2012 16:17:14 -0400
878+
879+ubuntu-automation-test-harness (0.1ubuntu167) oneiric; urgency=low
880+
881+ * Adding distribute tarball to source because buildds can't download
882+ it
883+
884+ -- Max Brustkern <max@canonical.com> Thu, 12 Apr 2012 11:36:27 -0400
885+
886+ubuntu-automation-test-harness (0.1ubuntu166) precise; urgency=low
887+
888+ * Update debian/postinst for new name client.py and fix self_test.py
889+ to output nosetest instructions.
890+
891+ -- Joe Talbott <joe.talbott@canonical.com> Thu, 12 Apr 2012 10:32:26 -0400
892+
893+ubuntu-automation-test-harness (0.1ubuntu165) precise; urgency=low
894+
895+ * Rename main.py to client.py and update README
896+
897+ -- Joe Talbott <joe.talbott@canonical.com> Thu, 12 Apr 2012 10:27:36 -0400
898+
899+ubuntu-automation-test-harness (0.1ubuntu164) precise; urgency=low
900+
901+ * Cleanup /etc/rc.local handling
902+
903+ -- Joe Talbott <joe.talbott@canonical.com> Thu, 12 Apr 2012 09:17:28 -0400
904+
905+ubuntu-automation-test-harness (0.1ubuntu163) oneiric; urgency=low
906+
907+ * Attempting to get build working in pbuilder
908+
909+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 18:45:10 -0400
910+
911+ubuntu-automation-test-harness (0.1ubuntu162) oneiric; urgency=low
912+
913+ * Attempting to get build working in pbuilder
914+
915+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 18:40:55 -0400
916+
917+ubuntu-automation-test-harness (0.1ubuntu161) oneiric; urgency=low
918+
919+ * Attempting to get build working in pbuilder
920+
921+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 18:37:45 -0400
922+
923+ubuntu-automation-test-harness (0.1ubuntu160) oneiric; urgency=low
924+
925+ * Attempting to use correct package name for build-dep
926+
927+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 18:11:56 -0400
928+
929+ubuntu-automation-test-harness (0.1ubuntu159) oneiric; urgency=low
930+
931+ * Adding debuild build-dep
932+
933+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 18:09:10 -0400
934+
935+ubuntu-automation-test-harness (0.1ubuntu158) oneiric; urgency=low
936+
937+ * Trying rule override to use python build system and Makefile
938+
939+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 18:05:50 -0400
940+
941+ubuntu-automation-test-harness (0.1ubuntu157) oneiric; urgency=low
942+
943+ * Additional changes to support inner package
944+
945+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 18:00:57 -0400
946+
947+ubuntu-automation-test-harness (0.1ubuntu156) oneiric; urgency=low
948+
949+ * Additional changes to support inner package
950+
951+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:50:46 -0400
952+
953+ubuntu-automation-test-harness (0.1ubuntu155) oneiric; urgency=low
954+
955+ * Made possibly final changes to inner package Makefile
956+
957+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:44:51 -0400
958+
959+ubuntu-automation-test-harness (0.1ubuntu154) oneiric; urgency=low
960+
961+ * Made changes to inner package Makefile
962+
963+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:37:56 -0400
964+
965+ubuntu-automation-test-harness (0.1ubuntu153) oneiric; urgency=low
966+
967+ * Made getroot executable
968+
969+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:35:25 -0400
970+
971+ubuntu-automation-test-harness (0.1ubuntu152) oneiric; urgency=low
972+
973+ * Made changes to inner package Makefile
974+
975+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:34:30 -0400
976+
977+ubuntu-automation-test-harness (0.1ubuntu151) oneiric; urgency=low
978+
979+ * Made changes to inner package Makefile
980+
981+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:32:57 -0400
982+
983+ubuntu-automation-test-harness (0.1ubuntu150) oneiric; urgency=low
984+
985+ * Added Makefile instead of Makefile~
986+
987+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:28:33 -0400
988+
989+ubuntu-automation-test-harness (0.1ubuntu149) oneiric; urgency=low
990+
991+ * bzr removed file instead of just removing it
992+
993+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:26:51 -0400
994+
995+ubuntu-automation-test-harness (0.1ubuntu148) oneiric; urgency=low
996+
997+ * Created initial attempt at inner package
998+
999+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:25:53 -0400
1000+
1001+ubuntu-automation-test-harness (0.1ubuntu147) oneiric; urgency=low
1002+
1003+ * Moving back to a single package; will build the client package
1004+ separately from the Makefile
1005+
1006+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 17:17:13 -0400
1007+
1008+ubuntu-automation-test-harness (0.1ubuntu146) precise; urgency=low
1009+
1010+ * Treat timeouts as errors
1011+
1012+ -- Joe Talbott <joe.talbott@canonical.com> Wed, 11 Apr 2012 16:27:31 -0400
1013+
1014+ubuntu-automation-test-harness (0.1ubuntu145) oneiric; urgency=low
1015+
1016+ * Trying different method of target-dependent override_dh_auto_build
1017+
1018+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 16:20:53 -0400
1019+
1020+ubuntu-automation-test-harness (0.1ubuntu144) oneiric; urgency=low
1021+
1022+ * Trying target-dependent override_dh_auto_build
1023+
1024+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 16:18:29 -0400
1025+
1026+ubuntu-automation-test-harness (0.1ubuntu143) oneiric; urgency=low
1027+
1028+ * Trying override_dh_auto_build again
1029+
1030+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 16:15:04 -0400
1031+
1032+ubuntu-automation-test-harness (0.1ubuntu142) oneiric; urgency=low
1033+
1034+ * Trying override_dh_auto_build again
1035+
1036+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 16:12:13 -0400
1037+
1038+ubuntu-automation-test-harness (0.1ubuntu141) oneiric; urgency=low
1039+
1040+ * Trying override_dh_auto_build
1041+
1042+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 16:09:25 -0400
1043+
1044+ubuntu-automation-test-harness (0.1ubuntu140) oneiric; urgency=low
1045+
1046+ * Trying override_dh_auto_install stanza for client package only
1047+
1048+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 15:55:34 -0400
1049+
1050+ubuntu-automation-test-harness (0.1ubuntu139) oneiric; urgency=low
1051+
1052+ * Trying override_dh_auto_install stanza
1053+
1054+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 15:44:03 -0400
1055+
1056+ubuntu-automation-test-harness (0.1ubuntu138) oneiric; urgency=low
1057+
1058+ * Attempting to add usr dir to install files
1059+
1060+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 15:39:01 -0400
1061+
1062+ubuntu-automation-test-harness (0.1ubuntu137) oneiric; urgency=low
1063+
1064+ * Retrying package split to get build differences
1065+
1066+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 15:28:49 -0400
1067+
1068+ubuntu-automation-test-harness (0.1ubuntu136) oneiric; urgency=low
1069+
1070+ * Attempting to get the package building correctly again
1071+
1072+ -- Max Brustkern <max@canonical.com> Wed, 11 Apr 2012 15:22:27 -0400
1073+
1074+ubuntu-automation-test-harness (0.1ubuntu135) precise; urgency=low
1075+
1076+ * Add better results indication and tests
1077+
1078+ -- Joe Talbott <joe.talbott@canonical.com> Wed, 11 Apr 2012 15:11:57 -0400
1079+
1080+ubuntu-automation-test-harness (0.1ubuntu134) precise; urgency=low
1081+
1082+ * clean up uath/self_test.py
1083+
1084+ -- Joe Talbott <joe.talbott@canonical.com> Wed, 11 Apr 2012 11:48:14 -0400
1085+
1086 ubuntu-automation-test-harness (0.1ubuntu133) oneiric; urgency=low
1087
1088 * First attempt at split binary configuration
1089
1090=== renamed file 'debian/ubuntu-automation-test-harness.conffiles' => 'debian/conffiles'
1091--- debian/ubuntu-automation-test-harness.conffiles 2012-04-11 13:22:05 +0000
1092+++ debian/conffiles 2012-05-09 00:33:18 +0000
1093@@ -1,2 +1,1 @@
1094-var/lib/uath/.profile
1095 var/lib/uath/.ssh/config
1096
1097=== modified file 'debian/control'
1098--- debian/control 2012-04-11 13:22:05 +0000
1099+++ debian/control 2012-05-09 00:33:18 +0000
1100@@ -3,7 +3,7 @@
1101 X-Python-Version: >= 2.5
1102 Priority: optional
1103 Maintainer: Max Brustkern <max@canonical.com>
1104-Build-Depends: debhelper, python-all
1105+Build-Depends: debhelper, python-all, devscripts
1106 Standards-Version: 3.9.2
1107 Homepage: https://code.launchpad.net/ubuntu-automation-test-harness
1108 Vcs-Bzr: https://code.launchpad.net/ubuntu-automation-test-harness
1109@@ -16,9 +16,3 @@
1110 Conflicts: ubuntu-automation-test-harness-client
1111 Description: Ubuntu Automation Test Harness
1112 Automation framework for testing in Ubuntu
1113-
1114-Package: ubuntu-automation-test-harness-client
1115-Architecture: all
1116-Depends: ${python:Depends}, python-apt, python-yaml
1117-Description: Ubuntu Automation Test Harness Client
1118- Automation framework for testing in Ubuntu, client portion
1119
1120=== renamed file 'debian/ubuntu-automation-test-harness.dirs' => 'debian/dirs'
1121--- debian/ubuntu-automation-test-harness.dirs 2012-04-11 13:22:05 +0000
1122+++ debian/dirs 2012-05-09 00:33:18 +0000
1123@@ -1,2 +1,3 @@
1124 var/cache/uath/iso/cache
1125 var/lib/uath/vm
1126+var/log/uath
1127
1128=== renamed file 'debian/ubuntu-automation-test-harness.install' => 'debian/install'
1129--- debian/ubuntu-automation-test-harness.install 2012-04-11 13:22:05 +0000
1130+++ debian/install 2012-05-09 00:33:18 +0000
1131@@ -1,4 +1,6 @@
1132 uqt-vm-tools.conf etc/uath
1133-.profile var/lib/uath
1134+shell-profile etc/uath
1135 config var/lib/uath/.ssh
1136 examples usr/share/uath
1137+ubuntu-automation-test-harness-client_*_all.deb usr/share/uath
1138+uath/client/examples/master.run usr/share/uath/examples
1139
1140=== renamed file 'debian/ubuntu-automation-test-harness.postinst' => 'debian/postinst'
1141--- debian/ubuntu-automation-test-harness.postinst 2012-04-11 13:22:05 +0000
1142+++ debian/postinst 2012-05-09 00:33:18 +0000
1143@@ -5,7 +5,7 @@
1144 # Sane defaults:
1145
1146 [ -z "$SERVER_HOME" ] && SERVER_HOME=/var/lib/uath
1147- [ -z "$SERVER_DIRS" ] && SERVER_DIRS="$SERVER_HOME /var/cache/uath"
1148+ [ -z "$SERVER_DIRS" ] && SERVER_DIRS="$SERVER_HOME /var/cache/uath /var/log/uath"
1149 [ -z "$SERVER_USER" ] && SERVER_USER=uath
1150 [ -z "$SERVER_NAME" ] && SERVER_NAME="UATH"
1151 [ -z "$SERVER_GROUP" ] && SERVER_GROUP=uath
1152@@ -24,8 +24,8 @@
1153 do
1154 if ! dpkg-statoverride --list $DIR >/dev/null
1155 then
1156- chown -R $SERVER_USER:adm $DIR
1157- chmod 2755 $DIR
1158+ chown -R $SERVER_USER:$SERVER_GROUP $DIR
1159+ chmod 2775 $DIR
1160 fi
1161 done
1162 }
1163@@ -50,18 +50,19 @@
1164
1165 usersetup
1166
1167-ln -sf /etc/uath/uqt-vm-tools.conf /var/lib/uath/.uqt-vm-tools.conf
1168-ln -sf /usr/share/pyshared/uath/main.py /usr/bin/uath
1169+ln -sf /etc/uath/uqt-vm-tools.conf ~uath/.uqt-vm-tools.conf
1170+ln -sf /etc/uath/shell-profile ~uath/.profile
1171+ln -sf /usr/share/pyshared/uath/client.py /usr/bin/uath
1172
1173-if ! ([ -f "/var/lib/uath/.ssh/uath" ] && [ -f "/var/lib/uath/.ssh/uath.pub" ])
1174+if ! ([ -f ~uath/.ssh/uath ] && [ -f ~uath/.ssh/uath.pub ])
1175 then
1176 echo "Generating RSA keypair for uath user..." 1>&2
1177 su - uath -c 'ssh-keygen -t rsa -f ~/.ssh/uath -q -P ""'
1178 fi
1179
1180-if ! [ -f "/var/lib/uath/.ssh/known_hosts" ]
1181+if ! [ -f ~uath/.ssh/known_hosts ]
1182 then
1183- su - uath -c "touch /var/lib/uath/.ssh/known_hosts"
1184+ su - uath -c "touch ~/.ssh/known_hosts"
1185 fi
1186
1187 dnssetup
1188
1189=== renamed file 'debian/ubuntu-automation-test-harness.postrm' => 'debian/postrm'
1190=== modified file 'debian/rules'
1191--- debian/rules 2012-04-02 19:33:08 +0000
1192+++ debian/rules 2012-05-09 00:33:18 +0000
1193@@ -1,4 +1,8 @@
1194 #!/usr/bin/make -f
1195
1196 %:
1197- dh $@ --buildsystem=python_distutils --with=python2
1198+ dh $@ --buildsystem=python_distutils --with=python2
1199+
1200+override_dh_auto_build:
1201+ dh_auto_build
1202+ make
1203
1204=== added directory 'debian/source'
1205=== added file 'debian/source/format'
1206--- debian/source/format 1970-01-01 00:00:00 +0000
1207+++ debian/source/format 2012-05-09 00:33:18 +0000
1208@@ -0,0 +1,1 @@
1209+3.0 (quilt)
1210
1211=== removed file 'debian/ubuntu-automation-test-harness-client.install'
1212--- debian/ubuntu-automation-test-harness-client.install 2012-04-11 13:22:05 +0000
1213+++ debian/ubuntu-automation-test-harness-client.install 1970-01-01 00:00:00 +0000
1214@@ -1,1 +0,0 @@
1215-examples usr/share/uath
1216
1217=== added file 'distribute-0.6.25.tar.gz'
1218Binary files distribute-0.6.25.tar.gz 1970-01-01 00:00:00 +0000 and distribute-0.6.25.tar.gz 2012-05-09 00:33:18 +0000 differ
1219=== removed file 'examples/provision.py'
1220--- examples/provision.py 2012-04-09 20:46:17 +0000
1221+++ examples/provision.py 1970-01-01 00:00:00 +0000
1222@@ -1,45 +0,0 @@
1223-#!/usr/bin/python
1224-
1225-from uath import UATHException
1226-import uath.provisioning
1227-import os.path
1228-
1229-try:
1230- # Create a catalog to manage VMs
1231- # Default catalog configuration attempts to prevent multiple scripts from requesting the same resources
1232- # We use a non-default db and lockfile to work around this for the example
1233- # For normal use, omit the db and lockfile parameters
1234- catalog = uath.provisioning.catalog.TinySQLiteCatalog(db = os.path.expanduser('~/.uath-example-sqlite-catalog'), lockfile = os.path.expanduser('~/.uath-example-catalog'))
1235-
1236- # Request a VM from the catalog
1237- # arch can be i386 or amd64/x86_64
1238- # installtype is used for automatic iso selection (desktop, server, mini, alternate)
1239- # prefix is used by vm-tools to allow opportunities on multiple simultaneous VMs
1240- # default is uath, we use a non-default option to avoid collision with existing VMs
1241- # machineid is appened to prefix to give unique names
1242- # only mini currently works for precise, all should work on release
1243- # series can be any supported series, should be all lowercase
1244- # no arguments will give i386, desktop, precise, which currently fails to download, but should work on release
1245- # For normal use, omit prefix, and the other parameters are optional
1246- machine = catalog.request(arch='i386', installtype='mini', prefix='uath-example', series='precise')
1247-
1248- # Run a command
1249- # This will check if the machine is provisioned, and provision it if it is not using vm-new
1250- # Provisioning may take a while, and currently times out at a max of around 90 minutes
1251- # After provision(), the machine will be stopped, so it will be automatically started before running the command
1252- # machine.provision() and machine.run() could be run manually before this, but that is not required
1253- machine.run('/sbin/ifconfig')
1254-
1255- # Destroy the VM
1256- # This uses vm-remove to do a hard stop and then delete the machine
1257- machine.destroy()
1258-
1259- # Remove the machine from the catalog and delete it
1260- catalog.destroy(machine.machineid)
1261- del machine
1262-
1263- # Delete the catalog
1264- catalog.delete()
1265-
1266-except UATHException as error:
1267- print('Exception: ' + error.msg)
1268
1269=== added file 'examples/run_test_vm.py'
1270--- examples/run_test_vm.py 1970-01-01 00:00:00 +0000
1271+++ examples/run_test_vm.py 2012-05-09 00:33:18 +0000
1272@@ -0,0 +1,105 @@
1273+#!/usr/bin/python
1274+
1275+import argparse
1276+import getpass
1277+import grp
1278+import logging
1279+import os
1280+import sys
1281+import time
1282+import urllib
1283+
1284+from uath import UATHException
1285+import uath.provisioning
1286+
1287+def check_user_group(group='uath'):
1288+ return grp.getgrnam(group)[2] in os.getgroups()
1289+
1290+
1291+def get_parser():
1292+ parser = argparse.ArgumentParser(description='Create a virtual machine and run a UATH runlist there.', epilog="For example:\n\t%(prog)s -s oneiric -t server -a i386 /usr/share/uath/examples/master.run 'http://people.canonical.com/~max/max_test.run'", formatter_class=argparse.RawDescriptionHelpFormatter)
1293+ parser.add_argument('runlists', metavar='runlist', nargs='+', help='URLs of runlist files to run')
1294+ parser.add_argument('-s', '--series', choices=('hardy', 'lucid', 'natty', 'oneiric', 'precise', 'quantal'), default='oneiric', help='Series to use for VM creation')
1295+ parser.add_argument('-t', '--type', choices=('desktop', 'server', 'mini', 'alternate'), default='server', help='Install type to use for VM creation')
1296+ parser.add_argument('-a', '--arch', choices=('i386', 'amd64'), default='i386', help='Architecture to use for VM creation')
1297+ parser.add_argument('-n', '--no-destroy', action='store_true', help='Preserve VM after tests have run')
1298+ parser.add_argument('-d', '--debug', action='store_true', help='Enable debug logging')
1299+ parser.add_argument('-j', '--json', action='store_true', help='Enable json logging (Default is YAML)')
1300+ return parser
1301+
1302+
1303+def run_test_vm(args = None):
1304+ if not check_user_group():
1305+ sys.stderr.write("Error: you are not in the UATH group.\n")
1306+ sys.stderr.write("If you believe you have properly configured your user account for UATH use, try:\n")
1307+ sys.stderr.write(' sudo usermod -a -G uath ' + getpass.getuser() + "\n")
1308+ sys.stderr.write("Otherwise, please run this script as the uath user, i.e.:\n")
1309+ argv = list(sys.argv)
1310+ argv[0] = os.path.abspath(__file__)
1311+ sys.stderr.write(" sudo su - uath -c '" + ' '.join(argv) + "'\n")
1312+ sys.exit(3)
1313+
1314+ if args is None:
1315+ args = get_parser().parse_args()
1316+
1317+ locallogs = []
1318+ machine = None
1319+
1320+ if args.json:
1321+ extraopts = ' -f json'
1322+ suffix = '.json'
1323+ else:
1324+ extraopts = ''
1325+ suffix = '.yaml'
1326+
1327+ try:
1328+ catalog = uath.provisioning.catalog.TinySQLiteCatalog()
1329+ machine = catalog.request(arch=args.arch, debug=args.debug, installtype=args.type, new=True, dlpercentincrement=10, series=args.series)
1330+ for logger in [machine.logger] + machine.logger.handlers:
1331+ if logger.level > logging.INFO:
1332+ logger.setLevel(logging.INFO)
1333+ machine.installclient()
1334+ for runlist in args.runlists:
1335+ locallist = urllib.urlretrieve(runlist)[0]
1336+ listname = os.path.split(runlist)[1]
1337+ remotelog = os.path.normpath('/var/log/uath/' + machine.name + '_' + listname + '_tmp')
1338+ locallog = os.path.normpath('/var/log/uath/' + machine.name + '_' + listname + time.strftime('_%Y-%m-%d_%H-%m-%S', time.gmtime()) + suffix)
1339+ try:
1340+ machine.uploadfiles([locallist], os.path.normpath('/tmp'))
1341+ machine.run('uath' + extraopts + ' -r /tmp/' + os.path.split(locallist)[1] + ' -o ' + remotelog, root=True)
1342+ machine.downloadfiles([remotelog], locallog)
1343+ print('Test log copied to ' + locallog)
1344+ locallogs.append(locallog)
1345+ except UATHException as error:
1346+ print('Failed to run test: ' + error.msg)
1347+ finally:
1348+ try:
1349+ pass
1350+ machine.run('rm -f ' + remotelog)
1351+ except UATHException as error:
1352+ print('Failed to cleanup test: ' + error.msg)
1353+
1354+ except UATHException as error:
1355+ print('Exception: ' + error.msg)
1356+
1357+ finally:
1358+ urllib.urlcleanup()
1359+ if not args.no_destroy and machine is not None:
1360+ try:
1361+ machine.destroy()
1362+ except UATHException as error:
1363+ print('Failed to destroy machine: ' + error.msg)
1364+ finally:
1365+ try:
1366+ catalog.destroy(machine.machineid)
1367+ except UATHException as error:
1368+ print('Failed to update catalog: ' + error.msg)
1369+ finally:
1370+ del machine
1371+ if len(locallogs) != 0:
1372+ print('Test logs copied to the following files:')
1373+ print("\t" + "\n\t".join(locallogs))
1374+
1375+
1376+if __name__ == '__main__':
1377+ run_test_vm()
1378
1379=== added file 'examples/run_uath_tests.py'
1380--- examples/run_uath_tests.py 1970-01-01 00:00:00 +0000
1381+++ examples/run_uath_tests.py 2012-05-09 00:33:18 +0000
1382@@ -0,0 +1,71 @@
1383+#!/usr/bin/python
1384+
1385+import argparse
1386+import getpass
1387+import grp
1388+import logging
1389+import os
1390+import sys
1391+import time
1392+import urllib
1393+
1394+from uath import UATHException
1395+import uath.provisioning
1396+
1397+def check_user_group(group='uath'):
1398+ return grp.getgrnam(group)[2] in os.getgroups()
1399+
1400+
1401+def get_parser():
1402+ parser = argparse.ArgumentParser(description='Provision a machine and run one or more UATH runlists there.', epilog="For example:\n\t%(prog)s -s oneiric -t server -a i386 /usr/share/uath/examples/master.run 'http://people.canonical.com/~max/max_test.run'", formatter_class=argparse.RawDescriptionHelpFormatter)
1403+ parser.add_argument('runlists', metavar='runlist', nargs='+', help='URLs of runlist files to run')
1404+ parser.add_argument('-m', '--machinetype', choices=('physical', 'virtual'), default='virtual', help='Type of machine to provision')
1405+ parser.add_argument('-i', '--image', type=argparse.FileType('r'), help='Image/ISO file to use for installation')
1406+ parser.add_argument('-k', '--kernel', type=argparse.FileType('r'), help='Kernel file to use for installation')
1407+ parser.add_argument('-r', '--initrd', type=argparse.FileType('r'), help='InitRD file to use for installation')
1408+ parser.add_argument('-p', '--preseed', type=argparse.FileType('r'), help='Preseed file to use for installation')
1409+ parser.add_argument('-s', '--series', choices=('hardy', 'lucid', 'natty', 'oneiric', 'precise', 'quantal'), default='oneiric', help='Series to use for installation')
1410+ parser.add_argument('-t', '--type', choices=('desktop', 'server', 'mini', 'alternate'), default='server', help='Install type to use for installation')
1411+ parser.add_argument('-a', '--arch', choices=('i386', 'amd64', 'arm'), default='i386', help='Architecture to use for installation')
1412+ parser.add_argument('-v', '--variant', help='Variant of architecture, i.e., armel, armhf')
1413+ parser.add_argument('-n', '--no-destroy', action='store_true', help='Preserve machine after tests have run')
1414+ parser.add_argument('-d', '--debug', action='store_true', help='Enable debug logging')
1415+ parser.add_argument('-j', '--json', action='store_true', help='Enable json logging (Default is YAML)')
1416+ return parser
1417+
1418+
1419+def run_uath_tests(args=None):
1420+ if not check_user_group():
1421+ sys.stderr.write("Error: you are not in the UATH group.\n")
1422+ sys.stderr.write("If you believe you have properly configured your user account for UATH use, try:\n")
1423+ sys.stderr.write(' sudo usermod -a -G uath ' + getpass.getuser() + "\n")
1424+ sys.stderr.write("Otherwise, please run this script as the uath user, i.e.:\n")
1425+ argv = list(sys.argv)
1426+ argv[0] = os.path.abspath(__file__)
1427+ sys.stderr.write(" sudo su - uath -c '" + ' '.join(argv) + "'\n")
1428+ sys.exit(3)
1429+
1430+ if args is None:
1431+ args = get_parser().parse_args()
1432+
1433+ if args.machinetype == 'physical':
1434+ sys.stderr.write("Physical machine provisioning is not supported in this release.\n")
1435+ sys.stderr.write("Please check the roadmap, as it will be included in a future version.\n")
1436+ sys.exit(4)
1437+
1438+ for option in [args.image, args.kernel, args.initrd, args.preseed]:
1439+ if option is not None:
1440+ sys.stderr.write("Custom VM provisioning is not supported in this release.\n")
1441+ sys.stderr.write("Please check the roadmap, as it will be included in a future version.\n")
1442+ sys.exit(4)
1443+
1444+ if 'arm' in args.arch:
1445+ sys.stderr.write("ARM support is not included in this release.\n")
1446+ sys.stderr.write("Please check the roadmap, as it will be included in a future version.\n")
1447+ sys.exit(4)
1448+
1449+ from run_test_vm import run_test_vm
1450+ run_test_vm(args=args)
1451+
1452+if __name__ == '__main__':
1453+ run_uath_tests()
1454
1455=== added file 'shell-profile'
1456--- shell-profile 1970-01-01 00:00:00 +0000
1457+++ shell-profile 2012-05-09 00:33:18 +0000
1458@@ -0,0 +1,1 @@
1459+umask 002
1460
1461=== added directory 'uath/client'
1462=== renamed file 'uath/README' => 'uath/client/README'
1463--- uath/README 2012-04-10 20:49:15 +0000
1464+++ uath/client/README 2012-05-09 00:33:18 +0000
1465@@ -79,4 +79,4 @@
1466 [command: <testcase command>]
1467 [tc_setup: <testcase setup command>]
1468 [tc_cleanup: <testcase cleanup command>]
1469-[reboot: {always, success, never}] # defaults to 'never'
1470+[reboot: {always, pass, never}] # defaults to 'never'
1471
1472=== added file 'uath/client/__init__.py'
1473=== renamed file 'uath/common.py' => 'uath/client/common.py'
1474--- uath/common.py 2012-04-04 21:52:30 +0000
1475+++ uath/client/common.py 2012-05-09 00:33:18 +0000
1476@@ -2,19 +2,20 @@
1477 Common methods.
1478 """
1479
1480+import datetime
1481 import os
1482 import signal
1483 import subprocess
1484 import yaml
1485
1486
1487-SUCCESS=0
1488-FAILURE=1
1489+PASS=0
1490+FAIL=1
1491 ERROR_TIMEOUT=-9
1492
1493 CONFIG = {
1494- 'DEBUG': False,
1495- 'TEST_DIR': '/var/uath',
1496+ 'DEBUG': False,
1497+ 'TEST_DIR': '/var/lib/uath',
1498 }
1499
1500 # Default TestSuite filenames
1501@@ -22,134 +23,149 @@
1502 DEFAULT_TSCONTROL='ts_control'
1503
1504 MASTER_RUNLIST="master.run"
1505-UATH_DIR="/var/uath"
1506+UATH_DIR=CONFIG['TEST_DIR']
1507 DEFAULT_STATE_FILE=os.path.join(UATH_DIR, "state.yaml")
1508
1509+RETURN_CODES={'PASS': 0, 'ERROR': -1, 'FAIL': 1}
1510+
1511 def do_nothing(obj=None):
1512- """
1513- Do nothing method used a placeholder for save_state_callbacks.
1514- """
1515- pass
1516+ """
1517+ Do nothing method used a placeholder for save_state_callbacks.
1518+ """
1519+ pass
1520
1521 # Inspired by:
1522 # http://stackoverflow.com/a/3326559
1523 def run_cmd(command, cwd=None, timeout=0):
1524
1525- if command is None:
1526- return
1527-
1528- class TimeoutAlarm(Exception):
1529- pass
1530-
1531- def alarm_handler(signum, frame):
1532- raise TimeoutAlarm
1533-
1534- p = subprocess.Popen(command, shell=True, cwd=cwd, stdout=subprocess.PIPE,
1535- stderr=subprocess.PIPE)
1536-
1537- if timeout != 0:
1538- signal.signal(signal.SIGALRM, alarm_handler)
1539- signal.alarm(timeout)
1540-
1541- try:
1542- stdout, stderr = p.communicate()
1543+ if command is None:
1544+ return
1545+
1546+ class TimeoutAlarm(Exception):
1547+ pass
1548+
1549+ def alarm_handler(signum, frame):
1550+ raise TimeoutAlarm
1551+
1552+ start_time = datetime.datetime.now()
1553+ p = subprocess.Popen(command, shell=True, cwd=cwd, stdout=subprocess.PIPE,
1554+ stderr=subprocess.PIPE)
1555+
1556 if timeout != 0:
1557- signal.alarm(0)
1558- except TimeoutAlarm:
1559- pids = [p.pid]
1560- # Kill p's children too.
1561- pids.extend(get_process_children(p.pid))
1562-
1563- for pid in pids:
1564- # process might have died before getting to this line
1565- # so wrap to avoid OSError: no such process
1566- try:
1567- os.kill(pid, signal.SIGKILL)
1568- except OSError:
1569- pass
1570- return make_result((command, ERROR_TIMEOUT, '', ''))
1571-
1572- return make_result((command, p.returncode, stdout, stderr))
1573+ signal.signal(signal.SIGALRM, alarm_handler)
1574+ signal.alarm(timeout)
1575+
1576+ try:
1577+ stdout, stderr = p.communicate()
1578+ if timeout != 0:
1579+ signal.alarm(0)
1580+ except TimeoutAlarm:
1581+ pids = [p.pid]
1582+ # Kill p's children too.
1583+ pids.extend(get_process_children(p.pid))
1584+
1585+ for pid in pids:
1586+ # process might have died before getting to this line
1587+ # so wrap to avoid OSError: no such process
1588+ try:
1589+ os.kill(pid, signal.SIGKILL)
1590+ except OSError:
1591+ pass
1592+
1593+ time_delta = datetime.datetime.now() - start_time
1594+
1595+ return make_result(command=command,
1596+ retcode=ERROR_TIMEOUT,
1597+ start_time=str(start_time),
1598+ time_delta=str(time_delta),
1599+ )
1600+
1601+ time_delta = datetime.datetime.now() - start_time
1602+
1603+ return make_result(command=command,
1604+ retcode=p.returncode,
1605+ stdout=stdout,
1606+ stderr=stderr,
1607+ start_time=str(start_time),
1608+ time_delta=str(time_delta),
1609+ )
1610
1611 # TODO: it might make sense to have a result object that keeps track of
1612-# test success, failure, error and serializes it's data.
1613-def make_result(result):
1614- """
1615- Turn a tuple of (returncode, stdout, stderr) into a dict.
1616- """
1617-
1618- # If result is not a tuple of size 3 return a blank result
1619- if len(result) != 4:
1620- result = (None, None, '', '')
1621-
1622- res = {
1623- 'command': result[0],
1624- 'returncode': result[1],
1625- 'stdout': result[2],
1626- 'stderr': result[3]
1627- }
1628-
1629- return res
1630+# test pass, fail, error and serializes it's data.
1631+def make_result(command, retcode, stdout='', stderr='', start_time='', time_delta=''):
1632+ """
1633+ Make a result dictionary.
1634+ """
1635+ res = {
1636+ 'command': command,
1637+ 'returncode': retcode,
1638+ 'stdout': stdout,
1639+ 'stderr': stderr,
1640+ 'start_time': start_time,
1641+ 'time_delta': time_delta,
1642+ }
1643+
1644+ return res
1645
1646 def get_process_children(pid):
1647- p = subprocess.Popen('ps --no-headers -o pid --ppid %d' % pid, shell=True,
1648- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1649- stdout, stderr = p.communicate()
1650+ p = subprocess.Popen('ps --no-headers -o pid --ppid %d' % pid, shell=True,
1651+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1652+ stdout, stderr = p.communicate()
1653
1654- return [int(pid) for pid in stdout.split()]
1655+ return [int(pid) for pid in stdout.split()]
1656
1657
1658 def run_cmd_depricated(command):
1659- """
1660- Runs 'command' using a shell, so be careful.
1661- """
1662-
1663- if command is None:
1664- return
1665-
1666- rc = subprocess.call(command, shell=True)
1667- if rc != 0:
1668- raise Exception("command failed: %s: %d" % (command, rc))
1669+ """
1670+ Runs 'command' using a shell, so be careful.
1671+ """
1672+
1673+ if command is None:
1674+ return
1675+
1676+ rc = subprocess.call(command, shell=True)
1677+ if rc != 0:
1678+ raise Exception("command failed: %s: %d" % (command, rc))
1679
1680 def parse_control_file(filename, required=None, optional=None):
1681- """
1682- Parse a control file and only include the items in required and optional.
1683+ """
1684+ Parse a control file and only include the items in required and optional.
1685
1686- If any required items are missing an exception is raised. Optional items
1687- that are not in the control file are set to None.
1688- """
1689- fp = open(filename, 'r')
1690-
1691- data = yaml.load(fp)
1692-
1693- fp.close()
1694-
1695- control_data = {}
1696-
1697- for item in required:
1698- control_data[item] = data[item]
1699-
1700- for item in optional:
1701- control_data[item] = data.get(item)
1702-
1703- if not required and not optional:
1704- control_data = data
1705-
1706- return control_data
1707+ If any required items are missing an exception is raised. Optional items
1708+ that are not in the control file are set to None.
1709+ """
1710+ fp = open(filename, 'r')
1711+
1712+ data = yaml.load(fp)
1713+
1714+ fp.close()
1715+
1716+ control_data = {}
1717+
1718+ for item in required:
1719+ control_data[item] = data[item]
1720+
1721+ for item in optional:
1722+ control_data[item] = data.get(item)
1723+
1724+ if not required and not optional:
1725+ control_data = data
1726+
1727+ return control_data
1728
1729 def debug_print(data, force=False):
1730- """
1731- Print debugging information if CONFIG['DEBUG'] is defined and True
1732- """
1733- try:
1734- debug = CONFIG['DEBUG']
1735- except NameError:
1736- debug = False
1737-
1738- debug = debug or force
1739-
1740- if debug:
1741- print "DEBUG:", data
1742-
1743- # return True if we printed some output
1744- return debug
1745+ """
1746+ Print debugging information if CONFIG['DEBUG'] is defined and True
1747+ """
1748+ try:
1749+ debug = CONFIG['DEBUG']
1750+ except NameError:
1751+ debug = False
1752+
1753+ debug = debug or force
1754+
1755+ if debug:
1756+ print "DEBUG:", data
1757+
1758+ # return True if we printed some output
1759+ return debug
1760
1761=== renamed directory 'uath/examples' => 'uath/client/examples'
1762=== modified file 'uath/client/examples/master.run'
1763--- uath/examples/master.run 2012-04-10 20:42:15 +0000
1764+++ uath/client/examples/master.run 2012-05-09 00:33:18 +0000
1765@@ -1,3 +1,3 @@
1766 ---
1767 - name: uath_tests
1768- fetch_cmd: rm -fr uath_tests && cp -r /usr/share/uath/examples/uath_tests .
1769+ fetch_cmd: rm -fr uath_tests && cp -r /usr/share/uath/client/examples/uath_tests .
1770
1771=== modified file 'uath/client/examples/uath_tests/test_one/tc_control'
1772--- uath/examples/uath_tests/test_one/tc_control 2012-04-10 20:23:50 +0000
1773+++ uath/client/examples/uath_tests/test_one/tc_control 2012-05-09 00:33:18 +0000
1774@@ -7,7 +7,7 @@
1775 1. Expected result 1
1776 2. Expected result 2
1777 type: userland
1778-#timeout: 200
1779+timeout: 200
1780 build_cmd: echo "building for test_one"
1781 tc_setup: echo "setup for test_one"
1782 tc_cleanup: echo "cleanup for test_one"
1783
1784=== added file 'uath/client/exceptions.py'
1785--- uath/client/exceptions.py 1970-01-01 00:00:00 +0000
1786+++ uath/client/exceptions.py 2012-05-09 00:33:18 +0000
1787@@ -0,0 +1,7 @@
1788+#!/usr/bin/python
1789+
1790+class BadDir(Exception):
1791+ pass
1792+
1793+class MissingFile(Exception):
1794+ pass
1795
1796=== renamed file 'uath/result.py' => 'uath/client/result.py'
1797--- uath/result.py 2012-04-06 20:40:47 +0000
1798+++ uath/client/result.py 2012-05-09 00:33:18 +0000
1799@@ -1,87 +1,110 @@
1800+import json
1801 import sys
1802 import yaml
1803-import datetime
1804+
1805+from common import debug_print
1806
1807 RESULT_FIELD_COUNT = 4
1808
1809 class Result(object):
1810- """
1811- Result collection class.
1812- """
1813- def __init__(self, name=None):
1814- self.results = []
1815- self.status = 'SUCCESS'
1816- self.name = name
1817-
1818- def add_result(self, result):
1819- """
1820- Add a result to the object.
1821-
1822- Note: 'result' is expected to be a dictionary like this:
1823- {
1824- 'command': '',
1825- 'returncode': 0,
1826- 'stdout': '',
1827- 'stderr': '',
1828- }
1829-
1830- """
1831- if result is None:
1832- return
1833-
1834- result['date'] = str(datetime.datetime.now())
1835- self.results.append(result)
1836-
1837- if result['returncode'] == 1 and self.status != 'ERROR':
1838- self.status = 'FAILURE'
1839-
1840- if result['returncode'] not in [0, 1]:
1841- self.status = 'ERROR'
1842-
1843- def result(self, verbose=False):
1844- """
1845- Output a text based result.
1846- """
1847- sep = '-'*70
1848-
1849- print sep
1850-
1851- if self.name:
1852- print self.name
1853- print sep
1854-
1855- for result in self.results:
1856- print "command: %s" % result['command']
1857- print "returned: %d" % result['returncode']
1858-
1859- if self.status != 'SUCCESS' or verbose and result['stdout'] != '':
1860- print "stdout: "
1861- print result['stdout']
1862-
1863- if self.status != 'SUCCESS' or verbose and result['stderr'] != '':
1864- print "stderr: "
1865- print result['stderr']
1866-
1867- print
1868- self.clear_results()
1869-
1870- def clear_results(self):
1871- # reset the list of results so this object can be reused.
1872- self.results = []
1873-
1874- # reset the status, this should be safe since the only places that
1875- # should be calling clear_results() is testcase.run(), testsuite.run(),
1876- # and runner after attempting to fetch the testsuite.
1877- self.status = 'SUCCESS'
1878-
1879- def count_results(self):
1880- return len(self.results)
1881+ """
1882+ Result collection class.
1883+ """
1884+ def __init__(self, name=None):
1885+ self.results = []
1886+ self.status = 'PASS'
1887+ self.name = name
1888+
1889+ def add_result(self, result):
1890+ """
1891+ Add a result to the object.
1892+
1893+ Note: 'result' is expected to be a dictionary like this:
1894+ {
1895+ 'command': '',
1896+ 'returncode': 0,
1897+ 'stdout': '',
1898+ 'stderr': '',
1899+ 'start_time': '',
1900+ 'time_delta': '',
1901+ }
1902+
1903+ """
1904+ if result is None:
1905+ return
1906+
1907+ self.results.append(result)
1908+
1909+ if result['returncode'] == 1 and self.status != 'ERROR':
1910+ self.status = 'FAIL'
1911+
1912+ if result['returncode'] not in [0, 1]:
1913+ self.status = 'ERROR'
1914+
1915+ def result(self, verbose=False):
1916+ """
1917+ Output a text based result.
1918+ """
1919+ status = self.status
1920+ sep = '-'*70
1921+
1922+ print sep
1923+
1924+ if self.name:
1925+ print self.name
1926+ print sep
1927+
1928+ for result in self.results:
1929+ print "command: %s" % result['command']
1930+ print "returned: %d" % result['returncode']
1931+ print "started: %s" % result['start_time']
1932+ print "runtime: %s" % result['time_delta']
1933+
1934+ if self.status != 'PASS' or verbose and result['stdout'] != '':
1935+ print "stdout: "
1936+ print result['stdout']
1937+
1938+ if self.status != 'PASS' or verbose and result['stderr'] != '':
1939+ print "stderr: "
1940+ print result['stderr']
1941+
1942+ print
1943+ self.clear_results()
1944+
1945+ return status
1946+
1947+ def clear_results(self):
1948+ # reset the list of results so this object can be reused.
1949+ self.results = []
1950+
1951+ # reset the status, this should be safe since the only places that
1952+ # should be calling clear_results() is testcase.run(), testsuite.run(),
1953+ # and runner after attempting to fetch the testsuite.
1954+ self.status = 'PASS'
1955+
1956+ def count_results(self):
1957+ return len(self.results)
1958
1959 class ResultYAML(Result):
1960- def result(self, verbose=False):
1961-
1962- if self.results:
1963- print "---"
1964- yaml.dump(self.results, sys.stdout, default_flow_style=False)
1965-
1966- self.clear_results()
1967+ def result(self, verbose=False):
1968+
1969+ if self.results:
1970+ print "---"
1971+ yaml.dump(self.results, sys.stdout, default_flow_style=False)
1972+
1973+ status = self.status
1974+ self.clear_results()
1975+
1976+ return status
1977+
1978+class ResultJSON(Result):
1979+ def result(self, verbose=False):
1980+
1981+ if self.results:
1982+ json.dump(self.results, sys.stdout, indent=4)
1983+
1984+ status = self.status
1985+ self.clear_results()
1986+
1987+ return status
1988+
1989
1990=== renamed file 'uath/runner.py' => 'uath/client/runner.py'
1991--- uath/runner.py 2012-04-10 15:46:12 +0000
1992+++ uath/client/runner.py 2012-05-09 00:33:18 +0000
1993@@ -10,280 +10,318 @@
1994 import yaml
1995
1996 from common import MASTER_RUNLIST, UATH_DIR, DEFAULT_STATE_FILE
1997+from common import RETURN_CODES
1998
1999 RC_LOCAL='/etc/rc.local'
2000 RC_LOCAL_BACKUP='%s-uath.bak' % RC_LOCAL
2001
2002 rc_local_content="""#!/bin/sh
2003
2004-cd /home/josepht/bazaar/uath-dev || exit 1
2005-python main.py --resume --append -o /var/uath/uath.out
2006+/usr/bin/uath --resume --append -o /var/lib/uath/uath.out
2007 """
2008
2009 class Runner(object):
2010- """
2011- The main runner.
2012-
2013- Parses a master runlist, builds a list of TestSuites, and runs them.
2014- """
2015-
2016- status = "NOTRUN"
2017- uath_exec_path = '/usr/bin/uath'
2018-
2019- def __init__(self, runlist=MASTER_RUNLIST, result_class=Result,
2020- testdir=UATH_DIR, state_agent=None, resume=False):
2021-
2022- self.master_runlist = runlist
2023- self.testdir = testdir
2024- self.suites = []
2025- self.result = result_class()
2026- self.result_class = result_class
2027- self.state_file = DEFAULT_STATE_FILE
2028- self.fetched_suites = {}
2029-
2030- self.state_agent = state_agent or StateAgentYAML()
2031-
2032- # Cleanup the state file if this is supposed to be a fresh run.
2033- if not resume:
2034- self.state_agent.clean()
2035- else:
2036- self.reset_rc_local()
2037-
2038- try:
2039- os.chdir(self.testdir)
2040- except OSError as e:
2041- raise exceptions.BadDir(str(e))
2042-
2043- # needs to be run prior to process_master_runlist since that's where
2044- # test suites are fetched.
2045- self.fetched_suites = self.get_fetched_suites()
2046-
2047- self.process_master_runlist()
2048-
2049- def backup_rc_local(self):
2050- """
2051- Backup /etc/rc.local if it exists.
2052- """
2053-
2054- cmd = None
2055-
2056- if os.path.exists(RC_LOCAL):
2057- cmd = ['mv', RC_LOCAL, RC_LOCAL_BACKUP]
2058-
2059- if cmd:
2060- subprocess.call(cmd)
2061-
2062- def reset_rc_local(self):
2063- """
2064- Restore original /etc/rc.local if there is a backup otherwise remove it.
2065- """
2066- cmd = None
2067-
2068- if os.path.exists(RC_LOCAL_BACKUP):
2069- cmd = ['mv', RC_LOCAL_BACKUP, RC_LOCAL]
2070- elif os.path.exists(RC_LOCAL):
2071- cmd = ['rm', RC_LOCAL]
2072-
2073- if cmd:
2074- subprocess.call(cmd)
2075-
2076- def run(self):
2077- """
2078- Run the test suites we've parsed.
2079- """
2080-
2081- # Return value to indicate whether processing of a Runner should
2082- # continue. This is to avoid a shutdown race on reboot cases.
2083- keep_going = True
2084-
2085- self.status = "RUN"
2086- for suite in self.suites:
2087- # Start in self.testdir.
2088- os.chdir(self.testdir)
2089-
2090- if not suite.is_done():
2091- keep_going = suite.run()
2092- if not keep_going:
2093- return keep_going
2094-
2095- self.status = "DONE"
2096- self.save_state()
2097-
2098- return keep_going
2099-
2100- def add_suite(self, suite):
2101- self.suites.append(suite)
2102-
2103- def get_fetched_suites(self):
2104- """
2105- Return a list of fetched suites from the state_agent.
2106- """
2107- state = self.state_agent.load_state()
2108-
2109- fetched_suites = []
2110+ """
2111+ The main runner.
2112+
2113+ Parses a master runlist, builds a list of TestSuites, and runs them.
2114+ """
2115+
2116+ status = "NOTRUN"
2117+ uath_exec_path = '/usr/bin/uath'
2118+
2119+ def __init__(self, runlist=MASTER_RUNLIST, result_class=Result,
2120+ testdir=UATH_DIR, state_agent=None, resume=False):
2121+
2122+ self.master_runlist = runlist
2123+ self.testdir = testdir
2124+ self.suites = []
2125+ self.result = result_class()
2126+ self.result_class = result_class
2127+ self.state_file = DEFAULT_STATE_FILE
2128+ self.fetched_suites = {}
2129+
2130+ self.errors = 0
2131+ self.passes = 0
2132+ self.failures = 0
2133+ self.fetch_errors = 0
2134+
2135+ self.state_agent = state_agent or StateAgentYAML()
2136+
2137+ # Cleanup the state file if this is supposed to be a fresh run.
2138+ if not resume:
2139+ self.state_agent.clean()
2140+ else:
2141+ self.reset_rc_local()
2142+
2143+ try:
2144+ os.chdir(self.testdir)
2145+ except OSError as e:
2146+ raise exceptions.BadDir(str(e))
2147+
2148+ # needs to be run prior to process_master_runlist since that's where
2149+ # test suites are fetched.
2150+ self.fetched_suites = self.get_fetched_suites()
2151+
2152+ self.process_master_runlist()
2153+
2154+ def backup_rc_local(self):
2155+ """
2156+ Backup /etc/rc.local if it exists.
2157+ """
2158+
2159+ # Ignore permission denied errors since we only care if a reboot is
2160+ # pending whether or not we can write to RC_LOCAL(_BACKUP).
2161+ try:
2162+ os.rename(RC_LOCAL, RC_LOCAL_BACKUP)
2163+ except OSError as e:
2164+ if e.errno in [13]:
2165+ pass
2166+
2167+ def reset_rc_local(self):
2168+ """
2169+ Restore original /etc/rc.local if there is a backup otherwise remove it.
2170+ """
2171+
2172+ # Ignore permission denied errors since we only care if a reboot is
2173+ # pending whether or not we can write to RC_LOCAL(_BACKUP).
2174+ try:
2175+ if os.path.exists(RC_LOCAL_BACKUP):
2176+ os.rename(RC_LOCAL_BACKUP, RC_LOCAL)
2177+ elif os.path.exists(RC_LOCAL):
2178+ os.remove(RC_LOCAL)
2179+ except OSError as e:
2180+ if e.errno in [13]:
2181+ pass
2182+
2183+ def setup_rc_local(self, rc_local=RC_LOCAL):
2184+ """
2185+ Setup /etc/rc.local to kick-off a --resume on successful boot.
2186+ """
2187+
2188+ self.rc_local_content = rc_local_content
2189+
2190+ fp = open(rc_local, 'w')
2191+ fp.write(self.rc_local_content)
2192+ fp.close()
2193+
2194+ os.chmod(rc_local, stat.S_IRWXU | stat.S_IROTH | stat.S_IRGRP)
2195+
2196+ def run(self):
2197+ """
2198+ Run the test suites we've parsed.
2199+ """
2200+
2201+ # Return value to indicate whether processing of a Runner should
2202+ # continue. This is to avoid a shutdown race on reboot cases.
2203+ keep_going = True
2204+
2205+ self.status = "RUN"
2206+ for suite in self.suites:
2207+ # Start in self.testdir.
2208+ os.chdir(self.testdir)
2209+
2210+ if not suite.is_done():
2211+ keep_going = suite.run()
2212+ if not keep_going:
2213+ return self.returncode()
2214+
2215+ self.errors += suite.errors
2216+ self.passes += suite.passes
2217+ self.failures += suite.failures
2218+
2219+ self.status = "DONE"
2220+ self.save_state()
2221+
2222+ return self.returncode()
2223+
2224+ def add_suite(self, suite):
2225+ self.suites.append(suite)
2226+
2227+ def get_fetched_suites(self):
2228+ """
2229+ Return a list of fetched suites from the state_agent.
2230+ """
2231+ state = self.state_agent.load_state()
2232+
2233+ fetched_suites = []
2234
2235- if state and 'fetched_suites' in state:
2236- fetched_suites = state['fetched_suites']
2237-
2238- return fetched_suites
2239-
2240- def load_state(self):
2241- state = self.state_agent.load_state()
2242-
2243- self.master_runlist = state['master_runlist']
2244- self.status = state['status']
2245- self.result_class = state['result_class']
2246-
2247- self.suites = []
2248- for state_suite in state['suites']:
2249- suite = TestSuite(name=state_suite['name'],
2250- path=self.testdir,
2251- result_class=self.result_class,
2252- _save_state_callback=self.save_state,
2253- _reboot_callback=self.reboot)
2254- suite.load_state(state_suite)
2255- self.suites.append(suite)
2256-
2257- self.fetched_suites = state['fetched_suites']
2258-
2259- def save_state(self):
2260- """
2261- Save the list of tests we are to run and whether we've run them.
2262- """
2263-
2264- state = {
2265- 'master_runlist': self.master_runlist,
2266- 'status': self.status,
2267- 'result_class': self.result_class,
2268- 'suites': [],
2269- 'fetched_suites': self.fetched_suites,
2270- }
2271-
2272- for suite in self.suites:
2273- state['suites'].append(suite.save_state())
2274-
2275- # hand the state dictionary off to the StateAgent to process
2276- self.state_agent.save_state(state)
2277-
2278- return state
2279-
2280- def count_suites(self):
2281- return len(self.suites)
2282-
2283- def count_tests(self):
2284- tests = 0
2285-
2286- for suite in self.suites:
2287- tests += suite.count_tests()
2288-
2289- return tests
2290-
2291- def process_master_runlist(self):
2292- """
2293- Parse a master runlist building a list of suites from the parsed data.
2294- """
2295-
2296- try:
2297- fp = open(self.master_runlist, 'r')
2298- data = yaml.load(fp)
2299- fp.close()
2300- except IOError as e:
2301- raise exceptions.MissingFile(str(e))
2302-
2303- seen = []
2304-
2305- for suite in data:
2306- fetch_cmd = suite['fetch_cmd']
2307-
2308- name = suite['name']
2309- includes = suite.get('include_tests')
2310- excludes = suite.get('exclude_tests')
2311-
2312- suite_runlist = suite.get('runlist', DEFAULT_TSLIST)
2313-
2314- if name in seen:
2315- raise Exception("%s duplicated in runlist" % name)
2316-
2317- # Fetch the testsuite.
2318-
2319- if not os.path.exists(name):
2320- os.mkdir(name)
2321-
2322- if name not in self.fetched_suites:
2323- self.result.add_result(run_cmd(fetch_cmd, cwd=name))
2324- self.fetched_suites.append(name)
2325-
2326- # If fetch_cmd failed move on to the next testsuite.
2327- if self.result.status != 'SUCCESS':
2328- self.result.result()
2329- continue
2330-
2331- # Create a TestSuite
2332- # TODO: pass master.run overrides into the test suite.
2333- s = TestSuite(name=name, runlist_file=suite_runlist,
2334- includes=includes, excludes=excludes, result=self.result,
2335- path=self.testdir, _save_state_callback=self.save_state,
2336- _reboot_callback=self.reboot)
2337- self.add_suite(s)
2338-
2339- self.result.result()
2340-
2341- def get_next_suite(self):
2342- """
2343- Return the next suite to be run.
2344-
2345- Mainly used for debugging.
2346- """
2347-
2348- suite = None
2349-
2350- for s in self.suites:
2351- if not s.is_done():
2352- suite = s
2353- break
2354-
2355- return suite
2356-
2357- def get_next_test(self):
2358- """
2359- Return the next test to be run.
2360-
2361- Mainly used for debugging.
2362- """
2363- return self.get_next_suite().get_next_test()
2364-
2365- def setup_rc_local(self, rc_local='/etc/rc.local'):
2366- """
2367- Setup /etc/rc.local to kick-off a --resume on successful boot.
2368- """
2369- # temporary override while developing/testing
2370- self.uath_exec_path = '/home/josepht/bazaar/uath-dev'
2371- self.rc_local_content = """#!/bin/sh\n\ncd %s || exit 1\npython main.py --resume >>/tmp/uath.out 2>&1 \n""" % self.uath_exec_path
2372- self.rc_local_content = """#!/bin/sh\n\necho "I'd be resuming here... >>/var/uath/uath.out 2>&1\n"""
2373-
2374- self.rc_local_content = rc_local_content
2375-
2376- fp = open(rc_local, 'w')
2377- fp.write(self.rc_local_content)
2378- fp.close()
2379-
2380- os.chmod(rc_local, stat.S_IRWXU | stat.S_IROTH | stat.S_IRGRP)
2381-
2382- def reboot(self):
2383- """
2384- Reboot the machine.
2385-
2386- Save state, setup /etc/rc.local, and shutdown.
2387- """
2388-
2389- self.result.result()
2390-
2391- self.save_state()
2392-
2393- self.backup_rc_local()
2394- self.setup_rc_local()
2395-
2396- import subprocess
2397- subprocess.call(['shutdown', '-r', 'now'])
2398-
2399- # End of execution
2400+ if state and 'fetched_suites' in state:
2401+ fetched_suites = state['fetched_suites']
2402+
2403+ return fetched_suites
2404+
2405+ def load_state(self):
2406+ state = self.state_agent.load_state()
2407+
2408+ self.master_runlist = state['master_runlist']
2409+ self.status = state['status']
2410+ self.result_class = state['result_class']
2411+
2412+ self.suites = []
2413+ for state_suite in state['suites']:
2414+ suite = TestSuite(name=state_suite['name'],
2415+ path=self.testdir,
2416+ result_class=self.result_class,
2417+ _save_state_callback=self.save_state,
2418+ _reboot_callback=self.reboot)
2419+ suite.load_state(state_suite)
2420+ self.suites.append(suite)
2421+
2422+ self.fetched_suites = state['fetched_suites']
2423+
2424+ def save_state(self):
2425+ """
2426+ Save the list of tests we are to run and whether we've run them.
2427+ """
2428+
2429+ state = {
2430+ 'master_runlist': self.master_runlist,
2431+ 'status': self.status,
2432+ 'passes': self.passes,
2433+ 'failures': self.failures,
2434+ 'errors': self.errors,
2435+ 'fetch_errors': self.fetch_errors,
2436+ 'result_class': self.result_class,
2437+ 'suites': [],
2438+ 'fetched_suites': self.fetched_suites,
2439+ }
2440+
2441+ for suite in self.suites:
2442+ state['suites'].append(suite.save_state())
2443+
2444+ # hand the state dictionary off to the StateAgent to process
2445+ self.state_agent.save_state(state)
2446+
2447+ return state
2448+
2449+ def count_suites(self):
2450+ return len(self.suites)
2451+
2452+ def count_tests(self):
2453+ tests = 0
2454+
2455+ for suite in self.suites:
2456+ tests += suite.count_tests()
2457+
2458+ return tests
2459+
2460+ def process_master_runlist(self):
2461+ """
2462+ Parse a master runlist building a list of suites from the parsed data.
2463+ """
2464+
2465+ try:
2466+ fp = open(self.master_runlist, 'r')
2467+ data = yaml.load(fp)
2468+ fp.close()
2469+ except IOError as e:
2470+ raise exceptions.MissingFile(str(e))
2471+
2472+ seen = []
2473+
2474+ for suite in data:
2475+ fetch_cmd = suite['fetch_cmd']
2476+
2477+ name = suite['name']
2478+ includes = suite.get('include_tests')
2479+ excludes = suite.get('exclude_tests')
2480+
2481+ suite_runlist = suite.get('runlist', DEFAULT_TSLIST)
2482+
2483+ if name in seen:
2484+ raise Exception("%s duplicated in runlist" % name)
2485+
2486+ # Fetch the testsuite.
2487+
2488+ if not os.path.exists(name):
2489+ os.mkdir(name)
2490+
2491+ if name not in self.fetched_suites:
2492+ self.result.add_result(run_cmd(fetch_cmd, cwd=name))
2493+ self.fetched_suites.append(name)
2494+
2495+ # If fetch_cmd failed move on to the next testsuite.
2496+ if self.result.status != 'PASS':
2497+ self.result.result()
2498+ self.fetch_errors += 1
2499+ continue
2500+
2501+ # Create a TestSuite
2502+ # TODO: pass master.run overrides into the test suite.
2503+ s = TestSuite(name=name, runlist_file=suite_runlist,
2504+ includes=includes, excludes=excludes, result=self.result,
2505+ path=self.testdir, _save_state_callback=self.save_state,
2506+ _reboot_callback=self.reboot)
2507+ self.add_suite(s)
2508+
2509+ self.result.result()
2510+
2511+ def get_next_suite(self):
2512+ """
2513+ Return the next suite to be run.
2514+
2515+ Mainly used for debugging.
2516+ """
2517+
2518+ suite = None
2519+
2520+ for s in self.suites:
2521+ if not s.is_done():
2522+ suite = s
2523+ break
2524+
2525+ return suite
2526+
2527+ def get_next_test(self):
2528+ """
2529+ Return the next test to be run.
2530+
2531+ Mainly used for debugging.
2532+ """
2533+ return self.get_next_suite().get_next_test()
2534+
2535+ def reboot(self):
2536+ """
2537+ Reboot the machine.
2538+
2539+ Save state, setup /etc/rc.local, and shutdown.
2540+ """
2541+
2542+ self.result.result()
2543+
2544+ self.save_state()
2545+
2546+ self.backup_rc_local()
2547+ self.setup_rc_local()
2548+
2549+ import subprocess
2550+ subprocess.call(['shutdown', '-r', 'now'])
2551+
2552+ # End of execution
2553+
2554+ def returncode(self):
2555+# if self.errors > 0 or self.fetch_errors:
2556+# In the code review, we determined that errors at the suite level should produce a non-zero exit status for the overall process
2557+ if self.fetch_errors > 0:
2558+ return RETURN_CODES['ERROR']
2559+ elif self.failures > 0:
2560+ return RETURN_CODES['FAIL']
2561+ else:
2562+ return RETURN_CODES['PASS']
2563+
2564+ def report(self):
2565+ tests_run = self.passes + self.errors + self.failures
2566+
2567+ output = "total: %d, passes: %d, failure: %d, error: %d" % (tests_run, self.passes, self.failures, self.errors + self.fetch_errors)
2568+
2569+# if self.errors > 0 or self.fetch_errors:
2570+# In the code review, we determined that errors at the suite level should produce a non-zero exit status for the overall process
2571+ if self.fetch_errors > 0:
2572+ result = "ERROR"
2573+ elif self.failures > 0:
2574+ result = "FAIL"
2575+ else:
2576+ result = "PASS"
2577+
2578+ return "%s - %s" % (result, output)
2579
2580=== renamed file 'uath/self_test.py' => 'uath/client/self_test.py'
2581--- uath/self_test.py 2012-04-10 15:46:12 +0000
2582+++ uath/client/self_test.py 2012-05-09 00:33:18 +0000
2583@@ -4,6 +4,7 @@
2584 from state_agent import StateAgent, StateAgentYAML
2585
2586 from common import UATH_DIR, MASTER_RUNLIST
2587+from common import RETURN_CODES
2588
2589 import exceptions
2590 import os
2591@@ -17,385 +18,444 @@
2592 master_runlist_content="""# uath/self_test.py master runlist
2593 # needed for uath/self_test.py runs
2594 - name: examples
2595- fetch_cmd: rsync -a /home/josepht/bazaar/uath-dev/uath/examples/ .
2596+ fetch_cmd: rsync -a /usr/share/uath/client/examples/examples .
2597 - name: uath_tests
2598- fetch_cmd: git clone /home/josepht/git/uath_tests.git || sh -c 'cd uath_tests && git pull'
2599+ fetch_cmd: rsync -a /usr/share/uath/client/examples/uath_tests .
2600 - name: uath_tests_sample
2601- fetch_cmd: git clone /home/josepht/git/uath_tests_sample.git || sh -c 'cd uath_tests_sample && git pull'
2602+ fetch_cmd: rsync -a /usr/share/uath/client/examples/uath_tests_sample .
2603 """
2604
2605 partial_state_file_content="""master_runlist: master.run
2606 status: RUN
2607-result_class: !!python/name:uath.result.ResultYAML ''
2608+result_class: !!python/name:uath.client.result.ResultYAML ''
2609 fetched_suites:
2610 - uath_tests
2611 - uath_tests_sample
2612 suites:
2613 - build_cmd: null
2614- name: uath_tests
2615- status: INPROGRESS
2616- tests:
2617- - build_cmd: echo "building for test_one"
2618- command: python test_one.py
2619- description: A first sample test case.
2620- name: test_one
2621- path: uath_tests/test_one
2622- status: DONE
2623- tc_cleanup: echo "cleanup for test_one"
2624- tc_setup: echo "setup for test_one"
2625- timeout: 3
2626- type: userland
2627- - build_cmd: echo "building for test_two"
2628- command: python test_two.py
2629- description: A second sample test case.
2630- name: test_two
2631- path: uath_tests/test_two
2632- status: SETUP
2633- tc_cleanup: echo "cleanup for test_two"
2634- tc_setup: echo "setup for test_two"
2635- timeout: 300
2636- type: userland
2637- timeout: 0
2638- ts_cleanup: null
2639- ts_setup: null
2640+ name: uath_tests
2641+ status: INPROGRESS
2642+ tests:
2643+ - build_cmd: echo "building for test_one"
2644+ command: python test_one.py
2645+ description: A first sample test case.
2646+ name: test_one
2647+ path: uath_tests/test_one
2648+ status: DONE
2649+ tc_cleanup: echo "cleanup for test_one"
2650+ tc_setup: echo "setup for test_one"
2651+ timeout: 3
2652+ type: userland
2653+ - build_cmd: echo "building for test_two"
2654+ command: python test_two.py
2655+ description: A second sample test case.
2656+ name: test_two
2657+ path: uath_tests/test_two
2658+ status: SETUP
2659+ tc_cleanup: echo "cleanup for test_two"
2660+ tc_setup: echo "setup for test_two"
2661+ timeout: 300
2662+ type: userland
2663+ timeout: 0
2664+ ts_cleanup: null
2665+ ts_setup: null
2666 - build_cmd: null
2667- name: uath_tests_sample
2668- status: DONE
2669- tests:
2670- - build_cmd: echo "building for sample_one"
2671- command: python sample.py
2672- description: A sample test case.
2673- name: sample_one
2674- path: uath_tests_sample/sample_one
2675+ name: uath_tests_sample
2676 status: DONE
2677- tc_cleanup: echo "cleanup for sample_one"
2678- tc_setup: echo "setup for sample_one"
2679- timeout: 200
2680- type: userland
2681- timeout: 0
2682- ts_cleanup: null
2683- ts_setup: null
2684+ tests:
2685+ - build_cmd: echo "building for sample_one"
2686+ command: python sample.py
2687+ description: A sample test case.
2688+ name: sample_one
2689+ path: uath_tests_sample/sample_one
2690+ status: DONE
2691+ tc_cleanup: echo "cleanup for sample_one"
2692+ tc_setup: echo "setup for sample_one"
2693+ timeout: 200
2694+ type: userland
2695+ timeout: 0
2696+ ts_cleanup: null
2697+ ts_setup: null
2698 """
2699 master_runlist = os.path.join(UATH_DIR, MASTER_RUNLIST)
2700 master_runlist_bak = master_runlist + ".bak"
2701
2702 def setUp():
2703- """
2704- Set up a master.run file that will copy our 'examples' directory to the
2705- testing directory (usually /var/uath).
2706- """
2707-
2708- # save a copy of the master runlist
2709- subprocess.call(['mv', master_runlist, master_runlist_bak])
2710-
2711- fp = open(master_runlist, 'w')
2712- fp.write(master_runlist_content)
2713- fp.close()
2714+ """
2715+ Set up a master.run file that will copy our 'examples' directory to the
2716+ testing directory (usually /var/lib/uath).
2717+ """
2718+
2719+ # save a copy of the master runlist
2720+ if os.path.exists(master_runlist):
2721+ subprocess.call(['mv', master_runlist, master_runlist_bak])
2722+
2723+ fp = open(master_runlist, 'w')
2724+ fp.write(master_runlist_content)
2725+ fp.close()
2726
2727 def tearDown():
2728- """
2729- Cleanup after ourselves.
2730- """
2731+ """
2732+ Cleanup after ourselves.
2733+ """
2734
2735- # restore the copy of the master runlist
2736- subprocess.call(['mv', master_runlist_bak, master_runlist])
2737+ # restore the copy of the master runlist
2738+ if os.path.exists(master_runlist_bak):
2739+ subprocess.call(['mv', master_runlist_bak, master_runlist])
2740+ else:
2741+ os.remove(master_runlist)
2742
2743 class TestTestSuite(unittest.TestCase):
2744- """
2745- Tests for the TestSuite class.
2746- """
2747- def setUp(self):
2748- """
2749- Use the 'examples' test suite that's part of the UATH package.
2750- """
2751- self.suite = testsuite.TestSuite(name='examples', path='/var/uath', result_class=ResultYAML,
2752- _save_state_callback=state_saver)
2753-
2754- def test_run(self):
2755- print "found %d tests" % self.suite.count_tests()
2756- self.suite.run()
2757-
2758- def test_count_tests(self):
2759- self.assertEquals(self.suite.count_tests(), 2)
2760-
2761- def test_next_test(self):
2762- self.assertEquals(self.suite.get_next_test().name, 'test_one')
2763-
2764- def test_is_done(self):
2765- self.assertFalse(self.suite.is_done())
2766-
2767- def test_save_state(self):
2768- state = self.suite.save_state()
2769-
2770- print state
2771- self.assertEqual(state['status'], 'NOTRUN')
2772-
2773- tests = 0
2774- for test in state['tests']:
2775- self.assertEqual(test['status'], 'NOTRUN')
2776- tests += 1
2777-
2778- self.assertEqual(tests, self.suite.count_tests())
2779+ """
2780+ Tests for the TestSuite class.
2781+ """
2782+ def setUp(self):
2783+ """
2784+ Use the 'examples' test suite that's part of the UATH package.
2785+ """
2786+ self.suite = testsuite.TestSuite(name='examples', path='/var/lib/uath', result_class=ResultYAML,
2787+ _save_state_callback=state_saver)
2788+
2789+ def test_run(self):
2790+ print "found %d tests" % self.suite.count_tests()
2791+ self.suite.run()
2792+ self.assertEqual(self.suite.result.status, 'PASS')
2793+
2794+ def test_count_tests(self):
2795+ self.assertEquals(self.suite.count_tests(), 2)
2796+
2797+ def test_next_test(self):
2798+ self.assertEquals(self.suite.get_next_test().name, 'test_one')
2799+
2800+ def test_is_done(self):
2801+ self.assertFalse(self.suite.is_done())
2802+
2803+ def test_save_state(self):
2804+ state = self.suite.save_state()
2805+
2806+ print state
2807+ self.assertEqual(state['status'], 'NOTRUN')
2808+
2809+ tests = 0
2810+ for test in state['tests']:
2811+ self.assertEqual(test['status'], 'NOTRUN')
2812+ tests += 1
2813+
2814+ self.assertEqual(tests, self.suite.count_tests())
2815
2816 class TestTestCase(unittest.TestCase):
2817- """
2818- Tests for the TestCase class.
2819- """
2820- def setUp(self):
2821- self.timeout = 10
2822- self.command = 'echo "command"'
2823- self.result = ResultYAML()
2824- self.name = "test_one"
2825- self.path = "examples/test_one"
2826- self.suite_dir = os.path.join(UATH_DIR, 'examples')
2827+ """
2828+ Tests for the TestCase class.
2829+ """
2830+ def setUp(self):
2831+ self.timeout = 10
2832+ self.command = 'echo "command"'
2833+ self.result = ResultYAML()
2834+ self.name = "test_one"
2835+ self.path = "examples/test_one"
2836+ self.suite_dir = os.path.join(UATH_DIR, 'examples')
2837
2838- # Change to the testsuite's working directory
2839- os.chdir(self.suite_dir)
2840-
2841- self.case = testcase.TestCase(
2842- name=self.name,
2843- path=self.path,
2844- command=self.command,
2845- timeout=self.timeout,
2846- result=self.result,
2847- )
2848-
2849- def test_run(self):
2850- """
2851- Test that a TestCase can be run.
2852- """
2853- self.case.run()
2854-
2855- # make sure after a testcase run that the cwd hasn't changed.
2856- self.assertEqual(os.getcwd(), self.suite_dir)
2857- self.assertTrue(self.case.is_done())
2858-
2859- def test_timeout(self):
2860- self.assertEqual(self.case.timeout, self.timeout)
2861-
2862- def test_command(self):
2863- self.assertEqual(self.case.command, self.command)
2864-
2865- def test_save_state(self):
2866- state = self.case.save_state()
2867-
2868- self.assertEqual(state['status'], 'NOTRUN')
2869- self.assertEqual(state['command'], self.command)
2870- self.assertEqual(state['timeout'], self.timeout)
2871-
2872- def test_save_and_load_state(self):
2873- state = self.case.save_state()
2874-
2875- case = testcase.TestCase(name=self.name, path=self.path)
2876- case.load_state(state)
2877-
2878- self.assertFalse(case.is_done())
2879-
2880- self.assertEqual(case.name, self.name)
2881- self.assertEqual(case.path, self.path)
2882- self.assertEqual(case.command, self.command)
2883- self.assertEqual(case.timeout, self.timeout)
2884-
2885- case.run()
2886-
2887- self.assertTrue(case.is_done())
2888+ # Change to the testsuite's working directory
2889+ os.chdir(self.suite_dir)
2890+
2891+ self.case = testcase.TestCase(
2892+ name=self.name,
2893+ path=self.path,
2894+ command=self.command,
2895+ timeout=self.timeout,
2896+ result=self.result,
2897+ )
2898+
2899+ def test_run(self):
2900+ """
2901+ Test that a TestCase can be run.
2902+ """
2903+ self.case.run()
2904+
2905+ # make sure after a testcase run that the cwd hasn't changed.
2906+ self.assertEqual(os.getcwd(), self.suite_dir)
2907+ self.assertTrue(self.case.is_done())
2908+
2909+ def test_timeout(self):
2910+ self.assertEqual(self.case.timeout, self.timeout)
2911+
2912+ def test_command(self):
2913+ self.assertEqual(self.case.command, self.command)
2914+
2915+ def test_save_state(self):
2916+ state = self.case.save_state()
2917+
2918+ self.assertEqual(state['status'], 'NOTRUN')
2919+ self.assertEqual(state['command'], self.command)
2920+ self.assertEqual(state['timeout'], self.timeout)
2921+
2922+ def test_save_and_load_state(self):
2923+ state = self.case.save_state()
2924+
2925+ case = testcase.TestCase(name=self.name, path=self.path)
2926+ case.load_state(state)
2927+
2928+ self.assertFalse(case.is_done())
2929+
2930+ self.assertEqual(case.name, self.name)
2931+ self.assertEqual(case.path, self.path)
2932+ self.assertEqual(case.command, self.command)
2933+ self.assertEqual(case.timeout, self.timeout)
2934+
2935+ case.run()
2936+
2937+ self.assertTrue(case.is_done())
2938
2939 class TestResult(unittest.TestCase):
2940- def setUp(self):
2941- self.result_yaml = ResultYAML("result_yaml")
2942- self.result_text = Result("result")
2943-
2944- self.result = {
2945- 'command': 'echo hi',
2946- 'returncode': 0,
2947- 'stdout': 'hi',
2948- 'stderr': ''
2949- }
2950-
2951- self.result_fail = {
2952- 'command': 'echo failure',
2953- 'returncode': 2,
2954- 'stdout': 'hi',
2955- 'stderr': ''
2956- }
2957-
2958- def test_add_result(self):
2959- self.result_yaml.add_result(self.result)
2960-
2961- self.assertEqual(self.result_yaml.count_results(), 1)
2962-
2963- self.result_yaml.result()
2964-
2965- def test_result(self):
2966- self.result_yaml.add_result(self.result)
2967-
2968- # check that the result was added.
2969- self.assertEqual(self.result_yaml.count_results(), 1)
2970-
2971- self.result_yaml.result()
2972-
2973- # check that the result was processed and removed.
2974- self.assertEqual(self.result_yaml.count_results(), 0)
2975-
2976- def test_result_success(self):
2977- """
2978- Test that adding a successful result sets the Result object's status
2979- to 'SUCCESS'.
2980- """
2981- self.result_yaml.add_result(self.result)
2982-
2983- self.assertEqual(self.result_yaml.status, 'SUCCESS')
2984-
2985- def test_result_failure(self):
2986- """
2987- Test that adding a failure result sets the Result object's status
2988- to 'ERROR'.
2989- """
2990- self.result_yaml.add_result(self.result_fail)
2991-
2992- self.assertEqual(self.result_yaml.status, 'ERROR')
2993-
2994- def test_result_yaml(self):
2995- self.result_yaml.add_result(self.result)
2996-
2997- import sys
2998- import StringIO
2999-
3000- str_output = StringIO.StringIO()
3001-
3002- # redirect output to a string
3003- old_stdout = sys.stdout
3004- sys.stdout = str_output
3005-
3006- self.result_yaml.result()
3007-
3008- sys.stdout = old_stdout
3009-
3010- result_str = str_output.getvalue()
3011-
3012- print("result_str: %s" % result_str)
3013-
3014-
3015- data = yaml.load(result_str)
3016-
3017- self.assertEqual(len(data), 1)
3018-
3019- result = data[0]
3020-
3021- print("data: %s" % data)
3022-
3023- self.assertEqual(result['command'], self.result['command'])
3024- self.assertEqual(result['returncode'], self.result['returncode'])
3025- self.assertEqual(result['stdout'], self.result['stdout'])
3026- self.assertEqual(result['stderr'], self.result['stderr'])
3027+ def setUp(self):
3028+ self.result_yaml = ResultYAML("result_yaml")
3029+ self.result_text = Result("result")
3030+
3031+ self.result = {
3032+ 'command': 'echo hi',
3033+ 'returncode': 0,
3034+ 'stdout': 'hi',
3035+ 'stderr': ''
3036+ }
3037+
3038+ self.result_fail = {
3039+ 'command': 'echo failure',
3040+ 'returncode': 2,
3041+ 'stdout': 'hi',
3042+ 'stderr': ''
3043+ }
3044+
3045+ def test_add_result(self):
3046+ self.result_yaml.add_result(self.result)
3047+
3048+ self.assertEqual(self.result_yaml.count_results(), 1)
3049+
3050+ self.result_yaml.result()
3051+
3052+ def test_result(self):
3053+ self.result_yaml.add_result(self.result)
3054+
3055+ # check that the result was added.
3056+ self.assertEqual(self.result_yaml.count_results(), 1)
3057+
3058+ self.result_yaml.result()
3059+
3060+ # check that the result was processed and removed.
3061+ self.assertEqual(self.result_yaml.count_results(), 0)
3062+
3063+ def test_result_pass(self):
3064+ """
3065+ Test that adding a pass result sets the Result object's status
3066+ to 'PASS'.
3067+ """
3068+ self.result_yaml.add_result(self.result)
3069+
3070+ self.assertEqual(self.result_yaml.status, 'PASS')
3071+
3072+ def test_result_failure(self):
3073+ """
3074+ Test that adding a failure result sets the Result object's status
3075+ to 'ERROR'.
3076+ """
3077+ self.result_yaml.add_result(self.result_fail)
3078+
3079+ self.assertEqual(self.result_yaml.status, 'ERROR')
3080+
3081+ def test_result_clear_result(self):
3082+ """
3083+ Test that the result is actually cleared.
3084+ """
3085+ self.result_yaml.add_result(self.result_fail)
3086+ self.assertEqual(self.result_yaml.status, 'ERROR')
3087+
3088+ self.result_yaml.clear_results()
3089+
3090+ self.assertEqual(self.result_yaml.status, 'PASS')
3091+ self.assertEqual(self.result_yaml.count_results(), 0)
3092+
3093+ def test_result_result(self):
3094+ """
3095+ Test that ERROR status is retained after adding pass
3096+ results.
3097+ """
3098+ self.result_yaml.add_result(self.result_fail)
3099+
3100+ self.assertEqual(self.result_yaml.status, 'ERROR')
3101+
3102+ self.result_yaml.add_result(self.result)
3103+
3104+ self.assertEqual(self.result_yaml.status, 'ERROR')
3105+
3106+
3107+ def test_result_yaml(self):
3108+ self.result_yaml.add_result(self.result)
3109+
3110+ import sys
3111+ import StringIO
3112+
3113+ str_output = StringIO.StringIO()
3114+
3115+ # redirect output to a string
3116+ old_stdout = sys.stdout
3117+ sys.stdout = str_output
3118+
3119+ self.result_yaml.result()
3120+
3121+ sys.stdout = old_stdout
3122+
3123+ result_str = str_output.getvalue()
3124+
3125+ print("result_str: %s" % result_str)
3126+
3127+ data = yaml.load(result_str)
3128+
3129+ self.assertEqual(len(data), 1)
3130+
3131+ result = data[0]
3132+
3133+ print("data: %s" % data)
3134+
3135+ self.assertEqual(result['command'], self.result['command'])
3136+ self.assertEqual(result['returncode'], self.result['returncode'])
3137+ self.assertEqual(result['stdout'], self.result['stdout'])
3138+ self.assertEqual(result['stderr'], self.result['stderr'])
3139
3140 class TestRunner(unittest.TestCase):
3141- def setUp(self):
3142- self.result_class = ResultYAML
3143- self.state_file = '/tmp/state.yaml'
3144- self.state_agent = StateAgentYAML(state_file=self.state_file)
3145- self.runner = Runner(result_class=self.result_class,
3146- state_agent=self.state_agent)
3147- self.bad_dir = "/tmp/does_not_exist"
3148-
3149- self.assertFalse(os.path.exists(self.bad_dir))
3150-
3151- def tearDown(self):
3152-
3153- # Remove self.bad_dir if it got created.
3154- try:
3155- os.removedirs(self.bad_dir)
3156- except OSError as e:
3157- if e.errno == 2:
3158- pass
3159- else:
3160- raise
3161-
3162- def test_rc_local(self):
3163- tmp_rc_local = '/tmp/rc.local'
3164- self.runner.setup_rc_local(tmp_rc_local)
3165-
3166- self.assertTrue(os.path.exists(tmp_rc_local))
3167-
3168- def test_run(self):
3169- self.runner.run()
3170-
3171- for suite in self.runner.suites:
3172- self.assertTrue(suite.is_done())
3173-
3174- def test_missing_testdir(self):
3175- """
3176- Test that the runner fails gracefully on missing testdir.
3177- """
3178-
3179- self.assertRaises(exceptions.BadDir,
3180- Runner, result_class=self.result_class, testdir=self.bad_dir)
3181-
3182- def test_missing_master_runlist(self):
3183- """
3184- Test that the runner fails gracefully on missing 'master.run'
3185- """
3186-
3187- tmp_master_runlist = os.path.join(self.bad_dir, 'master.run')
3188-
3189- if not os.path.exists(self.bad_dir):
3190- os.mkdir(self.bad_dir)
3191-
3192- # TODO: fix to use --master-runlist flag once it's been added.
3193- if os.path.exists(tmp_master_runlist):
3194- os.remove(tmp_master_runlist)
3195-
3196- self.assertRaises(exceptions.MissingFile,
3197- Runner, result_class=self.result_class, testdir=self.bad_dir)
3198-
3199- def test_runlist(self):
3200- """
3201- Test that passing a runlist to the Runner works properly.
3202- """
3203- runlist = '/tmp/master_runlist_test'
3204-
3205- # fail if there is already a runlist there.
3206- self.assertFalse(os.path.exists(runlist))
3207-
3208- fp = open(runlist, 'w')
3209- fp.write(master_runlist_content)
3210- fp.close()
3211-
3212- runner = Runner(result_class=self.result_class, runlist=runlist)
3213-
3214- self.assertTrue(runner.count_tests(), 0)
3215-
3216- os.remove(runlist)
3217+ def setUp(self):
3218+ self.result_class = ResultYAML
3219+ self.state_file = '/tmp/state.yaml'
3220+ self.state_agent = StateAgentYAML(state_file=self.state_file)
3221+ self.runner = Runner(result_class=self.result_class,
3222+ state_agent=self.state_agent)
3223+ self.bad_dir = "/tmp/does_not_exist"
3224+
3225+ self.assertFalse(os.path.exists(self.bad_dir))
3226+
3227+ def tearDown(self):
3228+
3229+ # Remove self.bad_dir if it got created.
3230+ try:
3231+ os.removedirs(self.bad_dir)
3232+ except OSError as e:
3233+ if e.errno == 2:
3234+ pass
3235+ else:
3236+ raise
3237+
3238+ def test_fetch_failed(self):
3239+ print "Starting test"
3240+ # exit 2 should be flagged as an error.
3241+ bad_runlist_content="""---\n- name: fake_tests\n fetch_cmd: exit 2\n"""
3242+ runlist = '/tmp/master_fetch_fails.run'
3243+
3244+ self.assertFalse(os.path.exists(runlist), runlist)
3245+
3246+ fp = open(runlist, 'w')
3247+ fp.write(bad_runlist_content)
3248+ fp.close()
3249+
3250+ runner = Runner(result_class=self.result_class,
3251+ state_agent=self.state_agent, runlist=runlist)
3252+
3253+ # remove the runlist as early as possible to avoid leaving it
3254+ # laying around if this test fails.
3255+ try:
3256+ os.remove(runlist)
3257+ except OSError:
3258+ pass
3259+
3260+ runner.run()
3261+ print runner.report()
3262+
3263+ self.assertEqual(runner.fetch_errors, 1)
3264+
3265+ def test_rc_local(self):
3266+ tmp_rc_local = '/tmp/rc.local'
3267+ self.runner.setup_rc_local(tmp_rc_local)
3268+
3269+ self.assertTrue(os.path.exists(tmp_rc_local))
3270+
3271+ def test_run(self):
3272+ retcode = self.runner.run()
3273+
3274+ for suite in self.runner.suites:
3275+ self.assertTrue(suite.is_done())
3276+
3277+ self.assertEqual(retcode, RETURN_CODES['ERROR'])
3278+
3279+ def test_missing_testdir(self):
3280+ """
3281+ Test that the runner fails gracefully on missing testdir.
3282+ """
3283+
3284+ self.assertRaises(exceptions.BadDir,
3285+ Runner, result_class=self.result_class, testdir=self.bad_dir)
3286+
3287+ def test_missing_master_runlist(self):
3288+ """
3289+ Test that the runner fails gracefully on missing 'master.run'
3290+ """
3291+
3292+ tmp_master_runlist = os.path.join(self.bad_dir, 'master.run')
3293+
3294+ if not os.path.exists(self.bad_dir):
3295+ os.mkdir(self.bad_dir)
3296+
3297+ # TODO: fix to use --master-runlist flag once it's been added.
3298+ if os.path.exists(tmp_master_runlist):
3299+ os.remove(tmp_master_runlist)
3300+
3301+ self.assertRaises(exceptions.MissingFile,
3302+ Runner, result_class=self.result_class, testdir=self.bad_dir)
3303+
3304+ def test_runlist(self):
3305+ """
3306+ Test that passing a runlist to the Runner works properly.
3307+ """
3308+ runlist = '/tmp/master_runlist_test'
3309+
3310+ # fail if there is already a runlist there.
3311+ self.assertFalse(os.path.exists(runlist), "%s exists" % runlist)
3312+
3313+ fp = open(runlist, 'w')
3314+ fp.write(master_runlist_content)
3315+ fp.close()
3316+
3317+ runner = Runner(result_class=self.result_class, runlist=runlist)
3318+
3319+ self.assertTrue(runner.count_tests(), 0)
3320+
3321+ os.remove(runlist)
3322
3323 def state_saver():
3324- debug_print("Saving state", force=True)
3325+ debug_print("Saving state", force=True)
3326
3327 class TestCommon(unittest.TestCase):
3328
3329- def test_run_cmd(self):
3330- result = run_cmd('find . -name "*.py"')
3331-
3332- self.assertEqual(result['returncode'], 0)
3333-
3334- def test_debug_print(self):
3335- """
3336- Test that debug_print doesn't print when DEBUG is False unless
3337- 'debug' is passed in and is True.
3338- """
3339- from common import CONFIG
3340-
3341- old_debug = CONFIG['DEBUG']
3342-
3343- CONFIG['DEBUG'] = True
3344- if not debug_print("testing debug_print"):
3345- raise Exception("didn't print with CONFIG['DEBUG'] set to True")
3346-
3347- CONFIG['DEBUG'] = False
3348- self.assertEqual(debug_print("shouldn't be printed unless common.CONFIG['DEBUG'] is True"), CONFIG['DEBUG'], "printed with CONFIG['DEBUG'] set to False")
3349-
3350- self.assertEqual(debug_print("should be printed (using 'force=True')", force=True), True)
3351-
3352- CONFIG['DEBUG'] = old_debug
3353+ def test_run_cmd(self):
3354+ result = run_cmd('find . -name "*.py"')
3355+
3356+ self.assertEqual(result['returncode'], 0)
3357+
3358+ def test_debug_print(self):
3359+ """
3360+ Test that debug_print doesn't print when DEBUG is False unless
3361+ 'debug' is passed in and is True.
3362+ """
3363+ from common import CONFIG
3364+
3365+ old_debug = CONFIG['DEBUG']
3366+
3367+ CONFIG['DEBUG'] = True
3368+ if not debug_print("testing debug_print"):
3369+ raise Exception("didn't print with CONFIG['DEBUG'] set to True")
3370+
3371+ CONFIG['DEBUG'] = False
3372+ self.assertEqual(debug_print("shouldn't be printed unless common.CONFIG['DEBUG'] is True"), CONFIG['DEBUG'], "printed with CONFIG['DEBUG'] set to False")
3373+
3374+ self.assertEqual(debug_print("should be printed (using 'force=True')", force=True), True)
3375+
3376+ CONFIG['DEBUG'] = old_debug
3377
3378 ######################################################################
3379 ### The tests after this comment really need to be moved into the
3380@@ -404,123 +464,98 @@
3381
3382
3383 class TestStateAgentYAML(unittest.TestCase):
3384- def setUp(self):
3385- """
3386- Create a state agent for testing.
3387- """
3388- self.state_file = '/tmp/state.yaml'
3389-
3390- # Fail if there is already a test state file.
3391- self.assertFalse(os.path.exists(self.state_file), "state file (%s) already exists" % self.state_file)
3392-
3393- self.state_agent = StateAgentYAML(state_file=self.state_file)
3394- self.runner = Runner(result_class=ResultYAML, state_agent=self.state_agent)
3395- self.partial_state_file_content = partial_state_file_content
3396-
3397- def tearDown(self):
3398- """
3399- Clean up the test state file.
3400- """
3401- try:
3402- os.remove(self.state_file)
3403- except OSError as e:
3404- if e.errno == 2: # does not exist
3405- pass
3406- else:
3407- raise
3408-
3409- def test_creation(self):
3410- suite_count = self.runner.count_suites()
3411- test_count = self.runner.count_tests()
3412-
3413- self.runner.run()
3414-
3415- self.assertTrue(os.path.exists(self.state_file))
3416-
3417- def test_cleanup(self):
3418- self.runner.save_state()
3419-
3420- self.assertTrue(os.path.exists(self.state_file), "Runner failed to save state")
3421-
3422- self.state_agent.clean()
3423-
3424- self.assertFalse(os.path.exists(self.state_file), "Failed to remove state file (%s)" % self.state_file)
3425-
3426- def test_status(self):
3427- """
3428- Test that a runner properly saves its state during test runs.
3429- """
3430- suite_count = self.runner.count_suites()
3431- test_count = self.runner.count_tests()
3432-
3433- self.runner.run()
3434-
3435- self.assertTrue(os.path.exists(self.state_file))
3436-
3437- self.runner.load_state()
3438-
3439- self.assertEqual(self.runner.count_suites(), suite_count)
3440- self.assertEqual(self.runner.count_tests(), test_count)
3441-
3442- fp = open(self.state_file, 'r')
3443- state_data = yaml.load(fp)
3444- fp.close()
3445-
3446- self.assertEqual(state_data['status'], 'DONE')
3447-
3448- def test_load_state_partial(self):
3449- """
3450- Test that a partially run state file can be resumed.
3451- """
3452- fp = open(self.state_file, 'w')
3453- fp.write(self.partial_state_file_content)
3454- fp.close()
3455-
3456- state_agent = StateAgentYAML(state_file=self.state_file)
3457- runner = Runner(state_agent=state_agent, resume=True)
3458- runner.load_state()
3459-
3460- self.assertEqual(runner.status, 'RUN')
3461-
3462- next_test = runner.get_next_test()
3463-
3464- self.assertEqual(next_test.name, 'test_two')
3465-
3466- runner.run()
3467+ def setUp(self):
3468+ """
3469+ Create a state agent for testing.
3470+ """
3471+ self.state_file = '/tmp/state.yaml'
3472+
3473+ # Fail if there is already a test state file.
3474+ self.assertFalse(os.path.exists(self.state_file), "state file (%s) already exists" % self.state_file)
3475+
3476+ self.state_agent = StateAgentYAML(state_file=self.state_file)
3477+ self.runner = Runner(result_class=ResultYAML, state_agent=self.state_agent)
3478+ self.partial_state_file_content = partial_state_file_content
3479+
3480+ def tearDown(self):
3481+ """
3482+ Clean up the test state file.
3483+ """
3484+ try:
3485+ os.remove(self.state_file)
3486+ except OSError as e:
3487+ if e.errno == 2: # does not exist
3488+ pass
3489+ else:
3490+ raise
3491+
3492+ def test_creation(self):
3493+ suite_count = self.runner.count_suites()
3494+ test_count = self.runner.count_tests()
3495+
3496+ self.runner.run()
3497+
3498+ self.assertTrue(os.path.exists(self.state_file))
3499+
3500+ def test_cleanup(self):
3501+ self.runner.save_state()
3502+
3503+ self.assertTrue(os.path.exists(self.state_file), "Runner failed to save state")
3504+
3505+ self.state_agent.clean()
3506+
3507+ self.assertFalse(os.path.exists(self.state_file), "Failed to remove state file (%s)" % self.state_file)
3508+
3509+ def test_status(self):
3510+ """
3511+ Test that a runner properly saves its state during test runs.
3512+ """
3513+ suite_count = self.runner.count_suites()
3514+ test_count = self.runner.count_tests()
3515+
3516+ self.runner.run()
3517+
3518+ self.assertTrue(os.path.exists(self.state_file))
3519+
3520+ self.runner.load_state()
3521+
3522+ self.assertEqual(self.runner.count_suites(), suite_count)
3523+ self.assertEqual(self.runner.count_tests(), test_count)
3524+
3525+ fp = open(self.state_file, 'r')
3526+ state_data = yaml.load(fp)
3527+ fp.close()
3528+
3529+ self.assertEqual(state_data['status'], 'DONE')
3530+
3531+ def test_load_state_partial(self):
3532+ """
3533+ Test that a partially run state file can be resumed.
3534+ """
3535+ fp = open(self.state_file, 'w')
3536+ fp.write(self.partial_state_file_content)
3537+ fp.close()
3538+
3539+ state_agent = StateAgentYAML(state_file=self.state_file)
3540+ runner = Runner(state_agent=state_agent, resume=True)
3541+ runner.load_state()
3542+
3543+ self.assertEqual(runner.status, 'RUN')
3544+
3545+ next_test = runner.get_next_test()
3546+
3547+ self.assertEqual(next_test.name, 'test_two')
3548+
3549+ runner.run()
3550
3551 def main():
3552- """
3553- Run any tests listed in 'funcs'. Comment ones out if you want to skip them.
3554-
3555- Also you can run all tests via nose like this:
3556-
3557- nosetests main.py
3558-
3559- See nosetests(1) for more info.
3560-
3561- """
3562- funcs = [
3563- #test_testcase_default_results,
3564- #test_run_cmd,
3565- #test_testcase,
3566- #test_testsuite,
3567- #test_result_success,
3568- #test_result_failure,
3569- #test_result_yaml_success,
3570- #test_result_yaml_failure,
3571- #test_runner,
3572- #test_debug_print,
3573- #test_save_state,
3574- #test_testcase_save_and_load_state,
3575- #test_testsuite_save_and_load_state,
3576- #test_state_agent_YAML,
3577- #test_load_state_partial,
3578- test_state_agent_cleanup,
3579- test_testsuite_is_done,
3580- ]
3581-
3582- for func in funcs:
3583- func()
3584-
3585+ print """
3586+ You can run all tests via nose like this:
3587+
3588+ nosetests self_test.py
3589+
3590+ See nosetests(1) for more info.
3591+
3592+ """
3593 if __name__ == "__main__":
3594- main()
3595+ main()
3596
3597=== renamed file 'uath/state_agent.py' => 'uath/client/state_agent.py'
3598--- uath/state_agent.py 2012-04-09 19:43:57 +0000
3599+++ uath/client/state_agent.py 2012-05-09 00:33:18 +0000
3600@@ -6,75 +6,75 @@
3601
3602
3603 class StateAgent(object):
3604- """
3605- State saving base class.
3606-
3607- Accepts a dictionary of state info and prints it to STDOUT.
3608- """
3609-
3610- state_file = DEFAULT_STATE_FILE
3611-
3612- def __init__(self, state_file=None):
3613- if state_file is not None:
3614- self.state_file = state_file
3615-
3616- def clean(self):
3617- """
3618- Clean up the state file if it exists.
3619- """
3620- import os
3621-
3622- # Don't fail if the state_file doesn't exists.
3623- try:
3624- os.remove(self.state_file)
3625- except OSError as e:
3626- if e.errno == 2: # file not found
3627- pass
3628-
3629- def save_state(self, state):
3630- """
3631- Save state to the state_file.
3632- """
3633-
3634- fp = open(self.state_file, 'w')
3635- fp.write(str(state))
3636- fp.close()
3637-
3638- def load_state(self):
3639- """
3640- Load state from the state_file.
3641- """
3642- state = {}
3643-
3644- if os.path.exists(self.state_file):
3645- fp = open(self.state_file, 'r')
3646- state = fp.read()
3647- fp.close()
3648-
3649- return state
3650+ """
3651+ State saving base class.
3652+
3653+ Accepts a dictionary of state info and prints it to STDOUT.
3654+ """
3655+
3656+ state_file = DEFAULT_STATE_FILE
3657+
3658+ def __init__(self, state_file=None):
3659+ if state_file is not None:
3660+ self.state_file = state_file
3661+
3662+ def clean(self):
3663+ """
3664+ Clean up the state file if it exists.
3665+ """
3666+ import os
3667+
3668+ # Don't fail if the state_file doesn't exists.
3669+ try:
3670+ os.remove(self.state_file)
3671+ except OSError as e:
3672+ if e.errno == 2: # file not found
3673+ pass
3674+
3675+ def save_state(self, state):
3676+ """
3677+ Save state to the state_file.
3678+ """
3679+
3680+ fp = open(self.state_file, 'w')
3681+ fp.write(str(state))
3682+ fp.close()
3683+
3684+ def load_state(self):
3685+ """
3686+ Load state from the state_file.
3687+ """
3688+ state = {}
3689+
3690+ if os.path.exists(self.state_file):
3691+ fp = open(self.state_file, 'r')
3692+ state = fp.read()
3693+ fp.close()
3694+
3695+ return state
3696
3697 class StateAgentYAML(StateAgent):
3698- """
3699- YAML based state saver.
3700- """
3701-
3702- def save_state(self, state):
3703- """
3704- Output the state as YAML.
3705- """
3706-
3707- super(StateAgentYAML, self).save_state(yaml.dump(state, default_flow_style=False))
3708-
3709- def load_state(self):
3710- """
3711- Load state from YAML state_file.
3712- """
3713-
3714- state = {}
3715-
3716- if os.path.exists(self.state_file):
3717- fp = open(self.state_file, 'r')
3718- state = yaml.load(fp)
3719- fp.close()
3720-
3721- return state
3722+ """
3723+ YAML based state saver.
3724+ """
3725+
3726+ def save_state(self, state):
3727+ """
3728+ Output the state as YAML.
3729+ """
3730+
3731+ super(StateAgentYAML, self).save_state(yaml.dump(state, default_flow_style=False))
3732+
3733+ def load_state(self):
3734+ """
3735+ Load state from YAML state_file.
3736+ """
3737+
3738+ state = {}
3739+
3740+ if os.path.exists(self.state_file):
3741+ fp = open(self.state_file, 'r')
3742+ state = yaml.load(fp)
3743+ fp.close()
3744+
3745+ return state
3746
3747=== renamed file 'uath/testcase.py' => 'uath/client/testcase.py'
3748--- uath/testcase.py 2012-04-06 15:44:09 +0000
3749+++ uath/client/testcase.py 2012-05-09 00:33:18 +0000
3750@@ -5,198 +5,201 @@
3751 from common import run_cmd, parse_control_file, debug_print
3752 from common import do_nothing
3753 from result import Result
3754+from exceptions import MissingFile
3755
3756 class TestCase(object):
3757- """
3758- The TestCase class.
3759-
3760- status is one of 'NOTRUN', 'BUILD', 'SETUP', 'RUN', 'CLEANUP', or 'DONE'
3761- """
3762-
3763- status = 'NOTRUN'
3764- build_cmd = None
3765- tc_setup = None
3766- tc_cleanup = None
3767- reboot = 'never'
3768- reboot_callback = None
3769- save_state_callback = do_nothing
3770-
3771- def __init__(self, name, path, command=None, result=None, timeout=0,
3772- _control_data=None, _save_state_callback=None, _reboot_callback=None):
3773- """
3774- Build a TestCase from a control file's data.
3775-
3776- 'name' is a directory where the test case resides.
3777- """
3778- self.name = name
3779- self.path = path
3780- self.working_dir = path
3781- self.filename = "%s/tc_control" % path
3782- self.results = []
3783- self.result = result
3784- self.timeout = timeout
3785- self.command = command
3786- self.reboot_callback = _reboot_callback
3787-
3788- if _save_state_callback is not None:
3789- self.save_state_callback = _save_state_callback
3790-
3791- required_items = ['description', 'type']
3792- optional_items = ['build_cmd',
3793- 'timeout',
3794- 'command',
3795- 'tc_setup',
3796- 'tc_cleanup',
3797- 'reboot', # 'always', 'success', or 'never'
3798- ]
3799-
3800-
3801- control_data = None
3802- import os
3803- if _control_data is None and os.path.exists(self.filename):
3804- control_data = parse_control_file(self.filename, required=required_items,
3805- optional=optional_items)
3806- else:
3807- control_data = _control_data
3808-
3809- if control_data is not None:
3810- self.__dict__.update(control_data)
3811-
3812- # Set any values passed into __init__() as final values
3813- if timeout != 0 or self.timeout == 0:
3814- self.timeout = timeout
3815- if command is not None:
3816- self.command = command
3817-
3818- def __str__(self):
3819- return "%s: %s, %s, %s" % (self.name, self.description, self.command, self.timeout)
3820-
3821- def set_status(self, status):
3822- """
3823- Set the status for the test case and call the save state callback.
3824- """
3825- self.status = status
3826- self.save_state_callback()
3827-
3828- def build(self, result):
3829- """
3830- Run build, but only if we haven't started a run yet.
3831- """
3832- if self.status == 'NOTRUN':
3833- self.set_status('BUILD')
3834- result.add_result(run_cmd(self.build_cmd, cwd=self.working_dir))
3835-
3836- def setup(self, result):
3837- """
3838- Run tc_setup, but only if build() has just run successfully.
3839- """
3840- if self.status == 'BUILD' and result.status == 'SUCCESS':
3841- self.set_status('SETUP')
3842- result.add_result(run_cmd(self.tc_setup, cwd=self.working_dir))
3843-
3844- def cleanup(self, result):
3845- """
3846- Run tc_cleanup after a run.
3847- """
3848- if self.status == 'RUN':
3849- self.set_status('CLEANUP')
3850- result.add_result(run_cmd(self.tc_cleanup, cwd=self.working_dir))
3851-
3852- def run(self, result=None):
3853- """
3854- Run the test case; including any build, setup, and cleanup commands.
3855- """
3856-
3857- # Return value to indicate whether processing of a TestSuite should
3858- # continue. This is to avoid a shutdown race on reboot cases.
3859- keep_going = True
3860-
3861- # If no result object was passed in and the instance does not have
3862- # one either then create a basic text result object.
3863- if result is None:
3864- if self.result is not None:
3865- result = self.result
3866- else:
3867- result = Result(self.name)
3868-
3869- self.build(result)
3870-
3871- # if the build fails don't run the test command
3872- if result.status != 'SUCCESS':
3873- return result.result()
3874-
3875- self.setup(result)
3876-
3877- # if the setup fails don't run the test command
3878- if result.status != 'SUCCESS':
3879- return result.result()
3880-
3881- if self.status == 'SETUP':
3882- timeout = self.timeout or 0
3883- self.status = "RUN"
3884- self.save_state_callback()
3885- result.add_result(run_cmd(self.command, timeout=timeout, cwd=self.working_dir))
3886-
3887- # Clean up whether 'command' failed or not.
3888- self.cleanup(result)
3889-
3890- need_reboot = False
3891- if self.reboot == 'always' or self.reboot == 'success' and result.status == 'SUCCESS':
3892- need_reboot = True
3893-
3894- result.result()
3895-
3896- if self.status == 'CLEANUP':
3897- self.status = "DONE"
3898- self.save_state_callback()
3899-
3900- if need_reboot and self.reboot_callback is not None:
3901- #print("JAT: *** HERE I WOULD HAVE REBOOTED ***")
3902- self.reboot_callback()
3903- keep_going = False
3904-
3905- return keep_going
3906-
3907- def process_overrides(self, overrides):
3908- """
3909- Sets override values from a TestSuite runlist for this test case.
3910- """
3911- for item in overrides:
3912- for key, value in item.iteritems():
3913- setattr(self, key, value)
3914-
3915- def load_state(self, state):
3916- """
3917- Restore state from the supplied dictionary.
3918-
3919- Requires that 'state' has the same fieldnames as the TestCase class.
3920- """
3921- self.__dict__.update(state)
3922-
3923- def save_state(self):
3924- """
3925- Returns a dictionary representing the test's state.
3926- """
3927- state = {
3928- 'name': self.name,
3929- 'path': self.path,
3930- 'command': self.command,
3931- 'timeout': self.timeout,
3932- 'status': self.status,
3933- 'build_cmd': self.build_cmd,
3934- 'tc_setup': self.tc_setup,
3935- 'tc_cleanup': self.tc_cleanup,
3936- 'type': self.type,
3937- 'description': self.description,
3938- }
3939-
3940- return state
3941-
3942- def is_done(self):
3943- """
3944- Determine if the case is done. This might mean that something has
3945- failed. Used by suite to determine if the suite needs to be re-run
3946- on resume.
3947- """
3948- return self.status == 'DONE' or self.status == 'CLEANUP'
3949+ """
3950+ The TestCase class.
3951+
3952+ status is one of 'NOTRUN', 'BUILD', 'SETUP', 'RUN', 'CLEANUP', or 'DONE'
3953+ """
3954+
3955+ status = 'NOTRUN'
3956+ build_cmd = None
3957+ tc_setup = None
3958+ tc_cleanup = None
3959+ reboot = 'never'
3960+ reboot_callback = None
3961+ save_state_callback = do_nothing
3962+ type = 'userland'
3963+
3964+ def __init__(self, name, path, command=None, result=None, timeout=0,
3965+ _control_data=None, _save_state_callback=None, _reboot_callback=None):
3966+ """
3967+ Build a TestCase from a control file's data.
3968+
3969+ 'name' is a directory where the test case resides.
3970+ """
3971+ self.name = name
3972+ self.path = path
3973+ self.working_dir = path
3974+ self.filename = "%s/tc_control" % path
3975+ self.results = []
3976+ self.result = result
3977+ self.timeout = timeout
3978+ self.command = command
3979+ self.reboot_callback = _reboot_callback
3980+
3981+ if _save_state_callback is not None:
3982+ self.save_state_callback = _save_state_callback
3983+
3984+ required_items = ['description', 'type', 'timeout', 'action']
3985+ optional_items = ['build_cmd',
3986+ 'command',
3987+ 'tc_setup',
3988+ 'tc_cleanup',
3989+ 'reboot', # 'always', 'pass', or 'never'
3990+ ]
3991+
3992+
3993+ control_data = None
3994+ import os
3995+ if _control_data is None:
3996+ if os.path.exists(self.filename):
3997+ control_data = parse_control_file(self.filename, required=required_items,
3998+ optional=optional_items)
3999+ else:
4000+ raise MissingFile(self.filename)
4001+ else:
4002+ control_data = _control_data
4003+
4004+ if control_data is not None:
4005+ self.__dict__.update(control_data)
4006+
4007+ # Set any values passed into __init__() as final values
4008+ if timeout != 0 or self.timeout == 0:
4009+ self.timeout = timeout
4010+ if command is not None:
4011+ self.command = command
4012+
4013+ def __str__(self):
4014+ return "%s: %s, %s, %s" % (self.name, self.description, self.command, self.timeout)
4015+
4016+ def set_status(self, status):
4017+ """
4018+ Set the status for the test case and call the save state callback.
4019+ """
4020+ self.status = status
4021+ self.save_state_callback()
4022+
4023+ def build(self, result):
4024+ """
4025+ Run build, but only if we haven't started a run yet.
4026+ """
4027+ if self.status == 'NOTRUN':
4028+ self.set_status('BUILD')
4029+ result.add_result(run_cmd(self.build_cmd, cwd=self.working_dir))
4030+
4031+ def setup(self, result):
4032+ """
4033+ Run tc_setup, but only if build() has just passed.
4034+ """
4035+ if self.status == 'BUILD' and result.status == 'PASS':
4036+ self.set_status('SETUP')
4037+ result.add_result(run_cmd(self.tc_setup, cwd=self.working_dir))
4038+
4039+ def cleanup(self, result):
4040+ """
4041+ Run tc_cleanup after a run.
4042+ """
4043+ if self.status == 'RUN':
4044+ self.set_status('CLEANUP')
4045+ result.add_result(run_cmd(self.tc_cleanup, cwd=self.working_dir))
4046+
4047+ def run(self, result=None):
4048+ """
4049+ Run the test case; including any build, setup, and cleanup commands.
4050+ """
4051+
4052+ # Return value to indicate whether processing of a TestSuite should
4053+ # continue. This is to avoid a shutdown race on reboot cases.
4054+ keep_going = True
4055+
4056+ # If no result object was passed in and the instance does not have
4057+ # one either then create a basic text result object.
4058+ if result is None:
4059+ if self.result is not None:
4060+ result = self.result
4061+ else:
4062+ result = Result(self.name)
4063+
4064+ self.build(result)
4065+
4066+ # if the build fails don't run the test command
4067+ if result.status != 'PASS':
4068+ return result.result()
4069+
4070+ self.setup(result)
4071+
4072+ # if the setup fails don't run the test command
4073+ if result.status != 'PASS':
4074+ return result.result()
4075+
4076+ if self.status == 'SETUP':
4077+ timeout = self.timeout or 0
4078+ self.status = "RUN"
4079+ self.save_state_callback()
4080+ result.add_result(run_cmd(self.command, timeout=timeout, cwd=self.working_dir))
4081+
4082+ # Clean up whether 'command' failed or not.
4083+ self.cleanup(result)
4084+
4085+ need_reboot = False
4086+ if self.reboot == 'always' or self.reboot == 'pass' and result.status == 'PASS':
4087+ need_reboot = True
4088+
4089+ self.run_status = result.result()
4090+
4091+ if self.status == 'CLEANUP':
4092+ self.status = "DONE"
4093+ self.save_state_callback()
4094+
4095+ if need_reboot and self.reboot_callback is not None:
4096+ self.reboot_callback()
4097+ keep_going = False
4098+
4099+ return keep_going
4100+
4101+ def process_overrides(self, overrides):
4102+ """
4103+ Sets override values from a TestSuite runlist for this test case.
4104+ """
4105+ for item in overrides:
4106+ for key, value in item.iteritems():
4107+ setattr(self, key, value)
4108+
4109+ def load_state(self, state):
4110+ """
4111+ Restore state from the supplied dictionary.
4112+
4113+ Requires that 'state' has the same fieldnames as the TestCase class.
4114+ """
4115+ self.__dict__.update(state)
4116+
4117+ def save_state(self):
4118+ """
4119+ Returns a dictionary representing the test's state.
4120+ """
4121+ state = {
4122+ 'name': self.name,
4123+ 'path': self.path,
4124+ 'command': self.command,
4125+ 'timeout': self.timeout,
4126+ 'status': self.status,
4127+ 'build_cmd': self.build_cmd,
4128+ 'tc_setup': self.tc_setup,
4129+ 'tc_cleanup': self.tc_cleanup,
4130+ 'type': self.type,
4131+ 'description': self.description,
4132+ }
4133+
4134+ return state
4135+
4136+ def is_done(self):
4137+ """
4138+ Determine if the case is done. This might mean that something has
4139+ failed. Used by suite to determine if the suite needs to be re-run
4140+ on resume.
4141+ """
4142+ return self.status == 'DONE' or self.status == 'CLEANUP'
4143
4144
4145=== renamed file 'uath/testsuite.py' => 'uath/client/testsuite.py'
4146--- uath/testsuite.py 2012-04-10 20:23:50 +0000
4147+++ uath/client/testsuite.py 2012-05-09 00:33:18 +0000
4148@@ -12,255 +12,268 @@
4149 import yaml
4150
4151 def parse_runlist_file(runlist_file):
4152- """
4153- Parse a tslist.run runlist file for a list of test cases.
4154- """
4155-
4156- fp = open(runlist_file, 'r')
4157- data = yaml.load(fp)
4158- fp.close()
4159-
4160- return data
4161+ """
4162+ Parse a tslist.run runlist file for a list of test cases.
4163+ """
4164+
4165+ fp = open(runlist_file, 'r')
4166+ data = yaml.load(fp)
4167+ fp.close()
4168+
4169+ return data
4170
4171 class TestSuite(object):
4172- """
4173- The TestSuite class.
4174- """
4175-
4176- build_cmd = None
4177- timeout = 0
4178- ts_setup = None
4179- ts_cleanup = None
4180- control_file = None
4181- # status is one of 'NOTRUN', 'BUILD', 'SETUP', 'INPROGRESS', 'CLEANUP', and 'DONE'
4182- status = "NOTRUN"
4183- save_state_callback = do_nothing
4184-
4185-
4186- def __init__(self, name, path, runlist_file=DEFAULT_TSLIST,
4187- control_file=DEFAULT_TSCONTROL, includes=None, excludes=None,
4188- result=None, result_class=Result,
4189- _control_data=None, _runlist_data=None,
4190- _save_state_callback=None, _reboot_callback=None):
4191-
4192- self.path = os.path.join(path, name)
4193- self.reboot_callback = _reboot_callback
4194-
4195- # work from within the testsuite's directory. eg. /var/uath/examples
4196- old_cwd = os.getcwd()
4197- os.chdir(self.path)
4198- """
4199- Build a TestSuite from a control file's data.
4200- """
4201- control_file = os.path.join(name, control_file)
4202-
4203- if os.path.exists(control_file):
4204- self.control_file = control_file
4205-
4206- self.runlist_file = os.path.join(name, runlist_file)
4207- self.tests = []
4208- self.name = name
4209-
4210- if _save_state_callback is not None:
4211- self.save_state_callback = _save_state_callback
4212-
4213- # Either use the result object passed in or create one.
4214- if result is None:
4215- self.result = result_class()
4216- else:
4217- self.result = result
4218-
4219- required_items = []
4220- optional_items = ['build_cmd',
4221- 'timeout',
4222- 'ts_setup',
4223- 'ts_cleanup',
4224- ]
4225-
4226- control_data = None
4227-
4228- if _control_data is not None:
4229- control_data = _control_data
4230- elif self.control_file is not None:
4231- control_data = parse_control_file(self.control_file,
4232- required=required_items, optional=optional_items)
4233-
4234- if control_data is not None:
4235- self.__dict__.update(control_data)
4236-
4237- if _runlist_data is not None:
4238- runlist_data = _runlist_data
4239- elif os.path.exists(self.runlist_file):
4240- runlist_data = parse_runlist_file(self.runlist_file)
4241- else:
4242- runlist_data = []
4243-
4244- for test in runlist_data:
4245- name = test['test']
4246- test_path = os.path.join(self.name, name)
4247-
4248- if includes is not None and name not in includes:
4249- continue
4250-
4251- if excludes is not None and name in excludes:
4252- continue
4253-
4254- command = test.get('command')
4255-
4256- tc = TestCase(name=name, path=test_path,
4257- command=command, timeout=self.timeout,
4258- result=self.result,
4259- _save_state_callback=self.save_state_callback,
4260- _reboot_callback=self.reboot_callback)
4261- if 'overrides' in test:
4262- tc.process_overrides(test['overrides'])
4263-
4264- self.tests.append(tc)
4265-
4266- # restore the old working directory
4267- os.chdir(old_cwd)
4268-
4269- def __str__(self):
4270- return "%s: %s" % (self.control_file, self.runlist_file)
4271-
4272- def set_status(self, status):
4273- """
4274- Set the status for the test suite and call the save state callback.
4275- """
4276- self.status = status
4277- self.save_state_callback()
4278-
4279- def build(self, result):
4280- """
4281- Run build, but only if we haven't started a run yet.
4282- """
4283- if self.status == 'NOTRUN':
4284- self.set_status('BUILD')
4285- result.add_result(run_cmd(self.build_cmd, cwd=self.name))
4286-
4287- def setup(self, result):
4288- """
4289- Run ts_setup, but only if build() has just run successfully.
4290- """
4291- if self.status == 'BUILD' and result.status == 'SUCCESS':
4292- self.set_status('SETUP')
4293- result.add_result(run_cmd(self.ts_setup, cwd=self.name))
4294-
4295- def cleanup(self, result):
4296- """
4297- Run ts_cleanup after a run.
4298- """
4299- if self.status == 'INPROGRESS':
4300- self.set_status('CLEANUP')
4301- result.add_result(run_cmd(self.ts_cleanup, cwd=self.name))
4302-
4303-
4304- def run(self):
4305- """
4306- Run the test cases; including any build, setup, and cleanup commands.
4307- """
4308-
4309- # Return value to indicate whether processing of a Runner should
4310- # continue. This is to avoid a shutdown race on reboot cases.
4311- keep_going = True
4312-
4313- # Work from the testsuite directory
4314- os.chdir(self.path)
4315-
4316- result = self.result
4317-
4318- self.build(result)
4319- self.setup(result)
4320-
4321- # Always enter this loop since test.run() will handle checking if
4322- # the test has already been run.
4323- self.status = "INPROGRESS"
4324- if result.status == 'SUCCESS':
4325- for test in self.tests:
4326- keep_going = test.run(self.result)
4327-
4328- if not keep_going:
4329- return keep_going
4330-
4331- self.cleanup(result)
4332-
4333- if self.status == 'CLEANUP':
4334- result.result()
4335- self.set_status("DONE")
4336-
4337- return keep_going
4338-
4339- def add_test(self, tests):
4340- """
4341- Add a single test or list of tests to this suite.
4342- """
4343- if isinstance(tests, TestCase):
4344- self.tests.append(tests)
4345- else:
4346- try:
4347- for test in tests:
4348- self.add_test(test)
4349- except TypeError:
4350- pass
4351-
4352- def count_tests(self):
4353- return len(self.tests)
4354-
4355- def load_state(self, state):
4356- """
4357- Restore our state from the supplied dictionary.
4358-
4359- Requires that the fieldnames in the dictionary match the class
4360- properties.
4361- """
4362- # XXX: Should this be done explicitly?
4363- self.__dict__.update(state)
4364-
4365- self.tests = []
4366- for state_test in state['tests']:
4367- test = TestCase(name=state_test['name'], path=state_test['path'],
4368- result=self.result,
4369- _save_state_callback=self.save_state_callback,
4370- _reboot_callback=self.reboot_callback)
4371- test.load_state(state_test)
4372- self.tests.append(test)
4373-
4374- def save_state(self):
4375- state = {
4376- 'name': self.name,
4377- 'status': self.status,
4378- 'tests': [],
4379- 'ts_setup': self.ts_setup,
4380- 'ts_cleanup': self.ts_cleanup,
4381- 'build_cmd': self.build_cmd,
4382- 'timeout': self.timeout,
4383- }
4384-
4385- for test in self.tests:
4386- state['tests'].append(test.save_state())
4387-
4388- return state
4389-
4390- def is_done(self):
4391- """
4392- Determine if the suite is done. This might mean that something has
4393- failed. Used by Runner to determine if the suite needs to be re-run
4394- on resume.
4395- """
4396- return self.status == 'DONE' or self.status == 'CLEANUP'
4397-
4398- def get_next_test(self):
4399- """
4400- Return the next test to be run.
4401-
4402- Mainly used for debugging.
4403- """
4404-
4405- test = None
4406-
4407- for t in self.tests:
4408- if not t.is_done():
4409- test = t
4410- break
4411-
4412- return test
4413+ """
4414+ The TestSuite class.
4415+ """
4416+
4417+ build_cmd = None
4418+ timeout = 0
4419+ ts_setup = None
4420+ ts_cleanup = None
4421+ control_file = None
4422+ # status is one of 'NOTRUN', 'BUILD', 'SETUP', 'INPROGRESS', 'CLEANUP', and 'DONE'
4423+ status = "NOTRUN"
4424+ save_state_callback = do_nothing
4425+
4426+
4427+ def __init__(self, name, path, runlist_file=DEFAULT_TSLIST,
4428+ control_file=DEFAULT_TSCONTROL, includes=None, excludes=None,
4429+ result=None, result_class=Result,
4430+ _control_data=None, _runlist_data=None,
4431+ _save_state_callback=None, _reboot_callback=None):
4432+
4433+ self.path = os.path.join(path, name)
4434+ self.reboot_callback = _reboot_callback
4435+ self.passes = 0
4436+ self.failures = 0
4437+ self.errors = 0
4438+
4439+ # work from within the testsuite's directory. eg. /var/lib/uath/examples
4440+ old_cwd = os.getcwd()
4441+ os.chdir(self.path)
4442+ """
4443+ Build a TestSuite from a control file's data.
4444+ """
4445+ control_file = os.path.join(name, control_file)
4446+
4447+ if os.path.exists(control_file):
4448+ self.control_file = control_file
4449+
4450+ self.runlist_file = os.path.join(name, runlist_file)
4451+ self.tests = []
4452+ self.name = name
4453+
4454+ if _save_state_callback is not None:
4455+ self.save_state_callback = _save_state_callback
4456+
4457+ # Either use the result object passed in or create one.
4458+ if result is None:
4459+ self.result = result_class()
4460+ else:
4461+ self.result = result
4462+
4463+ required_items = []
4464+ optional_items = ['build_cmd',
4465+ 'timeout',
4466+ 'ts_setup',
4467+ 'ts_cleanup',
4468+ ]
4469+
4470+ control_data = None
4471+
4472+ if _control_data is not None:
4473+ control_data = _control_data
4474+ elif self.control_file is not None:
4475+ control_data = parse_control_file(self.control_file,
4476+ required=required_items, optional=optional_items)
4477+
4478+ if control_data is not None:
4479+ self.__dict__.update(control_data)
4480+
4481+ if _runlist_data is not None:
4482+ runlist_data = _runlist_data
4483+ elif os.path.exists(self.runlist_file):
4484+ runlist_data = parse_runlist_file(self.runlist_file)
4485+ else:
4486+ runlist_data = []
4487+
4488+ for test in runlist_data:
4489+ name = test['test']
4490+ test_path = os.path.join(self.name, name)
4491+
4492+ if includes is not None and name not in includes:
4493+ continue
4494+
4495+ if excludes is not None and name in excludes:
4496+ continue
4497+
4498+ command = test.get('command')
4499+
4500+ tc = TestCase(name=name, path=test_path,
4501+ command=command, timeout=self.timeout,
4502+ result=self.result,
4503+ _save_state_callback=self.save_state_callback,
4504+ _reboot_callback=self.reboot_callback)
4505+ if 'overrides' in test:
4506+ tc.process_overrides(test['overrides'])
4507+
4508+ self.tests.append(tc)
4509+
4510+ # restore the old working directory
4511+ os.chdir(old_cwd)
4512+
4513+ def __str__(self):
4514+ return "%s: %s" % (self.control_file, self.runlist_file)
4515+
4516+ def set_status(self, status):
4517+ """
4518+ Set the status for the test suite and call the save state callback.
4519+ """
4520+ self.status = status
4521+ self.save_state_callback()
4522+
4523+ def build(self, result):
4524+ """
4525+ Run build, but only if we haven't started a run yet.
4526+ """
4527+ if self.status == 'NOTRUN':
4528+ self.set_status('BUILD')
4529+ result.add_result(run_cmd(self.build_cmd, cwd=self.name))
4530+
4531+ def setup(self, result):
4532+ """
4533+ Run ts_setup, but only if build() has just passed.
4534+ """
4535+ if self.status == 'BUILD' and result.status == 'PASS':
4536+ self.set_status('SETUP')
4537+ result.add_result(run_cmd(self.ts_setup, cwd=self.name))
4538+
4539+ def cleanup(self, result):
4540+ """
4541+ Run ts_cleanup after a run.
4542+ """
4543+ if self.status == 'INPROGRESS':
4544+ self.set_status('CLEANUP')
4545+ result.add_result(run_cmd(self.ts_cleanup, cwd=self.name))
4546+
4547+
4548+ def run(self):
4549+ """
4550+ Run the test cases; including any build, setup, and cleanup commands.
4551+ """
4552+
4553+ # Return value to indicate whether processing of a Runner should
4554+ # continue. This is to avoid a shutdown race on reboot cases.
4555+ keep_going = True
4556+
4557+ # Work from the testsuite directory
4558+ os.chdir(self.path)
4559+
4560+ result = self.result
4561+
4562+ self.build(result)
4563+ self.setup(result)
4564+
4565+ # Always enter this loop since test.run() will handle checking if
4566+ # the test has already been run.
4567+ self.status = "INPROGRESS"
4568+ if result.status == 'PASS':
4569+ for test in self.tests:
4570+ keep_going = test.run(self.result)
4571+
4572+ if test.run_status == 'PASS':
4573+ self.passes += 1
4574+ elif test.run_status == 'FAIL':
4575+ self.failures += 1
4576+ elif test.run_status == 'ERROR':
4577+ self.errors += 1
4578+
4579+ if not keep_going:
4580+ return keep_going
4581+
4582+ self.cleanup(result)
4583+
4584+ if self.status == 'CLEANUP':
4585+ result.result()
4586+ self.set_status("DONE")
4587+
4588+ return keep_going
4589+
4590+ def add_test(self, tests):
4591+ """
4592+ Add a single test or list of tests to this suite.
4593+ """
4594+ if isinstance(tests, TestCase):
4595+ self.tests.append(tests)
4596+ else:
4597+ try:
4598+ for test in tests:
4599+ self.add_test(test)
4600+ except TypeError:
4601+ pass
4602+
4603+ def count_tests(self):
4604+ return len(self.tests)
4605+
4606+ def load_state(self, state):
4607+ """
4608+ Restore our state from the supplied dictionary.
4609+
4610+ Requires that the fieldnames in the dictionary match the class
4611+ properties.
4612+ """
4613+ # XXX: Should this be done explicitly?
4614+ self.__dict__.update(state)
4615+
4616+ self.tests = []
4617+ for state_test in state['tests']:
4618+ test = TestCase(name=state_test['name'], path=state_test['path'],
4619+ result=self.result,
4620+ _save_state_callback=self.save_state_callback,
4621+ _reboot_callback=self.reboot_callback)
4622+ test.load_state(state_test)
4623+ self.tests.append(test)
4624+
4625+ def save_state(self):
4626+ state = {
4627+ 'name': self.name,
4628+ 'status': self.status,
4629+ 'passes': self.passes,
4630+ 'failures': self.failures,
4631+ 'errors': self.errors,
4632+ 'tests': [],
4633+ 'ts_setup': self.ts_setup,
4634+ 'ts_cleanup': self.ts_cleanup,
4635+ 'build_cmd': self.build_cmd,
4636+ 'timeout': self.timeout,
4637+ }
4638+
4639+ for test in self.tests:
4640+ state['tests'].append(test.save_state())
4641+
4642+ return state
4643+
4644+ def is_done(self):
4645+ """
4646+ Determine if the suite is done. This might mean that something has
4647+ failed. Used by Runner to determine if the suite needs to be re-run
4648+ on resume.
4649+ """
4650+ return self.status == 'DONE' or self.status == 'CLEANUP'
4651+
4652+ def get_next_test(self):
4653+ """
4654+ Return the next test to be run.
4655+
4656+ Mainly used for debugging.
4657+ """
4658+
4659+ test = None
4660+
4661+ for t in self.tests:
4662+ if not t.is_done():
4663+ test = t
4664+ break
4665+
4666+ return test
4667
4668=== modified file 'uath/exceptions.py'
4669--- uath/exceptions.py 2012-04-05 15:39:41 +0000
4670+++ uath/exceptions.py 2012-05-09 00:33:18 +0000
4671@@ -6,9 +6,3 @@
4672 """
4673 def __init__(self, msg):
4674 self.msg = msg
4675-
4676-class BadDir(Exception):
4677- pass
4678-
4679-class MissingFile(Exception):
4680- pass
4681
4682=== modified file 'uath/provisioning/catalog/__init__.py'
4683--- uath/provisioning/catalog/__init__.py 2012-04-06 15:23:15 +0000
4684+++ uath/provisioning/catalog/__init__.py 2012-05-09 00:33:18 +0000
4685@@ -1,3 +1,3 @@
4686+from .exceptions import *
4687 from .base import *
4688-from .exceptions import *
4689 from .sqlite import *
4690
4691=== modified file 'uath/provisioning/catalog/base.py'
4692--- uath/provisioning/catalog/base.py 2012-04-06 21:35:03 +0000
4693+++ uath/provisioning/catalog/base.py 2012-05-09 00:33:18 +0000
4694@@ -1,20 +1,29 @@
4695 #!/usr/bin/python
4696
4697 import os
4698-from .exceptions import *
4699+from uath.provisioning.catalog.exceptions import *
4700
4701 class Catalog(object):
4702 """
4703- Provide a generic class for an arbitrary catlog of machines.
4704+ Provide a generic class for an arbitrary catalog of machines.
4705
4706- Raise exceptions for most methods.
4707+ Raise exceptions for most methods, since they should be defined by
4708+ subclasses.
4709+ Except for special read-only cases, subclasses should provide request().
4710+ Subclasses will generally also need to provide delete() for cleanup
4711+ purposes.
4712+ All other methods (read, write, release, destroy) are optional.
4713 """
4714- def __init__(self, uniqueid, lockfile = os.path.expanduser('~/.uath-catalog')):
4715+ def __init__(self, uniqueid, lockfile=os.path.expanduser('~/.uath-catalog')):
4716 """
4717- To avoid provisioning machines to multiple requestors simultaneously, Catalog implements a lock file.
4718- Each catalog type should provide a unique id, which is combined with the class name to generate a catalog identifier.
4719- This identifier is stored in the lock file and checked on class instantiation to ensure the same catalog is running.
4720- Subclasses can circumvent this by not calling the superclass __init__ function if necessary.
4721+ To avoid provisioning machines to multiple requestors simultaneously,
4722+ Catalog implements a lock file.
4723+ Each catalog type should provide a unique id, which is combined with
4724+ the class name to generate a catalog identifier.
4725+ This identifier is stored in the lock file and checked on class
4726+ instantiation to ensure the same catalog is running.
4727+ Subclasses can circumvent this by not calling the superclass __init__
4728+ function if necessary.
4729 """
4730 catalog = self.__class__.__name__ + str(uniqueid)
4731 self.lockfile = lockfile
4732@@ -31,22 +40,50 @@
4733
4734 def delete(self):
4735 """
4736- Deleting a catalog removes the lock file so another catalog can be created.
4737+ Delete this catalog and remove the lockfile to facilitate use of
4738+ another one.
4739+ Subclasses can call this via super to delete the lockfile, or
4740+ implement lockfile deletion in their delete() method.
4741 """
4742 os.unlink(self.lockfile)
4743 del self
4744
4745 def read(self):
4746- raise UATHProvisioningCatalogException('Method not defined for this catalog')
4747+ """
4748+ Read information about the catalog from any available sources.
4749+ Primarily intended to force a synchronization of new external data if
4750+ any exists.
4751+ """
4752+ return False
4753
4754 def write(self):
4755- raise UATHProvisioningCatalogException('Method not defined for this catalog')
4756+ """
4757+ Force a commit all current catalog data in memory to internal or
4758+ external storage.
4759+ """
4760+ return False
4761
4762- def provision(self):
4763+ def request(self, machinetype=None, *args, **kw):
4764+ """
4765+ Return a Machine object that can be provisioned and used to run tests.
4766+ May interpret arguments and call the appropriate constructor with the
4767+ appropriate arguments, or may take a Machine class as an argument and
4768+ send all arguments to that constructor.
4769+ """
4770 raise UATHProvisioningCatalogException('Method not defined for this catalog')
4771
4772 def release(self):
4773- raise UATHProvisioningCatalogException('Method not defined for this catalog')
4774+ """
4775+ Indicate that a machine is not now in use for tests, but may still have
4776+ a working installed system, potentially suitable for further use.
4777+ """
4778+ return False
4779
4780 def destroy(self):
4781- raise UATHProvisioningCatalogException('Method not defined for this catalog')
4782+ """
4783+ Remove a machine from a catalog so that no further use of it will be
4784+ requested or expected.
4785+ Current implementations do not call machine.destroy(), requiring that
4786+ do be done separately.
4787+ """
4788+ return False
4789
4790=== modified file 'uath/provisioning/catalog/sqlite.py'
4791--- uath/provisioning/catalog/sqlite.py 2012-04-09 20:29:51 +0000
4792+++ uath/provisioning/catalog/sqlite.py 2012-05-09 00:33:18 +0000
4793@@ -2,15 +2,15 @@
4794
4795 import sqlite3
4796 import os
4797-from .exceptions import *
4798-from .base import Catalog
4799+from uath.provisioning.catalog.exceptions import *
4800+from uath.provisioning.catalog.base import Catalog
4801 from uath.provisioning.vm import VMToolsKVM
4802
4803 class SQLiteCatalog(Catalog):
4804 """
4805 Base class for SQLite catalog, opens a database connection and sets up a cursor.
4806 """
4807- def __init__(self, db = os.path.expanduser('~/.uath-sqlite-catalog'), *args, **kw):
4808+ def __init__(self, db=os.path.expanduser('~/.uath-sqlite-catalog'), *args, **kw):
4809 super(SQLiteCatalog, self).__init__(uniqueid = db, *args, **kw)
4810 self.db = db
4811 self.connection = sqlite3.connect(self.db)
4812@@ -31,9 +31,10 @@
4813 Initialize simple database.
4814 """
4815 super(TinySQLiteCatalog, self).__init__(*args, **kw)
4816+ self.connection.isolation_level = None
4817 self.connection.execute('CREATE TABLE IF NOT EXISTS machines(machineid INTEGER PRIMARY KEY, state TEXT)')
4818
4819- def request(self, machinetype = VMToolsKVM, *args, **kw):
4820+ def request(self, machinetype=VMToolsKVM, *args, **kw):
4821 """
4822 Takes a Machine class as machinetype, and passes the newly generated machineid along with all other arguments to that class's constructor, returning the resulting object.
4823 """
4824@@ -46,10 +47,16 @@
4825 """
4826 Updates the database to indicate the machine is available.
4827 """
4828- return self.connection.execute("UPDATE machines SET state='available' WHERE machineid=?", [machineid])
4829+ if self.connection.execute("UPDATE machines SET state='available' WHERE machineid=?", [machineid]):
4830+ return True
4831+ else:
4832+ return False
4833
4834 def destroy(self, machineid):
4835 """
4836 Updates the database to indicate the machine is destroyed, but does not destroy the machine.
4837 """
4838- return self.connection.execute("UPDATE machines SET state='destroyed' WHERE machineid=?", [machineid])
4839+ if self.connection.execute("UPDATE machines SET state='destroyed' WHERE machineid=?", [machineid]):
4840+ return True
4841+ else:
4842+ return False
4843
4844=== modified file 'uath/provisioning/provisioning.py'
4845--- uath/provisioning/provisioning.py 2012-04-05 17:14:32 +0000
4846+++ uath/provisioning/provisioning.py 2012-05-09 00:33:18 +0000
4847@@ -1,18 +1,52 @@
4848 #!/usr/bin/python
4849
4850-from .exceptions import *
4851+import logging
4852+import logging.handlers
4853+import os.path
4854+import socket
4855+import sys
4856+
4857+import apt.cache
4858+
4859+from uath.provisioning.exceptions import *
4860
4861 class Machine(object):
4862 """
4863 Provide a generic class to provision an arbitrary machine.
4864
4865- Raise exceptions for most methods.
4866+ Raise exceptions for most methods, since subclasses should provide them.
4867+ A fully implemented subclass will provide all public methods except read,
4868+ I.E.:
4869+ provisioncheck, activecheck, getclientdeb, installclient
4870+ (all these can be used from Machine or replaced.)
4871+ destroy, stop, uploadfiles, downloadfiles, run
4872+ (these must be implemented separately.)
4873 """
4874- def __init__(self, arch = None, initrd = None, installtype = None, iso = None, kernel = None, machineid = None, name = None, new = False, preseed = None, series = None, template = None):
4875+ def __init__(self, arch=None, debug=False, image=None, initrd=None,
4876+ installtype=None, kernel=None, machineid=None, name=None,
4877+ new=False, preseed=None, series=None, template=None):
4878+ """
4879+ Initialize the object representing the machine.
4880+ One of these groups of arguments should be included to specify the
4881+ provisioning method:
4882+ series, installtype, arch: Download an ISO from the mirrors and use
4883+ it to install the machine.
4884+ image, kernel, initrd, preseed: Install the machine from a
4885+ specified image/ISO file, with an optional kernel, initrd, and
4886+ preseed.
4887+ template: Clone the machine from a template or existing machine.
4888+ name: Request a specific machine. Combine with other groups to
4889+ reinstall a specific machine.
4890+ Other arguments:
4891+ debug: Enable debug logging.
4892+ new: Request a new machine (or a reinstall if a specific machine
4893+ was requested.)
4894+ """
4895 self.arch = arch
4896+ self.debug = debug
4897+ self.image = image
4898 self.initrd = initrd
4899 self.installtype = installtype
4900- self.iso = iso
4901 self.kernel = kernel
4902 self.machineid = machineid
4903 self.name = name
4904@@ -22,36 +56,176 @@
4905 self.template = template
4906 self.provisioned = False
4907 self.active = False
4908+ self.logger = logging.getLogger('uath.provisioning' + self.name)
4909+ self._loggersetup()
4910+ self.logger.debug('Machine init finished')
4911+
4912+ def _loggersetup(self):
4913+ """
4914+ Initialize the logging for the machine.
4915+ Subclasses can override or supplement to customize logging.
4916+ """
4917+ self.consolehandler = logging.StreamHandler(stream = sys.stderr)
4918+ self.consolehandler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
4919+ self.consolehandler.setLevel(logging.WARNING)
4920+ self.filehandler = logging.handlers.WatchedFileHandler('/var/log/uath/' + socket.gethostname() + '.log')
4921+ self.filehandler.setFormatter(logging.Formatter('%(asctime)s ' + self.name + ' %(levelname)s: %(message)s'))
4922+ self.filehandler.setLevel(logging.DEBUG)
4923+ self.logger.addHandler(self.consolehandler)
4924+ self.logger.addHandler(self.filehandler)
4925+ if self.debug:
4926+ self.logger.setLevel(logging.DEBUG)
4927+ else:
4928+ self.logger.setLevel(logging.INFO)
4929
4930 def provisioncheck(self):
4931 """
4932- Make sure the machine is provisioned.
4933+ Check if the machine is provisioned, provision it if necessary, and
4934+ raise an exception if it cannot be provisioned.
4935 """
4936+ self.logger.debug('Checking if machine is provisioned')
4937 if not self.provisioned:
4938- self.provision()
4939+ self._provision()
4940
4941 def activecheck(self):
4942 """
4943- Make sure the machine is running.
4944+ Check if the machine is running and able to accept comamnds, start it
4945+ if necessary, and raise an exception if it cannot be started.
4946 """
4947+ self.logger.debug('Checking if machine is active')
4948 if not self.active:
4949- self.start()
4950-
4951- def provision(self):
4952+ self._start()
4953+
4954+ def getclientdeb(self):
4955+ """
4956+ Return the path of the .deb file for the
4957+ ubuntu-automation-test-harness-client installed as part of the main
4958+ package.
4959+ """
4960+ debpath = os.path.normpath(os.path.join('/usr','share','uath','ubuntu-automation-test-harness-client_' + apt.cache.Cache()['ubuntu-automation-test-harness'].installedVersion + '_all.deb'))
4961+ self.logger.debug('Client deb path is ' + debpath)
4962+ return debpath
4963+
4964+ def installclient(self):
4965+ """
4966+ Install the ubuntu-automation-test-harness-client package on the
4967+ machine, and raise an exception if this fails.
4968+ """
4969+ self.logger.info('Installing client deb on machine')
4970+ tmppath = os.path.normpath('/tmp')
4971+ if self.uploadfiles([self.getclientdeb()], tmppath):
4972+ if self.run('DEBIAN_FRONTEND=noninteractive dpkg -i ' + os.path.join(tmppath, os.path.split(self.getclientdeb())[1]) + ' || apt-get -y -f install', root = True) == 0:
4973+ return True
4974+ else:
4975+ self.logger.error('Failed to install and configure ' + self.getclientdeb())
4976+ raise UATHProvisioningException('Failed to install and configure ' + self.getclientdeb())
4977+ else:
4978+ self.logger.error('Failed to copy ' + self.getclientdeb() + ' to machine')
4979+ raise UATHProvisioningException('Failed to copy ' + self.getclientdeb() + ' to machine')
4980+
4981+ def _provision(self):
4982+ """
4983+ Ready the machine for use, and set provisioned to True.
4984+ If new=True or the machine is not installed, this should install the
4985+ machine, possibly by calling _create().
4986+ If an existing machine is requested, this should make the machine
4987+ available, possibly by caling _load().
4988+ Should generally not be called directly outside of the class;
4989+ provisioncheck() or activecheck() should be used.
4990+ """
4991 raise UATHProvisioningException('Method not defined for this machine type')
4992
4993- def create(self):
4994+ def _create(self):
4995+ """
4996+ Install the system on the machine, and return True on success.
4997+ Can take arguments if provided by provisioncheck(), or can read
4998+ internal variables populated by __init__().
4999+ Should generally not be called directly outside of the class;
5000+ provisioncheck() or activecheck() should be used.
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: