diff options
author | kairat_kushaev <kkushaev@mirantis.com> | 2015-05-05 19:22:32 +0300 |
---|---|---|
committer | Zane Bitter <zbitter@redhat.com> | 2015-10-08 15:13:05 -0400 |
commit | 2a6e29113d9f68afa3e1e8c42af842a1495879cc (patch) | |
tree | 94a03bda4ba14972e930159a56efb41efde9ed31 | |
parent | 964c6e8c5f31e292f755944dd7a82152d600e9d6 (diff) | |
download | heat-2a6e29113d9f68afa3e1e8c42af842a1495879cc.tar.gz |
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
(cherry picked from commits da3777342a344a2dafb9244b7afca8722a398caa
and 3786262aed62d81ad35de3d4f703c38223907ffd)
-rw-r--r-- | heat/engine/update.py | 21 | ||||
-rw-r--r-- | heat/tests/test_parser.py | 138 |
2 files changed, 159 insertions, 0 deletions
diff --git a/heat/engine/update.py b/heat/engine/update.py index 9e2ef8e46..837e1c38a 100644 --- a/heat/engine/update.py +++ b/heat/engine/update.py @@ -119,6 +119,19 @@ class StackUpdate(object): existing_res.state_set(existing_res.UPDATE, existing_res.COMPLETE) self.existing_stack.add_resource(new_res) + + # Save new resource definition to backup stack if it is not + # present in backup stack template already + # it allows to resolve all dependencies that existing resource + # can have if it was copied to backup stack + if (res_name not in + self.previous_stack.t[self.previous_stack.t.RESOURCES]): + LOG.debug("Backing up new Resource %s" % res_name) + definition = new_res.t.reparse(self.previous_stack, + new_res.stack.t) + self.previous_stack.t.add_resource(definition) + self.previous_stack.t.store(self.previous_stack.context) + yield new_res.create() @scheduler.wrappertask @@ -133,6 +146,14 @@ class StackUpdate(object): except resource.UpdateReplace: pass else: + # Save updated resource definition to backup stack + # cause it allows the backup stack resources to be synchronized + LOG.debug("Backing up updated Resource %s" % res_name) + 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(_("Resource %(res_name)s for stack %(stack_name)s " "updated") % {'res_name': res_name, diff --git a/heat/tests/test_parser.py b/heat/tests/test_parser.py index 701c64424..277d72620 100644 --- a/heat/tests/test_parser.py +++ b/heat/tests/test_parser.py @@ -3576,6 +3576,144 @@ class StackTest(HeatTestCase): self.assertEqual('foo', self.stack['AResource'].properties['Foo']) self.m.VerifyAll() + 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 = parser.Stack(self.ctx, 'update_fail_test_stack', + template.Template(tmpl_create), + disable_rollback=True) + self.stack.store() + self.stack.create() + self.assertEqual((parser.Stack.CREATE, parser.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 = parser.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((parser.Stack.UPDATE, parser.Stack.FAILED), + self.stack.state) + + # try to update the stack again + updated_stack_second = parser.Stack(self.ctx, + 'update_fail_test_stack', + template.Template(tmpl_update)) + self.stack.update(updated_stack_second) + self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED), + self.stack.state) + + self.assertEqual(mock_create.call_count, 2) + + # delete the failed stack + self.stack.delete() + self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE), + self.stack.state) + + def test_backup_stack_synchronized_after_update(self): + """Test when backup stack updated correctly during stack update. + + Test checks the following scenario: + 1. Create stack + 2. Update stack (failed - so the backup should not be deleted) + 3. Update stack (failed - so the backup from step 2 should be updated) + The test checks that backup stack is synchronized with the main stack. + """ + # create a stack + tmpl_create = { + 'heat_template_version': '2013-05-23', + 'resources': { + 'Ares': {'type': 'GenericResourceType'} + } + } + self.stack = parser.Stack(self.ctx, 'test_update_stack_backup', + template.Template(tmpl_create), + disable_rollback=True) + self.stack.store() + self.stack.create() + self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE), + self.stack.state) + + # try to update a stack with a new resource that should be backed up + tmpl_update = { + 'heat_template_version': '2013-05-23', + 'resources': { + 'Ares': {'type': 'GenericResourceType'}, + 'Bres': { + 'type': 'ResWithComplexPropsAndAttrs', + 'properties': { + 'an_int': 0, + } + }, + 'Cres': { + 'type': 'ResourceWithPropsType', + 'properties': { + 'Foo': {'get_resource': 'Bres'}, + } + } + } + } + + self.patchobject(generic_rsrc.ResourceWithProps, + 'handle_create', + side_effect=[Exception, Exception]) + + stack_with_new_resource = parser.Stack( + self.ctx, + 'test_update_stack_backup', + template.Template(tmpl_update)) + self.stack.update(stack_with_new_resource) + self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED), + self.stack.state) + # assert that backup stack has been updated correctly + self.assertIn('Bres', self.stack._backup_stack()) + + # update the stack with resource that updated in-place + tmpl_update['resources']['Bres']['properties']['an_int'] = 1 + updated_stack_second = parser.Stack(self.ctx, + 'test_update_stack_backup', + template.Template(tmpl_update)) + self.stack.update(updated_stack_second) + self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED), + self.stack.state) + # assert that resource in backup stack also has been updated + backup = self.stack._backup_stack() + self.assertEqual(1, backup['Bres'].properties['an_int']) + def test_stack_create_timeout(self): self.m.StubOutWithMock(scheduler.DependencyTaskGroup, '__call__') self.m.StubOutWithMock(scheduler, 'wallclock') |