Merge "Add config option to set per_share_size_limit."

This commit is contained in:
Zuul 2021-03-12 05:07:37 +00:00 committed by Gerrit Code Review
commit 0a2ae6ff51
18 changed files with 267 additions and 7 deletions

View File

@ -164,13 +164,14 @@ REST_API_VERSION_HISTORY = """
provisioning:min_share_size extra specs, provisioning:min_share_size extra specs,
which can add minimum and maximum share size restrictions which can add minimum and maximum share size restrictions
on a per share-type granularity. on a per share-type granularity.
* 2.62 - Added quota control to per share size.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
# The default api version request is defined to be the # The default api version request is defined to be the
# minimum version of the API supported. # minimum version of the API supported.
_MIN_API_VERSION = "2.0" _MIN_API_VERSION = "2.0"
_MAX_API_VERSION = "2.61" _MAX_API_VERSION = "2.62"
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -337,3 +337,7 @@ user documentation.
Ability to add minimum and maximum share size restrictions which Ability to add minimum and maximum share size restrictions which
can be set on a per share-type granularity. Added new extra specs can be set on a per share-type granularity. Added new extra specs
'provisioning:max_share_size' and 'provisioning:min_share_size'. 'provisioning:max_share_size' and 'provisioning:min_share_size'.
2.62
----
Added quota control to per share size.

View File

@ -329,6 +329,9 @@ class QuotaSetsController(QuotaSetsMixin, wsgi.Controller):
elif req.api_version_request < api_version.APIVersionRequest("2.53"): elif req.api_version_request < api_version.APIVersionRequest("2.53"):
self._ensure_specific_microversion_args_are_absent( self._ensure_specific_microversion_args_are_absent(
body, ['share_replicas', 'replica_gigabytes'], "2.53") body, ['share_replicas', 'replica_gigabytes'], "2.53")
elif req.api_version_request < api_version.APIVersionRequest("2.62"):
self._ensure_specific_microversion_args_are_absent(
body, ['per_share_gigabytes'], "2.62")
return self._update(req, id, body) return self._update(req, id, body)
@wsgi.Controller.api_version('2.7') @wsgi.Controller.api_version('2.7')

View File

@ -22,6 +22,7 @@ class ViewBuilder(common.ViewBuilder):
_detail_version_modifiers = [ _detail_version_modifiers = [
"add_share_group_quotas", "add_share_group_quotas",
"add_share_replica_quotas", "add_share_replica_quotas",
"add_per_share_gigabytes_quotas",
] ]
def detail_list(self, request, quota_class_set, quota_class=None): def detail_list(self, request, quota_class_set, quota_class=None):
@ -52,3 +53,8 @@ class ViewBuilder(common.ViewBuilder):
def add_share_replica_quotas(self, context, view, quota_class_set): def add_share_replica_quotas(self, context, view, quota_class_set):
view['share_replicas'] = quota_class_set.get('share_replicas') view['share_replicas'] = quota_class_set.get('share_replicas')
view['replica_gigabytes'] = quota_class_set.get('replica_gigabytes') view['replica_gigabytes'] = quota_class_set.get('replica_gigabytes')
@common.ViewBuilder.versioned_method("2.62")
def add_per_share_gigabytes_quotas(self, context, view, quota_class_set):
view['per_share_gigabytes'] = quota_class_set.get(
'per_share_gigabytes')

View File

@ -22,6 +22,7 @@ class ViewBuilder(common.ViewBuilder):
_detail_version_modifiers = [ _detail_version_modifiers = [
"add_share_group_quotas", "add_share_group_quotas",
"add_share_replica_quotas", "add_share_replica_quotas",
"add_per_share_gigabytes_quotas",
] ]
def detail_list(self, request, quota_set, project_id=None, def detail_list(self, request, quota_set, project_id=None,
@ -59,3 +60,7 @@ class ViewBuilder(common.ViewBuilder):
def add_share_replica_quotas(self, context, view, quota_class_set): def add_share_replica_quotas(self, context, view, quota_class_set):
view['share_replicas'] = quota_class_set.get('share_replicas') view['share_replicas'] = quota_class_set.get('share_replicas')
view['replica_gigabytes'] = quota_class_set.get('replica_gigabytes') view['replica_gigabytes'] = quota_class_set.get('replica_gigabytes')
@common.ViewBuilder.versioned_method("2.62")
def add_per_share_gigabytes_quotas(self, context, view, quota_set):
view['per_share_gigabytes'] = quota_set.get('per_share_gigabytes')

View File

@ -0,0 +1,61 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""add_per_share_gigabytes_quota_class
Revision ID: 0c23aec99b74
Revises: 5aa813ae673d
Create Date: 2021-01-03 10:01:57.276225
"""
# revision identifiers, used by Alembic.
revision = '0c23aec99b74'
down_revision = '5aa813ae673d'
from alembic import op
from manila.db.migrations import utils
from oslo_log import log
from oslo_utils import timeutils
from sqlalchemy import MetaData
LOG = log.getLogger(__name__)
def upgrade():
meta = MetaData()
meta.bind = op.get_bind()
connection = op.get_bind().connect()
quota_classes_table = utils.load_table('quota_classes', connection)
try:
op.bulk_insert
(quota_classes_table,
[{'created_at': timeutils.utcnow(),
'class_name': 'default',
'resource': 'per_share_gigabytes',
'hard_limit': -1,
'deleted': False, }])
except Exception:
LOG.error("Default per_share_gigabytes row not inserted "
"into the quota_classes.")
raise
def downgrade():
"""Don't delete the 'default' entries at downgrade time.
We don't know if the user had default entries when we started.
If they did, we wouldn't want to remove them. So, the safest
thing to do is just leave the 'default' entries at downgrade time.
"""
pass

