Implement /meters to make discovery "nicer" from the client
The point of this api is to make discovery (esp. from a casual user) easier. So you don't really want to dump all the raw samples out just to see what is there. So instead "ceilometer meter-list" will GET /v1/meters (or /{proj|user|source}/{id}/meters) and this will just return a description (name, type, resource, user, etc) of the available meters, not each sample point. After this you will probably go and look at the samples that you are actually interested in. It is a kind of dynamic version of doc/source/measurements.rst Change-Id: I58f2757874ab151632b6d87043d6327104c5b65c
This commit is contained in:
parent
36eccfb76a
commit
b67d2c2dfb
@ -40,10 +40,11 @@
|
||||
#
|
||||
# [ ] /resources/<resource> -- metadata
|
||||
#
|
||||
# [ ] /projects/<project>/meters -- list of meters reporting for parent obj
|
||||
# [ ] /resources/<resource>/meters -- list of meters reporting for parent obj
|
||||
# [ ] /sources/<source>/meters -- list of meters reporting for parent obj
|
||||
# [ ] /users/<user>/meters -- list of meters reporting for parent obj
|
||||
# [x] /meters -- list of meters
|
||||
# [x] /projects/<project>/meters -- list of meters reporting for parent obj
|
||||
# [x] /resources/<resource>/meters -- list of meters reporting for parent obj
|
||||
# [x] /sources/<source>/meters -- list of meters reporting for parent obj
|
||||
# [x] /users/<user>/meters -- list of meters reporting for parent obj
|
||||
#
|
||||
# [x] /projects/<project>/meters/<meter> -- events
|
||||
# [x] /resources/<resource>/meters/<meter> -- events
|
||||
@ -99,6 +100,57 @@ def request_wants_html():
|
||||
flask.request.accept_mimetypes['application/json']
|
||||
|
||||
|
||||
## APIs for working with meters.
|
||||
|
||||
|
||||
@blueprint.route('/meters')
|
||||
def list_meters_all():
|
||||
"""Return a list of meters.
|
||||
"""
|
||||
meters = flask.request.storage_conn.get_meters()
|
||||
return flask.jsonify(meters=list(meters))
|
||||
|
||||
|
||||
@blueprint.route('/resources/<resource>/meters')
|
||||
def list_meters_by_resource(resource):
|
||||
"""Return a list of meters by resource.
|
||||
|
||||
:param resource: The ID of the resource.
|
||||
"""
|
||||
meters = flask.request.storage_conn.get_meters(resource=resource)
|
||||
return flask.jsonify(meters=list(meters))
|
||||
|
||||
|
||||
@blueprint.route('/users/<user>/meters')
|
||||
def list_meters_by_user(user):
|
||||
"""Return a list of meters by user.
|
||||
|
||||
:param user: The ID of the owning user.
|
||||
"""
|
||||
meters = flask.request.storage_conn.get_meters(user=user)
|
||||
return flask.jsonify(meters=list(meters))
|
||||
|
||||
|
||||
@blueprint.route('/projects/<project>/meters')
|
||||
def list_meters_by_project(project):
|
||||
"""Return a list of meters by project.
|
||||
|
||||
:param project: The ID of the owning project.
|
||||
"""
|
||||
meters = flask.request.storage_conn.get_meters(project=project)
|
||||
return flask.jsonify(meters=list(meters))
|
||||
|
||||
|
||||
@blueprint.route('/sources/<source>/meters')
|
||||
def list_meters_by_source(source):
|
||||
"""Return a list of meters by source.
|
||||
|
||||
:param source: The ID of the owning source.
|
||||
"""
|
||||
meters = flask.request.storage_conn.get_meters(source=source)
|
||||
return flask.jsonify(meters=list(meters))
|
||||
|
||||
|
||||
## APIs for working with resources.
|
||||
|
||||
def _list_resources(source=None, user=None, project=None):
|
||||
|
@ -133,3 +133,14 @@ class Connection(object):
|
||||
|
||||
( datetime.datetime(), datetime.datetime() )
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_meters(self, user=None, project=None, resource=None, source=None):
|
||||
"""Return a list of meters.
|
||||
{'resource_id': UUID string for the resource,
|
||||
'project_id': UUID of project owning the resource,
|
||||
'user_id': UUID of user owning the resource,
|
||||
'name': The name of the meter,
|
||||
'type': The meter type (gauge, counter, diff),
|
||||
}
|
||||
"""
|
||||
|
@ -80,6 +80,22 @@ class Connection(base.Connection):
|
||||
:param end_timestamp: Optional modified timestamp end range.
|
||||
"""
|
||||
|
||||
def get_meters(self, user=None, project=None, resource=None, source=None):
|
||||
"""Return an iterable of dictionaries containing meter information.
|
||||
|
||||
{ 'name': name of the meter,
|
||||
'type': type of the meter (guage, counter),
|
||||
'resource_id': UUID of the resource,
|
||||
'project_id': UUID of project owning the resource,
|
||||
'user_id': UUID of user owning the resource,
|
||||
}
|
||||
|
||||
:param user: Optional ID for user that owns the resource.
|
||||
:param project: Optional ID for project that owns the resource.
|
||||
:param resource: Optional ID of the resource.
|
||||
:param source: Optional source filter.
|
||||
"""
|
||||
|
||||
def get_raw_events(self, event_filter):
|
||||
"""Return an iterable of raw event data as created by
|
||||
:func:`ceilometer.meter.meter_message_from_counter`.
|
||||
|
@ -22,7 +22,6 @@ import copy
|
||||
import datetime
|
||||
|
||||
from ceilometer.openstack.common import log
|
||||
from ceilometer.openstack.common import cfg
|
||||
from ceilometer.storage import base
|
||||
|
||||
import bson.code
|
||||
@ -348,6 +347,40 @@ class Connection(base.Connection):
|
||||
del r['_id']
|
||||
yield r
|
||||
|
||||
def get_meters(self, user=None, project=None, resource=None, source=None):
|
||||
"""Return an iterable of dictionaries containing meter information.
|
||||
|
||||
{ 'name': name of the meter,
|
||||
'type': type of the meter (guage, counter),
|
||||
'resource_id': UUID of the resource,
|
||||
'project_id': UUID of project owning the resource,
|
||||
'user_id': UUID of user owning the resource,
|
||||
}
|
||||
|
||||
:param user: Optional ID for user that owns the resource.
|
||||
:param project: Optional ID for project that owns the resource.
|
||||
:param resource: Optional ID of the resource.
|
||||
:param source: Optional source filter.
|
||||
"""
|
||||
q = {}
|
||||
if user is not None:
|
||||
q['user_id'] = user
|
||||
if project is not None:
|
||||
q['project_id'] = project
|
||||
if resource is not None:
|
||||
q['_id'] = resource
|
||||
if source is not None:
|
||||
q['source'] = source
|
||||
for r in self.db.resource.find(q):
|
||||
for r_meter in r['meter']:
|
||||
m = {}
|
||||
m['name'] = r_meter['counter_name']
|
||||
m['type'] = r_meter['counter_type']
|
||||
m['resource_id'] = r['_id']
|
||||
m['project_id'] = r['project_id']
|
||||
m['user_id'] = r['user_id']
|
||||
yield m
|
||||
|
||||
def get_raw_events(self, event_filter):
|
||||
"""Return an iterable of raw event data as created by
|
||||
:func:`ceilometer.meter.meter_message_from_counter`.
|
||||
|
@ -19,7 +19,7 @@
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
from ceilometer.openstack.common import cfg, log, timeutils
|
||||
from ceilometer.openstack.common import log
|
||||
from ceilometer.storage import base
|
||||
from ceilometer.storage.sqlalchemy.models import Meter, Project, Resource
|
||||
from ceilometer.storage.sqlalchemy.models import Source, User
|
||||
@ -257,6 +257,48 @@ class Connection(base.Connection):
|
||||
del r['meters']
|
||||
yield r
|
||||
|
||||
def get_meters(self, user=None, project=None, source=None,
|
||||
resource=None):
|
||||
"""Return an iterable of dictionaries containing meter information.
|
||||
|
||||
{ 'name': name of the meter,
|
||||
'type': type of the meter (guage, counter),
|
||||
'resource_id': UUID of the resource,
|
||||
'project_id': UUID of project owning the resource,
|
||||
'user_id': UUID of user owning the resource,
|
||||
}
|
||||
|
||||
:param user: Optional ID for user that owns the resource.
|
||||
:param project: Optional ID for project that owns the resource.
|
||||
:param resource: Optional ID of the resource.
|
||||
:param source: Optional source filter.
|
||||
"""
|
||||
query = model_query(Resource, session=self.session)
|
||||
if user is not None:
|
||||
query = query.filter(Resource.user_id == user)
|
||||
if source is not None:
|
||||
query = query.filter(Resource.sources.any(id=source))
|
||||
if resource:
|
||||
query = query.filter(Resource.id == resource)
|
||||
if project is not None:
|
||||
query = query.filter(Resource.project_id == project)
|
||||
query = query.options(
|
||||
sqlalchemy_session.sqlalchemy.orm.joinedload('meters'))
|
||||
|
||||
for resource in query.all():
|
||||
meter_names = set()
|
||||
for meter in resource.meters:
|
||||
if meter.counter_name in meter_names:
|
||||
continue
|
||||
meter_names.add(meter.counter_name)
|
||||
m = {}
|
||||
m['resource_id'] = resource.id
|
||||
m['project_id'] = resource.project_id
|
||||
m['user_id'] = resource.user_id
|
||||
m['name'] = meter.counter_name
|
||||
m['type'] = meter.counter_type
|
||||
yield m
|
||||
|
||||
def get_raw_events(self, event_filter):
|
||||
"""Return an iterable of raw event data as created by
|
||||
:func:`ceilometer.meter.meter_message_from_counter`.
|
||||
|
@ -117,6 +117,18 @@ storage.objects.size Gauge bytes store ID Total size of stor
|
||||
storage.objects.containers Gauge containers store ID Number of containers
|
||||
========================== ========== ========== ======== ==================================================
|
||||
|
||||
Dynamically retrieving the Meters via ceilometer client
|
||||
=======================================================
|
||||
ceilometer meter-list -s openstack
|
||||
+------------+-------+--------------------------------------+---------+----------------------------------+
|
||||
| Name | Type | Resource ID | User ID | Project ID |
|
||||
+------------+-------+--------------------------------------+---------+----------------------------------+
|
||||
| image | gauge | 09e84d97-8712-4dd2-bcce-45970b2430f7 | | 57cf6d93688e4d39bf2fe3d3c03eb326 |
|
||||
|
||||
The above command will retrieve the available meters that can be queried on
|
||||
given the actual resource instances available.
|
||||
|
||||
|
||||
Naming convention
|
||||
=================
|
||||
If you plan on adding meters, please follow the convention bellow:
|
||||
|
155
tests/api/v1/test_list_meters.py
Normal file
155
tests/api/v1/test_list_meters.py
Normal file
@ -0,0 +1,155 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# Author: Angus Salkeld <asalkeld@redhat.com>
|
||||
#
|
||||
# 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.
|
||||
"""Test listing meters.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from ceilometer import counter
|
||||
from ceilometer import meter
|
||||
from ceilometer.openstack.common import cfg
|
||||
|
||||
from ceilometer.tests import api as tests_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestListEmptyMeters(tests_api.TestBase):
|
||||
|
||||
def test_empty(self):
|
||||
data = self.get('/meters')
|
||||
self.assertEquals({'meters': []}, data)
|
||||
|
||||
|
||||
class TestListMeters(tests_api.TestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestListMeters, self).setUp()
|
||||
|
||||
for cnt in [
|
||||
counter.Counter(
|
||||
'meter.test',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 40),
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter',
|
||||
}),
|
||||
counter.Counter(
|
||||
'meter.test',
|
||||
'cumulative',
|
||||
3,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 11, 40),
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter',
|
||||
}),
|
||||
counter.Counter(
|
||||
'meter.mine',
|
||||
'gauge',
|
||||
1,
|
||||
'user-id',
|
||||
'project-id',
|
||||
'resource-id2',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 41),
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter2',
|
||||
}),
|
||||
counter.Counter(
|
||||
'meter.test',
|
||||
'cumulative',
|
||||
1,
|
||||
'user-id2',
|
||||
'project-id2',
|
||||
'resource-id3',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 42),
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter3',
|
||||
}),
|
||||
counter.Counter(
|
||||
'meter.mine',
|
||||
'gauge',
|
||||
1,
|
||||
'user-id4',
|
||||
'project-id2',
|
||||
'resource-id4',
|
||||
timestamp=datetime.datetime(2012, 7, 2, 10, 43),
|
||||
resource_metadata={'display_name': 'test-server',
|
||||
'tag': 'self.counter4',
|
||||
})]:
|
||||
msg = meter.meter_message_from_counter(cnt,
|
||||
cfg.CONF.metering_secret,
|
||||
'test_list_resources')
|
||||
self.conn.record_metering_data(msg)
|
||||
|
||||
def test_list_meters(self):
|
||||
data = self.get('/meters')
|
||||
self.assertEquals(4, len(data['meters']))
|
||||
self.assertEquals(set(r['resource_id'] for r in data['meters']),
|
||||
set(['resource-id',
|
||||
'resource-id2',
|
||||
'resource-id3',
|
||||
'resource-id4']))
|
||||
self.assertEquals(set(r['name'] for r in data['meters']),
|
||||
set(['meter.test',
|
||||
'meter.mine']))
|
||||
|
||||
def test_with_resource(self):
|
||||
data = self.get('/resources/resource-id/meters')
|
||||
ids = set(r['name'] for r in data['meters'])
|
||||
self.assertEquals(set(['meter.test']), ids)
|
||||
|
||||
def test_with_source(self):
|
||||
data = self.get('/sources/test_list_resources/meters')
|
||||
ids = set(r['resource_id'] for r in data['meters'])
|
||||
self.assertEquals(set(['resource-id',
|
||||
'resource-id2',
|
||||
'resource-id3',
|
||||
'resource-id4']), ids)
|
||||
|
||||
def test_with_source_non_existent(self):
|
||||
data = self.get('/sources/test_list_resources_dont_exist/meters')
|
||||
self.assertEquals(data['meters'], [])
|
||||
|
||||
def test_with_user(self):
|
||||
data = self.get('/users/user-id/meters')
|
||||
|
||||
nids = set(r['name'] for r in data['meters'])
|
||||
self.assertEquals(set(['meter.mine', 'meter.test']), nids)
|
||||
|
||||
rids = set(r['resource_id'] for r in data['meters'])
|
||||
self.assertEquals(set(['resource-id', 'resource-id2']), rids)
|
||||
|
||||
def test_with_user_non_existent(self):
|
||||
data = self.get('/users/user-id-foobar123/meters')
|
||||
self.assertEquals(data['meters'], [])
|
||||
|
||||
def test_with_project(self):
|
||||
data = self.get('/projects/project-id2/meters')
|
||||
ids = set(r['resource_id'] for r in data['meters'])
|
||||
self.assertEquals(set(['resource-id3', 'resource-id4']), ids)
|
||||
|
||||
def test_with_project_non_existent(self):
|
||||
data = self.get('/projects/jd-was-here/meters')
|
||||
self.assertEquals(data['meters'], [])
|
@ -290,6 +290,18 @@ class MeterTest(MongoDBEngineTestBase):
|
||||
meter = self.db.meter.find_one()
|
||||
assert meter is not None
|
||||
|
||||
def test_get_meters(self):
|
||||
results = list(self.conn.get_meters())
|
||||
assert len(results) == 4
|
||||
|
||||
def test_get_meters_by_user(self):
|
||||
results = list(self.conn.get_meters(user='user-id'))
|
||||
assert len(results) == 1
|
||||
|
||||
def test_get_meters_by_project(self):
|
||||
results = list(self.conn.get_meters(project='project-id'))
|
||||
assert len(results) == 2
|
||||
|
||||
def test_get_raw_events_by_user(self):
|
||||
f = storage.EventFilter(user='user-id')
|
||||
results = list(self.conn.get_raw_events(f))
|
||||
|
@ -326,6 +326,20 @@ class MeterTest(SQLAlchemyEngineTestBase):
|
||||
meter = self.session.query(Meter).first()
|
||||
assert meter is not None
|
||||
|
||||
def test_get_meters(self):
|
||||
results = list(self.conn.get_meters())
|
||||
assert len(results) == 4
|
||||
|
||||
def test_get_meters_by_user(self):
|
||||
results = list(self.conn.get_meters(user='user-id'))
|
||||
assert len(results) == 1
|
||||
|
||||
def test_get_meters_by_project(self):
|
||||
results = list(self.conn.get_meters(project='project-id'))
|
||||
for r in results:
|
||||
print r
|
||||
assert len(results) == 2
|
||||
|
||||
def test_get_raw_events_by_user(self):
|
||||
f = storage.EventFilter(user='user-id')
|
||||
results = list(self.conn.get_raw_events(f))
|
||||
|
Loading…
x
Reference in New Issue
Block a user