Implement the /v2/sample API

This is a simple implementation of the new sample endpoint in the API.
It provides raw accesses to the samples collected by Ceilometer, without
imposing any kind of filtering on the meter name.

The fields are also correctly named this time.

Change-Id: I9e01a082c04e0462811befd98899e2f2d4a4dc82
Blueprint: sample-api
This commit is contained in:
Julien Danjou 2013-12-04 17:53:50 +01:00
parent 6cb9b88f09
commit 06a7b498b5
2 changed files with 124 additions and 7 deletions

View File

@ -527,8 +527,10 @@ def _send_notification(event, payload):
notification, notify.INFO, payload)
class Sample(_Base):
class OldSample(_Base):
"""A single measurement for a given meter and resource.
This class is deprecated in favor of Sample.
"""
source = wtypes.text
@ -576,9 +578,9 @@ class Sample(_Base):
if timestamp and isinstance(timestamp, basestring):
timestamp = timeutils.parse_isotime(timestamp)
super(Sample, self).__init__(counter_volume=counter_volume,
resource_metadata=resource_metadata,
timestamp=timestamp, **kwds)
super(OldSample, self).__init__(counter_volume=counter_volume,
resource_metadata=resource_metadata,
timestamp=timestamp, **kwds)
if self.resource_metadata in (wtypes.Unset, None):
self.resource_metadata = {}
@ -706,7 +708,7 @@ class MeterController(rest.RestController):
pecan.request.context['meter_id'] = meter_id
self._id = meter_id
@wsme_pecan.wsexpose([Sample], [Query], int)
@wsme_pecan.wsexpose([OldSample], [Query], int)
def get_all(self, q=[], limit=None):
"""Return samples for the meter.
@ -718,11 +720,11 @@ class MeterController(rest.RestController):
kwargs = _query_to_kwargs(q, storage.SampleFilter.__init__)
kwargs['meter'] = self._id
f = storage.SampleFilter(**kwargs)
return [Sample.from_db_model(e)
return [OldSample.from_db_model(e)
for e in pecan.request.storage_conn.get_samples(f, limit=limit)
]
@wsme_pecan.wsexpose([Sample], body=[Sample])
@wsme_pecan.wsexpose([OldSample], body=[OldSample])
def post(self, samples):
"""Post a list of new Samples to Ceilometer.
@ -884,6 +886,89 @@ class MetersController(rest.RestController):
for m in pecan.request.storage_conn.get_meters(**kwargs)]
class Sample(_Base):
"""One measurement."""
id = wtypes.text
"The unique identifier for the sample."
meter = wtypes.text
"The meter name this sample is for."
type = wtypes.Enum(str, *sample.TYPES)
"The meter type (see :ref:`measurements`)"
unit = wtypes.text
"The unit of measure."
volume = float
"The metered value."
user_id = wtypes.text
"The user this sample was taken for."
project_id = wtypes.text
"The project this sample was taken for."
resource_id = wtypes.text
"The :class:`Resource` this sample was taken for."
source = wtypes.text
"The source that identifies where the sample comes from."
timestamp = datetime.datetime
"When the sample has been generated."
metadata = {wtypes.text: wtypes.text}
"Arbitrary metadata associated with the sample."
@classmethod
def from_db_model(cls, m):
return cls(id=m.message_id,
meter=m.counter_name,
type=m.counter_type,
unit=m.counter_unit,
volume=m.counter_volume,
user_id=m.user_id,
project_id=m.project_id,
resource_id=m.resource_id,
timestamp=m.timestamp,
metadata=_flatten_metadata(m.resource_metadata))
@classmethod
def sample(cls):
return cls(id=str(uuid.uuid1()),
meter='instance',
type='gauge',
unit='instance',
resource_id='bd9431c1-8d69-4ad3-803a-8d4a6b89fd36',
project_id='35b17138-b364-4e6a-a131-8f3099c5be68',
user_id='efd87807-12d2-4b38-9c70-5f5c2ac427ff',
timestamp=timeutils.utcnow(),
source='openstack',
metadata={'name1': 'value1',
'name2': 'value2'},
)
class SamplesController(rest.RestController):
"""Controller managing the samples."""
@wsme_pecan.wsexpose([Sample], [Query], int)
def get_all(self, q=[], limit=None):
"""Return all known samples, based on the data recorded so far.
:param q: Filter rules for the samples to be returned.
:param limit: Maximum number of samples to be returned.
"""
if limit and limit < 0:
raise ClientSideError(_("Limit must be positive"))
kwargs = _query_to_kwargs(q, storage.SampleFilter.__init__)
f = storage.SampleFilter(**kwargs)
return map(Sample.from_db_model,
pecan.request.storage_conn.get_samples(f, limit=limit))
class Resource(_Base):
"""An externally defined object for which samples have been received.
"""
@ -1511,4 +1596,5 @@ class V2Controller(object):
resources = ResourcesController()
meters = MetersController()
samples = SamplesController()
alarms = AlarmsController()

View File

@ -146,6 +146,10 @@ class TestListMeters(FunctionalTest,
self.assertEqual(set(r['source'] for r in data),
set(['test_source', 'test_source1']))
def test_list_samples(self):
data = self.get_json('/samples')
self.assertEqual(5, len(data))
def test_list_meters_with_dict_metadata(self):
data = self.get_json('/meters/meter.mine',
q=[{'field':
@ -160,6 +164,33 @@ class TestListMeters(FunctionalTest,
self.assertEqual('self.sample4', metadata['tag'])
self.assertEqual('prop_value', metadata['properties.prop_1'])
def test_list_samples_with_dict_metadata(self):
data = self.get_json('/samples',
q=[{'field':
'metadata.properties.prop_2.sub_prop_1',
'op': 'eq',
'value': 'sub_prop_value',
}])
self.assertTrue('id' in data[0])
del data[0]['id'] # Randomly generated
self.assertEqual(data, [{
u'user_id': u'user-id4',
u'resource_id': u'resource-id4',
u'timestamp': u'2012-07-02T10:43:00',
u'meter': u'meter.mine',
u'volume': 1.0,
u'project_id': u'project-id2',
u'type': u'gauge',
u'unit': u'',
u'metadata': {u'display_name': u'test-server',
u'properties.prop_2:sub_prop_1': u'sub_prop_value',
u'util': u'0.58',
u'tag': u'self.sample4',
u'properties.prop_1': u'prop_value',
u'is_public': u'True',
u'size': u'0'}
}])
def test_list_meters_metadata_query(self):
data = self.get_json('/meters/meter.test',
q=[{'field': 'metadata.tag',