Add max share extend size limited by share_type

Allows set max share extend size that can be created in extra_specs
for each share_type. At API level as part of share extend for admin,
max share extend size will be checked whereas for non-admin users,
max share size will be checked.
New extra_specs key is added to set max extend size of share
i.e.'provisioning:max_share_extend_size'

Closes-Bug: #1987253
Change-Id: I61e590ed1851e8fa15996dc61e8e17e9413a9d91
This commit is contained in:
Kiran Pawar 2022-11-04 20:07:48 +00:00
parent b70e609d67
commit 7a4a81b53c
8 changed files with 100 additions and 10 deletions

View File

@ -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 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 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. 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.

View File

@ -245,6 +245,9 @@ REPLICATION_TYPE_WRITABLE = 'writable'
REPLICATION_TYPE_DR = 'dr' REPLICATION_TYPE_DR = 'dr'
POLICY_EXTEND_BEYOND_MAX_SHARE_SIZE = 'extend_beyond_max_share_size_spec'
class ExtraSpecs(object): class ExtraSpecs(object):
# Extra specs key names # Extra specs key names
@ -257,6 +260,7 @@ class ExtraSpecs(object):
AVAILABILITY_ZONES = "availability_zones" AVAILABILITY_ZONES = "availability_zones"
PROVISIONING_MAX_SHARE_SIZE = "provisioning:max_share_size" PROVISIONING_MAX_SHARE_SIZE = "provisioning:max_share_size"
PROVISIONING_MIN_SHARE_SIZE = "provisioning:min_share_size" PROVISIONING_MIN_SHARE_SIZE = "provisioning:min_share_size"
PROVISIONING_MAX_SHARE_EXTEND_SIZE = "provisioning:max_share_extend_size"
# Extra specs containers # Extra specs containers
REQUIRED = ( REQUIRED = (
@ -271,7 +275,8 @@ class ExtraSpecs(object):
MOUNT_SNAPSHOT_SUPPORT, MOUNT_SNAPSHOT_SUPPORT,
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
) )
# NOTE(cknight): Some extra specs are necessary parts of the Manila API and # NOTE(cknight): Some extra specs are necessary parts of the Manila API and

View File

@ -13,6 +13,7 @@
from oslo_log import versionutils from oslo_log import versionutils
from oslo_policy import policy from oslo_policy import policy
from manila.common import constants
from manila.policies import base from manila.policies import base
@ -464,6 +465,17 @@ shares_policies = [
'path': '/shares/{share_id}/action', '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( policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'shrink', name=BASE_POLICY_NAME % 'shrink',
check_str=base.ADMIN_OR_PROJECT_MEMBER, check_str=base.ADMIN_OR_PROJECT_MEMBER,

View File

@ -2460,7 +2460,17 @@ class API(base.Base):
context, share['instance']['share_type_id']) context, share['instance']['share_type_id'])
except (exception.InvalidShareType, exception.ShareTypeNotFound): except (exception.InvalidShareType, exception.ShareTypeNotFound):
share_type = None 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( replicas = self.db.share_replicas_get_all_by_share(
context, share['id']) context, share['id'])
@ -2570,7 +2580,8 @@ class API(base.Base):
context, share['instance']['share_type_id']) context, share['instance']['share_type_id'])
except (exception.InvalidShareType, exception.ShareTypeNotFound): except (exception.InvalidShareType, exception.ShareTypeNotFound):
share_type = None 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.update(context, share, {'status': constants.STATUS_SHRINKING})
self.share_rpcapi.shrink_share(context, share, new_size) self.share_rpcapi.shrink_share(context, share, new_size)

View File

