Skip to content

Commit f992617

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add support for get details of Quota"
2 parents 8159fc2 + 75cba9d commit f992617

7 files changed

Lines changed: 353 additions & 68 deletions

File tree

doc/source/cli/command-objects/quota.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ List quotas for all projects with non-default quota values
1717
1818
openstack quota list
1919
--compute | --network | --volume
20+
[--project <project>]
21+
[--detail]
2022
2123
.. option:: --network
2224

@@ -30,6 +32,14 @@ List quotas for all projects with non-default quota values
3032

3133
List volume quotas
3234

35+
.. option:: --project <project>
36+
37+
List quotas for this project <project> (name or ID)
38+
39+
.. option:: --detail
40+
41+
Show details about quotas usage
42+
3343
quota set
3444
---------
3545

openstackclient/common/quota.py

Lines changed: 167 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,164 @@ def _xform_get_quota(data, value, keys):
9797
return res
9898

9999

100-
class ListQuota(command.Lister):
101-
_description = _("List quotas for all projects "
102-
"with non-default quota values")
100+
class BaseQuota(object):
101+
def _get_project(self, parsed_args):
102+
if parsed_args.project is not None:
103+
identity_client = self.app.client_manager.identity
104+
project = utils.find_resource(
105+
identity_client.projects,
106+
parsed_args.project,
107+
)
108+
project_id = project.id
109+
project_name = project.name
110+
elif self.app.client_manager.auth_ref:
111+
# Get the project from the current auth
112+
project = self.app.client_manager.auth_ref
113+
project_id = project.project_id
114+
project_name = project.project_name
115+
else:
116+
project = None
117+
project_id = None
118+
project_name = None
119+
project_info = {}
120+
project_info['id'] = project_id
121+
project_info['name'] = project_name
122+
return project_info
123+
124+
def get_compute_quota(self, client, parsed_args):
125+
quota_class = (
126+
parsed_args.quota_class if 'quota_class' in parsed_args else False)
127+
detail = parsed_args.detail if 'detail' in parsed_args else False
128+
default = parsed_args.default if 'default' in parsed_args else False
129+
try:
130+
if quota_class:
131+
quota = client.quota_classes.get(parsed_args.project)
132+
else:
133+
project_info = self._get_project(parsed_args)
134+
project = project_info['id']
135+
if default:
136+
quota = client.quotas.defaults(project)
137+
else:
138+
quota = client.quotas.get(project, detail=detail)
139+
except Exception as e:
140+
if type(e).__name__ == 'EndpointNotFound':
141+
return {}
142+
else:
143+
raise
144+
return quota._info
145+
146+
def get_volume_quota(self, client, parsed_args):
147+
quota_class = (
148+
parsed_args.quota_class if 'quota_class' in parsed_args else False)
149+
default = parsed_args.default if 'default' in parsed_args else False
150+
try:
151+
if quota_class:
152+
quota = client.quota_classes.get(parsed_args.project)
153+
else:
154+
project_info = self._get_project(parsed_args)
155+
project = project_info['id']
156+
if default:
157+
quota = client.quotas.defaults(project)
158+
else:
159+
quota = client.quotas.get(project)
160+
except Exception as e:
161+
if type(e).__name__ == 'EndpointNotFound':
162+
return {}
163+
else:
164+
raise
165+
return quota._info
166+
167+
def get_network_quota(self, parsed_args):
168+
quota_class = (
169+
parsed_args.quota_class if 'quota_class' in parsed_args else False)
170+
detail = parsed_args.detail if 'detail' in parsed_args else False
171+
default = parsed_args.default if 'default' in parsed_args else False
172+
if quota_class:
173+
return {}
174+
if self.app.client_manager.is_network_endpoint_enabled():
175+
project_info = self._get_project(parsed_args)
176+
project = project_info['id']
177+
client = self.app.client_manager.network
178+
if default:
179+
network_quota = client.get_quota_default(project)
180+
if type(network_quota) is not dict:
181+
network_quota = network_quota.to_dict()
182+
else:
183+
network_quota = client.get_quota(project,
184+
details=detail)
185+
if type(network_quota) is not dict:
186+
network_quota = network_quota.to_dict()
187+
if detail:
188+
# NOTE(slaweq): Neutron returns values with key "used" but
189+
# Nova for example returns same data with key "in_use"
190+
# instead.
191+
# Because of that we need to convert Neutron key to
192+
# the same as is returned from Nova to make result
193+
# more consistent
194+
for key, values in network_quota.items():
195+
if type(values) is dict and "used" in values:
196+
values[u'in_use'] = values.pop("used")
197+
network_quota[key] = values
198+
return network_quota
199+
else:
200+
return {}
201+
202+
203+
class ListQuota(command.Lister, BaseQuota):
204+
_description = _(
205+
"List quotas for all projects with non-default quota values or "
206+
"list detailed quota informations for requested project")
207+
208+
def _get_detailed_quotas(self, parsed_args):
209+
columns = (
210+
'resource',
211+
'in_use',
212+
'reserved',
213+
'limit'
214+
)
215+
column_headers = (
216+
'Resource',
217+
'In Use',
218+
'Reserved',
219+
'Limit'
220+
)
221+
quotas = {}
222+
if parsed_args.compute:
223+
quotas.update(self.get_compute_quota(
224+
self.app.client_manager.compute, parsed_args))
225+
if parsed_args.network:
226+
quotas.update(self.get_network_quota(parsed_args))
227+
228+
result = []
229+
for resource, values in quotas.items():
230+
# NOTE(slaweq): there is no detailed quotas info for some resources
231+
# and it should't be displayed here
232+
if type(values) is dict:
233+
result.append({
234+
'resource': resource,
235+
'in_use': values.get('in_use'),
236+
'reserved': values.get('reserved'),
237+
'limit': values.get('limit')
238+
})
239+
return (column_headers,
240+
(utils.get_dict_properties(
241+
s, columns,
242+
) for s in result))
103243

