fix unmange share with manage_error status will lead to quota error

if we failed to manage a share, we don't need to commit the quota usages. so
we should skip quota usages cuts when delete or unmange the share with
status of "error_manage". and the size of error_manage share should be
zero.

Closes-Bug:#1883506

Change-Id: I5c81dd6780890c55c8c6a92491c3f4f507531fdb
This commit is contained in:
haixin 2020-07-17 15:18:44 +08:00
parent d8dab2d077
commit 73d0a74f3b
8 changed files with 59 additions and 41 deletions

View File

@ -28,6 +28,7 @@ STATUS_INACTIVE = 'inactive'
STATUS_MANAGING = 'manage_starting' STATUS_MANAGING = 'manage_starting'
STATUS_MANAGE_ERROR = 'manage_error' STATUS_MANAGE_ERROR = 'manage_error'
STATUS_UNMANAGING = 'unmanage_starting' STATUS_UNMANAGING = 'unmanage_starting'
STATUS_MANAGE_ERROR_UNMANAGING = 'manage_error_unmanage_starting'
STATUS_UNMANAGE_ERROR = 'unmanage_error' STATUS_UNMANAGE_ERROR = 'unmanage_error'
STATUS_UNMANAGED = 'unmanaged' STATUS_UNMANAGED = 'unmanaged'
STATUS_EXTENDING = 'extending' STATUS_EXTENDING = 'extending'

View File

@ -129,12 +129,13 @@ class SchedulerManager(manager.Manager):
"""Ensure that the host exists and can accept the share.""" """Ensure that the host exists and can accept the share."""
def _manage_share_set_error(self, context, ex, request_spec): def _manage_share_set_error(self, context, ex, request_spec):
# NOTE(nidhimittalhada): set size as 1 because design expects size # NOTE(haixin) if failed to scheduler backend for manage share,
# to be set, it also will allow us to handle delete/unmanage # and we do not commit quota usages here, so we should set size 0
# operations properly with this errored share according to quotas. # because we don't know the real size of the size, and we will
# skip quota cuts when unmanage share with manage_error status.
self._set_share_state_and_notify( self._set_share_state_and_notify(
'manage_share', 'manage_share',
{'status': constants.STATUS_MANAGE_ERROR, 'size': 1}, {'status': constants.STATUS_MANAGE_ERROR, 'size': 0},
context, ex, request_spec) context, ex, request_spec)
share_ref = db.share_get(context, share_id) share_ref = db.share_get(context, share_id)

View File

@ -916,7 +916,12 @@ class API(base.Base):
self._check_is_share_busy(share) self._check_is_share_busy(share)
update_data = {'status': constants.STATUS_UNMANAGING, if share['status'] == constants.STATUS_MANAGE_ERROR:
update_status = constants.STATUS_MANAGE_ERROR_UNMANAGING
else:
update_status = constants.STATUS_UNMANAGING
update_data = {'status': update_status,
'terminated_at': timeutils.utcnow()} 'terminated_at': timeutils.utcnow()}
share_ref = self.db.share_update(context, share['id'], update_data) share_ref = self.db.share_update(context, share['id'], update_data)
@ -1128,7 +1133,6 @@ class API(base.Base):
_("Share still has %d dependent share group snapshot " _("Share still has %d dependent share group snapshot "
"members.") % share_group_snapshot_members_count) "members.") % share_group_snapshot_members_count)
raise exception.InvalidShare(reason=msg) raise exception.InvalidShare(reason=msg)
self._check_is_share_busy(share) self._check_is_share_busy(share)
for share_instance in share.instances: for share_instance in share.instances:
if share_instance['host']: if share_instance['host']:

View File

@ -2589,6 +2589,8 @@ class ShareManager(manager.SchedulerDependentManager):
) )
if not share_update.get('size'): if not share_update.get('size'):
# NOTE(haixin)if failed to get real size of share, will not
# commit quota usages.
msg = _("Driver cannot calculate share size.") msg = _("Driver cannot calculate share size.")
raise exception.InvalidShare(reason=msg) raise exception.InvalidShare(reason=msg)
@ -2639,12 +2641,12 @@ class ShareManager(manager.SchedulerDependentManager):
self.db.share_update(context, share_id, share_update) self.db.share_update(context, share_id, share_update)
except Exception: except Exception:
# NOTE(vponomaryov): set size as 1 because design expects size # NOTE(haixin) we should set size 0 because we don't know the real
# to be set, it also will allow us to handle delete/unmanage # size of the size, and we will skip quota cuts when
# operations properly with this errored share according to quotas. # delete/unmanage share.
self.db.share_update( self.db.share_update(
context, share_id, context, share_id,
{'status': constants.STATUS_MANAGE_ERROR, 'size': 1}) {'status': constants.STATUS_MANAGE_ERROR, 'size': 0})
raise raise
@add_hooks @add_hooks
@ -2767,15 +2769,19 @@ class ShareManager(manager.SchedulerDependentManager):
("Share can not be unmanaged: %s."), e) ("Share can not be unmanaged: %s."), e)
return return
# NOTE(haixin) we will skip quota cuts when unmanag share with
# 'error_manage' status, because we have not commit quota usages when
# we failed to manage the share.
if share_ref['status'] != constants.STATUS_MANAGE_ERROR_UNMANAGING:
deltas = { deltas = {
'project_id': project_id, 'project_id': project_id,
'shares': -1, 'shares': -1,
'gigabytes': -share_ref['size'], 'gigabytes': -share_ref['size'],
'share_type_id': share_instance['share_type_id'], 'share_type_id': share_instance['share_type_id'],
} }
# NOTE(carloss): while unmanaging a share, a share will not contain # NOTE(carloss): while unmanaging a share, a share will not
# replicas other than the active one. So there is no need to # contain replicas other than the active one. So there is no need
# recalculate the amount of share replicas to be deallocated. # to recalculate the amount of share replicas to be deallocated.
if supports_replication: if supports_replication:
deltas.update({'share_replicas': -1, deltas.update({'share_replicas': -1,
'replica_gigabytes': -share_ref['size']}) 'replica_gigabytes': -share_ref['size']})

