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
|
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.
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
return
|
||||||
|
|
||||||
size_int = int(size)
|
size_int = int(size)
|
||||||
extra_specs = share_type.get('extra_specs', {})
|
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(
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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