From 8a307bbdf435d06f0fd0423ffb4036c2cde40193 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Fri, 19 Jan 2018 15:38:26 +0800 Subject: [PATCH] placement: support traits in allocation candidates API This patch add new query parameter `required` to the `GET /allocation_candidates` API, which is used to filter candidates with required traits. The candidate attached traits return in the provider summary also. Those API changes are added by new microversion. Also using specific exception TraitNotFound instead of the generic exception ValueError when invalid traits in the request. Change-Id: Id821b5b2768dcc698695ba6570c6201e1e9a8233 Implement blueprint add-trait-support-in-allocation-candidates --- .../handlers/allocation_candidate.py | 57 ++++++++++---- nova/api/openstack/placement/microversion.py | 2 + .../placement/rest_api_version_history.rst | 7 ++ .../placement/schemas/allocation_candidate.py | 6 ++ nova/api/openstack/placement/util.py | 4 +- nova/exception.py | 2 +- nova/objects/resource_provider.py | 2 +- .../api/openstack/placement/fixtures.py | 4 + .../gabbits/allocation-candidates.yaml | 77 +++++++++++++++++++ .../placement/gabbits/microversion.yaml | 4 +- .../db/test_allocation_candidates.py | 2 +- .../source/allocation_candidates.inc | 1 + placement-api-ref/source/parameters.yaml | 13 +++- ...on-candidates-traits-1adf079ed0c6563c.yaml | 10 +++ 14 files changed, 167 insertions(+), 24 deletions(-) create mode 100644 releasenotes/notes/allocation-candidates-traits-1adf079ed0c6563c.yaml diff --git a/nova/api/openstack/placement/handlers/allocation_candidate.py b/nova/api/openstack/placement/handlers/allocation_candidate.py index cc3eaae2889f..5d30ffd95da7 100644 --- a/nova/api/openstack/placement/handlers/allocation_candidate.py +++ b/nova/api/openstack/placement/handlers/allocation_candidate.py @@ -18,6 +18,7 @@ from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import encodeutils from oslo_utils import timeutils +import six import webob from nova.api.openstack.placement import microversion @@ -117,9 +118,10 @@ def _transform_allocation_requests_list(alloc_reqs): return results -def _transform_provider_summaries(p_sums): +def _transform_provider_summaries(p_sums, include_traits=False): """Turn supplied list of ProviderSummary objects into a dict, keyed by - resource provider UUID, of dicts of provider and inventory information. + resource provider UUID, of dicts of provider and inventory information. The + traits only show up when `include_traits` is `True`. { RP_UUID_1: { @@ -132,7 +134,11 @@ def _transform_provider_summaries(p_sums): 'capacity': 4, 'used': 0, } - } + }, + 'traits': [ + 'HW_CPU_X86_AVX512F', + 'HW_CPU_X86_AVX512CD' + ] }, RP_UUID_2: { 'resources': { @@ -144,20 +150,32 @@ def _transform_provider_summaries(p_sums): 'capacity': 4, 'used': 0, } - } + }, + 'traits': [ + 'HW_NIC_OFFLOAD_TSO', + 'HW_NIC_OFFLOAD_GRO' + ] } } """ - return { - ps.resource_provider.uuid: { - 'resources': { - psr.resource_class: { - 'capacity': psr.capacity, - 'used': psr.used, - } for psr in ps.resources - } - } for ps in p_sums - } + + ret = {} + + for ps in p_sums: + resources = { + psr.resource_class: { + 'capacity': psr.capacity, + 'used': psr.used, + } for psr in ps.resources + } + + ret[ps.resource_provider.uuid] = {'resources': resources} + + if include_traits: + ret[ps.resource_provider.uuid]['traits'] = [ + t.name for t in ps.traits] + + return ret def _transform_allocation_candidates(alloc_cands, want_version): @@ -175,7 +193,10 @@ def _transform_allocation_candidates(alloc_cands, want_version): else: a_reqs = _transform_allocation_requests_list( alloc_cands.allocation_requests) - p_sums = _transform_provider_summaries(alloc_cands.provider_summaries) + + include_traits = want_version.matches((1, 17)) + p_sums = _transform_provider_summaries(alloc_cands.provider_summaries, + include_traits) return { 'allocation_requests': a_reqs, 'provider_summaries': p_sums, @@ -195,7 +216,9 @@ def list_allocation_candidates(req): context = req.environ['placement.context'] want_version = req.environ[microversion.MICROVERSION_ENVIRON] get_schema = schema.GET_SCHEMA_1_10 - if want_version.matches((1, 16)): + if want_version.matches((1, 17)): + get_schema = schema.GET_SCHEMA_1_17 + elif want_version.matches((1, 16)): get_schema = schema.GET_SCHEMA_1_16 util.validate_query_params(req, get_schema) @@ -213,6 +236,8 @@ def list_allocation_candidates(req): raise webob.exc.HTTPBadRequest( _('Invalid resource class in resources parameter: %(error)s') % {'error': exc}) + except exception.TraitNotFound as exc: + raise webob.exc.HTTPBadRequest(six.text_type(exc)) response = req.response trx_cands = _transform_allocation_candidates(cands, want_version) diff --git a/nova/api/openstack/placement/microversion.py b/nova/api/openstack/placement/microversion.py index 637ac1a3d69e..19ac3c46254f 100644 --- a/nova/api/openstack/placement/microversion.py +++ b/nova/api/openstack/placement/microversion.py @@ -58,6 +58,8 @@ VERSIONS = [ # representation and 'in_tree' filter on GET /resource_providers '1.15', # Include last-modified and cache-control headers '1.16', # Add 'limit' query parameter to GET /allocation_candidates + '1.17', # Add 'required' query parameter to GET /allocation_candidates and + # return traits in the provider summary. ] diff --git a/nova/api/openstack/placement/rest_api_version_history.rst b/nova/api/openstack/placement/rest_api_version_history.rst index 7e7c27ebdb62..c88e241a4e80 100644 --- a/nova/api/openstack/placement/rest_api_version_history.rst +++ b/nova/api/openstack/placement/rest_api_version_history.rst @@ -214,3 +214,10 @@ header has been added to prevent inadvertent caching of resources. Add support for a ``limit`` query parameter when making a ``GET /allocation_candidates`` request. The parameter accepts an integer value, `N`, which limits the maximum number of candidates returned. + +1.17 Add 'required' parameter to the allocation candidates +---------------------------------------------------------- + +Add the `required` parameter to the `GET /allocation_candidates` API. It +accepts a list of traits separated by `,`. The provider summary in the response +will include the attached traits also. diff --git a/nova/api/openstack/placement/schemas/allocation_candidate.py b/nova/api/openstack/placement/schemas/allocation_candidate.py index d155f8ef0921..ff7216074562 100644 --- a/nova/api/openstack/placement/schemas/allocation_candidate.py +++ b/nova/api/openstack/placement/schemas/allocation_candidate.py @@ -40,3 +40,9 @@ GET_SCHEMA_1_16['properties']['limit'] = { "minimum": 1, "minLength": 1 } + +# Add required parameter. +GET_SCHEMA_1_17 = copy.deepcopy(GET_SCHEMA_1_16) +GET_SCHEMA_1_17['properties']['required'] = { + "type": ["string"] +} diff --git a/nova/api/openstack/placement/util.py b/nova/api/openstack/placement/util.py index 552ba2200607..30961cd10a89 100644 --- a/nova/api/openstack/placement/util.py +++ b/nova/api/openstack/placement/util.py @@ -306,8 +306,8 @@ def normalize_traits_qs_param(val): """ ret = set(substr.strip() for substr in val.split(',')) if not all(trait for trait in ret): - msg = _('Malformed traits parameter. Expected query string value ' - 'of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC. ' + msg = _('Invalid query string parameters: Expected \'required\' ' + 'parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC. ' 'Got: "%s"') % val raise webob.exc.HTTPBadRequest(msg) return ret diff --git a/nova/exception.py b/nova/exception.py index 9fb1e2af007a..493eac85951d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2216,7 +2216,7 @@ class PowerVMAPIFailed(NovaException): class TraitNotFound(NotFound): - msg_fmt = _("No such trait %(name)s.") + msg_fmt = _("No such trait(s): %(name)s.") class TraitExists(NovaException): diff --git a/nova/objects/resource_provider.py b/nova/objects/resource_provider.py index 5d26536eaf60..0ec49255a0a9 100644 --- a/nova/objects/resource_provider.py +++ b/nova/objects/resource_provider.py @@ -3515,7 +3515,7 @@ class AllocationCandidates(base.NovaObject): # Double-check that we found a trait ID for each requested name if len(trait_map) != len(traits): missing = traits - set(trait_map) - raise ValueError(_("Unknown traits requested: %s") % missing) + raise exception.TraitNotFound(name=', '.join(missing)) # Contains a set of resource provider IDs that share some inventory for # each resource class requested. We do this here as an optimization. If diff --git a/nova/tests/functional/api/openstack/placement/fixtures.py b/nova/tests/functional/api/openstack/placement/fixtures.py index 9e9634678673..549643c5dcc1 100644 --- a/nova/tests/functional/api/openstack/placement/fixtures.py +++ b/nova/tests/functional/api/openstack/placement/fixtures.py @@ -271,6 +271,10 @@ class SharedStorageFixture(APIFixture): inv_list = rp_obj.InventoryList(objects=[vcpu_inv, ram_inv]) cn.set_inventory(inv_list) + t_avx_sse = rp_obj.Trait.get_by_name(self.context, "HW_CPU_X86_SSE") + t_avx_sse2 = rp_obj.Trait.get_by_name(self.context, "HW_CPU_X86_SSE2") + cn1.set_traits(rp_obj.TraitList(objects=[t_avx_sse, t_avx_sse2])) + # Populate shared storage provider with DISK_GB inventory disk_inv = rp_obj.Inventory( self.context, diff --git a/nova/tests/functional/api/openstack/placement/gabbits/allocation-candidates.yaml b/nova/tests/functional/api/openstack/placement/gabbits/allocation-candidates.yaml index 36cfa1053876..6b94e0f5c938 100644 --- a/nova/tests/functional/api/openstack/placement/gabbits/allocation-candidates.yaml +++ b/nova/tests/functional/api/openstack/placement/gabbits/allocation-candidates.yaml @@ -170,3 +170,80 @@ tests: openstack-api-version: placement 1.16 response_json_paths: $.allocation_requests.`len`: 1 + +- name: get allocation candidates with required traits in old version + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE + status: 400 + request_headers: + openstack-api-version: placement 1.16 + response_strings: + - Invalid query string parameters + - "'required' was unexpected" + +- name: get allocation candidates without traits summary in old version + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100 + status: 200 + request_headers: + openstack-api-version: placement 1.16 + response_json_paths: + $.provider_summaries["$ENVIRON['CN1_UUID']"].`len`: 1 + $.provider_summaries["$ENVIRON['CN2_UUID']"].`len`: 1 + +- name: get allocation candidates with invalid trait + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=INVALID_TRAIT + status: 400 + request_headers: + openstack-api-version: placement 1.17 + response_strings: + - No such trait(s) + +- name: get allocation candidates with empty required value + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required= + status: 400 + request_headers: + openstack-api-version: placement 1.17 + response_strings: + - "Invalid query string parameters: Expected 'required' parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC." + +- name: get allocation candidates with invalid required value + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=,, + status: 400 + request_headers: + openstack-api-version: placement 1.17 + response_strings: + - "Invalid query string parameters: Expected 'required' parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC." + +- name: get allocation candidates with required trait + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE + status: 200 + request_headers: + openstack-api-version: placement 1.17 + response_json_paths: + $.allocation_requests.`len`: 1 + $.provider_summaries.`len`: 2 + $.provider_summaries["$ENVIRON['CN1_UUID']"].`len`: 2 + $.provider_summaries["$ENVIRON['CN1_UUID']"].traits.`sorted`: + - HW_CPU_X86_SSE + - HW_CPU_X86_SSE2 + +- name: get allocation candidates with multiple required traits + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE,HW_CPU_X86_SSE2 + status: 200 + request_headers: + openstack-api-version: placement 1.17 + response_json_paths: + $.allocation_requests.`len`: 1 + $.provider_summaries.`len`: 2 + $.provider_summaries["$ENVIRON['CN1_UUID']"].`len`: 2 + $.provider_summaries["$ENVIRON['CN1_UUID']"].traits.`sorted`: + - HW_CPU_X86_SSE + - HW_CPU_X86_SSE2 + +- name: get allocation candidates with required trait and no matching + GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&required=HW_CPU_X86_SSE3 + status: 200 + request_headers: + openstack-api-version: placement 1.17 + response_json_paths: + $.allocation_requests.`len`: 0 + $.provider_summaries.`len`: 0 diff --git a/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml b/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml index e4f098a6c99a..2a40d4f5cc17 100644 --- a/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml +++ b/nova/tests/functional/api/openstack/placement/gabbits/microversion.yaml @@ -39,13 +39,13 @@ tests: response_json_paths: $.errors[0].title: Not Acceptable -- name: latest microversion is 1.16 +- name: latest microversion is 1.17 GET: / request_headers: openstack-api-version: placement latest response_headers: vary: /OpenStack-API-Version/ - openstack-api-version: placement 1.16 + openstack-api-version: placement 1.17 - name: other accept header bad version GET: / diff --git a/nova/tests/functional/db/test_allocation_candidates.py b/nova/tests/functional/db/test_allocation_candidates.py index 580a7a336558..c074a2a5be80 100644 --- a/nova/tests/functional/db/test_allocation_candidates.py +++ b/nova/tests/functional/db/test_allocation_candidates.py @@ -425,7 +425,7 @@ class AllocationCandidatesTestCase(ProviderDBBase): requests = [placement_lib.RequestGroup( use_same_provider=False, resources=self.requested_resources, required_traits=missing)] - self.assertRaises(ValueError, + self.assertRaises(exception.TraitNotFound, rp_obj.AllocationCandidates.get_by_requests, self.ctx, requests) diff --git a/placement-api-ref/source/allocation_candidates.inc b/placement-api-ref/source/allocation_candidates.inc index 9ed805e62ed8..cccd4391c2b5 100644 --- a/placement-api-ref/source/allocation_candidates.inc +++ b/placement-api-ref/source/allocation_candidates.inc @@ -31,6 +31,7 @@ Request - resources: resources_query_required - limit: allocation_candidates_limit + - required: allocation_candidates_required Response (microversions 1.12 - ) -------------------------------- diff --git a/placement-api-ref/source/parameters.yaml b/placement-api-ref/source/parameters.yaml index e4d57e701b3a..80e35417d768 100644 --- a/placement-api-ref/source/parameters.yaml +++ b/placement-api-ref/source/parameters.yaml @@ -50,6 +50,16 @@ allocation_candidates_limit: description: > A positive integer used to limit the maximum number of allocation candidates returned in the response. +allocation_candidates_required: + type: string + in: query + required: false + min_version: 1.17 + description: > + Accepts a list of traits separated by `,`. Allocation requests in the + response will be for resource providers that have capacity for all + requested resources and the set of those resource providers will + *collectively* contain all of the required traits. member_of: type: string in: query @@ -241,7 +251,8 @@ provider_summaries: required: true description: > A dictionary keyed by resource provider UUID, - of dictionaries of inventory/capacity information. + of dictionaries of inventory/capacity information. The list of traits + the resource provider has associated with it is included in version `1.17`. reserved: &reserved type: integer in: body diff --git a/releasenotes/notes/allocation-candidates-traits-1adf079ed0c6563c.yaml b/releasenotes/notes/allocation-candidates-traits-1adf079ed0c6563c.yaml new file mode 100644 index 000000000000..af7aae46a0cd --- /dev/null +++ b/releasenotes/notes/allocation-candidates-traits-1adf079ed0c6563c.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Add ``required`` query parameter to the ``GET /allocation_candidates`` API + in new placement microversion 1.17. The parameter accepts a list of traits + separated by ``,``, which is used to further limit the list of allocation + requests to resource providers that have the capacity to fulfill the + requested resources AND *collectively* have all of the required traits + associated with them. In the same microversion, the candidate attached + traits returned in the provider summary.