From 44443f78561ce4f23d202a42de4a4ceac2ffa097 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 23 Sep 2022 15:24:43 +0100 Subject: [PATCH] quota: Add support for detailed volume quotas We were stating that this was not supported. That is not true. Correct the oversight. Change-Id: Ib9d9db641a18e142be0a1eccff783e7cccdf2db5 Signed-off-by: Stephen Finucane --- doc/source/cli/data/cinder.csv | 2 +- openstackclient/common/quota.py | 26 +++++++++---- .../tests/unit/common/test_quota.py | 37 +++++++++++++++++-- openstackclient/tests/unit/volume/v2/fakes.py | 29 +++++++++++++++ ...tailed-volume-quotas-198dc2e8f57ce1e7.yaml | 7 ++++ 5 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/detailed-volume-quotas-198dc2e8f57ce1e7.yaml diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 310ae172b0..9fe6c9d685 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,,Lists quota usage for a tenant. +quota-usage,quota list --detail,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 0110feb668..8477da9079 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -143,6 +143,7 @@ class BaseQuota(object): def get_volume_quota(self, client, parsed_args): quota_class = ( parsed_args.quota_class if 'quota_class' in parsed_args else False) + detail = parsed_args.detail if 'detail' in parsed_args else False default = parsed_args.default if 'default' in parsed_args else False try: if quota_class: @@ -153,7 +154,7 @@ class BaseQuota(object): if default: quota = client.quotas.defaults(project) else: - quota = client.quotas.get(project) + quota = client.quotas.get(project, usage=detail) except Exception as e: if type(e).__name__ == 'EndpointNotFound': return {} @@ -195,7 +196,7 @@ class BaseQuota(object): # more consistent for key, values in network_quota.items(): if type(values) is dict and "used" in values: - values[u'in_use'] = values.pop("used") + values['in_use'] = values.pop("used") network_quota[key] = values return network_quota else: @@ -205,7 +206,8 @@ class BaseQuota(object): class ListQuota(command.Lister, BaseQuota): _description = _( "List quotas for all projects with non-default quota values or " - "list detailed quota information for requested project") + "list detailed quota information for requested project" + ) def _get_detailed_quotas(self, parsed_args): columns = ( @@ -222,10 +224,21 @@ class ListQuota(command.Lister, BaseQuota): ) quotas = {} if parsed_args.compute: - quotas.update(self.get_compute_quota( - self.app.client_manager.compute, parsed_args)) + quotas.update( + self.get_compute_quota( + self.app.client_manager.compute, + parsed_args, + ) + ) if parsed_args.network: quotas.update(self.get_network_quota(parsed_args)) + if parsed_args.volume: + quotas.update( + self.get_volume_quota( + self.app.client_manager.volume, + parsed_args, + ), + ) result = [] for resource, values in quotas.items(): @@ -359,8 +372,7 @@ class ListQuota(command.Lister, BaseQuota): if parsed_args.volume: if parsed_args.detail: - LOG.warning("Volume service doesn't provide detailed quota" - " information") + return self._get_detailed_quotas(parsed_args) volume_client = self.app.client_manager.volume for p in project_ids: try: diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 087443c169..580072086d 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -279,6 +279,37 @@ class TestQuotaList(TestQuota): self.assertEqual( sorted(detailed_reference_data), sorted(ret_quotas)) + def test_quota_list_details_volume(self): + detailed_quota = ( + volume_fakes.FakeQuota.create_one_detailed_quota()) + + detailed_column_header = ( + 'Resource', + 'In Use', + 'Reserved', + 'Limit', + ) + detailed_reference_data = ( + self._get_detailed_reference_data(detailed_quota)) + + self.volume.quotas.get = mock.Mock(return_value=detailed_quota) + + arglist = [ + '--detail', + '--volume', + ] + verifylist = [ + ('detail', True), + ('volume', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(detailed_column_header, columns) + self.assertEqual(sorted(detailed_reference_data), sorted(ret_quotas)) + def test_quota_list_compute(self): # Two projects with non-default quotas self.compute.quotas.get = mock.Mock( @@ -1001,7 +1032,7 @@ class TestQuotaSet(TestQuota): class TestQuotaShow(TestQuota): def setUp(self): - super(TestQuotaShow, self).setUp() + super().setUp() self.compute_quota = compute_fakes.FakeQuota.create_one_comp_quota() self.compute_quotas_mock.get.return_value = self.compute_quota @@ -1066,7 +1097,7 @@ class TestQuotaShow(TestQuota): self.projects[0].id, detail=False ) self.volume_quotas_mock.get.assert_called_once_with( - self.projects[0].id, + self.projects[0].id, usage=False ) self.network.get_quota.assert_called_once_with( self.projects[0].id, details=False @@ -1128,7 +1159,7 @@ class TestQuotaShow(TestQuota): identity_fakes.project_id, detail=False ) self.volume_quotas_mock.get.assert_called_once_with( - identity_fakes.project_id, + identity_fakes.project_id, usage=False ) self.network.get_quota.assert_called_once_with( identity_fakes.project_id, details=False diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 96e381d3cc..6da69f8fcc 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -1193,6 +1193,35 @@ class FakeQuota(object): return quota + @staticmethod + def create_one_detailed_quota(attrs=None): + """Create one quota""" + attrs = attrs or {} + + quota_attrs = { + 'volumes': {'limit': 3, 'in_use': 1, 'reserved': 0}, + 'per_volume_gigabytes': {'limit': -1, 'in_use': 0, 'reserved': 0}, + 'snapshots': {'limit': 10, 'in_use': 0, 'reserved': 0}, + 'gigabytes': {'limit': 1000, 'in_use': 5, 'reserved': 0}, + 'backups': {'limit': 10, 'in_use': 0, 'reserved': 0}, + 'backup_gigabytes': {'limit': 1000, 'in_use': 0, 'reserved': 0}, + 'volumes_lvmdriver-1': {'limit': -1, 'in_use': 1, 'reserved': 0}, + 'gigabytes_lvmdriver-1': {'limit': -1, 'in_use': 5, 'reserved': 0}, + 'snapshots_lvmdriver-1': {'limit': -1, 'in_use': 0, 'reserved': 0}, + 'volumes___DEFAULT__': {'limit': -1, 'in_use': 0, 'reserved': 0}, + 'gigabytes___DEFAULT__': {'limit': -1, 'in_use': 0, 'reserved': 0}, + 'snapshots___DEFAULT__': {'limit': -1, 'in_use': 0, 'reserved': 0}, + 'groups': {'limit': 10, 'in_use': 0, 'reserved': 0}, + 'id': uuid.uuid4().hex, + } + quota_attrs.update(attrs) + + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + + return quota + class FakeLimits(object): """Fake limits""" diff --git a/releasenotes/notes/detailed-volume-quotas-198dc2e8f57ce1e7.yaml b/releasenotes/notes/detailed-volume-quotas-198dc2e8f57ce1e7.yaml new file mode 100644 index 0000000000..bd0ef8522b --- /dev/null +++ b/releasenotes/notes/detailed-volume-quotas-198dc2e8f57ce1e7.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The ``quota list`` command can now provide detailed quotas for the volume + service, e.g.:: + + $ openstack quota list --detail --volume