diff --git a/codegenerator/openapi/base.py b/codegenerator/openapi/base.py index de20908..e83883d 100644 --- a/codegenerator/openapi/base.py +++ b/codegenerator/openapi/base.py @@ -641,6 +641,7 @@ class OpenStackServerSourceBase: start_version, end_version, action_name, + operation_name, ) if hasattr(func, "_wsme_definition"): @@ -1178,7 +1179,8 @@ class OpenStackServerSourceBase: mode: str, start_version, end_version, - action_name: str | None, + action_name: str | None = None, + operation_name: str | None = None, ): """Extract schemas from the decorated method.""" # Unwrap operation decorators to access all properties diff --git a/codegenerator/openapi/keystone.py b/codegenerator/openapi/keystone.py index b1049c9..f4bcf4b 100644 --- a/codegenerator/openapi/keystone.py +++ b/codegenerator/openapi/keystone.py @@ -395,6 +395,7 @@ class KeystoneGenerator(OpenStackServerSourceBase): start_version, end_version, None, + None, ) ) diff --git a/codegenerator/openapi/octavia.py b/codegenerator/openapi/octavia.py index 91517d7..4924a47 100644 --- a/codegenerator/openapi/octavia.py +++ b/codegenerator/openapi/octavia.py @@ -13,17 +13,574 @@ import inspect from multiprocessing import Process from pathlib import Path +from typing import Any from unittest import mock import fixtures from codegenerator.common.schema import SpecSchema -from codegenerator.openapi.base import OpenStackServerSourceBase +from codegenerator.openapi.base import ( + OpenStackServerSourceBase, + _convert_wsme_to_jsonschema, +) from codegenerator.openapi.utils import merge_api_ref_doc from ruamel.yaml.scalarstring import LiteralScalarString +TAGS_PARAMETERS: dict[str, Any] = { + "tags": { + "type": "array", + "items": {"type": "string"}, + "explode": False, + "style": "form", + "description": "Return the list of entities that have this tag or tags.", + }, + "tags-any": { + "type": "array", + "items": {"type": "string"}, + "explode": False, + "style": "form", + "description": "Return the list of entities that have one or more of the given tags.", + }, + "not-tags": { + "type": "array", + "items": {"type": "string"}, + "explode": False, + "style": "form", + "description": "Return the list of entities that do not have one or more of the given tags.", + }, + "not-tags-any": { + "type": "array", + "items": {"type": "string"}, + "explode": False, + "style": "form", + "description": "Return the list of entities that do not have at least one of the given tags.", + }, +} + +STATUSES: dict[str, Any] = { + "provisioning_status": { + "type": "string", + "description": "The provisioning status of the resource.", + "enum": [ + "ACTIVE", + "DELETED", + "ERROR", + "PENDING_CREATE", + "PENDING_UPDATE", + "PENDING_DELETE", + ], + }, + "operating_status": { + "type": "string", + "description": "The operating status of the resource.", + "enum": [ + "ONLINE", + "DRAINING", + "OFFLINE", + "DEGRADED", + "ERROR", + "NO_MONITOR", + ], + }, +} + + +LOADBALANCER_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A human-readable description for the resource.", + }, + "name": { + "type": "string", + "description": "Human-readable name of the resource.", + }, + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the resource", + }, + "project_id": { + "type": "string", + "format": "uuid", + "description": "The ID of the project owning this resource.", + }, + "flavor_id": { + "type": "string", + "format": "uuid", + "description": "The ID of the flavor.", + }, + "provider": { + "type": "string", + "description": "Provider name for the load balancer.", + }, + "vip_address": { + "type": "string", + "description": "The IP address of the Virtual IP (VIP).", + }, + "vip_network_id": { + "type": "string", + "format": "uuid", + "description": "The ID of the network for the Virtual IP (VIP).", + }, + "vip_port_id": { + "type": "string", + "format": "uuid", + "description": "The ID of the Virtual IP (VIP) port.", + }, + "vip_subnet_id": { + "type": "string", + "format": "uuid", + "description": "The ID of the subnet for the Virtual IP (VIP).", + }, + "vip_qos_policy_id": { + "type": "string", + "format": "uuid", + "description": "The ID of the QoS Policy which will apply to the Virtual IP (VIP).", + }, + "availability_zone": { + "type": "string", + "description": "An availability zone name.", + }, + **STATUSES, + **TAGS_PARAMETERS, + }, +} + +LISTENER_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the resource", + }, + "description": { + "type": "string", + "description": "A human-readable description for the resource.", + }, + "name": { + "type": "string", + "description": "Human-readable name of the resource.", + }, + "project_id": { + "type": "string", + "description": "The ID of the project owning this resource.", + }, + "connection_limit": { + "type": "string", + "description": "The maximum number of connections permitted for this listener. Default value is -1 which represents infinite connections or a default value defined by the provider driver.", + }, + "default_pool_id": { + "type": "string", + "description": "The ID of the pool used by the listener if no L7 policies match.", + }, + "protocol": { + "type": "string", + "description": "The protocol for the resource.", + "enum": [ + "HTTP", + "HTTPS", + "SCTP", + "PROMETHEUS", + "TCP", + "TERMINATED_HTTPS", + "UDP", + ], + }, + "protocol_port": { + "type": "integer", + "description": "The protocol port number for the resource.", + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was created.", + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was last updated.", + }, + "load_balancer_id": { + "type": "string", + "description": "Load balancer ID", + }, + "timeout_client_data": { + "type": "integer", + "description": "Frontend client inactivity timeout in milliseconds.", + }, + "timeout_member_connect": { + "type": "integer", + "description": "Backend member connection timeout in milliseconds.", + }, + "timeout_member_data": { + "type": "integer", + "description": "Backend member inactivity timeout in milliseconds.", + }, + "timeout_tcp_inspect": { + "type": "integer", + "description": "Time, in milliseconds, to wait for additional TCP packets for content inspection.", + }, + "tls_ciphers": { + "type": "string", + "description": "List of ciphers in OpenSSL format ", + }, + "tls_versions": { + "type": "string", + "description": "A list of TLS protocol versions.", + }, + "alpn_protocols": { + "type": "string", + "description": "A list of ALPN protocols. Available protocols: http/1.0, http/1.1, h2", + }, + "hsts_max_age": { + "type": "integer", + "description": "The value of the max_age directive for the Strict-Transport-Security HTTP response header.", + }, + "admin_state_up": { + "type": "boolean", + "description": "The administrative state of the resource", + }, + "hsts_include_subdomains": { + "type": "boolean", + "description": "Defines whether the includeSubDomains directive should be added to the Strict-Transport-Security HTTP response header.", + }, + "hsts_preload": { + "type": "boolean", + "description": "Defines whether the preload directive should be added to the Strict-Transport-Security HTTP response header.", + }, + **STATUSES, + **TAGS_PARAMETERS, + }, +} + +POOL_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the resource", + }, + "description": { + "type": "string", + "description": "A human-readable description for the resource.", + }, + "name": { + "type": "string", + "description": "Human-readable name of the resource.", + }, + "project_id": { + "type": "string", + "description": "The ID of the project owning this resource.", + }, + "admin_state_up": { + "type": "boolean", + "description": "The administrative state of the resource", + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was created.", + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was last updated.", + }, + "tls_enabled": {"type": "boolean"}, + "tls_ciphers": { + "type": "string", + "description": "List of ciphers in OpenSSL format ", + }, + "tls_versions": { + "type": "string", + "description": "A list of TLS protocol versions.", + }, + "alpn_protocols": { + "type": "string", + "description": "A list of ALPN protocols. Available protocols: http/1.0, http/1.1, h2", + }, + **STATUSES, + **TAGS_PARAMETERS, + }, +} + +POOL_MEMBER_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the resource", + }, + "description": { + "type": "string", + "description": "A human-readable description for the resource.", + }, + "name": { + "type": "string", + "description": "Human-readable name of the resource.", + }, + "project_id": { + "type": "string", + "description": "The ID of the project owning this resource.", + }, + "admin_state_up": { + "type": "boolean", + "description": "The administrative state of the resource", + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was created.", + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was last updated.", + }, + "address": { + "type": "string", + "description": "The IP address of the backend member server.", + }, + "protocol_port": { + "type": "integer", + "description": "The protocol port number the backend member server is listening on.", + }, + "subnet_id": { + "type": "string", + "format": "uuid", + "description": "The subnet ID the member service is accessible from.", + }, + "weight": { + "type": "integer", + "description": "The weight of a member determines the portion of requests or connections it services compared to the other members of the pool.", + }, + "monitor_address": { + "type": "string", + "description": "An alternate IP address used for health monitoring a backend member.", + }, + "monitor_port": { + "type": "string", + "description": "An alternate protocol port used for health monitoring a backend member.", + }, + "backup": { + "type": "boolean", + "description": "Is the member a backup?", + }, + **STATUSES, + **TAGS_PARAMETERS, + }, +} + +HEALTHMONITOR_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The ID of the resource", + }, + "description": { + "type": "string", + "description": "A human-readable description for the resource.", + }, + "name": { + "type": "string", + "description": "Human-readable name of the resource.", + }, + "project_id": { + "type": "string", + "description": "The ID of the project owning this resource.", + }, + "admin_state_up": { + "type": "boolean", + "description": "The administrative state of the resource", + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was created.", + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The UTC date and timestamp when the resource was last updated.", + }, + "delay": { + "type": "integer", + "description": "The time, in seconds, between sending probes to members.", + }, + "expected_codes": { + "type": "string", + "description": "The list of HTTP status codes expected in response from the member to declare it healthy.", + }, + "http_method": { + "type": "string", + "description": "The HTTP method that the health monitor uses for requests.", + "enum": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE", + ], + }, + "max_retries": { + "type": "integer", + "description": "The number of successful checks before changing the operating status of the member to ONLINE. A valid value is from 1 to 10.", + }, + "max_retries_down": { + "type": "integer", + "description": "The number of allowed check failures before changing the operating status of the member to ERROR. A valid value is from 1 to 10.", + }, + "pool_id": {"type": "string", "description": "The ID of the pool."}, + "timeout": { + "type": "integer", + "description": "The maximum time, in seconds, that a monitor waits to connect before it times out.", + }, + "type": { + "type": "string", + "description": "The type of health monitor.", + "enum": [ + "HTTP", + "HTTPS", + "PING", + "SCTP", + "TCP", + "TLS-HELLO", + "UDP-CONNECT", + ], + }, + "url_path": { + "type": "string", + "description": "The HTTP URL path of the request sent by the monitor to test the health of a backend member. Must be a string that begins with a forward slash (/).", + }, + **STATUSES, + **TAGS_PARAMETERS, + }, +} + +AMPHORAE_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": {"type": "string", "description": ""}, + "loadbalancer_id": {"type": "string", "description": ""}, + "compute_id": {"type": "string", "description": ""}, + "lb_network_ip": {"type": "string", "description": ""}, + "vrrp_ip": {"type": "string", "description": ""}, + "ha_ip": {"type": "string", "description": ""}, + "vrrp_port_id": {"type": "string", "description": ""}, + "ha_port_id": {"type": "string", "description": ""}, + "cert_expiration": {"type": "string", "description": ""}, + "cert_busy": {"type": "string", "description": ""}, + "role": {"type": "string", "description": ""}, + "status": {"type": "string", "description": ""}, + "vrrp_interface": {"type": "string", "description": ""}, + "vrrp_id": {"type": "string", "description": ""}, + "vrrp_priority": {"type": "string", "description": ""}, + "cached_zone": {"type": "string", "description": ""}, + "created_at": {"type": "string", "description": ""}, + "updated_at": {"type": "string", "description": ""}, + "image_id": {"type": "string", "description": ""}, + "compute_flavor": {"type": "string", "description": ""}, + }, +} + +AVAILABILITY_ZONE_PROFILE_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": {"type": "string", "description": ""}, + "name": {"type": "string", "description": ""}, + "provider_name": {"type": "string", "description": ""}, + "availability_zone_data": {"type": "string"}, + }, +} + +AVAILABILITY_ZONE_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "name": {"type": "string", "description": ""}, + "description": {"type": "string", "description": ""}, + "availability_zone_profile_id": {"type": "string", "description": ""}, + "status": {"type": "string"}, + }, +} + +FLAVOR_PROFILE_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": {"type": "string", "description": ""}, + "name": {"type": "string", "description": ""}, + "provider_name": {"type": "string", "description": ""}, + "flavor_data": {"type": "string", "description": ""}, + }, +} + +FLAVOR_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "id": {"type": "string", "description": ""}, + "name": {"type": "string", "description": ""}, + "description": {"type": "string", "description": ""}, + "flavor_profile_id": {"type": "string"}, + "enabled": {"type": "boolean", "description": ""}, + }, +} + +L7POLICY_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "action": {"type": "string", "description": ""}, + "description": {"type": "string", "description": ""}, + "listener_id": {"type": "string", "description": ""}, + "name": {"type": "string", "description": ""}, + "position": {"type": "string", "description": ""}, + "redirect_pool_id": {"type": "string", "description": ""}, + "redirect_url": {"type": "string", "description": ""}, + "provisioning_status": {"type": "string", "description": ""}, + "operating_status": {"type": "string", "description": ""}, + "redirect_prefix": {"type": "string", "description": ""}, + "project_id": {"type": "string", "description": ""}, + "admin_state_up": {"type": "boolean", "description": ""}, + }, +} + +L7RULE_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "compare_type": {"type": "string", "description": ""}, + "created_at": {"type": "string", "description": ""}, + "invert": {"type": "string", "description": ""}, + "key": {"type": "string", "description": ""}, + "project_id": {"type": "string", "description": ""}, + "provisioning_status": {"type": "string", "description": ""}, + "type": {"type": "string", "description": ""}, + "updated_at": {"type": "string", "description": ""}, + "rule_value": {"type": "string", "description": ""}, + "operating_status": {"type": "string", "description": ""}, + "admin_state_up": {"type": "boolean", "description": ""}, + }, +} + +PROVIDER_QUERY_PARAMETERS: dict[str, Any] = { + "type": "object", + "properties": { + "description": {"type": "string", "description": ""}, + "name": {"type": "string", "description": ""}, + }, +} + + class OctaviaGenerator(OpenStackServerSourceBase): URL_TAG_MAP = { "/lbaas/listeners": "listeners", @@ -396,3 +953,72 @@ class OctaviaGenerator(OpenStackServerSourceBase): lnk.symlink_to(impl_path.name) return impl_path + + def _process_decorators( + self, + func, + path_resource_names: list[str], + openapi_spec, + mode: str, + start_version, + end_version, + action_name: str | None = None, + operation_name: str | None = None, + ): + ( + query_params_versions, + body_schemas, + response_body_schema, + expected_errors, + ) = super()._process_decorators( + func, + path_resource_names, + openapi_spec, + mode, + start_version, + end_version, + action_name, + operation_name, + ) + + if operation_name in ["list", "index"]: + # NOTE(gtema) sadly there is no reasonable way how to extract + # supported query parameters out of octavia so we need to hardcode + # them for now + qp: dict[str, Any] | None = None + if path_resource_names == ["lbaas", "loadbalancers"]: + qp = LOADBALANCER_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "listeners"]: + qp = LISTENER_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "pools"]: + qp = POOL_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "pools", "members"]: + qp = POOL_MEMBER_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "healthmonitors"]: + qp = HEALTHMONITOR_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "l7policies"]: + qp = L7POLICY_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "l7policies", "rules"]: + qp = L7RULE_QUERY_PARAMETERS + elif path_resource_names == ["octavia", "amphorae"]: + qp = AMPHORAE_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "availabilityzoneprofiles"]: + qp = AVAILABILITY_ZONE_PROFILE_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "availabilityzones"]: + qp = AVAILABILITY_ZONE_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "flavorprofiles"]: + qp = FLAVOR_PROFILE_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "flavors"]: + qp = FLAVOR_QUERY_PARAMETERS + elif path_resource_names == ["lbaas", "providers"]: + qp = PROVIDER_QUERY_PARAMETERS + + if qp: + query_params_versions.append((qp, None, None)) + + return ( + query_params_versions, + body_schemas, + response_body_schema, + expected_errors, + )