View File

@ -424,6 +424,12 @@ class SnapshotSizeExceedsAvailableQuota(QuotaError):
"gigabytes quota.") "gigabytes quota.")
class ShareSizeExceedsLimit(QuotaError):
message = _(
"Requested share size %(size)d is larger than "
"maximum allowed limit %(limit)d.")
class ShareLimitExceeded(QuotaError): class ShareLimitExceeded(QuotaError):
message = _( message = _(
"Maximum number of shares allowed (%(allowed)d) either per " "Maximum number of shares allowed (%(allowed)d) either per "

View File

@ -39,6 +39,9 @@ quota_opts = [
cfg.IntOpt('quota_gigabytes', cfg.IntOpt('quota_gigabytes',
default=1000, default=1000,
help='Number of share gigabytes allowed per project.'), help='Number of share gigabytes allowed per project.'),
cfg.IntOpt('quota_per_share_gigabytes',
default=-1,
help='Max size allowed per share, in gigabytes.'),
cfg.IntOpt('quota_snapshot_gigabytes', cfg.IntOpt('quota_snapshot_gigabytes',
default=1000, default=1000,
help='Number of snapshot gigabytes allowed per project.'), help='Number of snapshot gigabytes allowed per project.'),
@ -383,6 +386,50 @@ class DbQuotaDriver(object):
return {k: v['limit'] for k, v in quotas.items()} return {k: v['limit'] for k, v in quotas.items()}
def limit_check(self, context, resources, values, project_id=None):
"""Check simple quota limits.
For limits--those quotas for which there is no usage
synchronization function--this method checks that a set of
proposed values are permitted by the limit restriction.
This method will raise a QuotaResourceUnknown exception if a
given resource is unknown or if it is not a simple limit
resource.
If any of the proposed values is over the defined quota, an
OverQuota exception will be raised with the sorted list of the
resources which are too high. Otherwise, the method returns
nothing.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resources.
:param values: A dictionary of the values to check against the
quota.
:param project_id: Specify the project_id if current context
is admin and admin wants to impact on
common user's tenant.
"""
# Ensure no value is less than zero
unders = [key for key, val in values.items() if val < 0]
if unders:
raise exception.InvalidQuotaValue(unders=sorted(unders))
# If project_id is None, then we use the project_id in context
if project_id is None:
project_id = context.project_id
quotas = self._get_quotas(context, resources, values.keys(),
has_sync=False, project_id=project_id)
# Check the quotas and construct a list of the resources that
# would be put over limit by the desired values
overs = [key for key, val in values.items()
if quotas[key] >= 0 and quotas[key] < val]
if overs:
raise exception.OverQuota(overs=sorted(overs), quotas=quotas,
usages={})
def reserve(self, context, resources, deltas, expire=None, def reserve(self, context, resources, deltas, expire=None,
project_id=None, user_id=None, share_type_id=None, project_id=None, user_id=None, share_type_id=None,
overquota_allowed=False): overquota_allowed=False):
@ -657,7 +704,8 @@ class ReservableResource(BaseResource):
""" """
super(ReservableResource, self).__init__(name, flag=flag) super(ReservableResource, self).__init__(name, flag=flag)
self.sync = sync if sync:
self.sync = sync
class AbsoluteResource(BaseResource): class AbsoluteResource(BaseResource):
@ -876,6 +924,34 @@ class QuotaEngine(object):
return res.count(context, *args, **kwargs) return res.count(context, *args, **kwargs)
def limit_check(self, context, project_id=None, **values):
"""Check simple quota limits.
For limits--those quotas for which there is no usage
synchronization function--this method checks that a set of
proposed values are permitted by the limit restriction. The
values to check are given as keyword arguments, where the key
identifies the specific quota limit to check, and the value is
the proposed value.
This method will raise a QuotaResourceUnknown exception if a
given resource is unknown or if it is not a simple limit
resource.
If any of the proposed values is over the defined quota, an
OverQuota exception will be raised with the sorted list of the
resources which are too high. Otherwise, the method returns
nothing.
:param context: The request context, for access checks.
:param project_id: Specify the project_id if current context
is admin and admin wants to impact on
common user's tenant.
"""
return self._driver.limit_check(context, self._resources, values,
project_id=project_id)
def reserve(self, context, expire=None, project_id=None, user_id=None, def reserve(self, context, expire=None, project_id=None, user_id=None,
share_type_id=None, overquota_allowed=False, **deltas): share_type_id=None, overquota_allowed=False, **deltas):
"""Check quotas and reserve resources. """Check quotas and reserve resources.
@ -1059,6 +1135,8 @@ resources = [
ReservableResource('shares', '_sync_shares', 'quota_shares'), ReservableResource('shares', '_sync_shares', 'quota_shares'),
ReservableResource('snapshots', '_sync_snapshots', 'quota_snapshots'), ReservableResource('snapshots', '_sync_snapshots', 'quota_snapshots'),
ReservableResource('gigabytes', '_sync_gigabytes', 'quota_gigabytes'), ReservableResource('gigabytes', '_sync_gigabytes', 'quota_gigabytes'),
ReservableResource('per_share_gigabytes', None,
'quota_per_share_gigabytes'),
ReservableResource('snapshot_gigabytes', '_sync_snapshot_gigabytes', ReservableResource('snapshot_gigabytes', '_sync_snapshot_gigabytes',
'quota_snapshot_gigabytes'), 'quota_snapshot_gigabytes'),
ReservableResource('share_networks', '_sync_share_networks', ReservableResource('share_networks', '_sync_share_networks',

View File

@ -236,6 +236,8 @@ class API(base.Base):
supported=CONF.enabled_share_protocols)) supported=CONF.enabled_share_protocols))
raise exception.InvalidInput(reason=msg) raise exception.InvalidInput(reason=msg)
self._check_is_share_size_within_per_share_quota_limit(context, size)
deltas = {'shares': 1, 'gigabytes': size} deltas = {'shares': 1, 'gigabytes': size}
share_type_attributes = self.get_share_attributes_from_share_type( share_type_attributes = self.get_share_attributes_from_share_type(
share_type) share_type)
@ -2020,6 +2022,17 @@ class API(base.Base):
} }
raise exception.ShareBusyException(reason=msg) raise exception.ShareBusyException(reason=msg)
def _check_is_share_size_within_per_share_quota_limit(self, context, size):
"""Raises an exception if share size above per share quota limit."""
try:
values = {'per_share_gigabytes': size}
QUOTAS.limit_check(context, project_id=context.project_id,
**values)
except exception.OverQuota as e:
quotas = e.kwargs['quotas']
raise exception.ShareSizeExceedsLimit(
size=size, limit=quotas['per_share_gigabytes'])
def _check_metadata_properties(self, metadata=None): def _check_metadata_properties(self, metadata=None):
if not metadata: if not metadata:
metadata = {} metadata = {}
@ -2098,6 +2111,9 @@ class API(base.Base):
'size': share['size']}) 'size': share['size']})
raise exception.InvalidInput(reason=msg) raise exception.InvalidInput(reason=msg)
self._check_is_share_size_within_per_share_quota_limit(context,
new_size)
# ensure we pass the share_type provisioning filter on size # ensure we pass the share_type provisioning filter on size
try: try:
share_type = share_types.get_share_type( share_type = share_types.get_share_type(

View File

@ -2674,6 +2674,16 @@ class ShareManager(manager.SchedulerDependentManager):
share_types.provision_filter_on_size(context, share_types.provision_filter_on_size(context,
share_type, share_type,
share_update.get('size')) share_update.get('size'))
try:
values = {'per_share_gigabytes': share_update.get('size')}
QUOTAS.limit_check(context, project_id=context.project_id,
**values)
except exception.OverQuota as e:
quotas = e.kwargs['quotas']
LOG.warning("Requested share size %(size)d is larger than "
"maximum allowed limit %(limit)d.",
{'size': share_update.get('size'),
'limit': quotas['per_share_gigabytes']})
deltas = { deltas = {
'project_id': project_id, 'project_id': project_id,

View File

@ -62,6 +62,7 @@ class QuotaSetsControllerTest(test.TestCase):
('os-', '2.6', quota_class_sets.QuotaClassSetsControllerLegacy), ('os-', '2.6', quota_class_sets.QuotaClassSetsControllerLegacy),
('', '2.7', quota_class_sets.QuotaClassSetsController), ('', '2.7', quota_class_sets.QuotaClassSetsController),
('', '2.53', quota_class_sets.QuotaClassSetsController), ('', '2.53', quota_class_sets.QuotaClassSetsController),
('', '2.62', quota_class_sets.QuotaClassSetsController),
) )
@ddt.unpack @ddt.unpack
def test_show_quota(self, url, version, controller): def test_show_quota(self, url, version, controller):
@ -94,6 +95,8 @@ class QuotaSetsControllerTest(test.TestCase):
if req.api_version_request >= api_version.APIVersionRequest("2.53"): if req.api_version_request >= api_version.APIVersionRequest("2.53"):
expected['quota_class_set']['share_replicas'] = 100 expected['quota_class_set']['share_replicas'] = 100
expected['quota_class_set']['replica_gigabytes'] = 1000 expected['quota_class_set']['replica_gigabytes'] = 1000
if req.api_version_request >= api_version.APIVersionRequest("2.62"):
expected['quota_class_set']['per_share_gigabytes'] = -1
result = controller().show(req, self.class_name) result = controller().show(req, self.class_name)
@ -119,6 +122,7 @@ class QuotaSetsControllerTest(test.TestCase):
('os-', '2.6', quota_class_sets.QuotaClassSetsControllerLegacy), ('os-', '2.6', quota_class_sets.QuotaClassSetsControllerLegacy),
('', '2.7', quota_class_sets.QuotaClassSetsController), ('', '2.7', quota_class_sets.QuotaClassSetsController),
('', '2.53', quota_class_sets.QuotaClassSetsController), ('', '2.53', quota_class_sets.QuotaClassSetsController),
('', '2.62', quota_class_sets.QuotaClassSetsController),
) )
@ddt.unpack @ddt.unpack
def test_update_quota(self, url, version, controller): def test_update_quota(self, url, version, controller):
@ -148,6 +152,8 @@ class QuotaSetsControllerTest(test.TestCase):
if req.api_version_request >= api_version.APIVersionRequest("2.53"): if req.api_version_request >= api_version.APIVersionRequest("2.53"):
expected['quota_class_set']['share_replicas'] = 100 expected['quota_class_set']['share_replicas'] = 100
expected['quota_class_set']['replica_gigabytes'] = 1000 expected['quota_class_set']['replica_gigabytes'] = 1000
if req.api_version_request >= api_version.APIVersionRequest("2.62"):
expected['quota_class_set']['per_share_gigabytes'] = -1
update_result = controller().update( update_result = controller().update(
req, self.class_name, body=body) req, self.class_name, body=body)

