Skip to content

Commit a45daaf

Browse files
committed
Delete resource provider in tree by top-down traversable order
When delete nova service by "nova service-delete <id>", this will delete the compute node's resource provider in Placement. But if there are child resource provider, Placement will throw exception(CannotDeleteParentResourceProvider) and don't delete the resource provider. When we add the host service again, Placement cannot insert new compute node resource provider, and we cannot use the compute node any more. So we should delete sub resource provider in tree when delete resource provider. Modify unit test. Change-Id: Ide8732be6c047ad1b141b89df676783b2fa2f25a Closes-Bug: #1872385
1 parent 1d29344 commit a45daaf

File tree

3 files changed

+124
-16
lines changed

3 files changed

+124
-16
lines changed

nova/scheduler/client/report.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,13 +2153,21 @@ def delete_resource_provider(self, context, compute_node, cascade=False):
21532153
context, host, nodename)
21542154
for instance_uuid in instance_uuids:
21552155
self.delete_allocation_for_instance(context, instance_uuid)
2156-
try:
2157-
self._delete_provider(rp_uuid, global_request_id=context.global_id)
2158-
except (exception.ResourceProviderInUse,
2159-
exception.ResourceProviderDeletionFailed):
2160-
# TODO(efried): Raise these. Right now this is being left a no-op
2161-
# for backward compatibility.
2162-
pass
2156+
# Ensure to delete resource provider in tree by top-down
2157+
# traversable order.
2158+
rps_to_refresh = self.get_providers_in_tree(context, rp_uuid)
2159+
self._provider_tree.populate_from_iterable(rps_to_refresh)
2160+
provider_uuids = self._provider_tree.get_provider_uuids_in_tree(
2161+
rp_uuid)
2162+
for provider_uuid in provider_uuids[::-1]:
2163+
try:
2164+
self._delete_provider(provider_uuid,
2165+
global_request_id=context.global_id)
2166+
except (exception.ResourceProviderInUse,
2167+
exception.ResourceProviderDeletionFailed):
2168+
# TODO(efried): Raise these. Right now this is being
2169+
# left a no-op for backward compatibility.
2170+
pass
21632171

