Implement the /v2/samples/<sample-id> 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 patch is about retreiving only one sample with the id. The fields are also correctly named this time. Change-Id: Ifdd7f024b066407cd42ec0572385f97acd312125 Blueprint: sample-api
This commit is contained in:
parent
8557ad1c0d
commit
12abf68a92
@ -973,6 +973,20 @@ class SamplesController(rest.RestController):
|
||||
return map(Sample.from_db_model,
|
||||
pecan.request.storage_conn.get_samples(f, limit=limit))
|
||||
|
||||
@wsme_pecan.wsexpose(Sample, wtypes.text)
|
||||
def get_one(self, sample_id):
|
||||
"""Return a sample
|
||||
|
||||
:param sample_id: the id of the sample
|
||||
"""
|
||||
f = storage.SampleFilter(message_id=sample_id)
|
||||
|
||||
samples = list(pecan.request.storage_conn.get_samples(f))
|
||||
if len(samples) < 1:
|
||||
raise EntityNotFound(_('Sample'), sample_id)
|
||||
|
||||
return Sample.from_db_model(samples[0])
|
||||
|
||||
|
||||
class Resource(_Base):
|
||||
"""An externally defined object for which samples have been received.
|
||||
|
@ -95,13 +95,15 @@ class SampleFilter(object):
|
||||
:param resource: Optional filter for resource id.
|
||||
:param meter: Optional filter for meter type using the meter name.
|
||||
:param source: Optional source filter.
|
||||
:param message_id: Optional sample_id filter.
|
||||
:param metaquery: Optional filter on the metadata
|
||||
"""
|
||||
def __init__(self, user=None, project=None,
|
||||
start=None, start_timestamp_op=None,
|
||||
end=None, end_timestamp_op=None,
|
||||
resource=None, meter=None,
|
||||
source=None, metaquery={}):
|
||||
source=None, message_id=None,
|
||||
metaquery={}):
|
||||
self.user = user
|
||||
self.project = project
|
||||
self.start = utils.sanitize_timestamp(start)
|
||||
@ -112,6 +114,7 @@ class SampleFilter(object):
|
||||
self.meter = meter
|
||||
self.source = source
|
||||
self.metaquery = metaquery
|
||||
self.message_id = message_id
|
||||
|
||||
|
||||
class EventFilter(object):
|
||||
|
@ -128,6 +128,8 @@ def make_query_from_filter(sample_filter, require_meter=True):
|
||||
q['resource_id'] = sample_filter.resource
|
||||
if sample_filter.source:
|
||||
q['source'] = sample_filter.source
|
||||
if sample_filter.message_id:
|
||||
q['message_id'] = sample_filter.message_id
|
||||
|
||||
# so the samples call metadata resource_metadata, so we convert
|
||||
# to that.
|
||||
|
@ -241,6 +241,7 @@ class Connection(base.Connection):
|
||||
# TODO(shengjie) extra dimensions need to be added as CQ
|
||||
'f:user_id': data['user_id'],
|
||||
'f:project_id': data['project_id'],
|
||||
'f:message_id': data['message_id'],
|
||||
'f:resource_id': data['resource_id'],
|
||||
'f:source': data['source'],
|
||||
# add in reversed_ts here for time range scan
|
||||
@ -707,7 +708,8 @@ def reverse_timestamp(dt):
|
||||
|
||||
def make_query(user=None, project=None, meter=None,
|
||||
resource=None, source=None, start=None, start_op=None,
|
||||
end=None, end_op=None, require_meter=True, query_only=False):
|
||||
end=None, end_op=None, message_id=None, require_meter=True,
|
||||
query_only=False):
|
||||
"""Return a filter query string based on the selected parameters.
|
||||
|
||||
:param user: Optional user-id
|
||||
@ -719,6 +721,7 @@ def make_query(user=None, project=None, meter=None,
|
||||
:param start_op: Optional start timestamp operator, like gt, ge
|
||||
:param end: Optional end timestamp
|
||||
:param end_op: Optional end timestamp operator, like lt, le
|
||||
:param message_id: Optional message_id
|
||||
:param require_meter: If true and the filter does not have a meter,
|
||||
raise an error.
|
||||
:param query_only: If true only returns the filter query,
|
||||
@ -735,6 +738,9 @@ def make_query(user=None, project=None, meter=None,
|
||||
if resource:
|
||||
q.append("SingleColumnValueFilter ('f', 'resource_id', =, 'binary:%s')"
|
||||
% resource)
|
||||
if message_id:
|
||||
q.append("SingleColumnValueFilter ('f', 'message_id', =, 'binary:%s')"
|
||||
% message_id)
|
||||
if source:
|
||||
q.append("SingleColumnValueFilter "
|
||||
"('f', 'source', =, 'binary:%s')" % source)
|
||||
@ -792,6 +798,7 @@ def make_query_from_filter(sample_filter, require_meter=True):
|
||||
sample_filter.start_timestamp_op,
|
||||
sample_filter.end,
|
||||
sample_filter.end_timestamp_op,
|
||||
sample_filter.message_id,
|
||||
require_meter)
|
||||
|
||||
|
||||
|
@ -131,6 +131,8 @@ def make_query_from_filter(sample_filter, require_meter=True):
|
||||
q['resource_id'] = sample_filter.resource
|
||||
if sample_filter.source:
|
||||
q['source'] = sample_filter.source
|
||||
if sample_filter.message_id:
|
||||
q['message_id'] = sample_filter.message_id
|
||||
|
||||
# so the samples call metadata resource_metadata, so we convert
|
||||
# to that.
|
||||
|
@ -159,6 +159,8 @@ def make_query_from_filter(session, query, sample_filter, require_meter=True):
|
||||
query = query.filter_by(project_id=sample_filter.project)
|
||||
if sample_filter.resource:
|
||||
query = query.filter_by(resource_id=sample_filter.resource)
|
||||
if sample_filter.message_id:
|
||||
query = query.filter_by(message_id=sample_filter.message_id)
|
||||
|
||||
if sample_filter.metaquery:
|
||||
query = apply_metaquery_filter(session, query,
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import json as jsonutils
|
||||
import logging
|
||||
import testscenarios
|
||||
|
||||
@ -47,6 +48,7 @@ class TestListMeters(FunctionalTest,
|
||||
|
||||
def setUp(self):
|
||||
super(TestListMeters, self).setUp()
|
||||
self.messages = []
|
||||
for cnt in [
|
||||
sample.Sample(
|
||||
'meter.test',
|
||||
@ -131,6 +133,7 @@ class TestListMeters(FunctionalTest,
|
||||
msg = utils.meter_message_from_counter(
|
||||
cnt,
|
||||
self.CONF.publisher.metering_secret)
|
||||
self.messages.append(msg)
|
||||
self.conn.record_metering_data(msg)
|
||||
|
||||
def test_list_meters(self):
|
||||
@ -164,6 +167,33 @@ class TestListMeters(FunctionalTest,
|
||||
self.assertEqual('self.sample4', metadata['tag'])
|
||||
self.assertEqual('prop_value', metadata['properties.prop_1'])
|
||||
|
||||
def test_get_one_sample(self):
|
||||
sample_id = self.messages[1]['message_id']
|
||||
data = self.get_json('/samples/%s' % sample_id)
|
||||
self.assertIn('id', data)
|
||||
self.assertEqual(data, {
|
||||
u'id': sample_id,
|
||||
u'metadata': {u'display_name': u'test-server',
|
||||
u'is_public': u'False',
|
||||
u'size': u'0',
|
||||
u'tag': u'self.sample1',
|
||||
u'util': u'0.47'},
|
||||
u'meter': u'meter.test',
|
||||
u'project_id': u'project-id',
|
||||
u'resource_id': u'resource-id',
|
||||
u'timestamp': u'2012-07-02T11:40:00',
|
||||
u'type': u'cumulative',
|
||||
u'unit': u'',
|
||||
u'user_id': u'user-id',
|
||||
u'volume': 3.0})
|
||||
|
||||
def test_get_not_existing_sample(self):
|
||||
resp = self.get_json('/samples/not_exists', expect_errors=True)
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
self.assertEqual(jsonutils.loads(resp.body)['error_message']
|
||||
['faultstring'],
|
||||
"Sample not_exists Not Found")
|
||||
|
||||
def test_list_samples_with_dict_metadata(self):
|
||||
data = self.get_json('/samples',
|
||||
q=[{'field':
|
||||
@ -171,7 +201,7 @@ class TestListMeters(FunctionalTest,
|
||||
'op': 'eq',
|
||||
'value': 'sub_prop_value',
|
||||
}])
|
||||
self.assertTrue('id' in data[0])
|
||||
self.assertIn('id', data[0])
|
||||
del data[0]['id'] # Randomly generated
|
||||
self.assertEqual(data, [{
|
||||
u'user_id': u'user-id4',
|
||||
|
Loading…
x
Reference in New Issue
Block a user