summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2022-10-12 11:22:09 +0000
committerGerrit Code Review <review@openstack.org>2022-10-12 11:22:09 +0000
commitccd935655000682102d2f024e6a0219064ac9320 (patch)
treecd5f39dcb4b420299c68a2767d28b3fb97b6163c /openstackclient
parent351b2b107431aaad620bf47286ec234d5cf48bf2 (diff)
parent04e68e0d5a49be93f79d6d71821ab8cd0b0ce589 (diff)
downloadpython-openstackclient-ccd935655000682102d2f024e6a0219064ac9320.tar.gz
Merge "quota: Add 'quota show --usage' option"
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/common/quota.py91
-rw-r--r--openstackclient/tests/functional/common/test_quota.py4
-rw-r--r--openstackclient/tests/unit/common/test_quota.py68
3 files changed, 133 insertions, 30 deletions
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py
index 308ad4f4..b1491700 100644
--- a/openstackclient/common/quota.py
+++ b/openstackclient/common/quota.py
@@ -233,19 +233,26 @@ class ListQuota(command.Lister):
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
+ # TODO(stephenfin): Remove in OSC 8.0
parser.add_argument(
'--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
- # 'quota show' command and deprecate this.
+ # TODO(stephenfin): Remove in OSC 8.0
parser.add_argument(
'--detail',
dest='detail',
action='store_true',
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.add_argument(
@@ -332,6 +339,19 @@ class ListQuota(command.Lister):
)
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 = []
project_ids = []
if parsed_args.project is None:
@@ -678,7 +698,7 @@ class SetQuota(common.NetDetectionMixin, command.Command):
**network_kwargs)
-class ShowQuota(command.ShowOne):
+class ShowQuota(command.Lister):
_description = _(
"Show quotas for project or class. "
"Specify ``--os-compute-api-version 2.50`` or higher to see "
@@ -692,7 +712,10 @@ class ShowQuota(command.ShowOne):
'project',
metavar='<project/class>',
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.add_argument(
@@ -709,6 +732,13 @@ class ShowQuota(command.ShowOne):
default=False,
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
def take_action(self, parsed_args):
@@ -726,18 +756,21 @@ class ShowQuota(command.ShowOne):
compute_quota_info = get_compute_quotas(
self.app,
project,
+ detail=parsed_args.usage,
quota_class=parsed_args.quota_class,
default=parsed_args.default,
)
volume_quota_info = get_volume_quotas(
self.app,
project,
+ detail=parsed_args.usage,
quota_class=parsed_args.quota_class,
default=parsed_args.default,
)
network_quota_info = get_network_quotas(
self.app,
project,
+ detail=parsed_args.usage,
quota_class=parsed_args.quota_class,
default=parsed_args.default,
)
@@ -762,20 +795,46 @@ class ShowQuota(command.ShowOne):
info[v] = info[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
if 'location' in info:
del info['location']
- # Handle class or project ID specially as they only appear in output
- if parsed_args.quota_class:
- info.pop('id', None)
- elif 'id' in info:
- info['project'] = info.pop('id')
- if 'project_id' in info:
- del info['project_id']
- info['project_name'] = project_info['name']
-
- return zip(*sorted(info.items()))
+ if not parsed_args.usage:
+ result = [
+ {'resource': k, 'limit': v} for k, v in info.items()
+ ]
+ else:
+ result = [
+ {'resource': k, **v} for k, v in 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):
diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py
index 783294df..08ec626f 100644
--- a/openstackclient/tests/functional/common/test_quota.py
+++ b/openstackclient/tests/functional/common/test_quota.py
@@ -114,6 +114,7 @@ class QuotaTests(base.TestCase):
cmd_output = json.loads(self.openstack(
'quota show -f json ' + self.PROJECT_NAME
))
+ cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
self.assertIsNotNone(cmd_output)
self.assertEqual(
31,
@@ -136,6 +137,7 @@ class QuotaTests(base.TestCase):
self.assertIsNotNone(cmd_output)
# We don't necessarily know the default quotas, we're checking the
# returned attributes
+ cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
self.assertTrue(cmd_output["cores"] >= 0)
self.assertTrue(cmd_output["backups"] >= 0)
if self.haz_network:
@@ -150,6 +152,7 @@ class QuotaTests(base.TestCase):
'quota show -f json --class default'
))
self.assertIsNotNone(cmd_output)
+ cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
self.assertEqual(
33,
cmd_output["key-pairs"],
@@ -166,6 +169,7 @@ class QuotaTests(base.TestCase):
self.assertIsNotNone(cmd_output)
# We don't necessarily know the default quotas, we're checking the
# returned attributes
+ cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
self.assertTrue(cmd_output["key-pairs"] >= 0)
self.assertTrue(cmd_output["snapshots"] >= 0)
diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py
index 663b62ea..53aab5f2 100644
--- a/openstackclient/tests/unit/common/test_quota.py
+++ b/openstackclient/tests/unit/common/test_quota.py
@@ -1094,17 +1094,20 @@ class TestQuotaShow(TestQuota):
self.cmd.take_action(parsed_args)
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.projects[0].id, usage=False
+ self.projects[0].id,
+ usage=False,
)
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)
- def test_quota_show_with_default(self):
+ def test_quota_show__with_default(self):
arglist = [
'--default',
self.projects[0].name,
@@ -1128,30 +1131,67 @@ class TestQuotaShow(TestQuota):
)
self.assertNotCalled(self.network.get_quota)
- def test_quota_show_with_class(self):
+ def test_quota_show__with_class(self):
arglist = [
'--class',
- self.projects[0].name,
+ 'default',
]
verifylist = [
('quota_class', True),
- ('project', self.projects[0].name),
+ ('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(
+ 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),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.compute_quotas_mock.get.assert_called_once_with(
+ self.projects[0].id,
+ detail=True,
)
- self.volume_quotas_class_mock.get.assert_called_once_with(
- self.projects[0].name,
+ self.volume_quotas_mock.get.assert_called_once_with(
+ 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):
- parsed_args = self.check_parser(self.cmd, [], [])
+ def test_quota_show__no_project(self):
+ arglist = []
+ verifylist = [
+ ('project', None),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)