Add the ability to check the tenant quota in detail
Now the quota-set API only returned single attribute 'limit', this change intends to add a new API 'quota-sets/{project_id}/detail' to retrieve more info with attributes 'in_use', 'limit', 'reserved'. APIImpact Implements: blueprint admin-check-tenant-quota-usage Depends-On: Ie0eb7d32b7b032ffdb7f7dd47f68841211e7d7a6 Change-Id: I499b099a3ba7704a2108cd15f80ff507e24b7cd0
This commit is contained in:
parent
f427dfe00b
commit
7275aafc9e
|
@ -2124,6 +2124,14 @@ quota_gigabytes:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
quota_gigabytes_detail:
|
||||||
|
description: |
|
||||||
|
The limit, in_use, reserved number of gigabytes allowed
|
||||||
|
for each tenant.
|
||||||
|
in: body
|
||||||
|
min_version: 2.25
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
quota_gigabytes_request:
|
quota_gigabytes_request:
|
||||||
description: |
|
description: |
|
||||||
The number of gigabytes for the tenant.
|
The number of gigabytes for the tenant.
|
||||||
|
@ -2143,6 +2151,14 @@ quota_share_networks:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
quota_share_networks_detail:
|
||||||
|
description: |
|
||||||
|
The limit, in_use, reserved number of share networks
|
||||||
|
allowed for each tenant.
|
||||||
|
in: body
|
||||||
|
min_version: 2.25
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
quota_share_networks_request:
|
quota_share_networks_request:
|
||||||
description: |
|
description: |
|
||||||
The number of share networks for the tenant.
|
The number of share networks for the tenant.
|
||||||
|
@ -2155,6 +2171,14 @@ quota_shares:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
quota_shares_detail:
|
||||||
|
description: |
|
||||||
|
The limit, in_use, reserved number of shares allowed
|
||||||
|
for each tenant.
|
||||||
|
in: body
|
||||||
|
min_version: 2.25
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
quota_shares_request:
|
quota_shares_request:
|
||||||
description: |
|
description: |
|
||||||
The number of shares for the tenant.
|
The number of shares for the tenant.
|
||||||
|
@ -2168,6 +2192,14 @@ quota_snapshot_gigabytes:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
quota_snapshot_gigabytes_detail:
|
||||||
|
description: |
|
||||||
|
The limit, in_use, reserved number of gigabytes for the
|
||||||
|
snapshots allowed for each tenant.
|
||||||
|
in: body
|
||||||
|
min_version: 2.25
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
quota_snapshot_gigabytes_request:
|
quota_snapshot_gigabytes_request:
|
||||||
description: |
|
description: |
|
||||||
The number of gigabytes for the snapshots for the
|
The number of gigabytes for the snapshots for the
|
||||||
|
@ -2181,6 +2213,14 @@ quota_snapshots:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
|
quota_snapshots_detail:
|
||||||
|
description: |
|
||||||
|
The limit, in_use, reserved number of snapshots allowed
|
||||||
|
for each tenant.
|
||||||
|
in: body
|
||||||
|
min_version: 2.25
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
quota_snapshots_request:
|
quota_snapshots_request:
|
||||||
description: |
|
description: |
|
||||||
The number of snapshots for the tenant.
|
The number of snapshots for the tenant.
|
||||||
|
|
|
@ -93,6 +93,49 @@ Response example
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
|
|
||||||
|
Show quota set in detail
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. rest_method:: GET /v2/{tenant_id}/quota-sets/{tenant_id}/detail?user_id={user_id}
|
||||||
|
|
||||||
|
Shows quotas for a tenant in detail.
|
||||||
|
|
||||||
|
If you specify the optional ``user_id`` query parameter, you get
|
||||||
|
the quotas for this user in the tenant. If you omit this parameter,
|
||||||
|
you get the quotas for the project.
|
||||||
|
|
||||||
|
Normal response codes: 200
|
||||||
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
|
||||||
|
|
||||||
|
Request
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- tenant_id: tenant_id_path
|
||||||
|
- tenant_id: tenant_id
|
||||||
|
- user_id: user_id_query
|
||||||
|
|
||||||
|
Response parameters
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- quota_set: quota_set
|
||||||
|
- id: quota_tenant_id
|
||||||
|
- gigabytes: quota_gigabytes_detail
|
||||||
|
- snapshots: quota_snapshots_detail
|
||||||
|
- shares: quota_shares_detail
|
||||||
|
- snapshot_gigabytes: quota_snapshot_gigabytes_detail
|
||||||
|
- share_networks: quota_share_networks_detail
|
||||||
|
|
||||||
|
Response example
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. literalinclude:: samples/quota-show-detail-response.json
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
|
|
||||||
Update quota set
|
Update quota set
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"quota_set": {
|
||||||
|
"id": "16e1ab15c35a457e9c2b2aa189f544e1",
|
||||||
|
"gigabytes": {"in_use": 0,
|
||||||
|
"limit": 1000,
|
||||||
|
"reserved": 0},
|
||||||
|
"shares": {"in_use": 0,
|
||||||
|
"limit": 50,
|
||||||
|
"reserved": 0},
|
||||||
|
"snapshot_gigabytes": {"in_use": 0,
|
||||||
|
"limit": 1000,
|
||||||
|
"reserved": 0},
|
||||||
|
"snapshots": {"in_use": 0,
|
||||||
|
"limit": 50,
|
||||||
|
"reserved": 0},
|
||||||
|
"share_networks": {"in_use": 0,
|
||||||
|
"limit": 10,
|
||||||
|
"reserved": 0}
|
||||||
|
}
|
||||||
|
}
|
|
@ -82,6 +82,7 @@ REST_API_VERSION_HISTORY = """
|
||||||
* 2.24 - Added optional create_share_from_snapshot_support extra spec,
|
* 2.24 - Added optional create_share_from_snapshot_support extra spec,
|
||||||
which was previously inferred from the 'snapshot_support' extra
|
which was previously inferred from the 'snapshot_support' extra
|
||||||
spec. Also made the 'snapshot_support' extra spec optional.
|
spec. Also made the 'snapshot_support' extra spec optional.
|
||||||
|
* 2.25 - Added quota-show detail API.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ REST_API_VERSION_HISTORY = """
|
||||||
# The default api version request is defined to be the
|
# The default api version request is defined to be the
|
||||||
# minimum version of the API supported.
|
# minimum version of the API supported.
|
||||||
_MIN_API_VERSION = "2.0"
|
_MIN_API_VERSION = "2.0"
|
||||||
_MAX_API_VERSION = "2.24"
|
_MAX_API_VERSION = "2.25"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -147,3 +147,7 @@ user documentation.
|
||||||
----
|
----
|
||||||
Added optional create_share_from_snapshot_support extra spec. Made
|
Added optional create_share_from_snapshot_support extra spec. Made
|
||||||
snapshot_support extra spec optional.
|
snapshot_support extra spec optional.
|
||||||
|
|
||||||
|
2.25
|
||||||
|
----
|
||||||
|
Added quota-show detail API.
|
||||||
|
|
|
@ -66,14 +66,18 @@ class QuotaSetsMixin(object):
|
||||||
return {k: v['limit'] for k, v in values.items()}
|
return {k: v['limit'] for k, v in values.items()}
|
||||||
|
|
||||||
@wsgi.Controller.authorize("show")
|
@wsgi.Controller.authorize("show")
|
||||||
def _show(self, req, id):
|
def _show(self, req, id, detail=False):
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
params = parse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||||
user_id = params.get('user_id', [None])[0]
|
user_id = params.get('user_id', [None])[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.authorize_project_context(context, id)
|
db.authorize_project_context(context, id)
|
||||||
|
# _get_quotas use 'usages' to indicate whether retrieve additional
|
||||||
|
# attributes, so pass detail to the argument.
|
||||||
return self._view_builder.detail_list(
|
return self._view_builder.detail_list(
|
||||||
self._get_quotas(context, id, user_id=user_id), id)
|
self._get_quotas(context, id, user_id=user_id,
|
||||||
|
usages=detail), id)
|
||||||
except exception.NotAuthorized:
|
except exception.NotAuthorized:
|
||||||
raise webob.exc.HTTPForbidden()
|
raise webob.exc.HTTPForbidden()
|
||||||
|
|
||||||
|
@ -221,6 +225,10 @@ class QuotaSetsController(QuotaSetsMixin, wsgi.Controller):
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
return self._show(req, id)
|
return self._show(req, id)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version('2.25')
|
||||||
|
def detail(self, req, id):
|
||||||
|
return self._show(req, id, True)
|
||||||
|
|
||||||
@wsgi.Controller.api_version('2.7')
|
@wsgi.Controller.api_version('2.7')
|
||||||
def defaults(self, req, id):
|
def defaults(self, req, id):
|
||||||
return self._defaults(req, id)
|
return self._defaults(req, id)
|
||||||
|
|
|
@ -106,7 +106,8 @@ class APIRouter(manila.api.openstack.APIRouter):
|
||||||
mapper.resource("quota-set",
|
mapper.resource("quota-set",
|
||||||
"quota-sets",
|
"quota-sets",
|
||||||
controller=self.resources["quota_sets"],
|
controller=self.resources["quota_sets"],
|
||||||
member={"defaults": "GET"})
|
member={"defaults": "GET",
|
||||||
|
"detail": "GET"})
|
||||||
|
|
||||||
self.resources["quota_class_sets_legacy"] = (
|
self.resources["quota_class_sets_legacy"] = (
|
||||||
quota_class_sets.create_resource_legacy())
|
quota_class_sets.create_resource_legacy())
|
||||||
|
|
|
@ -26,6 +26,7 @@ from oslo_config import cfg
|
||||||
import webob.exc
|
import webob.exc
|
||||||
import webob.response
|
import webob.response
|
||||||
|
|
||||||
|
from manila.api.openstack import api_version_request as api_version
|
||||||
from manila.api.v2 import quota_sets
|
from manila.api.v2 import quota_sets
|
||||||
from manila import context
|
from manila import context
|
||||||
from manila import exception
|
from manila import exception
|
||||||
|
@ -121,6 +122,48 @@ class QuotaSetsControllerTest(test.TestCase):
|
||||||
|
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
@ddt.data(REQ, REQ_WITH_USER)
|
||||||
|
def test_quota_detail(self, request):
|
||||||
|
request.api_version_request = api_version.APIVersionRequest('2.25')
|
||||||
|
quotas = {
|
||||||
|
"shares": 23,
|
||||||
|
"snapshots": 34,
|
||||||
|
"gigabytes": 45,
|
||||||
|
"snapshot_gigabytes": 56,
|
||||||
|
"share_networks": 67,
|
||||||
|
}
|
||||||
|
expected = {
|
||||||
|
'quota_set': {
|
||||||
|
'id': self.project_id,
|
||||||
|
'shares': {'in_use': 0,
|
||||||
|
'limit': quotas['shares'],
|
||||||
|
'reserved': 0},
|
||||||
|
'gigabytes': {'in_use': 0,
|
||||||
|
'limit': quotas['gigabytes'], 'reserved': 0},
|
||||||
|
'snapshots': {'in_use': 0,
|
||||||
|
'limit': quotas['snapshots'], 'reserved': 0},
|
||||||
|
'snapshot_gigabytes': {
|
||||||
|
'in_use': 0,
|
||||||
|
'limit': quotas['snapshot_gigabytes'],
|
||||||
|
'reserved': 0,
|
||||||
|
},
|
||||||
|
'share_networks': {
|
||||||
|
'in_use': 0,
|
||||||
|
'limit': quotas['share_networks'],
|
||||||
|
'reserved': 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in quotas.items():
|
||||||
|
CONF.set_default('quota_' + k, v)
|
||||||
|
|
||||||
|
result = self.controller.detail(request, self.project_id)
|
||||||
|
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
self.mock_policy_check.assert_called_once_with(
|
||||||
|
request.environ['manila.context'], self.resource_name, 'show')
|
||||||
|
|
||||||
@ddt.data(REQ, REQ_WITH_USER)
|
@ddt.data(REQ, REQ_WITH_USER)
|
||||||
def test_show_quota(self, request):
|
def test_show_quota(self, request):
|
||||||
quotas = {
|
quotas = {
|
||||||
|
|
|
@ -30,7 +30,7 @@ ShareGroup = [
|
||||||
help="The minimum api microversion is configured to be the "
|
help="The minimum api microversion is configured to be the "
|
||||||
"value of the minimum microversion supported by Manila."),
|
"value of the minimum microversion supported by Manila."),
|
||||||
cfg.StrOpt("max_api_microversion",
|
cfg.StrOpt("max_api_microversion",
|
||||||
default="2.24",
|
default="2.25",
|
||||||
help="The maximum api microversion is configured to be the "
|
help="The maximum api microversion is configured to be the "
|
||||||
"value of the latest microversion supported by Manila."),
|
"value of the latest microversion supported by Manila."),
|
||||||
cfg.StrOpt("region",
|
cfg.StrOpt("region",
|
||||||
|
|
|
@ -785,6 +785,17 @@ class SharesV2Client(shares_client.SharesClient):
|
||||||
self.expected_success(202, resp.status)
|
self.expected_success(202, resp.status)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
def detail_quotas(self, tenant_id, user_id=None, url=None,
|
||||||
|
version=LATEST_MICROVERSION):
|
||||||
|
if url is None:
|
||||||
|
url = self._get_quotas_url(version)
|
||||||
|
url += '/%s/detail' % tenant_id
|
||||||
|
if user_id is not None:
|
||||||
|
url += "?user_id=%s" % user_id
|
||||||
|
resp, body = self.get(url, version=version)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
def update_quotas(self, tenant_id, user_id=None, shares=None,
|
def update_quotas(self, tenant_id, user_id=None, shares=None,
|
||||||
snapshots=None, gigabytes=None, snapshot_gigabytes=None,
|
snapshots=None, gigabytes=None, snapshot_gigabytes=None,
|
||||||
share_networks=None, force=True, url=None,
|
share_networks=None, force=True, url=None,
|
||||||
|
|
|
@ -203,3 +203,15 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
||||||
self.shares_v2_client.tenant_id,
|
self.shares_v2_client.tenant_id,
|
||||||
version=version, url=url,
|
version=version, url=url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||||
|
def test_show_quota_detail_with_wrong_versions(self):
|
||||||
|
version = '2.24'
|
||||||
|
url = 'quota-sets'
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
lib_exc.NotFound,
|
||||||
|
self.shares_v2_client.detail_quotas,
|
||||||
|
self.shares_v2_client.tenant_id,
|
||||||
|
version=version, url=url,
|
||||||
|
)
|
||||||
|
|
|
@ -64,3 +64,28 @@ class SharesQuotasTest(base.BaseSharesTest):
|
||||||
self.assertGreater(int(quotas["shares"]), -2)
|
self.assertGreater(int(quotas["shares"]), -2)
|
||||||
self.assertGreater(int(quotas["snapshots"]), -2)
|
self.assertGreater(int(quotas["snapshots"]), -2)
|
||||||
self.assertGreater(int(quotas["share_networks"]), -2)
|
self.assertGreater(int(quotas["share_networks"]), -2)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||||
|
@base.skip_if_microversion_not_supported("2.25")
|
||||||
|
def test_show_quotas_detail(self):
|
||||||
|
quotas = self.shares_v2_client.detail_quotas(self.tenant_id)
|
||||||
|
quota_keys = list(quotas.keys())
|
||||||
|
for outer in ('gigabytes', 'snapshot_gigabytes', 'shares',
|
||||||
|
'snapshots', 'share_networks'):
|
||||||
|
self.assertIn(outer, quota_keys)
|
||||||
|
for inner in ('in_use', 'limit', 'reserved'):
|
||||||
|
self.assertIn(inner, list(quotas[outer].keys()))
|
||||||
|
self.assertGreater(int(quotas[outer][inner]), -2)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||||
|
@base.skip_if_microversion_not_supported("2.25")
|
||||||
|
def test_show_quotas_detail_for_user(self):
|
||||||
|
quotas = self.shares_v2_client.detail_quotas(self.tenant_id,
|
||||||
|
self.user_id)
|
||||||
|
quota_keys = list(quotas.keys())
|
||||||
|
for outer in ('gigabytes', 'snapshot_gigabytes', 'shares',
|
||||||
|
'snapshots', 'share_networks'):
|
||||||
|
self.assertIn(outer, quota_keys)
|
||||||
|
for inner in ('in_use', 'limit', 'reserved'):
|
||||||
|
self.assertIn(inner, list(quotas[outer].keys()))
|
||||||
|
self.assertGreater(int(quotas[outer][inner]), -2)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added detail API to show user and tenant specific usages through the
|
||||||
|
quota-sets resource.
|
Loading…
Reference in New Issue