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.