Cloudpulse application code
Change-Id: I84795eae78ff3109af3e4ba525615382dc04f377 Implements: blueprint cloudpulse-api-handler
This commit is contained in:
parent
cd2e5a7d64
commit
787e3f128f
|
@ -0,0 +1,62 @@
|
||||||
|
# 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
|
||||||
|
import pecan
|
||||||
|
|
||||||
|
from cloudpulse.api import auth
|
||||||
|
from cloudpulse.api import config as api_config
|
||||||
|
from cloudpulse.api import middleware
|
||||||
|
|
||||||
|
# Register options for the service
|
||||||
|
API_SERVICE_OPTS = [
|
||||||
|
cfg.IntOpt('port',
|
||||||
|
default=9511,
|
||||||
|
help='The port for the cloudpulse API server'),
|
||||||
|
cfg.StrOpt('host',
|
||||||
|
default='127.0.0.1',
|
||||||
|
help='The listen IP for the cloudpulse API server'),
|
||||||
|
cfg.IntOpt('max_limit',
|
||||||
|
default=1000,
|
||||||
|
help='The maximum number of items returned in a single '
|
||||||
|
'response from a collection resource.')
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
opt_group = cfg.OptGroup(name='api',
|
||||||
|
title='Options for the cloudpulse-api service')
|
||||||
|
CONF.register_group(opt_group)
|
||||||
|
CONF.register_opts(API_SERVICE_OPTS, opt_group)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pecan_config():
|
||||||
|
# Set up the pecan configuration
|
||||||
|
filename = api_config.__file__.replace('.pyc', '.py')
|
||||||
|
return pecan.configuration.conf_from_file(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_app(config=None):
|
||||||
|
if not config:
|
||||||
|
config = get_pecan_config()
|
||||||
|
|
||||||
|
app_conf = dict(config.app)
|
||||||
|
|
||||||
|
app = pecan.make_app(
|
||||||
|
app_conf.pop('root'),
|
||||||
|
logging=getattr(config, 'logging', {}),
|
||||||
|
wrap_app=middleware.ParsableErrorMiddleware,
|
||||||
|
**app_conf
|
||||||
|
)
|
||||||
|
|
||||||
|
# TBD Add test hook later
|
||||||
|
# cpulseTimer(10, timerfunc, "Cpulse")
|
||||||
|
return auth.install(app, CONF, config.app.acl_public_routes)
|
|
@ -0,0 +1,48 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Access Control Lists (ACL's) control access the API server."""
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from cloudpulse.api.middleware import auth_token
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_OPTS = [
|
||||||
|
cfg.BoolOpt('enable_authentication',
|
||||||
|
default=True,
|
||||||
|
help='This option enables or disables user authentication '
|
||||||
|
'via keystone. Default value is True.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(AUTH_OPTS)
|
||||||
|
|
||||||
|
|
||||||
|
def install(app, conf, public_routes):
|
||||||
|
"""Install ACL check on application.
|
||||||
|
|
||||||
|
:param app: A WSGI applicatin.
|
||||||
|
:param conf: Settings. Dict'ified and passed to keystonemiddleware
|
||||||
|
:param public_routes: The list of the routes which will be allowed to
|
||||||
|
access without authentication.
|
||||||
|
:return: The same WSGI application with ACL installed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not cfg.CONF.get('enable_authentication'):
|
||||||
|
return app
|
||||||
|
return auth_token.AuthTokenMiddleware(app,
|
||||||
|
conf=dict(conf),
|
||||||
|
public_api_routes=public_routes)
|
|
@ -0,0 +1,32 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# 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 cloudpulse.api import hooks
|
||||||
|
|
||||||
|
# Pecan Application Configurations
|
||||||
|
app = {
|
||||||
|
'root': 'cloudpulse.api.controllers.root.RootController',
|
||||||
|
'modules': ['cloudpulse.api'],
|
||||||
|
'debug': False,
|
||||||
|
'hooks': [
|
||||||
|
hooks.ContextHook(),
|
||||||
|
hooks.RPCHook(),
|
||||||
|
hooks.NoExceptionTracebackHook(),
|
||||||
|
],
|
||||||
|
'acl_public_routes': [
|
||||||
|
'/'
|
||||||
|
],
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright © 2012 New Dream Network, LLC (DreamHost)
|
||||||
|
#
|
||||||
|
# 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 oslo_utils import importutils
|
||||||
|
from pecan import hooks
|
||||||
|
|
||||||
|
from cloudpulse.common import context
|
||||||
|
from cloudpulse.conductor import api as conductor_api
|
||||||
|
|
||||||
|
|
||||||
|
class ContextHook(hooks.PecanHook):
|
||||||
|
"""Configures a request context and attaches it to the request.
|
||||||
|
|
||||||
|
The following HTTP request headers are used:
|
||||||
|
|
||||||
|
X-User:
|
||||||
|
Used for context.user.
|
||||||
|
|
||||||
|
X-User-Id:
|
||||||
|
Used for context.user_id.
|
||||||
|
|
||||||
|
X-Project-Name:
|
||||||
|
Used for context.project.
|
||||||
|
|
||||||
|
X-Project-Id:
|
||||||
|
Used for context.project_id.
|
||||||
|
|
||||||
|
X-Auth-Token:
|
||||||
|
Used for context.auth_token.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def before(self, state):
|
||||||
|
headers = state.request.headers
|
||||||
|
user = headers.get('X-User')
|
||||||
|
user_id = headers.get('X-User-Id')
|
||||||
|
project = headers.get('X-Project-Name')
|
||||||
|
project_id = headers.get('X-Project-Id')
|
||||||
|
domain_id = headers.get('X-User-Domain-Id')
|
||||||
|
domain_name = headers.get('X-User-Domain-Name')
|
||||||
|
auth_token = headers.get('X-Storage-Token')
|
||||||
|
auth_token = headers.get('X-Auth-Token', auth_token)
|
||||||
|
auth_token_info = state.request.environ.get('keystone.token_info')
|
||||||
|
|
||||||
|
auth_url = headers.get('X-Auth-Url')
|
||||||
|
if auth_url is None:
|
||||||
|
importutils.import_module('keystonemiddleware.auth_token')
|
||||||
|
auth_url = cfg.CONF.keystone_authtoken.auth_uri
|
||||||
|
|
||||||
|
state.request.context = context.make_context(
|
||||||
|
auth_token=auth_token,
|
||||||
|
auth_url=auth_url,
|
||||||
|
auth_token_info=auth_token_info,
|
||||||
|
user=user,
|
||||||
|
user_id=user_id,
|
||||||
|
project=project,
|
||||||
|
project_id=project_id,
|
||||||
|
domain_id=domain_id,
|
||||||
|
domain_name=domain_name)
|
||||||
|
|
||||||
|
|
||||||
|
class RPCHook(hooks.PecanHook):
|
||||||
|
"""Attach the rpcapi object to the request so controllers can get to it."""
|
||||||
|
|
||||||
|
def before(self, state):
|
||||||
|
state.request.rpcapi = conductor_api.API(context=state.request.context)
|
||||||
|
|
||||||
|
|
||||||
|
class NoExceptionTracebackHook(hooks.PecanHook):
|
||||||
|
"""Workaround rpc.common: deserialize_remote_exception.
|
||||||
|
|
||||||
|
deserialize_remote_exception builds rpc exception traceback into error
|
||||||
|
message which is then sent to the client. Such behavior is a security
|
||||||
|
concern so this hook is aimed to cut-off traceback from the error message.
|
||||||
|
"""
|
||||||
|
# NOTE(max_lobur): 'after' hook used instead of 'on_error' because
|
||||||
|
# 'on_error' never fired for wsme+pecan pair. wsme @wsexpose decorator
|
||||||
|
# catches and handles all the errors, so 'on_error' dedicated for unhandled
|
||||||
|
# exceptions never fired.
|
||||||
|
def after(self, state):
|
||||||
|
# Omit empty body. Some errors may not have body at this level yet.
|
||||||
|
if not state.response.body:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Do nothing if there is no error.
|
||||||
|
if 200 <= state.response.status_int < 400:
|
||||||
|
return
|
||||||
|
|
||||||
|
json_body = state.response.json
|
||||||
|
# Do not remove traceback when server in debug mode (except 'Server'
|
||||||
|
# errors when 'debuginfo' will be used for traces).
|
||||||
|
if cfg.CONF.debug and json_body.get('faultcode') != 'Server':
|
||||||
|
return
|
||||||
|
|
||||||
|
faultsting = json_body.get('faultstring')
|
||||||
|
traceback_marker = 'Traceback (most recent call last):'
|
||||||
|
if faultsting and (traceback_marker in faultsting):
|
||||||
|
# Cut-off traceback.
|
||||||
|
faultsting = faultsting.split(traceback_marker, 1)[0]
|
||||||
|
# Remove trailing newlines and spaces if any.
|
||||||
|
json_body['faultstring'] = faultsting.rstrip()
|
||||||
|
# Replace the whole json. Cannot change original one beacause it's
|
||||||
|
# generated on the fly.
|
||||||
|
state.response.json = json_body
|
|
@ -6,6 +6,7 @@ pbr>=0.6,!=0.7,<1.0
|
||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
|
|
||||||
jsonpatch>=1.1
|
jsonpatch>=1.1
|
||||||
|
keystonemiddleware>=1.5.0
|
||||||
oslo.concurrency>=1.8.0,<1.9.0 # Apache-2.0
|
oslo.concurrency>=1.8.0,<1.9.0 # Apache-2.0
|
||||||
oslo.config>=1.9.3,<1.10.0 # Apache-2.0
|
oslo.config>=1.9.3,<1.10.0 # Apache-2.0
|
||||||
oslo.context>=0.2.0,<0.3.0 # Apache-2.0
|
oslo.context>=0.2.0,<0.3.0 # Apache-2.0
|
||||||
|
|
Loading…
Reference in New Issue