View File

@ -37,6 +37,7 @@ from manila import utils
CONF = cfg.CONF CONF = cfg.CONF
sg_quota_keys = ['share_groups', 'share_group_snapshots'] sg_quota_keys = ['share_groups', 'share_group_snapshots']
replica_quota_keys = ['share_replicas'] replica_quota_keys = ['share_replicas']
per_share_size_quota_keys = ['per_share_gigabytes']
def _get_request(is_admin, user_in_url): def _get_request(is_admin, user_in_url):
@ -172,7 +173,6 @@ class QuotaSetsControllerTest(test.TestCase):
'reserved': 0, 'reserved': 0,
}, },
}} }}
for k, v in quotas.items(): for k, v in quotas.items():
CONF.set_default('quota_' + k, v) CONF.set_default('quota_' + k, v)
@ -277,6 +277,7 @@ class QuotaSetsControllerTest(test.TestCase):
({"quota_set": {"foo": "bar"}}, sg_quota_keys, '2.40'), ({"quota_set": {"foo": "bar"}}, sg_quota_keys, '2.40'),
({"foo": "bar"}, replica_quota_keys, '2.53'), ({"foo": "bar"}, replica_quota_keys, '2.53'),
({"quota_set": {"foo": "bar"}}, replica_quota_keys, '2.53'), ({"quota_set": {"foo": "bar"}}, replica_quota_keys, '2.53'),
({"quota_set": {"foo": "bar"}}, per_share_size_quota_keys, '2.62'),
) )
@ddt.unpack @ddt.unpack
def test__ensure_specific_microversion_args_are_absent_success( def test__ensure_specific_microversion_args_are_absent_success(
@ -293,6 +294,8 @@ class QuotaSetsControllerTest(test.TestCase):
({"quota_set": {"share_group_snapshots": 8}}, sg_quota_keys, '2.40'), ({"quota_set": {"share_group_snapshots": 8}}, sg_quota_keys, '2.40'),
({"quota_set": {"share_replicas": 9}}, replica_quota_keys, '2.53'), ({"quota_set": {"share_replicas": 9}}, replica_quota_keys, '2.53'),
({"quota_set": {"share_replicas": 10}}, replica_quota_keys, '2.53'), ({"quota_set": {"share_replicas": 10}}, replica_quota_keys, '2.53'),
({"quota_set": {"per_share_gigabytes": 10}},
per_share_size_quota_keys, '2.62'),
) )
@ddt.unpack @ddt.unpack
def test__ensure_specific_microversion_args_are_absent_error( def test__ensure_specific_microversion_args_are_absent_error(
@ -351,7 +354,6 @@ class QuotaSetsControllerTest(test.TestCase):
}, },
} }
} }
for k, v in quotas.items(): for k, v in quotas.items():
CONF.set_default('quota_' + k, v) CONF.set_default('quota_' + k, v)

