From 30d9144aacdfae81b3ac8a18e96752988d86ddb4 Mon Sep 17 00:00:00 2001 From: Valeriy Ponomaryov Date: Mon, 19 Oct 2015 14:59:08 +0300 Subject: [PATCH] Port used limits to core API Extension 'used limits' was extending core 'limits' API with used resources information. So, provide such information in core API and remove extension completely. It does not require neither update of API router nor bump of microversion. Partially implements bp ext-to-core Change-Id: I0ff71fbd0281eb2e34a9acbfc72ff8c14390e8d9 --- manila/api/contrib/used_limits.py | 63 -------- manila/api/v1/limits.py | 9 +- manila/api/views/limits.py | 35 +++-- manila/tests/api/contrib/test_used_limits.py | 62 -------- manila/tests/api/v1/test_limits.py | 136 ++++++++++++------ manila/tests/policy.json | 2 - manila_tempest_tests/tests/api/test_limits.py | 4 + 7 files changed, 127 insertions(+), 184 deletions(-) delete mode 100644 manila/api/contrib/used_limits.py delete mode 100644 manila/tests/api/contrib/test_used_limits.py diff --git a/manila/api/contrib/used_limits.py b/manila/api/contrib/used_limits.py deleted file mode 100644 index b1b0a75a57..0000000000 --- a/manila/api/contrib/used_limits.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import six - -from manila.api import extensions -from manila.api.openstack import wsgi -from manila import quota - -QUOTAS = quota.QUOTAS - -authorize = extensions.extension_authorizer('limits', 'used_limits') - - -class UsedLimitsController(wsgi.Controller): - - @wsgi.extends - def index(self, req, resp_obj): - context = req.environ['manila.context'] - authorize(context) - - quotas = QUOTAS.get_project_quotas(context, - context.project_id, - usages=True) - - quota_map = { - 'totalSharesUsed': 'shares', - 'totalShareSnapshotsUsed': 'snapshots', - 'totalShareNetworksUsed': 'share_networks', - 'totalShareGigabytesUsed': 'gigabytes', - 'totalSnapshotGigabytesUsed': 'snapshot_gigabytes', - } - - used_limits = {} - for display_name, quota_name in six.iteritems(quota_map): - if quota_name in quotas: - used_limits[display_name] = quotas[quota_name]['in_use'] - - resp_obj.obj['limits']['absolute'].update(used_limits) - - -class Used_limits(extensions.ExtensionDescriptor): - """Provide data on limited resources that are being used.""" - - name = "UsedLimits" - alias = 'os-used-limits' - updated = "2014-03-27T00:00:00+00:00" - - def get_controller_extensions(self): - controller = UsedLimitsController() - extension = extensions.ControllerExtension(self, 'limits', controller) - return [extension] diff --git a/manila/api/v1/limits.py b/manila/api/v1/limits.py index 153fc0786f..eaaee175f8 100644 --- a/manila/api/v1/limits.py +++ b/manila/api/v1/limits.py @@ -45,15 +45,18 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 -class LimitsController(object): +class LimitsController(wsgi.Controller): """Controller for accessing limits in the OpenStack API.""" def index(self, req): """Return all global and rate limit information.""" context = req.environ['manila.context'] quotas = QUOTAS.get_project_quotas(context, context.project_id, - usages=False) - abs_limits = dict((k, v['limit']) for k, v in quotas.items()) + usages=True) + abs_limits = {'in_use': {}, 'limit': {}} + for k, v in quotas.items(): + abs_limits['limit'][k] = v['limit'] + abs_limits['in_use'][k] = v['in_use'] rate_limits = req.environ.get("manila.limits", []) builder = self._get_view_builder(req) diff --git a/manila/api/views/limits.py b/manila/api/views/limits.py index 536baf368f..0d03ad9c85 100644 --- a/manila/api/views/limits.py +++ b/manila/api/views/limits.py @@ -16,7 +16,6 @@ import datetime from oslo_utils import timeutils -import six class ViewBuilder(object): @@ -36,24 +35,34 @@ class ViewBuilder(object): return output def _build_absolute_limits(self, absolute_limits): - """Builder for absolute limits + """Builder for absolute limits. absolute_limits should be given as a dict of limits. - For example: {"ram": 512, "gigabytes": 1024}. - + For example: {"limit": {"shares": 10, "gigabytes": 1024}, + "in_use": {"shares": 8, "gigabytes": 256}}. """ limit_names = { - "gigabytes": ["maxTotalShareGigabytes"], - "snapshot_gigabytes": ["maxTotalSnapshotGigabytes"], - "shares": ["maxTotalShares"], - "snapshots": ["maxTotalShareSnapshots"], - "share_networks": ["maxTotalShareNetworks"], + "limit": { + "gigabytes": ["maxTotalShareGigabytes"], + "snapshot_gigabytes": ["maxTotalSnapshotGigabytes"], + "shares": ["maxTotalShares"], + "snapshots": ["maxTotalShareSnapshots"], + "share_networks": ["maxTotalShareNetworks"], + }, + "in_use": { + "shares": ["totalSharesUsed"], + "snapshots": ["totalShareSnapshotsUsed"], + "share_networks": ["totalShareNetworksUsed"], + "gigabytes": ["totalShareGigabytesUsed"], + "snapshot_gigabytes": ["totalSnapshotGigabytesUsed"], + }, } limits = {} - for name, value in six.iteritems(absolute_limits): - if name in limit_names and value is not None: - for name in limit_names[name]: - limits[name] = value + for mapping_key in limit_names.keys(): + for k, v in absolute_limits.get(mapping_key, {}).items(): + if k in limit_names.get(mapping_key, []) and v is not None: + for name in limit_names[mapping_key][k]: + limits[name] = v return limits def _build_rate_limits(self, rate_limits): diff --git a/manila/tests/api/contrib/test_used_limits.py b/manila/tests/api/contrib/test_used_limits.py deleted file mode 100644 index 7649c5e49b..0000000000 --- a/manila/tests/api/contrib/test_used_limits.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2014 Mirantis Inc. -# 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 six - -from manila.api.contrib import used_limits -from manila.api.openstack import wsgi -from manila import quota -from manila import test -from manila.tests.api import fakes - - -class FakeRequest(object): - def __init__(self, context): - self.environ = {'manila.context': context} - - -class UsedLimitsTestCase(test.TestCase): - - def setUp(self): - """Run before each test.""" - super(UsedLimitsTestCase, self).setUp() - self.controller = used_limits.UsedLimitsController() - - def test_used_limits(self): - fake_req = FakeRequest(fakes.FakeRequestContext('fake', 'fake')) - obj = {"limits": {"rate": [], "absolute": {}}} - res = wsgi.ResponseObject(obj) - quota_map = { - 'totalSharesUsed': 'shares', - 'totalShareSnapshotsUsed': 'snapshots', - 'totalShareNetworksUsed': 'share_networks', - 'totalShareGigabytesUsed': 'gigabytes', - } - limits = {} - for display_name, q in six.iteritems(quota_map): - limits[q] = {'limit': 2, 'in_use': 1, } - - def stub_get_project_quotas(*args, **kwargs): - return limits - - with mock.patch.object(quota.QUOTAS, 'get_project_quotas', - mock.Mock(side_effect=stub_get_project_quotas)): - - self.controller.index(fake_req, res) - abs_limits = res.obj['limits']['absolute'] - for used_limit, value in six.iteritems(abs_limits): - self.assertEqual(limits[quota_map[used_limit]]['in_use'], - value) diff --git a/manila/tests/api/v1/test_limits.py b/manila/tests/api/v1/test_limits.py index 339a0f3a57..1323982fa1 100644 --- a/manila/tests/api/v1/test_limits.py +++ b/manila/tests/api/v1/test_limits.py @@ -32,9 +32,9 @@ from manila import test TEST_LIMITS = [ limits.Limit("GET", "/delayed", "^/delayed", 1, limits.PER_MINUTE), limits.Limit("POST", "*", ".*", 7, limits.PER_MINUTE), - limits.Limit("POST", "/volumes", "^/volumes", 3, limits.PER_MINUTE), + limits.Limit("POST", "/shares", "^/shares", 3, limits.PER_MINUTE), limits.Limit("PUT", "*", "", 10, limits.PER_MINUTE), - limits.Limit("PUT", "/volumes", "^/volumes", 5, limits.PER_MINUTE), + limits.Limit("PUT", "/shares", "^/shares", 5, limits.PER_MINUTE), ] NS = { 'atom': 'http://www.w3.org/2005/Atom', @@ -52,8 +52,13 @@ class BaseLimitTestSuite(test.TestCase): self.absolute_limits = {} def stub_get_project_quotas(context, project_id, usages=True): - return dict((k, dict(limit=v)) - for k, v in self.absolute_limits.items()) + quotas = {} + for mapping_key in ('limit', 'in_use'): + for k, v in self.absolute_limits.get(mapping_key, {}).items(): + if k not in quotas: + quotas[k] = {} + quotas[k].update({mapping_key: v}) + return quotas self.mock_object(manila.quota.QUOTAS, "get_project_quotas", stub_get_project_quotas) @@ -112,9 +117,20 @@ class LimitsControllerTest(BaseLimitTestSuite): request = self._get_index_request() request = self._populate_limits(request) self.absolute_limits = { - 'gigabytes': 512, - 'shares': 5, - 'snapshots': 5 + 'limit': { + 'shares': 11, + 'gigabytes': 22, + 'snapshots': 33, + 'snapshot_gigabytes': 44, + 'share_networks': 55, + }, + 'in_use': { + 'shares': 3, + 'gigabytes': 4, + 'snapshots': 5, + 'snapshot_gigabytes': 6, + 'share_networks': 7, + }, } response = request.get_response(self.controller) expected = { @@ -155,10 +171,18 @@ class LimitsControllerTest(BaseLimitTestSuite): }, ], - "absolute": {"maxTotalShareGigabytes": 512, - "maxTotalShares": 5, - "maxTotalShareSnapshots": 5, - }, + "absolute": { + "totalSharesUsed": 3, + "totalShareGigabytesUsed": 4, + "totalShareSnapshotsUsed": 5, + "totalSnapshotGigabytesUsed": 6, + "totalShareNetworksUsed": 7, + "maxTotalShares": 11, + "maxTotalShareGigabytes": 22, + "maxTotalShareSnapshots": 33, + "maxTotalSnapshotGigabytes": 44, + "maxTotalShareNetworks": 55, + }, }, } body = jsonutils.loads(response.body) @@ -222,7 +246,10 @@ class LimitsControllerTest(BaseLimitTestSuite): self.assertEqual(expected, body['limits']['absolute']) def test_index_ignores_extra_absolute_limits_json(self): - self.absolute_limits = {'unknown_limit': 9001} + self.absolute_limits = { + 'in_use': {'unknown_limit': 9000}, + 'limit': {'unknown_limit': 9001}, + } self._test_index_absolute_limits_json({}) @@ -450,7 +477,7 @@ class LimiterTest(BaseLimitTestSuite): """ # First 6 requests on PUT /volumes expected = [None] * 5 + [12.0] - results = list(self._check(6, "PUT", "/volumes")) + results = list(self._check(6, "PUT", "/shares")) self.assertEqual(expected, results) # Next 5 request on PUT /anything @@ -734,47 +761,74 @@ class LimitsViewBuilderTest(test.TestCase): "remaining": 2, "unit": "MINUTE", "resetTime": 1311272226}, - {"URI": "*/volumes", - "regex": "^/volumes", + {"URI": "*/shares", + "regex": "^/shares", "value": 50, "verb": "POST", "remaining": 10, "unit": "DAY", "resetTime": 1311272226}] - self.absolute_limits = {"shares": 1, - "gigabytes": 5, - "snapshots": 5} + self.absolute_limits = { + "limit": { + "shares": 111, + "gigabytes": 222, + "snapshots": 333, + "snapshot_gigabytes": 444, + "share_networks": 555, + }, + "in_use": { + "shares": 65, + "gigabytes": 76, + "snapshots": 87, + "snapshot_gigabytes": 98, + "share_networks": 107, + }, + } def test_build_limits(self): tdate = "2011-07-21T18:17:06Z" - expected_limits = \ - {"limits": {"rate": [{"uri": "*", - "regex": ".*", - "limit": [{"value": 10, - "verb": "POST", - "remaining": 2, - "unit": "MINUTE", - "next-available": tdate}]}, - {"uri": "*/volumes", - "regex": "^/volumes", - "limit": [{"value": 50, - "verb": "POST", - "remaining": 10, - "unit": "DAY", - "next-available": tdate}]}], - "absolute": {"maxTotalShareGigabytes": 5, - "maxTotalShares": 1, - "maxTotalShareSnapshots": 5}}} + expected_limits = { + "limits": { + "rate": [ + {"uri": "*", + "regex": ".*", + "limit": [{"value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": tdate}]}, + {"uri": "*/shares", + "regex": "^/shares", + "limit": [{"value": 50, + "verb": "POST", + "remaining": 10, + "unit": "DAY", + "next-available": tdate}]} + ], + "absolute": { + "totalSharesUsed": 65, + "totalShareGigabytesUsed": 76, + "totalShareSnapshotsUsed": 87, + "totalSnapshotGigabytesUsed": 98, + "totalShareNetworksUsed": 107, + "maxTotalShares": 111, + "maxTotalShareGigabytes": 222, + "maxTotalShareSnapshots": 333, + "maxTotalSnapshotGigabytes": 444, + "maxTotalShareNetworks": 555, + } + } + } output = self.view_builder.build(self.rate_limits, self.absolute_limits) - self.assertDictMatch(output, expected_limits) + self.assertDictMatch(expected_limits, output) def test_build_limits_empty_limits(self): - expected_limits = {"limits": {"rate": [], - "absolute": {}}} - + expected_limits = {"limits": {"rate": [], "absolute": {}}} abs_limits = {} rate_limits = [] + output = self.view_builder.build(rate_limits, abs_limits) - self.assertDictMatch(output, expected_limits) + + self.assertDictMatch(expected_limits, output) diff --git a/manila/tests/policy.json b/manila/tests/policy.json index 81600eef02..63f5f70fef 100644 --- a/manila/tests/policy.json +++ b/manila/tests/policy.json @@ -63,8 +63,6 @@ "security_service:index": "", "security_service:get_all_security_services": "rule:admin_api", - "limits_extension:used_limits": "", - "scheduler_stats:pools:index": "rule:admin_api", "scheduler_stats:pools:detail": "rule:admin_api", diff --git a/manila_tempest_tests/tests/api/test_limits.py b/manila_tempest_tests/tests/api/test_limits.py index 044d6010c5..5046b30556 100644 --- a/manila_tempest_tests/tests/api/test_limits.py +++ b/manila_tempest_tests/tests/api/test_limits.py @@ -35,10 +35,12 @@ class ShareLimitsTest(base.BaseSharesTest): "maxTotalShares", "maxTotalShareSnapshots", "maxTotalShareNetworks", + "maxTotalSnapshotGigabytes", "totalSharesUsed", "totalShareSnapshotsUsed", "totalShareNetworksUsed", "totalShareGigabytesUsed", + "totalSnapshotGigabytesUsed", ] [self.assertIn(key, limits["absolute"].keys()) for key in abs_keys] @@ -54,7 +56,9 @@ class ShareLimitsTest(base.BaseSharesTest): self.assertGreater(int(abs_l["maxTotalShares"]), -2) self.assertGreater(int(abs_l["maxTotalShareSnapshots"]), -2) self.assertGreater(int(abs_l["maxTotalShareNetworks"]), -2) + self.assertGreater(int(abs_l["maxTotalSnapshotGigabytes"]), -2) self.assertGreater(int(abs_l["totalSharesUsed"]), -2) self.assertGreater(int(abs_l["totalShareSnapshotsUsed"]), -2) self.assertGreater(int(abs_l["totalShareNetworksUsed"]), -2) self.assertGreater(int(abs_l["totalShareGigabytesUsed"]), -2) + self.assertGreater(int(abs_l["totalSnapshotGigabytesUsed"]), -2)