Address issues raised in adding member_of to GET /a-c
In an earlier patch [0], there were some valid criticisms noted. They were not critical enough to require holding off on that patch, so they are being addressed here in a follow-up patch. [0] I5857e927a830914c96e040936804e322baccc24c Blueprint: alloc-candidates-member-of Change-Id: I762dc4a70613056f1bd9ba7bf11c3a4588bdac70
This commit is contained in:
@@ -200,22 +200,8 @@ def list_resource_providers(req):
|
|||||||
for attr in qpkeys:
|
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
|
|
||||||
# list, either by accepting the single value, or if it
|
|
||||||
# starts with 'in:' splitting on ','.
|
|
||||||
# NOTE(cdent): This will all change when we start using
|
|
||||||
# JSONSchema validation of query params.
|
|
||||||
if attr == 'member_of':
|
if attr == 'member_of':
|
||||||
if value.startswith('in:'):
|
value = util.normalize_member_of_qs_param(value)
|
||||||
value = value[3:].split(',')
|
|
||||||
else:
|
|
||||||
value = [value]
|
|
||||||
# Make sure the values are actually UUIDs.
|
|
||||||
for aggr_uuid in value:
|
|
||||||
if not uuidutils.is_uuid_like(aggr_uuid):
|
|
||||||
raise webob.exc.HTTPBadRequest(
|
|
||||||
_('Invalid uuid value: %(uuid)s') %
|
|
||||||
{'uuid': aggr_uuid})
|
|
||||||
elif attr == 'resources':
|
elif attr == 'resources':
|
||||||
value = util.normalize_resources_qs_param(value)
|
value = util.normalize_resources_qs_param(value)
|
||||||
elif attr == 'required':
|
elif attr == 'required':
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ VERSIONS = [
|
|||||||
'1.19', # Include generation and conflict detection in provider aggregates
|
'1.19', # Include generation and conflict detection in provider aggregates
|
||||||
# APIs
|
# APIs
|
||||||
'1.20', # Return 200 with provider payload from POST /resource_providers
|
'1.20', # Return 200 with provider payload from POST /resource_providers
|
||||||
'1.21', # Support ?member_of=<agg UUIDs> queryparam on
|
'1.21', # Support ?member_of=in:<agg UUIDs> queryparam on
|
||||||
# GET /allocation_candidates
|
# GET /allocation_candidates
|
||||||
'1.22', # Support forbidden traits in the required parameter of
|
'1.22', # Support forbidden traits in the required parameter of
|
||||||
# GET /resource_providers and GET /allocation_candidates
|
# GET /resource_providers and GET /allocation_candidates
|
||||||
|
|||||||
@@ -1229,7 +1229,9 @@ def _get_all_with_shared(ctx, resources, member_of=None):
|
|||||||
# AND sharing_disk_gb.resource_provider_id IN ($RPS_SHARING_DISK)
|
# AND sharing_disk_gb.resource_provider_id IN ($RPS_SHARING_DISK)
|
||||||
# INNER JOIN resource_provider_aggregates AS member_aggs
|
# INNER JOIN resource_provider_aggregates AS member_aggs
|
||||||
# ON rp.id = member_aggs.resource_provider_id
|
# ON rp.id = member_aggs.resource_provider_id
|
||||||
# AND member_aggs.aggregate_id IN ($MEMBER_OF)
|
# INNER JOIN placement_aggregates AS p_aggs
|
||||||
|
# ON member_aggs.aggregate_id = p_aggs.id
|
||||||
|
# AND p_aggs.uuid IN ($MEMBER_OF)
|
||||||
# WHERE (
|
# WHERE (
|
||||||
# (
|
# (
|
||||||
# COALESCE(usage_vcpu.used, 0) + $AMOUNT_VCPU <=
|
# COALESCE(usage_vcpu.used, 0) + $AMOUNT_VCPU <=
|
||||||
|
|||||||
@@ -258,9 +258,10 @@ a subsequent GET.
|
|||||||
|
|
||||||
Add support for the `member_of` query parameter to the `GET
|
Add support for the `member_of` query parameter to the `GET
|
||||||
/allocation_candidates` API. It accepts a comma-separated list of UUIDs for
|
/allocation_candidates` API. It accepts a comma-separated list of UUIDs for
|
||||||
aggregates. If this parameter is provided, the only resource providers returned
|
aggregates. Note that if more than one aggregate UUID is passed, the
|
||||||
will be those in one of the specified aggregates that meet the other parts of
|
comma-separated list must be prefixed with the "in:" operator. If this
|
||||||
the request.
|
parameter is provided, the only resource providers returned will be those in
|
||||||
|
one of the specified aggregates that meet the other parts of the request.
|
||||||
|
|
||||||
1.22 Support forbidden traits on resource providers and allocations candidates
|
1.22 Support forbidden traits on resource providers and allocations candidates
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -347,33 +347,36 @@ def normalize_traits_qs_param(val, allow_forbidden=False):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def normalize_member_of_qs_param(val):
|
def normalize_member_of_qs_param(value):
|
||||||
"""Parse a member_of query string parameter value.
|
"""We need to handle member_of as a special case to always make its value a
|
||||||
|
list, either by accepting the single value, or if it starts with 'in:'
|
||||||
|
splitting on ','.
|
||||||
|
|
||||||
Valid values are either a single UUID, or the prefix 'in:' followed by two
|
NOTE(cdent): This will all change when we start using
|
||||||
or more comma-separated UUIDs.
|
JSONSchema validation of query params.
|
||||||
|
|
||||||
:param val: A member_of query parameter of either a single UUID, or a
|
:param value: A member_of query parameter of either a single UUID, or a
|
||||||
comma-separated string of two or more UUIDs.
|
comma-separated string of one or more UUIDs, prefixed with
|
||||||
:return: A list of UUIDs
|
the "in:" operator.
|
||||||
:raises `webob.exc.HTTPBadRequest` if the val parameter is not in the
|
:return: A set of UUIDs
|
||||||
|
:raises `webob.exc.HTTPBadRequest` if the value parameter is not in the
|
||||||
expected format.
|
expected format.
|
||||||
"""
|
"""
|
||||||
# Ensure that multiple values are prefixed with "in:"
|
if "," in value and not value.startswith("in:"):
|
||||||
if "," in val and not val.startswith("in:"):
|
|
||||||
msg = _("Multiple values for 'member_of' must be prefixed with the "
|
msg = _("Multiple values for 'member_of' must be prefixed with the "
|
||||||
"'in:' keyword. Got: %s") % val
|
"'in:' keyword. Got: %s") % value
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
if val.startswith("in:"):
|
if value.startswith('in:'):
|
||||||
ret = val[3:].split(",")
|
value = set(value[3:].split(','))
|
||||||
else:
|
else:
|
||||||
ret = [val]
|
value = set([value])
|
||||||
# Ensure the UUIDs are valid
|
# Make sure the values are actually UUIDs.
|
||||||
if not all([uuidutils.is_uuid_like(agg) for agg in ret]):
|
for aggr_uuid in value:
|
||||||
|
if not uuidutils.is_uuid_like(aggr_uuid):
|
||||||
msg = _("Invalid query string parameters: Expected 'member_of' "
|
msg = _("Invalid query string parameters: Expected 'member_of' "
|
||||||
"parameter to contain valid UUID(s). Got: %s") % val
|
"parameter to contain valid UUID(s). Got: %s") % value
|
||||||
raise webob.exc.HTTPBadRequest(msg)
|
raise webob.exc.HTTPBadRequest(msg)
|
||||||
return ret
|
return value
|
||||||
|
|
||||||
|
|
||||||
def parse_qs_request_groups(qsdict, allow_forbidden=False):
|
def parse_qs_request_groups(qsdict, allow_forbidden=False):
|
||||||
@@ -383,7 +386,7 @@ def parse_qs_request_groups(qsdict, allow_forbidden=False):
|
|||||||
The input qsdict represents a query string of the form:
|
The input qsdict represents a query string of the form:
|
||||||
|
|
||||||
?resources=$RESOURCE_CLASS_NAME:$AMOUNT,$RESOURCE_CLASS_NAME:$AMOUNT
|
?resources=$RESOURCE_CLASS_NAME:$AMOUNT,$RESOURCE_CLASS_NAME:$AMOUNT
|
||||||
&required=$TRAIT_NAME,$TRAIT_NAME&member_of=$AGG_UUID
|
&required=$TRAIT_NAME,$TRAIT_NAME&member_of=in:$AGG1_UUID,$AGG2_UUID
|
||||||
&resources1=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
|
&resources1=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
|
||||||
&required1=$TRAIT_NAME,$TRAIT_NAME&member_of1=$AGG_UUID
|
&required1=$TRAIT_NAME,$TRAIT_NAME&member_of1=$AGG_UUID
|
||||||
&resources2=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
|
&resources2=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ tests:
|
|||||||
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=INVALID_UUID
|
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=INVALID_UUID
|
||||||
status: 400
|
status: 400
|
||||||
response_strings:
|
response_strings:
|
||||||
- Invalid query string parameters
|
- Expected 'member_of' parameter to contain valid UUID(s).
|
||||||
- Expected 'member_of' parameter to contain valid UUID(s)
|
|
||||||
|
|
||||||
- name: get allocation candidates no 'in:' for multiple member_of
|
- name: get allocation candidates no 'in:' for multiple member_of
|
||||||
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=$ENVIRON['AGGA_UUID'],$ENVIRON['AGGB_UUID']
|
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=$ENVIRON['AGGA_UUID'],$ENVIRON['AGGB_UUID']
|
||||||
@@ -38,8 +37,13 @@ tests:
|
|||||||
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=in:$ENVIRON['AGGA_UUID'],INVALID_UUID
|
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=in:$ENVIRON['AGGA_UUID'],INVALID_UUID
|
||||||
status: 400
|
status: 400
|
||||||
response_strings:
|
response_strings:
|
||||||
- Invalid query string parameters
|
- Expected 'member_of' parameter to contain valid UUID(s).
|
||||||
- Expected 'member_of' parameter to contain valid UUID(s)
|
|
||||||
|
- name: get allocation candidates multiple member_of with 'in:' but no aggregates
|
||||||
|
GET: /allocation_candidates?&member_of=in:&resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100
|
||||||
|
status: 400
|
||||||
|
response_strings:
|
||||||
|
- Expected 'member_of' parameter to contain valid UUID(s).
|
||||||
|
|
||||||
- name: get allocation candidates with no match for member_of
|
- name: get allocation candidates with no match for member_of
|
||||||
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=$ENVIRON['AGGA_UUID']
|
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=$ENVIRON['AGGA_UUID']
|
||||||
@@ -86,3 +90,21 @@ tests:
|
|||||||
status: 200
|
status: 200
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.allocation_requests.`len`: 0
|
$.allocation_requests.`len`: 0
|
||||||
|
|
||||||
|
- name: get current compute node 1 state
|
||||||
|
GET: /resource_providers/$ENVIRON['CN1_UUID']
|
||||||
|
|
||||||
|
- name: now associate the first compute node with both aggA and aggC
|
||||||
|
PUT: /resource_providers/$ENVIRON['CN1_UUID']/aggregates
|
||||||
|
data:
|
||||||
|
aggregates:
|
||||||
|
- $ENVIRON['AGGA_UUID']
|
||||||
|
- $ENVIRON['AGGC_UUID']
|
||||||
|
resource_provider_generation: $HISTORY['get current compute node 1 state'].$RESPONSE['$.generation']
|
||||||
|
|
||||||
|
- name: verify that the member_of call for aggs A and B still returns 2 allocation_candidates
|
||||||
|
GET: /allocation_candidates?resources=VCPU:1,MEMORY_MB:1024,DISK_GB:100&member_of=in:$ENVIRON['AGGA_UUID'],$ENVIRON['AGGB_UUID']
|
||||||
|
status: 200
|
||||||
|
response_json_paths:
|
||||||
|
$.allocation_requests.`len`: 2
|
||||||
|
status: 200
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ tests:
|
|||||||
GET: '/resource_providers?member_of=not+a+uuid'
|
GET: '/resource_providers?member_of=not+a+uuid'
|
||||||
status: 400
|
status: 400
|
||||||
response_strings:
|
response_strings:
|
||||||
- 'Invalid uuid value: not a uuid'
|
- Expected 'member_of' parameter to contain valid UUID(s).
|
||||||
response_json_paths:
|
response_json_paths:
|
||||||
$.errors[0].title: Bad Request
|
$.errors[0].title: Bad Request
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Request
|
|||||||
- resources: resources_query_required
|
- resources: resources_query_required
|
||||||
- limit: allocation_candidates_limit
|
- limit: allocation_candidates_limit
|
||||||
- required: allocation_candidates_required
|
- required: allocation_candidates_required
|
||||||
- member_of: member_of
|
- member_of: member_of_1_21
|
||||||
|
|
||||||
Response (microversions 1.12 - )
|
Response (microversions 1.12 - )
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ allocation_candidates_required:
|
|||||||
*collectively* contain all of the required traits. **Starting from
|
*collectively* contain all of the required traits. **Starting from
|
||||||
microversion 1.22** traits which are forbidden from any resource provider
|
microversion 1.22** traits which are forbidden from any resource provider
|
||||||
may be expressed by prefixing a trait with a ``!``.
|
may be expressed by prefixing a trait with a ``!``.
|
||||||
member_of:
|
member_of: &member_of
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
@@ -75,6 +75,9 @@ member_of:
|
|||||||
member_of=5e08ea53-c4c6-448e-9334-ac4953de3cfa
|
member_of=5e08ea53-c4c6-448e-9334-ac4953de3cfa
|
||||||
member_of=in:42896e0d-205d-4fe3-bd1e-100924931787,5e08ea53-c4c6-448e-9334-ac4953de3cfa
|
member_of=in:42896e0d-205d-4fe3-bd1e-100924931787,5e08ea53-c4c6-448e-9334-ac4953de3cfa
|
||||||
min_version: 1.3
|
min_version: 1.3
|
||||||
|
member_of_1_21:
|
||||||
|
<<: *member_of
|
||||||
|
min_version: 1.21
|
||||||
project_id: &project_id
|
project_id: &project_id
|
||||||
type: string
|
type: string
|
||||||
in: query
|
in: query
|
||||||
|
|||||||
Reference in New Issue
Block a user