diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index e60cc59d5..774ded60f 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.39' +MAX_VERSION = '2.40' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/tests/functional/test_quotas.py b/manilaclient/tests/functional/test_quotas.py index 75422ae88..a176c79ae 100644 --- a/manilaclient/tests/functional/test_quotas.py +++ b/manilaclient/tests/functional/test_quotas.py @@ -20,6 +20,7 @@ from tempest.lib.cli import output_parser from tempest.lib.common.utils import data_utils from tempest.lib import exceptions +from manilaclient import api_versions from manilaclient.tests.functional import base from manilaclient.tests.functional import utils @@ -54,12 +55,12 @@ class QuotasReadWriteTest(base.BaseTestCase): ) self.st_id = self.share_type["ID"] - def _verify_current_st_quotas_equal_to(self, quotas): + def _verify_current_st_quotas_equal_to(self, quotas, microversion): # Read share type quotas cmd = 'quota-show --tenant-id %s --share-type %s' % ( self.project_id, self.st_id) st_quotas_raw = self.admin_client.manila( - cmd, microversion=self.microversion) + cmd, microversion=microversion) st_quotas = output_parser.details(st_quotas_raw) # Verify that quotas @@ -71,11 +72,109 @@ class QuotasReadWriteTest(base.BaseTestCase): self.assertIn(key, quotas) self.assertEqual(int(quotas[key]), int(value)) - def test_update_share_type_quotas_positive(self): + def _verify_current_quotas_equal_to(self, quotas, microversion): + # Read quotas + cmd = 'quota-show --tenant-id %s' % self.project_id + quotas_raw = self.admin_client.manila( + cmd, microversion=microversion) + quotas = output_parser.details(quotas_raw) + + # Verify that quotas + self.assertGreater(len(quotas), 3) + for key, value in quotas.items(): + if key not in ('shares', 'gigabytes', 'snapshots', + 'snapshot_gigabytes', + 'share_groups', 'share_group_snapshots'): + continue + self.assertIn(key, quotas) + self.assertEqual(int(quotas[key]), int(value)) + + @ddt.data(*set([ + "2.40", api_versions.MAX_VERSION, + ])) + def test_update_quotas_for_share_groups(self, microversion): + if not utils.is_microversion_supported(microversion): + msg = "Microversion '%s' not supported." % microversion + raise self.skipException(msg) + + # Get default quotas + cmd = 'quota-defaults' + quotas_raw = self.admin_client.manila(cmd, microversion=microversion) + default_quotas = output_parser.details(quotas_raw) + # Get project quotas cmd = 'quota-show --tenant-id %s ' % self.project_id - quotas_raw = self.admin_client.manila( - cmd, microversion=self.microversion) + quotas_raw = self.admin_client.manila(cmd, microversion=microversion) + p_quotas = output_parser.details(quotas_raw) + + # Define custom share group quotas for project + p_custom_quotas = { + 'share_groups': -1 if int(p_quotas['share_groups']) != -1 else 999, + 'share_group_snapshots': -1 if int( + p_quotas['share_group_snapshots']) != -1 else 999, + } + + # Update share group quotas for project + cmd = ('quota-update %s --share-groups %s ' + '--share-group-snapshots %s') % ( + self.project_id, + p_custom_quotas['share_groups'], + p_custom_quotas['share_group_snapshots'], + ) + self.admin_client.manila(cmd, microversion=microversion) + + # Verify quotas + self._verify_current_quotas_equal_to(p_custom_quotas, microversion) + + # Reset quotas + cmd = 'quota-delete --tenant-id %s --share-type %s' % ( + self.project_id, self.st_id) + self.admin_client.manila(cmd, microversion=microversion) + + # Verify quotas after reset + self._verify_current_quotas_equal_to(default_quotas, microversion) + + # Return project quotas back + cmd = ('quota-update %s --share-groups %s ' + '--share-group-snapshots %s') % ( + self.project_id, + p_quotas['share_groups'], p_quotas['share_group_snapshots']) + self.admin_client.manila(cmd, microversion=microversion) + + # Verify quotas after reset + self._verify_current_quotas_equal_to(p_quotas, microversion) + + @ddt.data('--share-groups', '--share-group-snapshots') + @utils.skip_if_microversion_not_supported("2.39") + def test_update_quotas_for_share_groups_using_too_old_microversion(self, + arg): + cmd = 'quota-update %s %s 13' % (self.project_id, arg) + self.assertRaises( + exceptions.CommandFailed, + self.admin_client.manila, + cmd, microversion='2.39') + + @ddt.data('--share-groups', '--share-group-snapshots') + @utils.skip_if_microversion_not_supported("2.40") + def test_update_share_type_quotas_for_share_groups(self, arg): + cmd = 'quota-update %s --share-type %s %s 13' % ( + self.project_id, self.st_id, arg) + self.assertRaises( + exceptions.CommandFailed, + self.admin_client.manila, + cmd, microversion='2.40') + + @ddt.data(*set([ + "2.39", "2.40", api_versions.MAX_VERSION, + ])) + def test_update_share_type_quotas_positive(self, microversion): + if not utils.is_microversion_supported(microversion): + msg = "Microversion '%s' not supported." % microversion + raise self.skipException(msg) + + # Get project quotas + cmd = 'quota-show --tenant-id %s ' % self.project_id + quotas_raw = self.admin_client.manila(cmd, microversion=microversion) p_quotas = output_parser.details(quotas_raw) # Define share type quotas @@ -96,18 +195,18 @@ class QuotasReadWriteTest(base.BaseTestCase): st_custom_quotas['gigabytes'], st_custom_quotas['snapshots'], st_custom_quotas['snapshot_gigabytes']) - self.admin_client.manila(cmd, microversion=self.microversion) + self.admin_client.manila(cmd, microversion=microversion) # Verify share type quotas - self._verify_current_st_quotas_equal_to(st_custom_quotas) + self._verify_current_st_quotas_equal_to(st_custom_quotas, microversion) # Reset share type quotas cmd = 'quota-delete --tenant-id %s --share-type %s' % ( self.project_id, self.st_id) - self.admin_client.manila(cmd, microversion=self.microversion) + self.admin_client.manila(cmd, microversion=microversion) # Verify share type quotas after reset - self._verify_current_st_quotas_equal_to(p_quotas) + self._verify_current_st_quotas_equal_to(p_quotas, microversion) @utils.skip_if_microversion_not_supported("2.38") def test_read_share_type_quotas_with_too_old_microversion(self): diff --git a/manilaclient/tests/unit/v2/fakes.py b/manilaclient/tests/unit/v2/fakes.py index 16258c621..b0cd8bb31 100644 --- a/manilaclient/tests/unit/v2/fakes.py +++ b/manilaclient/tests/unit/v2/fakes.py @@ -302,6 +302,9 @@ class FakeHTTPClient(fakes.FakeHTTPClient): } return (200, {}, instances) + def put_quota_sets_1234(self, *args, **kwargs): + return (200, {}, {}) + def get_quota_sets_1234(self, *args, **kwargs): quota_set = { 'quota_set': { diff --git a/manilaclient/tests/unit/v2/test_quotas.py b/manilaclient/tests/unit/v2/test_quotas.py index 5a793b346..b3bcb171b 100644 --- a/manilaclient/tests/unit/v2/test_quotas.py +++ b/manilaclient/tests/unit/v2/test_quotas.py @@ -131,7 +131,7 @@ class QuotaSetsTest(utils.TestCase): manager._update.assert_called_once_with( expected_url, expected_body, "quota_set") - @ddt.data("2.6", "2.7", "2.38", "2.39") + @ddt.data("2.6", "2.7", "2.38", "2.39", "2.40") def test_update_user_quota(self, microversion): tenant_id = 'test' user_id = 'fake_user' @@ -148,11 +148,26 @@ class QuotaSetsTest(utils.TestCase): 'share_networks': 5, }, } + kwargs = { + 'shares': expected_body['quota_set']['shares'], + 'snapshots': expected_body['quota_set']['snapshots'], + 'gigabytes': expected_body['quota_set']['gigabytes'], + 'snapshot_gigabytes': expected_body['quota_set'][ + 'snapshot_gigabytes'], + 'share_networks': expected_body['quota_set']['share_networks'], + 'user_id': user_id, + } + if microversion == '2.40': + expected_body['quota_set']['share_groups'] = 6 + expected_body['quota_set']['share_group_snapshots'] = 7 + kwargs['share_groups'] = expected_body['quota_set'][ + 'share_groups'] + kwargs['share_group_snapshots'] = expected_body['quota_set'][ + 'share_group_snapshots'] + with mock.patch.object(manager, '_update', mock.Mock(return_value='fake_update')): - manager.update( - tenant_id, shares=1, snapshots=2, gigabytes=3, - snapshot_gigabytes=4, share_networks=5, user_id=user_id) + manager.update(tenant_id, **kwargs) manager._update.assert_called_once_with( expected_url, expected_body, "quota_set") diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py index e84ce3719..782ab6364 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -1804,6 +1804,38 @@ class ShellTest(test_utils.TestCase): ) mock_print_dict.assert_called_once_with(mock.ANY) + @ddt.data( + ('--shares 13', {'shares': 13}), + ('--gigabytes 14', {'gigabytes': 14}), + ('--snapshots 15', {'snapshots': 15}), + ('--snapshot-gigabytes 13', {'snapshot_gigabytes': 13}), + ('--share-networks 13', {'share_networks': 13}), + ('--share-groups 13', {'share_groups': 13}), + ('--share-groups 0', {'share_groups': 0}), + ('--share-group-snapshots 13', {'share_group_snapshots': 13}), + ('--share-group-snapshots 0', {'share_group_snapshots': 0}), + ) + @ddt.unpack + def test_quota_update(self, cmd, expected_body): + self.run_command('quota-update 1234 %s' % cmd) + + expected = {'quota_set': expected_body} + self.assert_called('PUT', '/quota-sets/1234', body=expected) + + @ddt.data( + "quota-update 1234 --share-groups 13 --share-type foo", + "quota-update 1234 --share-group-snapshots 14 --share-type bar", + ("quota-update 1234 --share-groups 13 --share-type foo " + "--share-group-snapshots 14"), + "--os-share-api-version 2.39 quota-update 1234 --share-groups 13", + ("--os-share-api-version 2.39 quota-update 1234 " + "--share-group-snapshots 13"), + ("--os-share-api-version 2.38 quota-update 1234 --shares 5 " + "--share-type foo"), + ) + def test_quota_update_with_wrong_combinations(self, cmd): + self.assertRaises(exceptions.CommandError, self.run_command, cmd) + @mock.patch.object(cliutils, 'print_list', mock.Mock()) def test_pool_list_with_detail(self): self.run_command('pool-list --detail') diff --git a/manilaclient/v2/quotas.py b/manilaclient/v2/quotas.py index 7c59f9b7b..99b3365cd 100644 --- a/manilaclient/v2/quotas.py +++ b/manilaclient/v2/quotas.py @@ -90,8 +90,10 @@ class QuotaSetManager(base.ManagerWithFind): def _do_update(self, tenant_id, shares=None, snapshots=None, gigabytes=None, snapshot_gigabytes=None, - share_networks=None, force=None, user_id=None, - share_type=None, resource_path=RESOURCE_PATH): + share_networks=None, + force=None, user_id=None, share_type=None, + share_groups=None, share_group_snapshots=None, + resource_path=RESOURCE_PATH): self._check_user_id_and_share_type_args(user_id, share_type) body = { 'quota_set': { @@ -101,6 +103,8 @@ class QuotaSetManager(base.ManagerWithFind): 'gigabytes': gigabytes, 'snapshot_gigabytes': snapshot_gigabytes, 'share_networks': share_networks, + 'share_groups': share_groups, + 'share_group_snapshots': share_group_snapshots, 'force': force, }, } @@ -141,10 +145,26 @@ class QuotaSetManager(base.ManagerWithFind): share_networks, force, user_id, resource_path=RESOURCE_PATH, ) - @api_versions.wraps("2.39") # noqa + @api_versions.wraps("2.39", "2.39") # noqa + def update(self, tenant_id, user_id=None, share_type=None, + shares=None, snapshots=None, gigabytes=None, + snapshot_gigabytes=None, share_networks=None, force=None): + if share_type and share_networks: + raise ValueError( + "'share_networks' quota can be set only for project or user, " + "not share type.") + return self._do_update( + tenant_id, shares, snapshots, gigabytes, snapshot_gigabytes, + share_networks, force, user_id, + share_type=share_type, + resource_path=RESOURCE_PATH, + ) + + @api_versions.wraps("2.40") # noqa def update(self, tenant_id, user_id=None, share_type=None, shares=None, snapshots=None, gigabytes=None, snapshot_gigabytes=None, share_networks=None, + share_groups=None, share_group_snapshots=None, force=None): if share_type and share_networks: raise ValueError( @@ -154,6 +174,8 @@ class QuotaSetManager(base.ManagerWithFind): tenant_id, shares, snapshots, gigabytes, snapshot_gigabytes, share_networks, force, user_id, share_type=share_type, + share_groups=share_groups, + share_group_snapshots=share_group_snapshots, resource_path=RESOURCE_PATH, ) diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index 979a96b72..9f850d391 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -372,13 +372,6 @@ _quota_resources = [ ] -def _quota_show(quotas): - quota_dict = {} - for resource in _quota_resources: - quota_dict[resource] = getattr(quotas, resource, None) - cliutils.print_dict(quota_dict) - - def _quota_update(manager, identifier, args): updates = {} for resource in _quota_resources: @@ -450,7 +443,7 @@ def do_quota_show(cs, args): def do_quota_defaults(cs, args): """List the default quotas for a tenant.""" project = args.tenant_id or cs.keystone_client.project_id - _quota_show(cs.quotas.defaults(project)) + _quota_set_pretty_show(cs.quotas.defaults(project)) @cliutils.arg( @@ -497,6 +490,21 @@ def do_quota_defaults(cs, args): default=None, action='single_alias', help='New value for the "share_networks" quota.') +@cliutils.arg( + '--share-groups', '--share_groups', '--groups', + metavar='', + type=int, + default=None, + action='single_alias', + help='New value for the "share_groups" quota.') +@cliutils.arg( + '--share-group-snapshots', '--share_group_snapshots', + '--group-snapshots', '--group_snapshots', + metavar='', + type=int, + default=None, + action='single_alias', + help='New value for the "share_group_snapshots" quota.') @cliutils.arg( '--share-type', '--share_type', @@ -533,6 +541,17 @@ def do_quota_update(cs, args): "'share type' quotas are available only starting with " "'2.39' API microversion.") kwargs["share_type"] = args.share_type + if args.share_groups is not None or args.share_group_snapshots is not None: + if cs.api_version < api_versions.APIVersion("2.40"): + raise exceptions.CommandError( + "'share group' quotas are available only starting with " + "'2.40' API microversion.") + elif args.share_type is not None: + raise exceptions.CommandError( + "Share type quotas handle only 'shares', 'gigabytes', " + "'snapshots' and 'snapshot_gigabytes' resources.") + kwargs["share_groups"] = args.share_groups + kwargs["share_group_snapshots"] = args.share_group_snapshots cs.quotas.update(**kwargs) @@ -583,7 +602,7 @@ def do_quota_delete(cs, args): def do_quota_class_show(cs, args): """List the quotas for a quota class.""" - _quota_show(cs.quota_classes.get(args.class_name)) + _quota_set_pretty_show(cs.quota_classes.get(args.class_name)) @cliutils.arg( diff --git a/releasenotes/notes/add-share-group-quotas-support-b6563cec58209a1d.yaml b/releasenotes/notes/add-share-group-quotas-support-b6563cec58209a1d.yaml new file mode 100644 index 000000000..5820071f7 --- /dev/null +++ b/releasenotes/notes/add-share-group-quotas-support-b6563cec58209a1d.yaml @@ -0,0 +1,7 @@ +--- +features: + - Added support for share group and share group snapshot quotas. +upgrade: + - After addition of share group and share group snapshot quotas, it is now + possible to get 'over limit' error creating share groups and share group + snapshots.