Restore resource_id from backup_stack
When UpdateReplace is occurred, the resource is moved to backup_stack, and create new one. Then, resources which has dependency for updated resource will be moved to backup_stack. If the creation of update is interrupted by some reason(e.g. heat-engine down), dependencies between backup_stack and exist_stack will exist. Heat deletes backup_stack before deleting exist_stack. The dependency still exists, so it will be failed. Thus, add behavior that restores resource_id in the backup_stack when the stack is deleted. Change-Id: I32cecba17ae160d4631fb2611e34b91bb921dc5e Closes-bug: #1334514
This commit is contained in:
parent
377da55d51
commit
3fca8cb69f
@ -729,6 +729,10 @@ class Stack(collections.Mapping):
|
||||
|
||||
backup_stack = self._backup_stack(False)
|
||||
if backup_stack:
|
||||
def failed(child):
|
||||
return (child.action == child.CREATE and
|
||||
child.status in (child.FAILED, child.IN_PROGRESS))
|
||||
|
||||
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
|
||||
@ -739,18 +743,16 @@ class Stack(collections.Mapping):
|
||||
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 (any(failed(child) for child in
|
||||
self.dependencies[current_resource]) or
|
||||
current_resource.status in
|
||||
(current_resource.FAILED,
|
||||
current_resource.IN_PROGRESS)):
|
||||
# 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.
|
||||
|
@ -2194,6 +2194,138 @@ class StackTest(HeatTestCase):
|
||||
self.stack.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_modify_replace_create_in_progress_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))
|
||||
|
||||
# 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)
|
||||
# Override stack status and resources status for emulating
|
||||
# IN_PROGRESS situation
|
||||
self.stack.state_set(
|
||||
parser.Stack.UPDATE, parser.Stack.IN_PROGRESS, None)
|
||||
self.stack.resources['AResource'].state_set(
|
||||
resource.Resource.CREATE, resource.Resource.IN_PROGRESS, None)
|
||||
self.stack.delete()
|
||||
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_modify_replace_create_in_progress_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))
|
||||
|
||||
# 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')
|
||||
# Override stack status and resources status for emulating
|
||||
# IN_PROGRESS situation
|
||||
self.stack.state_set(
|
||||
parser.Stack.UPDATE, parser.Stack.IN_PROGRESS, None)
|
||||
self.stack.resources['BResource'].state_set(
|
||||
resource.Resource.CREATE, resource.Resource.IN_PROGRESS, None)
|
||||
self.stack.delete()
|
||||
self.assertEqual((parser.Stack.DELETE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_add_signal(self):
|
||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
||||
|
Loading…
Reference in New Issue
Block a user