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:
parent
c2afcc5d70
commit
d1d5dfe5fc
@ -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
|
||||||
|
@ -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
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user