Merge "Protect prepare_update_replace() with resource lock"

This commit is contained in:
Zuul 2017-12-11 12:51:41 +00:00 committed by Gerrit Code Review
commit 70ff59848c
2 changed files with 78 additions and 53 deletions

View File

@ -1514,16 +1514,34 @@ class Resource(status.ResourceStatus):
"error: %s", ex)
return after_props, before_props
def _prepare_update_replace_handler(self, action):
"""Return the handler method for preparing to replace a resource.
This may be either restore_prev_rsrc() (in the case of a legacy
rollback) or, more typically, prepare_for_replace().
If the plugin has not overridden the method, then None is returned in
place of the default method (which is empty anyway).
"""
if (self.stack.action == 'ROLLBACK' and
self.stack.status == 'IN_PROGRESS' and
not self.stack.convergence):
# handle case, when it's rollback and we should restore
# old resource
if self.restore_prev_rsrc != Resource.restore_prev_rsrc:
return self.restore_prev_rsrc
else:
if self.prepare_for_replace != Resource.prepare_for_replace:
return self.prepare_for_replace
return None
def _prepare_update_replace(self, action):
handler = self._prepare_update_replace_handler(action)
if handler is None:
return
try:
if (self.stack.action == 'ROLLBACK' and
self.stack.status == 'IN_PROGRESS' and
not self.stack.convergence):
# handle case, when it's rollback and we should restore
# old resource
self.restore_prev_rsrc()
else:
self.prepare_for_replace()
handler()
except Exception as e:
# if any exception happen, we should set the resource to
# FAILED, then raise ResourceFailure
@ -1582,61 +1600,65 @@ class Resource(status.ResourceStatus):
needs_update = self._needs_update(after, before,
after_props, before_props,
prev_resource)
if not needs_update:
if update_templ_func is not None:
update_templ_func(persist=True)
if self.status == self.FAILED:
status_reason = _('Update status to COMPLETE for '
'FAILED resource neither update '
'nor replace.')
lock = (self.LOCK_RESPECT if self.stack.convergence
else self.LOCK_NONE)
self.state_set(self.action, self.COMPLETE,
status_reason, lock=lock)
return
except UpdateReplace:
with excutils.save_and_reraise_exception():
if self._prepare_update_replace_handler(action) is not None:
with self.lock(self._calling_engine_id):
self._prepare_update_replace(action)
except exception.ResourceActionRestricted as ae:
failure = exception.ResourceFailure(ae, self, action)
self._add_event(action, self.FAILED, six.text_type(ae))
raise failure
if not self.stack.convergence:
if (self.action, self.status) in (
(self.CREATE, self.IN_PROGRESS),
(self.UPDATE, self.IN_PROGRESS),
(self.ADOPT, self.IN_PROGRESS)):
exc = Exception(_('Resource update already requested'))
raise exception.ResourceFailure(exc, self, action)
if not needs_update:
if update_templ_func is not None:
update_templ_func(persist=True)
if self.status == self.FAILED:
status_reason = _('Update status to COMPLETE for '
'FAILED resource neither update '
'nor replace.')
lock = (self.LOCK_RESPECT if self.stack.convergence
else self.LOCK_NONE)
self.state_set(self.action, self.COMPLETE,
status_reason, lock=lock)
return
LOG.info('updating %s', self)
if not self.stack.convergence:
if (self.action, self.status) in (
(self.CREATE, self.IN_PROGRESS),
(self.UPDATE, self.IN_PROGRESS),
(self.ADOPT, self.IN_PROGRESS)):
exc = Exception(_('Resource update already requested'))
raise exception.ResourceFailure(exc, self, action)
self.updated_time = datetime.utcnow()
LOG.info('updating %s', self)
with self._action_recorder(action, UpdateReplace):
after_props.validate()
self.updated_time = datetime.utcnow()
tmpl_diff = self.update_template_diff(after.freeze(), before)
with self._action_recorder(action, UpdateReplace):
after_props.validate()
self.properties = before_props
tmpl_diff = self.update_template_diff(after.freeze(), before)
try:
if tmpl_diff and self.needs_replace_with_tmpl_diff(tmpl_diff):
raise UpdateReplace(self)
prop_diff = self.update_template_diff_properties(after_props,
before_props)
self.properties = before_props
yield self.action_handler_task(action,
args=[after, tmpl_diff,
prop_diff])
self.t = after
self.reparse()
self._update_stored_properties()
if update_templ_func is not None:
# template/requires will be persisted by _action_recorder()
update_templ_func(persist=False)
except UpdateReplace:
with excutils.save_and_reraise_exception():
self._prepare_update_replace(action)
except exception.ResourceActionRestricted as ae:
# catch all ResourceActionRestricted exceptions
failure = exception.ResourceFailure(ae, self, action)
self._add_event(action, self.FAILED, six.text_type(ae))
raise failure
except UpdateReplace:
# catch all UpdateReplace exceptions
self._prepare_update_replace(action)
raise
self.t = after
self.reparse()
self._update_stored_properties()
if update_templ_func is not None:
# template/requires will be persisted by _action_recorder()
update_templ_func(persist=False)
yield self._break_if_required(
self.UPDATE, environment.HOOK_POST_UPDATE)
@ -2096,15 +2118,18 @@ class Resource(status.ResourceStatus):
def lock(self, engine_id):
self._calling_engine_id = engine_id
try:
self._store_with_lock({}, self.LOCK_ACQUIRE)
if engine_id is not None:
self._store_with_lock({}, self.LOCK_ACQUIRE)
yield
except exception.UpdateInProgress:
raise
except BaseException:
with excutils.save_and_reraise_exception():
self._store_with_lock({}, self.LOCK_RELEASE)
if engine_id is not None:
self._store_with_lock({}, self.LOCK_RELEASE)
else:
self._store_with_lock({}, self.LOCK_RELEASE)
if engine_id is not None:
self._store_with_lock({}, self.LOCK_RELEASE)
def _resolve_any_attribute(self, attr):
"""Method for resolving any attribute, including base attributes.

View File

@ -2409,7 +2409,7 @@ class ResourceTest(common.HeatTestCase):
self.assertEqual(stack.t.id, res.current_template_id)
# ensure that requires was not updated
self.assertItemsEqual([2], res.requires)
self._assert_resource_lock(res.id, None, None)
self._assert_resource_lock(res.id, None, 2)
def test_convergence_update_replace_rollback(self):
rsrc_def = rsrc_defn.ResourceDefinition('test_res',