From dbd7773e057aca59a4dbf27ce890f3e65fbde8b6 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 +- .../api/openstack/placement/fixtures.py | 4 + .../gabbits/allocation-candidates.yaml | 77 +++++++++++++++++++ .../placement/gabbits/microversion.yaml | 4 +- .../source/allocation_candidates.inc | 1 + placement-api-ref/source/parameters.yaml | 13 +++- ...on-candidates-traits-1adf079ed0c6563c.yaml | 10 +++ 11 files changed, 164 insertions(+), 21 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 cc3eaae28..5d30ffd95 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 637ac1a3d..19ac3c462 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 7e7c27ebd..c88e241a4 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 d155f8ef0..ff7216074 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 552ba2200..30961cd10 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/tests/functional/api/openstack/placement/fixtures.py b/nova/tests/functional/api/openstack/placement/fixtures.py index 9e9634678..549643c5d 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 36cfa1053..6b94e0f5c 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 e4f098a6c..2a40d4f5c 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/placement-api-ref/source/allocation_candidates.inc b/placement-api-ref/source/allocation_candidates.inc index 9ed805e62..cccd4391c 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 e4d57e701..80e35417d 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 000000000..af7aae46a --- /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.