104244
def get_parser(self, prog_name):
105245
parser = super(ListQuota, self).get_parser(prog_name)
246+
parser.add_argument(
247+
'--project',
248+
metavar='<project>',
249+
help=_('List quotas for this project <project> (name or ID)'),
250+
)
251+
parser.add_argument(
252+
'--detail',
253+
dest='detail',
254+
action='store_true',
255+
default=False,
256+
help=_('Show details about quotas usage')
257+
)
106258
option = parser.add_mutually_exclusive_group(required=True)
107259
option.add_argument(
108260
'--compute',
@@ -130,6 +282,8 @@ def take_action(self, parsed_args):
130282
project_ids = [getattr(p, 'id', '') for p in projects]
131283

132284
if parsed_args.compute:
285+
if parsed_args.detail:
286+
return self._get_detailed_quotas(parsed_args)
133287
compute_client = self.app.client_manager.compute
134288
for p in project_ids:
135289
try:
@@ -193,6 +347,9 @@ def take_action(self, parsed_args):
193347
) for s in result))
194348

195349
if parsed_args.volume:
350+
if parsed_args.detail:
351+
LOG.warning("Volume service doesn't provide detailed quota"
352+
" information")
196353
volume_client = self.app.client_manager.volume
197354
for p in project_ids:
198355
try:
@@ -243,6 +400,8 @@ def take_action(self, parsed_args):
243400
) for s in result))
244401

245402
if parsed_args.network:
403+
if parsed_args.detail:
404+
return self._get_detailed_quotas(parsed_args)
246405
client = self.app.client_manager.network
247406
for p in project_ids:
248407
try:
@@ -410,7 +569,7 @@ def take_action(self, parsed_args):
410569
**network_kwargs)
411570

412571

413-
class ShowQuota(command.ShowOne):
572+
class ShowQuota(command.ShowOne, BaseQuota):
414573
_description = _("Show quotas for project or class")
415574

416575
def get_parser(self, prog_name):
@@ -438,62 +597,6 @@ def get_parser(self, prog_name):
438597
)
439598
return parser
440599

