Add placement.allocation_candidate schema

Well, mostly. Placement support additionally: resourcesN, requiredN,
member_ofN, in_treeN dynamic query parameters. This is not supported by
OpenAPI and by codegenerator for now. Those are not included until the
way to deal with this situation is being found.

Change-Id: Ie26d37d7a6d64d0e77cf286cfd294cfea10df3eb
This commit is contained in:
Artem Goncharov
2024-11-05 15:17:09 +01:00
parent 6f92df028c
commit 6ce4a86099
2 changed files with 202 additions and 1 deletions

View File

@@ -24,6 +24,7 @@ from codegenerator.common.schema import ParameterSchema
from codegenerator.openapi import base
from codegenerator.openapi.base import OpenStackServerSourceBase
from codegenerator.openapi.utils import merge_api_ref_doc
from codegenerator.openapi.placement_schemas import allocation_candidate
from codegenerator.openapi.placement_schemas import resource_class
from codegenerator.openapi.placement_schemas import trait
from codegenerator.openapi.placement_schemas import reshaper
@@ -32,7 +33,7 @@ from codegenerator.openapi.placement_schemas import reshaper
class PlacementGenerator(OpenStackServerSourceBase):
URL_TAG_MAP = {"/versions": "version"}
RESOURCE_MODULES = [reshaper, resource_class, trait]
RESOURCE_MODULES = [allocation_candidate, reshaper, resource_class, trait]
VERSIONED_METHODS: dict = {}

View File

