Skip to content
This repository has been archived by the owner on Apr 10, 2024. It is now read-only.

Diverse changes #13

Merged
merged 4 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions codegenerator/common/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def get_sample(self):
class Null(BasePrimitiveType):
type_hint: str = "Value"
imports: set[str] = set(["serde_json::Value"])
builder_macros: set[str] = set(['default = "Value::Null"'])
builder_macros: set[str] = set([])
clap_macros: set[str] = set()
original_data_type: BaseCompoundType | BaseCompoundType | None = None

Expand Down Expand Up @@ -352,11 +352,15 @@ def get_sample(self):

def variant_serde_macros(self, variant: str):
"""Return serde macros"""
return (
"#[serde("
+ ", ".join([f'rename="{x}"' for x in self.variants[variant]])
+ ")]"
)
macros = set([])
vals = self.variants[variant]
if len(vals) > 1:
macros.add(f'rename(serialize = "{sorted(vals)[0]}")')
for val in vals:
macros.add(f'alias="{val}"')
else:
macros.add(f'rename = "{list(vals)[0]}"')
return "#[serde(" + ", ".join(sorted(macros)) + ")]"


class RequestParameter(BaseModel):
Expand Down Expand Up @@ -762,6 +766,13 @@ def _simplify_oneof_combinations(self, type_model, kinds):
kinds.clear()
jsonval_klass = self.primitive_type_mapping[model.PrimitiveAny]
kinds.append({"local": jsonval_klass(), "class": jsonval_klass})
elif len(set(kinds_classes)) == 1 and string_klass in kinds_classes:
# in the output oneOf of same type (but maybe different formats)
# makes no sense
# Example is server addresses which are ipv4 or ipv6
bck = kinds[0].copy()
kinds.clear()
kinds.append(bck)