View File

@ -35,6 +35,7 @@ class ViewBuilderTestCase(test.TestCase):
("fake_quota_class", "2.40"), (None, "2.40"), ("fake_quota_class", "2.40"), (None, "2.40"),
("fake_quota_class", "2.39"), (None, "2.39"), ("fake_quota_class", "2.39"), (None, "2.39"),
("fake_quota_class", "2.53"), (None, "2.53"), ("fake_quota_class", "2.53"), (None, "2.53"),
("fake_quota_class", "2.62"), (None, "2.62"),
) )
@ddt.unpack @ddt.unpack
def test_detail_list_with_share_type(self, quota_class, microversion): def test_detail_list_with_share_type(self, quota_class, microversion):
@ -75,6 +76,12 @@ class ViewBuilderTestCase(test.TestCase):
quota_class_set['share_replicas'] = fake_share_replicas_value quota_class_set['share_replicas'] = fake_share_replicas_value
quota_class_set['replica_gigabytes'] = fake_replica_gigabytes_value quota_class_set['replica_gigabytes'] = fake_replica_gigabytes_value
if req.api_version_request >= api_version.APIVersionRequest("2.62"):
fake_per_share_gigabytes = 10
expected[self.builder._collection_name][
"per_share_gigabytes"] = fake_per_share_gigabytes
quota_class_set['per_share_gigabytes'] = fake_per_share_gigabytes
result = self.builder.detail_list( result = self.builder.detail_list(
req, quota_class_set, quota_class=quota_class) req, quota_class_set, quota_class=quota_class)

