Implement user-api

This implements the blueprint user-api for version 1 of the API.

The ACL checks now don't return access denied on non-admin users.

All requests without a <project> in the URL limit their scope to the tenant
contained into X-Tenant-Id.

All request with a <project> in the URL returns 404 if the <project>
specified is different than the one from X-Tenant-Id.

Change-Id: If1ec55fa491ea5de30036ce7ed75d0f28e925457
Signed-off-by: Julien Danjou <julien@danjou.info>
This commit is contained in:
Julien Danjou 2012-12-27 18:51:48 +01:00
parent ade5b235b0
commit 0ba1ec0da9
14 changed files with 448 additions and 196 deletions

View File

@ -17,7 +17,6 @@
# under the License.
"""Set up the ACL to acces the API server."""
import flask
from ceilometer import policy
import keystoneclient.middleware.auth_token as auth_token
@ -37,14 +36,12 @@ def install(app, conf):
app.wsgi_app = auth_token.AuthProtocol(app.wsgi_app,
conf=conf,
)
app.before_request(check)
return app
def check():
"""Check application access."""
headers = flask.request.headers
def get_limited_to_project(headers):
"""Return the tenant the request should be limited to."""
if not policy.check_is_admin(headers.get('X-Roles', "").split(","),
headers.get('X-Tenant-Id'),
headers.get('X-Tenant-Name')):
return "Access denied", 401
return headers.get('X-Tenant-Id')

View File

