diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index 5a66a159f..0fae895e0 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.52' +MAX_VERSION = '2.53' 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 f0a6f1c21..9e5cf736b 100644 --- a/manilaclient/tests/functional/test_quotas.py +++ b/manilaclient/tests/functional/test_quotas.py @@ -24,6 +24,8 @@ from manilaclient import api_versions from manilaclient.tests.functional import base from manilaclient.tests.functional import utils +REPLICA_QUOTAS_MICROVERSION = '2.53' + def _get_share_type_quota_values(project_quota_value): project_quota_value = int(project_quota_value) @@ -153,6 +155,16 @@ class QuotasReadWriteTest(base.BaseTestCase): self.admin_client.manila, cmd, microversion='2.39') + @ddt.data('--share-replicas', '--replica-gigabytes') + @utils.skip_if_microversion_not_supported("2.52") + def test_update_quotas_for_share_replicas_using_too_old_microversion(self, + arg): + cmd = 'quota-update %s %s 10' % (self.project_id, arg) + self.assertRaises( + exceptions.CommandFailed, + self.admin_client.manila, + cmd, microversion='2.52') + @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): @@ -164,7 +176,7 @@ class QuotasReadWriteTest(base.BaseTestCase): cmd, microversion='2.40') @ddt.data(*set([ - "2.39", "2.40", api_versions.MAX_VERSION, + "2.39", "2.40", REPLICA_QUOTAS_MICROVERSION, api_versions.MAX_VERSION, ])) def test_update_share_type_quotas_positive(self, microversion): if not utils.is_microversion_supported(microversion): @@ -184,6 +196,20 @@ class QuotasReadWriteTest(base.BaseTestCase): 'snapshot_gigabytes': _get_share_type_quota_values( p_quotas['snapshot_gigabytes']), } + supports_share_replica_quotas = ( + api_versions.APIVersion(microversion) >= api_versions.APIVersion( + REPLICA_QUOTAS_MICROVERSION)) + + if supports_share_replica_quotas: + st_custom_quotas['share_replicas'] = _get_share_type_quota_values( + p_quotas['share_replicas'] + ) + st_custom_quotas['replica_gigabytes'] = ( + _get_share_type_quota_values(p_quotas['replica_gigabytes'])) + replica_params = (' --share-replicas %s ' + '--replica-gigabytes %s') % ( + st_custom_quotas['share_replicas'], + st_custom_quotas['replica_gigabytes']) # Update quotas for share type cmd = ('quota-update %s --share-type %s ' @@ -194,6 +220,9 @@ class QuotasReadWriteTest(base.BaseTestCase): st_custom_quotas['gigabytes'], st_custom_quotas['snapshots'], st_custom_quotas['snapshot_gigabytes']) + + if supports_share_replica_quotas: + cmd += replica_params self.admin_client.manila(cmd, microversion=microversion) # Verify share type quotas diff --git a/manilaclient/tests/unit/v2/test_quotas.py b/manilaclient/tests/unit/v2/test_quotas.py index b3bcb171b..b7e2d40e1 100644 --- a/manilaclient/tests/unit/v2/test_quotas.py +++ b/manilaclient/tests/unit/v2/test_quotas.py @@ -20,6 +20,8 @@ from manilaclient import api_versions from manilaclient.tests.unit import utils from manilaclient.v2 import quotas +REPLICA_QUOTAS_MICROVERSION = '2.53' + @ddt.ddt class QuotaSetsTest(utils.TestCase): @@ -104,6 +106,8 @@ class QuotaSetsTest(utils.TestCase): ("2.7", {}), ("2.7", {"force": True}), ("2.38", {}), ("2.38", {"force": True}), ("2.39", {}), ("2.39", {"force": True}), + ("2.53", {}), ("2.53", {"force": True, "share_replicas": 8, + "replica_gigabytes": 9}), ) @ddt.unpack def test_update_quota(self, microversion, extra_data): @@ -131,7 +135,8 @@ 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", "2.40") + @ddt.data("2.6", "2.7", "2.38", "2.39", "2.40", + REPLICA_QUOTAS_MICROVERSION) def test_update_user_quota(self, microversion): tenant_id = 'test' user_id = 'fake_user' @@ -157,13 +162,22 @@ class QuotaSetsTest(utils.TestCase): 'share_networks': expected_body['quota_set']['share_networks'], 'user_id': user_id, } - if microversion == '2.40': + if (api_versions.APIVersion(microversion) >= + api_versions.APIVersion('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'] + if (api_versions.APIVersion(microversion) >= + api_versions.APIVersion(REPLICA_QUOTAS_MICROVERSION)): + expected_body['quota_set']['share_replicas'] = 8 + expected_body['quota_set']['replica_gigabytes'] = 9 + kwargs['share_replicas'] = expected_body['quota_set'][ + 'share_replicas'] + kwargs['replica_gigabytes'] = expected_body['quota_set'][ + 'replica_gigabytes'] with mock.patch.object(manager, '_update', mock.Mock(return_value='fake_update')): @@ -172,11 +186,12 @@ class QuotaSetsTest(utils.TestCase): manager._update.assert_called_once_with( expected_url, expected_body, "quota_set") - def test_update_share_type_quota(self): + @ddt.data('2.39', REPLICA_QUOTAS_MICROVERSION) + def test_update_share_type_quota(self, microversion): tenant_id = 'fake_tenant_id' share_type = 'fake_share_type' - manager = self._get_manager('2.39') - resource_path = self._get_resource_path('2.39') + manager = self._get_manager(microversion) + resource_path = self._get_resource_path(microversion) expected_url = "%s/%s?share_type=%s" % ( resource_path, tenant_id, share_type) expected_body = { @@ -188,11 +203,16 @@ class QuotaSetsTest(utils.TestCase): 'snapshot_gigabytes': 4, }, } + kwargs = {} + if microversion >= REPLICA_QUOTAS_MICROVERSION: + expected_body['quota_set']['share_replicas'] = 8 + expected_body['quota_set']['replica_gigabytes'] = 9 + kwargs = {'share_replicas': 8, 'replica_gigabytes': 9} 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_type=share_type) + snapshot_gigabytes=4, share_type=share_type, **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 677a3fe6f..ea06fd1d8 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -2379,6 +2379,8 @@ class ShellTest(test_utils.TestCase): ('--share-groups 0', {'share_groups': 0}), ('--share-group-snapshots 13', {'share_group_snapshots': 13}), ('--share-group-snapshots 0', {'share_group_snapshots': 0}), + ('--share-replicas 15', {'share_replicas': 15}), + ('--replica_gigabytes 100', {'replica_gigabytes': 100}), ) @ddt.unpack def test_quota_update(self, cmd, expected_body): diff --git a/manilaclient/v2/quota_classes.py b/manilaclient/v2/quota_classes.py index bfb91ec9e..7bc3e1357 100644 --- a/manilaclient/v2/quota_classes.py +++ b/manilaclient/v2/quota_classes.py @@ -19,6 +19,7 @@ from manilaclient.common.apiclient import base as common_base RESOURCE_PATH_LEGACY = '/os-quota-class-sets' RESOURCE_PATH = '/quota-class-sets' +REPLICA_QUOTAS_MICROVERSION = "2.53" class QuotaClassSet(common_base.Resource): @@ -52,7 +53,8 @@ class QuotaClassSetManager(base.ManagerWithFind): def _do_update(self, class_name, shares=None, gigabytes=None, snapshots=None, snapshot_gigabytes=None, - share_networks=None, resource_path=RESOURCE_PATH): + share_networks=None, share_replicas=None, + replica_gigabytes=None, resource_path=RESOURCE_PATH): body = { 'quota_class_set': { 'class_name': class_name, @@ -61,6 +63,8 @@ class QuotaClassSetManager(base.ManagerWithFind): 'gigabytes': gigabytes, 'snapshot_gigabytes': snapshot_gigabytes, 'share_networks': share_networks, + "share_replicas": share_replicas, + "replica_gigabytes": replica_gigabytes } } @@ -78,12 +82,24 @@ class QuotaClassSetManager(base.ManagerWithFind): def update(self, class_name, shares=None, gigabytes=None, snapshots=None, snapshot_gigabytes=None, share_networks=None): return self._do_update( - class_name, shares, gigabytes, snapshots, snapshot_gigabytes, - share_networks, RESOURCE_PATH_LEGACY) + class_name, shares=shares, gigabytes=gigabytes, + snapshots=snapshots, snapshot_gigabytes=snapshot_gigabytes, + share_networks=share_networks, resource_path=RESOURCE_PATH_LEGACY) - @api_versions.wraps("2.7") # noqa + @api_versions.wraps("2.7", "2.52") # noqa def update(self, class_name, shares=None, gigabytes=None, snapshots=None, snapshot_gigabytes=None, share_networks=None): return self._do_update( - class_name, shares, gigabytes, snapshots, snapshot_gigabytes, - share_networks, RESOURCE_PATH) + class_name, shares=shares, gigabytes=gigabytes, + snapshots=snapshots, snapshot_gigabytes=snapshot_gigabytes, + share_networks=share_networks, resource_path=RESOURCE_PATH) + + @api_versions.wraps(REPLICA_QUOTAS_MICROVERSION) # noqa + def update(self, class_name, shares=None, gigabytes=None, + snapshots=None, snapshot_gigabytes=None, share_networks=None, + share_replicas=None, replica_gigabytes=None): + return self._do_update( + class_name, shares=shares, gigabytes=gigabytes, + snapshots=snapshots, snapshot_gigabytes=snapshot_gigabytes, + share_networks=share_networks, share_replicas=share_replicas, + replica_gigabytes=replica_gigabytes, resource_path=RESOURCE_PATH) diff --git a/manilaclient/v2/quotas.py b/manilaclient/v2/quotas.py index 99b3365cd..aee85a3a9 100644 --- a/manilaclient/v2/quotas.py +++ b/manilaclient/v2/quotas.py @@ -19,6 +19,7 @@ from manilaclient.common.apiclient import base as common_base RESOURCE_PATH_LEGACY = '/os-quota-sets' RESOURCE_PATH = '/quota-sets' +REPLICA_QUOTAS_MICROVERSION = "2.53" class QuotaSet(common_base.Resource): @@ -93,6 +94,7 @@ class QuotaSetManager(base.ManagerWithFind): share_networks=None, force=None, user_id=None, share_type=None, share_groups=None, share_group_snapshots=None, + share_replicas=None, replica_gigabytes=None, resource_path=RESOURCE_PATH): self._check_user_id_and_share_type_args(user_id, share_type) body = { @@ -106,6 +108,8 @@ class QuotaSetManager(base.ManagerWithFind): 'share_groups': share_groups, 'share_group_snapshots': share_group_snapshots, 'force': force, + 'share_replicas': share_replicas, + 'replica_gigabytes': replica_gigabytes, }, } @@ -145,14 +149,17 @@ class QuotaSetManager(base.ManagerWithFind): share_networks, force, user_id, resource_path=RESOURCE_PATH, ) - @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): + def _validate_st_and_sn_in_same_request(self, share_type, share_networks): if share_type and share_networks: raise ValueError( "'share_networks' quota can be set only for project or user, " "not share type.") + + @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): + self._validate_st_and_sn_in_same_request(share_type, share_networks) return self._do_update( tenant_id, shares, snapshots, gigabytes, snapshot_gigabytes, share_networks, force, user_id, @@ -160,16 +167,13 @@ class QuotaSetManager(base.ManagerWithFind): resource_path=RESOURCE_PATH, ) - @api_versions.wraps("2.40") # noqa + @api_versions.wraps("2.40", "2.52") # 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( - "'share_networks' quota can be set only for project or user, " - "not share type.") + self._validate_st_and_sn_in_same_request(share_type, share_networks) return self._do_update( tenant_id, shares, snapshots, gigabytes, snapshot_gigabytes, share_networks, force, user_id, @@ -179,6 +183,24 @@ class QuotaSetManager(base.ManagerWithFind): resource_path=RESOURCE_PATH, ) + @api_versions.wraps(REPLICA_QUOTAS_MICROVERSION) # 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, + share_replicas=None, replica_gigabytes=None, force=None): + self._validate_st_and_sn_in_same_request(share_type, share_networks) + return self._do_update( + 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, + share_replicas=share_replicas, + replica_gigabytes=replica_gigabytes, + resource_path=RESOURCE_PATH + ) + @api_versions.wraps("1.0", "2.6") def defaults(self, tenant_id): return self._get( diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index 22f645659..0685db5d5 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -29,7 +29,6 @@ from manilaclient.common.apiclient import utils as apiclient_utils from manilaclient.common import cliutils from manilaclient.common import constants from manilaclient import exceptions -from manilaclient.v2 import quotas def _poll_for_status(poll_fn, obj_id, action, final_ok_states, @@ -391,10 +390,12 @@ _quota_resources = [ 'gigabytes', 'snapshot_gigabytes', 'share_networks', + 'share_replicas', + 'replica_gigabytes' ] -def _quota_update(manager, identifier, args): +def _quota_class_update(manager, identifier, args): updates = {} for resource in _quota_resources: val = getattr(args, resource, None) @@ -402,15 +403,7 @@ def _quota_update(manager, identifier, args): updates[resource] = val if updates: - # default value of force is None to make sure this client - # will be compatible with old nova server - force_update = getattr(args, 'force', None) - user_id = getattr(args, 'user', None) - if isinstance(manager, quotas.QuotaSetManager): - manager.update(identifier, force=force_update, user_id=user_id, - **updates) - else: - manager.update(identifier, **updates) + manager.update(identifier, **updates) @cliutils.arg( @@ -537,6 +530,23 @@ def do_quota_defaults(cs, args): help="UUID or name of a share type to set the quotas for. Optional. " "Mutually exclusive with '--user-id'. " "Available only for microversion >= 2.39") +@cliutils.arg( + '--share-replicas', + '--share_replicas', + '--replicas', + metavar='', + type=int, + default=None, + help='New value for the "share_replicas" quota. Available only for ' + 'microversion >= 2.53') +@cliutils.arg( + '--replica-gigabytes', + '--replica_gigabytes', + metavar='', + type=int, + default=None, + help='New value for the "replica_gigabytes" quota. Available only for ' + 'microversion >= 2.53') @cliutils.arg( '--force', dest='force', @@ -570,10 +580,16 @@ def do_quota_update(cs, args): "'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.") + "Share type quotas cannot be used to constrain share groups.") kwargs["share_groups"] = args.share_groups kwargs["share_group_snapshots"] = args.share_group_snapshots + if args.share_replicas is not None or args.replica_gigabytes is not None: + if cs.api_version < api_versions.APIVersion("2.53"): + raise exceptions.CommandError( + "'share replica' quotas are available only starting with " + "'2.53' API microversion.") + kwargs["share_replicas"] = args.share_replicas + kwargs["replica_gigabytes"] = args.replica_gigabytes cs.quotas.update(**kwargs) @@ -665,10 +681,34 @@ def do_quota_class_show(cs, args): default=None, action='single_alias', help='New value for the "share_networks" quota.') +@cliutils.arg( + '--share-replicas', + '--share_replicas', # alias + '--replicas', # alias + metavar='', + type=int, + default=None, + action='single_alias', + help='New value for the "share_replicas" quota. Available only for ' + 'microversion >= 2.53') +@cliutils.arg( + '--replica-gigabytes', + '--replica_gigabytes', # alias + metavar='', + type=int, + default=None, + action='single_alias', + help='New value for the "replica_gigabytes" quota. Available only for ' + 'microversion >= 2.53') def do_quota_class_update(cs, args): """Update the quotas for a quota class (Admin only).""" + if args.share_replicas is not None or args.replica_gigabytes is not None: + if cs.api_version < api_versions.APIVersion("2.53"): + raise exceptions.CommandError( + "'share replica' quotas are available only starting with " + "'2.53' API microversion.") - _quota_update(cs.quota_classes, args.class_name, args) + _quota_class_update(cs.quota_classes, args.class_name, args) def do_absolute_limits(cs, args): diff --git a/releasenotes/notes/add-share-replicas-and-replica-gigabytes-quotas-909436c2b2420f2c.yaml b/releasenotes/notes/add-share-replicas-and-replica-gigabytes-quotas-909436c2b2420f2c.yaml new file mode 100644 index 000000000..970736e82 --- /dev/null +++ b/releasenotes/notes/add-share-replicas-and-replica-gigabytes-quotas-909436c2b2420f2c.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added support for two new quotas for share replicas: + `share_replicas` and `replica_gigabytes`. +upgrade: + - | + Due to the new 'share_replicas' and 'replica_gigabytes' quotas for + share replicas, it is now possible to hit an 'over limit' error + while creating replicated shares and share replicas. \ No newline at end of file