in_tree[N] alloc_cands with microversion 1.31

This patch adds microversion 1.31 supporting the `in_tree`/`in_tree<N>`
query parameters to the `GET /allocation_candidates` API. It accepts a
UUID for a resource provider. If this parameter is provided, the only
resource providers returned will be those with the same tree with the
given resource provider.

Change-Id: I24d333d1437168f27aaaac6d894a18271cb6ab91
Blueprint: alloc-candidates-in-tree
This commit is contained in:
Tetsuro Nakamura 2019-02-08 06:20:35 +00:00 committed by Eric Fried
parent e7f3b1d59d
commit ce10de2a29
12 changed files with 298 additions and 6 deletions

View File

@ -32,9 +32,11 @@ Request
- resources: resources_query_ac
- required: required_traits_unnumbered
- member_of: member_of_1_21
- in_tree: allocation_candidates_in_tree
- resourcesN: resources_query_granular
- requiredN: required_traits_granular
- member_ofN: member_of_granular
- in_treeN: allocation_candidates_in_tree_granular
- group_policy: allocation_candidates_group_policy
- limit: allocation_candidates_limit

View File

@ -55,6 +55,24 @@ allocation_candidates_group_policy:
``group_policy=isolate``, numbered groups are guaranteed to be satisfied by
*different* providers - though there may still be overlap with the
unnumbered group.
allocation_candidates_in_tree: &allocation_candidates_in_tree
type: string
in: query
required: false
description: >
A string representing a resource provider uuid. When supplied, it will
filter the returned allocation candidates to only those resource providers
that are in the same tree with the given resource provider.
min_version: 1.31
allocation_candidates_in_tree_granular:
<<: *allocation_candidates_in_tree
description: >
A string representing a resource provider uuid. The parameter key is
``in_treeN``, where ``N`` represents a positive integer suffix
corresponding with a ``resourcesN`` parameter. When supplied, it will
filter the returned allocation candidates for that numbered group to only
those resource providers that are in the same tree with the given resource
provider.
allocation_candidates_limit:
type: integer
in: query

View File

@ -282,7 +282,9 @@ def list_allocation_candidates(req):
context.can(policies.LIST)
want_version = req.environ[microversion.MICROVERSION_ENVIRON]
get_schema = schema.GET_SCHEMA_1_10
if want_version.matches((1, 25)):
if want_version.matches((1, 31)):
get_schema = schema.GET_SCHEMA_1_31
elif want_version.matches((1, 25)):
get_schema = schema.GET_SCHEMA_1_25
elif want_version.matches((1, 21)):
get_schema = schema.GET_SCHEMA_1_21

View File

