Skip to content

Commit 75cba9d

Browse files
slawqos10
authored andcommitted
Add support for get details of Quota
With passing "--detail" argument to "openstack quota list", details about current usage should be returned. It is currently supported by Nova and Neutron so details of resources from those projects can be returned. Change-Id: I48fda15b34283bb7c66ea18ed28262f48b9229fe Related-Bug: #1716043
1 parent 0a18790 commit 75cba9d

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)