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:
Rikimaru, Honjo 2014-08-04 16:38:33 +09:00
parent 377da55d51
commit 3fca8cb69f
2 changed files with 141 additions and 7 deletions

View File

@ -729,6 +729,10 @@ class Stack(collections.Mapping):
backup_stack = self._backup_stack(False) backup_stack = self._backup_stack(False)
if backup_stack: 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(): for key, backup_resource in backup_stack.resources.items():
# If UpdateReplace is failed, we must restore backup_resource # If UpdateReplace is failed, we must restore backup_resource
# to existing_stack in case of it may have dependencies in # 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 = self.resources[key]
current_resource_id = current_resource.resource_id current_resource_id = current_resource.resource_id
if backup_resource_id: if backup_resource_id:
child_failed = False if (any(failed(child) for child in
for child in self.dependencies[current_resource]: self.dependencies[current_resource]) or
current_resource.status in
(current_resource.FAILED,
current_resource.IN_PROGRESS)):
# If child resource failed to update, current_resource # If child resource failed to update, current_resource
# should be replaced to resolve dependencies. But this # should be replaced to resolve dependencies. But this
# is not fundamental solution. If there are update # is not fundamental solution. If there are update
# failer and success resources in the children, cannot # failer and success resources in the children, cannot
# delete the stack. # 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 # Stack class owns dependencies as set of resource's
# objects, so we switch members of the resource that is # objects, so we switch members of the resource that is
# needed to delete it. # needed to delete it.

View File

@ -2194,6 +2194,138 @@ class StackTest(HeatTestCase):
self.stack.state) self.stack.state)
self.m.VerifyAll() 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): def test_update_add_signal(self):
tmpl = {'HeatTemplateFormatVersion': '2012-12-12', tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'AResource': {'Type': 'GenericResourceType'}}} 'Resources': {'AResource': {'Type': 'GenericResourceType'}}}