Skip to content

Commit ccd9356

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "quota: Add 'quota show --usage' option"
2 parents 351b2b1 + 04e68e0 commit ccd9356

5 files changed

Lines changed: 152 additions & 31 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,quota list --detail,Lists quota usage for a tenant.
97+
quota-usage,quota show --usage,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: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,26 @@ class ListQuota(command.Lister):
233233

234234
def get_parser(self, prog_name):
235235
parser = super().get_parser(prog_name)
236+
# TODO(stephenfin): Remove in OSC 8.0
236237
parser.add_argument(
237238
'--project',
238239
metavar='<project>',
239-
help=_('List quotas for this project <project> (name or ID)'),
240+
help=_(
241+
"**Deprecated** List quotas for this project <project> "
242+
"(name or ID). "
243+
"Use 'quota show' instead."
244+
),
240245
)
241-
# TODO(stephenfin): This doesn't belong here. We should put it into the
242-
# 'quota show' command and deprecate this.
246+
# TODO(stephenfin): Remove in OSC 8.0
243247
parser.add_argument(
244248
'--detail',
245249
dest='detail',
246250
action='store_true',
247251
default=False,
248-
help=_('Show details about quotas usage'),
252+
help=_(
253+
"**Deprecated** Show details about quotas usage. "
254+
"Use 'quota show --usage' instead."
255+
),
249256
)
250257
option = parser.add_mutually_exclusive_group(required=True)
251258
option.add_argument(
@@ -332,6 +339,19 @@ def _get_detailed_quotas(self, parsed_args):
332339
)
333340

334341
def take_action(self, parsed_args):
342+
if parsed_args.detail:
343+
msg = _(
344+
"The --detail option has been deprecated. "
345+
"Use 'openstack quota show --usage' instead."
346+
)
347+
self.log.warning(msg)
348+
elif parsed_args.project: # elif to avoid being too noisy
349+
msg = _(
350+
"The --project option has been deprecated. "
351+
"Use 'openstack quota show' instead."
352+
)
353+
self.log.warning(msg)
354+
335355
result = []
336356
project_ids = []
337357
if parsed_args.project is None:
@@ -678,7 +698,7 @@ def take_action(self, parsed_args):
678698
**network_kwargs)
679699

680700

681-
class ShowQuota(command.ShowOne):
701+
class ShowQuota(command.Lister):
682702
_description = _(
683703
"Show quotas for project or class. "
684704
"Specify ``--os-compute-api-version 2.50`` or higher to see "
@@ -692,7 +712,10 @@ def get_parser(self, prog_name):
692712
'project',
693713
metavar='<project/class>',
694714
nargs='?',
695-
help=_('Show quotas for this project or class (name or ID)'),
715+
help=_(
716+
'Show quotas for this project or class (name or ID) '
717+
'(defaults to current project)'
718+
),
696719
)
697720
type_group = parser.add_mutually_exclusive_group()
698721
type_group.add_argument(
@@ -709,6 +732,13 @@ def get_parser(self, prog_name):
709732
default=False,
710733
help=_('Show default quotas for <project>'),
711734
)
735+
type_group.add_argument(
736+
'--usage',
737+
dest='usage',
738+
action='store_true',
739+
default=False,
740+
help=_('Show details about quotas usage'),
741+
)
712742
return parser
713743

714744
def take_action(self, parsed_args):
@@ -726,18 +756,21 @@ def take_action(self, parsed_args):
726756
compute_quota_info = get_compute_quotas(
727757
self.app,
728758
project,
759+
detail=parsed_args.usage,
729760
quota_class=parsed_args.quota_class,
730761
default=parsed_args.default,
731762
)
732763
volume_quota_info = get_volume_quotas(
733764
self.app,
734765
project,
766+
detail=parsed_args.usage,
735767
quota_class=parsed_args.quota_class,
736768
default=parsed_args.default,
737769
)
738770
network_quota_info = get_network_quotas(
739771
self.app,
740772
project,
773+
detail=parsed_args.usage,
741774
quota_class=parsed_args.quota_class,
742775
default=parsed_args.default,
743776
)
@@ -762,20 +795,46 @@ def take_action(self, parsed_args):
762795
info[v] = info[k]
763796
info.pop(k)
764797

