Allow an update after a failure
Change-Id: I41ce08c33780642c31b81763032d6c089e903c48 Implements: blueprint update-failure-recovery Closes-bug: #1160052
This commit is contained in:
parent
f80e5793f6
commit
c2ffae8cd0
|
@ -666,9 +666,8 @@ class Stack(collections.Mapping):
|
|||
"Invalid action %s" % action)
|
||||
return
|
||||
|
||||
if self.status != self.COMPLETE:
|
||||
if (action == self.ROLLBACK and
|
||||
self.state == (self.UPDATE, self.IN_PROGRESS)):
|
||||
if self.status == self.IN_PROGRESS:
|
||||
if action == self.ROLLBACK:
|
||||
LOG.debug("Starting update rollback for %s" % self.name)
|
||||
else:
|
||||
self.state_set(action, self.FAILED,
|
||||
|
|
|
@ -1847,17 +1847,6 @@ class StackTest(HeatTestCase):
|
|||
self.assertEqual((self.stack.ROLLBACK, self.stack.COMPLETE),
|
||||
self.stack.state)
|
||||
|
||||
def test_update_badstate(self):
|
||||
self.stack = parser.Stack(self.ctx, 'test_stack', self.tmpl,
|
||||
action=parser.Stack.CREATE,
|
||||
status=parser.Stack.FAILED)
|
||||
self.stack.store()
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.FAILED),
|
||||
self.stack.state)
|
||||
self.stack.update(mock.Mock())
|
||||
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
|
||||
self.stack.state)
|
||||
|
||||
def test_resource_by_refid(self):
|
||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
||||
|
@ -2949,6 +2938,250 @@ class StackTest(HeatTestCase):
|
|||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_failure_recovery(self):
|
||||
'''
|
||||
assertion:
|
||||
check that rollback still works with dynamic metadata
|
||||
this test fails the second instance
|
||||
'''
|
||||
|
||||
class ResourceTypeA(generic_rsrc.ResourceWithProps):
|
||||
count = 0
|
||||
|
||||
def handle_create(self):
|
||||
ResourceTypeA.count += 1
|
||||
self.resource_id_set('%s%d' % (self.name, self.count))
|
||||
|
||||
def handle_delete(self):
|
||||
return super(ResourceTypeA, self).handle_delete()
|
||||
|
||||
resource._register_class('ResourceTypeA', ResourceTypeA)
|
||||
|
||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'ResourceTypeA',
|
||||
'Properties': {'Foo': 'abc'}},
|
||||
'BResource': {'Type': 'ResourceWithPropsType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'ResourceTypeA',
|
||||
'Properties': {'Foo': 'smelly'}},
|
||||
'BResource': {'Type': 'ResourceWithPropsType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': '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)
|
||||
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
||||
self.assertEqual('AResource1',
|
||||
self.stack['BResource'].properties['Foo'])
|
||||
|
||||
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
|
||||
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
|
||||
self.m.StubOutWithMock(ResourceTypeA, 'handle_delete')
|
||||
|
||||
# mock to make the replace fail when creating the second
|
||||
# replacement resource
|
||||
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
|
||||
# delete the old resource on the second update
|
||||
generic_rsrc.ResourceWithProps.handle_delete()
|
||||
ResourceTypeA.handle_delete()
|
||||
generic_rsrc.ResourceWithProps.handle_create()
|
||||
generic_rsrc.ResourceWithProps.handle_delete()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
updated_stack = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2),
|
||||
disable_rollback=True)
|
||||
self.stack.update(updated_stack)
|
||||
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
|
||||
self.stack.state)
|
||||
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
||||
|
||||
self.stack = parser.Stack.load(self.ctx, self.stack.id)
|
||||
updated_stack2 = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2),
|
||||
disable_rollback=True)
|
||||
|
||||
self.stack.update(updated_stack2)
|
||||
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
||||
self.assertEqual('AResource2',
|
||||
self.stack['BResource'].properties['Foo'])
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_failure_recovery_new_param(self):
|
||||
'''
|
||||
assertion:
|
||||
check that rollback still works with dynamic metadata
|
||||
this test fails the second instance
|
||||
'''
|
||||
|
||||
class ResourceTypeA(generic_rsrc.ResourceWithProps):
|
||||
count = 0
|
||||
|
||||
def handle_create(self):
|
||||
ResourceTypeA.count += 1
|
||||
self.resource_id_set('%s%d' % (self.name, self.count))
|
||||
|
||||
def handle_delete(self):
|
||||
return super(ResourceTypeA, self).handle_delete()
|
||||
|
||||
resource._register_class('ResourceTypeA', ResourceTypeA)
|
||||
|
||||
tmpl = {
|
||||
'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Parameters': {
|
||||
'abc-param': {'Type': 'String'}
|
||||
},
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'ResourceTypeA',
|
||||
'Properties': {'Foo': {'Ref': 'abc-param'}}},
|
||||
'BResource': {'Type': 'ResourceWithPropsType',
|
||||
'Properties': {'Foo': {'Ref': 'AResource'}}}
|
||||
}
|
||||
}
|
||||
env1 = environment.Environment({'abc-param': 'abc'})
|
||||
tmpl2 = {
|
||||
'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Parameters': {
|
||||
'smelly-param': {'Type': 'String'}
|
||||
},
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'ResourceTypeA',
|
||||
'Properties': {'Foo': {'Ref': 'smelly-param'}}},
|
||||
'BResource': {'Type': 'ResourceWithPropsType',
|
||||
'Properties': {'Foo': {'Ref': 'AResource'}}}
|
||||
}
|
||||
}
|
||||
env2 = environment.Environment({'smelly-param': 'smelly'})
|
||||
|
||||
self.stack = parser.Stack(self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl), env1,
|
||||
disable_rollback=True)
|
||||
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
||||
self.assertEqual('AResource1',
|
||||
self.stack['BResource'].properties['Foo'])
|
||||
|
||||
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
|
||||
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
|
||||
self.m.StubOutWithMock(ResourceTypeA, 'handle_delete')
|
||||
|
||||
# mock to make the replace fail when creating the second
|
||||
# replacement resource
|
||||
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
|
||||
# delete the old resource on the second update
|
||||
generic_rsrc.ResourceWithProps.handle_delete()
|
||||
ResourceTypeA.handle_delete()
|
||||
generic_rsrc.ResourceWithProps.handle_create()
|
||||
generic_rsrc.ResourceWithProps.handle_delete()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
updated_stack = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2), env2,
|
||||
disable_rollback=True)
|
||||
self.stack.update(updated_stack)
|
||||
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
|
||||
self.stack.state)
|
||||
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
||||
|
||||
self.stack = parser.Stack.load(self.ctx, self.stack.id)
|
||||
updated_stack2 = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2), env2,
|
||||
disable_rollback=True)
|
||||
|
||||
self.stack.update(updated_stack2)
|
||||
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
|
||||
self.stack = parser.Stack.load(self.ctx, self.stack.id)
|
||||
self.assertEqual('smelly', self.stack['AResource'].properties['Foo'])
|
||||
self.assertEqual('AResource2',
|
||||
self.stack['BResource'].properties['Foo'])
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_create_failure_recovery(self):
|
||||
'''
|
||||
assertion:
|
||||
check that rollback still works with dynamic metadata
|
||||
this test fails the second instance
|
||||
'''
|
||||
|
||||
class ResourceTypeA(generic_rsrc.ResourceWithProps):
|
||||
count = 0
|
||||
|
||||
def handle_create(self):
|
||||
ResourceTypeA.count += 1
|
||||
self.resource_id_set('%s%d' % (self.name, self.count))
|
||||
|
||||
def handle_delete(self):
|
||||
return super(ResourceTypeA, self).handle_delete()
|
||||
|
||||
resource._register_class('ResourceTypeA', ResourceTypeA)
|
||||
|
||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||
'Resources': {
|
||||
'AResource': {'Type': 'ResourceTypeA',
|
||||
'Properties': {'Foo': 'abc'}},
|
||||
'BResource': {'Type': 'ResourceWithPropsType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
self.stack = parser.Stack(self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl),
|
||||
disable_rollback=True)
|
||||
|
||||
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_create')
|
||||
self.m.StubOutWithMock(generic_rsrc.ResourceWithProps, 'handle_delete')
|
||||
self.m.StubOutWithMock(ResourceTypeA, 'handle_delete')
|
||||
|
||||
# create
|
||||
generic_rsrc.ResourceWithProps.handle_create().AndRaise(Exception)
|
||||
|
||||
# update
|
||||
generic_rsrc.ResourceWithProps.handle_delete()
|
||||
generic_rsrc.ResourceWithProps.handle_create()
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.FAILED),
|
||||
self.stack.state)
|
||||
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
||||
|
||||
updated_stack = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl),
|
||||
disable_rollback=True)
|
||||
self.stack.update(updated_stack)
|
||||
self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
|
||||
self.stack.state)
|
||||
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
||||
self.assertEqual('AResource1',
|
||||
self.stack['BResource'].properties['Foo'])
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_update_replace_parameters(self):
|
||||
'''
|
||||
assertion:
|
||||
|
|
Loading…
Reference in New Issue