summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-06-19 14:08:12 +0000
committerGerrit Code Review <review@openstack.org>2017-06-19 14:08:12 +0000
commit54affb44379b02516bcdaf4c67b4c2b89cb542ad (patch)
tree081f032fdcedfd9f6b33cc51fb48bda51b59c7b7
parentbfdd36a57236b021c4fe436d582cdbbd6588c165 (diff)
parent4a2448bd15a0191df8bb4710870e2e0b5750278a (diff)
Merge "Add project_id admin filter to limits API"
-rw-r--r--cinder/api/contrib/used_limits.py12
-rw-r--r--cinder/api/openstack/api_version_request.py3
-rw-r--r--cinder/api/openstack/rest_api_version_history.rst4
-rw-r--r--cinder/api/v3/limits.py54
-rw-r--r--cinder/api/v3/router.py2
-rw-r--r--cinder/tests/unit/api/contrib/test_used_limits.py63
-rw-r--r--cinder/tests/unit/api/v3/test_limits.py70
-rw-r--r--releasenotes/notes/support-project-id-filter-for-limit-bc5d49e239baee2a.yaml3
8 files changed, 192 insertions, 19 deletions
diff --git a/cinder/api/contrib/used_limits.py b/cinder/api/contrib/used_limits.py
index 2665ed2..1c638a2 100644
--- a/cinder/api/contrib/used_limits.py
+++ b/cinder/api/contrib/used_limits.py
@@ -27,7 +27,17 @@ class UsedLimitsController(wsgi.Controller):
27 def index(self, req, resp_obj): 27 def index(self, req, resp_obj):
28 context = req.environ['cinder.context'] 28 context = req.environ['cinder.context']
29 if authorize(context): 29 if authorize(context):
30 quotas = QUOTAS.get_project_quotas(context, context.project_id, 30 params = req.params.copy()
31 req_version = req.api_version_request
32
33 # TODO(wangxiyuan): Support "tenant_id" here to keep the backwards
34 # compatibility. Remove it once we drop all support for "tenant".
35 if req_version.matches(None, "3.38") or not context.is_admin:
36 params.pop('project_id', None)
37 params.pop('tenant_id', None)
38 project_id = params.get(
39 'project_id', params.get('tenant_id', context.project_id))
40 quotas = QUOTAS.get_project_quotas(context, project_id,
31 usages=True) 41 usages=True)
32 42
33 quota_map = { 43 quota_map = {
diff --git a/cinder/api/openstack/api_version_request.py b/cinder/api/openstack/api_version_request.py
index 3d71906..1289b1f 100644
--- a/cinder/api/openstack/api_version_request.py
+++ b/cinder/api/openstack/api_version_request.py
@@ -92,6 +92,7 @@ REST_API_VERSION_HISTORY = """
92 * 3.36 - Add metadata to volumes/summary response body. 92 * 3.36 - Add metadata to volumes/summary response body.
93 * 3.37 - Support sort backup by "name". 93 * 3.37 - Support sort backup by "name".
94 * 3.38 - Add replication group API (Tiramisu). 94 * 3.38 - Add replication group API (Tiramisu).
95 * 3.39 - Add ``project_id`` admin filters support to limits.
95""" 96"""
96 97
97# The minimum and maximum versions of the API supported 98# The minimum and maximum versions of the API supported
@@ -99,7 +100,7 @@ REST_API_VERSION_HISTORY = """
99# minimum version of the API supported. 100# minimum version of the API supported.
100# Explicitly using /v1 or /v2 endpoints will still work 101# Explicitly using /v1 or /v2 endpoints will still work
101_MIN_API_VERSION = "3.0" 102_MIN_API_VERSION = "3.0"
102_MAX_API_VERSION = "3.38" 103_MAX_API_VERSION = "3.39"
103_LEGACY_API_VERSION1 = "1.0" 104_LEGACY_API_VERSION1 = "1.0"
104_LEGACY_API_VERSION2 = "2.0" 105_LEGACY_API_VERSION2 = "2.0"
105 106
diff --git a/cinder/api/openstack/rest_api_version_history.rst b/cinder/api/openstack/rest_api_version_history.rst
index 987989b..22aeb89 100644
--- a/cinder/api/openstack/rest_api_version_history.rst
+++ b/cinder/api/openstack/rest_api_version_history.rst
@@ -330,3 +330,7 @@ user documentation.
330---- 330----
331 Added enable_replication/disable_replication/failover_replication/ 331 Added enable_replication/disable_replication/failover_replication/
332 list_replication_targets for replication groups (Tiramisu). 332 list_replication_targets for replication groups (Tiramisu).
333
3343.39
335----
336 Add ``project_id`` admin filters support to limits.
diff --git a/cinder/api/v3/limits.py b/cinder/api/v3/limits.py
new file mode 100644
index 0000000..cc739a3
--- /dev/null
+++ b/cinder/api/v3/limits.py
@@ -0,0 +1,54 @@
1#
2# Licensed under the Apache License, Version 2.0 (the "License"); you may
3# not use this file except in compliance with the License. You may obtain
4# a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11# License for the specific language governing permissions and limitations
12# under the License.
13
14"""The limits V3 api."""
15
16from cinder.api.openstack import wsgi
17from cinder.api.v2 import limits as limits_v2
18from cinder.api.views import limits as limits_views
19from cinder import quota
20
21QUOTAS = quota.QUOTAS
22
23
24class LimitsController(limits_v2.LimitsController):
25 """Controller for accessing limits in the OpenStack API."""
26
27 def index(self, req):
28 """Return all global and rate limit information."""
29 context = req.environ['cinder.context']
30 params = req.params.copy()
31 req_version = req.api_version_request
32
33 # TODO(wangxiyuan): Support "tenant_id" here to keep the backwards
34 # compatibility. Remove it once we drop all support for "tenant".
35 if req_version.matches(None, "3.38") or not context.is_admin:
36 params.pop('project_id', None)
37 params.pop('tenant_id', None)
38 project_id = params.get(
39 'project_id', params.get('tenant_id', context.project_id))
40
41 quotas = QUOTAS.get_project_quotas(context, project_id,
42 usages=False)
43 abs_limits = {k: v['limit'] for k, v in quotas.items()}
44 rate_limits = req.environ.get("cinder.limits", [])
45
46 builder = self._get_view_builder(req)
47 return builder.build(rate_limits, abs_limits)
48
49 def _get_view_builder(self, req):
50 return limits_views.ViewBuilder()
51
52
53def create_resource():
54 return wsgi.Resource(LimitsController())
diff --git a/cinder/api/v3/router.py b/cinder/api/v3/router.py
index 53cadbe..f45e337 100644
--- a/cinder/api/v3/router.py
+++ b/cinder/api/v3/router.py
@@ -21,7 +21,6 @@ WSGI middleware for OpenStack Volume API.
21 21
22from cinder.api import extensions 22from cinder.api import extensions
23import cinder.api.openstack 23import cinder.api.openstack
24from cinder.api.v2 import limits
25from cinder.api.v2 import snapshot_metadata 24from cinder.api.v2 import snapshot_metadata
26from cinder.api.v2 import types 25from cinder.api.v2 import types
27from cinder.api.v3 import attachments 26from cinder.api.v3 import attachments
@@ -32,6 +31,7 @@ from cinder.api.v3 import group_snapshots
32from cinder.api.v3 import group_specs 31from cinder.api.v3 import group_specs
33from cinder.api.v3 import group_types 32from cinder.api.v3 import group_types
34from cinder.api.v3 import groups 33from cinder.api.v3 import groups
34from cinder.api.v3 import limits
35from cinder.api.v3 import messages 35from cinder.api.v3 import messages
36from cinder.api.v3 import resource_filters 36from cinder.api.v3 import resource_filters
37from cinder.api.v3 import snapshot_manage 37from cinder.api.v3 import snapshot_manage
diff --git a/cinder/tests/unit/api/contrib/test_used_limits.py b/cinder/tests/unit/api/contrib/test_used_limits.py
index 6396798..cca4323 100644
--- a/cinder/tests/unit/api/contrib/test_used_limits.py
+++ b/cinder/tests/unit/api/contrib/test_used_limits.py
@@ -13,9 +13,11 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16import ddt
16import mock 17import mock
17 18
18from cinder.api.contrib import used_limits 19from cinder.api.contrib import used_limits
20from cinder.api.openstack import api_version_request
19from cinder.api.openstack import wsgi 21from cinder.api.openstack import wsgi
20from cinder import exception 22from cinder import exception
21from cinder import test 23from cinder import test
@@ -24,21 +26,37 @@ from cinder.tests.unit import fake_constants as fake
24 26
25 27
26class FakeRequest(object): 28class FakeRequest(object):
27 def __init__(self, context): 29 def __init__(self, context, filter=None, api_version='2.0'):
28 self.environ = {'cinder.context': context} 30 self.environ = {'cinder.context': context}
31 self.params = filter or {}
32 self.api_version_request = api_version_request.APIVersionRequest(
33 api_version)
29 34
30 35
36@ddt.ddt
31class UsedLimitsTestCase(test.TestCase): 37class UsedLimitsTestCase(test.TestCase):
32 def setUp(self): 38 def setUp(self):
33 """Run before each test.""" 39 """Run before each test."""
34 super(UsedLimitsTestCase, self).setUp() 40 super(UsedLimitsTestCase, self).setUp()
35 self.controller = used_limits.UsedLimitsController() 41 self.controller = used_limits.UsedLimitsController()
36 42
43 @ddt.data(('2.0', False), ('3.38', True), ('3.38', False), ('3.39', True),
44 ('3.39', False))
37 @mock.patch('cinder.quota.QUOTAS.get_project_quotas') 45 @mock.patch('cinder.quota.QUOTAS.get_project_quotas')
38 @mock.patch('cinder.policy.enforce') 46 @mock.patch('cinder.policy.enforce')
39 def test_used_limits(self, _mock_policy_enforce, _mock_get_project_quotas): 47 def test_used_limits(self, ver_project, _mock_policy_enforce,
48 _mock_get_project_quotas):
49 version, has_project = ver_project
40 fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID, 50 fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
41 fake.PROJECT_ID)) 51 fake.PROJECT_ID,
52 is_admin=True),
53 api_version=version)
54 if has_project:
55 fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
56 fake.PROJECT_ID,
57 is_admin=True),
58 filter={'project_id': fake.UUID1},
59 api_version=version)
42 obj = { 60 obj = {
43 "limits": { 61 "limits": {
44 "rate": [], 62 "rate": [],
@@ -46,26 +64,39 @@ class UsedLimitsTestCase(test.TestCase):
46 }, 64 },
47 } 65 }
48 res = wsgi.ResponseObject(obj) 66 res = wsgi.ResponseObject(obj)
49 quota_map = {
50 'totalVolumesUsed': 'volumes',
51 'totalGigabytesUsed': 'gigabytes',
52 'totalSnapshotsUsed': 'snapshots',
53 }
54 67
55 limits = {} 68 def get_project_quotas(context, project_id, quota_class=None,
56 for display_name, q in quota_map.items(): 69 defaults=True, usages=True):
57 limits[q] = {'limit': 2, 70 if project_id == fake.UUID1:
58 'in_use': 1} 71 return {"gigabytes": {'limit': 5, 'in_use': 1}}
59 _mock_get_project_quotas.return_value = limits 72 return {"gigabytes": {'limit': 10, 'in_use': 2}}
60 73
74 _mock_get_project_quotas.side_effect = get_project_quotas
61 # allow user to access used limits 75 # allow user to access used limits
62 _mock_policy_enforce.return_value = None 76 _mock_policy_enforce.return_value = None
63 77
64 self.controller.index(fake_req, res) 78 self.controller.index(fake_req, res)
65 abs_limits = res.obj['limits']['absolute'] 79 abs_limits = res.obj['limits']['absolute']
66 for used_limit, value in abs_limits.items(): 80
67 self.assertEqual(value, 81 # if admin, only 3.39 and req contains project_id filter, cinder
68 limits[quota_map[used_limit]]['in_use']) 82 # returns the specified project's quota.
83 if version == '3.39' and has_project:
84 self.assertEqual(1, abs_limits['totalGigabytesUsed'])
85 else:
86 self.assertEqual(2, abs_limits['totalGigabytesUsed'])
87
88 fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
89 fake.PROJECT_ID),
90 api_version=version)
91 if has_project:
92 fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
93 fake.PROJECT_ID),
94 filter={'project_id': fake.UUID1},
95 api_version=version)
96 # if non-admin, cinder always returns self quota.
97 self.controller.index(fake_req, res)
98 abs_limits = res.obj['limits']['absolute']
99 self.assertEqual(2, abs_limits['totalGigabytesUsed'])
69 100
70 obj = { 101 obj = {
71 "limits": { 102 "limits": {
diff --git a/cinder/tests/unit/api/v3/test_limits.py b/cinder/tests/unit/api/v3/test_limits.py
new file mode 100644
index 0000000..14aa726
--- /dev/null
+++ b/cinder/tests/unit/api/v3/test_limits.py
@@ -0,0 +1,70 @@
1# Copyright 2017 Huawei Corporation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import ddt
17import mock
18
19from cinder.api.openstack import api_version_request as api_version
20from cinder.api.v3 import limits
21from cinder import test
22from cinder.tests.unit.api import fakes
23from cinder.tests.unit import fake_constants as fake
24
25
26@ddt.ddt
27class LimitsControllerTest(test.TestCase):
28 def setUp(self):
29 super(LimitsControllerTest, self).setUp()
30 self.controller = limits.LimitsController()
31
32 @ddt.data(('3.38', True), ('3.38', False), ('3.39', True), ('3.39', False))
33 @mock.patch('cinder.quota.VolumeTypeQuotaEngine.get_project_quotas')
34 def test_get_limit_with_project_id(self, ver_project, mock_get_quotas):
35 max_ver, has_project = ver_project
36 req = fakes.HTTPRequest.blank('/v3/limits', use_admin_context=True)
37 if has_project:
38 req = fakes.HTTPRequest.blank(
39 '/v3/limits?project_id=%s' % fake.UUID1,
40 use_admin_context=True)
41 req.api_version_request = api_version.APIVersionRequest(max_ver)
42
43 def get_project_quotas(context, project_id, quota_class=None,
44 defaults=True, usages=True):
45 if project_id == fake.UUID1:
46 return {"gigabytes": {'limit': 5}}
47 return {"gigabytes": {'limit': 10}}
48 mock_get_quotas.side_effect = get_project_quotas
49
50 resp_dict = self.controller.index(req)
51 # if admin, only 3.39 and req contains project_id filter, cinder
52 # returns the specified project's quota.
53 if max_ver == '3.39' and has_project:
54 self.assertEqual(
55 5, resp_dict['limits']['absolute']['maxTotalVolumeGigabytes'])
56 else:
57 self.assertEqual(
58 10, resp_dict['limits']['absolute']['maxTotalVolumeGigabytes'])
59
60 # if non-admin, cinder always returns self quota.
61 req = fakes.HTTPRequest.blank('/v3/limits', use_admin_context=False)
62 if has_project:
63 req = fakes.HTTPRequest.blank(
64 '/v3/limits?project_id=%s' % fake.UUID1,
65 use_admin_context=False)
66 req.api_version_request = api_version.APIVersionRequest(max_ver)
67 resp_dict = self.controller.index(req)
68
69 self.assertEqual(
70 10, resp_dict['limits']['absolute']['maxTotalVolumeGigabytes'])
diff --git a/releasenotes/notes/support-project-id-filter-for-limit-bc5d49e239baee2a.yaml b/releasenotes/notes/support-project-id-filter-for-limit-bc5d49e239baee2a.yaml
new file mode 100644
index 0000000..88a13e6
--- /dev/null
+++ b/releasenotes/notes/support-project-id-filter-for-limit-bc5d49e239baee2a.yaml
@@ -0,0 +1,3 @@
1---
2features:
3 - Supported ``project_id`` admin filters to limits API.