Start building Designate specs

Sadly no schemas, but at least first rough structure.

Change-Id: Id2ed7ecfbf20ca522e173cf11f236d00f75871c5
This commit is contained in:
Artem Goncharov
2024-10-01 20:07:09 +02:00
parent a9a1a7dee1
commit fa25f3b8f0
5 changed files with 296 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
# 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 inspect
from multiprocessing import Process
from pathlib import Path
from unittest import mock
import fixtures
from codegenerator.common.schema import SpecSchema
from codegenerator.openapi.base import OpenStackServerSourceBase
from codegenerator.openapi.utils import merge_api_ref_doc
from ruamel.yaml.scalarstring import LiteralScalarString
class DesignateGenerator(OpenStackServerSourceBase):
URL_TAG_MAP = {
"/zones/tasks/transfer_accepts": "zone-ownership-transfers-accepts",
"/zones/tasks/transfer_requests": "zone-ownership-transfers-requests",
"/zones/tasks/imports": "zone-imports",
"/zones/tasks/exports": "zone-exports",
"/zones/{zone_id}/tasks/export": "zone-exports",
"/zones/{zone_id}/tasks": "zone-tasks",
"/zones/{zone_id}/recordsets": "recordsets",
"/zones/{zone_id}/shares": "shared-zones",
"/service_statuses": "service-statuses",
"/tlds": "tld",
"/tsigkeys": "tsigkey",
"/reverse/floatingips": "floatingips",
}
def __init__(self):
self.api_version = "2.1"
self.min_api_version = "2.0"
def _api_ver_major(self, ver):
return ver.ver_major
def _api_ver_minor(self, ver):
return ver.ver_minor
def _api_ver(self, ver):
return (ver.ver_major, ver.ver_minor)
def _build_routes(
self,
mapper,
node,
path="",
sub_map: dict[str, str] = {},
subcontroller_name: str | None = None,
):
# path = f"{path}/{subcontroller_name}"
for part in [x for x in dir(node) if callable(getattr(node, x))]:
# Iterate over functions to find what is exposed on the current
# level
if part == "_route":
continue
obj = getattr(node, part)
_pecan = getattr(obj, "_pecan", None)
exposed = getattr(obj, "exposed", None)
if _pecan and exposed:
argspec = _pecan.get("argspec", None)
args: list[str] = []
if argspec:
args = argspec.args
if "self" in args:
args.remove("self")
# Only whatever is pecan exposed is of interest
conditions = {}
action = None
url = path
resource = None
# Check whether for mandatory params there is path defined
# If there is entry with all parameters we are most likely on
# the subcontroller level, however on the subcontroller there
# might be parent_id and id for which we do not have
# combination yet, thus take parent_id as base url
# for "zone_id" => /zones/{zone_id}/
# for "zone_id zone_transfer_request_id" => /zones/{zone_id}
# while in transfer_request controller
if " ".join(args) in sub_map:
url = sub_map[" ".join(args)]
if subcontroller_name and subcontroller_name not in url:
url += f"/{subcontroller_name}"
elif " ".join(args[0:-1]) in sub_map:
url = sub_map[" ".join(args[0:-1])]
if subcontroller_name and subcontroller_name not in url:
url += f"/{subcontroller_name}"
url += f"/{{{args[-1]}}}"
elif len(args) > 0:
url += f"/{{{args[-1]}}}"
# Identify the action from function name
# https://pecan.readthedocs.io/en/latest/rest.html#url-mapping
if part == "get_one":
conditions["method"] = ["GET"]
action = "show"
if " ".join(args) not in sub_map:
sub_map[" ".join(args)] = url
elif part == "get_all":
conditions["method"] = ["GET"]
action = "list"
elif part in ["post", "post_all"]:
conditions["method"] = ["POST"]
action = "create"
elif part in ["put"]:
conditions["method"] = ["PUT"]
action = "update"
elif part in ["patch_one"]:
conditions["method"] = ["PATCH"]
action = "update"
elif part in ["delete", "delete_one"]:
conditions["method"] = ["DELETE"]
action = "delete"
if action:
# If we identified method as "interesting" register it into
# the routes mapper
mapper.connect(
None,
url,
controller=obj,
action=action,
conditions=conditions,
)
# yield part
if not hasattr(node, "__dict__"):
return
for subcontroller, v in (
node.__dict__.items()
if isinstance(node, type)
else node.__class__.__dict__.items()
):
# Iterate over node attributes for subcontrollers
if subcontroller.startswith("_"):
continue
if subcontroller in ["central_api", "__wrapped__", "SORT_KEYS"]:
# Not underested in those
continue
subpath = f"{path}/{subcontroller}"
self._build_routes(mapper, v, subpath, sub_map, subcontroller)
return
def generate(self, target_dir, args):
proc = Process(target=self._generate, args=[target_dir, args])
proc.start()
proc.join()
if proc.exitcode != 0:
raise RuntimeError("Error generating Designate OpenAPI schema")
def _generate(self, target_dir, args):
from designate.api.v2.controllers import root
from designate.api.v2.controllers import zones
from designate.api.v2.controllers.zones import nameservers
from designate.api.v2.controllers.zones import recordsets
from designate.api.v2.controllers.zones import sharedzones
from designate.api.v2.controllers.zones import tasks
from oslo_config import cfg
# import oslo_messaging as messaging
# from oslo_messaging import conffixture as messaging_conffixture
from pecan import make_app as pecan_make_app
from routes import Mapper
work_dir = Path(target_dir)
work_dir.mkdir(parents=True, exist_ok=True)
impl_path = Path(
work_dir, "openapi_specs", "dns", f"v{self.api_version}.yaml"
)
impl_path.parent.mkdir(parents=True, exist_ok=True)
openapi_spec = self.load_openapi(Path(impl_path))
if not openapi_spec:
openapi_spec = SpecSchema(
info={
"title": "OpenStack DNS API",
"description": LiteralScalarString(
"DNS API provided by Designate service"
),
"version": self.api_version,
},
openapi="3.1.0",
security=[{"ApiKeyAuth": []}],
components={
"securitySchemes": {
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-Auth-Token",
}
},
"parameters": {
"x-auth-all-projects": {
"name": "x-auth-all-projects",
"in": "header",
"schema": {"type": "boolean"},
"description": "If enabled this will show results from all projects in Designate",
},
"x-auth-sudo-project-id": {
"name": "x-auth-sudo-project-id",
"in": "header",
"schema": {"type": "string", "format": "uuid"},
"description": "This allows a user to impersonate another project",
},
},
},
)
self._buses = {}
self.useFixture(
fixtures.MonkeyPatch(
"designate.central.rpcapi.CentralAPI", mock.MagicMock()
)
)
self.app = pecan_make_app(root.RootController())
self.root = self.app.application.root
mapper = Mapper()
self._build_routes(mapper, root.RootController, "/v2")
for route in mapper.matchlist:
self._process_route(route, openapi_spec, framework="pecan")
if args.api_ref_src:
merge_api_ref_doc(
openapi_spec, args.api_ref_src, allow_strip_version=False
)
self.dump_openapi(openapi_spec, Path(impl_path), args.validate)
lnk = Path(impl_path.parent, "v2.yaml")
lnk.unlink(missing_ok=True)
lnk.symlink_to(impl_path.name)
return impl_path

