diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py index 38e055c5bb84..96c5bcc456fa 100644 --- a/nova/conductor/manager.py +++ b/nova/conductor/manager.py @@ -878,6 +878,11 @@ class ComputeTaskManager(base.Base): if migration: migration.status = 'error' migration.save() + # 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() request_spec = request_spec.to_legacy_request_spec_dict() with excutils.save_and_reraise_exception(): self._set_vm_state_and_notify(context, instance.uuid, @@ -892,6 +897,11 @@ class ComputeTaskManager(base.Base): if migration: migration.status = 'error' migration.save() + # 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() request_spec = request_spec.to_legacy_request_spec_dict() with excutils.save_and_reraise_exception(): self._set_vm_state_and_notify(context, instance.uuid, diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index b100515861fc..039c2bf2af8a 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 @@ -1093,11 +1094,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 @@ -1140,15 +1142,23 @@ 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)) + def test_rebuild_with_new_image(self): """Rebuilds a server with a different image which will run it through the scheduler to validate the image is still OK with the compute host