798+
# Remove the 'id' field since it's not very useful
799+
if 'id' in info:
800+
del info['id']
801+
765802
# Remove the 'location' field for resources from openstacksdk
766803
if 'location' in info:
767804
del info['location']
768805

769-
# Handle class or project ID specially as they only appear in output
770-
if parsed_args.quota_class:
771-
info.pop('id', None)
772-
elif 'id' in info:
773-
info['project'] = info.pop('id')
774-
if 'project_id' in info:
775-
del info['project_id']
776-
info['project_name'] = project_info['name']
777-
778-
return zip(*sorted(info.items()))
806+
if not parsed_args.usage:
807+
result = [
808+
{'resource': k, 'limit': v} for k, v in info.items()
809+
]
810+
else:
811+
result = [
812+
{'resource': k, **v} for k, v in info.items()
813+
]
814+
815+
columns = (
816+
'resource',
817+
'limit',
818+
)
819+
column_headers = (
820+
'Resource',
821+
'Limit',
822+
)
823+
824+
if parsed_args.usage:
825+
columns += (
826+
'in_use',
827+
'reserved',
828+
)
829+
column_headers += (
830+
'In Use',
831+
'Reserved',
832+
)
833+
834+
return (
835+
column_headers,
836+
(utils.get_dict_properties(s, columns) for s in result),
837+
)
779838

780839

781840
class DeleteQuota(command.Command):

