diff --git a/doc/source/admin/capabilities_and_extra_specs.rst b/doc/source/admin/capabilities_and_extra_specs.rst index c73b42c697..f62c926aed 100644 --- a/doc/source/admin/capabilities_and_extra_specs.rst +++ b/doc/source/admin/capabilities_and_extra_specs.rst @@ -267,3 +267,10 @@ Share type common capability extra-specs that are not visible to end users: must be an integer and greater than 0. If administrators set this capability as an extra-spec in a share type, the size of share created with the share type can not be less than the specified value. + +* **provisioning:max_share_extend_size** can set the max size of share extend, + the value must be an integer and greater than 0. If administrators set this + capability as an extra-spec in a share type, the size of share extended with + the share type can not be greater than the specified value. This capability + is ignored for regular users and the "provisioning:max_share_size" is the + only effective limit. diff --git a/manila/common/constants.py b/manila/common/constants.py index 9a77f2703e..2ef1e9d6a5 100644 --- a/manila/common/constants.py +++ b/manila/common/constants.py @@ -245,6 +245,9 @@ REPLICATION_TYPE_WRITABLE = 'writable' REPLICATION_TYPE_DR = 'dr' +POLICY_EXTEND_BEYOND_MAX_SHARE_SIZE = 'extend_beyond_max_share_size_spec' + + class ExtraSpecs(object): # Extra specs key names @@ -257,6 +260,7 @@ class ExtraSpecs(object): AVAILABILITY_ZONES = "availability_zones" PROVISIONING_MAX_SHARE_SIZE = "provisioning:max_share_size" PROVISIONING_MIN_SHARE_SIZE = "provisioning:min_share_size" + PROVISIONING_MAX_SHARE_EXTEND_SIZE = "provisioning:max_share_extend_size" # Extra specs containers REQUIRED = ( @@ -271,7 +275,8 @@ class ExtraSpecs(object): MOUNT_SNAPSHOT_SUPPORT, AVAILABILITY_ZONES, PROVISIONING_MAX_SHARE_SIZE, - PROVISIONING_MIN_SHARE_SIZE + PROVISIONING_MIN_SHARE_SIZE, + PROVISIONING_MAX_SHARE_EXTEND_SIZE ) # NOTE(cknight): Some extra specs are necessary parts of the Manila API and diff --git a/manila/policies/shares.py b/manila/policies/shares.py index f9ad324c86..43a367827a 100644 --- a/manila/policies/shares.py +++ b/manila/policies/shares.py @@ -13,6 +13,7 @@ from oslo_log import versionutils from oslo_policy import policy +from manila.common import constants from manila.policies import base @@ -464,6 +465,17 @@ shares_policies = [ 'path': '/shares/{share_id}/action', } ]), + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % constants.POLICY_EXTEND_BEYOND_MAX_SHARE_SIZE, + check_str=base.ADMIN, + scope_types=['project'], + description="Extend share beyond max share size.", + operations=[ + { + 'method': 'POST', + 'path': '/shares/{share_id}/action', + } + ]), policy.DocumentedRuleDefault( name=BASE_POLICY_NAME % 'shrink', check_str=base.ADMIN_OR_PROJECT_MEMBER, diff --git a/manila/share/api.py b/manila/share/api.py index c29a6ceaba..f9e6951256 100644 --- a/manila/share/api.py +++ b/manila/share/api.py @@ -2460,7 +2460,17 @@ class API(base.Base): context, share['instance']['share_type_id']) except (exception.InvalidShareType, exception.ShareTypeNotFound): share_type = None - share_types.provision_filter_on_size(context, share_type, new_size) + + allowed_to_extend_past_max_share_size = policy.check_policy( + context, 'share', constants.POLICY_EXTEND_BEYOND_MAX_SHARE_SIZE, + target_obj=share, do_raise=False) + if allowed_to_extend_past_max_share_size: + share_types.provision_filter_on_size(context, share_type, + new_size, + operation='admin-extend') + else: + share_types.provision_filter_on_size(context, share_type, + new_size, operation='extend') replicas = self.db.share_replicas_get_all_by_share( context, share['id']) @@ -2570,7 +2580,8 @@ class API(base.Base): context, share['instance']['share_type_id']) except (exception.InvalidShareType, exception.ShareTypeNotFound): share_type = None - share_types.provision_filter_on_size(context, share_type, new_size) + share_types.provision_filter_on_size(context, share_type, new_size, + operation='shrink') self.update(context, share, {'status': constants.STATUS_SHRINKING}) self.share_rpcapi.shrink_share(context, share, new_size) diff --git a/manila/share/share_types.py b/manila/share/share_types.py index b847896121..59b7bf4872 100644 --- a/manila/share/share_types.py +++ b/manila/share/share_types.py @@ -37,6 +37,7 @@ QUOTAS = quota.QUOTAS MIN_SIZE_KEY = "provisioning:min_share_size" MAX_SIZE_KEY = "provisioning:max_share_size" +MAX_EXTEND_SIZE_KEY = "provisioning:max_share_extend_size" def create(context, name, extra_specs=None, is_public=True, @@ -356,7 +357,8 @@ def is_valid_optional_extra_spec(key, value): elif key == constants.ExtraSpecs.AVAILABILITY_ZONES: return is_valid_csv(value) elif key in [constants.ExtraSpecs.PROVISIONING_MAX_SHARE_SIZE, - constants.ExtraSpecs.PROVISIONING_MIN_SHARE_SIZE]: + constants.ExtraSpecs.PROVISIONING_MIN_SHARE_SIZE, + constants.ExtraSpecs.PROVISIONING_MAX_SHARE_EXTEND_SIZE]: try: common.validate_integer(value, 'share_size', min_value=1) return True @@ -433,7 +435,7 @@ def parse_boolean_extra_spec(extra_spec_key, extra_spec_value): raise exception.InvalidExtraSpec(reason=msg) -def provision_filter_on_size(context, share_type, size): +def provision_filter_on_size(context, share_type, size, operation='create'): """This function filters share provisioning requests on size limits. If a share type has provisioning size min/max set, this filter @@ -442,9 +444,12 @@ def provision_filter_on_size(context, share_type, size): """ if not share_type: share_type = get_default_share_type() - if share_type: - size_int = int(size) - extra_specs = share_type.get('extra_specs', {}) + if not share_type: + return + + size_int = int(size) + extra_specs = share_type.get('extra_specs', {}) + if operation in ['create', 'shrink']: min_size = extra_specs.get(MIN_SIZE_KEY) if min_size and size_int < int(min_size): msg = _("Specified share size of '%(req_size)d' is less " @@ -453,6 +458,7 @@ def provision_filter_on_size(context, share_type, size): ) % {'req_size': size_int, 'min_size': min_size, 'sha_type': share_type['name']} raise exception.InvalidInput(reason=msg) + if operation in ['create', 'extend']: max_size = extra_specs.get(MAX_SIZE_KEY) if max_size and size_int > int(max_size): msg = _("Specified share size of '%(req_size)d' is " @@ -461,6 +467,16 @@ def provision_filter_on_size(context, share_type, size): ) % {'req_size': size_int, 'max_size': max_size, 'sha_type': share_type['name']} raise exception.InvalidInput(reason=msg) + if operation in ['admin-extend']: + max_extend_size = extra_specs.get(MAX_EXTEND_SIZE_KEY) + if max_extend_size and size_int > int(max_extend_size): + msg = _("Specified share size of '%(req_size)d' is " + "greater than the maximum allowable extend size of " + "'%(max_extend_size)s' for share type '%(sha_type)s'." + ) % {'req_size': size_int, + 'max_extend_size': max_extend_size, + 'sha_type': share_type['name']} + raise exception.InvalidInput(reason=msg) def revert_allocated_share_type_quotas_during_migration( diff --git a/manila/tests/share/test_api.py b/manila/tests/share/test_api.py index 419d8f04aa..f6ef90424e 100644 --- a/manila/tests/share/test_api.py +++ b/manila/tests/share/test_api.py @@ -127,7 +127,8 @@ class ShareAPITestCase(test.TestCase): def _setup_sized_share_types(self): """create a share type with size limit""" spec_dict = {share_types.MIN_SIZE_KEY: 2, - share_types.MAX_SIZE_KEY: 4} + share_types.MAX_SIZE_KEY: 4, + share_types.MAX_EXTEND_SIZE_KEY: 6} db_utils.create_share_type(name='limit', extra_specs=spec_dict) self.sized_sha_type = db_api.share_type_get_by_name(self.context, 'limit') @@ -3040,14 +3041,33 @@ class ShareAPITestCase(test.TestCase): self.api.extend, self.context, share, new_size) def test_extend_with_share_type_size_limit(self): + ctx = context.RequestContext('fake_uid', 'fake_pid_1', is_admin=False) share = db_utils.create_share(status=constants.STATUS_AVAILABLE, size=3) self.mock_object(share_types, 'get_share_type', mock.Mock(return_value=self.sized_sha_type)) + self.mock_policy_check = self.mock_object( + policy, 'check_policy', mock.Mock(return_value=False)) + new_size = 5 self.assertRaises(exception.InvalidInput, - self.api.extend, self.context, + self.api.extend, ctx, + share, new_size) + + def test_extend_with_share_type_size_limit_admin(self): + ctx = context.RequestContext('fake_uid', 'fake_pid_1', is_admin=True) + share = db_utils.create_share(status=constants.STATUS_AVAILABLE, + size=3) + self.mock_object(share_types, 'get_share_type', + mock.Mock(return_value=self.sized_sha_type)) + self.mock_policy_check = self.mock_object( + policy, 'check_policy', mock.Mock(return_value=True)) + + new_size = 7 + + self.assertRaises(exception.InvalidInput, + self.api.extend, ctx, share, new_size) def _setup_extend_mocks(self, supports_replication): diff --git a/manila/tests/share/test_share_types.py b/manila/tests/share/test_share_types.py index 02f72b1519..77285bdc1f 100644 --- a/manila/tests/share/test_share_types.py +++ b/manila/tests/share/test_share_types.py @@ -551,6 +551,12 @@ class ShareTypesTestCase(test.TestCase): share_types.MAX_SIZE_KEY: "99", "key4": "val4", "driver_handles_share_servers": False}) + share_types.create(self.context, "type5", + extra_specs={ + share_types.MAX_SIZE_KEY: "95", + share_types.MAX_EXTEND_SIZE_KEY: "99", + "key4": "val4", + "driver_handles_share_servers": False}) # Make sure we don't raise if there are no min/max set type1 = share_types.get_share_type_by_name(self.context, 'type1') @@ -584,6 +590,14 @@ class ShareTypesTestCase(test.TestCase): share_types.provision_filter_on_size(self.context, type4, "99") share_types.provision_filter_on_size(self.context, type4, "30") + # verify max extend size requirements + type5 = share_types.get_share_type_by_name(self.context, 'type5') + self.assertRaises(exception.InvalidInput, + share_types.provision_filter_on_size, + self.context, type5, "100", operation="extend") + share_types.provision_filter_on_size(self.context, type5, "99", + operation="admin-extend") + @ddt.data(True, False) def test__revert_allocated_share_type_quotas_during_migration( self, failed_on_reservation): diff --git a/releasenotes/notes/max-share-extend-size-on-type-0528be9a5c27678b.yaml b/releasenotes/notes/max-share-extend-size-on-type-0528be9a5c27678b.yaml new file mode 100644 index 0000000000..cb1325f1a2 --- /dev/null +++ b/releasenotes/notes/max-share-extend-size-on-type-0528be9a5c27678b.yaml @@ -0,0 +1,5 @@ +--- +features: + - Admin-only ability to add maximum share extend size restrictions which can + be set on a per share-type granularity. Added new extra spec + 'provisioning:max_share_extend_size'.