Add Sample API support

Sample API has been implemented in Ceilometer for a long time, but
CLI is lack of such support, this patch implements Sample CLI.

Implements blueprint cli-samples-api

Change-Id: I67152c636526dad3ec27e06058ff73ad969ae2b9
DocImpact
This commit is contained in:
ZhiQiang Fan
2014-11-02 10:25:19 +08:00
committed by ZhiQiang Fan
parent 945f9a392a
commit 1831bc1160
7 changed files with 338 additions and 73 deletions

View File

@@ -19,6 +19,7 @@ import sys
import textwrap
import uuid
from oslo.serialization import jsonutils
from oslo.utils import encodeutils
from oslo.utils import importutils
import prettytable
@@ -89,7 +90,7 @@ def print_dict(d, dict_property="Property", wrap=0):
for k, v in sorted(six.iteritems(d)):
# convert dict to str to check length
if isinstance(v, dict):
v = str(v)
v = jsonutils.dumps(v)
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, six.string_types) and r'\n' in v:

View File

@@ -20,62 +20,103 @@ from ceilometerclient.openstack.common.apiclient import fake_client
from ceilometerclient.tests import utils
import ceilometerclient.v2.samples
GET_SAMPLE = {u'counter_name': u'instance',
u'user_id': u'user-id',
u'resource_id': u'resource-id',
u'timestamp': u'2012-07-02T10:40:00',
u'source': u'test_source',
u'message_id': u'54558a1c-6ef3-11e2-9875-5453ed1bbb5f',
u'counter_unit': u'',
u'counter_volume': 1.0,
u'project_id': u'project1',
u'resource_metadata': {u'tag': u'self.counter',
u'display_name': u'test-server'},
u'counter_type': u'cumulative'}
CREATE_SAMPLE = copy.deepcopy(GET_SAMPLE)
GET_OLD_SAMPLE = {u'counter_name': u'instance',
u'user_id': u'user-id',
u'resource_id': u'resource-id',
u'timestamp': u'2012-07-02T10:40:00',
u'source': u'test_source',
u'message_id': u'54558a1c-6ef3-11e2-9875-5453ed1bbb5f',
u'counter_unit': u'',
u'counter_volume': 1.0,
u'project_id': u'project1',
u'resource_metadata': {u'tag': u'self.counter',
u'display_name': u'test-server'},
u'counter_type': u'cumulative'}
CREATE_SAMPLE = copy.deepcopy(GET_OLD_SAMPLE)
del CREATE_SAMPLE['message_id']
del CREATE_SAMPLE['source']
base_url = '/v2/meters/instance'
args = ('q.field=resource_id&q.field=source&q.op=&q.op='
'&q.type=&q.type=&q.value=foo&q.value=bar')
args_limit = 'limit=1'
fixtures = {
base_url:
{
GET_SAMPLE = {
"user_id": None,
"resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05",
"timestamp": "2014-11-03T13:37:46",
"meter": "image",
"volume": 1.0,
"source": "openstack",
"recorded_at": "2014-11-03T13:37:46.994458",
"project_id": "2cc3a7bb859b4bacbeab0aa9ca673033",
"type": "gauge",
"id": "98b5f258-635e-11e4-8bdd-0025647390c1",
"unit": "image",
"resource_metadata": {},
}
METER_URL = '/v2/meters/instance'
SAMPLE_URL = '/v2/samples'
QUERIES = ('q.field=resource_id&q.field=source&q.op=&q.op='
'&q.type=&q.type=&q.value=foo&q.value=bar')
LIMIT = 'limit=1'
OLD_SAMPLE_FIXTURES = {
METER_URL: {
'GET': (
{},
[GET_SAMPLE]
[GET_OLD_SAMPLE]
),
'POST': (
{},
[CREATE_SAMPLE],
),
},
'%s?%s' % (base_url, args):
{
'%s?%s' % (METER_URL, QUERIES): {
'GET': (
{},
[],
),
},
'%s?%s' % (base_url, args_limit):
{
'%s?%s' % (METER_URL, LIMIT): {
'GET': (
{},
[GET_SAMPLE]
[GET_OLD_SAMPLE]
),
}
}
SAMPLE_FIXTURES = {
SAMPLE_URL: {
'GET': (
(),
[GET_SAMPLE]
),
},
'%s?%s' % (SAMPLE_URL, QUERIES): {
'GET': (
{},
[],
),
},
'%s?%s' % (SAMPLE_URL, LIMIT): {
'GET': (
{},
[GET_SAMPLE],
),
},
'%s/%s' % (SAMPLE_URL, GET_SAMPLE['id']): {
'GET': (
{},
GET_SAMPLE,
),
},
}
class SampleManagerTest(utils.BaseTestCase):
class OldSampleManagerTest(utils.BaseTestCase):
def setUp(self):
super(SampleManagerTest, self).setUp()
self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
super(OldSampleManagerTest, self).setUp()
self.http_client = fake_client.FakeHTTPClient(
fixtures=OLD_SAMPLE_FIXTURES)
self.api = client.BaseClient(self.http_client)
self.mgr = ceilometerclient.v2.samples.SampleManager(self.api)
self.mgr = ceilometerclient.v2.samples.OldSampleManager(self.api)
def test_list_by_meter_name(self):
samples = list(self.mgr.list(meter_name='instance'))
@@ -94,7 +135,7 @@ class SampleManagerTest(utils.BaseTestCase):
{"field": "source",
"value": "bar"},
]))
expect = ['GET', '%s?%s' % (base_url, args)]
expect = ['GET', '%s?%s' % (METER_URL, QUERIES)]
self.http_client.assert_called(*expect)
self.assertEqual(len(samples), 0)
@@ -111,3 +152,47 @@ class SampleManagerTest(utils.BaseTestCase):
expect = ['GET', '/v2/meters/instance?limit=1']
self.http_client.assert_called(*expect)
self.assertEqual(len(samples), 1)
class SampleManagerTest(utils.BaseTestCase):
def setUp(self):
super(SampleManagerTest, self).setUp()
self.http_client = fake_client.FakeHTTPClient(
fixtures=SAMPLE_FIXTURES)
self.api = client.BaseClient(self.http_client)
self.mgr = ceilometerclient.v2.samples.SampleManager(self.api)
def test_sample_list(self):
samples = list(self.mgr.list())
expect = [
'GET', '/v2/samples'
]
self.http_client.assert_called(*expect)
self.assertEqual(1, len(samples))
self.assertEqual('9b651dfd-7d30-402b-972e-212b2c4bfb05',
samples[0].resource_id)
def test_sample_list_with_queries(self):
queries = [
{"field": "resource_id",
"value": "foo"},
{"field": "source",
"value": "bar"},
]
samples = list(self.mgr.list(q=queries))
expect = ['GET', '%s?%s' % (SAMPLE_URL, QUERIES)]
self.http_client.assert_called(*expect)
self.assertEqual(0, len(samples))
def test_sample_list_with_limit(self):
samples = list(self.mgr.list(limit=1))
expect = ['GET', '/v2/samples?limit=1']
self.http_client.assert_called(*expect)
self.assertEqual(1, len(samples))
def test_sample_get(self):
sample = self.mgr.get(GET_SAMPLE['id'])
expect = ['GET', '/v2/samples/' + GET_SAMPLE['id']]
self.http_client.assert_called(*expect)
self.assertEqual(GET_SAMPLE, sample.to_dict())

View File

@@ -324,43 +324,70 @@ class ShellAlarmCommandTest(utils.BaseTestCase):
class ShellSampleListCommandTest(utils.BaseTestCase):
METER = 'cpu_util'
SAMPLES = [{"counter_name": "cpu_util",
"resource_id": "5dcf5537-3161-4e25-9235-407e1385bd35",
"timestamp": "2013-10-15T05:50:30",
"counter_unit": "%",
"counter_volume": 0.261666666667,
"counter_type": "gauge"},
{"counter_name": "cpu_util",
"resource_id": "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f",
"timestamp": "2013-10-15T05:50:29",
"counter_unit": "%",
"counter_volume": 0.261666666667,
"counter_type": "gauge"},
{"counter_name": "cpu_util",
"resource_id": "5dcf5537-3161-4e25-9235-407e1385bd35",
"timestamp": "2013-10-15T05:40:30",
"counter_unit": "%",
"counter_volume": 0.251247920133,
"counter_type": "gauge"},
{"counter_name": "cpu_util",
"resource_id": "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f",
"timestamp": "2013-10-15T05:40:29",
"counter_unit": "%",
"counter_volume": 0.26,
"counter_type": "gauge"}]
SAMPLE_VALUES = (
("cpu_util",
"5dcf5537-3161-4e25-9235-407e1385bd35",
"2013-10-15T05:50:30",
"%",
0.261666666667,
"gauge",
"86536501-b2c9-48f6-9c6a-7a5b14ba7482"),
("cpu_util",
"87d197e9-9cf6-4c25-bc66-1b1f4cedb52f",
"2013-10-15T05:50:29",
"%",
0.261666666667,
"gauge",
"fe2a91ec-602b-4b55-8cba-5302ce3b916e",),
("cpu_util",
"5dcf5537-3161-4e25-9235-407e1385bd35",
"2013-10-15T05:40:30",
"%",
0.251247920133,
"gauge",
"52768bcb-b4e9-4db9-a30c-738c758b6f43"),
("cpu_util",
"87d197e9-9cf6-4c25-bc66-1b1f4cedb52f",
"2013-10-15T05:40:29",
"%",
0.26,
"gauge",
"31ae614a-ac6b-4fb9-b106-4667bae03308"),
)
OLD_SAMPLES = [
dict(counter_name=s[0],
resource_id=s[1],
timestamp=s[2],
counter_unit=s[3],
counter_volume=s[4],
counter_type=s[5])
for s in SAMPLE_VALUES
]
SAMPLES = [
dict(meter=s[0],
resource_id=s[1],
timestamp=s[2],
unit=s[3],
volume=s[4],
type=s[5],
id=s[6])
for s in SAMPLE_VALUES
]
def setUp(self):
super(ShellSampleListCommandTest, self).setUp()
self.cc = mock.Mock()
self.args = mock.Mock()
self.args.meter = self.METER
self.args.query = None
self.args.limit = None
@mock.patch('sys.stdout', new=six.StringIO())
def test_sample_list(self):
sample_list = [samples.Sample(mock.Mock(), sample)
for sample in self.SAMPLES]
def test_old_sample_list(self):
self.args.meter = self.METER
sample_list = [samples.OldSample(mock.Mock(), sample)
for sample in self.OLD_SAMPLES]
self.cc.samples.list.return_value = sample_list
ceilometer_shell.do_sample_list(self.cc, self.args)
@@ -388,6 +415,91 @@ class ShellSampleListCommandTest(utils.BaseTestCase):
+------+---------------------+
''', sys.stdout.getvalue())
@mock.patch('sys.stdout', new=six.StringIO())
def test_sample_list(self):
self.args.meter = None
sample_list = [samples.Sample(mock.Mock(), sample)
for sample in self.SAMPLES]
self.cc.new_samples.list.return_value = sample_list
ceilometer_shell.do_sample_list(self.cc, self.args)
self.cc.new_samples.list.assert_called_once_with(
q=None,
limit=None)
self.assertEqual('''\
+--------------------------------------+--------------------------------------\
+----------+-------+----------------+------+---------------------+
| ID | Resource ID \
| Name | Type | Volume | Unit | Timestamp |
+--------------------------------------+--------------------------------------\
+----------+-------+----------------+------+---------------------+
| 86536501-b2c9-48f6-9c6a-7a5b14ba7482 | 5dcf5537-3161-4e25-9235-407e1385bd35 \
| cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:30 |
| fe2a91ec-602b-4b55-8cba-5302ce3b916e | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \
| cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:29 |
| 52768bcb-b4e9-4db9-a30c-738c758b6f43 | 5dcf5537-3161-4e25-9235-407e1385bd35 \
| cpu_util | gauge | 0.251247920133 | % | 2013-10-15T05:40:30 |
| 31ae614a-ac6b-4fb9-b106-4667bae03308 | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \
| cpu_util | gauge | 0.26 | % | 2013-10-15T05:40:29 |
+--------------------------------------+--------------------------------------\
+----------+-------+----------------+------+---------------------+
''', sys.stdout.getvalue())
class ShellSampleShowCommandTest(utils.BaseTestCase):
SAMPLE = {
"user_id": None,
"resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05",
"timestamp": "2014-11-03T13:37:46",
"meter": "image",
"volume": 1.0,
"source": "openstack",
"recorded_at": "2014-11-03T13:37:46.994458",
"project_id": "2cc3a7bb859b4bacbeab0aa9ca673033",
"type": "gauge",
"id": "98b5f258-635e-11e4-8bdd-0025647390c1",
"unit": "image",
"metadata": {
"name": "cirros-0.3.2-x86_64-uec",
}
}
def setUp(self):
super(ShellSampleShowCommandTest, self).setUp()
self.cc = mock.Mock()
self.args = mock.Mock()
self.args.sample_id = "98b5f258-635e-11e4-8bdd-0025647390c1"
@mock.patch('sys.stdout', new=six.StringIO())
def test_sample_show(self):
sample = samples.Sample(mock.Mock(), self.SAMPLE)
self.cc.samples.get.return_value = sample
ceilometer_shell.do_sample_show(self.cc, self.args)
self.cc.samples.get.assert_called_once_with(
"98b5f258-635e-11e4-8bdd-0025647390c1")
self.assertEqual('''\
+-------------+--------------------------------------+
| Property | Value |
+-------------+--------------------------------------+
| id | 98b5f258-635e-11e4-8bdd-0025647390c1 |
| metadata | {"name": "cirros-0.3.2-x86_64-uec"} |
| meter | image |
| project_id | 2cc3a7bb859b4bacbeab0aa9ca673033 |
| recorded_at | 2014-11-03T13:37:46.994458 |
| resource_id | 9b651dfd-7d30-402b-972e-212b2c4bfb05 |
| source | openstack |
| timestamp | 2014-11-03T13:37:46 |
| type | gauge |
| unit | image |
| user_id | None |
| volume | 1.0 |
+-------------+--------------------------------------+
''', sys.stdout.getvalue())
class ShellSampleCreateCommandTest(utils.BaseTestCase):
@@ -422,9 +534,9 @@ class ShellSampleCreateCommandTest(utils.BaseTestCase):
@mock.patch('sys.stdout', new=six.StringIO())
def test_sample_create(self):
ret_sample = [samples.Sample(mock.Mock(), sample)
ret_sample = [samples.OldSample(mock.Mock(), sample)
for sample in self.SAMPLE]
self.cc.samples.create.return_value = ret_sample
self.cc.old_samples.create.return_value = ret_sample
ceilometer_shell.do_sample_create(self.cc, self.args)

View File

@@ -63,7 +63,8 @@ class Client(object):
self.http_client = client.BaseClient(self.client)
self.meters = meters.MeterManager(self.http_client)
self.samples = samples.SampleManager(self.http_client)
self.samples = samples.OldSampleManager(self.http_client)
self.new_samples = samples.SampleManager(self.http_client)
self.statistics = statistics.StatisticsManager(self.http_client)
self.resources = resources.ResourceManager(self.http_client)
self.alarms = alarms.AlarmManager(self.http_client)

View File

@@ -26,13 +26,18 @@ CREATION_ATTRIBUTES = ('source',
'resource_metadata')
class Sample(base.Resource):
class OldSample(base.Resource):
"""Represents API v2 OldSample object.
Model definition:
http://docs.openstack.org/developer/ceilometer/webapi/v2.html#OldSample
"""
def __repr__(self):
return "<Sample %s>" % self._info
return "<OldSample %s>" % self._info
class SampleManager(base.Manager):
resource_class = Sample
class OldSampleManager(base.Manager):
resource_class = OldSample
@staticmethod
def _path(counter_name=None):
@@ -49,4 +54,29 @@ class SampleManager(base.Manager):
url = self._path(counter_name=kwargs['counter_name'])
body = self.api.post(url, json=[new]).json()
if body:
return [Sample(self, b) for b in body]
return [OldSample(self, b) for b in body]
class Sample(base.Resource):
"""Represents API v2 Sample object.
Model definition:
http://docs.openstack.org/developer/ceilometer/webapi/v2.html#Sample
"""
def __repr__(self):
return "<Sample %s>" % self._info
class SampleManager(base.Manager):
resource_class = Sample
def list(self, q=None, limit=None):
params = ['limit=%s' % str(limit)] if limit else None
return self._list(options.build_url("/v2/samples", q, params))
def get(self, sample_id):
path = "/v2/samples/" + sample_id
try:
return self._list(path, expect_single=True)[0]
except IndexError:
return None

View File

@@ -124,12 +124,19 @@ def do_statistics(cc, args):
@utils.arg('-q', '--query', metavar='<QUERY>',
help='key[op]data_type::value; list. data_type is optional, '
'but if supplied must be string, integer, float, or boolean.')
@utils.arg('-m', '--meter', metavar='<NAME>', required=True,
@utils.arg('-m', '--meter', metavar='<NAME>',
action=NotEmptyAction, help='Name of meter to show samples for.')
@utils.arg('-l', '--limit', metavar='<NUMBER>',
help='Maximum number of samples to return.')
def do_sample_list(cc, args):
"""List the samples for a meter."""
"""List the samples (return OldSample objects if -m/--meter is set)."""
if not args.meter:
return _do_sample_list(cc, args)
else:
return _do_old_sample_list(cc, args)
def _do_old_sample_list(cc, args):
fields = {'meter_name': args.meter,
'q': options.cli_to_array(args.query),
'limit': args.limit}
@@ -142,8 +149,36 @@ def do_sample_list(cc, args):
'Timestamp']
fields = ['resource_id', 'counter_name', 'counter_type',
'counter_volume', 'counter_unit', 'timestamp']
utils.print_list(samples, fields, field_labels,
sortby=None)
utils.print_list(samples, fields, field_labels, sortby=None)
def _do_sample_list(cc, args):
fields = {
'q': options.cli_to_array(args.query),
'limit': args.limit
}
samples = cc.new_samples.list(**fields)
field_labels = ['ID', 'Resource ID', 'Name', 'Type', 'Volume', 'Unit',
'Timestamp']
fields = ['id', 'resource_id', 'meter', 'type', 'volume', 'unit',
'timestamp']
utils.print_list(samples, fields, field_labels, sortby=None)
@utils.arg('sample_id', metavar='<SAMPLE_ID>', action=NotEmptyAction,
help='ID (aka message ID) of the sample to show.')
def do_sample_show(cc, args):
'''Show an sample.'''
sample = cc.samples.get(args.sample_id)
if sample is None:
raise exc.CommandError('Sample not found: %s' % args.sample_id)
fields = ['id', 'meter', 'volume', 'type', 'unit', 'source',
'resource_id', 'user_id', 'project_id',
'timestamp', 'recorded_at', 'metadata']
data = dict((f, getattr(sample, f, '')) for f in fields)
utils.print_dict(data, wrap=72)
@utils.arg('--project-id', metavar='<PROJECT_ID>',
@@ -181,7 +216,7 @@ def do_sample_create(cc, args={}):
fields[k] = json.loads(v)
else:
fields[arg_to_field_mapping.get(k, k)] = v
sample = cc.samples.create(**fields)
sample = cc.old_samples.create(**fields)
fields = ['counter_name', 'user_id', 'resource_id',
'timestamp', 'message_id', 'source', 'counter_unit',
'counter_volume', 'project_id', 'resource_metadata',

View File

@@ -5,6 +5,7 @@ pbr>=0.6,!=0.7,<1.0
argparse
iso8601>=0.1.9
oslo.i18n>=1.3.0 # Apache-2.0
oslo.serialization>=1.2.0 # Apache-2.0
oslo.utils>=1.2.0 # Apache-2.0
PrettyTable>=0.7,<0.8
python-keystoneclient>=0.11.1