summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhiQiang Fan <aji.zqfan@gmail.com>2014-11-02 10:25:19 +0800
committerZhiQiang Fan <zhiqiang.fan@huawei.com>2015-02-05 11:17:02 +0800
commit1831bc1160f65e13e1bc0a8b72bbe2a1f4c8dfd0 (patch)
treed2e676859acb67124699ad8daca969ebe9c0be19
parent945f9a392a725c2cae494dcb36cd77d75225718a (diff)
downloadpython-ceilometerclient-1831bc1160f65e13e1bc0a8b72bbe2a1f4c8dfd0.tar.gz
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
-rw-r--r--ceilometerclient/common/utils.py3
-rw-r--r--ceilometerclient/tests/v2/test_samples.py147
-rw-r--r--ceilometerclient/tests/v2/test_shell.py172
-rw-r--r--ceilometerclient/v2/client.py3
-rw-r--r--ceilometerclient/v2/samples.py40
-rw-r--r--ceilometerclient/v2/shell.py45
-rw-r--r--requirements.txt1
7 files changed, 338 insertions, 73 deletions
diff --git a/ceilometerclient/common/utils.py b/ceilometerclient/common/utils.py
index b5bbe79..4e77632 100644
--- a/ceilometerclient/common/utils.py
+++ b/ceilometerclient/common/utils.py
@@ -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:
diff --git a/ceilometerclient/tests/v2/test_samples.py b/ceilometerclient/tests/v2/test_samples.py
index dfbdf39..aee2220 100644
--- a/ceilometerclient/tests/v2/test_samples.py
+++ b/ceilometerclient/tests/v2/test_samples.py
@@ -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())
diff --git a/ceilometerclient/tests/v2/test_shell.py b/ceilometerclient/tests/v2/test_shell.py
index 65c8bb1..fcd2d6a 100644
--- a/ceilometerclient/tests/v2/test_shell.py
+++ b/ceilometerclient/tests/v2/test_shell.py
@@ -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)
diff --git a/ceilometerclient/v2/client.py b/ceilometerclient/v2/client.py
index 19f847d..a4a3619 100644
--- a/ceilometerclient/v2/client.py
+++ b/ceilometerclient/v2/client.py
@@ -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)
diff --git a/ceilometerclient/v2/samples.py b/ceilometerclient/v2/samples.py
index 8081efc..c046051 100644
--- a/ceilometerclient/v2/samples.py
+++ b/ceilometerclient/v2/samples.py
@@ -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
diff --git a/ceilometerclient/v2/shell.py b/ceilometerclient/v2/shell.py
index 7cab85e..145d542 100644
--- a/ceilometerclient/v2/shell.py
+++ b/ceilometerclient/v2/shell.py
@@ -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',
diff --git a/requirements.txt b/requirements.txt
index 99d597a..0fe0896 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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