diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py index 3380422eac9a..5c5cf49a4a31 100644 --- a/nova/conductor/manager.py +++ b/nova/conductor/manager.py @@ -733,6 +733,11 @@ class ComputeTaskManager(base.Base): host_dict['nodename'], host_dict['limits']) except exception.NoValidHost as ex: + # Rollback the image_ref if a new one was provided (this + # only happens in the rebuild case, not evacuate). + if orig_image_ref and orig_image_ref != image_ref: + instance.image_ref = orig_image_ref + instance.save() with excutils.save_and_reraise_exception(): self._set_vm_state_and_notify(context, instance.uuid, 'rebuild_server', @@ -743,6 +748,11 @@ class ComputeTaskManager(base.Base): compute_utils.add_instance_fault_from_exc(context, instance, ex, sys.exc_info()) except exception.UnsupportedPolicyException as ex: + # Rollback the image_ref if a new one was provided (this + # only happens in the rebuild case, not evacuate). + if orig_image_ref and orig_image_ref != image_ref: + instance.image_ref = orig_image_ref + instance.save() with excutils.save_and_reraise_exception(): self._set_vm_state_and_notify(context, instance.uuid, 'rebuild_server', diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index dfe0fd588360..03464317e42f 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -21,6 +21,7 @@ import mock from oslo_log import log as logging from oslo_serialization import base64 from oslo_utils import timeutils +import six from nova.compute import api as compute_api from nova.compute import instance_actions @@ -953,11 +954,12 @@ class ServerRebuildTestCase(integrated_helpers._IntegratedTestBase, self.flags(host='host2') self.compute2 = self.start_service('compute', host='host2') + # We hard-code from a fake image since we can't get images + # via the compute /images proxy API with microversion > 2.35. + original_image_ref = '155d900f-4e14-4e4c-a73d-069cbf4541e6' server_req_body = { 'server': { - # We hard-code from a fake image since we can't get images - # via the compute /images proxy API with microversion > 2.35. - 'imageRef': '155d900f-4e14-4e4c-a73d-069cbf4541e6', + 'imageRef': original_image_ref, 'flavorRef': '1', # m1.tiny from DefaultFlavorsFixture, 'name': 'test_rebuild_with_image_novalidhost', # We don't care about networking for this test. This requires @@ -1000,11 +1002,19 @@ class ServerRebuildTestCase(integrated_helpers._IntegratedTestBase, # Before microversion 2.51 events are only returned for instance # actions if you're an admin. self.api_fixture.admin_api) - # Unfortunately the server's image_ref is updated to be the new image - # even though the rebuild should not work. + # Assert the server image_ref was rolled back on failure. server = self.api.get_server(server['id']) - self.assertEqual(rebuild_image_ref, server['image']['id']) + self.assertEqual(original_image_ref, server['image']['id']) # The server should be in ERROR state self.assertEqual('ERROR', server['status']) self.assertIn('No valid host', server['fault']['message']) + + # Rebuild it again with the same bad image to make sure it's rejected + # again. Since we're using CastAsCall here, there is no 202 from the + # API, and the exception from conductor gets passed back through the + # API. + ex = self.assertRaises( + client.OpenStackApiException, self.api.api_post, + '/servers/%s/action' % server['id'], rebuild_req_body) + self.assertIn('NoValidHost', six.text_type(ex))