From 9f9c40d56954baf183968b6ea9db9aec62f4c064 Mon Sep 17 00:00:00 2001 From: Mahesh K P Date: Wed, 17 Apr 2013 11:41:41 +0530 Subject: Return Customer's Quota Usage through Admin API DocImpact: This patch adds an extension 'UsedLimitsForAdmin'. If this extention is enabled then it extends the used limits API behavior, such that admin can fetch the details of any customer's quota usage by passing the customer's tenant id in query parameters.The API signature for the same is 'v2/{tenant_id}/limits?tenant_id={customer_tenant_id}' Change-Id: I89b8b5083e46b899458407426c89a3865e960faa Implements: blueprint customer-quota-through-admin-api --- .../openstack/compute/contrib/test_used_limits.py | 119 ++++++++++++++++++++- .../tests/api/openstack/compute/test_extensions.py | 20 ++++ nova/tests/fake_policy.py | 2 +- .../all_extensions/extensions-get-resp.json.tpl | 8 ++ .../all_extensions/extensions-get-resp.xml.tpl | 3 + .../usedlimitsforadmin-get-resp.json.tpl | 90 ++++++++++++++++ .../usedlimitsforadmin-get-resp.xml.tpl | 37 +++++++ nova/tests/integrated/test_api_samples.py | 19 ++++ 8 files changed, 294 insertions(+), 4 deletions(-) create mode 100644 nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.xml.tpl (limited to 'nova/tests') diff --git a/nova/tests/api/openstack/compute/contrib/test_used_limits.py b/nova/tests/api/openstack/compute/contrib/test_used_limits.py index ebe3e852d..4f50916a7 100644 --- a/nova/tests/api/openstack/compute/contrib/test_used_limits.py +++ b/nova/tests/api/openstack/compute/contrib/test_used_limits.py @@ -17,8 +17,10 @@ from nova.api.openstack.compute.contrib import used_limits from nova.api.openstack.compute import limits +from nova.api.openstack import extensions from nova.api.openstack import wsgi import nova.context +from nova import exception from nova import quota from nova import test @@ -31,13 +33,15 @@ class FakeRequest(object): class UsedLimitsTestCase(test.TestCase): - def setUp(self): """Run before each test.""" super(UsedLimitsTestCase, self).setUp() - self.controller = used_limits.UsedLimitsController() + self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager) + self.controller = used_limits.UsedLimitsController(self.ext_mgr) self.fake_context = nova.context.RequestContext('fake', 'fake') + self.mox.StubOutWithMock(used_limits, 'authorize_for_admin') + self.authorize_for_admin = used_limits.authorize_for_admin def _do_test_used_limits(self, reserved): fake_req = FakeRequest(self.fake_context, reserved=reserved) @@ -63,8 +67,11 @@ class UsedLimitsTestCase(test.TestCase): def stub_get_project_quotas(context, project_id, usages=True): return limits + self.stubs.Set(quota.QUOTAS, "get_project_quotas", stub_get_project_quotas) + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False) + self.mox.ReplayAll() self.controller.index(fake_req, res) abs_limits = res.obj['limits']['absolute'] @@ -79,6 +86,100 @@ class UsedLimitsTestCase(test.TestCase): def test_used_limits_with_reserved(self): self._do_test_used_limits(True) + def test_admin_can_fetch_limits_for_a_given_tenant_id(self): + project_id = "123456" + user_id = "A1234" + tenant_id = 'abcd' + self.fake_context.project_id = project_id + self.fake_context.user_id = user_id + obj = { + "limits": { + "rate": [], + "absolute": {}, + }, + } + target = { + "project_id": tenant_id, + "user_id": user_id + } + fake_req = FakeRequest(self.fake_context) + fake_req.GET = {'tenant_id': tenant_id} + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True) + self.authorize_for_admin(self.fake_context, target=target) + self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas') + quota.QUOTAS.get_project_quotas(self.fake_context, '%s' % tenant_id, + usages=True).AndReturn({}) + self.mox.ReplayAll() + res = wsgi.ResponseObject(obj) + self.controller.index(fake_req, res) + + def test_admin_can_fetch_used_limits_for_own_project(self): + project_id = "123456" + user_id = "A1234" + self.fake_context.project_id = project_id + self.fake_context.user_id = user_id + obj = { + "limits": { + "rate": [], + "absolute": {}, + }, + } + fake_req = FakeRequest(self.fake_context) + fake_req.GET = {} + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True) + self.mox.StubOutWithMock(extensions, 'extension_authorizer') + self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas') + quota.QUOTAS.get_project_quotas(self.fake_context, '%s' % project_id, + usages=True).AndReturn({}) + self.mox.ReplayAll() + res = wsgi.ResponseObject(obj) + self.controller.index(fake_req, res) + + def test_non_admin_cannot_fetch_used_limits_for_any_other_project(self): + project_id = "123456" + user_id = "A1234" + tenant_id = "abcd" + self.fake_context.project_id = project_id + self.fake_context.user_id = user_id + obj = { + "limits": { + "rate": [], + "absolute": {}, + }, + } + target = { + "project_id": tenant_id, + "user_id": user_id + } + fake_req = FakeRequest(self.fake_context) + fake_req.GET = {'tenant_id': tenant_id} + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(True) + self.authorize_for_admin(self.fake_context, target=target). \ + AndRaise(exception.PolicyNotAuthorized( + action="compute_extension:used_limits_for_admin")) + self.mox.ReplayAll() + res = wsgi.ResponseObject(obj) + self.assertRaises(exception.PolicyNotAuthorized, self.controller.index, + fake_req, res) + + def test_used_limits_fetched_for_context_project_id(self): + project_id = "123456" + self.fake_context.project_id = project_id + obj = { + "limits": { + "rate": [], + "absolute": {}, + }, + } + fake_req = FakeRequest(self.fake_context) + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False) + self.mox.StubOutWithMock(quota.QUOTAS, 'get_project_quotas') + quota.QUOTAS.get_project_quotas(self.fake_context, project_id, + usages=True).AndReturn({}) + self.mox.ReplayAll() + res = wsgi.ResponseObject(obj) + self.controller.index(fake_req, res) + def test_used_ram_added(self): fake_req = FakeRequest(self.fake_context) obj = { @@ -86,15 +187,19 @@ class UsedLimitsTestCase(test.TestCase): "rate": [], "absolute": { "maxTotalRAMSize": 512, - }, + }, }, } res = wsgi.ResponseObject(obj) def stub_get_project_quotas(context, project_id, usages=True): return {'ram': {'limit': 512, 'in_use': 256}} + + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False) self.stubs.Set(quota.QUOTAS, "get_project_quotas", stub_get_project_quotas) + self.mox.ReplayAll() + self.controller.index(fake_req, res) abs_limits = res.obj['limits']['absolute'] self.assertTrue('totalRAMUsed' in abs_limits) @@ -112,8 +217,12 @@ class UsedLimitsTestCase(test.TestCase): def stub_get_project_quotas(context, project_id, usages=True): return {} + + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False) self.stubs.Set(quota.QUOTAS, "get_project_quotas", stub_get_project_quotas) + self.mox.ReplayAll() + self.controller.index(fake_req, res) abs_limits = res.obj['limits']['absolute'] self.assertFalse('totalRAMUsed' in abs_limits) @@ -131,8 +240,12 @@ class UsedLimitsTestCase(test.TestCase): def stub_get_project_quotas(context, project_id, usages=True): return {} + + self.ext_mgr.is_loaded('os-used-limits-for-admin').AndReturn(False) self.stubs.Set(quota.QUOTAS, "get_project_quotas", stub_get_project_quotas) + self.mox.ReplayAll() + self.controller.index(fake_req, res) response = res.serialize(None, 'xml') self.assertTrue(used_limits.XMLNS in response.body) diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index 6e400a075..3122023a4 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -26,7 +26,9 @@ from nova.api.openstack.compute import extensions as compute_extensions from nova.api.openstack import extensions as base_extensions from nova.api.openstack import wsgi from nova.api.openstack import xmlutil +from nova import exception from nova.openstack.common import jsonutils +import nova.policy from nova import test from nova.tests.api.openstack import fakes from nova.tests import matchers @@ -147,6 +149,24 @@ class ExtensionTestCase(test.TestCase): if fox not in ext_list: ext_list.append(fox) self.flags(osapi_compute_extension=ext_list) + self.fake_context = nova.context.RequestContext('fake', 'fake') + + def test_extension_authorizer_throws_exception_if_policy_fails(self): + target = {'project_id': '1234', + 'user_id': '5678'} + self.mox.StubOutWithMock(nova.policy, 'enforce') + nova.policy.enforce(self.fake_context, + "compute_extension:used_limits_for_admin", + target).AndRaise( + exception.PolicyNotAuthorized( + action="compute_extension:used_limits_for_admin")) + ('compute', 'used_limits_for_admin') + self.mox.ReplayAll() + authorize = base_extensions.extension_authorizer('compute', + 'used_limits_for_admin' + ) + self.assertRaises(exception.PolicyNotAuthorized, authorize, + self.fake_context, target=target) class ExtensionControllerTest(ExtensionTestCase): diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 1290ef80b..3bec6beda 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -180,7 +180,7 @@ policy_data = """ "compute_extension:zones": "", "compute_extension:availability_zone:list": "", "compute_extension:availability_zone:detail": "is_admin:True", - + "compute_extension:used_limits_for_admin": "is_admin:True", "volume:create": "", "volume:get": "", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index d559b4890..33b6a74b9 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -488,6 +488,14 @@ "namespace": "http://docs.openstack.org/compute/ext/used_limits/api/v1.1", "updated": "%(timestamp)s" }, + { + "alias": "os-used-limits-for-admin", + "description": "%(text)s", + "links": [], + "name": "UsedLimitsForAdmin", + "namespace": "http://docs.openstack.org/compute/ext/used_limits_for_admin/api/v1.1", + "updated": "%(timestamp)s" + }, { "alias": "os-user-data", "description": "%(text)s", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index cc9ae4c02..9ca1739e5 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -183,6 +183,9 @@ %(text)s + + %(text)s + %(text)s diff --git a/nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.json.tpl new file mode 100644 index 000000000..d83dd87c3 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.json.tpl @@ -0,0 +1,90 @@ +{ + "limits": { + "absolute": { + "maxImageMeta": 128, + "maxPersonality": 5, + "maxPersonalitySize": 10240, + "maxSecurityGroupRules": 20, + "maxSecurityGroups": 10, + "maxServerMeta": 128, + "maxTotalCores": 20, + "maxTotalFloatingIps": 10, + "maxTotalInstances": 10, + "maxTotalKeypairs": 100, + "maxTotalRAMSize": 51200, + "totalCoresUsed": 0, + "totalInstancesUsed": 0, + "totalRAMUsed": 0, + "totalSecurityGroupsUsed": 0, + "totalFloatingIpsUsed": 0 + }, + "rate": [ + { + "limit": [ + { + "next-available": "%(timestamp)s", + "remaining": 10, + "unit": "MINUTE", + "value": 10, + "verb": "POST" + }, + { + "next-available": "%(timestamp)s", + "remaining": 10, + "unit": "MINUTE", + "value": 10, + "verb": "PUT" + }, + { + "next-available": "%(timestamp)s", + "remaining": 100, + "unit": "MINUTE", + "value": 100, + "verb": "DELETE" + } + ], + "regex": ".*", + "uri": "*" + }, + { + "limit": [ + { + "next-available": "%(timestamp)s", + "remaining": 50, + "unit": "DAY", + "value": 50, + "verb": "POST" + } + ], + "regex": "^/servers", + "uri": "*/servers" + }, + { + "limit": [ + { + "next-available": "%(timestamp)s", + "remaining": 3, + "unit": "MINUTE", + "value": 3, + "verb": "GET" + } + ], + "regex": ".*changes-since.*", + "uri": "*changes-since*" + }, + { + "limit": [ + { + "next-available": "%(timestamp)s", + "remaining": 12, + "unit": "HOUR", + "value": 12, + "verb": "GET" + } + ], + "regex": "^/os-fping", + "uri": "*/os-fping" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.xml.tpl new file mode 100644 index 000000000..c1b907670 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-used-limits-for-admin/usedlimitsforadmin-get-resp.xml.tpl @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index e24f24189..3f47474df 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -1928,6 +1928,25 @@ class UsedLimitsSamplesXmlTest(UsedLimitsSamplesJsonTest): ctype = "xml" +class UsedLimitsForAdminSamplesJsonTest(ApiSampleTestBase): + extends_name = ("nova.api.openstack.compute.contrib.used_limits." + "Used_limits") + extension_name = ( + "nova.api.openstack.compute.contrib.used_limits_for_admin." + "Used_limits_for_admin") + + def test_get_used_limits_for_admin(self): + tenant_id = 'openstack' + response = self._do_get('limits?tenant_id=%s' % tenant_id) + subs = self._get_regexes() + return self._verify_response('usedlimitsforadmin-get-resp', subs, + response, 200) + + +class UsedLimitsForAdminSamplesXmlTest(UsedLimitsForAdminSamplesJsonTest): + ctype = "xml" + + class MultipleCreateJsonTest(ServersSampleBase): extension_name = ("nova.api.openstack.compute.contrib.multiple_create." "Multiple_create") -- cgit