Cloudpulse application code
Change-Id: I84795eae78ff3109af3e4ba525615382dc04f377 Implements: blueprint cloudpulse-api-handler
This commit is contained in:
		
							
								
								
									
										0
									
								
								cloudpulse/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								cloudpulse/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										62
									
								
								cloudpulse/api/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								cloudpulse/api/app.py
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
							
								
								
									
										48
									
								
								cloudpulse/api/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								cloudpulse/api/auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
							
								
								
									
										32
									
								
								cloudpulse/api/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								cloudpulse/api/config.py
									
									
									
									
									
										Normal file
									
								
							@@ -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': [
 | 
			
		||||
        '/'
 | 
			
		||||
    ],
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								cloudpulse/api/hooks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								cloudpulse/api/hooks.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
		Reference in New Issue
	
	Block a user