summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkairat_kushaev <kkushaev@mirantis.com>2015-05-05 19:22:32 +0300
committerZane Bitter <zbitter@redhat.com>2015-10-08 15:13:05 -0400
commit2a6e29113d9f68afa3e1e8c42af842a1495879cc (patch)
tree94a03bda4ba14972e930159a56efb41efde9ed31
parent964c6e8c5f31e292f755944dd7a82152d600e9d6 (diff)
downloadheat-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.py21
-rw-r--r--heat/tests/test_parser.py138
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')