From 6d27b2f2b6137d809b630233e7c87bdac5ca0218 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Mon, 17 Mar 2025 16:08:00 +0100 Subject: [PATCH] Fix networking quota usage show Quotas details returned from the Neutron service are in different format then quota details returned from Nova and Cinder services. This patch fixes helper function to convert data from Neutron to the same format as data from Nova and Cinder is given. Closes-Bug: #2102513 Change-Id: I18649f6c2ee179b64b7e605f4ea07d4b0c7a1635 --- openstackclient/common/quota.py | 53 ++++++++++++++----- .../tests/functional/common/test_quota.py | 2 + .../tests/unit/common/test_quota.py | 29 ++++++++-- 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 638d2e1cea..6faae8b772 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -176,25 +176,37 @@ def get_network_quotas( default=False, ): def _network_quota_to_dict(network_quota, detail=False): - if not isinstance(network_quota, dict): - dict_quota = network_quota.to_dict() - else: - dict_quota = network_quota + dict_quota = network_quota.to_dict(computed=False) - result = {} + if not detail: + return dict_quota + # Neutron returns quota details in dict which is in format like: + # {'resource_name': {'in_use': X, 'limit': Y, 'reserved': Z}, + # 'resource_name_2': {'in_use': X2, 'limit': Y2, 'reserved': Z2}} + # + # but Nova and Cinder returns quota in different format, like: + # {'resource_name': X, + # 'resource_name_2': X2, + # 'usage': { + # 'resource_name': Y, + # 'resource_name_2': Y2 + # }, + # 'reserved': { + # 'resource_name': Z, + # 'resource_name_2': Z2 + # }} + # + # so we need to make conversion to have data in same format from + # all of the services + result = {"usage": {}, "reservation": {}} for key, values in dict_quota.items(): if values is None: continue - - # NOTE(slaweq): Neutron returns values with key "used" but Nova for - # example returns same data with key "in_use" instead. Because of - # that we need to convert Neutron key to the same as is returned - # from Nova to make result more consistent - if isinstance(values, dict) and 'used' in values: - values['in_use'] = values.pop("used") - - result[key] = values + if isinstance(values, dict): + result[key] = values['limit'] + result["reservation"][key] = values['reserved'] + result["usage"][key] = values['used'] return result @@ -756,6 +768,19 @@ and ``server-group-members`` output for a given quota class.""" ) info = {} + if parsed_args.usage: + info["reservation"] = compute_quota_info.pop("reservation", {}) + info["reservation"].update( + volume_quota_info.pop("reservation", {}) + ) + info["reservation"].update( + network_quota_info.pop("reservation", {}) + ) + + info["usage"] = compute_quota_info.pop("usage", {}) + info["usage"].update(volume_quota_info.pop("usage", {})) + info["usage"].update(network_quota_info.pop("usage", {})) + info.update(compute_quota_info) info.update(volume_quota_info) info.update(network_quota_info) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 677db50367..a2edc4259d 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -250,6 +250,8 @@ class QuotaTests(base.TestCase): row_headers = [str(r) for r in row.keys()] self.assertEqual(sorted(expected_headers), sorted(row_headers)) resources.append(row['Resource']) + for header in expected_headers[1:]: + self.assertIsInstance(row[header], int) # Ensure that returned quota has network quota... self.assertIn("networks", resources) # ...and compute quota diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index e18161d4e4..fd18b0619f 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -955,6 +955,23 @@ class TestQuotaSet(TestQuota): class TestQuotaShow(TestQuota): + _network_quota_details = { + 'floating_ips': {'limit': 0, 'reserved': 0, 'used': 0}, + 'health_monitors': {'limit': 0, 'reserved': 0, 'used': 0}, + 'l7_policies': {'limit': 0, 'reserved': 0, 'used': 0}, + 'listeners': {'limit': 0, 'reserved': 0, 'used': 0}, + 'load_balancers': {'limit': 0, 'reserved': 0, 'used': 0}, + 'networks': {'limit': 0, 'reserved': 0, 'used': 0}, + 'pools': {'limit': 0, 'reserved': 0, 'used': 0}, + 'ports': {'limit': 0, 'reserved': 0, 'used': 0}, + 'rbac_policies': {'limit': 0, 'reserved': 0, 'used': 0}, + 'routers': {'limit': 0, 'reserved': 0, 'used': 0}, + 'security_group_rules': {'limit': 0, 'reserved': 0, 'used': 0}, + 'security_groups': {'limit': 0, 'reserved': 0, 'used': 0}, + 'subnet_pools': {'limit': 0, 'reserved': 0, 'used': 0}, + 'subnets': {'limit': 0, 'reserved': 0, 'used': 0}, + } + def setUp(self): super().setUp() @@ -980,9 +997,15 @@ class TestQuotaShow(TestQuota): self.default_volume_quotas ) - self.network_client.get_quota.return_value = ( - sdk_fakes.generate_fake_resource(_network_quota_set.Quota) - ) + def get_network_quota_mock(*args, **kwargs): + if kwargs.get("details"): + return sdk_fakes.generate_fake_resource( + _network_quota_set.QuotaDetails, + **self._network_quota_details, + ) + return sdk_fakes.generate_fake_resource(_network_quota_set.Quota) + + self.network_client.get_quota.side_effect = get_network_quota_mock self.default_network_quotas = sdk_fakes.generate_fake_resource( _network_quota_set.QuotaDefault )