Adds support min/max share size limited by share_type

Allows set min/max share size that can be created in
extra_specs for each share_type.the share size will
be checked at API level as part of share create,
extend, shrink, migration_start. when manage share,
check it after get true size of share at manager layer.
new extra_specs keys are supported for set min/max
size of share.
'provisioning:max_share_size'
'provisioning:min_share_size'

Implements: blueprint share-size-limited-by-share-type
Change-Id: I5ce0fabf59bfca5ebaf0be5ffe9986e2b0480295
This commit is contained in:
haixin 2020-12-08 19:35:03 +08:00
parent 067d44aab6
commit 74415f6d4a
12 changed files with 242 additions and 1 deletions

View File

@ -257,3 +257,13 @@ Share type common capability extra-specs that are not visible to end users:
can be accessed via IPv6 protocol. If administrators do not set this
capability as an extra-spec in a share type, the scheduler can place new
shares of that type in pools without regard for whether IPv6 is supported.
* **provisioning:max_share_size** can set the max size of share, the value
must be integers 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 greater than the specified value.
* **provisioning:min_share_size** can set the min size of share, the value
must be integers 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 less than the specified value.

View File

@ -482,6 +482,22 @@ def validate_access(*args, **kwargs):
raise webob.exc.HTTPBadRequest(explanation=exc_str)
def validate_integer(value, name, min_value=None, max_value=None):
"""Make sure that value is a valid integer, potentially within range.
:param value: the value of the integer
:param name: the name of the integer
:param min_length: the min_length of the integer
:param max_length: the max_length of the integer
:returns: integer
"""
try:
value = strutils.validate_integer(value, name, min_value, max_value)
return value
except ValueError as e:
raise webob.exc.HTTPBadRequest(explanation=str(e))
def validate_public_share_policy(context, api_params, api='create'):
"""Validates if policy allows is_public parameter to be set to True.

View File

@ -160,13 +160,17 @@ REST_API_VERSION_HISTORY = """
view.
* 2.59 - Add driver ``details`` field to migration get progress.
* 2.60 - API URLs no longer need to include a project_id parameter.
* 2.61 - Added optional provisioning:max_share_size and
provisioning:min_share_size extra spec,
which was add minimum and maximum share size restrictions
on a per share-type granularity.
"""
# The minimum and maximum versions of the API supported
# The default api version request is defined to be the
# minimum version of the API supported.
_MIN_API_VERSION = "2.0"
_MAX_API_VERSION = "2.60"
_MAX_API_VERSION = "2.61"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -331,3 +331,9 @@ user documentation.
equivalent to https://$(controller)s/share/v2/shares. When interacting
with the manila service as system or domain scoped users, project_id should
not be specified in the API path.
2.61
----
Ability to add minimum and maximum share size restrictions which
can be set on a per share-type granularity. Added new extra specs
'provisioning:max_share_size' and 'provisioning:min_share_size'.

View File

@ -235,6 +235,8 @@ class ExtraSpecs(object):
REVERT_TO_SNAPSHOT_SUPPORT = "revert_to_snapshot_support"
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"
# Extra specs containers
REQUIRED = (
@ -248,6 +250,8 @@ class ExtraSpecs(object):
REPLICATION_TYPE_SPEC,
MOUNT_SNAPSHOT_SUPPORT,
AVAILABILITY_ZONES,
PROVISIONING_MAX_SHARE_SIZE,
PROVISIONING_MIN_SHARE_SIZE
)
# NOTE(cknight): Some extra specs are necessary parts of the Manila API and

View File

@ -190,6 +190,9 @@ class API(base.Base):
"than snapshot size") % size)
raise exception.InvalidInput(reason=msg)
# ensure we pass the share_type provisioning filter on size
share_types.provision_filter_on_size(context, share_type, size)
if snapshot is None:
share_type_id = share_type['id'] if share_type else None
else:
@ -1412,6 +1415,9 @@ class API(base.Base):
if new_share_type:
share_type = new_share_type
# ensure pass the size limitations in the share type
size = share['size']
share_types.provision_filter_on_size(context, share_type, size)
new_share_type_id = new_share_type['id']
dhss = share_type['extra_specs']['driver_handles_share_servers']
dhss = strutils.bool_from_string(dhss, strict=True)
@ -2088,6 +2094,14 @@ class API(base.Base):
'size': share['size']})
raise exception.InvalidInput(reason=msg)
# ensure we pass the share_type provisioning filter on size
try:
share_type = share_types.get_share_type(
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)
replicas = self.db.share_replicas_get_all_by_share(
context, share['id'])
supports_replication = len(replicas) > 0
@ -2180,6 +2194,14 @@ class API(base.Base):
'size': share['size']})
raise exception.InvalidInput(reason=msg)
# ensure we pass the share_type provisioning filter on size
try:
share_type = share_types.get_share_type(
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)
self.update(context, share, {'status': constants.STATUS_SHRINKING})
self.share_rpcapi.shrink_share(context, share, new_size)
LOG.info("Shrink share (id=%(id)s) request issued successfully."

View File

@ -2566,6 +2566,8 @@ class ShareManager(manager.SchedulerDependentManager):
context = context.elevated()
share_ref = self.db.share_get(context, share_id)
share_instance = self._get_share_instance(context, share_ref)
share_type = share_types.get_share_type(
context, share_instance['share_type_id'])
share_type_extra_specs = self._get_extra_specs_from_share_type(
context, share_instance['share_type_id'])
share_type_supports_replication = share_type_extra_specs.get(
@ -2595,6 +2597,10 @@ class ShareManager(manager.SchedulerDependentManager):
if not share_update.get('size'):
msg = _("Driver cannot calculate share size.")
raise exception.InvalidShare(reason=msg)
else:
share_types.provision_filter_on_size(context,
share_type,
share_update.get('size'))
deltas = {
'project_id': project_id,

View File

@ -24,6 +24,7 @@ from oslo_utils import strutils
from oslo_utils import uuidutils
import six
from manila.api import common
from manila.common import constants
from manila import context
from manila import db
@ -33,6 +34,9 @@ from manila.i18n import _
CONF = cfg.CONF
LOG = log.getLogger(__name__)
MIN_SIZE_KEY = "provisioning:min_share_size"
MAX_SIZE_KEY = "provisioning:max_share_size"
def create(context, name, extra_specs=None, is_public=True,
projects=None, description=None):
@ -350,6 +354,13 @@ def is_valid_optional_extra_spec(key, value):
return parse_boolean_extra_spec(key, value) is not None
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]:
try:
common.validate_integer(value, 'share_size', min_value=1)
return True
except ValueError:
return False
return False
@ -419,3 +430,33 @@ def parse_boolean_extra_spec(extra_spec_key, extra_spec_value):
msg = (_('Invalid boolean extra spec %(key)s : %(value)s') %
{'key': extra_spec_key, 'value': extra_spec_value})
raise exception.InvalidExtraSpec(reason=msg)
def provision_filter_on_size(context, share_type, size):
"""This function filters share provisioning requests on size limits.
If a share type has provisioning size min/max set, this filter
will ensure that the share size requested is within the size
limits specified in the share type.
"""
if not share_type:
share_type = get_default_share_type()
if share_type:
size_int = int(size)
extra_specs = share_type.get('extra_specs', {})
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 "
"than the minimum required size of '%(min_size)s' "
"for share type '%(sha_type)s'."
) % {'req_size': size_int, 'min_size': min_size,
'sha_type': share_type['name']}
raise exception.InvalidInput(reason=msg)
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 "
"greater than the maximum allowable size of "
"'%(max_size)s' for share type '%(sha_type)s'."
) % {'req_size': size_int, 'max_size': max_size,
'sha_type': share_type['name']}
raise exception.InvalidInput(reason=msg)

View File

@ -120,6 +120,15 @@ class ShareAPITestCase(test.TestCase):
self.mock_object(timeutils, 'utcnow',
mock.Mock(return_value=self.dt_utc))
self.mock_object(share_api.policy, 'check_policy')
self._setup_sized_share_types()
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}
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')
def _setup_create_mocks(self, protocol='nfs', **kwargs):
share = db_utils.create_share(
@ -736,6 +745,24 @@ class ShareAPITestCase(test.TestCase):
self.assertEqual(expected_az_names, compatible_azs)
def test_create_share_with_share_type_size_limit(self):
self.assertRaises(exception.InvalidInput,
self.api.create,
self.context,
'nfs',
1,
'display_name',
'display_description',
share_type=self.sized_sha_type)
self.assertRaises(exception.InvalidInput,
self.api.create,
self.context,
'nfs',
5,
'display_name',
'display_description',
share_type=self.sized_sha_type)
@ddt.data(
{'availability_zones': None, 'azs_with_subnet': ['fake_az_1']},
{'availability_zones': ['fake_az_2'],
@ -2794,6 +2821,17 @@ class ShareAPITestCase(test.TestCase):
self.assertRaises(exception.InvalidInput,
self.api.extend, self.context, share, new_size)
def test_extend_with_share_type_size_limit(self):
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))
new_size = 5
self.assertRaises(exception.InvalidInput,
self.api.extend, self.context,
share, new_size)
def _setup_extend_mocks(self, supports_replication):
replica_list = []
if supports_replication:
@ -2933,6 +2971,17 @@ class ShareAPITestCase(test.TestCase):
self.context, share, new_size
)
def test_shrink_with_share_type_size_limit(self):
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))
new_size = 1
self.assertRaises(exception.InvalidInput,
self.api.shrink, self.context,
share, new_size)
def test_snapshot_allow_access(self):
access_to = '1.1.1.1'
access_type = 'ip'
@ -3178,6 +3227,19 @@ class ShareAPITestCase(test.TestCase):
self.context, share.instance['id'],
{'status': constants.STATUS_MIGRATING})
def test_migration_start_with_new_share_type_limit(self):
host = 'fake2@backend#pool'
self.mock_object(utils, 'validate_service_host')
share = db_utils.create_share(
status=constants.STATUS_AVAILABLE,
size=1)
self.assertRaises(exception.InvalidInput,
self.api.migration_start,
self.context,
share, host, False, True,
True, True, True, None,
self.sized_sha_type)
def test_migration_start_destination_az_unsupported(self):
host = 'fake2@backend#pool'
host_without_pool = host.split('#')[0]

View File

@ -2616,6 +2616,8 @@ class ShareManagerTestCase(test.TestCase):
self.share_manager, '_get_extra_specs_from_share_type',
mock.Mock(return_value={}))
self.mock_object(self.share_manager.db, 'share_update', mock.Mock())
self.mock_object(share_types, 'get_share_type', mock.Mock())
self.mock_object(share_types, 'provision_filter_on_size', mock.Mock())
share = db_utils.create_share()
share_id = share['id']
driver_options = {'fake': 'fake'}
@ -2722,6 +2724,8 @@ class ShareManagerTestCase(test.TestCase):
"manage_existing",
mock.Mock(return_value=None))
self.mock_object(self.share_manager.db, 'share_update', mock.Mock())
self.mock_object(share_types, 'get_share_type', mock.Mock())
self.mock_object(share_types, 'provision_filter_on_size', mock.Mock())
self.mock_object(
self.share_manager, '_get_extra_specs_from_share_type',
mock.Mock(return_value={}))
@ -2755,6 +2759,8 @@ class ShareManagerTestCase(test.TestCase):
self.mock_object(quota.QUOTAS, 'reserve',
mock.Mock(side_effect=exception.QuotaError))
self.mock_object(self.share_manager.db, 'share_update', mock.Mock())
self.mock_object(share_types, 'get_share_type', mock.Mock())
self.mock_object(share_types, 'provision_filter_on_size', mock.Mock())
self.mock_object(
self.share_manager, '_get_extra_specs_from_share_type',
mock.Mock(return_value={}))
@ -2780,6 +2786,8 @@ class ShareManagerTestCase(test.TestCase):
self.mock_object(self.share_manager, 'driver')
self.share_manager.driver.driver_handles_share_servers = False
share = db_utils.create_share()
self.mock_object(share_types, 'get_share_type', mock.Mock())
self.mock_object(share_types, 'provision_filter_on_size', mock.Mock())
self.mock_object(share_types,
'get_share_type_extra_specs',
mock.Mock(return_value="True"))
@ -2813,6 +2821,8 @@ class ShareManagerTestCase(test.TestCase):
export_locations = driver_data.get('export_locations')
self.mock_object(self.share_manager.db, 'share_update', mock.Mock())
self.mock_object(quota.QUOTAS, 'reserve', mock.Mock())
self.mock_object(share_types, 'get_share_type', mock.Mock())
self.mock_object(share_types, 'provision_filter_on_size', mock.Mock())
self.mock_object(
self.share_manager.db,
'share_export_locations_update',

View File

@ -526,3 +526,58 @@ class ShareTypesTestCase(test.TestCase):
share_types.parse_boolean_extra_spec,
'fake_key',
spec_value)
def test_provision_filter_on_size(self):
share_types.create(self.context, "type1",
extra_specs={
"key1": "val1",
"key2": "val2",
"driver_handles_share_servers": False})
share_types.create(self.context, "type2",
extra_specs={
share_types.MIN_SIZE_KEY: "12",
"key3": "val3",
"driver_handles_share_servers": False})
share_types.create(self.context, "type3",
extra_specs={
share_types.MAX_SIZE_KEY: "99",
"key4": "val4",
"driver_handles_share_servers": False})
share_types.create(self.context, "type4",
extra_specs={
share_types.MIN_SIZE_KEY: "24",
share_types.MAX_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')
share_types.provision_filter_on_size(self.context, type1, "11")
# verify minimum size requirements
type2 = share_types.get_share_type_by_name(self.context, 'type2')
self.assertRaises(exception.InvalidInput,
share_types.provision_filter_on_size,
self.context, type2, "11")
share_types.provision_filter_on_size(self.context, type2, "12")
share_types.provision_filter_on_size(self.context, type2, "100")
# verify max size requirements
type3 = share_types.get_share_type_by_name(self.context, 'type3')
self.assertRaises(exception.InvalidInput,
share_types.provision_filter_on_size,
self.context, type3, "100")
share_types.provision_filter_on_size(self.context, type3, "99")
share_types.provision_filter_on_size(self.context, type3, "1")
# verify min and max
type4 = share_types.get_share_type_by_name(self.context, 'type4')
self.assertRaises(exception.InvalidInput,
share_types.provision_filter_on_size,
self.context, type4, "20")
self.assertRaises(exception.InvalidInput,
share_types.provision_filter_on_size,
self.context, type4, "100")
share_types.provision_filter_on_size(self.context, type4, "24")
share_types.provision_filter_on_size(self.context, type4, "99")
share_types.provision_filter_on_size(self.context, type4, "30")

View File

@ -0,0 +1,5 @@
---
features:
- Ability to add minimum and maximum share size restrictions which
can be set on a per share-type granularity. Added new extra specs
'provisioning:max_share_size' and 'provisioning:min_share_size'.