From 7ba498cf58f3b1ade76e01f2f28f3711587c920a Mon Sep 17 00:00:00 2001 From: Eyal Date: Sun, 29 Nov 2015 15:00:22 +0200 Subject: [PATCH] add initial files for topology api Implements: blueprint get-topology-api Change-Id: I3fa8a811edb04decf52dba875f5d8073b69b91a6 --- requirements.txt | 5 + setup.cfg | 10 ++ vitrage/api/app.py | 101 ++++++++++++++++++ vitrage/api/app.wsgi | 23 ++++ vitrage/api/controllers/__init__.py | 0 vitrage/api/controllers/root.py | 37 +++++++ vitrage/api/controllers/v1/__init__.py | 0 .../api/controllers/v1/root.py | 8 +- vitrage/api/controllers/v1/topology.py | 19 ++++ vitrage/api/hooks.py | 38 +++++++ vitrage/cmd/api.py | 19 ++++ vitrage/i18n.py | 42 ++++++++ vitrage/opts.py | 24 +++++ vitrage/service.py | 56 ++++++++++ 14 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 vitrage/api/app.py create mode 100644 vitrage/api/app.wsgi create mode 100644 vitrage/api/controllers/__init__.py create mode 100644 vitrage/api/controllers/root.py create mode 100644 vitrage/api/controllers/v1/__init__.py rename __init__.py => vitrage/api/controllers/v1/root.py (81%) create mode 100644 vitrage/api/controllers/v1/topology.py create mode 100644 vitrage/api/hooks.py create mode 100644 vitrage/cmd/api.py create mode 100644 vitrage/i18n.py create mode 100644 vitrage/opts.py create mode 100644 vitrage/service.py diff --git a/requirements.txt b/requirements.txt index f261bd6ef..a25a0cc88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,8 @@ pbr>=1.6 Babel>=1.3 oslo.log>=1.12.0 # Apache-2.0 +pecan>=0.8.0 +PasteDeploy>=1.5.0 +Werkzeug>=0.7 +oslo.policy>=0.3.0 +keystonemiddleware>=2.3.0 diff --git a/setup.cfg b/setup.cfg index 25078ce6a..f344f7be1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,16 @@ classifier = Programming Language :: Python :: 2.7 Topic :: System :: Monitoring +[global] +setup-hooks = + pbr.hooks.setup_hook + +console_scripts = + vitrage-api = vitrage.cmd.api:main + +oslo.config.opts = + vitrage = vitrage.opts:list_opts + [files] packages = vitrage diff --git a/vitrage/api/app.py b/vitrage/api/app.py new file mode 100644 index 000000000..409b823ac --- /dev/null +++ b/vitrage/api/app.py @@ -0,0 +1,101 @@ +# 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 logging +import os +import pecan + +from oslo_config import cfg +from oslo_log import log +from paste import deploy +from vitrage.api import hooks +from vitrage.i18n import _ +from vitrage.i18n import _LW +from vitrage import service +from werkzeug import serving + +LOG = log.getLogger(__name__) + +PECAN_CONFIG = { + 'app': { + 'root': 'vitrage.api.controllers.root.RootController', + 'modules': ['vitrage.api'], + }, +} + + +def setup_app(pecan_config=PECAN_CONFIG, conf=None): + if conf is None: + raise RuntimeError("Config is actually mandatory") + app_hooks = [hooks.ConfigHook(conf), + hooks.TranslationHook()] + + pecan.configuration.set_config(dict(pecan_config), overwrite=True) + pecan_debug = conf.api.pecan_debug + if conf.api.workers != 1 and pecan_debug: + pecan_debug = False + LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, ' + 'the value is overridden with False')) + + app = pecan.make_app( + pecan_config['app']['root'], + debug=pecan_debug, + hooks=app_hooks, + guess_content_type_from_ext=False + ) + + return app + + +def load_app(conf): + # Build the WSGI app + cfg_file = None + cfg_path = conf.api.paste_config + if not os.path.isabs(cfg_path): + cfg_file = conf.find_file(cfg_path) + elif os.path.exists(cfg_path): + cfg_file = cfg_path + + if not cfg_file: + raise cfg.ConfigFilesNotFoundError([conf.api.paste_config]) + LOG.info("Full WSGI config used: %s" % cfg_file) + return deploy.loadapp("config:" + cfg_file) + + +def build_server(conf): + app = load_app(conf) + # Create the WSGI server and start it + host, port = conf.api.host, conf.api.port + + LOG.info(_('Starting server in PID %s') % os.getpid()) + LOG.info(_("Configuration:")) + conf.log_opt_values(LOG, logging.INFO) + + if host == '0.0.0.0': + LOG.info(_( + 'serving on 0.0.0.0:%(sport)s, view at http://127.0.0.1:%(vport)s') + % ({'sport': port, 'vport': port})) + else: + LOG.info(_("serving on http://%(host)s:%(port)s") % ( + {'host': host, 'port': port})) + + serving.run_simple(host, port, + app, processes=conf.api.workers) + + +def _app(): + conf = service.prepare_service() + return setup_app(conf=conf) + + +def app_factory(global_config, **local_conf): + return _app() diff --git a/vitrage/api/app.wsgi b/vitrage/api/app.wsgi new file mode 100644 index 000000000..17879921b --- /dev/null +++ b/vitrage/api/app.wsgi @@ -0,0 +1,23 @@ +# 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. + +"""Use this file for deploying the API under mod_wsgi. +See http://pecan.readthedocs.org/en/latest/deployment.html for details. +""" +from oslo_config import cfg + +from vitrage.api import app +from vitrage import service + +# Initialize the oslo configuration library and logging +conf = service.prepare_service() +application = app.load_app(conf) \ No newline at end of file diff --git a/vitrage/api/controllers/__init__.py b/vitrage/api/controllers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/vitrage/api/controllers/root.py b/vitrage/api/controllers/root.py new file mode 100644 index 000000000..8bc436f49 --- /dev/null +++ b/vitrage/api/controllers/root.py @@ -0,0 +1,37 @@ +# 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 as pecan +from vitrage.api.controllers.v1 import root as v1 + + +class RootController(object): + v1 = v1.V1Controller() + + @staticmethod + @pecan.expose('json') + def index(): + return { + "versions": [ + { + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": pecan.request.application_url + "/v1/" + } + ], + "id": "v1.0", + "updated": "2015-11-29" + } + ] + } diff --git a/vitrage/api/controllers/v1/__init__.py b/vitrage/api/controllers/v1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/__init__.py b/vitrage/api/controllers/v1/root.py similarity index 81% rename from __init__.py rename to vitrage/api/controllers/v1/root.py index 7a05fb1f4..b66b27529 100644 --- a/__init__.py +++ b/vitrage/api/controllers/v1/root.py @@ -1,5 +1,3 @@ -# Copyright 2015 - Alcatel-Lucent -# # 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 @@ -12,4 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -__author__ = 'stack' +from vitrage.api.controllers.v1 import topology + + +class V1Controller(object): + topology = topology.TopologyController() diff --git a/vitrage/api/controllers/v1/topology.py b/vitrage/api/controllers/v1/topology.py new file mode 100644 index 000000000..478c95ae3 --- /dev/null +++ b/vitrage/api/controllers/v1/topology.py @@ -0,0 +1,19 @@ +# 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 pecan import rest + + +class TopologyController(rest.RestController): + """Manages operations on the topology.""" + + pass diff --git a/vitrage/api/hooks.py b/vitrage/api/hooks.py new file mode 100644 index 000000000..983b540f6 --- /dev/null +++ b/vitrage/api/hooks.py @@ -0,0 +1,38 @@ +# 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_policy import policy +from pecan import hooks + + +class ConfigHook(hooks.PecanHook): + """Attach the configuration and policy enforcer object to the request. """ + + def __init__(self, conf): + self.conf = conf + self.enforcer = policy.Enforcer(conf) + + def before(self, state): + state.request.cfg = self.conf + state.request.enforcer = self.enforcer + + +class TranslationHook(hooks.PecanHook): + + def after(self, state): + # After a request has been done, we need to see if + # ClientSideError has added an error onto the response. + # If it has we need to get it info the thread-safe WSGI + # environ to be used by the ParsableErrorMiddleware. + if hasattr(state.response, 'translatable_error'): + state.request.environ['translatable_error'] = ( + state.response.translatable_error) diff --git a/vitrage/cmd/api.py b/vitrage/cmd/api.py new file mode 100644 index 000000000..60eb67ae6 --- /dev/null +++ b/vitrage/cmd/api.py @@ -0,0 +1,19 @@ +# 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 vitrage.api import app +from vitrage import service + + +def main(): + conf = service.prepare_service() + app.build_server(conf) diff --git a/vitrage/i18n.py b/vitrage/i18n.py new file mode 100644 index 000000000..b0619cead --- /dev/null +++ b/vitrage/i18n.py @@ -0,0 +1,42 @@ +# 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. + +"""oslo.i18n integration module. +See http://docs.openstack.org/developer/oslo.i18n/usage.html +""" + +import oslo_i18n + +DOMAIN = 'vitrage' + +_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical + + +def translate(value, user_locale): + return oslo_i18n.translate(value, user_locale) + + +def get_available_languages(): + return oslo_i18n.get_available_languages(DOMAIN) diff --git a/vitrage/opts.py b/vitrage/opts.py new file mode 100644 index 000000000..d994b6382 --- /dev/null +++ b/vitrage/opts.py @@ -0,0 +1,24 @@ +# 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 + + +def list_opts(): + return [("api", ( + cfg.IntOpt('workers', default=1, + min=1, + help='Number of workers for vitrage API server.'), + + cfg.BoolOpt('pecan_debug', default=False, + help='Toggle Pecan Debug Middleware.') + ))] diff --git a/vitrage/service.py b/vitrage/service.py new file mode 100644 index 000000000..c0f034cfb --- /dev/null +++ b/vitrage/service.py @@ -0,0 +1,56 @@ +# 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 keystoneclient.auth +import logging + + +from keystonemiddleware import opts as ks_opts +from oslo_config import cfg +from oslo_log import log +from oslo_policy import opts as policy_opts + +from vitrage import opts + +LOG = log.getLogger(__name__) + + +def prepare_service(args=None, default_opts=None, conf=None): + if conf is None: + conf = cfg.ConfigOpts() + log.register_options(conf) + for group, options in ks_opts.list_auth_token_opts(): + conf.register_opts(list(options), group=group) + policy_opts.set_defaults(conf) + + for group, options in opts.list_opts(): + conf.register_opts(list(options), + group=None if group == "DEFAULT" else group) + + for opt, value, group in default_opts or []: + conf.set_default(opt, value, group) + + conf(args, project='vitrage', validate_default_values=True) + log.setup(conf, 'vitrage') + conf.log_opt_values(LOG, logging.DEBUG) + + # NOTE(sileht): keystonemiddleware assume we use the global CONF object + # (LP#1428317). In gnocchi, this is not the case, so we have to register + # some keystoneclient options ourself. Missing options are hidden into + # private area of keystonemiddleware and keystoneclient, so we + # create a keystoneclient AuthPlugin object, that will register the options + # into our configuration object. This have to be done after the + # configuration files have been loaded because the authplugin options + # depends of the authplugin present in the configuration file. + keystoneclient.auth.register_conf_options(conf, 'keystone_authtoken') + keystoneclient.auth.load_from_conf_options(conf, 'keystone_authtoken') + return conf