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:
parent
ef4000a0d3
commit
558540a27c
nova
api/openstack/placement
objects
tests/functional/api/openstack/placement/gabbits
placement-api-ref/source
releasenotes/notes
@ -174,7 +174,9 @@ def list_resource_providers(req):
|
|||||||
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
|
||||||
|
|
||||||
schema = rp_schema.GET_RPS_SCHEMA_1_0
|
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
|
schema = rp_schema.GET_RPS_SCHEMA_1_14
|
||||||
elif want_version.matches((1, 4)):
|
elif want_version.matches((1, 4)):
|
||||||
schema = rp_schema.GET_RPS_SCHEMA_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)
|
util.validate_query_params(req, schema)
|
||||||
|
|
||||||
filters = {}
|
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:
|
if attr in req.GET:
|
||||||
value = req.GET[attr]
|
value = req.GET[attr]
|
||||||
# special case member_of to always make its value a
|
# special case member_of to always make its value a
|
||||||
@ -203,10 +206,11 @@ def list_resource_providers(req):
|
|||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
_('Invalid uuid value: %(uuid)s') %
|
_('Invalid uuid value: %(uuid)s') %
|
||||||
{'uuid': aggr_uuid})
|
{'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
|
filters[attr] = value
|
||||||
if 'resources' in req.GET:
|
|
||||||
resources = util.normalize_resources_qs_param(req.GET['resources'])
|
|
||||||
filters['resources'] = resources
|
|
||||||
try:
|
try:
|
||||||
resource_providers = rp_obj.ResourceProviderList.get_all_by_filters(
|
resource_providers = rp_obj.ResourceProviderList.get_all_by_filters(
|
||||||
context, filters)
|
context, filters)
|
||||||
@ -214,6 +218,10 @@ def list_resource_providers(req):
|
|||||||
raise webob.exc.HTTPBadRequest(
|
raise webob.exc.HTTPBadRequest(
|
||||||
_('Invalid resource class in resources parameter: %(error)s') %
|
_('Invalid resource class in resources parameter: %(error)s') %
|
||||||
{'error': exc})
|
{'error': exc})
|
||||||
|
except exception.TraitNotFound as exc:
|
||||||
|
raise webob.exc.HTTPBadRequest(
|
||||||
|
_('Invalid trait(s) in "required" parameter: %(error)s') %
|
||||||
|
{'error': exc})
|
||||||
|
|
||||||
response = req.response
|
response = req.response
|
||||||
output, last_modified = _serialize_providers(
|
output, last_modified = _serialize_providers(
|
||||||
|
@ -60,6 +60,7 @@ VERSIONS = [
|
|||||||
'1.16', # Add 'limit' query parameter to GET /allocation_candidates
|
'1.16', # Add 'limit' query parameter to GET /allocation_candidates
|
||||||
'1.17', # Add 'required' query parameter to GET /allocation_candidates and
|
'1.17', # Add 'required' query parameter to GET /allocation_candidates and
|
||||||
# return traits in the provider summary.
|
# return traits in the provider summary.
|
||||||
|
'1.18', # Support ?required=<traits> queryparam on GET /resource_providers
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ for resources.
|
|||||||
|
|
||||||
The ``/resource_providers/{rp_uuid}/allocations`` endpoint has been available
|
The ``/resource_providers/{rp_uuid}/allocations`` endpoint has been available
|
||||||
since version 1.0, but was not listed in the ``links`` section of the
|
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}
|
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
|
Add the `required` parameter to the `GET /allocation_candidates` API. It
|
||||||
accepts a list of traits separated by `,`. The provider summary in the response
|
accepts a list of traits separated by `,`. The provider summary in the response
|
||||||
will include the attached traits also.
|
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.
|
||||||
|
@ -94,3 +94,13 @@ GET_RPS_SCHEMA_1_14['properties']['in_tree'] = {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid",
|
"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",
|
||||||
|
}
|
||||||
|
@ -1396,6 +1396,7 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
|
|||||||
# 'MEMORY_MB': 1024
|
# 'MEMORY_MB': 1024
|
||||||
# },
|
# },
|
||||||
# 'in_tree': <uuid>,
|
# 'in_tree': <uuid>,
|
||||||
|
# 'required': [<trait_name>, ...]
|
||||||
# }
|
# }
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = {}
|
filters = {}
|
||||||
@ -1406,6 +1407,7 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
|
|||||||
name = filters.pop('name', None)
|
name = filters.pop('name', None)
|
||||||
uuid = filters.pop('uuid', None)
|
uuid = filters.pop('uuid', None)
|
||||||
member_of = filters.pop('member_of', [])
|
member_of = filters.pop('member_of', [])
|
||||||
|
required = filters.pop('required', [])
|
||||||
|
|
||||||
resources = filters.pop('resources', {})
|
resources = filters.pop('resources', {})
|
||||||
# NOTE(sbauza): We want to key the dict by the resource class IDs
|
# 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)
|
[resource_provider_id]).select_from(join_statement)
|
||||||
query = query.where(rp.c.id.in_(rps_in_aggregates))
|
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:
|
if not resources:
|
||||||
# Returns quickly the list in case we don't need to check the
|
# Returns quickly the list in case we don't need to check the
|
||||||
# resource usage
|
# resource usage
|
||||||
@ -1575,6 +1590,7 @@ class ResourceProviderList(base.ObjectListBase, base.NovaObject):
|
|||||||
:type filters: dict
|
:type filters: dict
|
||||||
"""
|
"""
|
||||||
_ensure_rc_cache(context)
|
_ensure_rc_cache(context)
|
||||||
|
_ensure_trait_sync(context)
|
||||||
resource_providers = cls._get_all_by_filters_from_db(context, filters)
|
resource_providers = cls._get_all_by_filters_from_db(context, filters)
|
||||||
return base.obj_make_list(context, cls(context),
|
return base.obj_make_list(context, cls(context),
|
||||||
ResourceProvider, resource_providers)
|
ResourceProvider, resource_providers)
|
||||||
|
@ -39,13 +39,13 @@ tests:
|
|||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Not Acceptable
|
$.errors[0].title: Not Acceptable
|
||||||
|
|
||||||
- name: latest microversion is 1.17
|
- name: latest microversion is 1.18
|
||||||
GET: /
|
GET: /
|
||||||
request_headers:
|
request_headers:
|
||||||
openstack-api-version: placement latest
|
openstack-api-version: placement latest
|
||||||
response_headers:
|
response_headers:
|
||||||
vary: /OpenStack-API-Version/
|
vary: /OpenStack-API-Version/
|
||||||
openstack-api-version: placement 1.17
|
openstack-api-version: placement 1.18
|
||||||
|
|
||||||
- name: other accept header bad version
|
- name: other accept header bad version
|
||||||
GET: /
|
GET: /
|
||||||
|
@ -392,6 +392,107 @@ tests:
|
|||||||
$.resource_providers.`len`: 1
|
$.resource_providers.`len`: 1
|
||||||
$.resource_providers[?uuid="$ENVIRON['ALT_PARENT_PROVIDER_UUID']"].root_provider_uuid: $ENVIRON['ALT_PARENT_PROVIDER_UUID']
|
$.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
|
- name: fail trying to re-parent to a different provider
|
||||||
PUT: /resource_providers/$ENVIRON['RP_UUID']
|
PUT: /resource_providers/$ENVIRON['RP_UUID']
|
||||||
request_headers:
|
request_headers:
|
||||||
|
@ -85,6 +85,14 @@ resource_provider_name_query:
|
|||||||
required: false
|
required: false
|
||||||
description: >
|
description: >
|
||||||
The name of a resource provider to filter the list.
|
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:
|
resource_provider_tree_query:
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
|
@ -28,6 +28,7 @@ of all filters are merged with a boolean `AND`.
|
|||||||
- uuid: resource_provider_uuid_query
|
- uuid: resource_provider_uuid_query
|
||||||
- member_of: member_of
|
- member_of: member_of
|
||||||
- resources: resources_query
|
- resources: resources_query
|
||||||
|
- required: resource_provider_required_query
|
||||||
- in_tree: resource_provider_tree_query
|
- in_tree: resource_provider_tree_query
|
||||||
|
|
||||||
Response
|
Response
|
||||||
|
12
releasenotes/notes/placement-required-traits-on-list-resource-providers-fab11cdb36cd3502.yaml
Normal file
12
releasenotes/notes/placement-required-traits-on-list-resource-providers-fab11cdb36cd3502.yaml
Normal 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.
|
Loading…
x
Reference in New Issue
Block a user