View File

@@ -68,6 +68,11 @@ class OpenApiSchemaGenerator(BaseGenerator):
ManilaGenerator().generate(target_dir, args)
def generate_designate(self, target_dir, args):
from codegenerator.openapi.designate import DesignateGenerator
DesignateGenerator().generate(target_dir, args)
def generate(
self, res, target_dir, openapi_spec=None, operation_id=None, args=None
):
@@ -80,6 +85,8 @@ class OpenApiSchemaGenerator(BaseGenerator):
self.generate_nova(target_dir, args)
elif args.service_type in ["block-storage", "volume"]:
self.generate_cinder(target_dir, args)
elif args.service_type == "dns":
self.generate_designate(target_dir, args)
elif args.service_type == "image":
self.generate_glance(target_dir, args)
elif args.service_type == "identity":

View File

@@ -34,3 +34,7 @@ fi
if [ -z "$1" -o "$1" = "shared-file-system" ]; then
openstack-codegenerator --work-dir wrk --target openapi-spec --service-type shared-file-system --api-ref-src ${API_REF_BUILD_ROOT}/manila/api-ref/build/html/index.html --validate
fi
if [ -z "$1" -o "$1" = "dns" ]; then
openstack-codegenerator --work-dir wrk --target openapi-spec --service-type dns --api-ref-src ${API_REF_BUILD_ROOT}/designate/api-ref/build/html/dns-api-v2-index.html --validate
fi

View File

@@ -91,6 +91,35 @@
project: "opendev.org/openstack/nova"
path: "/api-ref/build/html/index.html"
- job:
name: codegenerator-openapi-dns-tips
parent: codegenerator-openapi-tips-base
description: |
Generate OpenAPI spec for Designate
required-projects:
- name: openstack/designate
vars:
openapi_service: dns
install_additional_projects:
- project: "opendev.org/openstack/designate"
name: "."
- job:
name: codegenerator-openapi-dns-tips-with-api-ref
parent: codegenerator-openapi-dns-tips
description: |
Generate OpenAPI spec for Designate consuming API-REF
required-projects:
- name: openstack/designate
pre-run:
- playbooks/openapi/pre-api-ref.yaml
vars:
codegenerator_api_ref:
project: "opendev.org/openstack/designate"
path: "/api-ref/build/html/dns-api-v2-index.html"
- job:
name: codegenerator-openapi-identity-tips
parent: codegenerator-openapi-tips-base
@@ -290,6 +319,8 @@
soft: true
- name: codegenerator-openapi-compute-tips-with-api-ref
soft: true
- name: codegenerator-openapi-dns-tips-with-api-ref
soft: true
- name: codegenerator-openapi-identity-tips-with-api-ref
soft: true
- name: codegenerator-openapi-image-tips-with-api-ref

View File

@@ -8,6 +8,7 @@
- openstack-tox-py311
- codegenerator-openapi-block-storage-tips-with-api-ref
- codegenerator-openapi-compute-tips-with-api-ref
- codegenerator-openapi-dns-tips-with-api-ref
- codegenerator-openapi-identity-tips-with-api-ref
- codegenerator-openapi-image-tips-with-api-ref
- codegenerator-openapi-load-balancing-tips-with-api-ref
@@ -23,6 +24,7 @@
- openstack-tox-py311
- codegenerator-openapi-block-storage-tips-with-api-ref
- codegenerator-openapi-compute-tips-with-api-ref
- codegenerator-openapi-dns-tips-with-api-ref
- codegenerator-openapi-identity-tips-with-api-ref
- codegenerator-openapi-image-tips-with-api-ref
- codegenerator-openapi-load-balancing-tips-with-api-ref