View File

@ -52,9 +52,9 @@ class ShareUnmanageTest(test.TestCase):
self.mock_policy_check = self.mock_object( self.mock_policy_check = self.mock_object(
policy, 'check_policy', mock.Mock(return_value=True)) policy, 'check_policy', mock.Mock(return_value=True))
def test_unmanage_share(self): @ddt.data(constants.STATUS_AVAILABLE, constants.STATUS_MANAGE_ERROR)
share = dict(status=constants.STATUS_AVAILABLE, id='foo_id', def test_unmanage_share(self, status):
instance={}) share = dict(status=status, id='foo_id', instance={})
self.mock_object(share_api.API, 'get', mock.Mock(return_value=share)) self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
self.mock_object(share_api.API, 'unmanage', mock.Mock()) self.mock_object(share_api.API, 'unmanage', mock.Mock())
self.mock_object( self.mock_object(

View File

@ -353,7 +353,7 @@ class SchedulerManagerTestCase(test.TestCase):
{'share_id': share_id}, None) {'share_id': share_id}, None)
db_update.assert_called_once_with( db_update.assert_called_once_with(
self.context, share_id, self.context, share_id,
{'status': constants.STATUS_MANAGE_ERROR, 'size': 1}) {'status': constants.STATUS_MANAGE_ERROR, 'size': 0})
def test_create_share_replica_exception_path(self): def test_create_share_replica_exception_path(self):
"""Test 'raisable' exceptions for create_share_replica.""" """Test 'raisable' exceptions for create_share_replica."""

View File

@ -2631,7 +2631,7 @@ class ShareManagerTestCase(test.TestCase):
self.share_manager.db.share_update.assert_called_once_with( self.share_manager.db.share_update.assert_called_once_with(
utils.IsAMatcher(context.RequestContext), share_id, utils.IsAMatcher(context.RequestContext), share_id,
{'status': constants.STATUS_MANAGE_ERROR, 'size': 1}) {'status': constants.STATUS_MANAGE_ERROR, 'size': 0})
(self.share_manager._get_extra_specs_from_share_type. (self.share_manager._get_extra_specs_from_share_type.
assert_called_once_with( assert_called_once_with(
mock.ANY, share['instance']['share_type_id'])) mock.ANY, share['instance']['share_type_id']))
@ -2739,7 +2739,7 @@ class ShareManagerTestCase(test.TestCase):
assert_called_once_with(mock.ANY, driver_options)) assert_called_once_with(mock.ANY, driver_options))
self.share_manager.db.share_update.assert_called_once_with( self.share_manager.db.share_update.assert_called_once_with(
utils.IsAMatcher(context.RequestContext), share_id, utils.IsAMatcher(context.RequestContext), share_id,
{'status': constants.STATUS_MANAGE_ERROR, 'size': 1}) {'status': constants.STATUS_MANAGE_ERROR, 'size': 0})
(self.share_manager._get_extra_specs_from_share_type. (self.share_manager._get_extra_specs_from_share_type.
assert_called_once_with( assert_called_once_with(
mock.ANY, share['instance']['share_type_id'])) mock.ANY, share['instance']['share_type_id']))
@ -2772,7 +2772,7 @@ class ShareManagerTestCase(test.TestCase):
assert_called_once_with(mock.ANY, driver_options)) assert_called_once_with(mock.ANY, driver_options))
self.share_manager.db.share_update.assert_called_once_with( self.share_manager.db.share_update.assert_called_once_with(
mock.ANY, share_id, mock.ANY, share_id,
{'status': constants.STATUS_MANAGE_ERROR, 'size': 1}) {'status': constants.STATUS_MANAGE_ERROR, 'size': 0})
(self.share_manager._get_extra_specs_from_share_type. (self.share_manager._get_extra_specs_from_share_type.
assert_called_once_with( assert_called_once_with(
mock.ANY, share['instance']['share_type_id'])) mock.ANY, share['instance']['share_type_id']))

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Fixed `bug #1883506 <https://bugs.launchpad.net/manila/+bug/18835060>`_
that caused a quota error when delete or unmanage a share that failed to
manage.