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>
This commit is contained in:
		| @@ -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 | ||||||
		Reference in New Issue
	
	Block a user
	 Stephen Finucane
					Stephen Finucane