Merge lp:~nuclearbob/utah/exception-overhaul into lp:utah

Proposed by Max Brustkern
Status: Merged
Merged at revision: 838
Proposed branch: lp:~nuclearbob/utah/exception-overhaul
Merge into: lp:utah
Diff against target: 1192 lines (+419/-202)
17 files modified
debian/changelog (+1/-0)
tests/test_rsyslog.py (+2/-1)
utah/client/exceptions.py (+1/-1)
utah/client/runner.py (+74/-38)
utah/client/testcase.py (+3/-1)
utah/client/testsuite.py (+29/-16)
utah/config.py (+29/-21)
utah/exceptions.py (+1/-0)
utah/iso.py (+85/-45)
utah/parser.py (+10/-1)
utah/provisioning/baremetal/bamboofeeder.py (+25/-11)
utah/provisioning/baremetal/cobbler.py (+15/-11)
utah/provisioning/inventory/sqlite.py (+2/-2)
utah/provisioning/provisioning.py (+27/-10)
utah/provisioning/ssh.py (+65/-26)
utah/run.py (+37/-11)
utah/timeout.py (+13/-7)
To merge this branch: bzr merge lp:~nuclearbob/utah/exception-overhaul
Reviewer Review Type Date Requested Status
Max Brustkern (community) Needs Resubmitting
Javier Collado (community) Approve
Review via email: mp+153469@code.launchpad.net

Description of the change

I've made a lot of changes to exception handling to attempt to address a number of issues raised in the code review.

To post a comment you must log in.
833. By Javier Collado

Merged changes to add a timeout parameter to the retry function

Instead of calling time.sleep in every function that is called through retry,
now is retry itself that takes care of that using a new parameter.

Source branch: lp:~javier.collado/utah/retry-timeout

834. By Javier Collado

