diff --git a/nova/api/openstack/compute/limits.py b/nova/api/openstack/compute/limits.py index 78e1e24066..958bdf392c 100644 --- a/nova/api/openstack/compute/limits.py +++ b/nova/api/openstack/compute/limits.py @@ -76,9 +76,7 @@ class LimitsController(wsgi.Controller): context.can(limits_policies.BASE_POLICY_NAME) project_id = req.params.get('tenant_id', context.project_id) quotas = QUOTAS.get_project_quotas(context, project_id, - usages=False) - abs_limits = {k: v['limit'] for k, v in quotas.items()} - + usages=True) builder = limits_views.ViewBuilder() - return builder.build(abs_limits, filtered_limits=filtered_limits, + return builder.build(req, quotas, filtered_limits=filtered_limits, max_image_meta=max_image_meta) diff --git a/nova/api/openstack/compute/routes.py b/nova/api/openstack/compute/routes.py index f0799a6364..d4fa3c1718 100644 --- a/nova/api/openstack/compute/routes.py +++ b/nova/api/openstack/compute/routes.py @@ -82,7 +82,6 @@ from nova.api.openstack.compute import shelve from nova.api.openstack.compute import simple_tenant_usage from nova.api.openstack.compute import suspend_server from nova.api.openstack.compute import tenant_networks -from nova.api.openstack.compute import used_limits from nova.api.openstack.compute import versionsV21 from nova.api.openstack.compute import virtual_interfaces from nova.api.openstack.compute import volumes @@ -226,7 +225,6 @@ keypairs_controller = functools.partial( limits_controller = functools.partial( _create_controller, limits.LimitsController, [ - used_limits.UsedLimitsController, ], []) diff --git a/nova/api/openstack/compute/used_limits.py b/nova/api/openstack/compute/used_limits.py deleted file mode 100644 index 5b4f36ad59..0000000000 --- a/nova/api/openstack/compute/used_limits.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# -# 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 nova.api.openstack import api_version_request -from nova.api.openstack.api_version_request \ - import MIN_WITHOUT_PROXY_API_SUPPORT_VERSION -from nova.api.openstack import wsgi -from nova.policies import used_limits as ul_policies -from nova import quota - - -QUOTAS = quota.QUOTAS - - -class UsedLimitsController(wsgi.Controller): - - @wsgi.extends - @wsgi.expected_errors(()) - def index(self, req, resp_obj): - context = req.environ['nova.context'] - project_id = self._project_id(context, req) - quotas = QUOTAS.get_project_quotas(context, project_id, usages=True) - if api_version_request.is_supported( - req, min_version=MIN_WITHOUT_PROXY_API_SUPPORT_VERSION): - quota_map = { - 'totalRAMUsed': 'ram', - 'totalCoresUsed': 'cores', - 'totalInstancesUsed': 'instances', - 'totalServerGroupsUsed': 'server_groups', - } - else: - quota_map = { - 'totalRAMUsed': 'ram', - 'totalCoresUsed': 'cores', - 'totalInstancesUsed': 'instances', - 'totalFloatingIpsUsed': 'floating_ips', - 'totalSecurityGroupsUsed': 'security_groups', - 'totalServerGroupsUsed': 'server_groups', - } - - used_limits = {} - for display_name, key in quota_map.items(): - if key in quotas: - used_limits[display_name] = quotas[key]['in_use'] - - resp_obj.obj['limits']['absolute'].update(used_limits) - - def _project_id(self, context, req): - if 'tenant_id' in req.GET: - tenant_id = req.GET.get('tenant_id') - target = { - 'project_id': tenant_id, - 'user_id': context.user_id - } - context.can(ul_policies.BASE_POLICY_NAME, target) - return tenant_id - return context.project_id diff --git a/nova/api/openstack/compute/views/limits.py b/nova/api/openstack/compute/views/limits.py index db935a56e9..f58f3191f5 100644 --- a/nova/api/openstack/compute/views/limits.py +++ b/nova/api/openstack/compute/views/limits.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +from nova.policies import used_limits as ul_policies + class ViewBuilder(object): """OpenStack API base limits view builder.""" @@ -35,12 +37,17 @@ class ViewBuilder(object): "server_group_members": ["maxServerGroupMembers"] } - def build(self, absolute_limits, filtered_limits=None, + def build(self, request, quotas, filtered_limits=None, max_image_meta=True): + filtered_limits = filtered_limits or [] absolute_limits = self._build_absolute_limits( - absolute_limits, filtered_limits, + quotas, filtered_limits, max_image_meta=max_image_meta) + used_limits = self._build_used_limits( + request, quotas, filtered_limits) + + absolute_limits.update(used_limits) output = { "limits": { "rate": [], @@ -50,7 +57,7 @@ class ViewBuilder(object): return output - def _build_absolute_limits(self, absolute_limits, filtered_limits=None, + def _build_absolute_limits(self, quotas, filtered_limits=None, max_image_meta=True): """Builder for absolute limits @@ -60,7 +67,7 @@ class ViewBuilder(object): filtered_limits is an optional list of limits to exclude from the result set. """ - filtered_limits = filtered_limits or [] + absolute_limits = {k: v['limit'] for k, v in quotas.items()} limits = {} for name, value in absolute_limits.items(): if (name in self.limit_names and @@ -70,3 +77,30 @@ class ViewBuilder(object): continue limits[limit_name] = value return limits + + def _build_used_limits(self, request, quotas, filtered_limits): + self._check_requested_project_scope(request) + quota_map = { + 'totalRAMUsed': 'ram', + 'totalCoresUsed': 'cores', + 'totalInstancesUsed': 'instances', + 'totalFloatingIpsUsed': 'floating_ips', + 'totalSecurityGroupsUsed': 'security_groups', + 'totalServerGroupsUsed': 'server_groups', + } + used_limits = {} + for display_name, key in quota_map.items(): + if (key in quotas and key not in filtered_limits): + used_limits[display_name] = quotas[key]['in_use'] + + return used_limits + + def _check_requested_project_scope(self, request): + if 'tenant_id' in request.GET: + context = request.environ['nova.context'] + tenant_id = request.GET.get('tenant_id') + target = { + 'project_id': tenant_id, + 'user_id': context.user_id + } + context.can(ul_policies.BASE_POLICY_NAME, target) diff --git a/nova/tests/unit/api/openstack/compute/test_limits.py b/nova/tests/unit/api/openstack/compute/test_limits.py index 68d2e129aa..902eb49e62 100644 --- a/nova/tests/unit/api/openstack/compute/test_limits.py +++ b/nova/tests/unit/api/openstack/compute/test_limits.py @@ -28,6 +28,8 @@ from nova.api.openstack.compute import views from nova.api.openstack import wsgi import nova.context from nova import exception +from nova.policies import used_limits as ul_policies +from nova import quota from nova import test from nova.tests.unit.api.openstack import fakes from nova.tests.unit import matchers @@ -42,7 +44,7 @@ class BaseLimitTestSuite(test.NoDBTestCase): self.absolute_limits = {} def stub_get_project_quotas(context, project_id, usages=True): - return {k: dict(limit=v) + return {k: dict(limit=v, in_use=v // 2) for k, v in self.absolute_limits.items()} mock_get_project_quotas = mock.patch.object( @@ -51,6 +53,9 @@ class BaseLimitTestSuite(test.NoDBTestCase): side_effect = stub_get_project_quotas) mock_get_project_quotas.start() self.addCleanup(mock_get_project_quotas.stop) + patcher = self.mock_can = mock.patch('nova.context.RequestContext.can') + self.mock_can = patcher.start() + self.addCleanup(patcher.stop) def _get_time(self): """Return the "time" according to this test suite.""" @@ -68,7 +73,8 @@ class LimitsControllerTestV21(BaseLimitTestSuite): self.ctrler = self.limits_controller() def _get_index_request(self, accept_header="application/json", - tenant_id=None): + tenant_id=None, user_id='testuser', + project_id='testproject'): """Helper to set routing arguments.""" request = fakes.HTTPRequest.blank('', version='2.1') if tenant_id: @@ -80,7 +86,7 @@ class LimitsControllerTestV21(BaseLimitTestSuite): "action": "index", "controller": "", }) - context = nova.context.RequestContext('testuser', 'testproject') + context = nova.context.RequestContext(user_id, project_id) request.environ["nova.context"] = context return request @@ -130,12 +136,18 @@ class LimitsControllerTestV21(BaseLimitTestSuite): "maxTotalFloatingIps": 10, "maxSecurityGroups": 10, "maxSecurityGroupRules": 20, + "totalRAMUsed": 256, + "totalCoresUsed": 10, + "totalInstancesUsed": 2, + "totalFloatingIpsUsed": 5, + "totalSecurityGroupsUsed": 5, }, }, } def _get_project_quotas(context, project_id, usages=True): - return {k: dict(limit=v) for k, v in self.absolute_limits.items()} + return {k: dict(limit=v, in_use=v // 2) + for k, v in self.absolute_limits.items()} with mock.patch('nova.quota.QUOTAS.get_project_quotas') as \ get_project_quotas: @@ -146,7 +158,144 @@ class LimitsControllerTestV21(BaseLimitTestSuite): body = jsonutils.loads(response.body) self.assertEqual(expected, body) get_project_quotas.assert_called_once_with(context, tenant_id, - usages=False) + usages=True) + + def _do_test_used_limits(self, reserved): + request = self._get_index_request(tenant_id=None) + quota_map = { + 'totalRAMUsed': 'ram', + 'totalCoresUsed': 'cores', + 'totalInstancesUsed': 'instances', + 'totalFloatingIpsUsed': 'floating_ips', + 'totalSecurityGroupsUsed': 'security_groups', + 'totalServerGroupsUsed': 'server_groups', + } + limits = {} + expected_abs_limits = [] + for display_name, q in quota_map.items(): + limits[q] = {'limit': len(display_name), + 'in_use': len(display_name) // 2, + 'reserved': 0} + expected_abs_limits.append(display_name) + + def stub_get_project_quotas(context, project_id, usages=True): + return limits + + self.stub_out('nova.quota.QUOTAS.get_project_quotas', + stub_get_project_quotas) + + res = request.get_response(self.controller) + body = jsonutils.loads(res.body) + abs_limits = body['limits']['absolute'] + for limit in expected_abs_limits: + value = abs_limits[limit] + r = limits[quota_map[limit]]['reserved'] if reserved else 0 + self.assertEqual(limits[quota_map[limit]]['in_use'] + r, value) + + def test_used_limits_basic(self): + self._do_test_used_limits(False) + + 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' + target = { + "project_id": tenant_id, + "user_id": user_id + } + fake_req = self._get_index_request(tenant_id=tenant_id, + user_id=user_id, + project_id=project_id) + context = fake_req.environ["nova.context"] + with mock.patch.object(quota.QUOTAS, 'get_project_quotas', + return_value={}) as mock_get_quotas: + fake_req.get_response(self.controller) + self.assertEqual(2, self.mock_can.call_count) + self.mock_can.assert_called_with(ul_policies.BASE_POLICY_NAME, + target) + mock_get_quotas.assert_called_once_with(context, + tenant_id, usages=True) + + def _test_admin_can_fetch_used_limits_for_own_project(self, req_get): + project_id = "123456" + if 'tenant_id' in req_get: + project_id = req_get['tenant_id'] + + user_id = "A1234" + fake_req = self._get_index_request(user_id=user_id, + project_id=project_id) + context = fake_req.environ["nova.context"] + + with mock.patch.object(quota.QUOTAS, 'get_project_quotas', + return_value={}) as mock_get_quotas: + fake_req.get_response(self.controller) + mock_get_quotas.assert_called_once_with(context, + project_id, usages=True) + + def test_admin_can_fetch_used_limits_for_own_project(self): + req_get = {} + self._test_admin_can_fetch_used_limits_for_own_project(req_get) + + def test_admin_can_fetch_used_limits_for_dummy_only(self): + # for back compatible we allow additional param to be send to req.GET + # it can be removed when we add restrictions to query param later + req_get = {'dummy': 'dummy'} + self._test_admin_can_fetch_used_limits_for_own_project(req_get) + + def test_admin_can_fetch_used_limits_with_positive_int(self): + req_get = {'tenant_id': 123} + self._test_admin_can_fetch_used_limits_for_own_project(req_get) + + def test_admin_can_fetch_used_limits_with_negative_int(self): + req_get = {'tenant_id': -1} + self._test_admin_can_fetch_used_limits_for_own_project(req_get) + + def test_admin_can_fetch_used_limits_with_unkown_param(self): + req_get = {'tenant_id': '123', 'unknown': 'unknown'} + self._test_admin_can_fetch_used_limits_for_own_project(req_get) + + def test_used_limits_fetched_for_context_project_id(self): + project_id = "123456" + fake_req = self._get_index_request(project_id=project_id) + context = fake_req.environ["nova.context"] + with mock.patch.object(quota.QUOTAS, 'get_project_quotas', + return_value={}) as mock_get_quotas: + fake_req.get_response(self.controller) + + mock_get_quotas.assert_called_once_with(context, + project_id, usages=True) + + def test_used_ram_added(self): + fake_req = self._get_index_request() + + def stub_get_project_quotas(context, project_id, usages=True): + return {'ram': {'limit': 512, 'in_use': 256}} + + with mock.patch.object(quota.QUOTAS, 'get_project_quotas', + side_effect=stub_get_project_quotas + ) as mock_get_quotas: + + res = fake_req.get_response(self.controller) + body = jsonutils.loads(res.body) + abs_limits = body['limits']['absolute'] + self.assertIn('totalRAMUsed', abs_limits) + self.assertEqual(256, abs_limits['totalRAMUsed']) + self.assertEqual(1, mock_get_quotas.call_count) + + def test_no_ram_quota(self): + fake_req = self._get_index_request() + + with mock.patch.object(quota.QUOTAS, 'get_project_quotas', + return_value={}) as mock_get_quotas: + + res = fake_req.get_response(self.controller) + body = jsonutils.loads(res.body) + abs_limits = body['limits']['absolute'] + self.assertNotIn('totalRAMUsed', abs_limits) + self.assertEqual(1, mock_get_quotas.call_count) class FakeHttplibSocket(object): @@ -198,10 +347,15 @@ class LimitsViewBuilderTest(test.NoDBTestCase): def setUp(self): super(LimitsViewBuilderTest, self).setUp() self.view_builder = views.limits.ViewBuilder() + self.req = fakes.HTTPRequest.blank('/?tenant_id=None') self.rate_limits = [] - self.absolute_limits = {"metadata_items": 1, - "injected_files": 5, - "injected_file_content_bytes": 5} + patcher = self.mock_can = mock.patch('nova.context.RequestContext.can') + self.mock_can = patcher.start() + self.addCleanup(patcher.stop) + self.absolute_limits = {"metadata_items": {'limit': 1, 'in_use': 1}, + "injected_files": {'limit': 5, 'in_use': 1}, + "injected_file_content_bytes": + {'limit': 5, 'in_use': 1}} def test_build_limits(self): expected_limits = {"limits": { @@ -211,17 +365,38 @@ class LimitsViewBuilderTest(test.NoDBTestCase): "maxPersonality": 5, "maxPersonalitySize": 5}}} - output = self.view_builder.build(self.absolute_limits) + output = self.view_builder.build(self.req, self.absolute_limits) self.assertThat(output, matchers.DictMatches(expected_limits)) def test_build_limits_empty_limits(self): expected_limits = {"limits": {"rate": [], "absolute": {}}} - abs_limits = {} - output = self.view_builder.build(abs_limits) + quotas = {} + output = self.view_builder.build(self.req, quotas) self.assertThat(output, matchers.DictMatches(expected_limits)) + def test_non_admin_cannot_fetch_used_limits_for_any_other_project(self): + project_id = "123456" + user_id = "A1234" + tenant_id = "abcd" + target = { + "project_id": tenant_id, + "user_id": user_id + } + req = fakes.HTTPRequest.blank('/?tenant_id=%s' % tenant_id) + context = nova.context.RequestContext(user_id, project_id) + req.environ["nova.context"] = context + + self.mock_can.side_effect = exception.PolicyNotAuthorized( + action="os_compute_api:os-used-limits") + self.assertRaises(exception.PolicyNotAuthorized, + self.view_builder.build, + req, self.absolute_limits) + + self.mock_can.assert_called_with(ul_policies.BASE_POLICY_NAME, + target) + class LimitsPolicyEnforcementV21(test.NoDBTestCase): @@ -261,7 +436,8 @@ class LimitsControllerTestV236(BaseLimitTestSuite): } def _get_project_quotas(context, project_id, usages=True): - return {k: dict(limit=v) for k, v in absolute_limits.items()} + return {k: dict(limit=v, in_use=v // 2) + for k, v in absolute_limits.items()} with mock.patch('nova.quota.QUOTAS.get_project_quotas') as \ get_project_quotas: @@ -275,6 +451,10 @@ class LimitsControllerTestV236(BaseLimitTestSuite): "maxTotalInstances": 5, "maxTotalCores": 21, "maxTotalKeypairs": 10, + "totalRAMUsed": 256, + "totalCoresUsed": 10, + "totalInstancesUsed": 2, + }, }, } @@ -295,7 +475,8 @@ class LimitsControllerTestV239(BaseLimitTestSuite): } def _get_project_quotas(context, project_id, usages=True): - return {k: dict(limit=v) for k, v in absolute_limits.items()} + return {k: dict(limit=v, in_use=v // 2) + for k, v in absolute_limits.items()} with mock.patch('nova.quota.QUOTAS.get_project_quotas') as \ get_project_quotas: diff --git a/nova/tests/unit/api/openstack/compute/test_used_limits.py b/nova/tests/unit/api/openstack/compute/test_used_limits.py deleted file mode 100644 index fbf5d5cced..0000000000 --- a/nova/tests/unit/api/openstack/compute/test_used_limits.py +++ /dev/null @@ -1,272 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# 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 -# 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 mock -import webob - -from nova.api.openstack import api_version_request -from nova.api.openstack.compute import used_limits \ - as used_limits_v21 -from nova.api.openstack import wsgi -import nova.context -from nova import exception -from nova.policies import used_limits as ul_policies -from nova import quota -from nova import test - - -class FakeRequest(object): - def __init__(self, context, reserved=False): - self.environ = {'nova.context': context} - self.reserved = reserved - - self.api_version_request = api_version_request.min_api_version() - if reserved: - self.GET = webob.request.MultiDict({'reserved': 1}) - else: - self.GET = webob.request.MultiDict({}) - - def is_legacy_v2(self): - return False - - -class UsedLimitsTestCaseV21(test.NoDBTestCase): - used_limit_extension = "os_compute_api:os-used-limits" - include_server_group_quotas = True - - def setUp(self): - """Run before each test.""" - super(UsedLimitsTestCaseV21, self).setUp() - self._set_up_controller() - self.fake_context = nova.context.RequestContext('fake', 'fake') - - def _set_up_controller(self): - self.controller = used_limits_v21.UsedLimitsController() - patcher = self.mock_can = mock.patch('nova.context.RequestContext.can') - self.mock_can = patcher.start() - self.addCleanup(patcher.stop) - - def _do_test_used_limits(self, reserved): - fake_req = FakeRequest(self.fake_context, reserved=reserved) - obj = { - "limits": { - "rate": [], - "absolute": {}, - }, - } - res = wsgi.ResponseObject(obj) - quota_map = { - 'totalRAMUsed': 'ram', - 'totalCoresUsed': 'cores', - 'totalInstancesUsed': 'instances', - 'totalFloatingIpsUsed': 'floating_ips', - 'totalSecurityGroupsUsed': 'security_groups', - 'totalServerGroupsUsed': 'server_groups', - } - limits = {} - expected_abs_limits = [] - for display_name, q in quota_map.items(): - limits[q] = {'limit': len(display_name), - 'in_use': len(display_name) / 2, - 'reserved': 0} - if (self.include_server_group_quotas or - display_name != 'totalServerGroupsUsed'): - expected_abs_limits.append(display_name) - - def stub_get_project_quotas(context, project_id, usages=True): - return limits - - self.stub_out('nova.quota.QUOTAS.get_project_quotas', - stub_get_project_quotas) - - self.controller.index(fake_req, res) - abs_limits = res.obj['limits']['absolute'] - for limit in expected_abs_limits: - value = abs_limits[limit] - r = limits[quota_map[limit]]['reserved'] if reserved else 0 - self.assertEqual(limits[quota_map[limit]]['in_use'] + r, value) - - def test_used_limits_basic(self): - self._do_test_used_limits(False) - - 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 = webob.request.MultiDict({'tenant_id': tenant_id}) - - with mock.patch.object(quota.QUOTAS, 'get_project_quotas', - return_value={}) as mock_get_quotas: - res = wsgi.ResponseObject(obj) - self.controller.index(fake_req, res) - self.mock_can.assert_called_once_with(ul_policies.BASE_POLICY_NAME, - target) - mock_get_quotas.assert_called_once_with(self.fake_context, - tenant_id, usages=True) - - def _test_admin_can_fetch_used_limits_for_own_project(self, req_get): - project_id = "123456" - if 'tenant_id' in req_get: - project_id = req_get['tenant_id'] - - 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 = webob.request.MultiDict(req_get) - - with mock.patch.object(quota.QUOTAS, 'get_project_quotas', - return_value={}) as mock_get_quotas: - res = wsgi.ResponseObject(obj) - self.controller.index(fake_req, res) - - mock_get_quotas.assert_called_once_with(self.fake_context, - project_id, usages=True) - - def test_admin_can_fetch_used_limits_for_own_project(self): - req_get = {} - self._test_admin_can_fetch_used_limits_for_own_project(req_get) - - def test_admin_can_fetch_used_limits_for_dummy_only(self): - # for back compatible we allow additional param to be send to req.GET - # it can be removed when we add restrictions to query param later - req_get = {'dummy': 'dummy'} - self._test_admin_can_fetch_used_limits_for_own_project(req_get) - - def test_admin_can_fetch_used_limits_with_positive_int(self): - req_get = {'tenant_id': 123} - self._test_admin_can_fetch_used_limits_for_own_project(req_get) - - def test_admin_can_fetch_used_limits_with_negative_int(self): - req_get = {'tenant_id': -1} - self._test_admin_can_fetch_used_limits_for_own_project(req_get) - - def test_admin_can_fetch_used_limits_with_unkown_param(self): - req_get = {'tenant_id': '123', 'unknown': 'unknown'} - self._test_admin_can_fetch_used_limits_for_own_project(req_get) - - 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 = webob.request.MultiDict({'tenant_id': tenant_id}) - - self.mock_can.side_effect = exception.PolicyNotAuthorized( - action=self.used_limit_extension) - - res = wsgi.ResponseObject(obj) - self.assertRaises(exception.PolicyNotAuthorized, - self.controller.index, - fake_req, res) - - self.mock_can.assert_called_once_with(ul_policies.BASE_POLICY_NAME, - target) - - 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) - - with mock.patch.object(quota.QUOTAS, 'get_project_quotas', - return_value={}) as mock_get_quotas: - res = wsgi.ResponseObject(obj) - self.controller.index(fake_req, res) - - mock_get_quotas.assert_called_once_with(self.fake_context, - project_id, usages=True) - - def test_used_ram_added(self): - fake_req = FakeRequest(self.fake_context) - obj = { - "limits": { - "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}} - - with mock.patch.object(quota.QUOTAS, 'get_project_quotas', - side_effect=stub_get_project_quotas - ) as mock_get_quotas: - - self.controller.index(fake_req, res) - abs_limits = res.obj['limits']['absolute'] - self.assertIn('totalRAMUsed', abs_limits) - self.assertEqual(256, abs_limits['totalRAMUsed']) - self.assertEqual(1, mock_get_quotas.call_count) - - def test_no_ram_quota(self): - fake_req = FakeRequest(self.fake_context) - obj = { - "limits": { - "rate": [], - "absolute": {}, - }, - } - res = wsgi.ResponseObject(obj) - - with mock.patch.object(quota.QUOTAS, 'get_project_quotas', - return_value={}) as mock_get_quotas: - - self.controller.index(fake_req, res) - abs_limits = res.obj['limits']['absolute'] - self.assertNotIn('totalRAMUsed', abs_limits) - self.assertEqual(1, mock_get_quotas.call_count)