From 5e114768a3e960628874977c51aabfc816ce8d26 Mon Sep 17 00:00:00 2001 From: Tetsuro Nakamura Date: Mon, 15 Oct 2018 21:03:51 +0900 Subject: [PATCH] Adds subnormal path for placement client (1) We start to support placement api from Stein, so there are cases where the child resource provider of a host doesn't exist if the host has been created in blazar before Rocky release. This patch changes the placement client code to be aware of the cases. Namely, in cases the resource provider is not found, - Change get_resource_provider() to return None - Change delete_reservation_provider() to skip the operation - Change update_reservation_inventory() to create the resource provider - Change delete_reservation_inventory() to raise an error Change-Id: I82abbc73f69f4036f60b7c549c1cbd0c0b91a130 Blueprint: placement-api --- .../tests/utils/openstack/test_placement.py | 136 ++++++++++++++++++ blazar/utils/openstack/placement.py | 21 ++- 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/blazar/tests/utils/openstack/test_placement.py b/blazar/tests/utils/openstack/test_placement.py index 6787112f..fadb262c 100644 --- a/blazar/tests/utils/openstack/test_placement.py +++ b/blazar/tests/utils/openstack/test_placement.py @@ -130,6 +130,28 @@ class TestPlacementClient(tests.TestCase): 'parent_provider_uuid': parent_uuid} self.assertEqual(expected, result) + @mock.patch('keystoneauth1.session.Session.request') + def test_get_resource_provider_no_rp(self, kss_req): + rp_name = 'blazar' + + mock_json_data = { + 'resource_providers': [] + } + + kss_req.return_value = fake_requests.FakeResponse( + 200, content=json.dumps(mock_json_data)) + + result = self.client.get_resource_provider(rp_name) + + expected_url = '/resource_providers?name=blazar' + kss_req.assert_called_once_with( + expected_url, 'GET', + endpoint_filter={'service_type': 'placement', + 'interface': 'public'}, + headers={'accept': 'application/json'}, + microversion=PLACEMENT_MICROVERSION, raise_exc=False) + self.assertEqual(None, result) + @mock.patch('keystoneauth1.session.Session.request') def test_get_resource_provider_fail(self, kss_req): rp_name = 'blazar' @@ -286,6 +308,31 @@ class TestPlacementClient(tests.TestCase): headers={'accept': 'application/json'}, microversion=PLACEMENT_MICROVERSION, raise_exc=False) + @mock.patch('keystoneauth1.session.Session.request') + def test_delete_reservation_provider_no_rp(self, kss_req): + host_name = "compute-1" + rp_name = "blazar_compute-1" + get_json_mock = { + 'resource_providers': [] + } + mock_call1 = fake_requests.FakeResponse( + 200, content=json.dumps(get_json_mock)) + mock_call2 = fake_requests.FakeResponse(200) + kss_req.side_effect = [mock_call1, mock_call2] + + self.client.delete_reservation_provider(host_name) + + expected_url_get = "/resource_providers?name=%s" % rp_name + kss_req.assert_any_call( + expected_url_get, 'GET', + endpoint_filter={'service_type': 'placement', + 'interface': 'public'}, + headers={'accept': 'application/json'}, + microversion=PLACEMENT_MICROVERSION, raise_exc=False) + + # Ensure that mock_call2 for delete is not called + self.assertEqual(kss_req.call_count, 1) + @mock.patch('keystoneauth1.session.Session.request') def test_create_reservation_class(self, kss_req): rc_name = 'abc-def' @@ -429,6 +476,84 @@ class TestPlacementClient(tests.TestCase): microversion=PLACEMENT_MICROVERSION, raise_exc=False) self.assertEqual(mock_put_json, result) + @mock.patch('blazar.utils.openstack.placement.' + 'BlazarPlacementClient.get_resource_provider') + @mock.patch('blazar.utils.openstack.placement.' + 'BlazarPlacementClient.create_reservation_provider') + @mock.patch('blazar.utils.openstack.placement.' + 'BlazarPlacementClient.get') + @mock.patch('keystoneauth1.session.Session.request') + def test_update_reservation_inventory_no_rp( + self, kss_req, client_get, create_rp, get_rp): + host_uuid = uuidutils.generate_uuid() + host_name = "compute-1" + rp_uuid = uuidutils.generate_uuid() + rp_name = "blazar_compute-1" + + # Build the mock that there is no existing reservation provider + get_rp.return_value = None + + # Build the mock of created resource provider + mock_post_rp_json = {'uuid': rp_uuid, + 'name': rp_name, + 'generation': 0, + 'parent_provider_uuid': host_uuid} + create_rp.return_value = mock_post_rp_json + + # Build the mock of "current" inventory for get_inventory() + curr_gen = 0 + mock_get_inv_json = { + 'inventories': {}, + "resource_provider_generation": curr_gen + } + client_get.return_value = fake_requests.FakeResponse( + 200, content=json.dumps(mock_get_inv_json)) + + # Build the mock of "updated" inventory for update_inventory() + update_gen = 1 + mock_put_json = { + 'inventories': { + 'CUSTOM_RESERVATION_ADD': { + "allocation_ratio": 1.0, + "max_unit": 1, + "min_unit": 1, + "reserved": 0, + "step_size": 1, + "total": 3 + }, + }, + "resource_provider_generation": update_gen + } + kss_req.return_value = fake_requests.FakeResponse( + 200, content=json.dumps(mock_put_json)) + + result = self.client.update_reservation_inventory(host_name, 'add', 3) + + # Ensure that the create_reservation_provider() is called. + create_rp.assert_called_once_with(host_name) + + expected_data = { + 'inventories': { + 'CUSTOM_RESERVATION_ADD': { + "allocation_ratio": 1.0, + "max_unit": 1, + "min_unit": 1, + "reserved": 0, + "step_size": 1, + "total": 3 + }, + }, + "resource_provider_generation": curr_gen + } + expected_url = '/resource_providers/%s/inventories' % rp_uuid + kss_req.assert_called_once_with( + expected_url, 'PUT', json=expected_data, + endpoint_filter={'service_type': 'placement', + 'interface': 'public'}, + headers={'accept': 'application/json'}, + microversion=PLACEMENT_MICROVERSION, raise_exc=False) + self.assertEqual(mock_put_json, result) + @mock.patch('blazar.utils.openstack.placement.' 'BlazarPlacementClient.get_resource_provider') @mock.patch('keystoneauth1.session.Session.request') @@ -458,3 +583,14 @@ class TestPlacementClient(tests.TestCase): 'interface': 'public'}, headers={'accept': 'application/json'}, microversion=PLACEMENT_MICROVERSION, raise_exc=False) + + @mock.patch('blazar.utils.openstack.placement.' + 'BlazarPlacementClient.get_resource_provider') + def test_delete_reservation_inventory_no_rp(self, get_rp): + host_name = "compute-1" + # Build the mock that there is no existing reservation provider + get_rp.return_value = None + + self.assertRaises( + exceptions.ResourceProviderNotFound, + self.client.delete_reservation_inventory, host_name, "curr1") diff --git a/blazar/utils/openstack/placement.py b/blazar/utils/openstack/placement.py index dfbc4514..bd543981 100644 --- a/blazar/utils/openstack/placement.py +++ b/blazar/utils/openstack/placement.py @@ -98,14 +98,18 @@ class BlazarPlacementClient(object): """Calls the placement API for a resource provider record. :param rp_name: Name of the resource provider - :return: A dict of resource provider information. + :return: A dict of resource provider information + or None if the resource provider doesn't exist. :raise: ResourceProviderRetrievalFailed on error. """ url = "/resource_providers?name=%s" % rp_name resp = self.get(url) if resp: json_resp = resp.json() - return json_resp['resource_providers'][0] + if json_resp['resource_providers']: + return json_resp['resource_providers'][0] + else: + return None msg = ("Failed to get resource provider %(name)s. " "Got %(status_code)d: %(err_text)s.") @@ -192,6 +196,10 @@ class BlazarPlacementClient(object): """Delete the reservation provider, the child of the given host""" rp_name = "blazar_" + host_name rp = self.get_resource_provider(rp_name) + if rp is None: + # If the reservation provider doesn't exist, + # no operation will be performed. + return rp_uuid = rp['uuid'] self.delete_resource_provider(rp_uuid) @@ -319,6 +327,10 @@ class BlazarPlacementClient(object): # Get reservation provider uuid rp_name = "blazar_" + host_name rp = self.get_resource_provider(rp_name) + if rp is None: + # If the reservation provider is not created yet, + # this function creates it. + rp = self.create_reservation_provider(host_name) rp_uuid = rp['uuid'] # Build inventory data @@ -341,10 +353,15 @@ class BlazarPlacementClient(object): :param host_name: The name of the target host :param reserv_uuid: The reservation uuid + :raises: ResourceProviderNotFound if the reservation + provider is not found """ # Get reservation provider uuid rp_name = "blazar_" + host_name rp = self.get_resource_provider(rp_name) + if rp is None: + raise exceptions.ResourceProviderNotFound( + resource_provider=rp_name) rp_uuid = rp['uuid'] # Convert reservation uuid to resource class name