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.
|
# under the License.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -29,59 +30,56 @@ from aodh import storage
|
|||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
PECAN_CONFIG = {
|
|
||||||
'app': {
|
# NOTE(sileht): pastedeploy uses ConfigParser to handle
|
||||||
'root': 'aodh.api.controllers.root.RootController',
|
# global_conf, since python 3 ConfigParser doesn't
|
||||||
'modules': ['aodh.api'],
|
# 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):
|
def setup_app(root, conf):
|
||||||
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
|
|
||||||
app_hooks = [hooks.ConfigHook(conf),
|
app_hooks = [hooks.ConfigHook(conf),
|
||||||
hooks.DBHook(
|
hooks.DBHook(
|
||||||
storage.get_connection_from_config(conf)),
|
storage.get_connection_from_config(conf)),
|
||||||
hooks.TranslationHook()]
|
hooks.TranslationHook()]
|
||||||
|
return pecan.make_app(
|
||||||
pecan.configuration.set_config(dict(pecan_config), overwrite=True)
|
root,
|
||||||
|
|
||||||
app = pecan.make_app(
|
|
||||||
pecan_config['app']['root'],
|
|
||||||
hooks=app_hooks,
|
hooks=app_hooks,
|
||||||
wrap_app=middleware.ParsableErrorMiddleware,
|
wrap_app=middleware.ParsableErrorMiddleware,
|
||||||
guess_content_type_from_ext=False
|
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
|
# Build the WSGI app
|
||||||
cfg_file = None
|
|
||||||
cfg_path = conf.api.paste_config
|
cfg_path = conf.api.paste_config
|
||||||
if not os.path.isabs(cfg_path):
|
if not os.path.isabs(cfg_path):
|
||||||
cfg_file = conf.find_file(cfg_path)
|
cfg_path = conf.find_file(cfg_path)
|
||||||
elif os.path.exists(cfg_path):
|
|
||||||
cfg_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])
|
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):
|
def build_wsgi_app(argv=None):
|
||||||
return load_app(service.prepare_service(argv=argv))
|
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
|
import pecan
|
||||||
|
|
||||||
from aodh.api.controllers.v2 import root as v2
|
|
||||||
|
|
||||||
MEDIA_TYPE_JSON = 'application/vnd.openstack.telemetry-%s+json'
|
MEDIA_TYPE_JSON = 'application/vnd.openstack.telemetry-%s+json'
|
||||||
MEDIA_TYPE_XML = 'application/vnd.openstack.telemetry-%s+xml'
|
MEDIA_TYPE_XML = 'application/vnd.openstack.telemetry-%s+xml'
|
||||||
|
|
||||||
|
|
||||||
class RootController(object):
|
class VersionsController(object):
|
||||||
|
|
||||||
v2 = v2.V2Controller()
|
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
def index(self):
|
def index(self):
|
||||||
|
@ -792,7 +792,7 @@ class AlarmsController(rest.RestController):
|
|||||||
|
|
||||||
alarm = conn.create_alarm(alarm_in)
|
alarm = conn.create_alarm(alarm_in)
|
||||||
self._record_creation(conn, change, alarm.alarm_id, now)
|
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)
|
return Alarm.from_db_model(alarm)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose([Alarm], [base.Query], [str], int, str)
|
@wsme_pecan.wsexpose([Alarm], [base.Query], [str], int, str)
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from oslo_config import fixture as fixture_config
|
from oslo_config import fixture as fixture_config
|
||||||
import pecan
|
import webtest
|
||||||
import pecan.testing
|
|
||||||
|
|
||||||
|
from aodh.api import app
|
||||||
from aodh import service
|
from aodh import service
|
||||||
from aodh.tests.functional import db as db_test_base
|
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',
|
self.CONF.set_override('policy_file',
|
||||||
os.path.abspath('etc/aodh/policy.json'),
|
os.path.abspath('etc/aodh/policy.json'),
|
||||||
group='oslo_policy', enforce_type=True)
|
group='oslo_policy', enforce_type=True)
|
||||||
self.app = self._make_app()
|
self.CONF.set_override('paste_config',
|
||||||
|
os.path.abspath('etc/aodh/api_paste.ini'),
|
||||||
def _make_app(self):
|
group='api', enforce_type=True)
|
||||||
self.config = {
|
self.app = webtest.TestApp(app.load_app(
|
||||||
'app': {
|
self.CONF, appname='aodh+noauth'))
|
||||||
'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)
|
|
||||||
|
|
||||||
def put_json(self, path, params, expect_errors=False, headers=None,
|
def put_json(self, path, params, expect_errors=False, headers=None,
|
||||||
extra_environ=None, status=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
|
from oslo_serialization import jsonutils
|
||||||
import six
|
import six
|
||||||
from six import moves
|
from six import moves
|
||||||
|
import webtest
|
||||||
|
|
||||||
|
from aodh.api import app
|
||||||
from aodh.cmd import alarm_conversion
|
from aodh.cmd import alarm_conversion
|
||||||
from aodh import messaging
|
from aodh import messaging
|
||||||
from aodh.storage import models
|
from aodh.storage import models
|
||||||
@ -390,7 +392,8 @@ class TestAlarms(TestAlarmsBase):
|
|||||||
pf = os.path.abspath('aodh/tests/functional/api/v2/policy.json-test')
|
pf = os.path.abspath('aodh/tests/functional/api/v2/policy.json-test')
|
||||||
self.CONF.set_override('policy_file', pf, group='oslo_policy',
|
self.CONF.set_override('policy_file', pf, group='oslo_policy',
|
||||||
enforce_type=True)
|
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',
|
response = self.get_json('/alarms',
|
||||||
expect_errors=True,
|
expect_errors=True,
|
||||||
|
@ -26,16 +26,31 @@ from oslo_policy import opts
|
|||||||
from six.moves.urllib import parse as urlparse
|
from six.moves.urllib import parse as urlparse
|
||||||
import sqlalchemy_utils
|
import sqlalchemy_utils
|
||||||
|
|
||||||
|
from aodh.api import app
|
||||||
from aodh import service
|
from aodh import service
|
||||||
from aodh import storage
|
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):
|
class ConfigFixture(fixture.GabbiFixture):
|
||||||
"""Establish the relevant configuration for a test run."""
|
"""Establish the relevant configuration for a test run."""
|
||||||
|
|
||||||
def start_fixture(self):
|
def start_fixture(self):
|
||||||
"""Set up config."""
|
"""Set up config."""
|
||||||
|
|
||||||
|
global LOAD_APP_KWARGS
|
||||||
|
|
||||||
self.conf = None
|
self.conf = None
|
||||||
self.conn = None
|
self.conn = None
|
||||||
|
|
||||||
@ -68,7 +83,7 @@ class ConfigFixture(fixture.GabbiFixture):
|
|||||||
enforce_type=True)
|
enforce_type=True)
|
||||||
conf.set_override(
|
conf.set_override(
|
||||||
'paste_config',
|
'paste_config',
|
||||||
os.path.abspath('aodh/tests/functional/gabbi/gabbi_paste.ini'),
|
os.path.abspath('etc/aodh/api_paste.ini'),
|
||||||
group='api',
|
group='api',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,6 +103,11 @@ class ConfigFixture(fixture.GabbiFixture):
|
|||||||
self.conn = storage.get_connection_from_config(self.conf)
|
self.conn = storage.get_connection_from_config(self.conf)
|
||||||
self.conn.upgrade()
|
self.conn.upgrade()
|
||||||
|
|
||||||
|
LOAD_APP_KWARGS = {
|
||||||
|
'conf': conf,
|
||||||
|
'appname': 'aodh+noauth',
|
||||||
|
}
|
||||||
|
|
||||||
def stop_fixture(self):
|
def stop_fixture(self):
|
||||||
"""Reset the config and remove data."""
|
"""Reset the config and remove data."""
|
||||||
if self.conn:
|
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 gabbi import driver
|
||||||
|
|
||||||
from aodh.api import app
|
|
||||||
from aodh import service
|
|
||||||
from aodh.tests.functional.gabbi import fixtures as fixture_module
|
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."""
|
"""Provide a TestSuite to the discovery process."""
|
||||||
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
|
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
|
||||||
return driver.build_tests(test_dir, loader, host=None,
|
return driver.build_tests(test_dir, loader, host=None,
|
||||||
intercept=setup_app,
|
intercept=fixture_module.setup_app,
|
||||||
fixture_module=fixture_module)
|
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.log
|
||||||
namespace = oslo.messaging
|
namespace = oslo.messaging
|
||||||
namespace = oslo.middleware.cors
|
namespace = oslo.middleware.cors
|
||||||
|
namespace = oslo.middleware.healthcheck
|
||||||
namespace = oslo.middleware.http_proxy_to_wsgi
|
namespace = oslo.middleware.http_proxy_to_wsgi
|
||||||
namespace = oslo.policy
|
namespace = oslo.policy
|
||||||
namespace = keystonemiddleware.auth_token
|
namespace = keystonemiddleware.auth_token
|
||||||
|
@ -1,14 +1,35 @@
|
|||||||
# aodh API WSGI Pipeline
|
[composite:aodh+noauth]
|
||||||
# Define the filters that make up the pipeline for processing WSGI requests
|
use = egg:Paste#urlmap
|
||||||
# Note: This pipeline is PasteDeploy's term rather than aodh's pipeline
|
/ = aodhversions_pipeline
|
||||||
# used for processing samples
|
/v2 = aodhv2_noauth_pipeline
|
||||||
|
/healthcheck = healthcheck
|
||||||
|
|
||||||
# Remove authtoken from the pipeline if you don't want to use keystone authentication
|
[composite:aodh+keystone]
|
||||||
[pipeline:main]
|
use = egg:Paste#urlmap
|
||||||
pipeline = cors http_proxy_to_wsgi request_id authtoken api-server
|
/ = 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
|
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]
|
[filter:authtoken]
|
||||||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
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
|
pbr<2.0,>=0.11
|
||||||
pecan>=0.8.0
|
pecan>=0.8.0
|
||||||
oslo.messaging>=5.2.0 # Apache-2.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.serialization>=1.4.0 # Apache-2.0
|
||||||
oslo.utils>=3.5.0 # Apache-2.0
|
oslo.utils>=3.5.0 # Apache-2.0
|
||||||
python-ceilometerclient>=1.5.0
|
python-ceilometerclient>=1.5.0
|
||||||
|
Loading…
Reference in New Issue
Block a user