Merged changes to add reboot support (LP: #1086450)

Source branch: lp:~javier.collado/utah/reboot-support
Based on: lp:~nuclearbob/utah/reboot-support

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

It looks nice.

The only issue I've seen that was partially fixed in another merge proposal
is that this:

try:
    self.external = kw.pop('external')
except KeyError:
    self.external = False

can be replaced with this:

self.external = kw.pop('external', False)

835. By Max Brustkern

Rebasing on dev branch

836. By Max Brustkern

Resolved merge conflicts

Revision history for this message
Max Brustkern (nuclearbob) wrote :

Merge conflicts should be fixed. Javier's suggestion should be addressed. I also went around and added handling for most of the rest of the subprocess calls. I thought about trying to find every OS call, but I think those generally give reasonably clear error on their own. If we think it would be beneficial to wrap those, I can.

review: Needs Resubmitting
837. By Max Brustkern

Overhauled exception handling based on code review

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

I've tested the changes running the `pass.run` runlist with a desktop amd64
image and everything worked fine.

Anyway, I'd like to make a couple of comments:

- I don't think we should create a new version (0.9ubuntu2) in the changelog.
  Since the old one (0.9ubuntu1) is still unreleased (i.e. not available in the
  stable PPA), what I think we should do is keep on adding entries to the same
  version.

- I'm confused about the commit messages in revs. 835 and 836. If you run `bzr
  rebase /path/to/dev` what you get is all the revisions in your branch
  replayed on top of the new version of the dev branch, that is, including all
  changes that were added since you started working on your own branch. Once the
  rebasing is done, there are not conflicts because otherwise the rebasing
  doesn't succeed, so no merging is needed.

Revision history for this message
Max Brustkern (nuclearbob) wrote :

I'll decrement the version. I may have done the rebase incorrectly. I tried running the rebase command and then pushing my local branch to launchpad, and it said that the branches had diverged, so I merged my launchpad branch into my local branch, which generated conflicts. Do I need to just overwrite the launchpad branch when doing that in the future?

838. By Max Brustkern

Edited changelod

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

Yes, after the rebase you need to overwrite the launchpad branch.

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

I think everything looks nice. Thanks.

review: Approve
839. By Max Brustkern

Currently, unpacking the initrd sometimes exits with status 512 even if it works. We'll want to address this later, and already have a plan to do so, but for now, we'll just log that so things continue to work

Revision history for this message
Max Brustkern (nuclearbob) wrote :

I ran into problems testing this; the pipe to unpack the initrd was exiting with status 512 for a gzipped initrd, even when things unpacked fine. Since we already have a plan to overhaul the use of pipes, I changed the exception to a log message there, and I'm retesting now.

840. By Max Brustkern

Added initrd exception handling to BambooFeederMachine

841. By Max Brustkern

Added handling on IOError in NFS-editing pipe

Revision history for this message
Max Brustkern (nuclearbob) wrote :

I fixed the issue I had in testing and added handling for possible pipe exceptions in other places. I think this should be good to go if nobody has further objections.

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

> I ran into problems testing this; the pipe to unpack the initrd was exiting
> with status 512 for a gzipped initrd, even when things unpacked fine. Since
> we already have a plan to overhaul the use of pipes, I changed the exception
> to a log message there, and I'm retesting now.

It looks like your patch ignores all non-zero codes. That seems a little heavy handed - should we just ensure the exit code is 0 or 512?

Revision history for this message
Max Brustkern (nuclearbob) wrote :

Since we're planning to get rid of pipes soon anyway, and we've been ignoring all codes up to this point since we started using pipes, I think just logging any non-zero code is okay. If we want to track other codes, we should check all currently supported images on a couple of different systems and see what they return.

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

On 03/18/2013 10:26 AM, Max Brustkern wrote:
> Since we're planning to get rid of pipes soon anyway, and we've been ignoring all codes up to this point since we started using pipes, I think just logging any non-zero code is okay. If we want to track other codes, we should check all currently supported images on a couple of different systems and see what they return.
>
Good point - your approach limits the chance of a regression.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2013-03-01 14:18:10 +0000
3+++ debian/changelog 2013-03-18 14:45:38 +0000
4@@ -12,6 +12,7 @@
5
6 [ Max Brustkern ]
7 * Fixed ProvisionedMachine template support (LP: #1147306)
8+ * Overhauled exception handling based on code review
9
10
11 -- Max Brustkern <max@canonical.com> Thu, 07 Mar 2013 17:11:12 -0500
12
13=== modified file 'tests/test_rsyslog.py'
14--- tests/test_rsyslog.py 2013-03-05 21:09:24 +0000
15+++ tests/test_rsyslog.py 2013-03-18 14:45:38 +0000
16@@ -202,7 +202,8 @@
17 with tempfile.NamedTemporaryFile() as f:
18 r = RSyslog("utah-test", '/tmp', f.name)
19 threading.Thread(
20- target=self.file_producer, args=(f, messages, truncate)).start()
21+ target=self.file_producer,
22+ args=(f, messages, truncate)).start()
23 r.wait_for_booted(steps)
24
25 def test_usefile(self):
26
27=== modified file 'utah/client/exceptions.py'
28--- utah/client/exceptions.py 2012-12-03 14:02:18 +0000
29+++ utah/client/exceptions.py 2013-03-18 14:45:38 +0000
30@@ -27,7 +27,7 @@
31 class BadDir(UTAHClientError):
32 """
33 Raised when some test directory isn't found
34- or and error happens when trying to change to it
35+ or an error happens when trying to change to it
36 """
37
38
39
40=== modified file 'utah/client/runner.py'
41--- utah/client/runner.py 2013-03-15 13:31:22 +0000
42+++ utah/client/runner.py 2013-03-18 14:45:38 +0000
43@@ -13,41 +13,38 @@
44 # You should have received a copy of the GNU General Public License along
45 # with this program. If not, see <http://www.gnu.org/licenses/>.
46
47+
48 import datetime
49+import jsonschema
50+import os
51+import shutil
52+import stat
53 import subprocess
54+import urllib
55
56+from utah.client import exceptions
57+from utah.client.battery import battery
58 from utah.client.common import (
59+ BzrHandler,
60 CMD_TC_REBOOT,
61 DATE_FORMAT,
62+ DEFAULT_STATE_FILE,
63 DEFAULT_TSLIST,
64 DefaultValidator,
65+ DevHandler,
66+ GitHandler,
67 make_result,
68+ MASTER_RUNLIST,
69+ parse_yaml_file,
70+ ReturnCodes,
71+ UTAH_DIR,
72 )
73 from utah.client.result import Result
74+from utah.client.state_agent import StateAgentYAML
75 from utah.client.testsuite import TestSuite
76-from utah.client.state_agent import StateAgentYAML
77-from utah.client import exceptions
78 from utah.exceptions import UTAHException
79+from utah.retry import retry
80 from utah.timeout import timeout
81-from utah.retry import retry
82-from utah.client.battery import battery
83-
84-import os
85-import shutil
86-import stat
87-import urllib
88-import jsonschema
89-
90-from utah.client.common import (
91- MASTER_RUNLIST,
92- UTAH_DIR,
93- DEFAULT_STATE_FILE,
94- ReturnCodes,
95- parse_yaml_file,
96- BzrHandler,
97- DevHandler,
98- GitHandler,
99-)
100
101 RC_LOCAL = '/etc/rc.local'
102 RC_LOCAL_BACKUP = '%s-utah.bak' % RC_LOCAL
103@@ -167,11 +164,17 @@
104
105 # create the testsuite directory
106 if not os.path.exists(self.testsuitedir):
107- os.mkdir(self.testsuitedir)
108+ try:
109+ os.mkdir(self.testsuitedir)
110+ except OSError as err:
111+ raise exceptions.BadDir(err)
112
113 # If no runlist is supplied look for one in the testdir
114 if runlist is None:
115 runlist = os.path.join(testdir, 'master.run')
116+ if not os.path.isfile(runlist):
117+ raise exceptions.MissingFile(
118+ 'Runlist {} does not exist'.format(runlist))
119
120 self.master_runlist = runlist
121 self.suites = []
122@@ -222,8 +225,9 @@
123 try:
124 os.rename(RC_LOCAL, RC_LOCAL_BACKUP)
125 except OSError as e:
126- if e.errno in [13]:
127- pass
128+ if e.errno != 13:
129+ raise exceptions.UTAHClientError(
130+ 'Failed to backup rc.local: {}'.format(e))
131
132 def reset_rc_local(self):
133 """
134@@ -239,8 +243,9 @@
135 elif os.path.exists(RC_LOCAL):
136 os.remove(RC_LOCAL)
137 except OSError as e:
138- if e.errno in [13]:
139- pass
140+ if e.errno != 13:
141+ raise exceptions.UTAHClientError(
142+ 'Failed to backup rc.local: {}'.format(e))
143
144 def setup_rc_local(self, rc_local=RC_LOCAL, runlist=None):
145 """
146@@ -250,11 +255,14 @@
147
148 self.rc_local_content = rc_local_content
149
150- fp = open(rc_local, 'w')
151- fp.write(self.rc_local_content.format(runlist=runlist))
152- fp.close()
153-
154- os.chmod(rc_local, stat.S_IRWXU | stat.S_IROTH | stat.S_IRGRP)
155+ try:
156+ with open(rc_local, 'w') as fp:
157+ fp.write(self.rc_local_content.format(runlist=runlist))
158+ fp.close()
159+ os.fchmod(fp, stat.S_IRWXU | stat.S_IROTH | stat.S_IRGRP)
160+ except (IOError, OSError) as err:
161+ raise exceptions.UTAHClientError(
162+ 'Error setting up rc.local: {}'.format(err))
163
164 def process_results(self):
165 # Add stats to the results
166@@ -281,7 +289,10 @@
167 self.status = "RUN"
168 for suite in self.suites:
169 # Start in self.testdir.
170- os.chdir(self.testsuitedir)
171+ try:
172+ os.chdir(self.testsuitedir)
173+ except OSError as err:
174+ raise exceptions.BadDir(err)
175
176 if not suite.is_done():
177 keep_going = suite.run()
178@@ -349,7 +360,12 @@
179 if (os.path.exists(
180 self.master_runlist)
181 and self.master_runlist != self.backup_runlist):
182- shutil.copyfile(self.master_runlist, self.backup_runlist)
183+ try:
184+ shutil.copyfile(self.master_runlist, self.backup_runlist)
185+ except OSError as err:
186+ raise exceptions.UTAHClientError(
187+ 'Failed to backup master runlist from {} to {}: {}'
188+ .format(self.master_runlist, self.backup_runlist, err))
189
190 state = {
191 'master_runlist': self.backup_runlist,
192@@ -397,6 +413,10 @@
193 local_filename = urllib.urlretrieve(runlist)[0]
194 except IOError:
195 raise exceptions.MissingFile(runlist)
196+ except urllib.ContentTooShortError as err:
197+ raise exceptions.MissingFile(
198+ 'Error when downloading {} (probably interrupted): {}'
199+ .format(runlist, err))
200
201 data = parse_yaml_file(local_filename)
202 validator = DefaultValidator()
203@@ -440,7 +460,10 @@
204 self.process_master_runlist(suite['include'])
205 continue
206
207- os.chdir(orig_dir)
208+ try:
209+ os.chdir(orig_dir)
210+ except OSError as err:
211+ raise exceptions.BadDir(err)
212 fetch_method = suite['fetch_method']
213 fetch_location = suite['fetch_location']
214
215@@ -466,7 +489,8 @@
216 suite_runlist = suite.get('runlist', DEFAULT_TSLIST)
217
218 if name in seen:
219- raise Exception("%s duplicated in runlist" % name)
220+ raise exceptions.BadMasterRunlist(
221+ "{} duplicated in runlist".format(name))
222
223 # Fetch the testsuite. On resume don't remove the testsuite
224 # directory.
225@@ -475,9 +499,17 @@
226 # except on failures where it's easier
227 # to find troubleshoot permission problems
228 absolute_name = os.path.abspath(name)
229- shutil.rmtree(absolute_name)
230+ try:
231+ shutil.rmtree(absolute_name)
232+ except OSError as err:
233+ raise exceptions.UTAHClientError(
234+ 'Error removing the testsuite directory {}: {}'
235+ .format(absolute_name, err))
236 if not os.path.exists(name):
237- os.mkdir(name)
238+ try:
239+ os.mkdir(name)
240+ except OSError as err:
241+ raise exceptions.BadDir(err)
242
243 def vcs_get_retriable():
244 result = vcs_handler.get(directory=name)
245@@ -572,7 +604,11 @@
246 self.backup_rc_local()
247 self.setup_rc_local(runlist=self.backup_runlist)
248
249- subprocess.call(['shutdown', '-r', 'now'])
250+ try:
251+ subprocess.call(['shutdown', '-r', 'now'])
252+ except OSError as err:
253+ raise exceptions.UTAHClientError('Failed to reboot: {}'
254+ .format(err))
255
256 # End of execution
257
258
259=== modified file 'utah/client/testcase.py'
260--- utah/client/testcase.py 2013-02-11 19:56:06 +0000
261+++ utah/client/testcase.py 2013-03-18 14:45:38 +0000
262@@ -16,7 +16,10 @@
263 """
264 Testcase specific code.
265 """
266+
267+
268 import jsonschema
269+import os
270
271 from utah.client.common import (
272 run_cmd,
273@@ -125,7 +128,6 @@
274 self.save_state_callback = _save_state_callback
275
276 control_data = None
277- import os
278 if _control_data is None:
279 if os.path.exists(self.filename):
280 try:
281
282=== modified file 'utah/client/testsuite.py'
283--- utah/client/testsuite.py 2013-02-11 21:47:33 +0000
284+++ utah/client/testsuite.py 2013-03-18 14:45:38 +0000
285@@ -21,12 +21,18 @@
286 import os
287 import yaml
288
289-from .common import run_cmd, parse_control_file
290-from .common import DEFAULT_TSLIST, DEFAULT_TSCONTROL
291-from .common import CMD_TS_BUILD, CMD_TS_SETUP, CMD_TS_CLEANUP
292-from .common import do_nothing
293-from .testcase import TestCase
294-from .exceptions import MissingFile, ValidationError, YAMLEmptyFile
295+from utah.client import exceptions
296+from utah.client.common import (
297+ CMD_TS_BUILD,
298+ CMD_TS_CLEANUP,
299+ CMD_TS_SETUP,
300+ DEFAULT_TSCONTROL,
301+ DEFAULT_TSLIST,
302+ do_nothing,
303+ parse_control_file,
304+ run_cmd,
305+)
306+from utah.client.testcase import TestCase
307
308
309 def parse_runlist_file(runlist_file):
310@@ -94,12 +100,15 @@
311 self.timeout = timeout
312 self.battery_measurements = battery_measurements
313
314- # work from within the testsuite's directory.
315- # eg. /var/lib/utah/examples
316- if not os.path.exists(self.path):
317- os.mkdir(self.path)
318+ try:
319+ # work from within the testsuite's directory.
320+ # eg. /var/lib/utah/examples
321+ if not os.path.exists(self.path):
322+ os.mkdir(self.path)
323
324- os.chdir(self.path)
325+ os.chdir(self.path)
326+ except OSError as err:
327+ raise exceptions.BadDir(err)
328 # Build a TestSuite from a control file's data.
329 control_file = os.path.join(name, control_file)
330
331@@ -123,12 +132,12 @@
332 try:
333 control_data = parse_control_file(self.control_file,
334 self.CONTROL_SCHEMA)
335- except YAMLEmptyFile:
336+ except exceptions.YAMLEmptyFile:
337 # Skip schema validation for empty file
338 # Schema allows an empty dictionary, but not None
339 control_data = None
340 except jsonschema.ValidationError as exception:
341- raise ValidationError(
342+ raise exceptions.ValidationError(
343 '{!r} test suite control file invalid: {!r}\n1'
344 'Detailed information: {}'
345 .format(self.name, self.control_file, exception))
346@@ -146,12 +155,13 @@
347 try:
348 runlist_data = parse_runlist_file(self.runlist_file)
349 except jsonschema.ValidationError as exception:
350- raise ValidationError(
351+ raise exceptions.ValidationError(
352 '{!r} test suite runlist invalid: {!r}\n'
353 'Detailed information: {}'
354 .format(self.name, self.runlist_file, exception))
355 else:
356- raise MissingFile('File not found: {}'.format(self.runlist_file))
357+ raise exceptions.MissingFile(
358+ 'File not found: {}'.format(self.runlist_file))
359
360 for test in runlist_data:
361 name = test['test']
362@@ -243,7 +253,10 @@
363 keep_going = True
364
365 # Work from the testsuite directory
366- os.chdir(self.path)
367+ try:
368+ os.chdir(self.path)
369+ except OSError as err:
370+ raise exceptions.BadDir(err)
371
372 result = self.result
373
374
375=== modified file 'utah/config.py'
376--- utah/config.py 2013-03-15 13:31:22 +0000
377+++ utah/config.py 2013-03-18 14:45:38 +0000
378@@ -42,8 +42,12 @@
379 """
380 dns = os.path.join('/', 'etc', 'utah', 'dns')
381 if os.path.isfile(dns):
382- return (subprocess.Popen([dns], stdout=subprocess.PIPE)
383- .communicate()[0].strip())
384+ try:
385+ process = subprocess.Popen([dns], stdout=subprocess.PIPE)
386+ output = process.communicate()[0].strip()
387+ return output
388+ except OSError:
389+ return 'virbr0'
390 else:
391 return 'virbr0'
392
393@@ -166,7 +170,8 @@
394 'pattern': [
395 '.*ubiquity reboot',
396 '.*log-output -t ubiquity umount /target$',
397- '.*finish-install.d/94save-logs', # needed for server bootspeed
398+ # needed for server bootspeed
399+ '.*finish-install.d/94save-logs',
400 '.*finish-install: umount',
401 '.*rsyslogd:.*exiting on signal 15.',
402 ],
403@@ -237,25 +242,28 @@
404 # http://stackoverflow.com/questions/699325/
405 # suppress-output-in-python-calls-to-executables
406 with open(os.devnull, 'w') as fnull:
407- if subprocess.call(['which', 'dpkg-architecture'],
408- stdout=fnull, stderr=fnull) == 0:
409- # If dpkg-architecture is available, default to current host arch
410- LOCALDEFAULTS['arch'] = subprocess.check_output(
411- ['dpkg-architecture', '-qDEB_HOST_ARCH']).strip()
412- else:
413- # Otherwise, default to i386
414- LOCALDEFAULTS['arch'] = 'i386'
415+ # Default to i386
416+ LOCALDEFAULTS['arch'] = 'i386'
417+ try:
418+ if subprocess.call(['which', 'dpkg-architecture'],
419+ stdout=fnull, stderr=fnull) == 0:
420+ # If dpkg-architecture is available, default to current host arch
421+ LOCALDEFAULTS['arch'] = subprocess.check_output(
422+ ['dpkg-architecture', '-qDEB_HOST_ARCH']).strip()
423+ except OSError:
424+ pass
425
426-# I'm not sure lsb_release is always around
427-with open(os.devnull, 'w') as fnull:
428- if subprocess.call(['which', 'lsb_release'],
429- stdout=fnull, stderr=fnull) == 0:
430- # If lsb_release is available, default to current series
431- LOCALDEFAULTS['series'] = subprocess.check_output(
432- ['lsb_release', '-sc']).strip()
433- else:
434- # Otherwise, default to precise
435- LOCALDEFAULTS['series'] = 'precise'
436+ # Default to precise
437+ LOCALDEFAULTS['series'] = 'precise'
438+ # I'm not sure lsb_release is always around
439+ try:
440+ if subprocess.call(['which', 'lsb_release'],
441+ stdout=fnull, stderr=fnull) == 0:
442+ # If lsb_release is available, default to current series
443+ LOCALDEFAULTS['series'] = subprocess.check_output(
444+ ['lsb_release', '-sc']).strip()
445+ except (OSError, subprocess.CalledProcessError):
446+ pass
447
448 DEFAULTS.update(LOCALDEFAULTS)
449
450
451=== modified file 'utah/exceptions.py'
452--- utah/exceptions.py 2013-03-13 16:29:27 +0000
453+++ utah/exceptions.py 2013-03-18 14:45:38 +0000
454@@ -25,4 +25,5 @@
455 """
456 def __init__(self, *args, **kw):
457 self.retry = kw.pop('retry', False)
458+ self.external = kw.pop('external', False)
459 super(UTAHException, self).__init__(*args, **kw)
460
461=== modified file 'utah/iso.py'
462--- utah/iso.py 2013-01-23 17:17:23 +0000
463+++ utah/iso.py 2013-03-18 14:45:38 +0000
464@@ -32,6 +32,24 @@
465 from utah.exceptions import UTAHException
466
467
468+class UTAHISOException(UTAHException):
469+ pass
470+
471+
472+def get_resource(method, url, *args, **kw):
473+ try:
474+ resource = method(url, *args, **kw)
475+ return resource
476+ except IOError as err:
477+ raise UTAHISOException(
478+ 'IOError when downloading {}: {}'
479+ .format(url, err), external=True)
480+ except urllib.ContentTooShortError as err:
481+ raise UTAHISOException(
482+ 'Error when downloading {} (probably interrupted): {}'
483+ .format(url, err), external=True)
484+
485+
486 class ISO(object):
487 """
488 Provide a simplified method of interfacing with images.
489@@ -64,7 +82,8 @@
490
491 self.percent = 0
492 self.logger.info('Preparing image: ' + path)
493- self.image = urllib.urlretrieve(path, reporthook=self.dldisplay)[0]
494+ self.image = get_resource(urllib.urlretrieve, path,
495+ reporthook=self.dldisplay)[0]
496 self.logger.debug(path + ' is locally available as ' + self.image)
497 self.installtype = self.getinstalltype()
498 if self.installtype == 'mini':
499@@ -178,10 +197,19 @@
500 cmd = ['bsdtar', '-t', '-f', self.image]
501 self.logger.debug('bsdtar list command: ' + ' '.join(cmd))
502 if returnlist:
503- return subprocess.check_output(cmd).strip().split('\n')
504+ try:
505+ proc = subprocess.check_output(cmd).strip().split('\n')
506+ except (OSError, subprocess.CalledProcessError) as err:
507+ raise UTAHISOException('Error listing files in ISO: {}'
508+ .format(err))
509 else:
510- return subprocess.Popen(cmd, stdout=subprocess.PIPE,
511- stderr=subprocess.PIPE).communicate()
512+ try:
513+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
514+ stderr=subprocess.PIPE).communicate()
515+ except OSError as err:
516+ raise UTAHISOException('Error listing files in ISO: {}'
517+ .format(err))
518+ return proc
519
520 def getrealfile(self, filename):
521 """Return a command to safely extract a file from an ISO.
522@@ -198,17 +226,16 @@
523 self.logger.debug('bsdtar list command: ' + ' '.join(cmd))
524 try:
525 output = subprocess.check_output(cmd)
526- except subprocess.CalledProcessError:
527- raise UTAHException('Cannot list {path} in {image}'
528- .format(path=filename, image=self.image))
529+ except (OSError, subprocess.CalledProcessError) as err:
530+ raise UTAHISOException('Cannot list {} in {}: {}'
531+ .format(filename, self.image, err))
532 try:
533 columns = output.splitlines()[0].split()
534 realfile = columns[-1]
535- except (IndexError, subprocess.CalledProcessError):
536- raise UTAHException('Cannot parse bsdtar list output '
537- 'for {path} in {image}: {output}'
538- .format(path=filename, image=self.image,
539- output=output))
540+ except IndexError as err:
541+ raise UTAHISOException('Cannot parse bsdtar list output '
542+ 'for {} in {}: {}'
543+ .format(filename, self.image, output))
544 cmd = ['bsdtar', '-x', '-f', self.image, '-O', realfile]
545 self.logger.debug('bsdtar extract command: ' + ' '.join(cmd))
546 return cmd
547@@ -231,9 +258,9 @@
548 cmd = self.getrealfile(filename)
549 try:
550 stdout = subprocess.check_output(cmd, **kw)
551- except subprocess.CalledProcessError:
552- raise UTAHException('Cannot extract {path} from {image}'
553- .format(path=filename, image=self.image))
554+ except (OSError, subprocess.CalledProcessError) as err:
555+ raise UTAHISOException('Cannot extract {} from {}: {}'
556+ .format(filename, self.image, err))
557
558 if outfile is None:
559 outfile = os.path.join(outdir, filename)
560@@ -260,9 +287,9 @@
561 cmd = self.getrealfile(filename)
562 try:
563 stdout = subprocess.check_output(cmd, **kw)
564- except subprocess.CalledProcessError:
565- raise UTAHException('Cannot extract {path} from {image}'
566- .format(path=filename, image=self.image))
567+ except (OSError, subprocess.CalledProcessError) as err:
568+ raise UTAHISOException('Cannot extract {} from {}: {}'
569+ .format(filename, self.image, err))
570
571 return stdout
572
573@@ -296,26 +323,35 @@
574 self.logger.info('Attempting to retrieve ' + filename)
575 with open(os.devnull, "w") as fnull:
576 # If dlcommand (default dl-ubuntu-test-iso) is available, use it
577- if subprocess.call(['which', config.dlcommand],
578- stdout=fnull, stderr=fnull) == 0:
579- self.logger.debug('Using ' + config.dlcommand)
580- if installtype == 'server':
581- flavor = 'ubuntu-server'
582- else:
583- flavor = 'ubuntu'
584- cmd = [config.dlcommand,
585- '-q',
586- '--flavor=' + flavor,
587- '--release=' + series,
588- '--variant=' + installtype,
589- '--arch=' + arch,
590- '--isoroot=' + config.isodir]
591- self.logger.info('Downloading ISO')
592- self.logger.debug(' '.join(cmd))
593- subprocess.check_call(cmd)
594- path = os.path.join(config.isodir, flavor, filename)
595- if os.path.isfile(path):
596- return path
597+ cmd = ['which', config.dlcommand]
598+ try:
599+ if subprocess.call(cmd,
600+ stdout=fnull, stderr=fnull) == 0:
601+ self.logger.debug('Using ' + config.dlcommand)
602+ if installtype == 'server':
603+ flavor = 'ubuntu-server'
604+ else:
605+ flavor = 'ubuntu'
606+ cmd = [config.dlcommand,
607+ '-q',
608+ '--flavor=' + flavor,
609+ '--release=' + series,
610+ '--variant=' + installtype,
611+ '--arch=' + arch,
612+ '--isoroot=' + config.isodir]
613+ self.logger.info('Downloading ISO')
614+ self.logger.debug(' '.join(cmd))
615+ try:
616+ subprocess.check_call(cmd)
617+ except (OSError, subprocess.CalledProcessError) as err:
618+ raise UTAHISOException(
619+ "Failed to download ISO using '{}': {}"
620+ .format(' '.join(cmd), err))
621+ path = os.path.join(config.isodir, flavor, filename)
622+ if os.path.isfile(path):
623+ return path
624+ except OSError as err:
625+ self.logger.warning('{} failed: {}'.format(cmd, err))
626
627 # If we haven't returned, dlcommand didn't give us an image
628 # We'll need to use the utah image cache in config.isodir
629@@ -339,7 +375,9 @@
630
631 # Scan the MD5SUMS file for our file
632 servermd5 = None
633- for line in urllib.urlopen(remotepath + '/MD5SUMS'):
634+ md5path = '{}/MD5SUMS'.format(remotepath)
635+ md5list = get_resource(urllib.urlopen, md5path)
636+ for line in md5list:
637 if isopattern in line:
638 servermd5, isofile = line.split()
639 isofile = isofile.strip('*')
640@@ -347,8 +385,9 @@
641
642 # If we can't find the ISO, raise an exception
643 if servermd5 is None:
644- raise UTAHException('Specified ISO: ' + filename
645- + ' not found on mirrors.')
646+ raise UTAHISOException(
647+ 'Specified ISO: {} not found on mirrors.'
648+ .format(filename), external=True)
649 self.logger.debug('Server md5 is ' + servermd5)
650
651 # If the ISO exists locally and the md5 matches, use it
652@@ -360,8 +399,8 @@
653 isopath = os.path.join(remotepath, isofile)
654 self.percent = 0
655 self.logger.info('Attempting to download ' + isopath)
656- temppath = urllib.urlretrieve(isopath,
657- reporthook=self.dldisplay)[0]
658+ temppath = get_resource(urllib.urlretrieve(isopath,
659+ reporthook=self.dldisplay))[0]
660
661 # Try to copy it into our cache
662 self.logger.debug('Copying ' + temppath + ' to ' + path)
663@@ -370,8 +409,9 @@
664 if os.path.isfile(path) and servermd5 == self.getmd5(path):
665 return path
666 else:
667- raise UTAHException('Image failed to download after ' +
668- config.dlretries + ' tries')
669+ raise UTAHISOException(
670+ 'Image failed to download after {} tries'
671+ .format(config.dlretries), external=True)
672
673 def kernelpath(self):
674 kernelpath = './install/vmlinuz'
675
676=== modified file 'utah/parser.py'
677--- utah/parser.py 2012-12-06 16:30:34 +0000
678+++ utah/parser.py 2013-03-18 14:45:38 +0000
679@@ -164,7 +164,16 @@
680 data = None
681
682 if logfile.startswith('http'):
683- fp = urllib2.urlopen(logfile)
684+ try:
685+ fp = urllib2.urlopen(logfile)
686+ except IOError as err:
687+ raise ParserError(
688+ 'IOError when downloading {}: {}'
689+ .format(logfile, err))
690+ except urllib2.ContentTooShortError as err:
691+ raise ParserError(
692+ 'Error when downloading {} (probably interrupted): {}'
693+ .format(logfile, err))
694 data = self._parse_stream(fp)
695 else:
696 with open(logfile, 'r') as fp:
697
698=== modified file 'utah/provisioning/baremetal/bamboofeeder.py'
699--- utah/provisioning/baremetal/bamboofeeder.py 2013-03-13 16:36:02 +0000
700+++ utah/provisioning/baremetal/bamboofeeder.py 2013-03-18 14:45:38 +0000
701@@ -95,10 +95,12 @@
702 self.imagedir = os.path.join(self.tmpdir, 'image.d')
703 if not os.path.isdir(self.imagedir):
704 os.makedirs(self.imagedir)
705- self.sector = int(
706- subprocess.check_output(
707- ['file', self.wwwimage]
708- ).strip().split(';')[1].split(',')[3].split()[1])
709+ try:
710+ output = subprocess.check_output(['file', self.wwwimage]).strip()
711+ self.sector = output.split(';')[1].split(',')[3].split()[1]
712+ except (OSError, subprocess.CalledProcessError) as err:
713+ raise UTAHBMProvisioningException('Failed to get image info: {}'
714+ .format(err))
715 self.logger.debug('Image start sector is ' + str(self.sector))
716 self.cleanfunction(self._umountimage)
717 self.logger.debug('Mounting ' + self.wwwimage + ' at ' +
718@@ -126,11 +128,15 @@
719 os.makedirs(os.path.join(self.tmpdir, 'initrd.d'))
720 os.chdir(os.path.join(self.tmpdir, 'initrd.d'))
721 filesize = os.path.getsize(self.initrd)
722- for line in subprocess.check_output(
723- ['mkimage', '-l', self.initrd]).splitlines():
724- if 'Data Size:' in line:
725- datasize = int(line.split()[2])
726- break
727+ try:
728+ for line in subprocess.check_output(
729+ ['mkimage', '-l', self.initrd]).splitlines():
730+ if 'Data Size:' in line:
731+ datasize = int(line.split()[2])
732+ break
733+ except (OSError, subprocess.CalledProcessError) as err:
734+ raise UTAHBMProvisioningException('Failed to get initrd info: {}'
735+ .format(err))
736 headersize = filesize - datasize
737 self.logger.debug('uInitrd header size is ' + str(headersize))
738 pipe = pipes.Template()
739@@ -138,7 +144,12 @@
740 ' 2>/dev/null', 'f-')
741 pipe.append('gunzip', '--')
742 pipe.append('cpio -ivd 2>/dev/null', '-.')
743- pipe.copy(self.initrd, '/dev/null')
744+ exitstatus = pipe.copy(self.initrd, '/dev/null')
745+ if exitstatus != 0:
746+ # Currently this comes up as 512 when things seem fine
747+ # TODO: refactor this and check for codes at all stages
748+ self.logger.debug(
749+ 'Unpacking initrd exited with status {}'.format(exitstatus))
750
751 def _repackinitrd(self):
752 """
753@@ -152,7 +163,10 @@
754 pipe.append('gzip -9fc', '--')
755 initrd = os.path.join(self.tmpdir, 'initrd.gz')
756 self.logger.debug('Repacking compressed initrd')
757- pipe.copy('/dev/null', initrd)
758+ exitstatus = pipe.copy('/dev/null', initrd)
759+ if exitstatus != 0:
760+ raise UTAHBMProvisioningException(
761+ 'Unpacking initrd exited with status: {}'.format(exitstatus))
762 self.logger.debug('Creating uInitrd with mkimage')
763 self._runargs(['mkimage',
764 '-A', 'arm',
765
766=== modified file 'utah/provisioning/baremetal/cobbler.py'
767--- utah/provisioning/baremetal/cobbler.py 2013-03-13 16:36:46 +0000
768+++ utah/provisioning/baremetal/cobbler.py 2013-03-18 14:45:38 +0000
769@@ -84,10 +84,8 @@
770 # (only if we'll be using cobbler for a while longer)
771 machines = self._cobble(['system', 'find'])['output'].splitlines()
772 if self.name not in machines:
773- raise UTAHBMProvisioningException('No machine named ' + self.name
774- + ' exists in cobbler')
775- else:
776- return True
777+ raise UTAHBMProvisioningException(
778+ 'No machine named {} exists in cobbler'.format(self.name))
779
780 def _create(self, checktimeout=config.checktimeout,
781 installtimeout=config.installtimeout):
782@@ -165,8 +163,12 @@
783 pipe = pipes.Template()
784 pipe.append('sudo tee -a ' + str(config.nfsconfigfile) +
785 ' >/dev/null', '-.')
786- pipe.open('/dev/null', 'w').write(self.isodir + ' ' +
787- config.nfsoptions + "\n")
788+ try:
789+ pipe.open('/dev/null', 'w').write(self.isodir + ' ' +
790+ config.nfsoptions + "\n")
791+ except IOError as err:
792+ raise UTAHBMProvisioningException(
793+ 'Failed to setup NFS: {}'.format(err))
794 self.logger.debug('Reloading NFS config')
795 self._runargs(config.nfscommand + ['reload'])
796 self.logger.debug('Adding NFS boot options')
797@@ -191,7 +193,8 @@
798 for arg in self.machineinfo])
799
800 self.restart()
801- self.rsyslog.wait_for_install(config.install_steps, self._disable_netboot)
802+ self.rsyslog.wait_for_install(config.install_steps,
803+ self._disable_netboot)
804 self.rsyslog.wait_for_booted(config.boot_steps)
805
806 if self.installtype == 'desktop':
807@@ -216,11 +219,10 @@
808 Pull out all cobbler commands so that we can later support changing
809 users, remote login, not sudo, etc.
810 """
811- tail_process = \
812- subprocess.Popen(['tail', '-n0', '-f',
813- '/var/log/cobbler/cobbler.log'],
814- stdout=subprocess.PIPE)
815 try:
816+ tail_process = subprocess.Popen(
817+ ['tail', '-n0', '-f', '/var/log/cobbler/cobbler.log'],
818+ stdout=subprocess.PIPE)
819 command_results = self._runargs(['sudo', 'cobbler'] + cmd)
820 retcode = command_results['returncode']
821 if retcode != 0:
822@@ -233,6 +235,8 @@
823 raise UTAHBMProvisioningException(error_msg)
824 else:
825 return command_results
826+ except (OSError, subprocess.CalledProcessError) as err:
827+ self.logger.error('Failed to get cobbler logs: {}'.format(err))
828 finally:
829 # tail process will wait forever because of the -f/--follow option,
830 # so it has to be terminated in order to read from stdout.
831
832=== modified file 'utah/provisioning/inventory/sqlite.py'
833--- utah/provisioning/inventory/sqlite.py 2013-01-24 16:47:04 +0000
834+++ utah/provisioning/inventory/sqlite.py 2013-03-18 14:45:38 +0000
835@@ -32,8 +32,8 @@
836 db = os.path.expanduser(db)
837 super(SQLiteInventory, self).__init__(*args, uniqueid=db, **kw)
838 self.db = db
839- self._connection = sqlite3.connect(self.db,
840- config.sqlite3_db_connection_timeout)
841+ self._connection = sqlite3.connect(
842+ self.db, config.sqlite3_db_connection_timeout)
843 self._connection.isolation_level = None
844 self._connection.row_factory = sqlite3.Row
845
846
847=== modified file 'utah/provisioning/provisioning.py'
848--- utah/provisioning/provisioning.py 2013-03-13 16:56:34 +0000
849+++ utah/provisioning/provisioning.py 2013-03-18 14:45:38 +0000
850@@ -755,9 +755,13 @@
851 initrd = self.initrd
852 if tmpdir is None:
853 tmpdir = self.tmpdir
854- if not os.path.isdir(os.path.join(tmpdir, 'initrd.d')):
855- os.makedirs(os.path.join(tmpdir, 'initrd.d'))
856- os.chdir(os.path.join(tmpdir, 'initrd.d'))
857+ try:
858+ if not os.path.isdir(os.path.join(tmpdir, 'initrd.d')):
859+ os.makedirs(os.path.join(tmpdir, 'initrd.d'))
860+ os.chdir(os.path.join(tmpdir, 'initrd.d'))
861+ except OSError as err:
862+ raise UTAHProvisioningException(
863+ 'Error using temp directory {}: {}'.format(tmpdir, err))
864 pipe = pipes.Template()
865 if os.path.splitext(initrd)[1] == '.gz':
866 self.logger.debug('Using gzip based on file extension')
867@@ -765,8 +769,17 @@
868 elif os.path.splitext(initrd)[1] == '.lz':
869 self.logger.debug('Using lzma based on file extension')
870 pipe.prepend('lzcat -S .lz $IN', 'f-')
871+ else:
872+ raise UTAHProvisioningException(
873+ 'initrd file does have have gz or lz extension: {}'
874+ .format(initrd))
875 pipe.append('cpio -ivd 2>/dev/null', '-.')
876- pipe.copy(initrd, '/dev/null')
877+ exitstatus = pipe.copy(initrd, '/dev/null')
878+ if exitstatus != 0:
879+ # Currently this comes up as 512 when things seem fine
880+ # TODO: refactor this and check for codes at all stages
881+ self.logger.debug(
882+ 'Unpacking initrd exited with status {}'.format(exitstatus))
883
884 def _setuplatecommand(self, tmpdir=None):
885 """
886@@ -943,11 +956,14 @@
887 os.chmod(filename, 0755)
888
889 orderfilename = os.path.join(casper_dir, 'ORDER')
890- self._runargs(['sed', '-i',
891- '1i/scripts/casper-bottom/'
892- 'utah\\n'
893- '[ -e /conf/param.conf ] && . /conf/param.conf',
894- orderfilename])
895+ exitstatus = self._runargs(
896+ ['sed', '-i',
897+ '1i/scripts/casper-bottom/'
898+ 'utah\\n'
899+ '[ -e /conf/param.conf ] && . /conf/param.conf',
900+ orderfilename])['returncode']
901+ if exitstatus != 0:
902+ raise UTAHProvisioningException('Failed to setup casper script')
903
904 casper_file = os.path.join(tmpdir, 'initrd.d', 'etc', 'casper.conf')
905 tmpfilename = casper_file + '.tmp'
906@@ -1010,7 +1026,8 @@
907 self.logger.debug('Using gzip because installtype is not desktop')
908 pipe.append('gzip -9fc ', '--')
909 initrd = os.path.join(tmpdir, 'initrd.gz')
910- pipe.copy('/dev/null', initrd)
911+ if pipe.copy('/dev/null', initrd) != 0:
912+ raise UTAHProvisioningException('Failed to repack initrd')
913 return initrd
914
915 def _cmdlinesetup(self, boot=None):
916
917=== modified file 'utah/provisioning/ssh.py'
918--- utah/provisioning/ssh.py 2013-03-15 13:31:22 +0000
919+++ utah/provisioning/ssh.py 2013-03-18 14:45:38 +0000
920@@ -95,24 +95,37 @@
921 # http://od-eon.com/blogs/
922 # stefan/automating-remote-commands-over-ssh-paramiko/
923 self.ssh_logger.debug('Connecting SSH')
924- self.ssh_client.connect(self.name,
925- username=user,
926- key_filename=config.sshprivatekey)
927-
928- self.ssh_logger.debug('Opening SSH session')
929- channel = self.ssh_client.get_transport().open_session()
930-
931- self.ssh_logger.info('Running command through SSH: ' + commandstring)
932- stdout = channel.makefile('rb')
933- stderr = channel.makefile_stderr('rb')
934- if timeout is None:
935- channel.exec_command(commandstring)
936- else:
937- utah.timeout.timeout(timeout, channel.exec_command, commandstring)
938- retval = channel.recv_exit_status()
939-
940- self.ssh_logger.debug('Closing SSH connection')
941- self.ssh_client.close()
942+ retval = 'not run'
943+ try:
944+ self.ssh_client.connect(self.name,
945+ username=user,
946+ key_filename=config.sshprivatekey)
947+
948+ self.ssh_logger.debug('Opening SSH session')
949+ channel = self.ssh_client.get_transport().open_session()
950+
951+ self.ssh_logger.info('Running command through SSH: {}'
952+ .format(commandstring))
953+ stdout = channel.makefile('rb')
954+ stderr = channel.makefile_stderr('rb')
955+ if timeout is None:
956+ channel.exec_command(commandstring)
957+ else:
958+ utah.timeout.timeout(timeout, channel.exec_command,
959+ commandstring)
960+ retval = channel.recv_exit_status()
961+
962+ except paramiko.BadHostKeyException as err:
963+ raise UTAHProvisioningException(
964+ 'Host key exception encountered; '
965+ 'machine may be in use by another process: {}'
966+ .format(err), external=True)
967+ except (paramiko.AuthenticationException, paramiko.SSHException,
968+ socket.error) as err:
969+ raise UTAHProvisioningException(err)
970+ finally:
971+ self.ssh_logger.debug('Closing SSH connection')
972+ self.ssh_client.close()
973
974 if retval == 0:
975 self.ssh_logger.debug('Return code: {}'.format(retval))
976@@ -146,12 +159,12 @@
977 files = [files]
978
979 self.activecheck()
980- self.ssh_client.connect(self.name,
981- username=config.user,
982- key_filename=config.sshprivatekey)
983- sftp_client = self.ssh_client.open_sftp()
984 failed = []
985 try:
986+ self.ssh_client.connect(self.name,
987+ username=config.user,
988+ key_filename=config.sshprivatekey)
989+ sftp_client = self.ssh_client.open_sftp()
990 for localpath in files:
991 if os.path.isfile(localpath):
992 self.ssh_logger.info('Uploading {} from the host '
993@@ -162,6 +175,14 @@
994 sftp_client.put(localpath, remotepath)
995 else:
996 failed.append(localpath)
997+ except paramiko.BadHostKeyException as err:
998+ raise UTAHProvisioningException(
999+ 'Host key exception encountered; '
1000+ 'machine may be in use by another process: {}'
1001+ .format(err), external=True)
1002+ except (paramiko.AuthenticationException, paramiko.SSHException,
1003+ socket.error) as err:
1004+ raise UTAHProvisioningException(err)
1005 finally:
1006 sftp_client.close()
1007 if len(failed) > 0:
1008@@ -180,10 +201,6 @@
1009 files = [files]
1010
1011 self.activecheck()
1012- self.ssh_client.connect(self.name,
1013- username=config.user,
1014- key_filename=config.sshprivatekey)
1015- sftp_client = self.ssh_client.open_sftp()
1016 if os.path.isdir(target):
1017 get_localpath = lambda remotepath: \
1018 os.path.join(target, os.path.basename(remotepath))
1019@@ -191,12 +208,24 @@
1020 get_localpath = lambda remotepath: target
1021
1022 try:
1023+ self.ssh_client.connect(self.name,
1024+ username=config.user,
1025+ key_filename=config.sshprivatekey)
1026+ sftp_client = self.ssh_client.open_sftp()
1027 for remotepath in files:
1028 localpath = get_localpath(remotepath)
1029 self.ssh_logger.info('Downloading {} from the machine '
1030 'to {} on the host'
1031 .format(remotepath, target))
1032 sftp_client.get(remotepath, localpath)
1033+ except paramiko.BadHostKeyException as err:
1034+ raise UTAHProvisioningException(
1035+ 'Host key exception encountered; '
1036+ 'machine may be in use by another process: {}'
1037+ .format(err), external=True)
1038+ except (paramiko.AuthenticationException, paramiko.SSHException,
1039+ socket.error) as err:
1040+ raise UTAHProvisioningException(err)
1041 finally:
1042 sftp_client.close()
1043
1044@@ -270,8 +299,18 @@
1045 self.ssh_client.connect(self.name,
1046 username=config.user,
1047 key_filename=config.sshprivatekey)
1048+ except paramiko.BadHostKeyException as err:
1049+ raise UTAHProvisioningException(
1050+ 'Host key exception encountered; '
1051+ 'machine may be in use by another process: {}'
1052+ .format(err), external=True)
1053+ except (paramiko.AuthenticationException,
1054+ paramiko.SSHException) as err:
1055+ raise UTAHProvisioningException(err)
1056 except socket.error as err:
1057 raise UTAHProvisioningException(str(err), retry=True)
1058+ finally:
1059+ self.ssh_client.close()
1060
1061 def sshpoll(self, timeout=None,
1062 checktimeout=config.checktimeout, logmethod=None):
1063
1064=== modified file 'utah/run.py'
1065--- utah/run.py 2013-03-13 17:13:32 +0000
1066+++ utah/run.py 2013-03-18 14:45:38 +0000
1067@@ -151,7 +151,16 @@
1068 def _run(runlist):
1069 """Run a single runlist."""
1070 try:
1071- runlist_local_path = urllib.urlretrieve(runlist)[0]
1072+ try:
1073+ runlist_local_path = urllib.urlretrieve(runlist)[0]
1074+ except IOError as err:
1075+ raise UTAHException(
1076+ 'IOError when downloading {}: {}'
1077+ .format(runlist, err), external=True)
1078+ except urllib.ContentTooShortError as err:
1079+ raise UTAHException(
1080+ 'Error when downloading {} (probably interrupted): {}'
1081+ .format(runlist, err), external=True)
1082 target_dir = os.path.normpath('/tmp')
1083 machine.uploadfiles([runlist_local_path], target_dir)
1084
1085@@ -205,12 +214,20 @@
1086 suffix=report_type))
1087 report_local_path = os.path.join(config.logpath, log_filename)
1088 report_local_path = os.path.normpath(report_local_path)
1089- machine.downloadfiles(report_remote_path, report_local_path)
1090- logging.info('Test log copied to ' + report_local_path)
1091- locallogs.append(report_local_path)
1092- if args.dumplogs:
1093- with open(report_local_path, 'r') as f:
1094- print(f.read())
1095+ try:
1096+ machine.downloadfiles(report_remote_path, report_local_path)
1097+ except UTAHException:
1098+ logging.error('Test log not retrieved')
1099+ else:
1100+ logging.info('Test log copied to ' + report_local_path)
1101+ locallogs.append(report_local_path)
1102+ if args.dumplogs:
1103+ try:
1104+ with open(report_local_path, 'r') as f:
1105+ print(f.read())
1106+ except IOError as err:
1107+ logging.warning('Test log could not be dumped: {}'
1108+ .format(err))
1109
1110 # Write the machine name to standard out for log gathering
1111 print('Running on machine: ' + machine.name)
1112@@ -232,7 +249,7 @@
1113 if args.files is not None:
1114 try:
1115 locallogs += getfiles(args, machine)
1116- except Exception as err:
1117+ except UTAHException as err:
1118 logging.warning('Failed to download files: ' + str(err))
1119
1120 # Provisioned systems have an image already installed
1121@@ -245,8 +262,13 @@
1122 logging.debug('Capturing preseed due to config option')
1123 preseedfile = os.path.join(config.logpath,
1124 machine.name + '-preseed.cfg')
1125- shutil.copyfile(machine.finalpreseed, preseedfile)
1126- locallogs.append(preseedfile)
1127+ try:
1128+ shutil.copyfile(machine.finalpreseed, preseedfile)
1129+ except (IOError, shutil.Error) as err:
1130+ logging.warning('Failed to copy preseed file: {}'
1131+ .format(err))
1132+ else:
1133+ locallogs.append(preseedfile)
1134
1135 return exitstatus, locallogs
1136
1137@@ -257,7 +279,11 @@
1138 else:
1139 outdir = args.outdir
1140 if not os.path.isdir(outdir):
1141- os.makedirs(outdir)
1142+ try:
1143+ os.makedirs(outdir)
1144+ except OSError as err:
1145+ raise UTAHException('Failed to create output directory {}: {}'
1146+ .format(outdir, err), external=True)
1147
1148 machine.downloadfilesrecursive(args.files, outdir)
1149
1150
1151=== modified file 'utah/timeout.py'
1152--- utah/timeout.py 2013-03-04 13:09:18 +0000
1153+++ utah/timeout.py 2013-03-18 14:45:38 +0000
1154@@ -17,8 +17,9 @@
1155 Provide functionality to execute command with timeouts.
1156 """
1157 import os
1158+import signal
1159 import subprocess
1160-import signal
1161+import sys
1162
1163 from utah.exceptions import UTAHException
1164 from utah.commandstr import commandstr
1165@@ -141,7 +142,7 @@
1166 raise UTAHTimeout(msg)
1167
1168
1169-def get_process_children(pid):
1170+def get_process_children(pid, logmethod=sys.stderr.write):
1171 """Find process children so they can be killed when the timeout expires.
1172
1173 :param pid: Process ID for the parent process
1174@@ -150,8 +151,13 @@
1175 :rtype: list(int)
1176
1177 """
1178- p = subprocess.Popen('ps --no-headers -o pid --ppid %d' % pid, shell=True,
1179- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1180- stdout, _stderr = p.communicate()
1181-
1182- return [int(pid) for pid in stdout.split()]
1183+ try:
1184+ p = subprocess.Popen('ps --no-headers -o pid --ppid %d' % pid,
1185+ shell=True, stdout=subprocess.PIPE,
1186+ stderr=subprocess.PIPE)
1187+ stdout, _stderr = p.communicate()
1188+ pids = [int(pid) for pid in stdout.split()]
1189+ return pids
1190+ except OSError as err:
1191+ logmethod('Could not kill process children: {}'.format(err))
1192+ return []

Subscribers

People subscribed via source and target branches