From f2edd0d68ea84f8ca212df0ec1d6d92463a030aa Mon Sep 17 00:00:00 2001 From: kairat_kushaev Date: Tue, 5 May 2015 19:22:32 +0300 Subject: [PATCH] Save updated-in-place resources to backup stack The patch changes the approach of resource backup during stack update. It stores the definition of resource that needs to be updated in-place to backup stack(if it was not created before). Without this functionality, InvalidTemplateReference will be thrown every time when update-replaced resource has a reference to updated in-place resource and stack update was failed. In this case, backup stack has a resource that refers to not-presented another resource and backup stack deletion generates an exception. Change-Id: I436c44a579bb4df3031d1a17b9ca5b62da37afeb Closes-bug: #1446575 --- heat/engine/update.py | 9 +++++ heat/tests/test_stack_update.py | 68 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/heat/engine/update.py b/heat/engine/update.py index 67d4fda83..874779bd5 100644 --- a/heat/engine/update.py +++ b/heat/engine/update.py @@ -135,6 +135,15 @@ class StackUpdate(object): except resource.UpdateReplace: pass else: + # Save resource definition to backup stack if it is not + # present in backup stack template already + if res_name not in self.previous_stack.t[ + self.previous_stack.t.RESOURCES]: + definition = existing_res.t.reparse(self.previous_stack, + existing_res.stack.t) + self.previous_stack.t.add_resource(definition) + self.previous_stack.t.store(self.previous_stack.context) + LOG.info(_LI("Resource %(res_name)s for stack %(stack_name)s " "updated"), {'res_name': res_name, diff --git a/heat/tests/test_stack_update.py b/heat/tests/test_stack_update.py index f8bf76aa4..3e0eade67 100644 --- a/heat/tests/test_stack_update.py +++ b/heat/tests/test_stack_update.py @@ -1535,3 +1535,71 @@ class StackUpdateTest(common.HeatTestCase): self.assertEqual((stack.Stack.UPDATE, stack.Stack.COMPLETE), self.stack.state) self.assertEqual('foo', self.stack['AResource'].properties['Foo']) + + def test_delete_stack_when_update_failed_twice(self): + """Test when stack update failed twice and delete the stack. + + Test checks the following scenario: + 1. Create stack + 2. Update stack (failed) + 3. Update stack (failed) + 4. Delete stack + The test checks the behavior of backup stack when update is failed. + If some resources were not backed up correctly then test will fail. + """ + tmpl_create = { + 'heat_template_version': '2013-05-23', + 'resources': { + 'Ares': {'type': 'GenericResourceType'} + } + } + # create a stack + self.stack = stack.Stack(self.ctx, 'update_fail_test_stack', + template.Template(tmpl_create), + disable_rollback=True) + self.stack.store() + self.stack.create() + self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE), + self.stack.state) + + tmpl_update = { + 'heat_template_version': '2013-05-23', + 'resources': { + 'Ares': {'type': 'GenericResourceType'}, + 'Bres': {'type': 'GenericResourceType'}, + 'Cres': { + 'type': 'ResourceWithPropsType', + 'properties': { + 'Foo': {'get_resource': 'Bres'}, + } + } + } + } + + mock_create = self.patchobject( + generic_rsrc.ResourceWithProps, + 'handle_create', + side_effect=[Exception, Exception]) + + updated_stack_first = stack.Stack(self.ctx, + 'update_fail_test_stack', + template.Template(tmpl_update)) + self.stack.update(updated_stack_first) + self.stack.resources['Cres'].resource_id_set('c_res') + self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED), + self.stack.state) + + # try to update the stack again + updated_stack_second = stack.Stack(self.ctx, + 'update_fail_test_stack', + template.Template(tmpl_update)) + self.stack.update(updated_stack_second) + self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED), + self.stack.state) + + self.assertEqual(mock_create.call_count, 2) + + # delete the failed stack + self.stack.delete() + self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE), + self.stack.state)