Merge "Add resources and meters to the v2 shell"

This commit is contained in:
Jenkins
2013-03-04 12:58:16 +00:00
committed by Gerrit Code Review
8 changed files with 252 additions and 28 deletions

View File

@@ -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)

View File

@@ -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 "<Meter %s>" % 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))

View File

@@ -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)
if len(fragments) == 0:
#single char 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

View File

@@ -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 "<Resource %s>" % 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))

View File

@@ -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))

View File

@@ -1,5 +1,6 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2013 Red Hat
# Copyright © 2013 Red Hat, Inc
#
# Author: Angus Salkeld <asalkeld@redhat.com>
#
@@ -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 <meter name>)')
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 <meter name>)')
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='<QUERY>',
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='<QUERY>',
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)

View File

@@ -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 "<Statistics %s>" % 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))

68
tests/v2/test_options.py Normal file
View File

@@ -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')