From d1d5dfe5fca9311100ccc1b3188c05754cd4767f Mon Sep 17 00:00:00 2001 From: Kien Nguyen Date: Sun, 23 Dec 2018 12:06:46 +0700 Subject: [PATCH] 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 Closes-Bug: #1807620 Change-Id: I9109d968d8010692644fd004a8085a43e1bdf60f --- api-ref/source/parameters.yaml | 6 +++++ api-ref/source/quotas.inc | 21 +++++++++++++---- zun/api/controllers/v1/quotas.py | 23 +++++++++++-------- zun/common/policies/quota.py | 6 ++--- .../unit/api/controllers/v1/test_quotas.py | 18 ++++++++------- 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index f2b84bd7a..7559484c8 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -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 diff --git a/api-ref/source/quotas.inc b/api-ref/source/quotas.inc index a4ecbeb72..9883f9a44 100644 --- a/api-ref/source/quotas.inc +++ b/api-ref/source/quotas.inc @@ -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 -------- diff --git a/zun/api/controllers/v1/quotas.py b/zun/api/controllers/v1/quotas.py index bac6b7e17..dc1329f1a 100644 --- a/zun/api/controllers/v1/quotas.py +++ b/zun/api/controllers/v1/quotas.py @@ -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) diff --git a/zun/common/policies/quota.py b/zun/common/policies/quota.py index c6f344f87..41ca4faea 100644 --- a/zun/common/policies/quota.py +++ b/zun/common/policies/quota.py @@ -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' } ] diff --git a/zun/tests/unit/api/controllers/v1/test_quotas.py b/zun/tests/unit/api/controllers/v1/test_quotas.py index 67253943c..7f3b7a7cb 100644 --- a/zun/tests/unit/api/controllers/v1/test_quotas.py +++ b/zun/tests/unit/api/controllers/v1/test_quotas.py @@ -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)