Fix stack-list after failed update
When a update is being performed, the existing template is updated incrementally as resources change. Now if update fails, template will have mix of resources from existing and new template. Hence we need to ensure we have the user params of both. Change-Id: I9e6831c58b167809e3faed519d2fb4f7c61244fc Closes-Bug: #1477812
This commit is contained in:
parent
a21614438c
commit
2aebdf7f3f
|
@ -25,6 +25,7 @@ from osprofiler import profiler
|
|||
import six
|
||||
|
||||
from heat.common import context as common_context
|
||||
from heat.common import environment_format as env_fmt
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.common.i18n import _LE
|
||||
|
@ -1131,6 +1132,8 @@ class Stack(collections.Mapping):
|
|||
**kwargs)
|
||||
|
||||
backup_stack = self._backup_stack()
|
||||
existing_params = environment.Environment({env_fmt.PARAMETERS:
|
||||
self.t.env.params})
|
||||
try:
|
||||
update_task = update.StackUpdate(
|
||||
self, newstack, backup_stack,
|
||||
|
@ -1194,6 +1197,12 @@ class Stack(collections.Mapping):
|
|||
|
||||
notification.send(self)
|
||||
self._add_event(self.action, self.status, self.status_reason)
|
||||
if self.status == self.FAILED:
|
||||
# Since template was incrementally updated based on existing and
|
||||
# new stack resources, we should have user params of both.
|
||||
existing_params.load(newstack.t.env.user_env_as_dict())
|
||||
self.t.env = existing_params
|
||||
self.t.store(self.context)
|
||||
self.store()
|
||||
|
||||
lifecycle_plugin_utils.do_post_ops(self.context, self,
|
||||
|
|
|
@ -19,6 +19,7 @@ from heat.common import template_format
|
|||
from heat.engine import environment
|
||||
from heat.engine import resource
|
||||
from heat.engine import scheduler
|
||||
from heat.engine import service
|
||||
from heat.engine import stack
|
||||
from heat.engine import template
|
||||
from heat.tests import common
|
||||
|
@ -1385,6 +1386,107 @@ class StackUpdateTest(common.HeatTestCase):
|
|||
mock_delete_A.assert_called_once_with()
|
||||
self.assertEqual(2, mock_create.call_count)
|
||||
|
||||
def test_update_failure_recovery_new_param_stack_list(self):
|
||||
'''
|
||||
assertion:
|
||||
check that stack-list is not broken if update fails in between.
|
||||
Also ensure that next update passes
|
||||
'''
|
||||
|
||||
class ResourceTypeA(generic_rsrc.ResourceWithProps):
|
||||
count = 0
|
||||
|
||||
def handle_create(self):
|
||||
ResourceTypeA.count += 1
|
||||
self.resource_id_set('%s%d' % (self.name, self.count))
|
||||
|
||||
def handle_delete(self):
|
||||
return super(ResourceTypeA, self).handle_delete()
|
||||
|
||||
resource._register_class('ResourceTypeA', ResourceTypeA)
|
||||
|
||||
tmpl = {
|
||||
'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Parameters': {
|
||||
'abc-param': {'Type': 'String'}
|
||||
},
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'ResourceTypeA',
|
||||
'Properties': {'Foo': {'Ref': 'abc-param'}}},
|
||||
'BResource': {'Type': 'ResourceWithPropsType',
|
||||
'Properties': {'Foo': {'Ref': 'AResource'}}}
|
||||
}
|
||||
}
|
||||
env1 = environment.Environment({'abc-param': 'abc'})
|
||||
tmpl2 = {
|
||||
'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Parameters': {
|
||||
'smelly-param': {'Type': 'String'}
|
||||
},
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'ResourceTypeA',
|
||||
'Properties': {'Foo': {'Ref': 'smelly-param'}}},
|
||||
'BResource': {'Type': 'ResourceWithPropsType',
|
||||
'Properties': {'Foo': {'Ref': 'AResource'}}}
|
||||
}
|
||||
}
|
||||
env2 = environment.Environment({'smelly-param': 'smelly'})
|
||||
|
||||
self.stack = stack.Stack(self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl, env=env1),
|
||||
disable_rollback=True)
|
||||
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
|
||||
self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
||||
self.assertEqual('AResource1',
|
||||
self.stack['BResource'].properties['Foo'])
|
||||
|
||||
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
||||
'handle_create',
|
||||
side_effect=[Exception, None])
|
||||
mock_delete = self.patchobject(generic_rsrc.ResourceWithProps,
|
||||
'handle_delete')
|
||||
mock_delete_A = self.patchobject(ResourceTypeA, 'handle_delete')
|
||||
|
||||
updated_stack = stack.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2, env=env2),
|
||||
disable_rollback=True)
|
||||
self.stack.update(updated_stack)
|
||||
|
||||
# Ensure UPDATE FAILED
|
||||
mock_create.assert_called_once_with()
|
||||
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
|
||||
self.stack.state)
|
||||
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
||||
|
||||
# check if heat stack-list works, wherein it tries to fetch template
|
||||
# parameters value from env
|
||||
self.eng = service.EngineService('a-host', 'a-topic')
|
||||
self.eng.list_stacks(self.ctx)
|
||||
|
||||
# Check if next update works fine
|
||||
self.stack = stack.Stack.load(self.ctx, self.stack.id)
|
||||
updated_stack2 = stack.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2, env=env2),
|
||||
disable_rollback=True)
|
||||
|
||||
self.stack.update(updated_stack2)
|
||||
self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
|
||||
self.stack = stack.Stack.load(self.ctx, self.stack.id)
|
||||
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
||||
self.assertEqual('AResource2',
|
||||
self.stack['BResource'].properties['Foo'])
|
||||
|
||||
self.assertEqual(2, mock_delete.call_count)
|
||||
mock_delete_A.assert_called_once_with()
|
||||
self.assertEqual(2, mock_create.call_count)
|
||||
|
||||
def test_update_replace_parameters(self):
|
||||
'''
|
||||
assertion:
|
||||
|
|
Loading…
Reference in New Issue