View File

@ -43,6 +43,9 @@ class ViewBuilderTestCase(test.TestCase):
(None, 'fake_share_type_id', "2.53"), (None, 'fake_share_type_id', "2.53"),
('fake_project_id', None, "2.53"), ('fake_project_id', None, "2.53"),
(None, None, "2.53"), (None, None, "2.53"),
(None, 'fake_share_type_id', "2.62"),
('fake_project_id', None, "2.62"),
(None, None, "2.62"),
) )
@ddt.unpack @ddt.unpack
def test_detail_list_with_share_type(self, project_id, share_type, def test_detail_list_with_share_type(self, project_id, share_type,
@ -86,6 +89,12 @@ class ViewBuilderTestCase(test.TestCase):
quota_set['share_replicas'] = fake_share_replicas_value quota_set['share_replicas'] = fake_share_replicas_value
quota_set['replica_gigabytes'] = fake_replica_gigabytes_value quota_set['replica_gigabytes'] = fake_replica_gigabytes_value
if req.api_version_request >= api_version.APIVersionRequest("2.62"):
fake_per_share_gigabytes = 10
expected[self.builder._collection_name]["per_share_gigabytes"] = (
fake_per_share_gigabytes)
quota_set['per_share_gigabytes'] = fake_per_share_gigabytes
result = self.builder.detail_list( result = self.builder.detail_list(
req, quota_set, project_id=project_id, share_type=share_type) req, quota_set, project_id=project_id, share_type=share_type)

View File

@ -889,6 +889,31 @@ class ShareAPITestCase(test.TestCase):
self.context, share_type_id=None, self.context, share_type_id=None,
shares=1, gigabytes=share_data['size']) shares=1, gigabytes=share_data['size'])
@ddt.data({'overs': {'per_share_gigabytes': 'fake'},
'expected_exception': exception.ShareSizeExceedsLimit})
@ddt.unpack
def test_create_share_over_per_share_quota(self, overs,
expected_exception):
share, share_data = self._setup_create_mocks()
quota.CONF.set_default("quota_per_share_gigabytes", 5)
share_data['size'] = 20
usages = {'per_share_gigabytes': {'reserved': 0, 'in_use': 0}}
quotas = {'per_share_gigabytes': 10}
exc = exception.OverQuota(overs=overs, usages=usages, quotas=quotas)
self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(side_effect=exc))
self.assertRaises(
expected_exception,
self.api.create,
self.context,
share_data['share_proto'],
share_data['size'],
share_data['display_name'],
share_data['display_description']
)
@ddt.data(exception.QuotaError, exception.InvalidShare) @ddt.data(exception.QuotaError, exception.InvalidShare)
def test_create_share_error_on_quota_commit(self, expected_exception): def test_create_share_error_on_quota_commit(self, expected_exception):
share, share_data = self._setup_create_mocks() share, share_data = self._setup_create_mocks()
@ -2823,6 +2848,14 @@ class ShareAPITestCase(test.TestCase):
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
self.api.extend, self.context, share, new_size) self.api.extend, self.context, share, new_size)
def test_extend_share_over_per_share_quota(self):
quota.CONF.set_default("quota_per_share_gigabytes", 5)
share = db_utils.create_share(status=constants.STATUS_AVAILABLE,
size=4)
new_size = 6
self.assertRaises(exception.ShareSizeExceedsLimit,
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):
share = db_utils.create_share(status=constants.STATUS_AVAILABLE, share = db_utils.create_share(status=constants.STATUS_AVAILABLE,
size=3) size=3)

