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:
parent
b70e609d67
commit
7a4a81b53c
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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'.
|
Loading…
Reference in New Issue
Block a user