@@ -0,0 +1,200 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import copy
from typing import Any
from codegenerator.common.schema import TypeSchema
from codegenerator.common.schema import ParameterSchema
from placement.schemas import allocation_candidate
ALLOCATION_CANDIDATES_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
"allocation_requests": {
"type": "array",
"description": "A list of objects that contain a serialized HTTP body that a client may subsequently use in a call to PUT /allocations/{consumer_uuid} to claim resources against a related set of resource providers.",
"items": {
"type": "object",
"properties": {
"allocations": {
"type": "object",
"patternProperties": {
"^[0-9a-fA-F-]{36}$": {
"type": "object",
"properties": {
"resources": {
"type": "object",
"patternProperties": {
"^[A-Z0-9_]+$": {"type": "integer"}
},
}
},
}
},
},
"mappings": {
"type": "object",
"patternProperties": {
"[a-zA-Z0-9_-]*": {
"type": "array",
"items": {"type": "string"},
}
},
"x-openstack": {"min-ver": "1.34"},
},
},
"required": ["allocations"],
"additionalProperties": False,
},
},
"provider_summaries": {
"type": "object",
"patternProperties": {
"^[0-9a-fA-F-]{36}$": {
"type": "object",
"properties": {
"resources": {
"type": "object",
"patternProperties": {
"^[A-Z0-9_]+$": {
"type": "object",
"properties": {
"capacity": {
"type": "integer",
"description": "The amount of the resource that the provider can accommodate.",
},
"used": {
"type": "integer",
"description": "The amount of the resource that has been already allocated.",
},
},
}
},
},
"traits": {
"type": "array",
"items": {"type": "string"},
"x-openstack": {"min-ver": "1.17"},
},
"parent_provider_uuid": {
"type": ["string", "null"],
"x-openstack": {"min-ver": "1.29"},
},
"root_provider_uuid": {
"type": ["string", "null"],
"x-openstack": {"min-ver": "1.29"},
},
},
"required": ["resources"],
}
},
},
},
}
ALLOCATION_CANDIDATE_LIST_PARAMETERS: dict[str, Any] = {
"resources": {
"in": "query",
"name": "resources",
"description": "A comma-separated list of strings indicating an amount of resource of a specified class that providers in each allocation request must collectively have the capacity and availability to serve: ``resources=VCPU:4,DISK_GB:64,MEMORY_MB:2048`` These resources may be satisfied by any provider in the same non-sharing tree or associated via aggregate.",
"schema": {"type": "string"},
},
"required": {
"in": "query",
"name": "required",
"description": "A comma-separated list of traits that a provider must have:\n ``required=HW_CPU_X86_AVX,HW_CPU_X86_SSE``\n Allocation requests in the response will be for resource providers that have capacity for all requested resources and the set of those resource providers will collectively contain all of the required traits. These traits may be satisfied by any provider in the same non-sharing tree or associated via aggregate as far as that provider also contributes resource to the request. Starting from microversion 1.22 traits which are forbidden from any resource provider contributing resources to the request may be expressed by prefixing a trait with a `!`.\nStarting from microversion 1.39 the required query parameter can be repeated. The trait lists from the repeated parameters are AND-ed together. So:\n``required=T1,!T2&required=T3`` means T1 and not T2 and T3.\n Also starting from microversion 1.39 the required parameter supports the syntax:\n``required=in:T1,T2,T3`` which means T1 or T2 or T3.\nMixing forbidden traits into an in: prefixed value is not supported and rejected. But mixing a normal trait list and an in: prefixed trait list in two query params within the same request is supported. So: ``required=in:T3,T4&required=T1,!T2`` is supported and it means T1 and not T2 and (T3 or T4).",
"schema": {"type": "array", "items": {"type": "string"}},
"style": "form",
"explode": True,
"x-openstack": {"min-ver": "1.17"},
},
"member_of": {
"in": "query",
"name": "member_of",
"description": "A string representing an aggregate uuid; or the prefix in: followed by a comma-separated list of strings representing aggregate uuids. The resource providers in the allocation request in the response must directly or via the root provider be associated with the aggregate or aggregates identified by uuid:\n``member_of=5e08ea53-c4c6-448e-9334-ac4953de3cfa``, ``member_of=in:42896e0d-205d-4fe3-bd1e-100924931787,5e08ea53-c4c6-448e-9334-ac4953de3cfa``\nStarting from microversion 1.24 specifying multiple member_of query string parameters is possible. Multiple member_of parameters will result in filtering providers that are directly or via root provider associated with aggregates listed in all of the member_of query string values. For example, to get the providers that are associated with aggregate A as well as associated with any of aggregates B or C, the user could issue the following query: ``member_of=AGGA_UUID&member_of=in:AGGB_UUID,AGGC_UUID``\nStarting from microversion 1.32 specifying forbidden aggregates is supported in the member_of query string parameter. Forbidden aggregates are prefixed with a !. This negative expression can also be used in multiple member_of parameters: ``member_of=AGGA_UUID&member_of=!AGGB_UUID`` would translate logically to “Candidate resource providers must be in AGGA and not in AGGB.” We do NOT support ! on the values within in:, but we support !in:. Both of the following two example queries return candidate resource providers that are NOT in AGGA, AGGB, or AGGC: ``member_of=!in:AGGA_UUID,AGGB_UUID,AGGC_UUID``, ``member_of=!AGGA_UUID&member_of=!AGGB_UUID&member_of=!AGGC_UUID``\nWe do not check if the same aggregate uuid is in both positive and negative expression to return 400 BadRequest. We still return 200 for such cases. For example: ``member_of=AGGA_UUID&member_of=!AGGA_UUID`` would return empty allocation_requests and provider_summaries, while: ``member_of=in:AGGA_UUID,AGGB_UUID&member_of=!AGGA_UUID`` would return resource providers that are NOT in AGGA but in AGGB.",
"schema": {"type": "array", "items": {"type": "string"}},
"style": "form",
"explode": True,
"x-openstack": {"min-ver": "1.21"},
},
"in_tree": {
"in": "query",
"name": "in_tree",
"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.",
"schema": {"type": "string"},
"x-openstack": {"min-ver": "1.13"},
},
"group_policy": {
"in": "query",
"name": "group_policy",
"description": "When more than one resourcesN query parameter is supplied, group_policy is required to indicate how the groups should interact. With group_policy=none, separate groupings - with or without a suffix - may or may not be satisfied by the same provider. With group_policy=isolate, suffixed groups are guaranteed to be satisfied by different providers - though there may still be overlap with the suffixless group.",
"schema": {"type": "string"},
"x-openstack": {"min-ver": "1.25"},
},
"limit": {
"in": "query",
"name": "limit",
"description": "A positive integer used to limit the maximum number of allocation candidates returned in the response.",
"schema": {"type": "integer"},
"x-openstack": {"min-ver": "1.16"},
},
"root_required": {
"in": "query",
"name": "root_required",
"description": "A comma-separated list of trait requirements that the root provider of the (non-sharing) tree must satisfy: ``root_required=COMPUTE_SUPPORTS_MULTI_ATTACH,!CUSTOM_WINDOWS_LICENSED`` Allocation requests in the response will be limited to those whose (non-sharing) trees root provider satisfies the specified trait requirements. Traits which are forbidden (must not be present on the root provider) are expressed by prefixing the trait with a !.",
"schema": {"type": "string"},
"x-openstack": {"min-ver": "1.35"},
},
"same_subtree": {
"in": "query",
"name": "same_subtree",
"description": "A comma-separated list of request group suffix strings ($S). Each must exactly match a suffix on a granular group somewhere else in the request. Importantly, the identified request groups need not have a resources[$S]. If this is provided, at least one of the resource providers satisfying a specified request group must be an ancestor of the rest. The same_subtree query parameter can be repeated and each repeat group is treated independently.",
"schema": {"type": "string"},
"x-openstack": {"min-ver": "1.36"},
},
}
def _post_process_operation_hook(
openapi_spec, operation_spec, path: str | None = None
):
"""Hook to allow service specific generator to modify details"""
operationId = operation_spec.operationId
if operationId == "allocation_candidates:get":
for key, val in ALLOCATION_CANDIDATE_LIST_PARAMETERS.items():
openapi_spec.components.parameters.setdefault(
key, ParameterSchema(**val)
)
ref = f"#/components/parameters/{key}"
if ref not in [x.ref for x in operation_spec.parameters]:
operation_spec.parameters.append(ParameterSchema(ref=ref))
def _get_schema_ref(
openapi_spec, name, description=None, schema_def=None, action_name=None
) -> tuple[str | None, str | None, bool]:
mime_type: str = "application/json"
ref: str
if name == "Allocation_CandidatesList_Allocation_CandidatesResponse":
openapi_spec.components.schemas.setdefault(
name, TypeSchema(**ALLOCATION_CANDIDATES_SCHEMA)
)
ref = f"#/components/schemas/{name}"
else:
return (None, None, False)
return (ref, mime_type, True)