Move to Paste and Paste-deploy

Start using Paste and Paste deploy for providing some flexability
to system administrator while deploying freezer-api.

Implements blueprint move-to-paste

Change-Id: I3f68a98ae7822495627791edb5be125556ff0b98
This commit is contained in:
Saad Zaher 2016-05-25 12:38:15 +00:00
parent 6a1280457d
commit 525c7f12cb
16 changed files with 227 additions and 98 deletions

View File

@ -36,8 +36,10 @@ TOC
--------------------
::
# sudo cp etc/freezer-api.conf /etc/freezer-api.conf
# sudo vi /etc/freezer-api.conf
# sudo cp etc/freezer/freezer-api.conf /etc/freezer/freezer-api.conf
# sudo cp etc/freezer/freezer-paste.ini /etc/freezer/freezer-paste.ini
# sudo vi /etc/freezer/freezer-api.conf
# sudo vi /etc/freezer/freezer-paste.ini
1.4 setup/configure the db
@ -63,7 +65,7 @@ To do that, use the freezer-db-init command:
# freezer-db-init [db-host]
The url of the db-host is optional and can be automatically guessed from
/etc/freezer-api.conf
/etc/freezer/freezer-api.conf
To get information about optional additional parameters:
::

View File

@ -1,5 +1,5 @@
[DEFAULT]
output_file = etc/freezer-api.conf.sample
output_file = etc/freezer/freezer-api.conf.sample
wrap_width = 79
namespace = "freezer-api"
namespace = oslo.log

View File