View File

@ -602,3 +602,10 @@ class ManilaExceptionResponseCode413(test.TestCase):
# verify response code for exception.PortLimitExceeded # verify response code for exception.PortLimitExceeded
e = exception.PortLimitExceeded() e = exception.PortLimitExceeded()
self.assertEqual(413, e.code) self.assertEqual(413, e.code)
def test_per_share_limit_exceeded(self):
# verify response code for exception.ShareSizeExceedsLimit
size = 779 # amount of share size
limit = 775 # amount of allowed share size limit
e = exception.ShareSizeExceedsLimit(size=size, limit=limit)
self.assertEqual(413, e.code)

View File

@ -714,7 +714,7 @@ class QuotaEngineTestCase(test.TestCase):
def test_current_common_resources(self): def test_current_common_resources(self):
self.assertEqual( self.assertEqual(
['gigabytes', 'replica_gigabytes', 'share_group_snapshots', ['gigabytes', 'per_share_gigabytes', 'replica_gigabytes',
'share_groups', 'share_networks', 'share_replicas', 'shares', 'share_group_snapshots', 'share_groups', 'share_networks',
'snapshot_gigabytes', 'snapshots'], 'share_replicas', 'shares', 'snapshot_gigabytes', 'snapshots'],
quota.QUOTAS.resources) quota.QUOTAS.resources)

View File

@ -0,0 +1,6 @@
---
features:
- |
'quota_per_share_gigabytes' config option allows admin to set per share
size limit for a project. The default value is -1["No Limit"] always
unless changed in manila.conf by admin.