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>

Depends-On: https://review.openstack.org/627075
Change-Id: Ia147575d3e8d2c6d54bddd98846f0c9e3e518ec3
Related-Bug: #1807620
This commit is contained in:
Kien Nguyen 2018-12-23 16:47:00 +07:00
parent 77046229b8
commit eedf5c7fdd
4 changed files with 72 additions and 25 deletions

View File

@ -52,6 +52,10 @@ class UpdateQuota(command.ShowOne):
metavar='<disk>', metavar='<disk>',
help='The number of gigabytes of container Disk ' help='The number of gigabytes of container Disk '
'allowed per project') 'allowed per project')
parser.add_argument(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
@ -61,7 +65,7 @@ class UpdateQuota(command.ShowOne):
opts['memory'] = parsed_args.memory opts['memory'] = parsed_args.memory
opts['cpu'] = parsed_args.cpu opts['cpu'] = parsed_args.cpu
opts['disk'] = parsed_args.disk opts['disk'] = parsed_args.disk
quota = client.quotas.update(**opts) quota = client.quotas.update(parsed_args.project_id, **opts)
columns = _quota_columns(quota) columns = _quota_columns(quota)
return columns, utils.get_item_properties(quota, columns) return columns, utils.get_item_properties(quota, columns)
@ -77,11 +81,17 @@ class GetQuota(command.ShowOne):
'--usages', '--usages',
action='store_true', action='store_true',
help='Whether show quota usage statistic or not') help='Whether show quota usage statistic or not')
parser.add_argument(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
client = _get_client(self, parsed_args) client = _get_client(self, parsed_args)
quota = client.quotas.get(usages=parsed_args.usages) quota = client.quotas.get(
parsed_args.project_id,
usages=parsed_args.usages)
columns = _quota_columns(quota) columns = _quota_columns(quota)
return columns, utils.get_item_properties(quota, columns) return columns, utils.get_item_properties(quota, columns)
@ -91,9 +101,17 @@ class GetDefaultQuota(command.ShowOne):
log = logging.getLogger(__name__ + '.GetDefeaultQuota') log = logging.getLogger(__name__ + '.GetDefeaultQuota')
def get_parser(self, prog_name):
parser = super(GetDefaultQuota, self).get_parser(prog_name)
parser.add_argument(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
client = _get_client(self, parsed_args) client = _get_client(self, parsed_args)
default_quota = client.quotas.defaults() default_quota = client.quotas.defaults(parsed_args.project_id)
columns = _quota_columns(default_quota) columns = _quota_columns(default_quota)
return columns, utils.get_item_properties( return columns, utils.get_item_properties(
default_quota, columns) default_quota, columns)
@ -104,10 +122,18 @@ class DeleteQuota(command.Command):
log = logging.getLogger(__name__ + '.DeleteQuota') log = logging.getLogger(__name__ + '.DeleteQuota')
def get_parser(self, prog_name):
parser = super(DeleteQuota, self).get_parser(prog_name)
parser.add_argument(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
client = _get_client(self, parsed_args) client = _get_client(self, parsed_args)
try: try:
client.quotas.delete() client.quotas.delete(parsed_args.project_id)
print(_('Request to delete quotas has been accepted.')) print(_('Request to delete quotas has been accepted.'))
except Exception as e: except Exception as e:
print("Delete for quotas failed: %(e)s" % {'e': e}) print("Delete for quotas failed: %(e)s" % {'e': e})

View File

@ -40,7 +40,7 @@ MODIFIED_USAGE_QUOTAS = {
} }
fake_responses = { fake_responses = {
'/v1/quotas': '/v1/quotas/test_project_id':
{ {
'GET': ( 'GET': (
{}, {},
@ -55,14 +55,14 @@ fake_responses = {
None None
) )
}, },
'/v1/quotas/defaults': '/v1/quotas/test_project_id/defaults':
{ {
'GET': ( 'GET': (
{}, {},
DEFAULT_QUOTAS DEFAULT_QUOTAS
) )
}, },
'/v1/quotas?usages=True': '/v1/quotas/test_project_id?usages=True':
{ {
'GET': ( 'GET': (
{}, {},
@ -80,9 +80,9 @@ class QuotaManagerTest(testtools.TestCase):
self.mgr = quotas.QuotaManager(self.api) self.mgr = quotas.QuotaManager(self.api)
def test_quotas_get_defaults(self): def test_quotas_get_defaults(self):
quotas = self.mgr.defaults() quotas = self.mgr.defaults('test_project_id')
expect = [ expect = [
('GET', '/v1/quotas/defaults', {}, None) ('GET', '/v1/quotas/test_project_id/defaults', {}, None)
] ]
self.assertEqual(expect, self.api.calls) self.assertEqual(expect, self.api.calls)
self.assertEqual(quotas.containers, DEFAULT_QUOTAS['containers']) self.assertEqual(quotas.containers, DEFAULT_QUOTAS['containers'])

View File

@ -22,16 +22,18 @@ class QuotaManager(base.Manager):
resource_class = Quota resource_class = Quota
@staticmethod @staticmethod
def _path(): def _path(project_id):
if project_id is not None:
return '/v1/quotas/{}'.format(project_id)
return '/v1/quotas' return '/v1/quotas'
def get(self, **kwargs): def get(self, project_id, **kwargs):
if not kwargs.get('usages'): if not kwargs.get('usages'):
kwargs = {} kwargs = {}
return self._list(self._path(), qparams=kwargs)[0] return self._list(self._path(project_id), qparams=kwargs)[0]
def update(self, containers=None, memory=None, def update(self, project_id, containers=None,
cpu=None, disk=None): memory=None, cpu=None, disk=None):
resources = {} resources = {}
if cpu is not None: if cpu is not None:
resources['cpu'] = cpu resources['cpu'] = cpu
@ -41,10 +43,10 @@ class QuotaManager(base.Manager):
resources['containers'] = containers resources['containers'] = containers
if disk is not None: if disk is not None:
resources['disk'] = disk resources['disk'] = disk
return self._update(self._path(), resources, method='PUT') return self._update(self._path(project_id), resources, method='PUT')
def defaults(self): def defaults(self, project_id):
return self._list(self._path() + '/defaults')[0] return self._list(self._path(project_id) + '/defaults')[0]
def delete(self): def delete(self, project_id):
return self._delete(self._path()) return self._delete(self._path(project_id))

View File

@ -33,9 +33,14 @@ from zunclient.common import cliutils as utils
metavar='<disk>', metavar='<disk>',
type=int, type=int,
help='The number of gigabytes of container Disk allowed per project') help='The number of gigabytes of container Disk allowed per project')
@utils.arg(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
def do_quota_update(cs, args): def do_quota_update(cs, args):
"""Print an updated quotas for a project""" """Print an updated quotas for a project"""
utils.print_dict(cs.quotas.update(containers=args.containers, utils.print_dict(cs.quotas.update(args.project_id,
containers=args.containers,
memory=args.memory, memory=args.memory,
cpu=args.cpu, cpu=args.cpu,
disk=args.disk)._info) disk=args.disk)._info)
@ -46,20 +51,34 @@ def do_quota_update(cs, args):
default=False, default=False,
action='store_true', action='store_true',
help='Whether show quota usage statistic or not') help='Whether show quota usage statistic or not')
@utils.arg(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
def do_quota_get(cs, args): def do_quota_get(cs, args):
"""Print a quotas for a project with usages (optional)""" """Print a quotas for a project with usages (optional)"""
if args.usages: if args.usages:
utils.print_dict(cs.quotas.get(usages=args.usages)._info, utils.print_dict(
cs.quotas.get(args.project_id, usages=args.usages)._info,
value_fields=('limit', 'in_use')) value_fields=('limit', 'in_use'))
else: else:
utils.print_dict(cs.quotas.get(usages=args.usages)._info) utils.print_dict(
cs.quotas.get(args.project_id, usages=args.usages)._info)
@utils.arg(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
def do_quota_defaults(cs, args): def do_quota_defaults(cs, args):
"""Print a default quotas for a project""" """Print a default quotas for a project"""
utils.print_dict(cs.quotas.defaults()._info) utils.print_dict(cs.quotas.defaults(args.project_id)._info)
@utils.arg(
'project_id',
metavar='<project_id>',
help='The UUID of project in a multi-project cloud')
def do_quota_delete(cs, args): def do_quota_delete(cs, args):
"""Delete quotas for a project""" """Delete quotas for a project"""
cs.quotas.delete() cs.quotas.delete(args.project_id)