Enable healthcheck app to check API status
Change-Id: Ide2aa8613fe4767c9e93e1421dbf7ba7439bb3c7
This commit is contained in:
parent
aa9f7df75d
commit
e86f216aa2
@ -15,6 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
@ -29,59 +30,56 @@ from aodh import storage
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
PECAN_CONFIG = {
|
||||
'app': {
|
||||
'root': 'aodh.api.controllers.root.RootController',
|
||||
'modules': ['aodh.api'],
|
||||
},
|
||||
}
|
||||
|
||||
# NOTE(sileht): pastedeploy uses ConfigParser to handle
|
||||
# global_conf, since python 3 ConfigParser doesn't
|
||||
# allow to store object as config value, only strings are
|
||||
# permit, so to be able to pass an object created before paste load
|
||||
# the app, we store them into a global var. But the each loaded app
|
||||
# store it's configuration in unique key to be concurrency safe.
|
||||
global APPCONFIGS
|
||||
APPCONFIGS = {}
|
||||
|
||||
|
||||
def setup_app(pecan_config=PECAN_CONFIG, conf=None):
|
||||
if conf is None:
|
||||
# NOTE(jd) That sucks but pecan forces us to use kwargs :(
|
||||
raise RuntimeError("Config is actually mandatory")
|
||||
# FIXME: Replace DBHook with a hooks.TransactionHook
|
||||
def setup_app(root, conf):
|
||||
app_hooks = [hooks.ConfigHook(conf),
|
||||
hooks.DBHook(
|
||||
storage.get_connection_from_config(conf)),
|
||||
hooks.TranslationHook()]
|
||||
|
||||
pecan.configuration.set_config(dict(pecan_config), overwrite=True)
|
||||
|
||||
app = pecan.make_app(
|
||||
pecan_config['app']['root'],
|
||||
return pecan.make_app(
|
||||
root,
|
||||
hooks=app_hooks,
|
||||
wrap_app=middleware.ParsableErrorMiddleware,
|
||||
guess_content_type_from_ext=False
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
def load_app(conf, appname="aodh+keystone"):
|
||||
global APPCONFIGS
|
||||
|
||||
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
|
||||
cfg_path = conf.find_file(cfg_path)
|
||||
|
||||
if not cfg_file:
|
||||
if cfg_path is None or not os.path.exists(cfg_path):
|
||||
raise cfg.ConfigFilesNotFoundError([conf.api.paste_config])
|
||||
LOG.info(_LI("Full WSGI config used: %s"), cfg_file)
|
||||
return deploy.loadapp("config:" + cfg_file)
|
||||
|
||||
config = dict(conf=conf)
|
||||
configkey = str(uuid.uuid4())
|
||||
APPCONFIGS[configkey] = config
|
||||
|
||||
LOG.info(_LI("WSGI config used: %s"), cfg_path)
|
||||
return deploy.loadapp("config:" + cfg_path,
|
||||
name=appname,
|
||||
global_conf={'configkey': configkey})
|
||||
|
||||
|
||||
def app_factory(global_config, **local_conf):
|
||||
global APPCONFIGS
|
||||
appconfig = APPCONFIGS.get(global_config.get('configkey'))
|
||||
return setup_app(root=local_conf.get('root'), **appconfig)
|
||||
|
||||
|
||||
def build_wsgi_app(argv=None):
|
||||
return load_app(service.prepare_service(argv=argv))
|
||||
|
||||
|
||||
def _app():
|
||||
conf = service.prepare_service()
|
||||
return setup_app(conf=conf)
|
||||
|
||||
|
||||
def app_factory(global_config, **local_conf):
|
||||
return _app()
|
||||
|
@ -15,15 +15,11 @@
|
||||
|
||||
import pecan
|
||||
|
||||
from aodh.api.controllers.v2 import root as v2
|
||||
|
||||
MEDIA_TYPE_JSON = 'application/vnd.openstack.telemetry-%s+json'
|
||||
MEDIA_TYPE_XML = 'application/vnd.openstack.telemetry-%s+xml'
|
||||
|
||||
|
||||
class RootController(object):
|
||||
|
||||
v2 = v2.V2Controller()
|
||||
class VersionsController(object):
|
||||
|
||||
@pecan.expose('json')
|
||||
def index(self):
|
||||
|
@ -792,7 +792,7 @@ class AlarmsController(rest.RestController):
|
||||
|
||||
alarm = conn.create_alarm(alarm_in)
|
||||
self._record_creation(conn, change, alarm.alarm_id, now)
|
||||
v2_utils.set_resp_location_hdr("/v2/alarms/" + alarm.alarm_id)
|
||||
v2_utils.set_resp_location_hdr("/alarms/" + alarm.alarm_id)
|
||||
return Alarm.from_db_model(alarm)
|
||||
|
||||
@wsme_pecan.wsexpose([Alarm], [base.Query], [str], int, str)
|
||||
|
@ -19,9 +19,9 @@
|
||||
import os
|
||||
|
||||
from oslo_config import fixture as fixture_config
|
||||
import pecan
|
||||
import pecan.testing
|
||||
import webtest
|
||||
|
||||
from aodh.api import app
|
||||
from aodh import service
|
||||
from aodh.tests.functional import db as db_test_base
|
||||
|
||||
@ -44,24 +44,11 @@ class FunctionalTest(db_test_base.TestBase):
|
||||
self.CONF.set_override('policy_file',
|
||||
os.path.abspath('etc/aodh/policy.json'),
|
||||
group='oslo_policy', enforce_type=True)
|
||||
self.app = self._make_app()
|
||||
|
||||
def _make_app(self):
|
||||
self.config = {
|
||||
'app': {
|
||||
'root': 'aodh.api.controllers.root.RootController',
|
||||
'modules': ['aodh.api'],
|
||||
},
|
||||
'wsme': {
|
||||
'debug': True,
|
||||
},
|
||||
}
|
||||
|
||||
return pecan.testing.load_test_app(self.config, conf=self.CONF)
|
||||
|
||||
def tearDown(self):
|
||||
super(FunctionalTest, self).tearDown()
|
||||
pecan.set_config({}, overwrite=True)
|
||||
self.CONF.set_override('paste_config',
|
||||
os.path.abspath('etc/aodh/api_paste.ini'),
|
||||
group='api', enforce_type=True)
|
||||
self.app = webtest.TestApp(app.load_app(
|
||||
self.CONF, appname='aodh+noauth'))
|
||||
|
||||
def put_json(self, path, params, expect_errors=False, headers=None,
|
||||
extra_environ=None, status=None):
|
||||
|
@ -1,49 +0,0 @@
|
||||
#
|
||||
# Copyright 2012 New Dream Network, LLC (DreamHost)
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
#
|
||||
# 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.
|
||||
"""Test ACL."""
|
||||
|
||||
import mock
|
||||
import webtest
|
||||
|
||||
from aodh.api import app
|
||||
from aodh.tests.functional.api import v2
|
||||
|
||||
|
||||
class TestAPIACL(v2.FunctionalTest):
|
||||
|
||||
def _make_app(self):
|
||||
file_name = self.path_get('etc/aodh/api_paste.ini')
|
||||
self.CONF.set_override("paste_config", file_name, "api")
|
||||
# We need the other call to prepare_service in app.py to return the
|
||||
# same tweaked conf object.
|
||||
with mock.patch('aodh.service.prepare_service') as ps:
|
||||
ps.return_value = self.CONF
|
||||
return webtest.TestApp(app.load_app(conf=self.CONF))
|
||||
|
||||
def test_non_authenticated(self):
|
||||
response = self.get_json('/alarms', expect_errors=True)
|
||||
self.assertEqual(401, response.status_int)
|
||||
|
||||
def test_authenticated_wrong_role(self):
|
||||
response = self.get_json('/alarms',
|
||||
expect_errors=True,
|
||||
headers={
|
||||
"X-Roles": "Member",
|
||||
"X-Tenant-Name": "admin",
|
||||
"X-Project-Id":
|
||||
"bc23a9d531064583ace8f67dad60f6bb",
|
||||
})
|
||||
self.assertEqual(401, response.status_int)
|
@ -24,7 +24,9 @@ import oslo_messaging.conffixture
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
from six import moves
|
||||
import webtest
|
||||
|
||||
from aodh.api import app
|
||||
from aodh.cmd import alarm_conversion
|
||||
from aodh import messaging
|
||||
from aodh.storage import models
|
||||
@ -390,7 +392,8 @@ class TestAlarms(TestAlarmsBase):
|
||||
pf = os.path.abspath('aodh/tests/functional/api/v2/policy.json-test')
|
||||
self.CONF.set_override('policy_file', pf, group='oslo_policy',
|
||||
enforce_type=True)
|
||||
self.app = self._make_app()
|
||||
self.app = webtest.TestApp(app.load_app(
|
||||
self.CONF, appname='aodh+noauth'))
|
||||
|
||||
response = self.get_json('/alarms',
|
||||
expect_errors=True,
|
||||
|
@ -26,16 +26,31 @@ from oslo_policy import opts
|
||||
from six.moves.urllib import parse as urlparse
|
||||
import sqlalchemy_utils
|
||||
|
||||
from aodh.api import app
|
||||
from aodh import service
|
||||
from aodh import storage
|
||||
|
||||
|
||||
# NOTE(chdent): Hack to restore semblance of global configuration to
|
||||
# pass to the WSGI app used per test suite. LOAD_APP_KWARGS are the olso
|
||||
# configuration, and the pecan application configuration of
|
||||
# which the critical part is a reference to the current indexer.
|
||||
LOAD_APP_KWARGS = None
|
||||
|
||||
|
||||
def setup_app():
|
||||
global LOAD_APP_KWARGS
|
||||
return app.load_app(**LOAD_APP_KWARGS)
|
||||
|
||||
|
||||
class ConfigFixture(fixture.GabbiFixture):
|
||||
"""Establish the relevant configuration for a test run."""
|
||||
|
||||
def start_fixture(self):
|
||||
"""Set up config."""
|
||||
|
||||
global LOAD_APP_KWARGS
|
||||
|
||||
self.conf = None
|
||||
self.conn = None
|
||||
|
||||
@ -68,7 +83,7 @@ class ConfigFixture(fixture.GabbiFixture):
|
||||
enforce_type=True)
|
||||
conf.set_override(
|
||||
'paste_config',
|
||||
os.path.abspath('aodh/tests/functional/gabbi/gabbi_paste.ini'),
|
||||
os.path.abspath('etc/aodh/api_paste.ini'),
|
||||
group='api',
|
||||
)
|
||||
|
||||
@ -88,6 +103,11 @@ class ConfigFixture(fixture.GabbiFixture):
|
||||
self.conn = storage.get_connection_from_config(self.conf)
|
||||
self.conn.upgrade()
|
||||
|
||||
LOAD_APP_KWARGS = {
|
||||
'conf': conf,
|
||||
'appname': 'aodh+noauth',
|
||||
}
|
||||
|
||||
def stop_fixture(self):
|
||||
"""Reset the config and remove data."""
|
||||
if self.conn:
|
||||
|
@ -1,21 +0,0 @@
|
||||
# aodh API WSGI Pipeline
|
||||
# Define the filters that make up the pipeline for processing WSGI requests
|
||||
# Note: This pipeline is PasteDeploy's term rather than aodh's pipeline
|
||||
# used for processing samples
|
||||
#
|
||||
# This is the gabbi paste file, which creates the full pipeline without the
|
||||
# auth middleware.
|
||||
|
||||
# Remove authtoken from the pipeline if you don't want to use keystone authentication
|
||||
[pipeline:main]
|
||||
pipeline = cors request_id api-server
|
||||
|
||||
[app:api-server]
|
||||
paste.app_factory = aodh.api.app:app_factory
|
||||
|
||||
[filter:request_id]
|
||||
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||
|
||||
[filter:cors]
|
||||
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||
oslo_config_project = aodh
|
7
aodh/tests/functional/gabbi/gabbits/healthcheck.yaml
Normal file
7
aodh/tests/functional/gabbi/gabbits/healthcheck.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
fixtures:
|
||||
- ConfigFixture
|
||||
|
||||
tests:
|
||||
- name: healthcheck
|
||||
GET: /healthcheck
|
||||
status: 200
|
@ -22,8 +22,6 @@ import os
|
||||
|
||||
from gabbi import driver
|
||||
|
||||
from aodh.api import app
|
||||
from aodh import service
|
||||
from aodh.tests.functional.gabbi import fixtures as fixture_module
|
||||
|
||||
|
||||
@ -34,10 +32,5 @@ def load_tests(loader, tests, pattern):
|
||||
"""Provide a TestSuite to the discovery process."""
|
||||
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
|
||||
return driver.build_tests(test_dir, loader, host=None,
|
||||
intercept=setup_app,
|
||||
intercept=fixture_module.setup_app,
|
||||
fixture_module=fixture_module)
|
||||
|
||||
|
||||
def setup_app():
|
||||
conf = service.prepare_service([])
|
||||
return app.load_app(conf)
|
||||
|
@ -7,6 +7,7 @@ namespace = oslo.db
|
||||
namespace = oslo.log
|
||||
namespace = oslo.messaging
|
||||
namespace = oslo.middleware.cors
|
||||
namespace = oslo.middleware.healthcheck
|
||||
namespace = oslo.middleware.http_proxy_to_wsgi
|
||||
namespace = oslo.policy
|
||||
namespace = keystonemiddleware.auth_token
|
||||
|
@ -1,14 +1,35 @@
|
||||
# aodh API WSGI Pipeline
|
||||
# Define the filters that make up the pipeline for processing WSGI requests
|
||||
# Note: This pipeline is PasteDeploy's term rather than aodh's pipeline
|
||||
# used for processing samples
|
||||
[composite:aodh+noauth]
|
||||
use = egg:Paste#urlmap
|
||||
/ = aodhversions_pipeline
|
||||
/v2 = aodhv2_noauth_pipeline
|
||||
/healthcheck = healthcheck
|
||||
|
||||
# Remove authtoken from the pipeline if you don't want to use keystone authentication
|
||||
[pipeline:main]
|
||||
pipeline = cors http_proxy_to_wsgi request_id authtoken api-server
|
||||
[composite:aodh+keystone]
|
||||
use = egg:Paste#urlmap
|
||||
/ = aodhversions_pipeline
|
||||
/v2 = aodhv2_keystone_pipeline
|
||||
/healthcheck = healthcheck
|
||||
|
||||
[app:api-server]
|
||||
[app:healthcheck]
|
||||
use = egg:oslo.middleware#healthcheck
|
||||
oslo_config_project = aodh
|
||||
|
||||
[pipeline:aodhversions_pipeline]
|
||||
pipeline = cors http_proxy_to_wsgi aodhversions
|
||||
|
||||
[app:aodhversions]
|
||||
paste.app_factory = aodh.api.app:app_factory
|
||||
root = aodh.api.controllers.root.VersionsController
|
||||
|
||||
[pipeline:aodhv2_keystone_pipeline]
|
||||
pipeline = cors http_proxy_to_wsgi request_id authtoken aodhv2
|
||||
|
||||
[pipeline:aodhv2_noauth_pipeline]
|
||||
pipeline = cors http_proxy_to_wsgi request_id aodhv2
|
||||
|
||||
[app:aodhv2]
|
||||
paste.app_factory = aodh.api.app:app_factory
|
||||
root = aodh.api.controllers.v2.root.V2Controller
|
||||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||
|
5
releasenotes/notes/healthcheck-560700b72ae68e18.yaml
Normal file
5
releasenotes/notes/healthcheck-560700b72ae68e18.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- A healthcheck endpoint is provided by default at /healthcheck. It leverages
|
||||
oslo_middleware healthcheck middleware. It allows to retrieve information
|
||||
about the health of the API service.
|
@ -19,7 +19,7 @@ PasteDeploy>=1.5.0
|
||||
pbr<2.0,>=0.11
|
||||
pecan>=0.8.0
|
||||
oslo.messaging>=5.2.0 # Apache-2.0
|
||||
oslo.middleware>=3.0.0 # Apache-2.0
|
||||
oslo.middleware>=3.22.0 # Apache-2.0
|
||||
oslo.serialization>=1.4.0 # Apache-2.0
|
||||
oslo.utils>=3.5.0 # Apache-2.0
|
||||
python-ceilometerclient>=1.5.0
|
||||
|
Loading…
Reference in New Issue
Block a user