@ -27,14 +27,16 @@ from placement import util
_QS_RESOURCES = 'resources'
_QS_REQUIRED = 'required'
_QS_MEMBER_OF = 'member_of'
_QS_IN_TREE = 'in_tree'
_QS_KEY_PATTERN = re.compile(
r"^(%s)([1-9][0-9]*)?$" % '|'.join(
(_QS_RESOURCES, _QS_REQUIRED, _QS_MEMBER_OF)))
(_QS_RESOURCES, _QS_REQUIRED, _QS_MEMBER_OF, _QS_IN_TREE)))
class RequestGroup(object):
def __init__(self, use_same_provider=True, resources=None,
required_traits=None, forbidden_traits=None, member_of=None):
required_traits=None, forbidden_traits=None, member_of=None,
in_tree=None):
"""Create a grouping of resource and trait requests.
:param use_same_provider:
@ -47,12 +49,15 @@ class RequestGroup(object):
:param forbidden_traits: A set of { trait_name, ... }
:param member_of: A list of [ [aggregate_UUID],
[aggregate_UUID, aggregate_UUID] ... ]
:param in_tree: A UUID of a root or a non-root provider from whose
tree this RequestGroup must be satisfied.
"""
self.use_same_provider = use_same_provider
self.resources = resources or {}
self.required_traits = required_traits or set()
self.forbidden_traits = forbidden_traits or set()
self.member_of = member_of or []
self.in_tree = in_tree
def __str__(self):
ret = 'RequestGroup(use_same_provider=%s' % str(self.use_same_provider)
@ -75,7 +80,7 @@ class RequestGroup(object):
match = _QS_KEY_PATTERN.match(key)
if not match:
continue
# `prefix` is 'resources', 'required', or 'member_of'
# `prefix` is 'resources', 'required', 'member_of', or 'in_tree'
# `suffix` is an integer string, or None
prefix, suffix = match.groups()
suffix = suffix or ''
@ -99,6 +104,9 @@ class RequestGroup(object):
# JSONSchema
request_group.member_of = util.normalize_member_of_qs_params(
req, suffix)
elif prefix == _QS_IN_TREE:
request_group.in_tree = util.normalize_in_tree_qs_params(
val)
return ret
@staticmethod
@ -160,6 +168,7 @@ class RequestGroup(object):
?resources=$RESOURCE_CLASS_NAME:$AMOUNT,$RESOURCE_CLASS_NAME:$AMOUNT
&required=$TRAIT_NAME,$TRAIT_NAME&member_of=in:$AGG1_UUID,$AGG2_UUID
&in_tree=$RP_UUID
&resources1=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
&required1=$TRAIT_NAME,$TRAIT_NAME&member_of1=$AGG_UUID
&resources2=$RESOURCE_CLASS_NAME:$AMOUNT,RESOURCE_CLASS_NAME:$AMOUNT
@ -186,6 +195,7 @@ class RequestGroup(object):
&required=HW_CPU_X86_VMX,CUSTOM_STORAGE_RAID
&member_of=9323b2b1-82c9-4e91-bdff-e95e808ef954
&member_of=in:8592a199-7d73-4465-8df6-ab00a6243c82,ddbd9226-d6a6-475e-a85f-0609914dd058 # noqa
&in_tree=b9fc9abb-afc2-44d7-9722-19afc977446a
&resources1=SRIOV_NET_VF:2
&required1=CUSTOM_PHYSNET_PUBLIC,CUSTOM_SWITCH_A
&resources2=SRIOV_NET_VF:1
@ -209,6 +219,7 @@ class RequestGroup(object):
[8592a199-7d73-4465-8df6-ab00a6243c82,
ddbd9226-d6a6-475e-a85f-0609914dd058],
],
in_tree=b9fc9abb-afc2-44d7-9722-19afc977446a,
),
'1': RequestGroup(
use_same_provider=True,

View File

@ -77,6 +77,8 @@ VERSIONS = [
'1.29', # Support nested providers in GET /allocation_candidates API.
'1.30', # Add POST /reshaper for atomically migrating resource provider
# inventories and allocations.
'1.31', # Add in_tree and in_tree<N> queryparam on
# `GET /allocation_candidates` API
]

View File

@ -4129,6 +4129,15 @@ class AllocationCandidates(object):
member_of = request.member_of
tree_root_id = None
if request.in_tree:
tree_ids = _provider_ids_from_uuid(context, request.in_tree)
if tree_ids is None:
# List operations should simply return an empty list when a
# non-existing resource provider UUID is given for in_tree.
return [], []
tree_root_id = tree_ids.root_id
LOG.debug("getting allocation candidates in the same tree"
"with the root provider %s", tree_ids.root_uuid)
any_sharing = any(sharing_providers.values())
if not request.use_same_provider and (has_trees or any_sharing):

View File

@ -517,3 +517,26 @@ of inventory moves from a parent provider to a new child provider.
.. note:: This is a special operation that should only be used in rare cases
of resource provider topology changing when inventory is in use.
Only use this if you are really sure of what you are doing.
1.31 Add in_tree queryparam on GET /allocation_candidates (Maximum in Stein)
----------------------------------------------------------------------------
.. versionadded:: Stein
Add support for the ``in_tree`` query parameter to the ``GET
/allocation_candidates`` API. It accepts a UUID for a resource provider.
If this parameter is provided, the only resource providers returned will be
those in the same tree with the given resource provider. The numbered syntax
``in_tree<N>`` is also supported. This restricts providers satisfying the Nth
granular request group to the tree of the specified provider. This may be
redundant with other ``in_tree<N>`` values specified in other groups
(including the unnumbered group). However, it can be useful in cases where a
specific resource (e.g. DISK_GB) needs to come from a specific sharing
provider (e.g. shared storage).
For example, a request for ``VCPU`` and ``VGPU`` resources from ``myhost``
and ``DISK_GB`` resources from ``sharing1`` might look like::
?resources=VCPU:1&in_tree=<myhost_uuid>
&resources1=VGPU:1&in_tree1=<myhost_uuid>
&resources2=DISK_GB:100&in_tree2=<sharing1_uuid>

View File

@ -76,3 +76,8 @@ GET_SCHEMA_1_25["properties"]["group_policy"] = {
"type": "string",
"enum": ["none", "isolate"],
}
# Add in_tree parameter.
GET_SCHEMA_1_31 = copy.deepcopy(GET_SCHEMA_1_25)
GET_SCHEMA_1_31["patternProperties"][_GROUP_PAT_FMT % "in_tree"] = {
"type": "string"}

View File

@ -419,3 +419,186 @@ tests:
response_json_paths:
$.allocation_requests.`len`: 4
$.provider_summaries.`len`: 5
- name: get allocation candidates in tree old microversion
GET: /allocation_candidates?resources=VCPU:1,SRIOV_NET_VF:4&in_tree=$ENVIRON['CN1_UUID']
status: 400
request_headers:
openstack-api-version: placement 1.30
response_strings:
- "Invalid query string parameters"
- name: get allocation candidates in tree with invalid uuid
GET: /allocation_candidates?resources=VCPU:1,SRIOV_NET_VF:4&in_tree=life-is-beautiful
status: 400
request_headers:
openstack-api-version: placement 1.31
response_strings:
- "Expected 'in_tree' parameter to be a format of uuid"
- name: get allocation candidates in tree with root
GET: /allocation_candidates?resources=VCPU:1,SRIOV_NET_VF:4&in_tree=$ENVIRON['CN1_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 2
$.provider_summaries.`len`: 5
$.allocation_requests..allocations["$ENVIRON['CN1_UUID']"].resources.VCPU: [1, 1]
$.allocation_requests..allocations["$ENVIRON['PF1_1_UUID']"].resources.SRIOV_NET_VF: 4
$.allocation_requests..allocations["$ENVIRON['PF1_2_UUID']"].resources.SRIOV_NET_VF: 4
- name: get allocation candidates in tree with child
GET: /allocation_candidates?resources=VCPU:1,SRIOV_NET_VF:4&in_tree=$ENVIRON['PF1_2_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 2
$.provider_summaries.`len`: 5
$.allocation_requests..allocations["$ENVIRON['CN1_UUID']"].resources.VCPU: [1, 1]
$.allocation_requests..allocations["$ENVIRON['PF1_1_UUID']"].resources.SRIOV_NET_VF: 4
$.allocation_requests..allocations["$ENVIRON['PF1_2_UUID']"].resources.SRIOV_NET_VF: 4
- name: get allocation candidates in tree with shared 1
GET: /allocation_candidates?resources=VCPU:1,DISK_GB:10&in_tree=$ENVIRON['CN1_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
# CN1 has no local disk. SS can't be used since it's out of the CN1 tree.
$.allocation_requests.`len`: 0
- name: get allocation candidates in tree with shared 2
GET: /allocation_candidates?resources=VCPU:1,DISK_GB:10&in_tree=$ENVIRON['CN2_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
# CN2 has local disk, but we don't get SS's disk
# because it's out of the CN2 tree.
$.allocation_requests.`len`: 1
$.allocation_requests..allocations["$ENVIRON['CN2_UUID']"].resources.VCPU: 1
$.allocation_requests..allocations["$ENVIRON['CN2_UUID']"].resources.DISK_GB: 10
- name: get allocation candidates in tree with shared 3
GET: /allocation_candidates?resources=VCPU:1,DISK_GB:10&in_tree=$ENVIRON['SS_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
# SS doesn't have VCPU.
$.allocation_requests.`len`: 0
# Test granular scenarios with `in_tree<N>`
- name: get allocation candidates in tree granular error orphaned
GET: /allocation_candidates?resources=VCPU:1&in_tree1=$ENVIRON['CN1_UUID']
status: 400
request_headers:
openstack-api-version: placement 1.31
response_strings:
- "All request groups must specify resources."
- name: get allocation candidates, in_tree=$root, granular root resource
GET: /allocation_candidates?resources1=VCPU:1&in_tree1=$ENVIRON['CN1_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 1
$.allocation_requests..allocations["$ENVIRON['CN1_UUID']"].resources.VCPU: 1
- name: get allocation candidates, in_tree=$child, granular root resource
GET: /allocation_candidates?resources1=VCPU:1&in_tree1=$ENVIRON['PF1_1_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 1
$.allocation_requests..allocations["$ENVIRON['CN1_UUID']"].resources.VCPU: 1
- name: get allocation candidates, in_tree=$root, granular child resource
GET: /allocation_candidates?resources1=SRIOV_NET_VF:4&in_tree1=$ENVIRON['CN1_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 2
$.allocation_requests..allocations["$ENVIRON['PF1_1_UUID']"].resources.SRIOV_NET_VF: 4
$.allocation_requests..allocations["$ENVIRON['PF1_2_UUID']"].resources.SRIOV_NET_VF: 4
- name: get allocation candidates, in_tree=$child, granular child resource
GET: /allocation_candidates?resources1=SRIOV_NET_VF:4&in_tree1=$ENVIRON['PF1_1_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 2
$.allocation_requests..allocations["$ENVIRON['PF1_1_UUID']"].resources.SRIOV_NET_VF: 4
$.allocation_requests..allocations["$ENVIRON['PF1_2_UUID']"].resources.SRIOV_NET_VF: 4
- name: get allocation candidates in tree granular local storage 1
GET: /allocation_candidates?resources=VCPU:1&resources1=DISK_GB:10&in_tree1=$ENVIRON['CN1_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
# CN1 has no local storage
$.allocation_requests.`len`: 0
- name: get allocation candidates in tree granular local storage 2
GET: /allocation_candidates?resources=VCPU:1&resources1=DISK_GB:10&in_tree1=$ENVIRON['CN2_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 1
$.allocation_requests..allocations["$ENVIRON['CN2_UUID']"].resources.VCPU: 1
$.allocation_requests..allocations["$ENVIRON['CN2_UUID']"].resources.DISK_GB: 10
# Practical usage for "Give me DISK_GB from SS and VCPU from I-don't-care-where"
- name: get allocation candidates in tree granular shared storage
GET: /allocation_candidates?resources=VCPU:1&resources1=DISK_GB:10&in_tree1=$ENVIRON['SS_UUID']
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 2
$.allocation_requests..allocations["$ENVIRON['CN1_UUID']"].resources.VCPU: 1
$.allocation_requests..allocations["$ENVIRON['CN2_UUID']"].resources.VCPU: 1
$.allocation_requests..allocations["$ENVIRON['SS_UUID']"].resources.DISK_GB: [10, 10]
# Practical usage for "Give me VCPU from CN1 and DISK_GB from I-don't-care-where"
- name: get allocation candidates in tree granular compute 1
GET: /allocation_candidates?resources=VCPU:1&in_tree=$ENVIRON['CN1_UUID']&resources1=DISK_GB:10
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
# CN1 has no local storage
$.allocation_requests.`len`: 1
$.allocation_requests..allocations["$ENVIRON['CN1_UUID']"].resources.VCPU: 1
$.allocation_requests..allocations["$ENVIRON['SS_UUID']"].resources.DISK_GB: 10
- name: get allocation candidates in tree granular compute 2
GET: /allocation_candidates?resources=VCPU:1&in_tree=$ENVIRON['CN2_UUID']&resources1=DISK_GB:10
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
# CN2 has local storage
$.allocation_requests.`len`: 2
$.allocation_requests..allocations["$ENVIRON['CN2_UUID']"].resources.VCPU: [1, 1]
$.allocation_requests..allocations["$ENVIRON['CN2_UUID']"].resources.DISK_GB: 10
$.allocation_requests..allocations["$ENVIRON['SS_UUID']"].resources.DISK_GB: 10
# Practical usage for "Give me VCPU from CN1 and DISK_GB from SS"
- name: get allocation candidates in tree granular compute and granular shared storage
GET: /allocation_candidates?resources1=VCPU:1&in_tree1=$ENVIRON['CN1_UUID']&resources2=DISK_GB:10&in_tree2=$ENVIRON['SS_UUID']&group_policy=none
status: 200
request_headers:
openstack-api-version: placement 1.31
response_json_paths:
$.allocation_requests.`len`: 1
$.allocation_requests..allocations["$ENVIRON['CN1_UUID']"].resources.VCPU: 1
$.allocation_requests..allocations["$ENVIRON['SS_UUID']"].resources.DISK_GB: 10

View File

@ -41,13 +41,13 @@ tests:
response_json_paths:
$.errors[0].title: Not Acceptable
- name: latest microversion is 1.30
- name: latest microversion is 1.31
GET: /
request_headers:
openstack-api-version: placement latest
response_headers:
vary: /openstack-api-version/
openstack-api-version: placement 1.30
openstack-api-version: placement 1.31
- name: other accept header bad version
GET: /

View File

@ -393,6 +393,23 @@ def normalize_member_of_qs_param(value):
return value
def normalize_in_tree_qs_params(value):
"""Parse a in_tree query string parameter value.
:param value: in_tree query parameter: A UUID of a resource provider.
:return: A UUID of a resource provider.
:raises `webob.exc.HTTPBadRequest` if the val parameter is not in the
expected format.
"""
ret = value.strip()
if not uuidutils.is_uuid_like(ret):
msg = _("Invalid query string parameters: Expected 'in_tree' "
"parameter to be a format of uuid. "
"Got: %(val)s") % {'val': value}
raise webob.exc.HTTPBadRequest(msg)
return ret
def run_once(message, logger, cleanup=None):
"""This is a utility function decorator to ensure a function
is run once and only once in an interpreter instance.

View File

@ -0,0 +1,20 @@
---
features:
- |
Add support for the ``in_tree`` query parameter to the ``GET
/allocation_candidates`` API. It accepts a UUID for a resource provider.
If this parameter is provided, the only resource providers returned will
be those in the same tree with the given resource provider. The numbered
syntax ``in_tree<N>`` is also supported. This restricts providers
satisfying the Nth granular request group to the tree of the specified
provider. This may be redundant with other ``in_tree<N>`` values specified
in other groups (including the unnumbered group). However, it can be
useful in cases where a specific resource (e.g. DISK_GB) needs to come
from a specific sharing provider (e.g. shared storage).
For example, a request for ``VCPU`` and ``VGPU`` resources from ``myhost``
and ``DISK_GB`` resources from ``sharing1`` might look like::
?resources=VCPU:1&in_tree=<myhost_uuid>
&resources1=VGPU:1&in_tree1=<myhost_uuid>
&resources2=DISK_GB:100&in_tree2=<sharing1_uuid>