Skip to content

Commit 44443f7

Browse files
committed
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>
1 parent 45bec04 commit 44443f7

5 files changed

Lines changed: 90 additions & 11 deletions

File tree

doc/source/cli/data/cinder.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ quota-defaults,quota show --default,Lists default quotas for a tenant.
9494
quota-delete,quota delete --volume,Delete the quotas for a tenant.
9595
quota-show,quota show,Lists quotas for a tenant.
9696
quota-update,quota set,Updates quotas for a tenant.
97-
quota-usage,,Lists quota usage for a tenant.
97+
quota-usage,quota list --detail,Lists quota usage for a tenant.
9898
rate-limits,limits show --rate,Lists rate limits for a user.
9999
readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag.
100100
rename,volume set --name,Renames a volume.

openstackclient/common/quota.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def get_compute_quota(self, client, parsed_args):
143143
def get_volume_quota(self, client, parsed_args):
144144
quota_class = (
145145
parsed_args.quota_class if 'quota_class' in parsed_args else False)
146+
detail = parsed_args.detail if 'detail' in parsed_args else False
146147
default = parsed_args.default if 'default' in parsed_args else False
147148
try:
148149
if quota_class:
@@ -153,7 +154,7 @@ def get_volume_quota(self, client, parsed_args):
153154
if default:
154155
quota = client.quotas.defaults(project)
155156
else:
156-
quota = client.quotas.get(project)
157+
quota = client.quotas.get(project, usage=detail)
157158
except Exception as e:
158159
if type(e).__name__ == 'EndpointNotFound':
159160
return {}
@@ -195,7 +196,7 @@ def get_network_quota(self, parsed_args):
195196
# more consistent
196197
for key, values in network_quota.items():
197198
if type(values) is dict and "used" in values:
198-
values[u'in_use'] = values.pop("used")
199+
values['in_use'] = values.pop("used")
199200
network_quota[key] = values
200201
return network_quota
201202
else:
@@ -205,7 +206,8 @@ def get_network_quota(self, parsed_args):
205206
class ListQuota(command.Lister, BaseQuota):
206207
_description = _(
207208
"List quotas for all projects with non-default quota values or "
208-
"list detailed quota information for requested project")
209+
"list detailed quota information for requested project"
210+
)
209211

210212
def _get_detailed_quotas(self, parsed_args):
211213
columns = (
@@ -222,10 +224,21 @@ def _get_detailed_quotas(self, parsed_args):
222224
)
223225
quotas = {}
224226
if parsed_args.compute:
225-
quotas.update(self.get_compute_quota(
226-
self.app.client_manager.compute, parsed_args))
227+
quotas.update(
228+
self.get_compute_quota(
229+
self.app.client_manager.compute,
230+
parsed_args,
231+
)
232+
)
227233
if parsed_args.network:
228234
quotas.update(self.get_network_quota(parsed_args))
235+
if parsed_args.volume:
236+
quotas.update(
237+
self.get_volume_quota(
238+
self.app.client_manager.volume,
239+
parsed_args,
240+
),
241+
)
229242

230243
result = []
231244
for resource, values in quotas.items():
@@ -359,8 +372,7 @@ def take_action(self, parsed_args):
359372

360373
if parsed_args.volume:
361374
if parsed_args.detail:
362-
LOG.warning("Volume service doesn't provide detailed quota"
363-
" information")
375+
return self._get_detailed_quotas(parsed_args)
364376
volume_client = self.app.client_manager.volume
365377
for p in project_ids:
366378
try:

openstackclient/tests/unit/common/test_quota.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,37 @@ def test_quota_list_details_network(self):
279279
self.assertEqual(
280280
sorted(detailed_reference_data), sorted(ret_quotas))
281281

