Allow setting quota of other tenants

Currently, the quota API/CLI allows users to set
quotas for current tenant only. However, admin
users needs the ability to set quota for other
tenants. For example:

  $ source /opt/stack/devstack/openrc admin admin
  $ openstack appcontainer quota update --containers 100 <DEMO_TENANT_UUID>

Closes-Bug: #1807620
Change-Id: I9109d968d8010692644fd004a8085a43e1bdf60f
This commit is contained in:
Kien Nguyen 2018-12-23 12:06:46 +07:00
parent c2afcc5d70
commit d1d5dfe5fc
5 changed files with 49 additions and 25 deletions

View File

@ -17,6 +17,12 @@ host_ident:
in: path
required: true
type: string
project_id:
description: |
The UUID of project in a multi-project cloud.
in: path
required: true
type: string
quota_class_name:
description: |
The name of quota class

View File

@ -9,7 +9,7 @@ Gets, updates, gets default and deletes quotas for a project.
Update quotas for a project
===========================
.. rest_method:: PUT /v1/quotas
.. rest_method:: PUT /v1/quotas/{project_id}
Update the quotas for a project
@ -31,6 +31,7 @@ Request
.. rest_parameters:: parameters.yaml
- project_id: project_id
- containers: container-request
- memory: memory-request
- cpu: cpu-request
@ -61,7 +62,7 @@ Response Example
Get quotas for a project
========================
.. rest_method:: GET /v1/quotas
.. rest_method:: GET /v1/quotas/{project_id}
Get quotas for a project
@ -81,6 +82,10 @@ Response Codes
Request
-------
.. rest_parameters:: parameters.yaml
- project_id: project_id
Response
--------
@ -101,7 +106,7 @@ Response Example
Get Default quotas for a project
================================
.. rest_method:: GET /v1/quotas/defaults
.. rest_method:: GET /v1/quotas/{project_id}/defaults
Get the default quotas for a project
@ -121,6 +126,10 @@ Response Codes
Request
-------
.. rest_parameters:: parameters.yaml
- project_id: project_id
Response
--------
@ -141,7 +150,7 @@ Response Example
Revert Quotas to defaults
=========================
.. rest_method:: DELETE /v1/quotas
.. rest_method:: DELETE /v1/quotas/{project_id}
Reverts the quotas to default values for a project
@ -157,6 +166,10 @@ Reverts the quotas to default values for a project
Request
-------
.. rest_parameters:: parameters.yaml
- project_id: project_id
Response
--------

View File

@ -30,8 +30,8 @@ class QuotaController(base.Controller):
'defaults': ['GET'],
}
def _get_quotas(self, context, usages=False):
values = QUOTAS.get_project_quotas(context, context.project_id,
def _get_quotas(self, context, project_id, usages=False):
values = QUOTAS.get_project_quotas(context, project_id,
usages=usages)
if usages:
@ -43,11 +43,11 @@ class QuotaController(base.Controller):
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_update)
@validation.validated(schema.query_param_update)
def put(self, **quotas_dict):
def put(self, project_id, **quotas_dict):
context = pecan.request.context
policy.enforce(context, 'quota:update',
target={'project_id': project_id},
action='quota:update')
project_id = context.project_id
for key, value in quotas_dict.items():
value = int(value)
quota = objects.Quota(context, project_id=project_id, resource=key,
@ -56,30 +56,33 @@ class QuotaController(base.Controller):
quota.create(context)
except exception.QuotaAlreadyExists:
quota.update(context)
return self._get_quotas(context)
return self._get_quotas(context, project_id)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get(self, **kwargs):
def get(self, project_id, **kwargs):
context = pecan.request.context
usages = kwargs.get('usages', False)
policy.enforce(context, 'quota:get',
target={'project_id': project_id},
action='quota:get')
return self._get_quotas(context, usages=usages)
return self._get_quotas(context, project_id, usages=usages)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def defaults(self):
def defaults(self, project_id):
context = pecan.request.context
policy.enforce(context, 'quota:get_default',
target={'project_id': project_id},
action='quota:get_default')
values = QUOTAS.get_defaults(context)
return values
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def delete(self):
def delete(self, project_id):
context = pecan.request.context
policy.enforce(context, 'quota:delete',
target={'project_id': project_id},
action='quota:delete')
QUOTAS.destroy_all_by_project(context, context.project_id)
QUOTAS.destroy_all_by_project(context, project_id)

View File

@ -23,7 +23,7 @@ rules = [
description='Update quotas for a project',
operations=[
{
'path': '/v1/quotas',
'path': '/v1/quotas/{project_id}',
'method': 'PUT'
}
]
@ -34,7 +34,7 @@ rules = [
description='Delete quotas for a project',
operations=[
{
'path': '/v1/quotas',
'path': '/v1/quotas/{project_id}',
'method': 'DELETE'
}
]
@ -45,7 +45,7 @@ rules = [
description='Get quotas for a project',
operations=[
{
'path': '/v1/quotas',
'path': '/v1/quotas/{project_id}',
'method': 'GET'
}
]

View File

@ -18,6 +18,7 @@ from zun.tests.unit.api import base as api_base
class TestQuotaController(api_base.FunctionalTest):
def setUp(self):
super(TestQuotaController, self).setUp()
self.test_project_id = 'test_project_id'
self.default_quotas = {
'containers': 40,
'cpu': 20,
@ -27,31 +28,32 @@ class TestQuotaController(api_base.FunctionalTest):
@mock.patch('zun.common.policy.enforce', return_value=True)
def test_get_defaults_quota(self, mock_policy):
response = self.get('/quotas/defaults')
response = self.get('/quotas/%s/defaults' % self.test_project_id)
self.assertEqual(200, response.status_int)
self.assertEqual(self.default_quotas, response.json)
@mock.patch('zun.common.policy.enforce', return_value=True)
def test_put_quota(self, mock_policy):
def test_put_quota_with_project_id(self, mock_policy):
update_quota_dicts = {
'containers': '50',
'memory': '61440'
}
response = self.put_json('/quotas', update_quota_dicts)
response = self.put_json('/quotas/%s' % self.test_project_id,
update_quota_dicts)
self.assertEqual(200, response.status_int)
self.assertEqual(50, response.json['containers'])
self.assertEqual(61440, response.json['memory'])
@mock.patch('zun.common.policy.enforce', return_value=True)
def test_get_quota(self, mock_policy):
response = self.get('/quotas')
def test_get_quota_with_project_id(self, mock_policy):
response = self.get('/quotas/%s' % self.test_project_id)
self.assertEqual(200, response.status_int)
self.assertEqual(self.default_quotas, response.json)
@mock.patch('zun.common.policy.enforce', return_value=True)
def test_delete_quota(self, mock_policy):
response = self.delete('/quotas')
def test_delete_quota_with_project_id(self, mock_policy):
response = self.delete('/quotas/%s' % self.test_project_id)
self.assertEqual(200, response.status_int)
response = self.get('/quotas')
response = self.get('/quotas/%s' % self.test_project_id)
self.assertEqual(self.default_quotas, response.json)