@ -87,13 +87,22 @@ function configure_freezer_api {
[ ! -d $FREEZER_API_LOG_DIR ] && sudo mkdir -m 755 -p $FREEZER_API_LOG_DIR
sudo chown $USER $FREEZER_API_LOG_DIR
sudo cp $FREEZER_API_DIR/etc/freezer-api.conf.sample $FREEZER_API_CONF_DIR/freezer-api.conf
sudo cp $FREEZER_API_DIR/etc/freezer/freezer-api.conf.sample $FREEZER_API_CONF_DIR/freezer-api.conf
sudo cp $FREEZER_API_DIR/etc/freezer/freezer-paste.ini $FREEZER_API_CONF_DIR/freezer-paste.ini
# set paste configuration
iniset $FREEZER_API_CONF 'paste_deploy' config_file $FREEZER_API_CONF_DIR/freezer-paste.ini
# make sure the stack user has the right permissions on the config folder
sudo chown -R $USER $FREEZER_API_CONF_DIR
#set elasticsearch configuration
iniset $FREEZER_API_CONF 'storage' db elasticsearch
iniset $FREEZER_API_CONF 'storage' index freezer
iniset $FREEZER_API_CONF 'storage' number_of_replicas 0
iniset $FREEZER_API_CONF 'storage' hosts http://$SERVICE_HOST:9200
# set keystone configuration
iniset $FREEZER_API_CONF 'keystone_authtoken' auth_protocol $KEYSTONE_AUTH_PROTOCOL
iniset $FREEZER_API_CONF 'keystone_authtoken' auth_host $KEYSTONE_AUTH_HOST
iniset $FREEZER_API_CONF 'keystone_authtoken' auth_port $KEYSTONE_AUTH_PORT

View File

@ -18,7 +18,7 @@
# Set up default directories
FREEZER_API_DIR=$DEST/freezer-api
FREEZER_API_FILES=${FREEZER_API_DIR}/devstack/files
FREEZER_API_CONF_DIR=${FREEZER_API_CONF_DIR:-/etc}
FREEZER_API_CONF_DIR=${FREEZER_API_CONF_DIR:-/etc/freezer}
FREEZER_API_CONF=$FREEZER_API_CONF_DIR/freezer-api.conf
FREEZER_API_LOG_DIR=$DEST/logs

View File

@ -18,10 +18,11 @@
# If set to true, the logging level will be set to DEBUG instead of the default
# INFO level. (boolean value)
# Note: This option can be changed without restarting.
#debug = false
# If set to false, the logging level will be set to WARNING instead of the
# default INFO level. (boolean value)
# DEPRECATED: If set to false, the logging level will be set to WARNING instead
# of the default INFO level. (boolean value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
#verbose = true
@ -166,8 +167,8 @@
# Determines the frequency at which the list of revoked tokens is retrieved
# from the Identity service (in seconds). A high number of revocation events
# combined with a low cache duration may significantly reduce performance.
# (integer value)
# combined with a low cache duration may significantly reduce performance. Only
# valid for PKI tokens. (integer value)
#revocation_cache_time = 10
# (Optional) If defined, indicate whether token data should be authenticated or
@ -279,6 +280,17 @@
#auth_section = <None>
[paste_deploy]
#
# From freezer-api
#
# Name of the paste configuration file that defines the available pipelines.
# (string value)
#config_file = freezer-paste.ini
[storage]
#

View File

@ -0,0 +1,23 @@
[app:freezer_app]
paste.app_factory = freezer_api.service:freezer_app_factory
[app:service_v1]
use = egg:freezer-api#service_v1
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
[filter:healthcheck]
paste.filter_factory = oslo_middleware:Healthcheck.factory
backends = disable_by_file
disable_by_file_path = /etc/freezer/healthcheck_disable
# @todo deprecated and should be removed soon
[filter:HealthApp]
paste.filter_factory = freezer_api.api.common.middleware:HealthApp.factory
[pipeline:main]
pipeline = HealthApp healthcheck authtoken freezer_app
[pipeline:unauthenticated_freezer_api]
pipeline = HealthApp Healthcheck freezer_app

View File

@ -18,45 +18,66 @@ limitations under the License.
import falcon
import json
from oslo_log import log
class HealthApp(object):
LOG = log.getLogger(__name__)
class Middleware(object):
"""
WSGI wrapper for all freezer middlewares. Use this will allow to manage
middlewares through paste
"""
def __init__(self, app):
self.app = app
def process_request(self, req):
"""
implement this function in your middleware to change the request
if the function return None the request will be handled in the next
level functions
"""
return None
def process_response(self, resp):
"""
Implement this to modify your response
"""
return None
@classmethod
def factory(cls, global_conf, **local_conf):
def filter(app):
return cls(app)
return filter
def __call__(self, req, resp):
response = self.process_request(req)
if response:
return response
response = req.get_response(self.app)
response.req = req
try:
self.process_response(response)
except falcon.HTTPError as e:
LOG.error(e)
# @todo this should be removed and oslo.middleware should be used instead
class HealthApp(Middleware):
"""
Simple WSGI app to support HAProxy polling.
If the requested url matches the configured path it replies
with a 200 otherwise passes the request to the inner app
"""
def __init__(self, app, path):
self.app = app
self.path = path
def __call__(self, environ, start_response):
if environ.get('PATH_INFO') == self.path:
if environ.get('PATH_INFO') == '/v1/health':
start_response('200 OK', [])
return []
return self.app(environ, start_response)
class SignedHeadersAuth(object):
def __init__(self, app, auth_app):
self._app = app
self._auth_app = auth_app
def __call__(self, environ, start_response):
path = environ.get('PATH_INFO')
# NOTE(flwang): The root path of Zaqar service shouldn't require any
# auth.
if path == '/':
return self._app(environ, start_response)
signature = environ.get('HTTP_URL_SIGNATURE')
if signature is None or path.startswith('/v1'):
return self._auth_app(environ, start_response)
return self._app(environ, start_response)
class RequireJSON(object):
def process_request(self, req, resp):

View File

@ -20,10 +20,11 @@ from __future__ import print_function
import falcon
import sys
from keystonemiddleware import auth_token
from oslo_config import cfg
from oslo_log import log
from wsgiref import simple_server
from paste import deploy
from paste import httpserver
from freezer_api.api.common import middleware
from freezer_api.api.common import utils
@ -39,19 +40,21 @@ CONF = cfg.CONF
_LOG = log.getLogger(__name__)
def get_application(db=None):
config.parse_args()
config.setup_logging()
def build_app(db=None):
"""
Building routes and forming the root freezer-api app
:param db: instance of elastic search db class if not freezer will try to
initialize this instance itself
:return: wsgi app
"""
if not db:
db = driver.get_db()
# injecting FreezerContext & hooks
middlware_list = [utils.FuncMiddleware(hook) for hook in
utils.before_hooks()]
middlware_list.append(middleware.JSONTranslator())
middlware_list.append(middleware.RequireJSON())
app = falcon.API(middleware=middlware_list)
middleware_list = [utils.FuncMiddleware(hook) for hook in
utils.before_hooks()]
middleware_list.append(middleware.JSONTranslator())
middleware_list.append(middleware.RequireJSON())
app = falcon.API(middleware=middleware_list)
for exception_class in freezer_api_exc.exception_handlers_catalog:
app.add_error_handler(exception_class, exception_class.handle)
@ -64,42 +67,30 @@ def get_application(db=None):
for route, resource in endpoints:
app.add_route(version_path + route, resource)
if 'keystone_authtoken' in config.CONF:
auth_app = auth_token.AuthProtocol(app,
conf={"oslo-config-config": CONF,
"oslo-config-project":
"freezer-api"})
else:
_LOG.warning(_i18n._LW("keystone authentication disabled"))
app = middleware.SignedHeadersAuth(app, auth_app)
app = middleware.HealthApp(app=app, path='/v1/health')
return app
def main():
try:
application = get_application()
except Exception as err:
message = _i18n._('Unable to start server: %s ') % err
print(message)
_LOG.error(message)
sys.exit(1)
# setup opts
config.parse_args()
config.setup_logging()
paste_conf = config.find_paste_config()
# quick simple server for testing purposes or simple scenarios
ip = CONF.get('bind_host', '0.0.0.0')
port = CONF.get('bind_port', 9090)
httpd = simple_server.make_server(ip, int(port), application)
message = _i18n._('Server listening on %(ip)s:%(port)s'
% {'ip': ip, 'port': port})
print(message)
_LOG.info(message)
try:
httpd.serve_forever()
httpserver.serve(
application=deploy.loadapp('config:%s' % paste_conf, name='main'),
host=ip,
port=port)
message = _i18n._('Server listening on %(ip)s:%(port)s' %
{'ip': ip, 'port': port})
_LOG.info(message)
print(message)
except KeyboardInterrupt:
print(_i18n._("\nThanks, Bye"))
print(_i18n._("Thank You ! \nBye."))
sys.exit(0)
if __name__ == '__main__':
main()

View File

@ -30,7 +30,7 @@ import requests
from freezer_api.common import db_mappings
DEFAULT_CONF_PATH = '/etc/freezer-api.conf'
DEFAULT_CONF_PATH = '/etc/freezer/freezer-api.conf'
DEFAULT_ES_SERVER_PORT = 9200
DEFAULT_INDEX = 'freezer'
DEFAULT_REPLICAS = 1
@ -307,7 +307,7 @@ def get_db_params(args):
"""
Extracts the db configuration parameters either from the provided
command line arguments or searching in the default freezer-api config
file /etc/freezer-api.conf
file /etc/freezer/freezer-api.conf
:param args: argparsed command line arguments
:return: (elasticsearch_url, elastichsearch_index, number_of_replicas)

View File

@ -14,20 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
from freezer_api.cmd.api import get_application
from freezer_api.storage import driver
from oslo_config import cfg
from oslo_log import log
from freezer_api.service import initialize_app
CONF = cfg.CONF
_LOG = log.getLogger(__name__)
db = None
try:
db = driver.get_db()
except Exception as err:
message = 'Unable to start server: {0} '.format(err)
_LOG.error(message)
application = get_application(db)
app = application = initialize_app()

View File

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import os
import sys
from oslo_config import cfg
@ -25,6 +26,12 @@ from keystonemiddleware import opts
CONF = cfg.CONF
paste_deploy = [
cfg.StrOpt('config_file', default='freezer-paste.ini',
help='Name of the paste configuration file that defines '
'the available pipelines.'),
]
def api_common_opts():
@ -46,6 +53,11 @@ def api_common_opts():
def parse_args():
CONF.register_cli_opts(api_common_opts())
driver.register_elk_opts()
# register paste configuration
paste_grp = cfg.OptGroup('paste_deploy',
'Paste Configuration')
CONF.register_group(paste_grp)
CONF.register_opts(paste_deploy, group=paste_grp)
log.register_options(CONF)
default_config_files = cfg.find_config_files('freezer', 'freezer-api')
CONF(args=sys.argv[1:],
@ -70,10 +82,42 @@ def setup_logging():
log.setup(CONF, 'freezer-api', version=FREEZER_API_VERSION)
def find_paste_config():
"""Find freezer's paste.deploy configuration file.
freezer's paste.deploy configuration file is specified in the
``[paste_deploy]`` section of the main freezer-api configuration file,
``freezer-api.conf``.
For example::
[paste_deploy]
config_file = freezer-paste.ini
:returns: The selected configuration filename
:raises: exception.ConfigFileNotFound
"""
if CONF.paste_deploy.config_file:
paste_config = CONF.paste_deploy.config_file
paste_config_value = paste_config
if not os.path.isabs(paste_config):
paste_config = CONF.find_file(paste_config)
elif CONF.config_file:
paste_config = CONF.config_file[0]
paste_config_value = paste_config
else:
# this provides backwards compatibility for keystone.conf files that
# still have the entire paste configuration included, rather than just
# a [paste_deploy] configuration section referring to an external file
paste_config = CONF.find_file('freezer-api.conf')
paste_config_value = 'freezer-api.conf'
if not paste_config or not os.path.exists(paste_config):
raise Exception('paste configuration file {0} not found !'.
format(paste_config))
return paste_config
def list_opts():
_OPTS = {
None: api_common_opts(),
'storage': driver.get_elk_opts(),
'paste_deploy': paste_deploy,
opts.auth_token_opts[0][0]: opts.auth_token_opts[0][1]
}
return _OPTS.items()

35
freezer_api/service.py Normal file
View File

@ -0,0 +1,35 @@
"""
(c) Copyright 2016 Hewlett-Packard Enterprise Development Company, L.P.
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 freezer_api.cmd.api import build_app
from freezer_api.common import config
from paste import deploy
def freezer_app_factory(global_conf, **local_conf):
return build_app()
def initialize_app(conf=None, name='main'):
try:
config.parse_args()
config.setup_logging()
except Exception as e:
pass
conf = config.find_paste_config()
app = deploy.loadapp('config:%s' % conf, name=name)
return app

View File

@ -21,7 +21,7 @@ import unittest
from mock import Mock, patch
from freezer_api.storage import driver
from freezer_api.cmd.api import get_application
from freezer_api.cmd.api import build_app
class TestStorageDriver(unittest.TestCase):

View File

@ -21,12 +21,13 @@ from mock import Mock
from freezer_api.api.common import middleware
class TestHealthApp(unittest.TestCase):
def test_call_nested_app(self):
mock_app = Mock()
mock_app.return_value = ['app_body']
health_app = middleware.HealthApp(mock_app, 'test_path_78908')
health_app = middleware.HealthApp(mock_app)
environ = {}
start_response = Mock()
result = health_app(environ, start_response)
@ -35,8 +36,8 @@ class TestHealthApp(unittest.TestCase):
def test_return_200_when_paths_match(self):
mock_app = Mock()
mock_app.return_value = ['app_body']
health_app = middleware.HealthApp(mock_app, 'test_path_6789')
environ = {'PATH_INFO': 'test_path_6789'}
health_app = middleware.HealthApp(mock_app)
environ = {'PATH_INFO': '/v1/health'}
start_response = Mock()
result = health_app(environ, start_response)
start_response.assert_called_once_with('200 OK', [])

View File

@ -2,6 +2,8 @@ elasticsearch>=1.3.0,<2.0 # Apache-2.0
falcon>=0.1.6 # Apache-2.0
jsonschema>=2.0.0,<3.0.0,!=2.5.0 # MIT
keystonemiddleware>=4.0.0 # Apache-2.0
Paste # MIT
PasteDeploy>=1.5.0 # MIT
oslo.config>=3.2.0 # Apache-2.0
oslo.context>=2.2.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0

View File

@ -49,7 +49,10 @@ console_scripts =
freezer-db-init = freezer_api.cmd.db_init:main
tempest.test_plugins =
freezer_api_tempest_tests = freezer_api.tests.freezer_api_tempest_plugin.plugin:FreezerApiTempestPlugin
paste.app_factory =
service_v1 = freezer_api.service:freezer_app_factory
wsgi_scripts =
freezer-api-wsgi = freezer_api.service:initialize_app
[pytests]
where=tests