def set_models(self, models):
"""Process (translate) ADT models into Rust SDK style"""
Expand Down Expand Up @@ -998,6 +1009,8 @@ def get_operation_variants(spec: dict, operation_name: str):
elif "application/json-patch+json" in content:
mime_type = "application/json-patch+json"
operation_variants.append({"mime_type": mime_type})
elif content == {}:
operation_variants.append({"body": None})
else:
# Explicitly register variant without body
operation_variants.append({"body": None})
Expand Down
1 change: 1 addition & 0 deletions codegenerator/common/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class TypeSchema(BaseModel):
type: Optional[str | List[str]] = None
format: Optional[str] = None
description: Optional[str] = None
summary: str | None = None
default: Optional[Any] = None
items: Optional[Dict[str, Any]] = None
# circular reference cause issues on deserializing
Expand Down
39 changes: 39 additions & 0 deletions codegenerator/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ def generate(
resource_name = "/".join(
[x for x in common.get_resource_names_from_url(path)]
)
if args.service_type == "object-store":
if path == "/v1/{account}":
resource_name = "account"
elif path == "/v1/{account}/{container}":
resource_name = "container"
if path == "/v1/{account}/{object}":
resource_name = "object"
if args.service_type == "compute" and resource_name in [
"agent",
"baremetal_node",
Expand All @@ -84,6 +91,8 @@ def generate(
"security_group_default_rule",
"security_group_rule",
"security_group",
"server/console",
"server/virtual_interface",
"snapshot",
"tenant_network",
"volume",
Expand Down Expand Up @@ -173,6 +182,18 @@ def generate(
and method == "post"
):
operation_key = "action"
elif (
args.service_type == "compute"
and resource_name == "server/security_group"
and method == "get"
):
operation_key = "list"
elif (
args.service_type == "compute"
and resource_name == "server/topology"
and method == "get"
):
operation_key = "list"

elif response_schema and (
method == "get"
Expand Down Expand Up @@ -623,6 +644,24 @@ def post_process_compute_operation(
elif resource_name == "keypair":
if operation_name == "list":
operation.targets["rust-sdk"].response_list_item_key = "keypair"
elif resource_name == "server/instance_action":
if operation_name == "list":
operation.targets["rust-sdk"].response_key = "instanceActions"
operation.targets["rust-cli"].response_key = "instanceActions"
else:
operation.targets["rust-sdk"].response_key = "instanceAction"
operation.targets["rust-cli"].response_key = "instanceAction"
elif resource_name == "server/topology":
if operation_name == "list":
operation.targets["rust-sdk"].response_key = "nodes"
operation.targets["rust-cli"].response_key = "nodes"
elif resource_name == "server/volume_attachment":
if operation_name == "list":
operation.targets["rust-sdk"].response_key = "volumeAttachments"
operation.targets["rust-cli"].response_key = "volumeAttachments"
elif operation_name in ["create", "show", "update"]:
operation.targets["rust-sdk"].response_key = "volumeAttachment"
operation.targets["rust-cli"].response_key = "volumeAttachment"

return operation

Expand Down
12 changes: 12 additions & 0 deletions codegenerator/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,19 @@ def parse_typelist(
parent_name: str | None = None,
ignore_read_only: bool | None = False,
):
if len(schema.get("type")) == 1:
# Bad schema with type being a list of 1 entry
schema["type"] = schema["type"][0]
obj = self.parse_schema(
schema,
results,
name=name,
ignore_read_only=ignore_read_only,
)
return obj

obj = OneOfType()

for kind_type in schema.get("type"):
kind_schema = copy.deepcopy(schema)
kind_schema["type"] = kind_type
Expand Down
22 changes: 19 additions & 3 deletions codegenerator/openapi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ def _process_route(
# Versioned actions in nova can be themelves as a
# version_select wrapped callable (i.e. baremetal.action)
key = closurevars.nonlocals.get("key", None)
slf = closurevars.nonlocals.get("self", None)

if key and key in versioned_methods:
# ACTION with version bounds
if len(versioned_methods[key]) > 1:
Expand All @@ -392,6 +394,20 @@ def _process_route(
end_version = ver_method.end_version
func = ver_method.func
logging.info("Versioned action %s", func)
elif slf and key:
vm = getattr(slf, "versioned_methods", None)
if vm and key in vm:
# ACTION with version bounds
if len(vm[key]) > 1:
raise RuntimeError(
"Multiple versioned methods for action %s",
action,
)
for ver_method in vm[key]:
start_version = ver_method.start_version
end_version = ver_method.end_version
func = ver_method.func
logging.info("Versioned action %s", func)
else:
func = op_name

Expand Down Expand Up @@ -551,8 +567,8 @@ def process_operation(
while hasattr(f, "__wrapped__"):
closure = inspect.getclosurevars(f)
closure_locals = closure.nonlocals
min_ver = closure_locals.get("min_version")
max_ver = closure_locals.get("max_version")
min_ver = closure_locals.get("min_version", start_version)
max_ver = closure_locals.get("max_version", end_version)

if "errors" in closure_locals:
expected_errors = closure_locals["errors"]
Expand Down Expand Up @@ -831,7 +847,7 @@ def process_body_parameters(
if cont_schema_name in openapi_spec.components.schemas:
# if we have already oneOf - add there
cont_schema = openapi_spec.components.schemas[cont_schema_name]
if body_schemas[0] not in [
if cont_schema.oneOf and body_schemas[0] not in [
x["$ref"] for x in cont_schema.oneOf
]:
cont_schema.oneOf.append({"$ref": body_schemas[0]})
Expand Down
6 changes: 6 additions & 0 deletions codegenerator/openapi/nova.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,12 @@ def _get_schema_ref(
name, TypeSchema(**nova_schemas.SERVER_TOPOLOGY_SCHEMA)
)
ref = f"#/components/schemas/{name}"
elif name == "ServersOs_Security_GroupsListResponse":
schema = openapi_spec.components.schemas.setdefault(
name,
TypeSchema(**nova_schemas.SERVER_SECURITY_GROUPS_LIST_SCHEMA),
)
ref = f"#/components/schemas/{name}"
elif name in [
"ServersTagsListResponse",
"ServersTagsUpdate_All",
Expand Down
54 changes: 54 additions & 0 deletions codegenerator/openapi/nova_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,60 @@
}
},
}
SERVER_SECURITY_GROUPS_LIST_SCHEMA: dict[str, Any] = {
"type": "object",
"properties": {
"security_groups": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The ID of the security group.",
},
"name": {
"type": "string",
"description": "The security group name.",
},
"description": {
"type": "string",
"description": "Security group description.",
},
"tenant_id": {
"type": "string",
"format": "uuid",
"description": "The UUID of the tenant in a multi-tenancy cloud.",
},
"rules": {
"type": "array",
"description": "The list of security group rules.",
"items": {
"type": "object",
"properties": {
"id": {"type": "string", "format": "uuid"},
"from_port": {"type": "integer"},
"to_port": {"type": "integer"},
"ip_protocol": {"type": "string"},
"ip_range": {"type": "object"},
"group": {
"type": "object",
"properties": {"name": {"type": "string"}},
},
"parent_group_id": {
"type": "string",
"format": "uuid",
},
},
},
},
},
},
},
},
}


VOLUME_ATTACHMENT_SCHEMA: dict[str, Any] = {
"type": "object",
Expand Down
32 changes: 29 additions & 3 deletions codegenerator/openapi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def merge_api_ref_doc(
:param doc_ver_prefix: Use additional path prefix to find url match

"""
# Set of processed operationIds.
processed_operations: set[str] = set()
with open(api_ref_src, "r") as fp:
html_doc = fp.read()

Expand Down Expand Up @@ -75,6 +77,7 @@ def merge_api_ref_doc(
"span", class_="label"
)
method = method_span.string

# Find operation
path_spec = openapi_spec.paths.get(url)
if (
Expand Down Expand Up @@ -153,6 +156,15 @@ def merge_api_ref_doc(
)
continue

if (
op_spec.operationId in processed_operations
and not url.endswith("/action")
):
# Do not update operation we have already processed
continue
else:
processed_operations.add(op_spec.operationId)

# Find the button in the operaion container to get ID of the
# details section
details_button = op.find("button")
Expand Down Expand Up @@ -216,6 +228,10 @@ def merge_api_ref_doc(
schema_specs,
doc_source_param_mapping,
)

if url.endswith("/action"):
for sch in schema_specs:
sch.summary = summary
# Neutron sometimes has h4 instead of h3 and "Response Parameters" instead of "Response"
elif (
details_child.h3
Expand Down Expand Up @@ -470,9 +486,19 @@ def _get_schema_candidates(

elif not action_name and section_description:
if candidate_action_name and (
candidate_action_name in section_summary
or candidate_action_name
in "".join(section_description)
re.search(
rf"\b{candidate_action_name}\b", section_summary
)
or (
url.endswith("/volumes/{volume_id}/action")
# Cinder doc does not contain action name in the
# summary, but looking only to description causes
# faulty matches in Nova
and re.search(
rf"\b{candidate_action_name}\b",
section_description,
)
)
):
# This is an action we are hopefully interested in
# Now we can have single schema or multiple (i.e. microversions)
Expand Down
Loading