21642172
def get_provider_by_name(self, context, name):
21652173
"""Queries the placement API for resource provider information matching

nova/tests/functional/api_sample_tests/test_services.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ def fake_hm_get_by_host(context, host):
143143
def fake_hm_destroy(context, host):
144144
return 1
145145

146+
def fake_get_providers_in_tree(self, context, compute_node_id):
147+
return [{
148+
'uuid': compute_node_id,
149+
'name': 'test',
150+
'generation': 1,
151+
'parent_provider_uuid': None
152+
}]
153+
154+
def fake_get_provider_uuids_in_tree(context, compute_node_id):
155+
return [compute_node_id]
156+
146157
self.stub_out('nova.db.api.service_get_by_uuid',
147158
db_service_get_by_uuid)
148159
self.stub_out('nova.db.api.compute_node_get_all_by_host',
@@ -152,6 +163,14 @@ def fake_hm_destroy(context, host):
152163
fake_hm_get_by_host)
153164
self.stub_out('nova.objects.host_mapping.HostMapping._destroy_in_db',
154165
fake_hm_destroy)
166+
self.stub_out(
167+
'nova.scheduler.client.report.SchedulerReportClient.'
168+
'get_providers_in_tree',
169+
fake_get_providers_in_tree)
170+
self.stub_out(
171+
'nova.compute.provider_tree.ProviderTree.'
172+
'get_provider_uuids_in_tree',
173+
fake_get_provider_uuids_in_tree)
155174

156175
def test_service_enable(self):
157176
"""Enable an existing service."""

nova/tests/unit/scheduler/client/test_report.py

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3146,17 +3146,24 @@ def test_refresh_associations_disabled(self):
31463146

31473147
class TestAllocations(SchedulerReportClientTestCase):
31483148

3149+
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
3150+
'get_providers_in_tree')
31493151
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
31503152
"delete")
31513153
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
31523154
"delete_allocation_for_instance")
31533155
@mock.patch("nova.objects.InstanceList.get_uuids_by_host_and_node")
31543156
def test_delete_resource_provider_cascade(self, mock_by_host,
3155-
mock_del_alloc, mock_delete):
3156-
self.client._provider_tree.new_root(uuids.cn, uuids.cn, generation=1)
3157+
mock_del_alloc, mock_delete, mock_get_rpt):
31573158
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
31583159
hypervisor_hostname="fake_hostname", )
31593160
mock_by_host.return_value = [uuids.inst1, uuids.inst2]
3161+
mock_get_rpt.return_value = [{
3162+
'uuid': cn.uuid,
3163+
'name': mock.sentinel.name,
3164+
'generation': 1,
3165+
'parent_provider_uuid': None
3166+
}]
31603167
resp_mock = mock.Mock(status_code=204)
31613168
mock_delete.return_value = resp_mock
31623169
self.client.delete_resource_provider(self.context, cn, cascade=True)
@@ -3168,17 +3175,24 @@ def test_delete_resource_provider_cascade(self, mock_by_host,
31683175
exp_url, global_request_id=self.context.global_id)
31693176
self.assertFalse(self.client._provider_tree.exists(uuids.cn))
31703177

3178+
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
3179+
'get_providers_in_tree')
31713180
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
31723181
"delete")
31733182
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
31743183
"delete_allocation_for_instance")
31753184
@mock.patch("nova.objects.InstanceList.get_uuids_by_host_and_node")
31763185
def test_delete_resource_provider_no_cascade(self, mock_by_host,
3177-
mock_del_alloc, mock_delete):
3178-
self.client._provider_tree.new_root(uuids.cn, uuids.cn, generation=1)
3186+
mock_del_alloc, mock_delete, mock_get_rpt):
31793187
self.client._association_refresh_time[uuids.cn] = mock.Mock()
31803188
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
31813189
hypervisor_hostname="fake_hostname", )
3190+
mock_get_rpt.return_value = [{
3191+
'uuid': cn.uuid,
3192+
'name': mock.sentinel.name,
3193+
'generation': 1,
3194+
'parent_provider_uuid': None
3195+
}]
31823196
mock_by_host.return_value = [uuids.inst1, uuids.inst2]
31833197
resp_mock = mock.Mock(status_code=204)
31843198
mock_delete.return_value = resp_mock
@@ -3189,14 +3203,21 @@ def test_delete_resource_provider_no_cascade(self, mock_by_host,
31893203
exp_url, global_request_id=self.context.global_id)
31903204
self.assertNotIn(uuids.cn, self.client._association_refresh_time)
31913205

3206+
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
3207+
'get_providers_in_tree')
31923208
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
31933209
"delete")
31943210
@mock.patch('nova.scheduler.client.report.LOG')
3195-
def test_delete_resource_provider_log_calls(self, mock_log, mock_delete):
3196-
# First, check a successful call
3197-
self.client._provider_tree.new_root(uuids.cn, uuids.cn, generation=1)
3211+
def test_delete_resource_provider_log_calls(self, mock_log, mock_delete,
3212+
get_rpt_mock):
31983213
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
31993214
hypervisor_hostname="fake_hostname", )
3215+
get_rpt_mock.return_value = [{
3216+
'uuid': cn.uuid,
3217+
'name': mock.sentinel.name,
3218+
'generation': 1,
3219+
'parent_provider_uuid': None
3220+
}]
32003221
resp_mock = fake_requests.FakeResponse(204)
32013222
mock_delete.return_value = resp_mock
32023223
self.client.delete_resource_provider(self.context, cn)
@@ -3220,15 +3241,75 @@ def test_delete_resource_provider_log_calls(self, mock_log, mock_delete):
32203241
self.assertEqual(0, mock_log.info.call_count)
32213242
self.assertEqual(1, mock_log.error.call_count)
32223243

3244+
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
3245+
'get_providers_in_tree')
3246+
@mock.patch("nova.scheduler.client.report.SchedulerReportClient."
3247+
"delete")
3248+
@mock.patch('nova.scheduler.client.report.LOG')
3249+
def test_delete_resource_providers_by_order(self, mock_log, mock_delete,
3250+
mock_get_rpt):
3251+
"""Ensure that more than on RP is in the tree and that all of them
3252+
is gets deleted in the proper order.
3253+
"""
3254+
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
3255+
hypervisor_hostname="fake_hostname", )
3256+
mock_get_rpt.return_value = [
3257+
{
3258+
'uuid': uuids.child1,
3259+
'name': mock.sentinel.name,
3260+
'generation': 1,
3261+
'parent_provider_uuid': cn.uuid
3262+
},
3263+
{
3264+
'uuid': uuids.gc1_1,
3265+
'name': mock.sentinel.name,
3266+
'generation': 1,
3267+
'parent_provider_uuid': uuids.child1
3268+
},
3269+
{
3270+
'uuid': cn.uuid,
3271+
'name': mock.sentinel.name,
3272+
'generation': 1,
3273+
'parent_provider_uuid': None
3274+
}
3275+
]
3276+
mock_delete.return_value = True
3277+
self.client.delete_resource_provider(self.context, cn)
3278+
self.assertEqual(3, mock_delete.call_count)
3279+
# Delete RP in correct order
3280+
mock_delete.assert_has_calls([
3281+
mock.call('/resource_providers/%s' % uuids.gc1_1,
3282+
global_request_id=mock.ANY),
3283+
mock.call('/resource_providers/%s' % uuids.child1,
3284+
global_request_id=mock.ANY),
3285+
mock.call('/resource_providers/%s' % cn.uuid,
3286+
global_request_id=mock.ANY),
3287+
])
3288+
exp_url = "Deleted resource provider %s"
3289+
# Logging info in correct order: uuids.gc1_1, uuids.child1, cn.uuid
3290+
mock_log.assert_has_calls([
3291+
mock.call.info(exp_url, uuids.gc1_1),
3292+
mock.call.info(exp_url, uuids.child1),
3293+
mock.call.info(exp_url, cn.uuid),
3294+
])
3295+
3296+
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
3297+
'get_providers_in_tree')
32233298
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.delete',
32243299
new=mock.Mock(side_effect=ks_exc.EndpointNotFound()))
3225-
def test_delete_resource_provider_placement_exception(self):
3300+
def test_delete_resource_provider_placement_exception(self,
3301+
mock_get_rpt):
32263302
"""Ensure that a ksa exception in delete_resource_provider raises
32273303
through.
32283304
"""
3229-
self.client._provider_tree.new_root(uuids.cn, uuids.cn, generation=1)
32303305
cn = objects.ComputeNode(uuid=uuids.cn, host="fake_host",
32313306
hypervisor_hostname="fake_hostname", )
3307+
mock_get_rpt.return_value = [{
3308+
'uuid': cn.uuid,
3309+
'name': mock.sentinel.name,
3310+
'generation': 1,
3311+
'parent_provider_uuid': None
3312+
}]
32323313
self.assertRaises(
32333314
ks_exc.ClientException,
32343315
self.client.delete_resource_provider, self.context, cn)

0 commit comments

Comments
 (0)