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 <sfinucan@redhat.com>
This commit is contained in:
Stephen Finucane 2022-09-23 15:24:43 +01:00
parent 45bec041b2
commit 44443f7856
5 changed files with 90 additions and 11 deletions

View File

@ -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,,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. 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.

1 absolute-limits limits show --absolute Lists absolute limits for a user.
94 quota-delete quota delete --volume Delete the quotas for a tenant.
95 quota-show quota show Lists quotas for a tenant.
96 quota-update quota set Updates quotas for a tenant.
97 quota-usage quota list --detail Lists quota usage for a tenant.
98 rate-limits limits show --rate Lists rate limits for a user.
99 readonly-mode-update volume set --read-only-mode | --read-write-mode Updates volume read-only access-mode flag.
100 rename volume set --name Renames a volume.

View File

@ -143,6 +143,7 @@ class BaseQuota(object):
def get_volume_quota(self, client, parsed_args): def get_volume_quota(self, client, parsed_args):
quota_class = ( quota_class = (
parsed_args.quota_class if 'quota_class' in parsed_args else False) 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 default = parsed_args.default if 'default' in parsed_args else False
try: try:
if quota_class: if quota_class:
@ -153,7 +154,7 @@ class BaseQuota(object):
if default: if default:
quota = client.quotas.defaults(project) quota = client.quotas.defaults(project)
else: else:
quota = client.quotas.get(project) quota = client.quotas.get(project, usage=detail)
except Exception as e: except Exception as e:
if type(e).__name__ == 'EndpointNotFound': if type(e).__name__ == 'EndpointNotFound':
return {} return {}
@ -195,7 +196,7 @@ class BaseQuota(object):
# more consistent # more consistent
for key, values in network_quota.items(): for key, values in network_quota.items():
if type(values) is dict and "used" in values: 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 network_quota[key] = values
return network_quota return network_quota
else: else:
@ -205,7 +206,8 @@ class BaseQuota(object):
class ListQuota(command.Lister, BaseQuota): class ListQuota(command.Lister, BaseQuota):
_description = _( _description = _(
"List quotas for all projects with non-default quota values or " "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): def _get_detailed_quotas(self, parsed_args):
columns = ( columns = (
@ -222,10 +224,21 @@ class ListQuota(command.Lister, BaseQuota):
) )
quotas = {} quotas = {}
if parsed_args.compute: if parsed_args.compute:
quotas.update(self.get_compute_quota( quotas.update(
self.app.client_manager.compute, parsed_args)) self.get_compute_quota(
self.app.client_manager.compute,
parsed_args,
)
)
if parsed_args.network: if parsed_args.network:
quotas.update(self.get_network_quota(parsed_args)) 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 = [] result = []
for resource, values in quotas.items(): for resource, values in quotas.items():
@ -359,8 +372,7 @@ class ListQuota(command.Lister, BaseQuota):
if parsed_args.volume: if parsed_args.volume:
if parsed_args.detail: if parsed_args.detail:
LOG.warning("Volume service doesn't provide detailed quota" return self._get_detailed_quotas(parsed_args)
" information")
volume_client = self.app.client_manager.volume volume_client = self.app.client_manager.volume
for p in project_ids: for p in project_ids:
try: try:

View File

@ -279,6 +279,37 @@ class TestQuotaList(TestQuota):
self.assertEqual( self.assertEqual(
sorted(detailed_reference_data), sorted(ret_quotas)) 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): def test_quota_list_compute(self):
# Two projects with non-default quotas # Two projects with non-default quotas
self.compute.quotas.get = mock.Mock( self.compute.quotas.get = mock.Mock(
@ -1001,7 +1032,7 @@ class TestQuotaSet(TestQuota):
class TestQuotaShow(TestQuota): class TestQuotaShow(TestQuota):
def setUp(self): def setUp(self):
super(TestQuotaShow, self).setUp() super().setUp()
self.compute_quota = compute_fakes.FakeQuota.create_one_comp_quota() self.compute_quota = compute_fakes.FakeQuota.create_one_comp_quota()
self.compute_quotas_mock.get.return_value = self.compute_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.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, 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
@ -1128,7 +1159,7 @@ class TestQuotaShow(TestQuota):
identity_fakes.project_id, detail=False identity_fakes.project_id, detail=False
) )
self.volume_quotas_mock.get.assert_called_once_with( 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( self.network.get_quota.assert_called_once_with(
identity_fakes.project_id, details=False identity_fakes.project_id, details=False

View File

@ -1193,6 +1193,35 @@ class FakeQuota(object):
return quota 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): class FakeLimits(object):
"""Fake limits""" """Fake limits"""

View File

@ -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