@ -85,6 +85,8 @@ from ceilometer.openstack.common import timeutils
from ceilometer import storage
from ceilometer.api.v1 import acl
LOG = log.getLogger(__name__)
@ -105,6 +107,13 @@ def _get_metaquery(args):
for (k, v) in args.iteritems()
if k.startswith('metadata.'))
def check_authorized_project(project):
authorized_project = acl.get_limited_to_project(flask.request.headers)
if authorized_project and authorized_project != project:
flask.abort(404)
## APIs for working with meters.
@ -114,7 +123,9 @@ def list_meters_all():
:param metadata.<key> match on the metadata within the resource. (optional)
"""
rq = flask.request
meters = rq.storage_conn.get_meters(metaquery=_get_metaquery(rq.args))
meters = rq.storage_conn.get_meters(
project=acl.get_limited_to_project(rq.headers),
metaquery=_get_metaquery(rq.args))
return flask.jsonify(meters=list(meters))
@ -126,8 +137,10 @@ def list_meters_by_resource(resource):
:param metadata.<key> match on the metadata within the resource. (optional)
"""
rq = flask.request
meters = rq.storage_conn.get_meters(resource=resource,
metaquery=_get_metaquery(rq.args))
meters = rq.storage_conn.get_meters(
resource=resource,
project=acl.get_limited_to_project(rq.headers),
metaquery=_get_metaquery(rq.args))
return flask.jsonify(meters=list(meters))
@ -139,8 +152,10 @@ def list_meters_by_user(user):
:param metadata.<key> match on the metadata within the resource. (optional)
"""
rq = flask.request
meters = rq.storage_conn.get_meters(user=user,
metaquery=_get_metaquery(rq.args))
meters = rq.storage_conn.get_meters(
user=user,
project=acl.get_limited_to_project(rq.headers),
metaquery=_get_metaquery(rq.args))
return flask.jsonify(meters=list(meters))
@ -151,9 +166,12 @@ def list_meters_by_project(project):
:param project: The ID of the owning project.
:param metadata.<key> match on the metadata within the resource. (optional)
"""
check_authorized_project(project)
rq = flask.request
meters = rq.storage_conn.get_meters(project=project,
metaquery=_get_metaquery(rq.args))
meters = rq.storage_conn.get_meters(
project=project,
metaquery=_get_metaquery(rq.args))
return flask.jsonify(meters=list(meters))
@ -165,8 +183,10 @@ def list_meters_by_source(source):
:param metadata.<key> match on the metadata within the resource. (optional)
"""
rq = flask.request
meters = rq.storage_conn.get_meters(source=source,
metaquery=_get_metaquery(rq.args))
meters = rq.storage_conn.get_meters(
source=source,
project=acl.get_limited_to_project(rq.headers),
metaquery=_get_metaquery(rq.args))
return flask.jsonify(meters=list(meters))
@ -201,6 +221,7 @@ def list_resources_by_project(project):
:type end_timestamp: ISO date in UTC
:param metadata.<key> match on the metadata within the resource. (optional)
"""
check_authorized_project(project)
return _list_resources(project=project)
@ -216,7 +237,8 @@ def list_all_resources():
:type end_timestamp: ISO date in UTC
:param metadata.<key> match on the metadata within the resource. (optional)
"""
return _list_resources()
return _list_resources(
project=acl.get_limited_to_project(flask.request.headers))
@blueprint.route('/sources/<source>')
@ -242,7 +264,10 @@ def list_resources_by_source(source):
:type end_timestamp: ISO date in UTC
:param metadata.<key> match on the metadata within the resource. (optional)
"""
return _list_resources(source=source)
return _list_resources(
source=source,
project=acl.get_limited_to_project(flask.request.headers),
)
@blueprint.route('/users/<user>/resources')
@ -258,7 +283,10 @@ def list_resources_by_user(user):
:type end_timestamp: ISO date in UTC
:param metadata.<key> match on the metadata within the resource. (optional)
"""
return _list_resources(user=user)
return _list_resources(
user=user,
project=acl.get_limited_to_project(flask.request.headers),
)
## APIs for working with users.
@ -267,7 +295,13 @@ def list_resources_by_user(user):
def _list_users(source=None):
"""Return a list of user names.
"""
users = flask.request.storage_conn.get_users(source=source)
# TODO(jd) it might be better to return the real list of users that are
# belonging to the project, but that's not provided by the storage
# drivers for now
if acl.get_limited_to_project(flask.request.headers):
users = [flask.request.headers.get('X-User-id')]
else:
users = flask.request.storage_conn.get_users(source=source)
return flask.jsonify(users=list(users))
@ -294,7 +328,18 @@ def list_users_by_source(source):
def _list_projects(source=None):
"""Return a list of project names.
"""
projects = flask.request.storage_conn.get_projects(source=source)
project = acl.get_limited_to_project(flask.request.headers)
if project:
if source:
if project in flask.request.storage_conn.get_projects(
source=source):
projects = [project]
else:
projects = []
else:
projects = [project]
else:
projects = flask.request.storage_conn.get_projects(source=source)
return flask.jsonify(projects=list(projects))
@ -334,7 +379,7 @@ def _list_events(meter,
start=q_ts['start_timestamp'],
end=q_ts['end_timestamp'],
metaquery=_get_metaquery(flask.request.args),
)
)
events = list(flask.request.storage_conn.get_raw_events(f))
jsonified = flask.jsonify(events=events)
if request_wants_html():
@ -361,6 +406,7 @@ def list_events_by_project(project, meter):
(optional)
:type end_timestamp: ISO date in UTC
"""
check_authorized_project(project)
return _list_events(project=project,
meter=meter,
)
@ -379,9 +425,11 @@ def list_events_by_resource(resource, meter):
(optional)
:type end_timestamp: ISO date in UTC
"""
return _list_events(resource=resource,
meter=meter,
)
return _list_events(
resource=resource,
meter=meter,
project=acl.get_limited_to_project(flask.request.headers),
)
@blueprint.route('/sources/<source>/meters/<meter>')
@ -397,9 +445,11 @@ def list_events_by_source(source, meter):
(optional)
:type end_timestamp: ISO date in UTC
"""
return _list_events(source=source,
meter=meter,
)
return _list_events(
source=source,
meter=meter,
project=acl.get_limited_to_project(flask.request.headers),
)
@blueprint.route('/users/<user>/meters/<meter>')
@ -415,9 +465,11 @@ def list_events_by_user(user, meter):
(optional)
:type end_timestamp: ISO date in UTC
"""
return _list_events(user=user,
meter=meter,
)
return _list_events(
user=user,
meter=meter,
project=acl.get_limited_to_project(flask.request.headers),
)
## APIs for working with meter calculations.
@ -475,11 +527,13 @@ def compute_duration_by_resource(resource, meter):
# Query the database for the interval of timestamps
# within the desired range.
f = storage.EventFilter(meter=meter,
resource=resource,
start=q_ts['query_start'],
end=q_ts['query_end'],
)
f = storage.EventFilter(
meter=meter,
project=acl.get_limited_to_project(flask.request.headers),
resource=resource,
start=q_ts['query_start'],
end=q_ts['query_end'],
)
min_ts, max_ts = flask.request.storage_conn.get_event_interval(f)
# "Clamp" the timestamps we return to the original time
@ -533,11 +587,13 @@ def compute_max_resource_volume(resource, meter):
q_ts = _get_query_timestamps(flask.request.args)
# Query the database for the max volume
f = storage.EventFilter(meter=meter,
resource=resource,
start=q_ts['query_start'],
end=q_ts['query_end'],
)
f = storage.EventFilter(
meter=meter,
project=acl.get_limited_to_project(flask.request.headers),
resource=resource,
start=q_ts['query_start'],
end=q_ts['query_end'],
)
# TODO(sberler): do we want to return an error if the resource
# does not exist?
results = list(flask.request.storage_conn.get_volume_max(f))
@ -564,11 +620,13 @@ def compute_resource_volume_sum(resource, meter):
q_ts = _get_query_timestamps(flask.request.args)
# Query the database for the max volume
f = storage.EventFilter(meter=meter,
resource=resource,
start=q_ts['query_start'],
end=q_ts['query_end'],
)
f = storage.EventFilter(
meter=meter,
project=acl.get_limited_to_project(flask.request.headers),
resource=resource,
start=q_ts['query_start'],
end=q_ts['query_end'],
)
# TODO(sberler): do we want to return an error if the resource
# does not exist?
results = list(flask.request.storage_conn.get_volume_sum(f))
@ -592,6 +650,8 @@ def compute_project_volume_max(project, meter):
:param search_offset: Number of minutes before and
after start and end timestamps to query.
"""
check_authorized_project(project)
q_ts = _get_query_timestamps(flask.request.args)
f = storage.EventFilter(meter=meter,
@ -624,6 +684,8 @@ def compute_project_volume_sum(project, meter):
:param search_offset: Number of minutes before and
after start and end timestamps to query.
"""
check_authorized_project(project)
q_ts = _get_query_timestamps(flask.request.args)
f = storage.EventFilter(meter=meter,

View File

@ -97,7 +97,7 @@ def make_query_from_filter(event_filter, require_meter=True):
if event_filter.user:
q['user_id'] = event_filter.user
elif event_filter.project:
if event_filter.project:
q['project_id'] = event_filter.project
if event_filter.meter:

View File

@ -52,18 +52,20 @@ class TestBase(db_test_base.TestBase):
def attach_storage_connection():
flask.request.storage_conn = self.conn
def get(self, path, **kwds):
def get(self, path, headers=None, **kwds):
if kwds:
query = path + '?' + urllib.urlencode(kwds)
else:
query = path
rv = self.test_app.get(query)
try:
data = json.loads(rv.data)
except ValueError:
print 'RAW DATA:', rv
raise
return data
rv = self.test_app.get(query, headers=headers)
if rv.status_code == 200 and rv.content_type == 'application/json':
try:
data = json.loads(rv.data)
except ValueError:
print 'RAW DATA:', rv
raise
return data
return rv
class FunctionalTest(unittest.TestCase):

View File

@ -1,63 +0,0 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Julien Danjou <julien@danjou.info>
#
# 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 ACL."""
from ceilometer.tests import api as tests_api
from ceilometer.api.v1 import acl
class TestAPIACL(tests_api.TestBase):
def setUp(self):
super(TestAPIACL, self).setUp()
acl.install(self.app, {})
def test_non_authenticated(self):
with self.app.test_request_context('/'):
self.app.preprocess_request()
self.assertEqual(self.test_app.get().status_code, 401)
def test_authenticated_wrong_role(self):
with self.app.test_request_context('/', headers={
"X-Roles": "Member",
"X-Tenant-Name": "foobar",
"X-Tenant-Id": "bc23a9d531064583ace8f67dad60f6bb",
}):
self.app.preprocess_request()
self.assertEqual(self.test_app.get().status_code, 401)
# FIXME(dhellmann): This test is not properly looking at the tenant
# info. The status code returned is the expected value, but it
# is not clear why.
#
# def test_authenticated_wrong_tenant(self):
# with self.app.test_request_context('/', headers={
# "X-Roles": "admin",
# "X-Tenant-Name": "foobar",
# "X-Tenant-Id": "bc23a9d531064583ace8f67dad60f6bb",
# }):
# self.app.preprocess_request()
# self.assertEqual(self.test_app.get().status_code, 401)
def test_authenticated(self):
with self.app.test_request_context('/', headers={
"X-Roles": "admin",
"X-Tenant-Name": "admin",
"X-Tenant-Id": "bc23a9d531064583ace8f67dad60f6bb",
}):
self.assertEqual(self.app.preprocess_request(), None)

View File

@ -3,6 +3,7 @@
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -85,6 +86,18 @@ class TestListEvents(tests_api.TestBase):
data = self.get('/projects/project1/meters/instance')
self.assertEquals(2, len(data['events']))
def test_by_project_non_admin(self):
data = self.get('/projects/project1/meters/instance',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEquals(2, len(data['events']))
def test_by_project_wrong_tenant(self):
resp = self.get('/projects/project1/meters/instance',
headers={"X-Roles": "Member",
"X-Tenant-Id": "this-is-my-project"})
self.assertEquals(404, resp.status_code)
def test_by_project_with_timestamps(self):
data = self.get('/projects/project1/meters/instance',
start_timestamp=datetime.datetime(2012, 7, 2, 10, 42))
@ -98,6 +111,18 @@ class TestListEvents(tests_api.TestBase):
data = self.get('/resources/resource-id/meters/instance')
self.assertEquals(2, len(data['events']))
def test_by_resource_non_admin(self):
data = self.get('/resources/resource-id-alternate/meters/instance',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project2"})
self.assertEquals(1, len(data['events']))
def test_by_resource_some_tenant(self):
data = self.get('/resources/resource-id/meters/instance',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project2"})
self.assertEquals(0, len(data['events']))
def test_empty_source(self):
data = self.get('/sources/no-such-source/meters/instance')
self.assertEquals({'events': []}, data)
@ -106,6 +131,12 @@ class TestListEvents(tests_api.TestBase):
data = self.get('/sources/source1/meters/instance')
self.assertEquals(3, len(data['events']))
def test_by_source_non_admin(self):
data = self.get('/sources/source1/meters/instance',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project2"})
self.assertEquals(1, len(data['events']))
def test_by_source_with_timestamps(self):
data = self.get('/sources/source1/meters/instance',
end_timestamp=datetime.datetime(2012, 7, 2, 10, 42))
@ -119,6 +150,18 @@ class TestListEvents(tests_api.TestBase):
data = self.get('/users/user-id/meters/instance')
self.assertEquals(2, len(data['events']))
def test_by_user_non_admin(self):
data = self.get('/users/user-id/meters/instance',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEquals(2, len(data['events']))
def test_by_user_wrong_tenant(self):
data = self.get('/users/user-id/meters/instance',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project2"})
self.assertEquals(0, len(data['events']))
def test_by_user_with_timestamps(self):
data = self.get('/users/user-id/meters/instance',
start_timestamp=datetime.datetime(2012, 7, 2, 10, 41),
@ -130,12 +173,33 @@ class TestListEvents(tests_api.TestBase):
data = self.get('%s?metadata.tag=self.counter2' % q)
self.assertEquals(1, len(data['events']))
def test_metaquery1_wrong_tenant(self):
q = '/sources/source1/meters/instance'
data = self.get('%s?metadata.tag=self.counter2' % q,
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEquals(0, len(data['events']))
def test_metaquery2(self):
q = '/sources/source1/meters/instance'
data = self.get('%s?metadata.tag=self.counter' % q)
self.assertEquals(2, len(data['events']))
def test_metaquery2_non_admin(self):
q = '/sources/source1/meters/instance'
data = self.get('%s?metadata.tag=self.counter' % q,
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEquals(2, len(data['events']))
def test_metaquery3(self):
q = '/sources/source1/meters/instance'
data = self.get('%s?metadata.display_name=test-server' % q)
self.assertEquals(3, len(data['events']))
def test_metaquery3_with_project(self):
q = '/sources/source1/meters/instance'
data = self.get('%s?metadata.display_name=test-server' % q,
headers={"X-Roles": "Member",
"X-Tenant-Id": "project2"})
self.assertEquals(1, len(data['events']))

View File

@ -3,6 +3,7 @@
# Copyright 2012 Red Hat, Inc.
#
# Author: Angus Salkeld <asalkeld@redhat.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -115,6 +116,18 @@ class TestListMeters(tests_api.TestBase):
set(['meter.test',
'meter.mine']))
def test_list_meters_non_admin(self):
data = self.get('/meters',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEquals(2, len(data['meters']))
self.assertEquals(set(r['resource_id'] for r in data['meters']),
set(['resource-id',
'resource-id2']))
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'])
@ -128,6 +141,14 @@ class TestListMeters(tests_api.TestBase):
'resource-id3',
'resource-id4']), ids)
def test_with_source_non_admin(self):
data = self.get('/sources/test_list_resources/meters',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id2"})
ids = set(r['resource_id'] for r in data['meters'])
self.assertEquals(set(['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'], [])
@ -141,6 +162,23 @@ class TestListMeters(tests_api.TestBase):
rids = set(r['resource_id'] for r in data['meters'])
self.assertEquals(set(['resource-id', 'resource-id2']), rids)
def test_with_user_non_admin(self):
data = self.get('/users/user-id/meters',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
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_wrong_tenant(self):
data = self.get('/users/user-id/meters',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project666"})
self.assertEquals(data['meters'], [])
def test_with_user_non_existent(self):
data = self.get('/users/user-id-foobar123/meters')
self.assertEquals(data['meters'], [])
@ -150,6 +188,19 @@ class TestListMeters(tests_api.TestBase):
ids = set(r['resource_id'] for r in data['meters'])
self.assertEquals(set(['resource-id3', 'resource-id4']), ids)
def test_with_project_non_admin(self):
data = self.get('/projects/project-id2/meters',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id2"})
ids = set(r['resource_id'] for r in data['meters'])
self.assertEquals(set(['resource-id3', 'resource-id4']), ids)
def test_with_project_wrong_tenant(self):
data = self.get('/projects/project-id2/meters',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEqual(data.status_code, 404)
def test_with_project_non_existent(self):
data = self.get('/projects/jd-was-here/meters')
self.assertEquals(data['meters'], [])
@ -158,6 +209,30 @@ class TestListMeters(tests_api.TestBase):
data = self.get('/meters?metadata.tag=self.counter')
self.assertEquals(1, len(data['meters']))
def test_metaquery1_non_admin(self):
data = self.get('/meters?metadata.tag=self.counter',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEquals(1, len(data['meters']))
def test_metaquery1_wrong_tenant(self):
data = self.get('/meters?metadata.tag=self.counter',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-666"})
self.assertEquals(0, len(data['meters']))
def test_metaquery2(self):
data = self.get('/meters?metadata.tag=four.counter')
self.assertEquals(1, len(data['meters']))
def test_metaquery2_non_admin(self):
data = self.get('/meters?metadata.tag=four.counter',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id2"})
self.assertEquals(1, len(data['meters']))
def test_metaquery2_non_admin(self):
data = self.get('/meters?metadata.tag=four.counter',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-666"})
self.assertEquals(0, len(data['meters']))

View File

@ -3,6 +3,7 @@
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -30,13 +31,17 @@ from ceilometer.tests import api as tests_api
LOG = logging.getLogger(__name__)
class TestListProjects(tests_api.TestBase):
class TestListEmptyProjects(tests_api.TestBase):
def test_empty(self):
data = self.get('/projects')
self.assertEquals({'projects': []}, data)
def test_projects(self):
class TestListProjects(tests_api.TestBase):
def setUp(self):
super(TestListProjects, self).setUp()
counter1 = counter.Counter(
'instance',
'cumulative',
@ -73,45 +78,22 @@ class TestListProjects(tests_api.TestBase):
)
self.conn.record_metering_data(msg2)
def test_projects(self):
data = self.get('/projects')
self.assertEquals(['project-id', 'project-id2'], data['projects'])
def test_with_source(self):
counter1 = counter.Counter(
'instance',
'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',
}
)
msg = meter.meter_message_from_counter(counter1,
cfg.CONF.metering_secret,
'test_list_users',
)
self.conn.record_metering_data(msg)
counter2 = counter.Counter(
'instance',
'cumulative',
1,
'user-id2',
'project-id2',
'resource-id-alternate',
timestamp=datetime.datetime(2012, 7, 2, 10, 41),
resource_metadata={'display_name': 'test-server',
'tag': 'self.counter2',
}
)
msg2 = meter.meter_message_from_counter(counter2,
cfg.CONF.metering_secret,
'not-test',
)
self.conn.record_metering_data(msg2)
data = self.get('/sources/test_list_users/projects')
def test_projects_non_admin(self):
data = self.get('/projects',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEquals(['project-id'], data['projects'])
def test_with_source(self):
data = self.get('/sources/test_list_users/projects')
self.assertEquals(['project-id2'], data['projects'])
def test_with_source_non_admin(self):
data = self.get('/sources/test_list_users/projects',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id2"})
self.assertEquals(['project-id2'], data['projects'])

View File

@ -3,6 +3,7 @@
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -100,6 +101,15 @@ class TestListResources(tests_api.TestBase):
'resource-id-alternate',
'resource-id2']))
def test_list_resources_non_admin(self):
data = self.get('/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEquals(2, len(data['resources']))
self.assertEquals(set(r['resource_id'] for r in data['resources']),
set(['resource-id',
'resource-id-alternate']))
def test_list_resources_with_timestamps(self):
data = self.get('/resources',
start_timestamp=datetime.datetime(
@ -110,6 +120,17 @@ class TestListResources(tests_api.TestBase):
set(['resource-id-alternate',
'resource-id2']))
def test_list_resources_with_timestamps_non_admin(self):
data = self.get('/resources',
start_timestamp=datetime.datetime(
2012, 7, 2, 10, 41).isoformat(),
end_timestamp=datetime.datetime(
2012, 7, 2, 10, 43).isoformat(),
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEquals(set(r['resource_id'] for r in data['resources']),
set(['resource-id-alternate']))
def test_with_source(self):
data = self.get('/sources/test_list_resources/resources')
ids = set(r['resource_id'] for r in data['resources'])
@ -117,6 +138,14 @@ class TestListResources(tests_api.TestBase):
'resource-id2',
'resource-id-alternate']), ids)
def test_with_source_non_admin(self):
data = self.get('/sources/test_list_resources/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id',
'resource-id-alternate']), ids)
def test_with_source_with_timestamps(self):
data = self.get('/sources/test_list_resources/resources',
start_timestamp=datetime.datetime(
@ -127,6 +156,17 @@ class TestListResources(tests_api.TestBase):
self.assertEquals(set(['resource-id2',
'resource-id-alternate']), ids)
def test_with_source_with_timestamps_non_admin(self):
data = self.get('/sources/test_list_resources/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"},
start_timestamp=datetime.datetime(
2012, 7, 2, 10, 41).isoformat(),
end_timestamp=datetime.datetime(
2012, 7, 2, 10, 43).isoformat())
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id-alternate']), ids)
def test_with_source_non_existent(self):
data = self.get('/sources/test_list_resources_dont_exist/resources')
self.assertEquals(data['resources'], [])
@ -136,6 +176,20 @@ class TestListResources(tests_api.TestBase):
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id', 'resource-id-alternate']), ids)
def test_with_user_non_admin(self):
data = self.get('/users/user-id/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id', 'resource-id-alternate']), ids)
def test_with_user_wrong_tenant(self):
data = self.get('/users/user-id/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-jd"})
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(), ids)
def test_with_user_with_timestamps(self):
data = self.get('/users/user-id/resources',
start_timestamp=datetime.datetime(
@ -145,6 +199,17 @@ class TestListResources(tests_api.TestBase):
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(), ids)
def test_with_user_with_timestamps_non_admin(self):
data = self.get('/users/user-id/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"},
start_timestamp=datetime.datetime(
2012, 7, 2, 10, 42).isoformat(),
end_timestamp=datetime.datetime(
2012, 7, 2, 10, 42).isoformat())
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(), ids)
def test_with_user_non_existent(self):
data = self.get('/users/user-id-foobar123/resources')
self.assertEquals(data['resources'], [])
@ -154,6 +219,13 @@ class TestListResources(tests_api.TestBase):
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id', 'resource-id-alternate']), ids)
def test_with_project_non_admin(self):
data = self.get('/projects/project-id/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id', 'resource-id-alternate']), ids)
def test_with_project_with_timestamp(self):
data = self.get('/projects/project-id/resources',
start_timestamp=datetime.datetime(
@ -163,6 +235,17 @@ class TestListResources(tests_api.TestBase):
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id']), ids)
def test_with_project_with_timestamp_non_admin(self):
data = self.get('/projects/project-id/resources',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"},
start_timestamp=datetime.datetime(
2012, 7, 2, 10, 40).isoformat(),
end_timestamp=datetime.datetime(
2012, 7, 2, 10, 41).isoformat())
ids = set(r['resource_id'] for r in data['resources'])
self.assertEquals(set(['resource-id']), ids)
def test_with_project_non_existent(self):
data = self.get('/projects/jd-was-here/resources')
self.assertEquals(data['resources'], [])
@ -172,7 +255,21 @@ class TestListResources(tests_api.TestBase):
data = self.get('%s?metadata.display_name=test-server' % q)
self.assertEquals(3, len(data['resources']))
def test_metaquery1_non_admin(self):
q = '/sources/test_list_resources/resources'
data = self.get('%s?metadata.display_name=test-server' % q,
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEquals(2, len(data['resources']))
def test_metaquery2(self):
q = '/sources/test_list_resources/resources'
data = self.get('%s?metadata.tag=self.counter4' % q)
self.assertEquals(1, len(data['resources']))
def test_metaquery2_non_admin(self):
q = '/sources/test_list_resources/resources'
data = self.get('%s?metadata.tag=self.counter4' % q,
headers={"X-Roles": "Member",
"X-Tenant-Id": "project-id"})
self.assertEquals(1, len(data['resources']))

View File

@ -3,6 +3,7 @@
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -30,53 +31,18 @@ from ceilometer.tests import api as tests_api
LOG = logging.getLogger(__name__)
class TestListUsers(tests_api.TestBase):
class TestListEmptyUsers(tests_api.TestBase):
def test_empty(self):
data = self.get('/users')
self.assertEquals({'users': []}, data)
def test_users(self):
counter1 = counter.Counter(
'instance',
'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',
}
)
msg = meter.meter_message_from_counter(counter1,
cfg.CONF.metering_secret,
'test_list_users',
)
self.conn.record_metering_data(msg)
counter2 = counter.Counter(
'instance',
'cumulative',
1,
'user-id2',
'project-id',
'resource-id-alternate',
timestamp=datetime.datetime(2012, 7, 2, 10, 41),
resource_metadata={'display_name': 'test-server',
'tag': 'self.counter2',
}
)
msg2 = meter.meter_message_from_counter(counter2,
cfg.CONF.metering_secret,
'test_list_users',
)
self.conn.record_metering_data(msg2)
class TestListUsers(tests_api.TestBase):
data = self.get('/users')
self.assertEquals(['user-id', 'user-id2'], data['users'])
def setUp(self):
super(TestListUsers, self).setUp()
def test_with_source(self):
counter1 = counter.Counter(
'instance',
'cumulative',
@ -113,5 +79,24 @@ class TestListUsers(tests_api.TestBase):
)
self.conn.record_metering_data(msg2)
def test_users(self):
data = self.get('/users')
self.assertEquals(['user-id', 'user-id2'], data['users'])
def test_users_non_admin(self):
data = self.get('/users',
headers={"X-Roles": "Member",
"X-User-Id": "user-id",
"X-Tenant-Id": "project-id"})
self.assertEquals(['user-id'], data['users'])
def test_with_source(self):
data = self.get('/sources/test_list_users/users')
self.assertEquals(['user-id'], data['users'])
def test_with_source_non_admin(self):
data = self.get('/sources/test_list_users/users',
headers={"X-Roles": "Member",
"X-User-Id": "user-id",
"X-Tenant-Id": "project-id"})
self.assertEquals(['user-id'], data['users'])

View File

@ -3,6 +3,7 @@
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Steven Berler <steven.berler@dreamhost.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -60,6 +61,18 @@ class TestMaxProjectVolume(tests_api.TestBase):
expected = {'volume': 7}
assert data == expected
def test_no_time_bounds_non_admin(self):
data = self.get('/projects/project1/meters/volume.size/volume/max',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEqual(data, {'volume': 7})
def test_no_time_bounds_wrong_tenant(self):
resp = self.get('/projects/project1/meters/volume.size/volume/max',
headers={"X-Roles": "Member",
"X-Tenant-Id": "?"})
self.assertEqual(resp.status_code, 404)
def test_start_timestamp(self):
data = self.get('/projects/project1/meters/volume.size/volume/max',
start_timestamp='2012-09-25T11:30:00')

View File

@ -60,6 +60,18 @@ class TestMaxResourceVolume(tests_api.TestBase):
expected = {'volume': 7}
assert data == expected
def test_no_time_bounds_non_admin(self):
data = self.get('/resources/resource-id/meters/volume.size/volume/max',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEqual(data, {'volume': 7})
def test_no_time_bounds_wrong_tenant(self):
data = self.get('/resources/resource-id/meters/volume.size/volume/max',
headers={"X-Roles": "Member",
"X-Tenant-Id": "??"})
self.assertEqual(data, {'volume': None})
def test_start_timestamp(self):
data = self.get('/resources/resource-id/meters/volume.size/volume/max',
start_timestamp='2012-09-25T11:30:00')

View File

@ -3,6 +3,7 @@
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Steven Berler <steven.berler@dreamhost.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -60,6 +61,18 @@ class TestSumProjectVolume(tests_api.TestBase):
expected = {'volume': 5 + 6 + 7}
assert data == expected
def test_no_time_bounds_non_admin(self):
data = self.get('/projects/project1/meters/volume.size/volume/sum',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEqual(data, {'volume': 5 + 6 + 7})
def test_no_time_bounds_wrong_tenant(self):
resp = self.get('/projects/project1/meters/volume.size/volume/sum',
headers={"X-Roles": "Member",
"X-Tenant-Id": "???"})
self.assertEqual(resp.status_code, 404)
def test_start_timestamp(self):
data = self.get('/projects/project1/meters/volume.size/volume/sum',
start_timestamp='2012-09-25T11:30:00')

View File

@ -3,6 +3,7 @@
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
# Julien Danjou <julien@danjou.info>
#
# 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
@ -60,6 +61,18 @@ class TestSumResourceVolume(tests_api.TestBase):
expected = {'volume': 5 + 6 + 7}
assert data == expected
def test_no_time_bounds_non_admin(self):
data = self.get('/resources/resource-id/meters/volume.size/volume/sum',
headers={"X-Roles": "Member",
"X-Tenant-Id": "project1"})
self.assertEqual(data, {'volume': 5 + 6 + 7})
def test_no_time_bounds_wrong_tenant(self):
data = self.get('/resources/resource-id/meters/volume.size/volume/sum',
headers={"X-Roles": "Member",
"X-Tenant-Id": "?"})
self.assertEqual(data, {'volume': None})
def test_start_timestamp(self):
data = self.get('/resources/resource-id/meters/volume.size/volume/sum',
start_timestamp='2012-09-25T11:30:00')