From b806adb032567c2c84d61834e6f8d2684723c194 Mon Sep 17 00:00:00 2001 From: Chris Dent Date: Mon, 11 Jan 2016 12:05:00 +0000 Subject: [PATCH] Propagate qemu-img errors to compute manager When qemu-img is called with oslo_concurrency.process_utils.execute the ProcessExecutionError was raised when qemu-img either fails to execute or has a non-zero exit code. This error did not propagate up to the compute manager with any meaningful information meaning that if an instance build fails the error message is the generic "There are not enough hosts available". This change captures ProcessExecutionError and re-raises the exception as either InvalidDiskInfo (in qemu_img_info) or ImageUnacceptable (in convert_image and fetch_to_raw) and makes the manager accept this as a cause for a BuildAbortException on the logic that if the image is bad, things are dire, let's bail. Based on the code in qemu_img_info it appears there was a misunderstanding of how process_utils.execute behaves so it seems likely this problem is present elsewhere in the code. This change attempts to only address the issue as it shows up on the new instance path described in the related bug. Conflicts: nova/virt/images.py Change-Id: I4fa1c258db58c70dfbf0178b7bb13978fda3a11f Closes-Bug: #1436166 (cherry picked from commit 9a4ecfd96dad32fd4726c46dc6d89e956f1f2a29) --- nova/compute/manager.py | 3 ++- nova/tests/unit/compute/test_compute_mgr.py | 4 ++++ nova/tests/unit/virt/test_images.py | 25 ++++++++++++++++++++- nova/virt/images.py | 25 ++++++++++++++++++--- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 2c839decb92e..907803bf7d5b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -2045,7 +2045,8 @@ class ComputeManager(manager.Manager): except (exception.FlavorDiskTooSmall, exception.FlavorMemoryTooSmall, exception.ImageNotActive, - exception.ImageUnacceptable) as e: + exception.ImageUnacceptable, + exception.InvalidDiskInfo) as e: self._notify_about_instance_usage(context, instance, 'create.error', fault=e) raise exception.BuildAbortException(instance_uuid=instance.uuid, diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 7cab7e4e5d56..60b81e0a1f2c 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -3510,6 +3510,10 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): exception.ImageUnacceptable(image_id=self.image.get('id'), reason="")) + def test_build_and_run_invalid_disk_info_exception(self): + self._test_build_and_run_spawn_exceptions( + exception.InvalidDiskInfo(reason="")) + def _test_build_and_run_spawn_exceptions(self, exc): with contextlib.nested( mock.patch.object(self.compute.driver, 'spawn', diff --git a/nova/tests/unit/virt/test_images.py b/nova/tests/unit/virt/test_images.py index 50e85bda205f..46857ac09d0b 100644 --- a/nova/tests/unit/virt/test_images.py +++ b/nova/tests/unit/virt/test_images.py @@ -31,7 +31,7 @@ class QemuTestCase(test.NoDBTestCase): @mock.patch.object(os.path, 'exists', return_value=True) def test_qemu_info_with_errors(self, path_exists): - self.assertRaises(processutils.ProcessExecutionError, + self.assertRaises(exception.InvalidDiskInfo, images.qemu_img_info, '/fake/path') @@ -43,3 +43,26 @@ class QemuTestCase(test.NoDBTestCase): image_info = images.qemu_img_info('/fake/path') self.assertTrue(image_info) self.assertTrue(str(image_info)) + + @mock.patch.object(utils, 'execute', + side_effect=processutils.ProcessExecutionError) + def test_convert_image_with_errors(self, mocked_execute): + self.assertRaises(exception.ImageUnacceptable, + images.convert_image, + '/path/that/does/not/exist', + '/other/path/that/does/not/exist', + 'qcow2', + 'raw') + + @mock.patch.object(images, 'convert_image', + side_effect=exception.ImageUnacceptable) + @mock.patch.object(images, 'qemu_img_info') + @mock.patch.object(images, 'fetch') + def test_fetch_to_raw_errors(self, convert_image, qemu_img_info, fetch): + qemu_img_info.backing_file = None + qemu_img_info.file_format = 'qcow2' + qemu_img_info.virtual_size = 20 + self.assertRaisesRegex(exception.ImageUnacceptable, + 'Image href123 is unacceptable.*', + images.fetch_to_raw, + None, 'href123', '/no/path', None, None) diff --git a/nova/virt/images.py b/nova/virt/images.py index 6f3e48715b89..466bd270f162 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -21,6 +21,7 @@ Handling of VM disk images. import os +from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import fileutils @@ -59,7 +60,13 @@ def qemu_img_info(path, format=None): cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path) if format is not None: cmd = cmd + ('-f', format) - out, err = utils.execute(*cmd) + try: + out, err = utils.execute(*cmd) + except processutils.ProcessExecutionError as exp: + msg = (_("qemu-img failed to execute on %(path)s : %(exp)s") % + {'path': path, 'exp': exp}) + raise exception.InvalidDiskInfo(reason=msg) + if not out: msg = (_("Failed to run qemu-img info on %(path)s : %(error)s") % {'path': path, 'error': err}) @@ -93,7 +100,12 @@ def _convert_image(source, dest, in_format, out_format, run_as_root): cmd = ('qemu-img', 'convert', '-O', out_format, source, dest) if in_format is not None: cmd = cmd + ('-f', in_format) - utils.execute(*cmd, run_as_root=run_as_root) + try: + utils.execute(*cmd, run_as_root=run_as_root) + except processutils.ProcessExecutionError as exp: + msg = (_("Unable to convert image to %(format)s: %(exp)s") % + {'format': out_format, 'exp': exp}) + raise exception.ImageUnacceptable(image_id=source, reason=msg) def fetch(context, image_href, path, _user_id, _project_id, max_size=0): @@ -147,7 +159,14 @@ def fetch_to_raw(context, image_href, path, user_id, project_id, max_size=0): staged = "%s.converted" % path LOG.debug("%s was %s, converting to raw" % (image_href, fmt)) with fileutils.remove_path_on_error(staged): - convert_image(path_tmp, staged, fmt, 'raw') + try: + convert_image(path_tmp, staged, fmt, 'raw') + except exception.ImageUnacceptable as exp: + # re-raise to include image_href + raise exception.ImageUnacceptable(image_id=image_href, + reason=_("Unable to convert image to raw: %(exp)s") + % {'exp': exp}) + os.unlink(path_tmp) data = qemu_img_info(staged)