diff --git a/nova/api/openstack/compute/services.py b/nova/api/openstack/compute/services.py index 4abc637fb6ab..ada8dd15af49 100644 --- a/nova/api/openstack/compute/services.py +++ b/nova/api/openstack/compute/services.py @@ -237,9 +237,14 @@ class ServiceController(wsgi.Controller): ag.id, service.host) # remove the corresponding resource provider record from - # placement for this compute node - self.placementclient.delete_resource_provider( - context, service.compute_node, cascade=True) + # placement for the compute nodes managed by this service; + # remember that an ironic compute service can manage multiple + # nodes + compute_nodes = objects.ComputeNodeList.get_all_by_host( + context, service.host) + for compute_node in compute_nodes: + self.placementclient.delete_resource_provider( + context, compute_node, cascade=True) # remove the host_mapping of this host. try: hm = objects.HostMapping.get_by_host(context, service.host) diff --git a/nova/tests/unit/api/openstack/compute/test_services.py b/nova/tests/unit/api/openstack/compute/test_services.py index 4ab86e8b4902..dff73fdfece0 100644 --- a/nova/tests/unit/api/openstack/compute/test_services.py +++ b/nova/tests/unit/api/openstack/compute/test_services.py @@ -608,10 +608,15 @@ class ServicesTestV21(test.TestCase): """Tests that we are still able to successfully delete a nova-compute service even if the HostMapping is not found. """ + @mock.patch('nova.objects.ComputeNodeList.get_all_by_host', + return_value=objects.ComputeNodeList(objects=[ + objects.ComputeNode(host='host1', + hypervisor_hostname='node1'), + objects.ComputeNode(host='host1', + hypervisor_hostname='node2')])) @mock.patch.object(self.controller.host_api, 'service_get_by_id', return_value=objects.Service( - host='host1', binary='nova-compute', - compute_node=objects.ComputeNode())) + host='host1', binary='nova-compute')) @mock.patch.object(self.controller.aggregate_api, 'get_aggregates_by_host', return_value=objects.AggregateList()) @@ -619,15 +624,18 @@ class ServicesTestV21(test.TestCase): 'delete_resource_provider') @mock.patch.object(self.controller.host_api, 'service_delete') def _test(service_delete, delete_resource_provider, - get_aggregates_by_host, service_get_by_id): + get_aggregates_by_host, service_get_by_id, + cn_get_all_by_host): self.controller.delete(self.req, 2) ctxt = self.req.environ['nova.context'] service_get_by_id.assert_called_once_with(ctxt, 2) get_instances.assert_called_once_with(ctxt, 'host1') get_aggregates_by_host.assert_called_once_with(ctxt, 'host1') - delete_resource_provider.assert_called_once_with( - ctxt, service_get_by_id.return_value.compute_node, - cascade=True) + self.assertEqual(2, delete_resource_provider.call_count) + nodes = cn_get_all_by_host.return_value + delete_resource_provider.assert_has_calls([ + mock.call(ctxt, node, cascade=True) for node in nodes + ], any_order=True) get_hm.assert_called_once_with(ctxt, 'host1') service_delete.assert_called_once_with(ctxt, 2) _test() diff --git a/releasenotes/notes/bug-1811726-multi-node-delete-2ba17f02c6171fbb.yaml b/releasenotes/notes/bug-1811726-multi-node-delete-2ba17f02c6171fbb.yaml new file mode 100644 index 000000000000..66fe6a5f9945 --- /dev/null +++ b/releasenotes/notes/bug-1811726-multi-node-delete-2ba17f02c6171fbb.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + `Bug 1811726`_ is fixed by deleting the resource provider (in placement) + associated with each compute node record managed by a ``nova-compute`` + service when that service is deleted via the + ``DELETE /os-services/{service_id}`` API. This is particularly important + for compute services managing ironic baremetal nodes. + + .. _Bug 1811726: https://bugs.launchpad.net/nova/+bug/1811726