diff --git a/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml b/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml new file mode 100644 index 0000000000..21b74a6664 --- /dev/null +++ b/releasenotes/notes/Placement-client-for-placement-based-minimum-bw-allocation-27ed0938118752b6.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Add basic read-only Placement client to Tempest to make possible the + testing of the placement based bandwidth allocation feature. + The following API calls are available for tempest from now: + + * GET /allocation_candidates + * GET /allocations/{consumer_uuid} + + Add new config group ``placement``, with the config options: + + * ``endpoint_type`` to use for communication with placement service. + * ``catalog_type`` of the placement service. + * ``region`` as the placement region name to use. + * ``min_microversion`` and ``max_microversion`` as the range between + placement API requests are sent. diff --git a/tempest/config.py b/tempest/config.py index 716c0003dc..e431754c5c 100644 --- a/tempest/config.py +++ b/tempest/config.py @@ -365,6 +365,38 @@ ComputeGroup = [ "with format 'X.Y' or string 'latest'"), ] +placement_group = cfg.OptGroup(name='placement', + title='Placement Service Options') + +PlacementGroup = [ + cfg.StrOpt('endpoint_type', + default='public', + choices=['public', 'admin', 'internal'], + help="The endpoint type to use for the placement service."), + cfg.StrOpt('catalog_type', + default='placement', + help="Catalog type of the Placement service."), + cfg.StrOpt('region', + default='RegionOne', + help="The placement region name to use. If empty, the value " + "of [identity]/region is used instead. If no such region " + "is found in the service catalog, the first region found " + "is used."), + cfg.StrOpt('min_microversion', + default=None, + help="Lower version of the test target microversion range. " + "The format is 'X.Y', where 'X' and 'Y' are int values. " + "Valid values are string with format 'X.Y' or string " + "'latest'"), + cfg.StrOpt('max_microversion', + default=None, + help="Upper version of the test target microversion range. " + "The format is 'X.Y', where 'X' and 'Y' are int values. " + "Valid values are string with format 'X.Y' or string " + "'latest'"), +] + + compute_features_group = cfg.OptGroup(name='compute-feature-enabled', title="Enabled Compute Service Features") @@ -1096,6 +1128,7 @@ _opts = [ (scenario_group, ScenarioGroup), (service_available_group, ServiceAvailableGroup), (debug_group, DebugGroup), + (placement_group, PlacementGroup), (None, DefaultGroup) ] diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py index 833cfd6fcf..e7ac4238ea 100644 --- a/tempest/lib/services/clients.py +++ b/tempest/lib/services/clients.py @@ -32,6 +32,7 @@ from tempest.lib.services import identity from tempest.lib.services import image from tempest.lib.services import network from tempest.lib.services import object_storage +from tempest.lib.services import placement from tempest.lib.services import volume warnings.simplefilter("once") @@ -46,6 +47,7 @@ def tempest_modules(): """ return { 'compute': compute, + 'placement': placement, 'identity.v2': identity.v2, 'identity.v3': identity.v3, 'image.v1': image.v1, diff --git a/tempest/lib/services/placement/__init__.py b/tempest/lib/services/placement/__init__.py new file mode 100644 index 0000000000..5c20c57cfd --- /dev/null +++ b/tempest/lib/services/placement/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2019 Ericsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest.lib.services.placement.placement_client import \ + PlacementClient + +__all__ = ['PlacementClient'] diff --git a/tempest/lib/services/placement/base_placement_client.py b/tempest/lib/services/placement/base_placement_client.py new file mode 100644 index 0000000000..505a515d4a --- /dev/null +++ b/tempest/lib/services/placement/base_placement_client.py @@ -0,0 +1,43 @@ +# Copyright (c) 2019 Ericsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest.lib.common import api_version_utils +from tempest.lib.common import rest_client + +PLACEMENT_MICROVERSION = None + + +class BasePlacementClient(rest_client.RestClient): + + api_microversion_header_name = 'OpenStack-API-Version' + version_header_value = 'placement %s' + + def get_headers(self): + headers = super(BasePlacementClient, self).get_headers() + if PLACEMENT_MICROVERSION: + headers[self.api_microversion_header_name] = \ + self.version_header_value % PLACEMENT_MICROVERSION + return headers + + def request(self, method, url, extra_headers=False, headers=None, + body=None, chunked=False): + resp, resp_body = super(BasePlacementClient, self).request( + method, url, extra_headers, headers, body, chunked) + if (PLACEMENT_MICROVERSION and + PLACEMENT_MICROVERSION != api_version_utils.LATEST_MICROVERSION): + api_version_utils.assert_version_header_matches_request( + self.api_microversion_header_name, + self.version_header_value % PLACEMENT_MICROVERSION, + resp) + return resp, resp_body diff --git a/tempest/lib/services/placement/placement_client.py b/tempest/lib/services/placement/placement_client.py new file mode 100644 index 0000000000..2c6d9193f0 --- /dev/null +++ b/tempest/lib/services/placement/placement_client.py @@ -0,0 +1,50 @@ +# Copyright (c) 2019 Ericsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_serialization import jsonutils as json +from six.moves.urllib import parse as urllib + +from tempest.lib.common import rest_client +from tempest.lib.services.placement import base_placement_client + + +class PlacementClient(base_placement_client.BasePlacementClient): + + def list_allocation_candidates(self, **params): + """List allocation candidates. + + For full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/placement/#list-allocation-candidates + """ + url = '/allocation_candidates' + if params: + url += '?%s' % urllib.urlencode(params) + resp, body = self.get(url) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) + + def list_allocations(self, consumer_uuid): + """List all allocation records for the consumer. + + For full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/placement/#list-allocations + """ + url = '/allocations/%s' % consumer_uuid + resp, body = self.get(url) + self.expected_success(200, resp.status) + body = json.loads(body) + return rest_client.ResponseBody(resp, body) diff --git a/tempest/tests/lib/services/placement/__init__.py b/tempest/tests/lib/services/placement/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tempest/tests/lib/services/placement/test_placement_client.py b/tempest/tests/lib/services/placement/test_placement_client.py new file mode 100644 index 0000000000..1396a85aac --- /dev/null +++ b/tempest/tests/lib/services/placement/test_placement_client.py @@ -0,0 +1,89 @@ +# Copyright (c) 2019 Ericsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tempest.lib.services.placement import placement_client +from tempest.tests.lib import fake_auth_provider +from tempest.tests.lib.services import base + + +class TestPlacementClient(base.BaseServiceTest): + FAKE_ALLOCATION_CANDIDATES = { + 'allocation_requests': [ + {'allocations': { + 'rp-uuid': {'resources': {'VCPU': 42}} + }} + ], + 'provider_summaries': { + 'rp-uuid': { + 'resources': { + 'VCPU': {'used': 0, 'capacity': 64}, + 'MEMORY_MB': {'capacity': 11196, 'used': 0}, + 'DISK_GB': {'capacity': 19, 'used': 0} + }, + 'traits': ["HW_CPU_X86_SVM"], + } + } + } + + FAKE_ALLOCATIONS = { + 'allocations': { + 'rp-uuid-1': { + 'resources': { + 'NET_BW_IGR_KILOBIT_PER_SEC': 1 + }, + 'generation': 14 + }, + 'rp-uuid2': { + 'resources': { + 'MEMORY_MB': 256, + 'VCPU': 1 + }, + 'generation': 9 + } + } + } + + def setUp(self): + super(TestPlacementClient, self).setUp() + fake_auth = fake_auth_provider.FakeAuthProvider() + self.client = placement_client.PlacementClient( + fake_auth, 'placement', 'regionOne') + + def _test_list_allocation_candidates(self, bytes_body=False): + self.check_service_client_function( + self.client.list_allocation_candidates, + 'tempest.lib.common.rest_client.RestClient.get', + self.FAKE_ALLOCATION_CANDIDATES, + to_utf=bytes_body, + **{'resources1': 'NET_BW_IGR_KILOBIT_PER_SEC:1'}) + + def test_list_allocation_candidates_with_str_body(self): + self._test_list_allocation_candidates() + + def test_list_allocation_candidates_with_bytes_body(self): + self._test_list_allocation_candidates(bytes_body=True) + + def _test_list_allocations(self, bytes_body=False): + self.check_service_client_function( + self.client.list_allocations, + 'tempest.lib.common.rest_client.RestClient.get', + self.FAKE_ALLOCATIONS, + to_utf=bytes_body, + **{'consumer_uuid': 'foo-bar'}) + + def test_list_allocations_with_str_body(self): + self._test_list_allocations() + + def test_list_allocations_with_bytes_body(self): + self._test_list_allocations(bytes_body=True)