441-
def _get_project(self, parsed_args):
442-
if parsed_args.project is not None:
443-
identity_client = self.app.client_manager.identity
444-
project = utils.find_resource(
445-
identity_client.projects,
446-
parsed_args.project,
447-
)
448-
project_id = project.id
449-
project_name = project.name
450-
elif self.app.client_manager.auth_ref:
451-
# Get the project from the current auth
452-
project = self.app.client_manager.auth_ref
453-
project_id = project.project_id
454-
project_name = project.project_name
455-
else:
456-
project = None
457-
project_id = None
458-
project_name = None
459-
project_info = {}
460-
project_info['id'] = project_id
461-
project_info['name'] = project_name
462-
return project_info
463-
464-
def get_compute_volume_quota(self, client, parsed_args):
465-
try:
466-
if parsed_args.quota_class:
467-
quota = client.quota_classes.get(parsed_args.project)
468-
else:
469-
project_info = self._get_project(parsed_args)
470-
project = project_info['id']
471-
if parsed_args.default:
472-
quota = client.quotas.defaults(project)
473-
else:
474-
quota = client.quotas.get(project)
475-
except Exception as e:
476-
if type(e).__name__ == 'EndpointNotFound':
477-
return {}
478-
else:
479-
raise
480-
return quota._info
481-
482-
def get_network_quota(self, parsed_args):
483-
if parsed_args.quota_class:
484-
return {}
485-
if self.app.client_manager.is_network_endpoint_enabled():
486-
project_info = self._get_project(parsed_args)
487-
project = project_info['id']
488-
client = self.app.client_manager.network
489-
if parsed_args.default:
490-
network_quota = client.get_quota_default(project)
491-
else:
492-
network_quota = client.get_quota(project)
493-
return network_quota
494-
else:
495-
return {}
496-
497600
def take_action(self, parsed_args):
498601

499602
compute_client = self.app.client_manager.compute
@@ -504,10 +607,10 @@ def take_action(self, parsed_args):
504607
# does not exist. If this is determined to be the
505608
# intended behaviour of the API we will validate
506609
# the argument with Identity ourselves later.
507-
compute_quota_info = self.get_compute_volume_quota(compute_client,
508-
parsed_args)
509-
volume_quota_info = self.get_compute_volume_quota(volume_client,
510-
parsed_args)
610+
compute_quota_info = self.get_compute_quota(compute_client,
611+
parsed_args)
612+
volume_quota_info = self.get_volume_quota(volume_client,
613+
parsed_args)
511614
network_quota_info = self.get_network_quota(parsed_args)
512615
# NOTE(reedip): Remove the below check once requirement for
513616
# Openstack SDK is fixed to version 0.9.12 and above

openstackclient/tests/functional/common/test_quota.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,38 @@ def setUpClass(cls):
3131
cls.PROJECT_NAME =\
3232
cls.get_openstack_configuration_value('auth.project_name')
3333

34+
def test_quota_list_details_compute(self):
35+
expected_headers = ["Resource", "In Use", "Reserved", "Limit"]
36+
cmd_output = json.loads(self.openstack(
37+
'quota list -f json --detail --compute'
38+
))
39+
self.assertIsNotNone(cmd_output)
40+
resources = []
41+
for row in cmd_output:
42+
row_headers = [str(r) for r in row.keys()]
43+
self.assertEqual(sorted(expected_headers), sorted(row_headers))
44+
resources.append(row['Resource'])
45+
# Ensure that returned quota is compute quota
46+
self.assertIn("instances", resources)
47+
# and that there is no network quota here
48+
self.assertNotIn("networks", resources)
49+
50+
def test_quota_list_details_network(self):
51+
expected_headers = ["Resource", "In Use", "Reserved", "Limit"]
52+
cmd_output = json.loads(self.openstack(
53+
'quota list -f json --detail --network'
54+
))
55+
self.assertIsNotNone(cmd_output)
56+
resources = []
57+
for row in cmd_output:
58+
row_headers = [str(r) for r in row.keys()]
59+
self.assertEqual(sorted(expected_headers), sorted(row_headers))
60+
resources.append(row['Resource'])
61+
# Ensure that returned quota is network quota
62+
self.assertIn("networks", resources)
63+
# and that there is no compute quota here
64+
self.assertNotIn("instances", resources)
65+
3466
def test_quota_list_network_option(self):
3567
if not self.haz_network:
3668
self.skipTest("No Network service present")

0 commit comments

Comments
 (0)