diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py index dc6d74cda..8b8de6b1a 100644 --- a/ceilometer/api/controllers/v2.py +++ b/ceilometer/api/controllers/v2.py @@ -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() diff --git a/ceilometer/storage/base.py b/ceilometer/storage/base.py index d4a3b2cf2..b3c7ac633 100644 --- a/ceilometer/storage/base.py +++ b/ceilometer/storage/base.py @@ -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.')) diff --git a/ceilometer/storage/impl_db2.py b/ceilometer/storage/impl_db2.py index 26ae03515..3a1642fb2 100644 --- a/ceilometer/storage/impl_db2.py +++ b/ceilometer/storage/impl_db2.py @@ -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) diff --git a/ceilometer/storage/impl_hbase.py b/ceilometer/storage/impl_hbase.py index 5cb904c22..dec594b17 100644 --- a/ceilometer/storage/impl_hbase.py +++ b/ceilometer/storage/impl_hbase.py @@ -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 diff --git a/ceilometer/storage/impl_mongodb.py b/ceilometer/storage/impl_mongodb.py index 8a4d4cf94..895a14006 100644 --- a/ceilometer/storage/impl_mongodb.py +++ b/ceilometer/storage/impl_mongodb.py @@ -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) diff --git a/ceilometer/storage/impl_sqlalchemy.py b/ceilometer/storage/impl_sqlalchemy.py index 3c1cd8a02..e0893da51 100644 --- a/ceilometer/storage/impl_sqlalchemy.py +++ b/ceilometer/storage/impl_sqlalchemy.py @@ -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) diff --git a/ceilometer/utils.py b/ceilometer/utils.py index 0bbde9ae1..bf869223b 100644 --- a/ceilometer/utils.py +++ b/ceilometer/utils.py @@ -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