Merge "resource failure causes nested stacks to be rolled back"
This commit is contained in:
commit
5135ad7307
@ -377,7 +377,7 @@ class StackController(object):
|
||||
stack_identity = self._get_identity(con, stack_name)
|
||||
try:
|
||||
self.rpc_client.stack_cancel_update(
|
||||
con, stack_identity=stack_identity)
|
||||
con, stack_identity=stack_identity, cancel_with_rollback=True)
|
||||
except Exception as ex:
|
||||
return exception.map_remote_error(ex)
|
||||
|
||||
|
@ -111,8 +111,10 @@ class StackResource(resource.Resource):
|
||||
self.context.tenant_id,
|
||||
self.physical_resource_name(),
|
||||
self.resource_id)
|
||||
self.rpc_client().stack_cancel_update(self.context,
|
||||
stack_identity)
|
||||
self.rpc_client().stack_cancel_update(
|
||||
self.context,
|
||||
stack_identity,
|
||||
cancel_with_rollback=False)
|
||||
|
||||
def has_nested(self):
|
||||
if self.nested() is not None:
|
||||
|
@ -61,6 +61,9 @@ LOG = logging.getLogger(__name__)
|
||||
class ForcedCancel(BaseException):
|
||||
"""Exception raised to cancel task execution."""
|
||||
|
||||
def __init__(self, with_rollback=True):
|
||||
self.with_rollback = with_rollback
|
||||
|
||||
def __str__(self):
|
||||
return "Operation cancelled"
|
||||
|
||||
@ -1197,20 +1200,27 @@ class Stack(collections.Mapping):
|
||||
(self.status == self.FAILED))
|
||||
|
||||
def _update_exception_handler(self, exc, action, update_task):
|
||||
require_rollback = False
|
||||
'''
|
||||
Handle exceptions in update_task. Decide if we should cancel tasks or
|
||||
not. Also decide if we should rollback or not, depend on disable
|
||||
rollback flag if force rollback flag not trigered.
|
||||
:returns: a boolean for require rollback flag
|
||||
'''
|
||||
self.status_reason = six.text_type(exc)
|
||||
self.status = self.FAILED
|
||||
if action == self.UPDATE:
|
||||
if not self.disable_rollback:
|
||||
require_rollback = True
|
||||
if isinstance(exc, ForcedCancel):
|
||||
update_task.updater.cancel_all()
|
||||
require_rollback = True
|
||||
return require_rollback
|
||||
if action != self.UPDATE:
|
||||
return False
|
||||
if isinstance(exc, ForcedCancel):
|
||||
update_task.updater.cancel_all()
|
||||
return exc.with_rollback or not self.disable_rollback
|
||||
|
||||
return not self.disable_rollback
|
||||
|
||||
def _message_parser(self, message):
|
||||
if message == rpc_api.THREAD_CANCEL:
|
||||
raise ForcedCancel()
|
||||
raise ForcedCancel(with_rollback=False)
|
||||
elif message == rpc_api.THREAD_CANCEL_WITH_ROLLBACK:
|
||||
raise ForcedCancel(with_rollback=True)
|
||||
|
||||
def _delete_backup_stack(self, stack):
|
||||
# Delete resources in the backup stack referred to by 'stack'
|
||||
|
@ -2260,7 +2260,10 @@ class StackTest(common.HeatTestCase):
|
||||
self.assertTrue(res)
|
||||
elif isinstance(exc, stack.ForcedCancel):
|
||||
update_task.updater.cancel_all.assert_called_once_with()
|
||||
self.assertTrue(res)
|
||||
if exc.with_rollback or not disable_rollback:
|
||||
self.assertTrue(res)
|
||||
else:
|
||||
self.assertFalse(res)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_exception_handler_resource_failure_no_rollback(self):
|
||||
@ -2273,10 +2276,14 @@ class StackTest(common.HeatTestCase):
|
||||
exc = exception.ResourceFailure(reason, None, action='UPDATE')
|
||||
self.update_exception_handler(exc, disable_rollback=False)
|
||||
|
||||
def test_update_exception_handler_force_cancel(self):
|
||||
exc = stack.ForcedCancel()
|
||||
def test_update_exception_handler_force_cancel_with_rollback(self):
|
||||
exc = stack.ForcedCancel(with_rollback=True)
|
||||
self.update_exception_handler(exc, disable_rollback=False)
|
||||
|
||||
def test_update_exception_handler_force_cancel_no_rollback(self):
|
||||
exc = stack.ForcedCancel(with_rollback=False)
|
||||
self.update_exception_handler(exc, disable_rollback=True)
|
||||
|
||||
|
||||
class StackKwargsForCloningTest(common.HeatTestCase):
|
||||
scenarios = [
|
||||
|
@ -21,6 +21,7 @@ from heat.engine import resource
|
||||
from heat.engine import scheduler
|
||||
from heat.engine import stack
|
||||
from heat.engine import template
|
||||
from heat.rpc import api as rpc_api
|
||||
from heat.tests import common
|
||||
from heat.tests import generic_resource as generic_rsrc
|
||||
from heat.tests import utils
|
||||
@ -671,6 +672,54 @@ class StackUpdateTest(common.HeatTestCase):
|
||||
[mock.call(None), mock.call('c_res'), mock.call('b_res'),
|
||||
mock.call('a_res')])
|
||||
|
||||
def _update_force_cancel(self, state, disable_rollback=False,
|
||||
cancel_message=rpc_api.THREAD_CANCEL):
|
||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
||||
|
||||
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl))
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
|
||||
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'GenericResourceType'},
|
||||
'BResource': {'Type': 'GenericResourceType'}}}
|
||||
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2),
|
||||
disable_rollback=disable_rollback)
|
||||
|
||||
evt_mock = mock.MagicMock()
|
||||
evt_mock.ready.return_value = True
|
||||
evt_mock.wait.return_value = cancel_message
|
||||
|
||||
self.stack.update(updated_stack, event=evt_mock)
|
||||
|
||||
self.assertEqual(state, self.stack.state)
|
||||
evt_mock.ready.assert_called_once_with()
|
||||
evt_mock.wait.assert_called_once_with()
|
||||
|
||||
def test_update_force_cancel_no_rollback(self):
|
||||
self._update_force_cancel(
|
||||
state=(stack.Stack.UPDATE, stack.Stack.FAILED),
|
||||
disable_rollback=True,
|
||||
cancel_message=rpc_api.THREAD_CANCEL)
|
||||
|
||||
def test_update_force_cancel_rollback(self):
|
||||
self._update_force_cancel(
|
||||
state=(stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
||||
disable_rollback=False,
|
||||
cancel_message=rpc_api.THREAD_CANCEL)
|
||||
|
||||
def test_update_force_cancel_force_rollback(self):
|
||||
self._update_force_cancel(
|
||||
state=(stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
||||
disable_rollback=False,
|
||||
cancel_message=rpc_api.THREAD_CANCEL_WITH_ROLLBACK)
|
||||
|
||||
def test_update_add_signal(self):
|
||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user