Merge "Handle exceptions in initial convergence traversal setup"

This commit is contained in:
Zuul 2018-08-05 10:03:30 +00:00 committed by Gerrit Code Review
commit 99cbff3803
7 changed files with 98 additions and 82 deletions

View File

@ -92,27 +92,9 @@ class CheckResource(object):
LOG.debug('Resource id=%d modified by another traversal', rsrc.id)
return False
def _trigger_rollback(self, stack):
LOG.info("Triggering rollback of %(stack_name)s %(action)s ",
{'action': stack.action, 'stack_name': stack.name})
stack.rollback()
def _handle_failure(self, cnxt, stack, failure_reason):
updated = stack.state_set(stack.action, stack.FAILED, failure_reason)
if not updated:
return False
if (not stack.disable_rollback and
stack.action in (stack.CREATE, stack.ADOPT, stack.UPDATE,
stack.RESTORE)):
self._trigger_rollback(stack)
else:
stack.purge_db()
return True
def _handle_resource_failure(self, cnxt, is_update, rsrc_id,
stack, failure_reason):
failure_handled = self._handle_failure(cnxt, stack, failure_reason)
failure_handled = stack.mark_failed(failure_reason)
if not failure_handled:
# Another concurrent update has taken over. But there is a
# possibility for that update to be waiting for this rsrc to
@ -131,7 +113,7 @@ class CheckResource(object):
def _handle_stack_timeout(self, cnxt, stack):
failure_reason = u'Timed out'
self._handle_failure(cnxt, stack, failure_reason)
stack.mark_failed(failure_reason)
def _handle_resource_replacement(self, cnxt,
current_traversal, new_tmpl_id, requires,

View File

@ -90,10 +90,10 @@ def reset_state_on_error(func):
LOG.info('Stopped due to %(msg)s in %(func)s',
{'func': func.__name__, 'msg': errmsg})
finally:
if stack.status == stack.IN_PROGRESS:
if ((not stack.convergence or errmsg is not None) and
stack.status == stack.IN_PROGRESS):
rtnmsg = _("Unexpected exit while IN_PROGRESS.")
stack.state_set(stack.action, stack.FAILED,
errmsg if errmsg is not None else rtnmsg)
stack.mark_failed(errmsg if errmsg is not None else rtnmsg)
assert errmsg is not None, "Returned while IN_PROGRESS."
return handle_exceptions
@ -1306,6 +1306,7 @@ class Stack(collections.Mapping):
updater()
@profiler.trace('Stack.converge_stack', hide_args=False)
@reset_state_on_error
def converge_stack(self, template, action=UPDATE, new_stack=None,
pre_converge=None):
"""Update the stack template and trigger convergence for resources."""
@ -1364,6 +1365,7 @@ class Stack(collections.Mapping):
self.thread_group_mgr.start(self.id, self._converge_create_or_update,
pre_converge=pre_converge)
@reset_state_on_error
def _converge_create_or_update(self, pre_converge=None):
current_resources = self._update_or_store_resources()
self._compute_convg_dependencies(self.ext_rsrcs_db, self.dependencies,
@ -2107,13 +2109,31 @@ class Stack(collections.Mapping):
'tags': self.tags,
}
def mark_complete(self):
"""Mark the update as complete.
def mark_failed(self, failure_reason):
"""Mark the convergence update as failed."""
updated = self.state_set(self.action, self.FAILED, failure_reason)
if not updated:
return False
This currently occurs when all resources have been updated; there may
still be resources being cleaned up, but the Stack should now be in
service.
"""
if not self.convergence:
# This function is not generally used in the legacy path, but to
# allow it to be used by any kind of stack in the
# reset_state_on_error decorator, bail out before the
# convergence-specific part in legacy stacks.
return
if (not self.disable_rollback and
self.action in (self.CREATE, self.ADOPT, self.UPDATE,
self.RESTORE)):
LOG.info("Triggering rollback of %(stack_name)s %(action)s ",
{'action': self.action, 'stack_name': self.name})
self.rollback()
else:
self.purge_db()
return True
def mark_complete(self):
"""Mark the convergence update as complete."""
LOG.info('[%(name)s(%(id)s)] update traversal %(tid)s complete',
{'name': self.name, 'id': self.id,

View File

@ -42,6 +42,7 @@ class StackCreateTest(common.HeatTestCase):
self.ctx = utils.dummy_context()
self.man = service.EngineService('a-host', 'a-topic')
self.man.thread_group_mgr = service.ThreadGroupManager()
cfg.CONF.set_override('convergence_engine', False)
@mock.patch.object(threadgroup, 'ThreadGroup')
@mock.patch.object(stack.Stack, 'validate')
@ -53,7 +54,8 @@ class StackCreateTest(common.HeatTestCase):
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
stk = tools.get_stack(stack_name, self.ctx)
stk = tools.get_stack(stack_name, self.ctx,
convergence=cfg.CONF.convergence_engine)
files = None
if files_container:

View File

@ -205,7 +205,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.assertFalse(res)
mock_ss.assert_not_called()
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
@mock.patch.object(stack.Stack, 'rollback')
def test_resource_update_failure_sets_stack_state_as_failed(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.state_set(self.stack.UPDATE, self.stack.IN_PROGRESS, '')
@ -224,7 +224,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
'ResourceNotAvailable: resources.A: The Resource (A)'
' is not available.', s.status_reason)
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
@mock.patch.object(stack.Stack, 'rollback')
def test_resource_cleanup_failure_sets_stack_state_as_failed(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.is_update = False # invokes check_resource_cleanup
@ -244,9 +244,9 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
'ResourceNotAvailable: resources.A: The Resource (A)'
' is not available.', s.status_reason)
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_resource_update_failure_triggers_rollback_if_enabled(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
mock_tr = self.stack.rollback = mock.Mock(return_value=None)
self.stack.disable_rollback = False
self.stack.store()
dummy_ex = exception.ResourceNotAvailable(
@ -257,14 +257,11 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.stack.current_traversal, {},
self.is_update, None)
self.assertTrue(mock_tr.called)
# make sure the rollback is called on given stack
call_args, call_kwargs = mock_tr.call_args
called_stack = call_args[0]
self.assertEqual(self.stack.id, called_stack.id)
mock_tr.assert_called_once_with()
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_resource_cleanup_failure_triggers_rollback_if_enabled(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
mock_tr = self.stack.rollback = mock.Mock(return_value=None)
self.is_update = False # invokes check_resource_cleanup
self.stack.disable_rollback = False
self.stack.store()
@ -275,13 +272,9 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.worker.check_resource(self.ctx, self.resource.id,
self.stack.current_traversal, {},
self.is_update, None)
self.assertTrue(mock_tr.called)
# make sure the rollback is called on given stack
call_args, call_kwargs = mock_tr.call_args
called_stack = call_args[0]
self.assertEqual(self.stack.id, called_stack.id)
mock_tr.assert_called_once_with()
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
@mock.patch.object(stack.Stack, 'rollback')
def test_rollback_is_not_triggered_on_rollback_disabled_stack(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.disable_rollback = True
@ -295,7 +288,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.is_update, None)
self.assertFalse(mock_tr.called)
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
@mock.patch.object(stack.Stack, 'rollback')
def test_rollback_not_re_triggered_for_a_rolling_back_stack(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.disable_rollback = False
@ -435,23 +428,23 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(stack.Stack, 'purge_db')
def test_handle_failure(self, mock_purgedb, mock_cru, mock_crc, mock_pcr,
mock_csc):
self.cr._handle_failure(self.ctx, self.stack, 'dummy-reason')
self.stack.mark_failed('dummy-reason')
mock_purgedb.assert_called_once_with()
self.assertEqual('dummy-reason', self.stack.status_reason)
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_handle_failure_rollback(self, mock_tr, mock_cru, mock_crc,
def test_handle_failure_rollback(self, mock_cru, mock_crc,
mock_pcr, mock_csc):
mock_tr = self.stack.rollback = mock.Mock(return_value=None)
self.stack.disable_rollback = False
self.stack.state_set(self.stack.UPDATE, self.stack.IN_PROGRESS, '')
self.cr._handle_failure(self.ctx, self.stack, 'dummy-reason')
mock_tr.assert_called_once_with(self.stack)
self.stack.mark_failed('dummy-reason')
mock_tr.assert_called_once_with()
@mock.patch.object(stack.Stack, 'purge_db')
@mock.patch.object(stack.Stack, 'state_set')
@mock.patch.object(check_resource.CheckResource,
'retrigger_check_resource')
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
@mock.patch.object(stack.Stack, 'rollback')
def test_handle_rsrc_failure_when_update_fails(
self, mock_tr, mock_rcr, mock_ss, mock_pdb, mock_cru, mock_crc,
mock_pcr, mock_csc):
@ -469,7 +462,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(stack.Stack, 'state_set')
@mock.patch.object(check_resource.CheckResource,
'retrigger_check_resource')
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
@mock.patch.object(stack.Stack, 'rollback')
def test_handle_rsrc_failure_when_update_fails_different_traversal(
self, mock_tr, mock_rcr, mock_ss, mock_pdb, mock_cru, mock_crc,
mock_pcr, mock_csc):
@ -492,22 +485,21 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.assertFalse(mock_pdb.called)
self.assertFalse(mock_tr.called)
@mock.patch.object(check_resource.CheckResource, '_handle_failure')
def test_handle_stack_timeout(self, mock_hf, mock_cru, mock_crc, mock_pcr,
def test_handle_stack_timeout(self, mock_cru, mock_crc, mock_pcr,
mock_csc):
mock_mf = self.stack.mark_failed = mock.Mock(return_value=True)
self.cr._handle_stack_timeout(self.ctx, self.stack)
mock_hf.assert_called_once_with(self.ctx, self.stack, u'Timed out')
mock_mf.assert_called_once_with(u'Timed out')
@mock.patch.object(check_resource.CheckResource,
'_handle_failure')
def test_do_check_resource_marks_stack_as_failed_if_stack_timesout(
self, mock_hf, mock_cru, mock_crc, mock_pcr, mock_csc):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
mock_mf = self.stack.mark_failed = mock.Mock(return_value=True)
mock_cru.side_effect = scheduler.Timeout(None, 60)
self.is_update = True
self.cr._do_check_resource(self.ctx, self.stack.current_traversal,
self.stack.t, {}, self.is_update,
self.resource, self.stack, {})
mock_hf.assert_called_once_with(self.ctx, self.stack, u'Timed out')
mock_mf.assert_called_once_with(u'Timed out')
@mock.patch.object(check_resource.CheckResource,
'_handle_stack_timeout')

View File

@ -489,7 +489,8 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
def test_sync_point_delete_stack_create(self, mock_syncpoint_del,
mock_ccu, mock_cr):
stack = parser.Stack(utils.dummy_context(), 'convg_updated_time_test',
templatem.Template.create_empty_template())
templatem.Template.create_empty_template(),
convergence=True)
stack.thread_group_mgr = tools.DummyThreadGroupManager()
stack.converge_stack(template=stack.t, action=stack.CREATE)
self.assertFalse(mock_syncpoint_del.called)
@ -503,7 +504,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'R1': {'Type': 'GenericResourceType'}}}
stack = parser.Stack(utils.dummy_context(), 'updated_time_test',
templatem.Template(tmpl))
templatem.Template(tmpl), convergence=True)
stack.thread_group_mgr = tools.DummyThreadGroupManager()
stack.current_traversal = 'prev_traversal'
stack.converge_stack(template=stack.t, action=stack.UPDATE)
@ -514,7 +515,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'R1': {'Type': 'GenericResourceType'}}}
stack = parser.Stack(utils.dummy_context(), 'updated_time_test',
templatem.Template(tmpl))
templatem.Template(tmpl), convergence=True)
stack.current_traversal = 'prev_traversal'
stack.action, stack.status = stack.CREATE, stack.COMPLETE
stack.store()

View File

@ -2043,6 +2043,7 @@ class ResourceTest(common.HeatTestCase):
self.assertEqual(atomic_key, rs.atomic_key)
def test_create_convergence(self):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
res.action = res.CREATE
@ -2059,6 +2060,7 @@ class ResourceTest(common.HeatTestCase):
self._assert_resource_lock(res.id, None, None)
def test_create_convergence_throws_timeout(self):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
res.action = res.CREATE
@ -2074,6 +2076,7 @@ class ResourceTest(common.HeatTestCase):
Ensure that requires are computed correctly even if resource
create fails.
"""
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
res.store()
@ -2089,6 +2092,7 @@ class ResourceTest(common.HeatTestCase):
@mock.patch.object(resource.Resource, 'adopt')
def test_adopt_convergence_ok(self, mock_adopt):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
res.action = res.ADOPT
@ -2106,6 +2110,7 @@ class ResourceTest(common.HeatTestCase):
self._assert_resource_lock(res.id, None, None)
def test_adopt_convergence_bad_data(self):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
res.action = res.ADOPT
@ -2127,7 +2132,7 @@ class ResourceTest(common.HeatTestCase):
'test_res': {'Type': 'ResourceWithPropsType'}
}}, env=self.env)
stack = parser.Stack(utils.dummy_context(), 'test_stack',
tmpl)
tmpl, convergence=True)
stack.thread_group_mgr = tools.DummyThreadGroupManager()
stack.converge_stack(stack.t, action=stack.CREATE)
res = stack.resources['test_res']
@ -2144,8 +2149,8 @@ class ResourceTest(common.HeatTestCase):
}}, env=self.env)
new_temp.store(stack.context)
new_stack = parser.Stack(utils.dummy_context(), 'test_stack',
new_temp, stack_id=self.stack.id)
res.stack.convergence = True
new_temp, stack_id=self.stack.id,
convergence=True)
tr = scheduler.TaskRunner(res.update_convergence, new_temp.id,
{4, 3}, 'engine-007', 120, new_stack)
@ -2163,7 +2168,7 @@ class ResourceTest(common.HeatTestCase):
'test_res': {'Type': 'ResourceWithPropsType'}
}}, env=self.env)
stack = parser.Stack(utils.dummy_context(), 'test_stack',
tmpl)
tmpl, convergence=True)
stack.thread_group_mgr = tools.DummyThreadGroupManager()
stack.converge_stack(stack.t, action=stack.CREATE)
res = stack.resources['test_res']
@ -2177,7 +2182,8 @@ class ResourceTest(common.HeatTestCase):
}}, env=self.env)
new_temp.store(stack.context)
new_stack = parser.Stack(utils.dummy_context(), 'test_stack',
new_temp, stack_id=self.stack.id)
new_temp, stack_id=self.stack.id,
convergence=True)
tr = scheduler.TaskRunner(res.update_convergence, new_temp.id,
set(), 'engine-007', -1, new_stack,
@ -2185,6 +2191,7 @@ class ResourceTest(common.HeatTestCase):
self.assertRaises(scheduler.Timeout, tr)
def test_update_convergence_with_substitute_class(self):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res',
'GenericResourceType')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
@ -2198,13 +2205,15 @@ class ResourceTest(common.HeatTestCase):
}}, env=self.env)
new_temp.store(self.stack.context)
new_stack = parser.Stack(utils.dummy_context(), 'test_stack',
new_temp, stack_id=self.stack.id)
new_temp, stack_id=self.stack.id,
convergence=True)
self.assertRaises(resource.UpdateReplace, res.update_convergence,
new_temp.id, set(), 'engine-007',
-1, new_stack)
def test_update_convergence_checks_resource_class(self):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res',
'GenericResourceType')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
@ -2219,7 +2228,8 @@ class ResourceTest(common.HeatTestCase):
ctx = utils.dummy_context()
new_temp.store(ctx)
new_stack = parser.Stack(ctx, 'test_stack',
new_temp, stack_id=self.stack.id)
new_temp, stack_id=self.stack.id,
convergence=True)
tr = scheduler.TaskRunner(res.update_convergence, new_temp.id,
set(), 'engine-007', -1, new_stack,
@ -2245,7 +2255,8 @@ class ResourceTest(common.HeatTestCase):
'test_res': {'Type': 'ResourceWithPropsType'}
}}, env=self.env)
new_stack = parser.Stack(utils.dummy_context(), 'test_stack',
tmpl, stack_id=self.stack.id)
tmpl, stack_id=self.stack.id,
convergence=True)
tr = scheduler.TaskRunner(res.update_convergence, 'template_key',
{4, 3}, 'engine-007', self.dummy_timeout,
new_stack)
@ -2268,7 +2279,7 @@ class ResourceTest(common.HeatTestCase):
'test_res': {'Type': 'ResourceWithPropsType'}
}}, env=self.env)
stack = parser.Stack(utils.dummy_context(), 'test_stack',
tmpl)
tmpl, convergence=True)
stack.thread_group_mgr = tools.DummyThreadGroupManager()
stack.converge_stack(stack.t, action=stack.CREATE)
res = stack.resources['test_res']
@ -2285,7 +2296,8 @@ class ResourceTest(common.HeatTestCase):
new_temp.store(stack.context)
new_stack = parser.Stack(utils.dummy_context(), 'test_stack',
new_temp, stack_id=self.stack.id)
new_temp, stack_id=self.stack.id,
convergence=True)
res.stack.convergence = True
res._calling_engine_id = 'engine-9'
@ -2309,7 +2321,7 @@ class ResourceTest(common.HeatTestCase):
'test_res': {'Type': 'ResourceWithPropsType'}
}}, env=self.env)
stack = parser.Stack(utils.dummy_context(), 'test_stack',
tmpl)
tmpl, convergence=True)
stack.thread_group_mgr = tools.DummyThreadGroupManager()
stack.converge_stack(stack.t, action=stack.CREATE)
res = stack.resources['test_res']
@ -2341,6 +2353,7 @@ class ResourceTest(common.HeatTestCase):
self._assert_resource_lock(res.id, None, 2)
def test_convergence_update_replace_rollback(self):
self.stack.convergence = True
rsrc_def = rsrc_defn.ResourceDefinition('test_res',
'ResourceWithPropsType')
res = generic_rsrc.ResourceWithProps('test_res', rsrc_def, self.stack)
@ -2354,7 +2367,8 @@ class ResourceTest(common.HeatTestCase):
'Properties': {'Foo': 'abc'}}
}}, env=self.env)
new_stack = parser.Stack(utils.dummy_context(), 'test_stack',
new_temp, stack_id=self.stack.id)
new_temp, stack_id=self.stack.id,
convergence=True)
self.stack.state_set(self.stack.ROLLBACK, self.stack.IN_PROGRESS,
'Simulate rollback')
res.restore_prev_rsrc = mock.Mock()
@ -2365,6 +2379,7 @@ class ResourceTest(common.HeatTestCase):
self.assertTrue(res.restore_prev_rsrc.called)
def test_convergence_update_replace_rollback_restore_prev_rsrc_error(self):
self.stack.convergence = True
rsrc_def = rsrc_defn.ResourceDefinition('test_res',
'ResourceWithPropsType')
res = generic_rsrc.ResourceWithProps('test_res', rsrc_def, self.stack)
@ -2378,7 +2393,8 @@ class ResourceTest(common.HeatTestCase):
'Properties': {'Foo': 'abc'}}
}}, env=self.env)
new_stack = parser.Stack(utils.dummy_context(), 'test_stack',
new_temp, stack_id=self.stack.id)
new_temp, stack_id=self.stack.id,
convergence=True)
self.stack.state_set(self.stack.ROLLBACK, self.stack.IN_PROGRESS,
'Simulate rollback')
res.restore_prev_rsrc = mock.Mock(side_effect=Exception)
@ -2390,6 +2406,7 @@ class ResourceTest(common.HeatTestCase):
self.assertEqual((res.UPDATE, res.FAILED), res.state)
def test_delete_convergence_ok(self):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
res.current_template_id = 1
@ -2409,6 +2426,7 @@ class ResourceTest(common.HeatTestCase):
self._assert_resource_lock(res.id, None, None)
def test_delete_convergence_does_not_delete_same_template_resource(self):
self.stack.convergence = True
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
res = generic_rsrc.GenericResource('test_res', tmpl, self.stack)
res.current_template_id = 'same-template'

View File

@ -2985,7 +2985,8 @@ class ResetStateOnErrorTest(common.HeatTestCase):
status = COMPLETE
def __init__(self):
self.state_set = mock.MagicMock()
self.mark_failed = mock.MagicMock()
self.convergence = False
@stack.reset_state_on_error
def raise_exception(self):
@ -3010,27 +3011,27 @@ class ResetStateOnErrorTest(common.HeatTestCase):
dummy = self.DummyStack()
self.assertEqual('Hello world', dummy.succeed())
self.assertFalse(dummy.state_set.called)
self.assertFalse(dummy.mark_failed.called)
def test_failure(self):
dummy = self.DummyStack()
self.assertEqual('Hello world', dummy.fail())
self.assertFalse(dummy.state_set.called)
self.assertFalse(dummy.mark_failed.called)
def test_reset_state_exception(self):
dummy = self.DummyStack()
exc = self.assertRaises(ValueError, dummy.raise_exception)
self.assertIn('oops', str(exc))
self.assertTrue(dummy.state_set.called)
self.assertTrue(dummy.mark_failed.called)
def test_reset_state_exit_exception(self):
dummy = self.DummyStack()
exc = self.assertRaises(BaseException, dummy.raise_exit_exception)
self.assertIn('bye', str(exc))
self.assertTrue(dummy.state_set.called)
self.assertTrue(dummy.mark_failed.called)
class StackStateSetTest(common.HeatTestCase):