diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 10a38511dad2..0b5d201ba6d5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -4931,6 +4931,13 @@ class ComputeManager(manager.Manager): # terminate all the connections with the volume server and the host self._terminate_volume_connections(context, instance, bdms) + # Free up the resource allocations in the placement service. + # This should happen *before* the vm_state is changed to + # SHELVED_OFFLOADED in case client-side code is polling the API to + # schedule more instances (or unshelve) once this server is offloaded. + rt = self._get_resource_tracker() + rt.delete_allocation_for_shelve_offloaded_instance(context, instance) + instance.power_state = current_power_state # NOTE(mriedem): The vm_state has to be set before updating the # resource tracker, see vm_states.ALLOW_RESOURCE_REMOVAL. The host/node @@ -4944,9 +4951,6 @@ class ComputeManager(manager.Manager): # NOTE(ndipanov): Free resources from the resource tracker self._update_resource_tracker(context, instance) - rt = self._get_resource_tracker() - rt.delete_allocation_for_shelve_offloaded_instance(context, instance) - # NOTE(sfinucan): RPC calls should no longer be attempted against this # instance, so ensure any calls result in errors self._nil_out_instance_obj_host_and_node(instance) diff --git a/nova/tests/unit/compute/test_shelve.py b/nova/tests/unit/compute/test_shelve.py index 78a2a252637f..2ac38c2c3fda 100644 --- a/nova/tests/unit/compute/test_shelve.py +++ b/nova/tests/unit/compute/test_shelve.py @@ -225,16 +225,24 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase): fake_bdms = objects.BlockDeviceMappingList() mock_get_bdms.return_value = fake_bdms - with mock.patch.object(instance, 'save'): - self.compute.shelve_offload_instance(self.context, instance, - clean_shutdown=clean_shutdown) - mock_notify.assert_has_calls([ - mock.call(self.context, instance, 'fake-mini', - action='shelve_offload', phase='start', - bdms=fake_bdms), - mock.call(self.context, instance, 'fake-mini', - action='shelve_offload', phase='end', - bdms=fake_bdms)]) + def stub_instance_save(inst, *args, **kwargs): + # If the vm_state is changed to SHELVED_OFFLOADED make sure we + # have already freed up allocations in placement. + if inst.vm_state == vm_states.SHELVED_OFFLOADED: + self.assertTrue(mock_delete_alloc.called, + 'Allocations must be deleted before the ' + 'vm_status can change to shelved_offloaded.') + + self.stub_out('nova.objects.Instance.save', stub_instance_save) + self.compute.shelve_offload_instance(self.context, instance, + clean_shutdown=clean_shutdown) + mock_notify.assert_has_calls([ + mock.call(self.context, instance, 'fake-mini', + action='shelve_offload', phase='start', + bdms=fake_bdms), + mock.call(self.context, instance, 'fake-mini', + action='shelve_offload', phase='end', + bdms=fake_bdms)]) self.assertEqual(vm_states.SHELVED_OFFLOADED, instance.vm_state) self.assertIsNone(instance.task_state)