api: return 410 if only Gnocchi is enabled

If gnocchi dispatcher is enabled and not the database
dispatcher. All the ceilometer API calls (except alarms) will return
an empty result. To avoid misunderstanding of why a user got its data
ingested by ceilometer without error but he can't retrieve them via
ceilometer-api and should use the Gnocchi API.

We explicitly tell that through the API by returning a 410.

APIImpact
DocImpact

Change-Id: Ia947f354da73a9a98feaa8cc6c39a1791208613e
This commit is contained in:
Mehdi Abaakouk 2015-07-17 10:00:28 +02:00
parent cfe81a584e
commit b0f98cb767
6 changed files with 163 additions and 12 deletions

View File

@ -23,7 +23,8 @@ MEDIA_TYPE_XML = 'application/vnd.openstack.telemetry-%s+xml'
class RootController(object):
v2 = v2.V2Controller()
def __init__(self):
self.v2 = v2.V2Controller()
@pecan.expose('json')
def index(self):

View File

@ -401,9 +401,3 @@ class QueryAlarmsController(rest.RestController):
for s in conn.query_alarms(query.filter_expr,
query.orderby,
query.limit)]
class QueryController(rest.RestController):
samples = QuerySamplesController()
alarms = QueryAlarmsController()

View File

@ -18,6 +18,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneclient.openstack.common.apiclient import exceptions
from oslo_config import cfg
from oslo_log import log
import pecan
from ceilometer.api.controllers.v2 import alarms
from ceilometer.api.controllers.v2 import capabilities
from ceilometer.api.controllers.v2 import events
@ -25,16 +30,97 @@ from ceilometer.api.controllers.v2 import meters
from ceilometer.api.controllers.v2 import query
from ceilometer.api.controllers.v2 import resources
from ceilometer.api.controllers.v2 import samples
from ceilometer.i18n import _LW
from ceilometer import keystone_client
API_OPTS = [
cfg.BoolOpt('gnocchi_is_enabled',
default=None,
help=('Set True to disable resource/meter/sample URLs. '
'Default autodetection by querying keystone.')),
]
cfg.CONF.register_opts(API_OPTS, group='api')
cfg.CONF.import_opt('dispatcher', 'ceilometer.dispatcher')
LOG = log.getLogger(__name__)
def gnocchi_abort():
pecan.abort(410, ("This telemetry installation is configured to use "
"Gnocchi. Please use the Gnocchi API available on "
"the metric endpoint to retreive data."))
class QueryController(object):
def __init__(self, gnocchi_is_enabled=False):
self.gnocchi_is_enabled = gnocchi_is_enabled
@pecan.expose()
def _lookup(self, kind, *remainder):
if kind == 'alarms':
return query.QueryAlarmsController(), remainder
elif kind == 'samples' and self.gnocchi_is_enabled:
gnocchi_abort()
elif kind == 'samples':
return query.QuerySamplesController(), remainder
else:
pecan.abort(404)
class V2Controller(object):
"""Version 2 API controller root."""
resources = resources.ResourcesController()
meters = meters.MetersController()
samples = samples.SamplesController()
alarms = alarms.AlarmsController()
event_types = events.EventTypesController()
events = events.EventsController()
query = query.QueryController()
capabilities = capabilities.CapabilitiesController()
def __init__(self):
self._gnocchi_is_enabled = None
@property
def gnocchi_is_enabled(self):
if self._gnocchi_is_enabled is None:
if cfg.CONF.api.gnocchi_is_enabled is not None:
self._gnocchi_is_enabled = cfg.CONF.api.gnocchi_is_enabled
elif ("gnocchi" not in cfg.CONF.dispatcher
or "database" in cfg.CONF.dispatcher):
self._gnocchi_is_enabled = False
else:
try:
ks = keystone_client.get_client()
ks.service_catalog.url_for(service_type='metric')
except exceptions.EndpointNotFound:
self._gnocchi_is_enabled = False
except exceptions.ClientException:
LOG.warn(_LW("Can't connect to keystone, assuming gnocchi "
"is disabled and retry later"))
return False
else:
self._gnocchi_is_enabled = True
LOG.warn(_LW("ceilometer-api started with gnocchi "
"enabled. The resources/meters/samples "
"URLs are disabled."))
return self._gnocchi_is_enabled
@pecan.expose()
def _lookup(self, kind, *remainder):
if (kind in ['meters', 'resources', 'samples']
and self.gnocchi_is_enabled):
gnocchi_abort()
elif kind == 'meters':
return meters.MetersController(), remainder
elif kind == 'resources':
return resources.ResourcesController(), remainder
elif kind == 'samples':
return samples.SamplesController(), remainder
elif kind == 'query':
return QueryController(
gnocchi_is_enabled=self.gnocchi_is_enabled,
), remainder
elif kind == 'alarms':
return alarms.AlarmsController(), remainder
else:
pecan.abort(404)

