diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 9fe6c9d68..8b25d3fd2 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -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-show,quota show,Lists 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. 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. diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 308ad4f44..b1491700b 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='', - help=_('List quotas for this project (name or ID)'), + help=_( + "**Deprecated** List quotas for this 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='', 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 '), ) + 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'] + 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() + ] - 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): diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 783294df1..08ec626f8 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 663b62eab..53aab5f24 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', '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), ] 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.projects[0].name, + 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) diff --git a/releasenotes/notes/quota-show-usage-option-19b1f59fb5f3498f.yaml b/releasenotes/notes/quota-show-usage-option-19b1f59fb5f3498f.yaml new file mode 100644 index 000000000..b540edf4f --- /dev/null +++ b/releasenotes/notes/quota-show-usage-option-19b1f59fb5f3498f.yaml @@ -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.