diff --git a/ceilometerclient/v2/client.py b/ceilometerclient/v2/client.py index 82b29e05..71622a62 100644 --- a/ceilometerclient/v2/client.py +++ b/ceilometerclient/v2/client.py @@ -14,6 +14,8 @@ # under the License. from ceilometerclient.common import http +from ceilometerclient.v2 import meters +from ceilometerclient.v2 import resources from ceilometerclient.v2 import samples from ceilometerclient.v2 import statistics @@ -31,5 +33,7 @@ class Client(http.HTTPClient): def __init__(self, *args, **kwargs): """ Initialize a new client for the Ceilometer v1 API. """ super(Client, self).__init__(*args, **kwargs) + self.meters = meters.MeterManager(self) self.samples = samples.SampleManager(self) self.statistics = statistics.StatisticsManager(self) + self.resources = resources.ResourceManager(self) diff --git a/ceilometerclient/v2/meters.py b/ceilometerclient/v2/meters.py new file mode 100644 index 00000000..baa7cbdd --- /dev/null +++ b/ceilometerclient/v2/meters.py @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 Red Hat, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ceilometerclient.common import base +from ceilometerclient.v2 import options + + +class Meter(base.Resource): + def __repr__(self): + return "" % self._info + + +class MeterManager(base.Manager): + resource_class = Meter + + def list(self, q=None): + path = '/v2/meters' + return self._list(options.build_url(path, q)) diff --git a/ceilometerclient/v2/options.py b/ceilometerclient/v2/options.py new file mode 100644 index 00000000..debf6b21 --- /dev/null +++ b/ceilometerclient/v2/options.py @@ -0,0 +1,83 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import re +import urllib + + +def build_url(path, q): + ''' + This converts from a list of dict's to what the rest api needs + so from: + "[{field=this,op=le,value=34},{field=that,op=eq,value=foo}]" + to: + "?q.field=this&q.op=le&q.value=34& + q.field=that&q.op=eq&q.value=foo" + ''' + if q: + query_params = {'q.field': [], + 'q.value': [], + 'q.op': []} + + for query in q: + for name in ['field', 'op', 'value']: + query_params['q.%s' % name].append(query.get(name, '')) + + path += "?" + urllib.urlencode(query_params, doseq=True) + + return path + + +def cli_to_array(cli_query): + ''' + This converts from the cli list of queries to what is required + by the python api. + so from: + "this<=34;that=foo" + to + "[{field=this,op=le,value=34},{field=that,op=eq,value=foo}]" + ''' + if cli_query is None: + return None + + op_lookup = {'!=': 'ne', + '>=': 'ge', + '<=': 'le', + '>': 'gt', + '<': 'lt', + '=': 'eq'} + + def split_by_op(string): + # two character split (<=,!=) + fragments = re.findall(r'(\w+)([><=])([^ -,\t\n\r\f\v]+)', string) + return fragments + + opts = [] + queries = cli_query.split(';') + for q in queries: + frag = split_by_op(q) + if len(frag) > 1: + raise ValueError('incorrect seperator %s in query "%s"' % + ('(should be ";")', q)) + if len(frag) == 0: + raise ValueError('invalid query %s' % q) + query = frag[0] + opt = {} + opt['field'] = query[0] + opt['op'] = op_lookup[query[1]] + opt['value'] = query[2] + opts.append(opt) + return opts diff --git a/ceilometerclient/v2/resources.py b/ceilometerclient/v2/resources.py new file mode 100644 index 00000000..9b80671c --- /dev/null +++ b/ceilometerclient/v2/resources.py @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 Red Hat, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ceilometerclient.common import base +from ceilometerclient.v2 import options + + +class Resource(base.Resource): + def __repr__(self): + return "" % self._info + + +class ResourceManager(base.Manager): + resource_class = Resource + + def list(self, q=None): + path = '/v2/resources' + return self._list(options.build_url(path, q)) diff --git a/ceilometerclient/v2/samples.py b/ceilometerclient/v2/samples.py index f7e42686..11258a57 100644 --- a/ceilometerclient/v2/samples.py +++ b/ceilometerclient/v2/samples.py @@ -1,5 +1,3 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -13,9 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. -import urllib - from ceilometerclient.common import base +from ceilometerclient.v2 import options class Sample(base.Resource): @@ -26,23 +23,8 @@ class Sample(base.Resource): class SampleManager(base.Manager): resource_class = Sample - @staticmethod - def build_url(path, q): - if q: - query_params = {'q.field': [], - 'q.value': [], - 'q.op': []} - - for query in q: - for name in ['field', 'op', 'value']: - query_params['q.%s' % name].append(query.get(name, '')) - - path += "?" + urllib.urlencode(query_params, doseq=True) - - return path - def list(self, meter_name=None, q=None): path = '/v2/meters' if meter_name: path += '/' + meter_name - return self._list(self.build_url(path, q)) + return self._list(options.build_url(path, q)) diff --git a/ceilometerclient/v2/shell.py b/ceilometerclient/v2/shell.py index 524d46a3..1f11dc59 100644 --- a/ceilometerclient/v2/shell.py +++ b/ceilometerclient/v2/shell.py @@ -1,5 +1,6 @@ +# -*- encoding: utf-8 -*- # -# Copyright © 2013 Red Hat +# Copyright © 2013 Red Hat, Inc # # Author: Angus Salkeld # @@ -16,6 +17,7 @@ # under the License. from ceilometerclient.common import utils +from ceilometerclient.v2 import options import ceilometerclient.exc as exc @@ -26,7 +28,7 @@ import ceilometerclient.exc as exc def do_statistics(cc, args): '''List the statistics for this meters''' fields = {'meter_name': args.meter, - 'q': args.query} + 'q': options.cli_to_array(args.query)} if args.meter is None: raise exc.CommandError('Meter name not provided (-m )') try: @@ -50,7 +52,7 @@ def do_statistics(cc, args): def do_sample_list(cc, args): '''List the samples for this meters''' fields = {'meter_name': args.meter, - 'q': args.query} + 'q': options.cli_to_array(args.query)} if args.meter is None: raise exc.CommandError('Meter name not provided (-m )') try: @@ -64,3 +66,28 @@ def do_sample_list(cc, args): 'counter_volume', 'counter_unit', 'timestamp'] utils.print_list(samples, fields, field_labels, sortby=0) + + +@utils.arg('-q', '--query', metavar='', + help='key[op]value; list.') +def do_meter_list(cc, args={}): + '''List the user's meter''' + meters = cc.meters.list(q=options.cli_to_array(args.query)) + field_labels = ['Name', 'Type', 'Unit', 'Resource ID', 'User ID', + 'Project ID'] + fields = ['name', 'type', 'unit', 'resource_id', 'user_id', + 'project_id'] + utils.print_list(meters, fields, field_labels, + sortby=0) + + +@utils.arg('-q', '--query', metavar='', + help='key[op]value; list.') +def do_resource_list(cc, args={}): + '''List the resources''' + resources = cc.resources.list(q=options.cli_to_array(args.query)) + + field_labels = ['Resource ID', 'Source', 'User ID', 'Project ID'] + fields = ['resource_id', 'source', 'user_id', 'project_id'] + utils.print_list(resources, fields, field_labels, + sortby=1) diff --git a/ceilometerclient/v2/statistics.py b/ceilometerclient/v2/statistics.py index 9cf41982..f6639423 100644 --- a/ceilometerclient/v2/statistics.py +++ b/ceilometerclient/v2/statistics.py @@ -1,5 +1,3 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -13,8 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. -from ceilometerclient.v2 import samples from ceilometerclient.common import base +from ceilometerclient.v2 import options class Statistics(base.Resource): @@ -22,10 +20,10 @@ class Statistics(base.Resource): return "" % self._info -class StatisticsManager(samples.SampleManager): +class StatisticsManager(base.Manager): resource_class = Statistics def list(self, meter_name, q=None): - return self._list(self.build_url( + return self._list(options.build_url( '/v2/meters/' + meter_name + '/statistics', q)) diff --git a/tests/v2/test_options.py b/tests/v2/test_options.py new file mode 100644 index 00000000..33b1a2ba --- /dev/null +++ b/tests/v2/test_options.py @@ -0,0 +1,68 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +from ceilometerclient.v2 import options + + +class BuildUrlTest(unittest.TestCase): + + def test_one(self): + url = options.build_url('/', [{'field': 'this', + 'op': 'gt', + 'value': 43}]) + self.assertEqual(url, '/?q.op=gt&q.value=43&q.field=this') + + def test_two(self): + url = options.build_url('/', [{'field': 'this', + 'op': 'gt', + 'value': 43}, + {'field': 'that', + 'op': 'lt', + 'value': 88}]) + ops = 'q.op=gt&q.op=lt' + vals = 'q.value=43&q.value=88' + fields = 'q.field=this&q.field=that' + self.assertEqual(url, '/?%s&%s&%s' % (ops, vals, fields)) + + def test_default_op(self): + url = options.build_url('/', [{'field': 'this', + 'value': 43}]) + self.assertEqual(url, '/?q.op=&q.value=43&q.field=this') + + +class CliTest(unittest.TestCase): + + def test_one(self): + ar = options.cli_to_array('this<=34') + self.assertEqual(ar, [{'field': 'this','op': 'le','value': '34'}]) + + def test_two(self): + ar = options.cli_to_array('this<=34;that!=foo') + self.assertEqual(ar, [{'field': 'this','op': 'le','value': '34'}, + {'field': 'that','op': 'ne','value': 'foo'}]) + + def test_negative(self): + ar = options.cli_to_array('this>=-783') + self.assertEqual(ar, [{'field': 'this','op': 'ge','value': '-783'}]) + + def test_float(self): + ar = options.cli_to_array('this<=283.347') + self.assertEqual(ar, [{'field': 'this','op': 'le','value': '283.347'}]) + + def test_invalid_seperator(self): + self.assertRaises(ValueError, options.cli_to_array, 'this=2.4,fooo=doof') + + def test_invalid_operator(self): + self.assertRaises(ValueError, options.cli_to_array, 'this=2.4;fooo-doof')