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:
Ed Leafe
2018-03-19 20:07:12 +00:00
parent 17ad34ff19
commit 77d3fc3838
9 changed files with 65 additions and 48 deletions

View File

@@ -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':

View File

@@ -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

View File

@@ -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 <=

View File

@@ -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
------------------------------------------------------------------------------ ------------------------------------------------------------------------------

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 - )
-------------------------------- --------------------------------

View File

@@ -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