View File

@ -25,6 +25,7 @@ from ceilometer.tests import db as db_test_base
OPT_GROUP_NAME = 'keystone_authtoken'
cfg.CONF.import_group(OPT_GROUP_NAME, "keystonemiddleware.auth_token")
cfg.CONF.import_group('api', 'ceilometer.api.controllers.v2.root')
class FunctionalTest(db_test_base.TestBase):
@ -47,6 +48,9 @@ class FunctionalTest(db_test_base.TestBase):
self.CONF.set_override("policy_file",
self.path_get('etc/ceilometer/policy.json'),
group='oslo_policy')
self.CONF.set_override('gnocchi_is_enabled', False, group='api')
self.app = self._make_app()
def _make_app(self, enable_acl=False):

View File

@ -0,0 +1,65 @@
#
# 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 keystoneclient.openstack.common.apiclient import exceptions
import mock
from oslotest import mockpatch
from ceilometer.tests.api import v2
class TestAPIUpgradePath(v2.FunctionalTest):
def _setup_osloconfig_options(self):
self.CONF.set_override('gnocchi_is_enabled', True, group='api')
def _setup_keystone_mock(self):
self.CONF.set_override('gnocchi_is_enabled', None, group='api')
self.ks = mock.Mock()
self.ks.service_catalog.url_for.side_effect = self._url_for
self.useFixture(mockpatch.Patch(
'ceilometer.keystone_client.get_client', return_value=self.ks))
@staticmethod
def _url_for(service_type=None):
if service_type == 'metric':
return 'http://gnocchi/'
raise exceptions.EndpointNotFound()
def _do_test_gnocchi_enabled_without_database_backend(self):
self.CONF.set_override('dispatcher', 'gnocchi')
for endpoint in ['meters', 'samples', 'resources']:
response = self.app.get(self.PATH_PREFIX + '/' + endpoint,
status=410)
self.assertIn('Gnocchi API', response.body)
for endpoint in ['events', 'event_types']:
self.app.get(self.PATH_PREFIX + '/' + endpoint,
status=200)
response = self.post_json('/query/samples',
params={
"filter": '{"=": {"type": "creation"}}',
"orderby": '[{"timestamp": "DESC"}]',
"limit": 3
}, status=410)
self.assertIn('Gnocchi API', response.body)
def test_gnocchi_enabled_without_database_backend_keystone(self):
self._setup_keystone_mock()
self._do_test_gnocchi_enabled_without_database_backend()
self.ks.service_catalog.url_for.assert_called_once_with(
service_type="metric")
def test_gnocchi_enabled_without_database_backend_configoptions(self):
self._setup_osloconfig_options()
self._do_test_gnocchi_enabled_without_database_backend()

View File

@ -77,6 +77,7 @@ class ConfigFixture(fixture.GabbiFixture):
conf.set_override('alarm_connection', '', group='database')
conf.set_override('pecan_debug', True, group='api')
conf.set_override('gnocchi_is_enabled', False, group='api')
conf.set_override('store_events', True, group='notification')