Merge "quota: Add support for detailed volume quotas"
This commit is contained in:
commit
8be0c641e1
@ -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.
|
||||||
|
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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"""
|
||||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user