Merge "Restore resource_id from backup_stack when delete"

This commit is contained in:
Jenkins 2014-05-22 03:07:27 +00:00 committed by Gerrit Code Review
commit 4f42f255b0
3 changed files with 180 additions and 1 deletions

View File

@ -662,7 +662,40 @@ class Stack(collections.Mapping):
action)
backup_stack = self._backup_stack(False)
if backup_stack is not None:
if backup_stack:
for key, backup_resource in backup_stack.resources.items():
# If UpdateReplace is failed, we must restore backup_resource
# to existing_stack in case of it may have dependencies in
# these stacks. current_resource is the resource that just
# created and failed, so put into the backup_stack to delete
# anyway.
backup_resource_id = backup_resource.resource_id
current_resource = self.resources[key]
current_resource_id = current_resource.resource_id
if backup_resource_id:
child_failed = False
for child in self.dependencies[current_resource]:
# If child resource failed to update, current_resource
# should be replaced to resolve dependencies. But this
# is not fundamental solution. If there are update
# failer and success resources in the children, cannot
# delete the stack.
if (child.status == child.FAILED and child.action ==
child.CREATE):
child_failed = True
if (current_resource.status == current_resource.FAILED or
child_failed):
# Stack class owns dependencies as set of resource's
# objects, so we switch members of the resource that is
# needed to delete it.
self.resources[key].resource_id = backup_resource_id
self.resources[
key].properties = backup_resource.properties
backup_stack.resources[
key].resource_id = current_resource_id
backup_stack.resources[
key].properties = current_resource.properties
backup_stack.delete(backup=True)
if backup_stack.status != backup_stack.COMPLETE:
errs = backup_stack.status_reason

View File

@ -73,6 +73,20 @@ class ResourceWithProps(GenericResource):
properties_schema = {'Foo': {'Type': 'String'}}
class ResourceWithResourceID(GenericResource):
properties_schema = {'ID': {'Type': 'String'}}
def handle_create(self):
super(ResourceWithResourceID, self).handle_create()
self.resource_id_set(self.properties.get('ID'))
def handle_delete(self):
self.mox_resource_id(self.resource_id)
def mox_resource_id(self, resource_id):
pass
class ResourceWithComplexAttributes(GenericResource):
attributes_schema = {'list': 'A list',
'flat_dict': 'A flat dictionary',

View File

@ -1975,6 +1975,138 @@ class StackTest(HeatTestCase):
self.stack.state)
self.m.VerifyAll()
def test_update_modify_replace_failed_create_and_delete_1(self):
resource._register_class('ResourceWithResourceIDType',
generic_rsrc.ResourceWithResourceID)
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'AResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'a_res'}},
'BResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'b_res'},
'DependsOn': 'AResource'}}}
self.stack = parser.Stack(self.ctx, 'update_test_stack',
template.Template(tmpl),
disable_rollback=True)
self.stack.store()
self.stack.create()
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
self.stack.state)
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'AResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'xyz'}},
'BResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'b_res'},
'DependsOn': 'AResource'}}}
updated_stack = parser.Stack(self.ctx, 'updated_stack',
template.Template(tmpl2))
# Calls to GenericResource.handle_update will raise
# resource.UpdateReplace because we've not specified the modified
# key/property in update_allowed_keys/update_allowed_properties
# patch in a dummy handle_create making the replace fail creating
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
'handle_create')
generic_rsrc.ResourceWithResourceID.handle_create().AndRaise(Exception)
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
'mox_resource_id')
# First, attempts to delete backup_stack. The create (xyz) has been
# failed, so it has no resource_id.
generic_rsrc.ResourceWithResourceID.mox_resource_id(
None).AndReturn(None)
# There are dependency AResource and BResource, so we must delete
# BResource, then delete AResource.
generic_rsrc.ResourceWithResourceID.mox_resource_id(
'b_res').AndReturn(None)
generic_rsrc.ResourceWithResourceID.mox_resource_id(
'a_res').AndReturn(None)
self.m.ReplayAll()
self.stack.update(updated_stack)
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
self.stack.state)
self.stack.delete()
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
self.stack.state)
self.m.VerifyAll()
def test_update_modify_replace_failed_create_and_delete_2(self):
resource._register_class('ResourceWithResourceIDType',
generic_rsrc.ResourceWithResourceID)
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'AResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'a_res'}},
'BResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'b_res'},
'DependsOn': 'AResource'}}}
self.stack = parser.Stack(self.ctx, 'update_test_stack',
template.Template(tmpl),
disable_rollback=True)
self.stack.store()
self.stack.create()
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
self.stack.state)
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'AResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'c_res'}},
'BResource': {'Type':
'ResourceWithResourceIDType',
'Properties': {'ID': 'xyz'},
'DependsOn': 'AResource'}}}
updated_stack = parser.Stack(self.ctx, 'updated_stack',
template.Template(tmpl2))
# Calls to GenericResource.handle_update will raise
# resource.UpdateReplace because we've not specified the modified
# key/property in update_allowed_keys/update_allowed_properties
# patch in a dummy handle_create making the replace fail creating
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
'handle_create')
generic_rsrc.ResourceWithResourceID.handle_create()
generic_rsrc.ResourceWithResourceID.handle_create().AndRaise(Exception)
self.m.StubOutWithMock(generic_rsrc.ResourceWithResourceID,
'mox_resource_id')
# First, attempts to delete backup_stack. The create (xyz) has been
# failed, so it has no resource_id.
generic_rsrc.ResourceWithResourceID.mox_resource_id(
None).AndReturn(None)
generic_rsrc.ResourceWithResourceID.mox_resource_id(
'c_res').AndReturn(None)
# There are dependency AResource and BResource, so we must delete
# BResource, then delete AResource.
generic_rsrc.ResourceWithResourceID.mox_resource_id(
'b_res').AndReturn(None)
generic_rsrc.ResourceWithResourceID.mox_resource_id(
'a_res').AndReturn(None)
self.m.ReplayAll()
self.stack.update(updated_stack)
# set resource_id for AResource because handle_create() is overwritten
# by the mox.
self.stack.resources['AResource'].resource_id_set('c_res')
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
self.stack.state)
self.stack.delete()
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
self.stack.state)
self.m.VerifyAll()
def test_update_add_failed_create(self):
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}