rp: GET /resource_providers?required=<traits>

Introduce placement microversion 1.18 with a new ?required=<trait list>
query parameter accepted on the GET /resource_providers API.  Results
are filtered by providers possessing *all* of the specified traits.
Empty/invalid traits result in 400 errors.

Change-Id: I8191c9a390cb02b2a38a3f1c6e29457435994981
blueprint: traits-on-list-resource-providers
This commit is contained in:
Eric Fried 2018-02-21 19:33:33 -06:00
parent ef4000a0d3
commit 558540a27c
10 changed files with 177 additions and 8 deletions

View File

@ -174,7 +174,9 @@ def list_resource_providers(req):
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
schema = rp_schema.GET_RPS_SCHEMA_1_0
if want_version.matches((1, 14)):
if want_version.matches((1, 18)):
schema = rp_schema.GET_RPS_SCHEMA_1_18
elif want_version.matches((1, 14)):
schema = rp_schema.GET_RPS_SCHEMA_1_14
elif want_version.matches((1, 4)):
schema = rp_schema.GET_RPS_SCHEMA_1_4
@ -184,7 +186,8 @@ def list_resource_providers(req):
util.validate_query_params(req, schema)
filters = {}
for attr in ['uuid', 'name', 'member_of', 'in_tree']:
qpkeys = ('uuid', 'name', 'member_of', 'in_tree', 'resources', 'required')
for attr in qpkeys:
if attr in req.GET:
value = req.GET[attr]
# special case member_of to always make its value a
@ -203,10 +206,11 @@ def list_resource_providers(req):
raise webob.exc.HTTPBadRequest(
_('Invalid uuid value: %(uuid)s') %
{'uuid': aggr_uuid})
elif attr == 'resources':
value = util.normalize_resources_qs_param(value)
elif attr == 'required':
value = util.normalize_traits_qs_param(value)
filters[attr] = value
if 'resources' in req.GET:
resources = util.normalize_resources_qs_param(req.GET['resources'])
filters['resources'] = resources
try:
resource_providers = rp_obj.ResourceProviderList.get_all_by_filters(
context, filters)
@ -214,6 +218,10 @@ def list_resource_providers(req):
raise webob.exc.HTTPBadRequest(
_('Invalid resource class in resources parameter: %(error)s') %
{'error': exc})
except exception.TraitNotFound as exc:
raise webob.exc.HTTPBadRequest(
_('Invalid trait(s) in "required" parameter: %(error)s') %
{'error': exc})
response = req.response
output, last_modified = _serialize_providers(

View File

@ -60,6 +60,7 @@ VERSIONS = [
'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.
'1.18', # Support ?required=<traits> queryparam on GET /resource_providers
]

View File

@ -158,7 +158,7 @@ for resources.
The ``/resource_providers/{rp_uuid}/allocations`` endpoint has been available
since version 1.0, but was not listed in the ``links`` section of the
``GET /resource_providers`` response. The link is included as of version 1.11.
``GET /resource_providers`` response. The link is included as of version 1.11.
1.12 PUT dict format to /allocations/{consumer_uuid}
----------------------------------------------------
@ -221,3 +221,15 @@ value, `N`, which limits the maximum number of candidates returned.
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.
1.18 Support ?required=<traits> queryparam on GET /resource_providers
---------------------------------------------------------------------
Add support for the `required` query parameter to the `GET /resource_providers`
API. It accepts a comma-separated list of string trait names. When specified,
the API results will be filtered to include only resource providers marked with
all the specified traits. This is in addition to (logical AND) any filtering
based on other query parameters.
Trait names which are empty, do not exist, or are otherwise invalid will result
in a 400 error.

View File

@ -94,3 +94,13 @@ GET_RPS_SCHEMA_1_14['properties']['in_tree'] = {
"type": "string",
"format": "uuid",
}
# Microversion 1.18 adds support for the `required` query parameter to the
# `GET /resource_providers` API. It accepts a comma-separated list of string
# trait names. When specified, the API results will be filtered to include only
# resource providers marked with all the specified traits. This is in addition
# to (logical AND) any filtering based on other query parameters.
GET_RPS_SCHEMA_1_18 = copy.deepcopy(GET_RPS_SCHEMA_1_14)
GET_RPS_SCHEMA_1_18['properties']['required'] = {
"type": "string",
}

View File

@ -1396,6 +1396,7 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
# 'MEMORY_MB': 1024
# },
# 'in_tree': <uuid>,
# 'required': [<trait_name>, ...]
# }
if not filters:
filters = {}
@ -1406,6 +1407,7 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
name = filters.pop('name', None)
uuid = filters.pop('uuid', None)
member_of = filters.pop('member_of', [])
required = filters.pop('required', [])
resources = filters.pop('resources', {})
# NOTE(sbauza): We want to key the dict by the resource class IDs
@ -1473,6 +1475,19 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
[resource_provider_id]).select_from(join_statement)
query = query.where(rp.c.id.in_(rps_in_aggregates))
# If 'required' has values, add a filter to limit results to providers
# possessing *all* of the listed traits.
if required:
trait_map = _trait_ids_from_names(context, required)
if len(trait_map) != len(required):
missing = required - set(trait_map)
raise exception.TraitNotFound(names=', '.join(missing))
rp_ids = _get_provider_ids_having_all_traits(context, trait_map)
if not rp_ids:
# If no providers have the required traits, we're done
return []
query = query.where(rp.c.id.in_(rp_ids))
if not resources:
# Returns quickly the list in case we don't need to check the
# resource usage
@ -1575,6 +1590,7 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
:type filters: dict
"""
_ensure_rc_cache(context)
_ensure_trait_sync(context)
resource_providers = cls._get_all_by_filters_from_db(context, filters)
return base.obj_make_list(context, cls(context),
ResourceProvider, resource_providers)

View File

@ -39,13 +39,13 @@ tests:
response_json_paths:
$.errors[0].title: Not Acceptable
- name: latest microversion is 1.17
- name: latest microversion is 1.18
GET: /
request_headers:
openstack-api-version: placement latest
response_headers:
vary: /OpenStack-API-Version/
openstack-api-version: placement 1.17
openstack-api-version: placement 1.18
- name: other accept header bad version
GET: /

View File

@ -392,6 +392,107 @@ tests:
$.resource_providers.`len`: 1
$.resource_providers[?uuid="$ENVIRON['ALT_PARENT_PROVIDER_UUID']"].root_provider_uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
- name: filter providers by traits none of them have
GET: /resource_providers?required=HW_CPU_X86_SGX,HW_CPU_X86_SHA
response_json_paths:
$.resource_providers.`len`: 0
- name: add traits to a provider
PUT: /resource_providers/$ENVIRON['RP_UUID']/traits
request_headers:
content-type: application/json
data:
resource_provider_generation: 0
traits: ['HW_CPU_X86_SGX', 'STORAGE_DISK_SSD']
- name: add traits to another provider
PUT: /resource_providers/$ENVIRON['ALT_PARENT_PROVIDER_UUID']/traits
request_headers:
content-type: application/json
data:
resource_provider_generation: 0
traits: ['MISC_SHARES_VIA_AGGREGATE', 'STORAGE_DISK_SSD']
- name: filter providers with multiple traits where no provider has all of them
GET: /resource_providers?required=HW_CPU_X86_SGX,MISC_SHARES_VIA_AGGREGATE
response_json_paths:
$.resource_providers.`len`: 0
- name: filter providers with a trait some of them have
GET: /resource_providers?required=STORAGE_DISK_SSD
response_json_paths:
$.resource_providers.`len`: 2
# Don't really care about the root UUID - just validating that the providers present are the ones we expected
$.resource_providers[?uuid="$ENVIRON['ALT_PARENT_PROVIDER_UUID']"].root_provider_uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
$.resource_providers[?uuid="$ENVIRON['RP_UUID']"].root_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
- name: list providers with 'required' parameter filters cumulatively with in_tree
GET: /resource_providers?required=STORAGE_DISK_SSD&in_tree=$ENVIRON['RP_UUID']
response_json_paths:
$.resource_providers.`len`: 1
# Only RP_UUID satisfies both the tree and trait constraint
$.resource_providers[?uuid="$ENVIRON['RP_UUID']"].root_provider_uuid: $ENVIRON['PARENT_PROVIDER_UUID']
- name: create some inventory
PUT: /resource_providers/$ENVIRON['ALT_PARENT_PROVIDER_UUID']/inventories
request_headers:
content-type: application/json
data:
resource_provider_generation: 1
inventories:
IPV4_ADDRESS:
total: 253
DISK_GB:
total: 1024
status: 200
response_json_paths:
$.resource_provider_generation: 2
$.inventories.IPV4_ADDRESS.total: 253
$.inventories.IPV4_ADDRESS.reserved: 0
$.inventories.DISK_GB.total: 1024
$.inventories.DISK_GB.allocation_ratio: 1.0
- name: list providers with 'required' parameter filters cumulatively with resources
GET: /resource_providers?required=STORAGE_DISK_SSD&resources=IPV4_ADDRESS:10
response_json_paths:
$.resource_providers.`len`: 1
# Only ALT_PARENT_PROVIDER_UUID satisfies both the tree and trait constraint
$.resource_providers[?uuid="$ENVIRON['ALT_PARENT_PROVIDER_UUID']"].root_provider_uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
- name: invalid 'required' parameter - blank
GET: /resource_providers?required=
status: 400
response_strings:
- "Invalid query string parameters: Expected 'required' parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC."
response_json_paths:
$.errors[0].title: Bad Request
- name: invalid 'required' parameter - contains an empty trait name
GET: /resource_providers?required=STORAGE_DISK_SSD,,MISC_SHARES_VIA_AGGREGATE
status: 400
response_strings:
- "Invalid query string parameters: Expected 'required' parameter value of the form: HW_CPU_X86_VMX,CUSTOM_MAGIC."
response_json_paths:
$.errors[0].title: Bad Request
- name: invalid 'required' parameter - contains a nonexistent trait
GET: /resource_providers?required=STORAGE_DISK_SSD,BOGUS_TRAIT,MISC_SHARES_VIA_AGGREGATE
status: 400
response_strings:
- "No such trait(s): BOGUS_TRAIT."
response_json_paths:
$.errors[0].title: Bad Request
- name: schema validation fails with 'required' parameter on old microversion
request_headers:
openstack-api-version: placement 1.17
GET: /resource_providers?required=HW_CPU_X86_SGX,MISC_SHARES_VIA_AGGREGATE
status: 400
response_strings:
- Additional properties are not allowed
response_json_paths:
$.errors[0].title: Bad Request
- name: fail trying to re-parent to a different provider
PUT: /resource_providers/$ENVIRON['RP_UUID']
request_headers:

View File

@ -85,6 +85,14 @@ resource_provider_name_query:
required: false
description: >
The name of a resource provider to filter the list.
resource_provider_required_query:
type: string
in: query
required: false
description: >
A comma-delimited list of string trait names. Results will be filtered to
include only resource providers having all the specified traits.
min_version: 1.18
resource_provider_tree_query:
type: string
in: query

View File

@ -28,6 +28,7 @@ of all filters are merged with a boolean `AND`.
- uuid: resource_provider_uuid_query
- member_of: member_of
- resources: resources_query
- required: resource_provider_required_query
- in_tree: resource_provider_tree_query
Response

View File

@ -0,0 +1,12 @@
---
features:
- |
Placement API microversion 1.18 adds support for the `required` query
parameter to the `GET /resource_providers` API. It accepts a
comma-separated list of string trait names. When specified, the API
results will be filtered to include only resource providers marked with
all the specified traits. This is in addition to (logical AND) any
filtering based on other query parameters.
Trait names which are empty, do not exist, or are otherwise invalid will
result in a 400 error.