Merge "quota: Add 'quota show --usage' option"
This commit is contained in:
commit
ccd9356550
@ -94,7 +94,7 @@ quota-defaults,quota show --default,Lists default quotas for a tenant.
|
|||||||
quota-delete,quota delete --volume,Delete the quotas for a tenant.
|
quota-delete,quota delete --volume,Delete the quotas for a tenant.
|
||||||
quota-show,quota show,Lists quotas for a tenant.
|
quota-show,quota show,Lists quotas for a tenant.
|
||||||
quota-update,quota set,Updates quotas for a tenant.
|
quota-update,quota set,Updates quotas for a tenant.
|
||||||
quota-usage,quota list --detail,Lists quota usage for a tenant.
|
quota-usage,quota show --usage,Lists quota usage for a tenant.
|
||||||
rate-limits,limits show --rate,Lists rate limits for a user.
|
rate-limits,limits show --rate,Lists rate limits for a user.
|
||||||
readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag.
|
readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag.
|
||||||
rename,volume set --name,Renames a volume.
|
rename,volume set --name,Renames a volume.
|
||||||
|
|
@ -233,19 +233,26 @@ class ListQuota(command.Lister):
|
|||||||
|
|
||||||
def get_parser(self, prog_name):
|
def get_parser(self, prog_name):
|
||||||
parser = super().get_parser(prog_name)
|
parser = super().get_parser(prog_name)
|
||||||
|
# TODO(stephenfin): Remove in OSC 8.0
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--project',
|
'--project',
|
||||||
metavar='<project>',
|
metavar='<project>',
|
||||||
help=_('List quotas for this project <project> (name or ID)'),
|
help=_(
|
||||||
|
"**Deprecated** List quotas for this project <project> "
|
||||||
|
"(name or ID). "
|
||||||
|
"Use 'quota show' instead."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
# TODO(stephenfin): This doesn't belong here. We should put it into the
|
# TODO(stephenfin): Remove in OSC 8.0
|
||||||
# 'quota show' command and deprecate this.
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--detail',
|
'--detail',
|
||||||
dest='detail',
|
dest='detail',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help=_('Show details about quotas usage'),
|
help=_(
|
||||||
|
"**Deprecated** Show details about quotas usage. "
|
||||||
|
"Use 'quota show --usage' instead."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
option = parser.add_mutually_exclusive_group(required=True)
|
option = parser.add_mutually_exclusive_group(required=True)
|
||||||
option.add_argument(
|
option.add_argument(
|
||||||
@ -332,6 +339,19 @@ class ListQuota(command.Lister):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
|
if parsed_args.detail:
|
||||||
|
msg = _(
|
||||||
|
"The --detail option has been deprecated. "
|
||||||
|
"Use 'openstack quota show --usage' instead."
|
||||||
|
)
|
||||||
|
self.log.warning(msg)
|
||||||
|
elif parsed_args.project: # elif to avoid being too noisy
|
||||||
|
msg = _(
|
||||||
|
"The --project option has been deprecated. "
|
||||||
|
"Use 'openstack quota show' instead."
|
||||||
|
)
|
||||||
|
self.log.warning(msg)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
project_ids = []
|
project_ids = []
|
||||||
if parsed_args.project is None:
|
if parsed_args.project is None:
|
||||||
@ -678,7 +698,7 @@ class SetQuota(common.NetDetectionMixin, command.Command):
|
|||||||
**network_kwargs)
|
**network_kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ShowQuota(command.ShowOne):
|
class ShowQuota(command.Lister):
|
||||||
_description = _(
|
_description = _(
|
||||||
"Show quotas for project or class. "
|
"Show quotas for project or class. "
|
||||||
"Specify ``--os-compute-api-version 2.50`` or higher to see "
|
"Specify ``--os-compute-api-version 2.50`` or higher to see "
|
||||||
@ -692,7 +712,10 @@ class ShowQuota(command.ShowOne):
|
|||||||
'project',
|
'project',
|
||||||
metavar='<project/class>',
|
metavar='<project/class>',
|
||||||
nargs='?',
|
nargs='?',
|
||||||
help=_('Show quotas for this project or class (name or ID)'),
|
help=_(
|
||||||
|
'Show quotas for this project or class (name or ID) '
|
||||||
|
'(defaults to current project)'
|
||||||
|
),
|
||||||
)
|
)
|
||||||
type_group = parser.add_mutually_exclusive_group()
|
type_group = parser.add_mutually_exclusive_group()
|
||||||
type_group.add_argument(
|
type_group.add_argument(
|
||||||
@ -709,6 +732,13 @@ class ShowQuota(command.ShowOne):
|
|||||||
default=False,
|
default=False,
|
||||||
help=_('Show default quotas for <project>'),
|
help=_('Show default quotas for <project>'),
|
||||||
)
|
)
|
||||||
|
type_group.add_argument(
|
||||||
|
'--usage',
|
||||||
|
dest='usage',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=_('Show details about quotas usage'),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -726,18 +756,21 @@ class ShowQuota(command.ShowOne):
|
|||||||
compute_quota_info = get_compute_quotas(
|
compute_quota_info = get_compute_quotas(
|
||||||
self.app,
|
self.app,
|
||||||
project,
|
project,
|
||||||
|
detail=parsed_args.usage,
|
||||||
quota_class=parsed_args.quota_class,
|
quota_class=parsed_args.quota_class,
|
||||||
default=parsed_args.default,
|
default=parsed_args.default,
|
||||||
)
|
)
|
||||||
volume_quota_info = get_volume_quotas(
|
volume_quota_info = get_volume_quotas(
|
||||||
self.app,
|
self.app,
|
||||||
project,
|
project,
|
||||||
|
detail=parsed_args.usage,
|
||||||
quota_class=parsed_args.quota_class,
|
quota_class=parsed_args.quota_class,
|
||||||
default=parsed_args.default,
|
default=parsed_args.default,
|
||||||
)
|
)
|
||||||
network_quota_info = get_network_quotas(
|
network_quota_info = get_network_quotas(
|
||||||
self.app,
|
self.app,
|
||||||
project,
|
project,
|
||||||
|
detail=parsed_args.usage,
|
||||||
quota_class=parsed_args.quota_class,
|
quota_class=parsed_args.quota_class,
|
||||||
default=parsed_args.default,
|
default=parsed_args.default,
|
||||||
)
|
)
|
||||||
@ -762,20 +795,46 @@ class ShowQuota(command.ShowOne):
|
|||||||
info[v] = info[k]
|
info[v] = info[k]
|
||||||
info.pop(k)
|
info.pop(k)
|
||||||
|
|
||||||
|
# Remove the 'id' field since it's not very useful
|
||||||
|
if 'id' in info:
|
||||||
|
del info['id']
|
||||||
|
|
||||||
# Remove the 'location' field for resources from openstacksdk
|
# Remove the 'location' field for resources from openstacksdk
|
||||||
if 'location' in info:
|
if 'location' in info:
|
||||||
del info['location']
|
del info['location']
|
||||||
|
|
||||||
# Handle class or project ID specially as they only appear in output
|
if not parsed_args.usage:
|
||||||
if parsed_args.quota_class:
|
result = [
|
||||||
info.pop('id', None)
|
{'resource': k, 'limit': v} for k, v in info.items()
|
||||||
elif 'id' in info:
|
]
|
||||||
info['project'] = info.pop('id')
|
else:
|
||||||
if 'project_id' in info:
|
result = [
|
||||||
del info['project_id']
|
{'resource': k, **v} for k, v in info.items()
|
||||||
info['project_name'] = project_info['name']
|
]
|
||||||
|
|
||||||
return zip(*sorted(info.items()))
|
columns = (
|
||||||
|
'resource',
|
||||||
|
'limit',
|
||||||
|
)
|
||||||
|
column_headers = (
|
||||||
|
'Resource',
|
||||||
|
'Limit',
|
||||||
|
)
|
||||||
|
|
||||||
|
if parsed_args.usage:
|
||||||
|
columns += (
|
||||||
|
'in_use',
|
||||||
|
'reserved',
|
||||||
|
)
|
||||||
|
column_headers += (
|
||||||
|
'In Use',
|
||||||
|
'Reserved',
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
column_headers,
|
||||||
|
(utils.get_dict_properties(s, columns) for s in result),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeleteQuota(command.Command):
|
class DeleteQuota(command.Command):
|
||||||
|
@ -114,6 +114,7 @@ class QuotaTests(base.TestCase):
|
|||||||
cmd_output = json.loads(self.openstack(
|
cmd_output = json.loads(self.openstack(
|
||||||
'quota show -f json ' + self.PROJECT_NAME
|
'quota show -f json ' + self.PROJECT_NAME
|
||||||
))
|
))
|
||||||
|
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
|
||||||
self.assertIsNotNone(cmd_output)
|
self.assertIsNotNone(cmd_output)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
31,
|
31,
|
||||||
@ -136,6 +137,7 @@ class QuotaTests(base.TestCase):
|
|||||||
self.assertIsNotNone(cmd_output)
|
self.assertIsNotNone(cmd_output)
|
||||||
# We don't necessarily know the default quotas, we're checking the
|
# We don't necessarily know the default quotas, we're checking the
|
||||||
# returned attributes
|
# returned attributes
|
||||||
|
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
|
||||||
self.assertTrue(cmd_output["cores"] >= 0)
|
self.assertTrue(cmd_output["cores"] >= 0)
|
||||||
self.assertTrue(cmd_output["backups"] >= 0)
|
self.assertTrue(cmd_output["backups"] >= 0)
|
||||||
if self.haz_network:
|
if self.haz_network:
|
||||||
@ -150,6 +152,7 @@ class QuotaTests(base.TestCase):
|
|||||||
'quota show -f json --class default'
|
'quota show -f json --class default'
|
||||||
))
|
))
|
||||||
self.assertIsNotNone(cmd_output)
|
self.assertIsNotNone(cmd_output)
|
||||||
|
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
33,
|
33,
|
||||||
cmd_output["key-pairs"],
|
cmd_output["key-pairs"],
|
||||||
@ -166,6 +169,7 @@ class QuotaTests(base.TestCase):
|
|||||||
self.assertIsNotNone(cmd_output)
|
self.assertIsNotNone(cmd_output)
|
||||||
# We don't necessarily know the default quotas, we're checking the
|
# We don't necessarily know the default quotas, we're checking the
|
||||||
# returned attributes
|
# returned attributes
|
||||||
|
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
|
||||||
self.assertTrue(cmd_output["key-pairs"] >= 0)
|
self.assertTrue(cmd_output["key-pairs"] >= 0)
|
||||||
self.assertTrue(cmd_output["snapshots"] >= 0)
|
self.assertTrue(cmd_output["snapshots"] >= 0)
|
||||||
|
|
||||||
|
@ -1094,17 +1094,20 @@ class TestQuotaShow(TestQuota):
|
|||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.compute_quotas_mock.get.assert_called_once_with(
|
self.compute_quotas_mock.get.assert_called_once_with(
|
||||||
self.projects[0].id, detail=False
|
self.projects[0].id,
|
||||||
|
detail=False,
|
||||||
)
|
)
|
||||||
self.volume_quotas_mock.get.assert_called_once_with(
|
self.volume_quotas_mock.get.assert_called_once_with(
|
||||||
self.projects[0].id, usage=False
|
self.projects[0].id,
|
||||||
|
usage=False,
|
||||||
)
|
)
|
||||||
self.network.get_quota.assert_called_once_with(
|
self.network.get_quota.assert_called_once_with(
|
||||||
self.projects[0].id, details=False
|
self.projects[0].id,
|
||||||
|
details=False,
|
||||||
)
|
)
|
||||||
self.assertNotCalled(self.network.get_quota_default)
|
self.assertNotCalled(self.network.get_quota_default)
|
||||||
|
|
||||||
def test_quota_show_with_default(self):
|
def test_quota_show__with_default(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--default',
|
'--default',
|
||||||
self.projects[0].name,
|
self.projects[0].name,
|
||||||
@ -1128,30 +1131,67 @@ class TestQuotaShow(TestQuota):
|
|||||||
)
|
)
|
||||||
self.assertNotCalled(self.network.get_quota)
|
self.assertNotCalled(self.network.get_quota)
|
||||||
|
|
||||||
def test_quota_show_with_class(self):
|
def test_quota_show__with_class(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--class',
|
'--class',
|
||||||
self.projects[0].name,
|
'default',
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('quota_class', True),
|
('quota_class', True),
|
||||||
|
('project', 'default'), # project is actually a class here
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.compute_quotas_class_mock.get.assert_called_once_with('default')
|
||||||
|
self.volume_quotas_class_mock.get.assert_called_once_with('default')
|
||||||
|
# neutron doesn't have the concept of quota classes
|
||||||
|
self.assertNotCalled(self.network.get_quota)
|
||||||
|
self.assertNotCalled(self.network.get_quota_default)
|
||||||
|
|
||||||
|
def test_quota_show__with_usage(self):
|
||||||
|
# update mocks to return detailed quota instead
|
||||||
|
self.compute_quota = \
|
||||||
|
compute_fakes.FakeQuota.create_one_comp_detailed_quota()
|
||||||
|
self.compute_quotas_mock.get.return_value = self.compute_quota
|
||||||
|
self.volume_quota = \
|
||||||
|
volume_fakes.FakeQuota.create_one_detailed_quota()
|
||||||
|
self.volume_quotas_mock.get.return_value = self.volume_quota
|
||||||
|
self.network.get_quota.return_value = \
|
||||||
|
network_fakes.FakeQuota.create_one_net_detailed_quota()
|
||||||
|
|
||||||
|
arglist = [
|
||||||
|
'--usage',
|
||||||
|
self.projects[0].name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('usage', True),
|
||||||
('project', self.projects[0].name),
|
('project', self.projects[0].name),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.compute_quotas_class_mock.get.assert_called_once_with(
|
self.compute_quotas_mock.get.assert_called_once_with(
|
||||||
self.projects[0].name,
|
self.projects[0].id,
|
||||||
|
detail=True,
|
||||||
)
|
)
|
||||||
self.volume_quotas_class_mock.get.assert_called_once_with(
|
self.volume_quotas_mock.get.assert_called_once_with(
|
||||||
self.projects[0].name,
|
self.projects[0].id,
|
||||||
|
usage=True,
|
||||||
|
)
|
||||||
|
self.network.get_quota.assert_called_once_with(
|
||||||
|
self.projects[0].id,
|
||||||
|
details=True,
|
||||||
)
|
)
|
||||||
self.assertNotCalled(self.network.get_quota)
|
|
||||||
self.assertNotCalled(self.network.get_quota_default)
|
|
||||||
|
|
||||||
def test_quota_show_no_project(self):
|
def test_quota_show__no_project(self):
|
||||||
parsed_args = self.check_parser(self.cmd, [], [])
|
arglist = []
|
||||||
|
verifylist = [
|
||||||
|
('project', None),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
self.cmd.take_action(parsed_args)
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The ``quota show`` command now supports a ``--usage`` option. When
|
||||||
|
provided, this will result in the command returning usage information for
|
||||||
|
each quota. This replaces the ``quota list --detail`` command which is now
|
||||||
|
deprecated for removal.
|
||||||
|
deprecations:
|
||||||
|
- |
|
||||||
|
The ``--detail`` option for the ``quota list`` command has been deprecated
|
||||||
|
for removal. When used without the ``--detail`` option, the ``quota list``
|
||||||
|
command returned quota information for multiple projects yet when used with
|
||||||
|
this option it only returned (detailed) quota information for a single
|
||||||
|
project. This detailed quota information is now available via the
|
||||||
|
``quota show --usage`` command.
|
||||||
|
- |
|
||||||
|
The ``--project`` option for the ``quota list`` command has been deprecated
|
||||||
|
for removal. Use the ``quota show`` command instead.
|
Loading…
x
Reference in New Issue
Block a user