Merge "Restore resource_id from backup_stack when delete"
This commit is contained in:
commit
4f42f255b0
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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'}}}
|
||||
|
Loading…
Reference in New Issue
Block a user