282+
def test_quota_list_details_volume(self):
283+
detailed_quota = (
284+
volume_fakes.FakeQuota.create_one_detailed_quota())
285+
286+
detailed_column_header = (
287+
'Resource',
288+
'In Use',
289+
'Reserved',
290+
'Limit',
291+
)
292+
detailed_reference_data = (
293+
self._get_detailed_reference_data(detailed_quota))
294+
295+
self.volume.quotas.get = mock.Mock(return_value=detailed_quota)
296+
297+
arglist = [
298+
'--detail',
299+
'--volume',
300+
]
301+
verifylist = [
302+
('detail', True),
303+
('volume', True),
304+
]
305+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
306+
307+
columns, data = self.cmd.take_action(parsed_args)
308+
ret_quotas = list(data)
309+
310+
self.assertEqual(detailed_column_header, columns)
311+
self.assertEqual(sorted(detailed_reference_data), sorted(ret_quotas))
312+
282313
def test_quota_list_compute(self):
283314
# Two projects with non-default quotas
284315
self.compute.quotas.get = mock.Mock(
@@ -1001,7 +1032,7 @@ def test_quota_set_with_check_limit(self):
10011032
class TestQuotaShow(TestQuota):
10021033

10031034
def setUp(self):
1004-
super(TestQuotaShow, self).setUp()
1035+
super().setUp()
10051036

10061037
self.compute_quota = compute_fakes.FakeQuota.create_one_comp_quota()
10071038
self.compute_quotas_mock.get.return_value = self.compute_quota
@@ -1066,7 +1097,7 @@ def test_quota_show(self):
10661097
self.projects[0].id, detail=False
10671098
)
10681099
self.volume_quotas_mock.get.assert_called_once_with(
1069-
self.projects[0].id,
1100+
self.projects[0].id, usage=False
10701101
)
10711102
self.network.get_quota.assert_called_once_with(
10721103
self.projects[0].id, details=False
@@ -1128,7 +1159,7 @@ def test_quota_show_no_project(self):
11281159
identity_fakes.project_id, detail=False
11291160
)
11301161
self.volume_quotas_mock.get.assert_called_once_with(
1131-
identity_fakes.project_id,
1162+
identity_fakes.project_id, usage=False
11321163
)
11331164
self.network.get_quota.assert_called_once_with(
11341165
identity_fakes.project_id, details=False

openstackclient/tests/unit/volume/v2/fakes.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,35 @@ def create_one_default_vol_quota(attrs=None):
11931193

11941194
return quota
11951195

1196+
@staticmethod
1197+
def create_one_detailed_quota(attrs=None):
1198+
"""Create one quota"""
1199+
attrs = attrs or {}
1200+
1201+
quota_attrs = {
1202+
'volumes': {'limit': 3, 'in_use': 1, 'reserved': 0},
1203+
'per_volume_gigabytes': {'limit': -1, 'in_use': 0, 'reserved': 0},
1204+
'snapshots': {'limit': 10, 'in_use': 0, 'reserved': 0},
1205+
'gigabytes': {'limit': 1000, 'in_use': 5, 'reserved': 0},
1206+
'backups': {'limit': 10, 'in_use': 0, 'reserved': 0},
1207+
'backup_gigabytes': {'limit': 1000, 'in_use': 0, 'reserved': 0},
1208+
'volumes_lvmdriver-1': {'limit': -1, 'in_use': 1, 'reserved': 0},
1209+
'gigabytes_lvmdriver-1': {'limit': -1, 'in_use': 5, 'reserved': 0},
1210+
'snapshots_lvmdriver-1': {'limit': -1, 'in_use': 0, 'reserved': 0},
1211+
'volumes___DEFAULT__': {'limit': -1, 'in_use': 0, 'reserved': 0},
1212+
'gigabytes___DEFAULT__': {'limit': -1, 'in_use': 0, 'reserved': 0},
1213+
'snapshots___DEFAULT__': {'limit': -1, 'in_use': 0, 'reserved': 0},
1214+
'groups': {'limit': 10, 'in_use': 0, 'reserved': 0},
1215+
'id': uuid.uuid4().hex,
1216+
}
1217+
quota_attrs.update(attrs)
1218+
1219+
quota = fakes.FakeResource(
1220+
info=copy.deepcopy(quota_attrs),
1221+
loaded=True)
1222+
1223+
return quota
1224+
11961225

11971226
class FakeLimits(object):
11981227
"""Fake limits"""
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features:
3+
- |
4+
The ``quota list`` command can now provide detailed quotas for the volume
5+
service, e.g.::
6+
7+
$ openstack quota list --detail --volume

0 commit comments

Comments
 (0)