Add simple capabilities API

Precursor-to: BP selectable-aggregates

The per-deployment variation in API capabilities is effectively
determined by the lack of strict feature parity across storage
drivers.

Currently we require that callers either know a priori which
features are provided and which not, or else to probe the API
and handle the NotImplementedErrors.

With the advent of more complexity in the API (complex queries,
selectable aggregates etc.) it would be useful to allow a more
direct interrogation of capabilities in the current deployment.

We expose the current set of capabilities in the form of a
simple nested dictionary, e.g.:

    {
        'meters': {'pagination': True,
                   'query': {'simple': True,
                             'complex': False}},
        'resources': {'pagination': False,
                      'query': {'simple': True,
                                'metadata': True,
                                'complex': False}},
        'samples': {'pagination': True,
                    'groupby': True,
                    'query': {'simple': True,
                              'complex': True}},
        'statistics': {'pagination': True,
                       'groupby': True,
                       'query': {'simple': True,
                                 'metadata': True,
                                 'complex': False},
                       'aggregation': {'standard': True,
                                       'selectable': {
                                           'max': True,
                                           'min': True,
                                           'sum': True,
                                           'avg': True,
                                           'count': True,
                                           'stddev': True,
                                           'cardinality': True,
                                           'quartile': False}}},
        'alarms': {'query': {'simple': True,
                             'complex': True},
                   'history': {'query': {'simple': True,
                                         'complex': True}}},
        'events': {'query': {'simple': True}},
    }

which is flattened in the usual way (using a similar approach as
used for resource metadata).

Leaf nodes of the capabilities representation are boolean, with
absence being implicitly False.

Change-Id: I8de9e153d7641d5d38ca9b79bab66887257c3010
This commit is contained in:
Eoghan Glynn 2014-02-25 11:26:28 +00:00
parent c27d06421f
commit 4ca9550cfb
7 changed files with 209 additions and 1 deletions

View File

@ -2213,6 +2213,71 @@ class QueryController(rest.RestController):
alarms = QueryAlarmsController()
def _flatten_capabilities(capabilities):
return dict((k, v) for k, v in utils.recursive_keypairs(capabilities))
class Capabilities(_Base):
"""A representation of the API capabilities, usually constrained
by restrictions imposed by the storage driver.
"""
api = {wtypes.text: bool}
"A flattened dictionary of API capabilities"
@classmethod
def sample(cls):
return cls(
api=_flatten_capabilities({
'meters': {'pagination': True,
'query': {'simple': True,
'metadata': True,
'complex': False}},
'resources': {'pagination': False,
'query': {'simple': True,
'metadata': True,
'complex': False}},
'samples': {'pagination': True,
'groupby': True,
'query': {'simple': True,
'metadata': True,
'complex': True}},
'statistics': {'pagination': True,
'groupby': True,
'query': {'simple': True,
'metadata': True,
'complex': False},
'aggregation': {'standard': True,
'selectable': {
'max': True,
'min': True,
'sum': True,
'avg': True,
'count': True,
'stddev': True,
'cardinality': True,
'quartile': False}}},
'alarms': {'query': {'simple': True,
'complex': True},
'history': {'query': {'simple': True,
'complex': True}}},
'events': {'query': {'simple': True}},
})
)
class CapabilitiesController(rest.RestController):
"""Manages capabilities queries.
"""
@wsme_pecan.wsexpose(Capabilities)
def get(self):
# variation in API capabilities is effectively determined by
# the lack of strict feature parity across storage drivers
driver_capabilities = pecan.request.storage_conn.get_capabilities()
return Capabilities(api=_flatten_capabilities(driver_capabilities))
class V2Controller(object):
"""Version 2 API controller root."""
@ -2222,5 +2287,5 @@ class V2Controller(object):
alarms = AlarmsController()
event_types = EventTypesController()
events = EventsController()
query = QueryController()
capabilities = CapabilitiesController()

View File

@ -118,6 +118,44 @@ class StorageEngine(object):
class Connection(object):
"""Base class for storage system connections."""
"""A dictionary representing the capabilities of this driver.
"""
DEFAULT_CAPABILITIES = {
'meters': {'pagination': False,
'query': {'simple': False,
'metadata': False,
'complex': False}},
'resources': {'pagination': False,
'query': {'simple': False,
'metadata': False,
'complex': False}},
'samples': {'pagination': False,
'groupby': False,
'query': {'simple': False,
'metadata': False,
'complex': False}},
'statistics': {'pagination': False,
'groupby': False,
'query': {'simple': False,
'metadata': False,
'complex': False},
'aggregation': {'standard': False,
'selectable': {
'max': False,
'min': False,
'sum': False,
'avg': False,
'count': False,
'stddev': False,
'cardinality': False}}
},
'alarms': {'query': {'simple': False,
'complex': False},
'history': {'query': {'simple': False,
'complex': False}}},
'events': {'query': {'simple': False}},
}
@staticmethod
def __init__(conf):
"""Constructor."""
@ -355,3 +393,9 @@ class Connection(object):
raise NotImplementedError(_('Complex query for alarms \
history is not implemented.'))
@staticmethod
def get_capabilities():
"""Return an dictionary representing the capabilities of this driver.
"""
raise NotImplementedError(_('Capabilities not implemented.'))

