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 in: path
required: true required: true
type: string type: string
project_id:
description: |
The UUID of project in a multi-project cloud.
in: path
required: true
type: string
quota_class_name: quota_class_name:
description: | description: |
The name of quota class 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 Update quotas for a project
=========================== ===========================
.. rest_method:: PUT /v1/quotas .. rest_method:: PUT /v1/quotas/{project_id}
Update the quotas for a project Update the quotas for a project
@ -31,6 +31,7 @@ Request
.. rest_parameters:: parameters.yaml .. rest_parameters:: parameters.yaml
- project_id: project_id
- containers: container-request - containers: container-request
- memory: memory-request - memory: memory-request
- cpu: cpu-request - cpu: cpu-request
@ -61,7 +62,7 @@ Response Example
Get quotas for a project Get quotas for a project
======================== ========================
.. rest_method:: GET /v1/quotas .. rest_method:: GET /v1/quotas/{project_id}
Get quotas for a project Get quotas for a project
@ -81,6 +82,10 @@ Response Codes
Request Request
------- -------
.. rest_parameters:: parameters.yaml
- project_id: project_id
Response Response
-------- --------
@ -101,7 +106,7 @@ Response Example
Get Default quotas for a project 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 Get the default quotas for a project
@ -121,6 +126,10 @@ Response Codes
Request Request
------- -------
.. rest_parameters:: parameters.yaml
- project_id: project_id
Response Response
-------- --------
@ -141,7 +150,7 @@ Response Example
Revert Quotas to defaults 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 Reverts the quotas to default values for a project
@ -157,6 +166,10 @@ Reverts the quotas to default values for a project
Request Request
------- -------
.. rest_parameters:: parameters.yaml
- project_id: project_id
Response Response
-------- --------

View File

@ -30,8 +30,8 @@ class QuotaController(base.Controller):
'defaults': ['GET'], 'defaults': ['GET'],
} }
def _get_quotas(self, context, usages=False): def _get_quotas(self, context, project_id, usages=False):
values = QUOTAS.get_project_quotas(context, context.project_id, values = QUOTAS.get_project_quotas(context, project_id,
usages=usages) usages=usages)
if usages: if usages:
@ -43,11 +43,11 @@ class QuotaController(base.Controller):
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_update) @validation.validate_query_param(pecan.request, schema.query_param_update)
@validation.validated(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 context = pecan.request.context
policy.enforce(context, 'quota:update', policy.enforce(context, 'quota:update',
target={'project_id': project_id},
action='quota:update') action='quota:update')
project_id = context.project_id
for key, value in quotas_dict.items(): for key, value in quotas_dict.items():
value = int(value) value = int(value)
quota = objects.Quota(context, project_id=project_id, resource=key, quota = objects.Quota(context, project_id=project_id, resource=key,
@ -56,30 +56,33 @@ class QuotaController(base.Controller):
quota.create(context) quota.create(context)
except exception.QuotaAlreadyExists: except exception.QuotaAlreadyExists:
quota.update(context) quota.update(context)
return self._get_quotas(context) return self._get_quotas(context, project_id)
@pecan.expose('json') @pecan.expose('json')
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
def get(self, **kwargs): def get(self, project_id, **kwargs):
context = pecan.request.context context = pecan.request.context
usages = kwargs.get('usages', False) usages = kwargs.get('usages', False)
policy.enforce(context, 'quota:get', policy.enforce(context, 'quota:get',
target={'project_id': project_id},
action='quota:get') action='quota:get')
return self._get_quotas(context, usages=usages) return self._get_quotas(context, project_id, usages=usages)
@pecan.expose('json') @pecan.expose('json')
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
def defaults(self): def defaults(self, project_id):
context = pecan.request.context context = pecan.request.context
policy.enforce(context, 'quota:get_default', policy.enforce(context, 'quota:get_default',
target={'project_id': project_id},
action='quota:get_default') action='quota:get_default')
values = QUOTAS.get_defaults(context) values = QUOTAS.get_defaults(context)
return values return values
@pecan.expose('json') @pecan.expose('json')
@exception.wrap_pecan_controller_exception @exception.wrap_pecan_controller_exception
def delete(self): def delete(self, project_id):
context = pecan.request.context context = pecan.request.context
policy.enforce(context, 'quota:delete', policy.enforce(context, 'quota:delete',
target={'project_id': project_id},
action='quota:delete') 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', description='Update quotas for a project',
operations=[ operations=[
{ {
'path': '/v1/quotas', 'path': '/v1/quotas/{project_id}',
'method': 'PUT' 'method': 'PUT'
} }
] ]
@ -34,7 +34,7 @@ rules = [
description='Delete quotas for a project', description='Delete quotas for a project',
operations=[ operations=[
{ {
'path': '/v1/quotas', 'path': '/v1/quotas/{project_id}',
'method': 'DELETE' 'method': 'DELETE'
} }
] ]
@ -45,7 +45,7 @@ rules = [
description='Get quotas for a project', description='Get quotas for a project',
operations=[ operations=[
{ {
'path': '/v1/quotas', 'path': '/v1/quotas/{project_id}',
'method': 'GET' 'method': 'GET'
} }
] ]

View File

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