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

Commit

Permalink
Merge pull request #24 from gtema/dev
Browse files Browse the repository at this point in the history
feat: add placement openapi spec generator
  • Loading branch information
gtema authored Feb 27, 2024
2 parents a2d8311 + 0393d18 commit 8d11ec0
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 4 deletions.
48 changes: 44 additions & 4 deletions codegenerator/openapi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,11 @@ def _sanitize_param_ver_info(self, openapi_spec, min_api_version):
def _process_route(
self, route, openapi_spec, ver_prefix=None, framework=None
):
if "controller" not in route.defaults:
# Placement exposes "action" as controller in route defaults, all others - "controller"
if not ("controller" in route.defaults or "action" in route.defaults):
return
if "action" in route.defaults and "_methods" in route.defaults:
# placement 405 handler
return
# Path can be "/servers/{id}", but can be
# "/volumes/:volume_id/types/:(id)" - process
Expand All @@ -154,6 +158,10 @@ def _process_route(
else:
path += part

if path == "":
# placement has "" path - see weird explanation in the placement source code
return

# if "method" not in route.conditions:
# raise RuntimeError("Method not set for %s", route)
method = (
Expand All @@ -162,8 +170,8 @@ def _process_route(
else "GET"
)

controller = route.defaults["controller"]
action = route.defaults["action"]
controller = route.defaults.get("controller")
action = route.defaults.get("action")
logging.info(
"Path: %s; method: %s; operation: %s", path, method, action
)
Expand All @@ -189,6 +197,12 @@ def _process_route(
# Pecan base app
framework = "pecan"
contr = controller
elif not controller and action and hasattr(action, "func"):
# Placement base app
framework = "placement"
controller = action
contr = action
action = None
else:
raise RuntimeError("Unsupported controller %s" % controller)
# logging.debug("Actions: %s, Versioned methods: %s", actions, versioned_methods)
Expand Down Expand Up @@ -286,7 +300,7 @@ def _process_route(
start_version=start_version,
end_version=end_version,
)
elif hasattr(contr, action):
elif action and hasattr(contr, action):
# Normal REST operation without version bounds
func = getattr(contr, action)

Expand Down Expand Up @@ -462,6 +476,32 @@ def _process_route(
path=path,
)

elif framework == "placement":
if callable(controller.func):
func = controller.func
# Get the path/op spec only when we have
# something to fill in
path_spec = openapi_spec.paths.setdefault(
path, PathSchema(parameters=path_params)
)
operation_spec = getattr(path_spec, method.lower())
if not operation_spec.operationId:
operation_spec.operationId = operation_id
if operation_tags:
operation_spec.tags.extend(operation_tags)
operation_spec.tags = list(set(operation_spec.tags))

self.process_operation(
func,
openapi_spec,
operation_spec,
path_resource_names,
controller=controller,
operation_name=func.__name__,
method=method,
path=path,
)

else:
logging.warning(controller.__dict__.items())
logging.warning(contr.__dict__.items())
Expand Down
108 changes: 108 additions & 0 deletions codegenerator/openapi/placement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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.
#
from multiprocessing import Process
from pathlib import Path

from ruamel.yaml.scalarstring import LiteralScalarString

from codegenerator.common.schema import (
SpecSchema,
)
from codegenerator.openapi.base import OpenStackServerSourceBase
from codegenerator.openapi.utils import merge_api_ref_doc


class PlacementGenerator(OpenStackServerSourceBase):
URL_TAG_MAP = {
"/versions": "version",
}

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 _generate(self, target_dir, args):
from oslo_config import cfg
from oslo_config import fixture as config_fixture

from placement import microversion
from placement import handler
from placement import conf

self.api_version = microversion.max_version_string()
self.min_api_version = microversion.min_version_string()

config = cfg.ConfigOpts()
conf_fixture = self.useFixture(config_fixture.Config(config))
conf.register_opts(conf_fixture.conf)
handler = handler.PlacementHandler(config=conf_fixture.conf)

self.router = handler._map

work_dir = Path(target_dir)
work_dir.mkdir(parents=True, exist_ok=True)

impl_path = Path(work_dir, "openapi_specs", "placement", "v1.yaml")
impl_path.parent.mkdir(parents=True, exist_ok=True)

openapi_spec = self.load_openapi(impl_path)
if not openapi_spec:
openapi_spec = SpecSchema(
info=dict(
title="OpenStack Placement API",
description=LiteralScalarString(
"Placement API provided by Placement service"
),
version=self.api_version,
),
openapi="3.1.0",
security=[{"ApiKeyAuth": []}],
components=dict(
securitySchemes={
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-Auth-Token",
}
},
),
)

for route in self.router.matchlist:
self._process_route(route, openapi_spec)

self._sanitize_param_ver_info(openapi_spec, self.min_api_version)

if args.api_ref_src:
merge_api_ref_doc(
openapi_spec,
args.api_ref_src,
allow_strip_version=False,
)

self.dump_openapi(openapi_spec, impl_path, args.validate)

return impl_path

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 Placement OpenAPI schema")
return Path(target_dir, "openapi_specs", "placement", "v2.yaml")
7 changes: 7 additions & 0 deletions codegenerator/openapi_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ def generate_neutron(self, target_dir, args):

NeutronGenerator().generate(target_dir, args)

def generate_placement(self, target_dir, args):
from codegenerator.openapi.placement import PlacementGenerator

PlacementGenerator().generate(target_dir, args)

def generate(
self, res, target_dir, openapi_spec=None, operation_id=None, args=None
):
Expand All @@ -77,6 +82,8 @@ def generate(
self.generate_octavia(target_dir, args)
elif args.service_type == "network":
self.generate_neutron(target_dir, args)
elif args.service_type == "placement":
self.generate_placement(target_dir, args)
else:
raise RuntimeError(
"Service type %s is not supported", args.service_type
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mdformat # MIT
wsgi_intercept[spec]
oslotest[spec]
nova[spec]
openstack-placement[spec]
glance[spec]
neutron[spec]
neutron-vpnaas[spec]
Expand Down
1 change: 1 addition & 0 deletions tools/generate_openapi_specs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ openstack-codegenerator --work-dir wrk --target openapi-spec --service-type volu
openstack-codegenerator --work-dir wrk --target openapi-spec --service-type image --api-ref-src ${API_REF_BUILD_ROOT}/glance/api-ref/build/html/v2/index.html
openstack-codegenerator --work-dir wrk --target openapi-spec --service-type identity --api-ref-src ${API_REF_BUILD_ROOT}/keystone/api-ref/build/html/v3/index.html
openstack-codegenerator --work-dir wrk --target openapi-spec --service-type load-balancing --api-ref-src ${API_REF_BUILD_ROOT}/octavia/api-ref/build/html/v2/index.html
openstack-codegenerator --work-dir wrk --target openapi-spec --service-type placement --api-ref-src ${API_REF_BUILD_ROOT}/placement/api-ref/build/html/index.html

0 comments on commit 8d11ec0

Please sign in to comment.