diff --git a/nova/api/openstack/compute/services.py b/nova/api/openstack/compute/services.py index cf6f94eaafb6..b5aa42cdd56c 100644 --- a/nova/api/openstack/compute/services.py +++ b/nova/api/openstack/compute/services.py @@ -245,9 +245,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 a2fbc11f8888..ba8d6054c941 100644 --- a/nova/tests/unit/api/openstack/compute/test_services.py +++ b/nova/tests/unit/api/openstack/compute/test_services.py @@ -712,25 +712,33 @@ 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()) @mock.patch.object(self.controller.placementclient, 'delete_resource_provider') def _test(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() _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