@ -37,6 +37,7 @@ QUOTAS = quota.QUOTAS
MIN_SIZE_KEY = "provisioning:min_share_size" MIN_SIZE_KEY = "provisioning:min_share_size"
MAX_SIZE_KEY = "provisioning:max_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, 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: elif key == constants.ExtraSpecs.AVAILABILITY_ZONES:
return is_valid_csv(value) return is_valid_csv(value)
elif key in [constants.ExtraSpecs.PROVISIONING_MAX_SHARE_SIZE, 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: try:
common.validate_integer(value, 'share_size', min_value=1) common.validate_integer(value, 'share_size', min_value=1)
return True return True
@ -433,7 +435,7 @@ def parse_boolean_extra_spec(extra_spec_key, extra_spec_value):
raise exception.InvalidExtraSpec(reason=msg) 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. """This function filters share provisioning requests on size limits.
If a share type has provisioning size min/max set, this filter 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: if not share_type:
share_type = get_default_share_type() share_type = get_default_share_type()
if share_type: if not share_type:
size_int = int(size) return
extra_specs = share_type.get('extra_specs', {})
size_int = int(size)
extra_specs = share_type.get('extra_specs', {})
if operation in ['create', 'shrink']:
min_size = extra_specs.get(MIN_SIZE_KEY) min_size = extra_specs.get(MIN_SIZE_KEY)
if min_size and size_int < int(min_size): if min_size and size_int < int(min_size):
msg = _("Specified share size of '%(req_size)d' is less " 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, ) % {'req_size': size_int, 'min_size': min_size,
'sha_type': share_type['name']} 'sha_type': share_type['name']}
raise exception.InvalidInput(reason=msg) raise exception.InvalidInput(reason=msg)
if operation in ['create', 'extend']:
max_size = extra_specs.get(MAX_SIZE_KEY) max_size = extra_specs.get(MAX_SIZE_KEY)
if max_size and size_int > int(max_size): if max_size and size_int > int(max_size):
msg = _("Specified share size of '%(req_size)d' is " 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, ) % {'req_size': size_int, 'max_size': max_size,
'sha_type': share_type['name']} 'sha_type': share_type['name']}
raise exception.InvalidInput(reason=msg) 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( def revert_allocated_share_type_quotas_during_migration(

View File

@ -127,7 +127,8 @@ class ShareAPITestCase(test.TestCase):
def _setup_sized_share_types(self): def _setup_sized_share_types(self):
"""create a share type with size limit""" """create a share type with size limit"""
spec_dict = {share_types.MIN_SIZE_KEY: 2, 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) db_utils.create_share_type(name='limit', extra_specs=spec_dict)
self.sized_sha_type = db_api.share_type_get_by_name(self.context, self.sized_sha_type = db_api.share_type_get_by_name(self.context,
'limit') 'limit')
@ -3040,14 +3041,33 @@ class ShareAPITestCase(test.TestCase):
self.api.extend, self.context, share, new_size) self.api.extend, self.context, share, new_size)
def test_extend_with_share_type_size_limit(self): 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, share = db_utils.create_share(status=constants.STATUS_AVAILABLE,
size=3) size=3)
self.mock_object(share_types, 'get_share_type', self.mock_object(share_types, 'get_share_type',
mock.Mock(return_value=self.sized_sha_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 new_size = 5
self.assertRaises(exception.InvalidInput, 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) share, new_size)
def _setup_extend_mocks(self, supports_replication): def _setup_extend_mocks(self, supports_replication):

View File

@ -551,6 +551,12 @@ class ShareTypesTestCase(test.TestCase):
share_types.MAX_SIZE_KEY: "99", share_types.MAX_SIZE_KEY: "99",
"key4": "val4", "key4": "val4",
"driver_handles_share_servers": False}) "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 # Make sure we don't raise if there are no min/max set
type1 = share_types.get_share_type_by_name(self.context, 'type1') 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, "99")
share_types.provision_filter_on_size(self.context, type4, "30") 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) @ddt.data(True, False)
def test__revert_allocated_share_type_quotas_during_migration( def test__revert_allocated_share_type_quotas_during_migration(
self, failed_on_reservation): self, failed_on_reservation):

View File

@ -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'.