From 2dd193ae5c4e67bdfe9ff715f00dba128dd40a9c Mon Sep 17 00:00:00 2001 From: zhuli Date: Tue, 8 Aug 2017 10:37:13 +0800 Subject: [PATCH] add cyborg-api v1 & hooks add these for further expansion: 1. give an initial version of v1 to api service, 2. add Config and PublicUrl hook to app. 3. add resource endpoint in response body. Change-Id: I2b84508a07bbbf0f5133c828a8217a23398564db --- cyborg/api/app.py | 13 +++++- cyborg/api/controllers/link.py | 48 ++++++++++++++++++++++ cyborg/api/controllers/root.py | 25 ++++++++++++ cyborg/api/controllers/v1/__init__.py | 57 +++++++++++++++++++++++++++ cyborg/api/hooks.py | 36 +++++++++++++++++ cyborg/common/service.py | 2 +- cyborg/conf/api.py | 7 ++++ 7 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 cyborg/api/controllers/link.py create mode 100644 cyborg/api/controllers/v1/__init__.py create mode 100644 cyborg/api/hooks.py diff --git a/cyborg/api/app.py b/cyborg/api/app.py index 1c2db127..656bf1df 100644 --- a/cyborg/api/app.py +++ b/cyborg/api/app.py @@ -16,6 +16,7 @@ import pecan from cyborg.api import config +from cyborg.api import hooks from cyborg.api import middleware @@ -26,7 +27,8 @@ def get_pecan_config(): def setup_app(pecan_config=None, extra_hooks=None): - app_hooks = [] + app_hooks = [hooks.ConfigHook(), + hooks.PublicUrlHook()] if extra_hooks: app_hooks.extend(extra_hooks) @@ -45,3 +47,12 @@ def setup_app(pecan_config=None, extra_hooks=None): ) return app + + +class VersionSelectorApplication(object): + def __init__(self): + pc = get_pecan_config() + self.v1 = setup_app(pecan_config=pc) + + def __call__(self, environ, start_response): + return self.v1(environ, start_response) diff --git a/cyborg/api/controllers/link.py b/cyborg/api/controllers/link.py new file mode 100644 index 00000000..fe39c69f --- /dev/null +++ b/cyborg/api/controllers/link.py @@ -0,0 +1,48 @@ +# Copyright 2017 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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 pecan +from wsme import types as wtypes + +from cyborg.api.controllers import base + + +def build_url(resource, resource_args, bookmark=False, base_url=None): + if base_url is None: + base_url = pecan.request.public_url + + template = '%(url)s/%(res)s' if bookmark else '%(url)s/v1/%(res)s' + template += '%(args)s' if resource_args.startswith('?') else '/%(args)s' + return template % {'url': base_url, 'res': resource, 'args': resource_args} + + +class Link(base.APIBase): + """A link representation.""" + + href = wtypes.text + """The url of a link.""" + + rel = wtypes.text + """The name of a link.""" + + type = wtypes.text + """Indicates the type of document/link.""" + + @staticmethod + def make_link(rel_name, url, resource, resource_args, + bookmark=False, type=wtypes.Unset): + href = build_url(resource, resource_args, + bookmark=bookmark, base_url=url) + return Link(href=href, rel=rel_name, type=type) diff --git a/cyborg/api/controllers/root.py b/cyborg/api/controllers/root.py index 89c8eb00..3361bfef 100644 --- a/cyborg/api/controllers/root.py +++ b/cyborg/api/controllers/root.py @@ -13,13 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. +import pecan from pecan import rest from wsme import types as wtypes from cyborg.api.controllers import base +from cyborg.api.controllers import v1 from cyborg.api import expose +VERSION1 = 'v1' + + class Root(base.APIBase): name = wtypes.text """The name of the API""" @@ -42,6 +47,26 @@ class Root(base.APIBase): class RootController(rest.RestController): + _versions = [VERSION1] + """All supported API versions""" + + _default_version = VERSION1 + """The default API version""" + + v1 = v1.Controller() + @expose.expose(Root) def get(self): return Root.convert() + + @pecan.expose() + def _route(self, args, request=None): + """Overrides the default routing behavior. + + It redirects the request to the default version of the cyborg API + if the version number is not specified in the url. + """ + + if args[0] and args[0] not in self._versions: + args = [self._default_version] + args + return super(RootController, self)._route(args) diff --git a/cyborg/api/controllers/v1/__init__.py b/cyborg/api/controllers/v1/__init__.py new file mode 100644 index 00000000..f76283a5 --- /dev/null +++ b/cyborg/api/controllers/v1/__init__.py @@ -0,0 +1,57 @@ +# Copyright 2017 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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. + +"""Version 1 of the Cyborg API""" + +import pecan +from pecan import rest +from wsme import types as wtypes + +from cyborg.api.controllers import base +from cyborg.api.controllers import link +from cyborg.api import expose + + +class V1(base.APIBase): + """The representation of the version 1 of the API.""" + + id = wtypes.text + """The ID of the version""" + + accelerator = [link.Link] + """Links to the accelerator resource""" + + @staticmethod + def convert(): + v1 = V1() + v1.id = 'v1' + v1.accelerator = [ + link.Link.make_link('self', pecan.request.public_url, + 'accelerator', ''), + link.Link.make_link('bookmark', pecan.request.public_url, + 'accelerator', '', bookmark=True) + ] + return v1 + + +class Controller(rest.RestController): + """Version 1 API controller root""" + + @expose.expose(V1) + def get(self): + return V1.convert() + + +__all__ = ('Controller',) diff --git a/cyborg/api/hooks.py b/cyborg/api/hooks.py new file mode 100644 index 00000000..d48f7cf1 --- /dev/null +++ b/cyborg/api/hooks.py @@ -0,0 +1,36 @@ +# Copyright 2017 Huawei Technologies Co.,LTD. +# All Rights Reserved. +# +# 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 oslo_config import cfg +from pecan import hooks + + +class ConfigHook(hooks.PecanHook): + """Attach the config object to the request so controllers can get to it.""" + + def before(self, state): + state.request.cfg = cfg.CONF + + +class PublicUrlHook(hooks.PecanHook): + """Attach the right public_url to the request. + + Attach the right public_url to the request so resources can create + links even when the API service is behind a proxy or SSL terminator. + """ + + def before(self, state): + state.request.public_url = ( + cfg.CONF.api.public_endpoint or state.request.host_url) diff --git a/cyborg/common/service.py b/cyborg/common/service.py index 49a9ba30..eaa46889 100644 --- a/cyborg/common/service.py +++ b/cyborg/common/service.py @@ -56,7 +56,7 @@ class WSGIService(service.ServiceBase): :returns: None """ self.name = name - self.app = app.setup_app() + self.app = app.VersionSelectorApplication() self.workers = (CONF.api.api_workers or processutils.get_worker_count()) if self.workers and self.workers < 1: diff --git a/cyborg/conf/api.py b/cyborg/conf/api.py index cbfeede9..28eb8869 100644 --- a/cyborg/conf/api.py +++ b/cyborg/conf/api.py @@ -38,6 +38,13 @@ opts = [ "the service, this option should be False; note, you " "will want to change public API endpoint to represent " "SSL termination URL with 'public_endpoint' option.")), + cfg.StrOpt('public_endpoint', + help=_("Public URL to use when building the links to the API " + "resources (for example, \"https://cyborg.rocks:6666\")." + " If None the links will be built using the request's " + "host URL. If the API is operating behind a proxy, you " + "will want to change this to represent the proxy's URL. " + "Defaults to None.")), ] opt_group = cfg.OptGroup(name='api',