openstackclient/tests/functional/common/test_quota.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def test_quota_set_project(self):
114114
cmd_output = json.loads(self.openstack(
115115
'quota show -f json ' + self.PROJECT_NAME
116116
))
117+
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
117118
self.assertIsNotNone(cmd_output)
118119
self.assertEqual(
119120
31,
@@ -136,6 +137,7 @@ def test_quota_set_project(self):
136137
self.assertIsNotNone(cmd_output)
137138
# We don't necessarily know the default quotas, we're checking the
138139
# returned attributes
140+
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
139141
self.assertTrue(cmd_output["cores"] >= 0)
140142
self.assertTrue(cmd_output["backups"] >= 0)
141143
if self.haz_network:
@@ -150,6 +152,7 @@ def test_quota_set_class(self):
150152
'quota show -f json --class default'
151153
))
152154
self.assertIsNotNone(cmd_output)
155+
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
153156
self.assertEqual(
154157
33,
155158
cmd_output["key-pairs"],
@@ -166,6 +169,7 @@ def test_quota_set_class(self):
166169
self.assertIsNotNone(cmd_output)
167170
# We don't necessarily know the default quotas, we're checking the
168171
# returned attributes
172+
cmd_output = {x['Resource']: x['Limit'] for x in cmd_output}
169173
self.assertTrue(cmd_output["key-pairs"] >= 0)
170174
self.assertTrue(cmd_output["snapshots"] >= 0)
171175

openstackclient/tests/unit/common/test_quota.py

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,17 +1094,20 @@ def test_quota_show(self):
10941094
self.cmd.take_action(parsed_args)
10951095

10961096
self.compute_quotas_mock.get.assert_called_once_with(
1097-
self.projects[0].id, detail=False
1097+
self.projects[0].id,
1098+
detail=False,
10981099
)
10991100
self.volume_quotas_mock.get.assert_called_once_with(
1100-
self.projects[0].id, usage=False
1101+
self.projects[0].id,
1102+
usage=False,
11011103
)
11021104
self.network.get_quota.assert_called_once_with(
1103-
self.projects[0].id, details=False
1105+
self.projects[0].id,
1106+
details=False,
11041107
)
11051108
self.assertNotCalled(self.network.get_quota_default)
11061109

1107-
def test_quota_show_with_default(self):
1110+
def test_quota_show__with_default(self):
11081111
arglist = [
11091112
'--default',
11101113
self.projects[0].name,
@@ -1128,30 +1131,67 @@ def test_quota_show_with_default(self):
11281131
)
11291132
self.assertNotCalled(self.network.get_quota)
11301133

1131-
def test_quota_show_with_class(self):
1134+
def test_quota_show__with_class(self):
11321135
arglist = [
11331136
'--class',
1134-
self.projects[0].name,
1137+
'default',
11351138
]
11361139
verifylist = [
11371140
('quota_class', True),
1138-
('project', self.projects[0].name),
1141+
('project', 'default'), # project is actually a class here
11391142
]
11401143
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
11411144

11421145
self.cmd.take_action(parsed_args)
11431146

1144-
self.compute_quotas_class_mock.get.assert_called_once_with(
1147+
self.compute_quotas_class_mock.get.assert_called_once_with('default')
1148+
self.volume_quotas_class_mock.get.assert_called_once_with('default')
1149+
# neutron doesn't have the concept of quota classes
1150+
self.assertNotCalled(self.network.get_quota)
1151+
self.assertNotCalled(self.network.get_quota_default)
1152+
1153+
def test_quota_show__with_usage(self):
1154+
# update mocks to return detailed quota instead
1155+
self.compute_quota = \
1156+
compute_fakes.FakeQuota.create_one_comp_detailed_quota()
1157+
self.compute_quotas_mock.get.return_value = self.compute_quota
1158+
self.volume_quota = \
1159+
volume_fakes.FakeQuota.create_one_detailed_quota()
1160+
self.volume_quotas_mock.get.return_value = self.volume_quota
1161+
self.network.get_quota.return_value = \
1162+
network_fakes.FakeQuota.create_one_net_detailed_quota()
1163+
1164+
arglist = [
1165+
'--usage',
11451166
self.projects[0].name,
1167+
]
1168+
verifylist = [
1169+
('usage', True),
1170+
('project', self.projects[0].name),
1171+
]
1172+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1173+
1174+
self.cmd.take_action(parsed_args)
1175+
1176+
self.compute_quotas_mock.get.assert_called_once_with(
1177+
self.projects[0].id,
1178+
detail=True,
11461179
)
1147-
self.volume_quotas_class_mock.get.assert_called_once_with(
1148-
self.projects[0].name,
1180+
self.volume_quotas_mock.get.assert_called_once_with(
1181+
self.projects[0].id,
1182+
usage=True,
1183+
)
1184+
self.network.get_quota.assert_called_once_with(
1185+
self.projects[0].id,
1186+
details=True,
11491187
)
1150-
self.assertNotCalled(self.network.get_quota)
1151-
self.assertNotCalled(self.network.get_quota_default)
11521188

1153-
def test_quota_show_no_project(self):
1154-
parsed_args = self.check_parser(self.cmd, [], [])
1189+
def test_quota_show__no_project(self):
1190+
arglist = []
1191+
verifylist = [
1192+
('project', None),
1193+
]
1194+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
11551195

11561196
self.cmd.take_action(parsed_args)
11571197

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
features:
3+
- |
4+
The ``quota show`` command now supports a ``--usage`` option. When
5+
provided, this will result in the command returning usage information for
6+
each quota. This replaces the ``quota list --detail`` command which is now
7+
deprecated for removal.
8+
deprecations:
9+
- |
10+
The ``--detail`` option for the ``quota list`` command has been deprecated
11+
for removal. When used without the ``--detail`` option, the ``quota list``
12+
command returned quota information for multiple projects yet when used with
13+
this option it only returned (detailed) quota information for a single
14+
project. This detailed quota information is now available via the
15+
``quota show --usage`` command.
16+
- |
17+
The ``--project`` option for the ``quota list`` command has been deprecated
18+
for removal. Use the ``quota show`` command instead.

0 commit comments

Comments
 (0)