From ce10de2a297b3e44c5a8f75311cedd4e167c02dd Mon Sep 17 00:00:00 2001 From: Tetsuro Nakamura Date: Fri, 8 Feb 2019 06:20:35 +0000 Subject: [PATCH] in_tree[N] alloc_cands with microversion 1.31 This patch adds microversion 1.31 supporting the `in_tree`/`in_tree` 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 --- api-ref/source/allocation_candidates.inc | 2 + api-ref/source/parameters.yaml | 18 ++ placement/handlers/allocation_candidate.py | 4 +- placement/lib.py | 17 +- placement/microversion.py | 2 + placement/objects/resource_provider.py | 9 + placement/rest_api_version_history.rst | 23 +++ placement/schemas/allocation_candidate.py | 5 + .../gabbits/allocation-candidates.yaml | 183 ++++++++++++++++++ .../functional/gabbits/microversion.yaml | 4 +- placement/util.py | 17 ++ ...c-candidates-in-tree-f69b0de5ba33096b.yaml | 20 ++ 12 files changed, 298 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/alloc-candidates-in-tree-f69b0de5ba33096b.yaml diff --git a/api-ref/source/allocation_candidates.inc b/api-ref/source/allocation_candidates.inc index d2d224b5a..90da3cf13 100644 --- a/api-ref/source/allocation_candidates.inc +++ b/api-ref/source/allocation_candidates.inc @@ -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 diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 0362a3bc5..425807481 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -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 diff --git a/placement/handlers/allocation_candidate.py b/placement/handlers/allocation_candidate.py index 86e68d608..be633d6a1 100644 --- a/placement/handlers/allocation_candidate.py +++ b/placement/handlers/allocation_candidate.py @@ -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 diff --git a/placement/lib.py b/placement/lib.py index 9237df6f8..d70b37d58 100644 --- a/placement/lib.py +++ b/placement/lib.py @@ -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, diff --git a/placement/microversion.py b/placement/microversion.py index c832a1a4e..588222586 100644 --- a/placement/microversion.py +++ b/placement/microversion.py @@ -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 queryparam on + # `GET /allocation_candidates` API ] diff --git a/placement/objects/resource_provider.py b/placement/objects/resource_provider.py index 23d7ecb10..fe883f1c9 100644 --- a/placement/objects/resource_provider.py +++ b/placement/objects/resource_provider.py @@ -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): diff --git a/placement/rest_api_version_history.rst b/placement/rest_api_version_history.rst index 059dfdd7f..a6e2541b2 100644 --- a/placement/rest_api_version_history.rst +++ b/placement/rest_api_version_history.rst @@ -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`` 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`` 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= + &resources1=VGPU:1&in_tree1= + &resources2=DISK_GB:100&in_tree2= diff --git a/placement/schemas/allocation_candidate.py b/placement/schemas/allocation_candidate.py index d418366ff..337dbd7d5 100644 --- a/placement/schemas/allocation_candidate.py +++ b/placement/schemas/allocation_candidate.py @@ -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"} diff --git a/placement/tests/functional/gabbits/allocation-candidates.yaml b/placement/tests/functional/gabbits/allocation-candidates.yaml index e8cf7b185..0818753e4 100644 --- a/placement/tests/functional/gabbits/allocation-candidates.yaml +++ b/placement/tests/functional/gabbits/allocation-candidates.yaml @@ -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` +- 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 diff --git a/placement/tests/functional/gabbits/microversion.yaml b/placement/tests/functional/gabbits/microversion.yaml index fa73a3979..01ee3096d 100644 --- a/placement/tests/functional/gabbits/microversion.yaml +++ b/placement/tests/functional/gabbits/microversion.yaml @@ -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: / diff --git a/placement/util.py b/placement/util.py index 79ac0a087..bc35a0310 100644 --- a/placement/util.py +++ b/placement/util.py @@ -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. diff --git a/releasenotes/notes/alloc-candidates-in-tree-f69b0de5ba33096b.yaml b/releasenotes/notes/alloc-candidates-in-tree-f69b0de5ba33096b.yaml new file mode 100644 index 000000000..ac19c4999 --- /dev/null +++ b/releasenotes/notes/alloc-candidates-in-tree-f69b0de5ba33096b.yaml @@ -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`` 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`` 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= + &resources1=VGPU:1&in_tree1= + &resources2=DISK_GB:100&in_tree2=