View File

@ -38,6 +38,7 @@ from ceilometer import storage
from ceilometer.storage import base
from ceilometer.storage import models
from ceilometer.storage import pymongo_base
from ceilometer import utils
LOG = log.getLogger(__name__)
@ -409,3 +410,24 @@ class Connection(pymongo_base.Connection):
stat.period_start = stat.duration_start
stat.period_end = stat.duration_end
yield stat
def get_capabilities(self):
"""Return an dictionary representing the capabilities of this driver.
"""
available = {
'meters': {'query': {'simple': True,
'metadata': True}},
'resources': {'query': {'simple': True,
'metadata': True}},
'samples': {'query': {'simple': True,
'metadata': True,
'complex': True}},
'statistics': {'groupby': True,
'query': {'simple': True,
'metadata': True},
'aggregation': {'standard': True}},
'alarms': {'query': {'simple': True,
'complex': True},
'history': {'query': {'simple': True}}},
}
return utils.update_nested(self.DEFAULT_CAPABILITIES, available)

View File

@ -629,6 +629,22 @@ class Connection(base.Connection):
self._update_meter_stats(results[-1], meter)
return results
def get_capabilities(self):
"""Return an dictionary representing the capabilities of this driver.
"""
available = {
'meters': {'query': {'simple': True,
'metadata': True}},
'resources': {'query': {'simple': True,
'metadata': True}},
'samples': {'query': {'simple': True,
'metadata': True}},
'statistics': {'query': {'simple': True,
'metadata': True},
'aggregation': {'standard': True}},
}
return utils.update_nested(self.DEFAULT_CAPABILITIES, available)
###############
# This is a very crude version of "in-memory HBase", which implements just

View File

@ -39,6 +39,7 @@ from ceilometer import storage
from ceilometer.storage import base
from ceilometer.storage import models
from ceilometer.storage import pymongo_base
from ceilometer import utils
cfg.CONF.import_opt('time_to_live', 'ceilometer.storage',
group="database")
@ -701,3 +702,25 @@ class Connection(pymongo_base.Connection):
orderby,
limit,
models.AlarmChange)
def get_capabilities(self):
"""Return an dictionary representing the capabilities of this driver.
"""
available = {
'meters': {'query': {'simple': True,
'metadata': True}},
'resources': {'query': {'simple': True,
'metadata': True}},
'samples': {'query': {'simple': True,
'metadata': True,
'complex': True}},
'statistics': {'groupby': True,
'query': {'simple': True,
'metadata': True},
'aggregation': {'standard': True}},
'alarms': {'query': {'simple': True,
'complex': True},
'history': {'query': {'simple': True,
'complex': True}}},
}
return utils.update_nested(self.DEFAULT_CAPABILITIES, available)

View File

@ -1282,3 +1282,28 @@ class QueryTransformer(object):
def get_query(self):
return self.query
def get_capabilities(self):
"""Return an dictionary representing the capabilities of this driver.
"""
available = {
'meters': {'query': {'simple': True,
'metadata': True}},
'resources': {'query': {'simple': True,
'metadata': True}},
'samples': {'pagination': True,
'groupby': True,
'query': {'simple': True,
'metadata': True,
'complex': True}},
'statistics': {'groupby': True,
'query': {'simple': True,
'metadata': True},
'aggregation': {'standard': True}},
'alarms': {'query': {'simple': True,
'complex': True},
'history': {'query': {'simple': True,
'complex': True}}},
'events': {'query': {'simple': True}},
}
return utils.update_nested(self.DEFAULT_CAPABILITIES, available)

View File

@ -132,3 +132,16 @@ def lowercase_values(mapping):
items = mapping.items()
for key, value in items:
mapping[key] = value.lower()
def update_nested(d, u):
"""Updates the leaf nodes in a nest dict, without replacing
entire sub-dicts.
"""
for k, v in u.iteritems():
if isinstance(v, dict):
r = update_nested(d.get(k, {}), v)
d[k] = r
else:
d[k] = u[k]
return d