Merge "Remove some hardcoded nova schemas"

This commit is contained in:
Zuul 2024-12-14 21:17:42 +00:00 committed by Gerrit Code Review
commit 471f414492
4 changed files with 82 additions and 322 deletions

@ -296,7 +296,19 @@ class JsonSchemaParser:
return PrimitiveAny()
if not type_ and "format" in schema:
return ConstraintString(**schema)
raise RuntimeError("Cannot determine type for %s", schema)
const = schema.get("const")
if const is not None:
if isinstance(const, str):
obj = ConstraintString(**schema)
return obj
if isinstance(const, int):
obj = ConstraintInteger(**schema)
return obj
else:
raise RuntimeError(
"Unsupported const type", const, type(const)
)
raise RuntimeError("Cannot determine type", schema)
def parse_object(
self,

@ -160,67 +160,78 @@ class OpenStackServerSourceBase:
openapi_parser = model.OpenAPISchemaParser()
for res, res_spec in metadata.resources.items():
for operation_name, operation_spec in res_spec.operations.items():
operation_id = operation_spec.operation_id
try:
operation_id = operation_spec.operation_id
(path, method, spec) = common.find_openapi_operation(
openapi_spec, operation_id
)
resource_name = common.get_resource_names_from_url(path)[-1]
(path, method, spec) = common.find_openapi_operation(
openapi_spec, operation_id
)
resource_name = common.get_resource_names_from_url(path)[
-1
]
# Parse params
for param in openapi_spec["paths"][path].get("parameters", []):
openapi_parser.parse_parameter(param)
# Parse params
for param in openapi_spec["paths"][path].get(
"parameters", []
):
openapi_parser.parse_parameter(param)
op_name: str | None = None
response_key: str | None = None
sdk_target = operation_spec.targets.get("rust-sdk")
if sdk_target:
op_name = sdk_target.operation_name
response_key = sdk_target.response_key
op_name: str | None = None
response_key: str | None = None
sdk_target = operation_spec.targets.get("rust-sdk")
if sdk_target:
op_name = sdk_target.operation_name
response_key = sdk_target.response_key
operation_variants = common.get_operation_variants(
spec, op_name or operation_name
)
for operation_variant in operation_variants:
operation_body = operation_variant.get("body")
if operation_body:
openapi_parser.parse(
operation_body, ignore_read_only=True
)
if method.upper() != "HEAD":
response = common.find_response_schema(
spec["responses"],
response_key or resource_name,
(
operation_name
if operation_spec.operation_type == "action"
else None
),
operation_variants = common.get_operation_variants(
spec, op_name or operation_name
)
if response:
if response_key:
response_key = (
response_key
if response_key != "null"
else None
for operation_variant in operation_variants:
operation_body = operation_variant.get("body")
if operation_body:
openapi_parser.parse(
operation_body, ignore_read_only=True
)
else:
response_key = resource_name
response_def, _ = common.find_resource_schema(
response, None, response_key
if method.upper() != "HEAD":
response = common.find_response_schema(
spec["responses"],
response_key or resource_name,
(
operation_name
if operation_spec.operation_type == "action"
else None
),
)
if response_def:
if response_def.get(
"type", "object"
) == "object" or (
isinstance(response_def.get("type"), list)
and "object" in response_def["type"]
):
openapi_parser.parse(response_def)
if response:
if response_key:
response_key = (
response_key
if response_key != "null"
else None
)
else:
response_key = resource_name
response_def, _ = common.find_resource_schema(
response, None, response_key
)
if response_def:
if response_def.get(
"type", "object"
) == "object" or (
isinstance(response_def.get("type"), list)
and "object" in response_def["type"]
):
openapi_parser.parse(response_def)
except Exception as ex:
logging.exception(
"Error validating %s %s %s", res, operation_name, spec
)
raise
def _sanitize_param_ver_info(self, openapi_spec, min_api_version):
# Remove min_version of params if it matches to min_api_version
@ -1369,6 +1380,7 @@ class OpenStackServerSourceBase:
getattr(f, "_response_body_schema", {}),
)
response_body_schema = obj
print(f"the response schema is {response_body_schema}")
if "query_params_schema" in closure_locals or hasattr(
f, "_request_query_schema"
):

@ -156,8 +156,6 @@ class NovaGenerator(OpenStackServerSourceBase):
schema_def=None,
action_name=None,
):
from nova.api.openstack.compute.schemas import flavors
schema: None = None
ref: str | None
mime_type: str | None = "application/json"
@ -351,94 +349,12 @@ class NovaGenerator(OpenStackServerSourceBase):
)
ref = f"#/components/schemas/{name}"
# /flavors/...
elif name == "FlavorsListResponse":
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.FLAVORS_LIST_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name == "FlavorsDetailResponse":
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.FLAVORS_LIST_DETAIL_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name in [
"FlavorsCreateResponse",
"FlavorShowResponse",
"FlavorUpdateResponse",
]:
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.FLAVOR_CONTAINER_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name == "FlavorUpdateRequest":
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**flavors.update_v2_55)
)
ref = f"#/components/schemas/{name}"
elif name in [
"FlavorsOs_Flavor_AccessListResponse",
"FlavorsActionAddtenantaccessResponse",
"FlavorsActionRemovetenantaccessResponse",
]:
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.FLAVOR_ACCESSES_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name in [
"FlavorsOs_Extra_SpecsListResponse",
"FlavorsOs_Extra_SpecsCreateResponse",
]:
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.FLAVOR_EXTRA_SPECS_LIST_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name in [
"FlavorsOs_Extra_SpecShowResponse",
"FlavorsOs_Extra_SpecUpdateResponse",
]:
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.FLAVOR_EXTRA_SPEC_SCHEMA)
)
ref = f"#/components/schemas/{name}"
# /limits
elif name == "LimitsListResponse":
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.LIMITS_SCHEMA)
)
ref = f"#/components/schemas/{name}"
# /os-aggregates
elif name == "Os_AggregatesListResponse":
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.AGGREGATE_LIST_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name in [
"Os_AggregatesCreateResponse",
"Os_AggregateShowResponse",
"Os_AggregateUpdateResponse",
"Os_AggregatesActionAdd_HostResponse",
"Os_AggregatesActionRemove_HostResponse",
"Os_AggregatesActionSet_MetadataResponse",
]:
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.AGGREGATE_CONTAINER_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name == "Os_AggregatesImagesResponse":
return (None, None)
# /os-assisted-volume-snapshots
elif name == "Os_Assisted_Volume_SnapshotsCreateResponse":
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.VOLUME_SNAPSHOT_SCHEMA)
)
ref = f"#/components/schemas/{name}"
# /os-assisted-volume-snapshots
elif name == "Os_Assisted_Volume_SnapshotsCreateResponse":
schema = openapi_spec.components.schemas.setdefault(
name, TypeSchema(**nova_schemas.VOLUME_SNAPSHOT_SCHEMA)
)
ref = f"#/components/schemas/{name}"
# /os-availability-zone
elif name == "Os_Availability_ZoneListResponse":
schema = openapi_spec.components.schemas.setdefault(
@ -634,7 +550,11 @@ class NovaGenerator(OpenStackServerSourceBase):
return (None, None)
else:
(ref, mime_type) = super()._get_schema_ref(
openapi_spec, name, description, action_name=action_name
openapi_spec,
name,
description,
schema_def=schema_def,
action_name=action_name,
)
if action_name and schema:
if not schema.openstack:

@ -13,7 +13,6 @@
import copy
from typing import Any
from nova.api.openstack.compute.schemas import flavors_extraspecs
from nova.api.openstack.compute.schemas import quota_sets
from nova.api.openstack.compute.schemas import remote_consoles
from nova.api.validation import parameter_types
@ -93,124 +92,6 @@ SERVER_TOPOLOGY_SCHEMA: dict[str, Any] = {
},
}
FLAVOR_EXTRA_SPEC_SCHEMA: dict[str, Any] = {
"minProperties": 1,
"maxProperties": 1,
"examples": {"JSON Request": {"hw:numa_nodes": "1"}},
**flavors_extraspecs.metadata,
}
FLAVOR_EXTRA_SPECS_SCHEMA: dict[str, Any] = flavors_extraspecs.metadata
FLAVOR_EXTRA_SPECS_LIST_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "A dictionary of the flavors extra-specs key-and-value pairs. It appears in the os-extra-specs “create” REQUEST body, as well as the os-extra-specs “create” and “list” RESPONSE body.",
"properties": {"extra_specs": flavors_extraspecs.metadata},
}
FLAVOR_SHORT_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
"id": {"type": "string", "format": "uuid"},
"name": {"type": "string"},
"description": {"type": "string"},
"links": LINKS_SCHEMA,
},
}
FLAVOR_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The display name of a flavor.",
},
"id": {
"type": "string",
"description": "The ID of the flavor. While people often make this look like an int, this is really a string.",
"minLength": 1,
"maxLength": 255,
"pattern": "^(?! )[a-zA-Z0-9. _-]+(?<! )$",
},
"ram": {
"description": "The amount of RAM a flavor has, in MiB.",
**parameter_types.flavor_param_positive,
},
"vcpus": {
"description": "The number of virtual CPUs that will be allocated to the server.",
**parameter_types.flavor_param_positive,
},
"disk": {
"description": "The size of the root disk that will be created in GiB. If 0 the root disk will be set to exactly the size of the image used to deploy the instance. However, in this case the scheduler cannot select the compute host based on the virtual image size. Therefore, 0 should only be used for volume booted instances or for testing purposes. Volume-backed instances can be enforced for flavors with zero root disk via the os_compute_api:servers:create:zero_disk_flavor policy rule.",
**parameter_types.flavor_param_non_negative,
},
"OS-FLV-EXT-DATA:ephemeral": {
"description": "The size of the ephemeral disk that will be created, in GiB. Ephemeral disks may be written over on server state changes. So should only be used as a scratch space for applications that are aware of its limitations. Defaults to 0.",
**parameter_types.flavor_param_non_negative,
},
"swap": {
"description": "The size of a dedicated swap disk that will be allocated, in MiB. If 0 (the default), no dedicated swap disk will be created. Currently, the empty string () is used to represent 0. As of microversion 2.75 default return value of swap is 0 instead of empty string.",
**parameter_types.flavor_param_non_negative,
},
"rxtx_factor": {
"description": "The receive / transmit factor (as a float) that will be set on ports if the network backend supports the QOS extension. Otherwise it will be ignored. It defaults to 1.0.",
"type": ["number", "string"],
"pattern": r"^[0-9]+(\.[0-9]+)?$",
"minimum": 0,
"exclusiveMinimum": True,
"maximum": 3.40282e38,
},
"os-flavor-access:is_public": {
"description": "Whether the flavor is public (available to all projects) or scoped to a set of projects. Default is True if not specified.",
**parameter_types.boolean,
},
"extra_specs": FLAVOR_EXTRA_SPECS_SCHEMA,
"links": LINKS_SCHEMA,
},
"additionalProperties": False,
}
FLAVOR_CONTAINER_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "Single flavor details",
"properties": {"flavor": copy.deepcopy(FLAVOR_SCHEMA)},
}
FLAVORS_LIST_SCHEMA: dict[str, Any] = {
"description": "Flavors list response",
"type": "object",
"properties": {
"flavors": {
"type": "array",
"items": copy.deepcopy(FLAVOR_SHORT_SCHEMA),
}
},
}
FLAVORS_LIST_DETAIL_SCHEMA: dict[str, Any] = {
"description": "Detailed flavors list response",
"type": "object",
"properties": {
"flavors": {"type": "array", "items": copy.deepcopy(FLAVOR_SCHEMA)}
},
}
FLAVOR_ACCESS_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
"flavor_id": {"type": "string", "format": "uuid"},
"tenant_id": {"type": "string", "format": "uuid"},
},
}
FLAVOR_ACCESSES_SCHEMA: dict[str, Any] = {
"description": "A list of objects, each with the keys flavor_id and tenant_id.",
"type": "object",
"properties": {
"flavor_access": {
"type": "array",
"items": copy.deepcopy(FLAVOR_ACCESS_SCHEMA),
}
},
}
LIMITS_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
@ -274,71 +155,6 @@ LIMITS_SCHEMA: dict[str, Any] = {
},
}
AGGREGATE_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "The host aggregate object",
"properties": {
"availability_zone": {
"type": "string",
"description": "The availability zone of the host aggregate.",
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "The date and time when the resource was created.",
},
"deleted": {
"type": "boolean",
"description": "A boolean indicates whether this aggregate is deleted or not, if it has not been deleted, false will appear.",
},
"deleted_at": {
"type": ["string", "null"],
"format": "date-time",
"description": "The date and time when the resource was deleted. If the resource has not been deleted yet, this field will be null.",
},
"id": {
"type": "integer",
"description": "The ID of the host aggregate.",
},
"metadata": parameter_types.metadata,
"hosts": {
"type": "array",
"description": "A list of host ids in this aggregate.",
"items": {"type": "string"},
},
"updated_at": {
"type": ["string", "null"],
"format": "date-time",
"description": "The date and time when the resource was updated, if the resource has not been updated, this field will show as null.",
},
"uuid": {
"type": "string",
"format": "uuid",
"description": "The UUID of the host aggregate. New in version 2.41",
"x-openstack": {"min-ver": "2.41"},
},
},
}
AGGREGATE_CONTAINER_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "Aggregate object.",
"properties": {"aggregate": copy.deepcopy(AGGREGATE_SCHEMA)},
}
AGGREGATE_LIST_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "The list of existing aggregates.",
"properties": {
"aggregates": {
"type": "array",
"description": "The list of existing aggregates.",
"items": copy.deepcopy(AGGREGATE_SCHEMA),
}
},
}
VOLUME_SNAPSHOT_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "A partial representation of a snapshot that is used to create a snapshot.",