Merge "Add project_id admin filter to limits API"

This commit is contained in:
Jenkins 2017-06-19 14:08:12 +00:00 committed by Gerrit Code Review
commit 54affb4437
8 changed files with 192 additions and 19 deletions

View File

@ -27,7 +27,17 @@ class UsedLimitsController(wsgi.Controller):
def index(self, req, resp_obj):
context = req.environ['cinder.context']
if authorize(context):
quotas = QUOTAS.get_project_quotas(context, context.project_id,
params = req.params.copy()
req_version = req.api_version_request
# TODO(wangxiyuan): Support "tenant_id" here to keep the backwards
# compatibility. Remove it once we drop all support for "tenant".
if req_version.matches(None, "3.38") or not context.is_admin:
params.pop('project_id', None)
params.pop('tenant_id', None)
project_id = params.get(
'project_id', params.get('tenant_id', context.project_id))
quotas = QUOTAS.get_project_quotas(context, project_id,
usages=True)
quota_map = {

View File

@ -92,6 +92,7 @@ REST_API_VERSION_HISTORY = """
* 3.36 - Add metadata to volumes/summary response body.
* 3.37 - Support sort backup by "name".
* 3.38 - Add replication group API (Tiramisu).
* 3.39 - Add ``project_id`` admin filters support to limits.
"""
# The minimum and maximum versions of the API supported
@ -99,7 +100,7 @@ REST_API_VERSION_HISTORY = """
# minimum version of the API supported.
# Explicitly using /v1 or /v2 endpoints will still work
_MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.38"
_MAX_API_VERSION = "3.39"
_LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0"

View File

@ -330,3 +330,7 @@ user documentation.
----
Added enable_replication/disable_replication/failover_replication/
list_replication_targets for replication groups (Tiramisu).
3.39
----
Add ``project_id`` admin filters support to limits.

54
cinder/api/v3/limits.py Normal file
View File

@ -0,0 +1,54 @@
#
# 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.
"""The limits V3 api."""
from cinder.api.openstack import wsgi
from cinder.api.v2 import limits as limits_v2
from cinder.api.views import limits as limits_views
from cinder import quota
QUOTAS = quota.QUOTAS
class LimitsController(limits_v2.LimitsController):
"""Controller for accessing limits in the OpenStack API."""
def index(self, req):
"""Return all global and rate limit information."""
context = req.environ['cinder.context']
params = req.params.copy()
req_version = req.api_version_request
# TODO(wangxiyuan): Support "tenant_id" here to keep the backwards
# compatibility. Remove it once we drop all support for "tenant".
if req_version.matches(None, "3.38") or not context.is_admin:
params.pop('project_id', None)
params.pop('tenant_id', None)
project_id = params.get(
'project_id', 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()}
rate_limits = req.environ.get("cinder.limits", [])
builder = self._get_view_builder(req)
return builder.build(rate_limits, abs_limits)
def _get_view_builder(self, req):
return limits_views.ViewBuilder()
def create_resource():
return wsgi.Resource(LimitsController())

View File

@ -21,7 +21,6 @@ WSGI middleware for OpenStack Volume API.
from cinder.api import extensions
import cinder.api.openstack
from cinder.api.v2 import limits
from cinder.api.v2 import snapshot_metadata
from cinder.api.v2 import types
from cinder.api.v3 import attachments
@ -32,6 +31,7 @@ from cinder.api.v3 import group_snapshots
from cinder.api.v3 import group_specs
from cinder.api.v3 import group_types
from cinder.api.v3 import groups
from cinder.api.v3 import limits
from cinder.api.v3 import messages
from cinder.api.v3 import resource_filters
from cinder.api.v3 import snapshot_manage

View File

@ -13,9 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from cinder.api.contrib import used_limits
from cinder.api.openstack import api_version_request
from cinder.api.openstack import wsgi
from cinder import exception
from cinder import test
@ -24,21 +26,37 @@ from cinder.tests.unit import fake_constants as fake
class FakeRequest(object):
def __init__(self, context):
def __init__(self, context, filter=None, api_version='2.0'):
self.environ = {'cinder.context': context}
self.params = filter or {}
self.api_version_request = api_version_request.APIVersionRequest(
api_version)
@ddt.ddt
class UsedLimitsTestCase(test.TestCase):
def setUp(self):
"""Run before each test."""
super(UsedLimitsTestCase, self).setUp()
self.controller = used_limits.UsedLimitsController()
@ddt.data(('2.0', False), ('3.38', True), ('3.38', False), ('3.39', True),
('3.39', False))
@mock.patch('cinder.quota.QUOTAS.get_project_quotas')
@mock.patch('cinder.policy.enforce')
def test_used_limits(self, _mock_policy_enforce, _mock_get_project_quotas):
def test_used_limits(self, ver_project, _mock_policy_enforce,
_mock_get_project_quotas):
version, has_project = ver_project
fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
fake.PROJECT_ID))
fake.PROJECT_ID,
is_admin=True),
api_version=version)
if has_project:
fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
fake.PROJECT_ID,
is_admin=True),
filter={'project_id': fake.UUID1},
api_version=version)
obj = {
"limits": {
"rate": [],
@ -46,26 +64,39 @@ class UsedLimitsTestCase(test.TestCase):
},
}
res = wsgi.ResponseObject(obj)
quota_map = {
'totalVolumesUsed': 'volumes',
'totalGigabytesUsed': 'gigabytes',
'totalSnapshotsUsed': 'snapshots',
}
limits = {}
for display_name, q in quota_map.items():
limits[q] = {'limit': 2,
'in_use': 1}
_mock_get_project_quotas.return_value = limits
def get_project_quotas(context, project_id, quota_class=None,
defaults=True, usages=True):
if project_id == fake.UUID1:
return {"gigabytes": {'limit': 5, 'in_use': 1}}
return {"gigabytes": {'limit': 10, 'in_use': 2}}
_mock_get_project_quotas.side_effect = get_project_quotas
# allow user to access used limits
_mock_policy_enforce.return_value = None
self.controller.index(fake_req, res)
abs_limits = res.obj['limits']['absolute']
for used_limit, value in abs_limits.items():
self.assertEqual(value,
limits[quota_map[used_limit]]['in_use'])
# if admin, only 3.39 and req contains project_id filter, cinder
# returns the specified project's quota.
if version == '3.39' and has_project:
self.assertEqual(1, abs_limits['totalGigabytesUsed'])
else:
self.assertEqual(2, abs_limits['totalGigabytesUsed'])
fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
fake.PROJECT_ID),
api_version=version)
if has_project:
fake_req = FakeRequest(fakes.FakeRequestContext(fake.USER_ID,
fake.PROJECT_ID),
filter={'project_id': fake.UUID1},
api_version=version)
# if non-admin, cinder always returns self quota.
self.controller.index(fake_req, res)
abs_limits = res.obj['limits']['absolute']
self.assertEqual(2, abs_limits['totalGigabytesUsed'])
obj = {
"limits": {

View File

@ -0,0 +1,70 @@
# Copyright 2017 Huawei Corporation
# 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 ddt
import mock
from cinder.api.openstack import api_version_request as api_version
from cinder.api.v3 import limits
from cinder import test
from cinder.tests.unit.api import fakes
from cinder.tests.unit import fake_constants as fake
@ddt.ddt
class LimitsControllerTest(test.TestCase):
def setUp(self):
super(LimitsControllerTest, self).setUp()
self.controller = limits.LimitsController()
@ddt.data(('3.38', True), ('3.38', False), ('3.39', True), ('3.39', False))
@mock.patch('cinder.quota.VolumeTypeQuotaEngine.get_project_quotas')
def test_get_limit_with_project_id(self, ver_project, mock_get_quotas):
max_ver, has_project = ver_project
req = fakes.HTTPRequest.blank('/v3/limits', use_admin_context=True)
if has_project:
req = fakes.HTTPRequest.blank(
'/v3/limits?project_id=%s' % fake.UUID1,
use_admin_context=True)
req.api_version_request = api_version.APIVersionRequest(max_ver)
def get_project_quotas(context, project_id, quota_class=None,
defaults=True, usages=True):
if project_id == fake.UUID1:
return {"gigabytes": {'limit': 5}}
return {"gigabytes": {'limit': 10}}
mock_get_quotas.side_effect = get_project_quotas
resp_dict = self.controller.index(req)
# if admin, only 3.39 and req contains project_id filter, cinder
# returns the specified project's quota.
if max_ver == '3.39' and has_project:
self.assertEqual(
5, resp_dict['limits']['absolute']['maxTotalVolumeGigabytes'])
else:
self.assertEqual(
10, resp_dict['limits']['absolute']['maxTotalVolumeGigabytes'])
# if non-admin, cinder always returns self quota.
req = fakes.HTTPRequest.blank('/v3/limits', use_admin_context=False)
if has_project:
req = fakes.HTTPRequest.blank(
'/v3/limits?project_id=%s' % fake.UUID1,
use_admin_context=False)
req.api_version_request = api_version.APIVersionRequest(max_ver)
resp_dict = self.controller.index(req)
self.assertEqual(
10, resp_dict['limits']['absolute']['maxTotalVolumeGigabytes'])

View File

@ -0,0 +1,3 @@
---
features:
- Supported ``project_id`` admin filters to limits API.