make parsed template snapshots before updating
This is to make sure that as the update progresses and dependant resources are modified/replaced that we compare the original dynamic data with the current. bug 1134258 Change-Id: Ia4e474e914aa9e99e5e569c4f2276beb55798421
This commit is contained in:
parent
b8b5624d1b
commit
bc1de6ae78
|
@ -317,6 +317,10 @@ class Stack(object):
|
|||
else:
|
||||
self.state_set(self.ROLLBACK_IN_PROGRESS, 'Stack rollback started')
|
||||
|
||||
# cache all the resources runtime data.
|
||||
for r in self:
|
||||
r.cache_template()
|
||||
|
||||
# Now make the resources match the new stack definition
|
||||
with eventlet.Timeout(self.timeout_mins * 60) as tmo:
|
||||
try:
|
||||
|
@ -371,7 +375,7 @@ class Stack(object):
|
|||
# Compare resolved pre/post update resource snippets,
|
||||
# note the new resource snippet is resolved in the context
|
||||
# of the existing stack (which is the stack being updated)
|
||||
old_snippet = self.resolve_runtime_data(self[res.name].t)
|
||||
old_snippet = self[res.name].parsed_template(cached=True)
|
||||
new_snippet = self.resolve_runtime_data(res.t)
|
||||
|
||||
if old_snippet != new_snippet:
|
||||
|
|
|
@ -130,6 +130,7 @@ class Resource(object):
|
|||
self.name = name
|
||||
self.json_snippet = json_snippet
|
||||
self.t = stack.resolve_static_data(json_snippet)
|
||||
self.cached_t = None
|
||||
self.properties = Properties(self.properties_schema,
|
||||
self.t.get('Properties', {}),
|
||||
self.stack.resolve_runtime_data,
|
||||
|
@ -172,18 +173,29 @@ class Resource(object):
|
|||
return identifier.ResourceIdentifier(resource_name=self.name,
|
||||
**self.stack.identifier())
|
||||
|
||||
def parsed_template(self, section=None, default={}):
|
||||
def parsed_template(self, section=None, default={}, cached=False):
|
||||
'''
|
||||
Return the parsed template data for the resource. May be limited to
|
||||
only one section of the data, in which case a default value may also
|
||||
be supplied.
|
||||
'''
|
||||
if section is None:
|
||||
template = self.t
|
||||
if cached and self.cached_t:
|
||||
t = self.cached_t
|
||||
else:
|
||||
template = self.t.get(section, default)
|
||||
t = self.t
|
||||
if section is None:
|
||||
template = t
|
||||
else:
|
||||
template = t.get(section, default)
|
||||
return self.stack.resolve_runtime_data(template)
|
||||
|
||||
def cache_template(self):
|
||||
'''
|
||||
make a cache of the resource's parsed template
|
||||
this can then be used via parsed_template(cached=True)
|
||||
'''
|
||||
self.cached_t = self.stack.resolve_runtime_data(self.t)
|
||||
|
||||
def update_template_diff(self, json_snippet=None):
|
||||
'''
|
||||
Returns the difference between json_template and self.t
|
||||
|
@ -194,7 +206,8 @@ class Resource(object):
|
|||
update_allowed_set = set(self.update_allowed_keys)
|
||||
|
||||
# Create a set containing the keys in both current and update template
|
||||
current_template = self.parsed_template()
|
||||
current_template = self.parsed_template(cached=True)
|
||||
|
||||
template_keys = set(current_template.keys())
|
||||
new_template = self.stack.resolve_runtime_data(json_snippet)
|
||||
template_keys.update(set(new_template.keys()))
|
||||
|
@ -221,7 +234,9 @@ class Resource(object):
|
|||
update_allowed_set = set(self.update_allowed_properties)
|
||||
|
||||
# Create a set containing the keys in both current and update template
|
||||
current_properties = self.parsed_template().get('Properties', {})
|
||||
tmpl = self.parsed_template(cached=True)
|
||||
current_properties = tmpl.get('Properties', {})
|
||||
|
||||
template_properties = set(current_properties.keys())
|
||||
updated_properties = json_snippet.get('Properties', {})
|
||||
template_properties.update(set(updated_properties.keys()))
|
||||
|
|
|
@ -792,3 +792,235 @@ class StackTest(unittest.TestCase):
|
|||
self.m.VerifyAll()
|
||||
# Unset here so destroy() is not stubbed for stack.delete cleanup
|
||||
self.m.UnsetStubs()
|
||||
|
||||
@stack_delete_after
|
||||
def test_update_replace_by_reference(self):
|
||||
'''
|
||||
assertion:
|
||||
changes in dynamic attributes, due to other resources been updated
|
||||
are not ignored and can cause dependant resources to be updated.
|
||||
'''
|
||||
# patch in a dummy property schema for GenericResource
|
||||
dummy_schema = {'Foo': {'Type': 'String'}}
|
||||
resource.GenericResource.properties_schema = dummy_schema
|
||||
tmpl = {'Resources': {
|
||||
'AResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'abc'}},
|
||||
'BResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
tmpl2 = {'Resources': {
|
||||
'AResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'smelly'}},
|
||||
'BResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
|
||||
self.stack = parser.Stack(self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl))
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
|
||||
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
|
||||
self.assertEqual(self.stack['BResource'].properties['Foo'],
|
||||
'AResource')
|
||||
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'handle_update')
|
||||
resource.GenericResource.handle_update(
|
||||
tmpl2['Resources']['AResource']).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
|
||||
br2_snip = {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'inst-007'}}
|
||||
resource.GenericResource.handle_update(
|
||||
br2_snip).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'FnGetRefId')
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'AResource')
|
||||
resource.GenericResource.FnGetRefId().MultipleTimes().AndReturn(
|
||||
'inst-007')
|
||||
self.m.ReplayAll()
|
||||
|
||||
updated_stack = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2))
|
||||
self.stack.update(updated_stack)
|
||||
self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE)
|
||||
self.assertEqual(self.stack['AResource'].properties['Foo'], 'smelly')
|
||||
self.assertEqual(self.stack['BResource'].properties['Foo'], 'inst-007')
|
||||
self.m.VerifyAll()
|
||||
|
||||
@stack_delete_after
|
||||
def test_update_by_reference_and_rollback_1(self):
|
||||
'''
|
||||
assertion:
|
||||
check that rollback still works with dynamic metadata
|
||||
this test fails the first instance
|
||||
'''
|
||||
# patch in a dummy property schema for GenericResource
|
||||
dummy_schema = {'Foo': {'Type': 'String'}}
|
||||
resource.GenericResource.properties_schema = dummy_schema
|
||||
tmpl = {'Resources': {
|
||||
'AResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'abc'}},
|
||||
'BResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
tmpl2 = {'Resources': {
|
||||
'AResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'smelly'}},
|
||||
'BResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
|
||||
self.stack = parser.Stack(self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl),
|
||||
disable_rollback=False)
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
|
||||
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
|
||||
self.assertEqual(self.stack['BResource'].properties['Foo'],
|
||||
'AResource')
|
||||
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'handle_update')
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'FnGetRefId')
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'handle_create')
|
||||
|
||||
# mocks for first (failed update)
|
||||
resource.GenericResource.handle_update(
|
||||
tmpl2['Resources']['AResource']).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'AResource')
|
||||
|
||||
# mock to make the replace fail when creating the replacement resource
|
||||
resource.GenericResource.handle_create().AndRaise(Exception)
|
||||
|
||||
# mocks for second rollback update
|
||||
resource.GenericResource.handle_update(
|
||||
tmpl['Resources']['AResource']).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
|
||||
resource.GenericResource.handle_create().AndReturn(None)
|
||||
resource.GenericResource.FnGetRefId().MultipleTimes().AndReturn(
|
||||
'AResource')
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
updated_stack = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2),
|
||||
disable_rollback=False)
|
||||
self.stack.update(updated_stack)
|
||||
self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE)
|
||||
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
@stack_delete_after
|
||||
def test_update_by_reference_and_rollback_2(self):
|
||||
'''
|
||||
assertion:
|
||||
check that rollback still works with dynamic metadata
|
||||
this test fails the second instance
|
||||
'''
|
||||
# patch in a dummy property schema for GenericResource
|
||||
dummy_schema = {'Foo': {'Type': 'String'}}
|
||||
resource.GenericResource.properties_schema = dummy_schema
|
||||
tmpl = {'Resources': {
|
||||
'AResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'abc'}},
|
||||
'BResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
tmpl2 = {'Resources': {
|
||||
'AResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'smelly'}},
|
||||
'BResource': {'Type': 'GenericResourceType',
|
||||
'Properties': {
|
||||
'Foo': {'Ref': 'AResource'}}}}}
|
||||
|
||||
self.stack = parser.Stack(self.ctx, 'update_test_stack',
|
||||
template.Template(tmpl),
|
||||
disable_rollback=False)
|
||||
self.stack.store()
|
||||
self.stack.create()
|
||||
self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE)
|
||||
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
|
||||
self.assertEqual(self.stack['BResource'].properties['Foo'],
|
||||
'AResource')
|
||||
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'handle_update')
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'FnGetRefId')
|
||||
self.m.StubOutWithMock(resource.GenericResource, 'handle_create')
|
||||
|
||||
# mocks for first and second (failed update)
|
||||
resource.GenericResource.handle_update(
|
||||
tmpl2['Resources']['AResource']).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
br2_snip = {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'inst-007'}}
|
||||
resource.GenericResource.handle_update(
|
||||
br2_snip).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'AResource')
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
# self.state_set(self.UPDATE_IN_PROGRESS)
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
# self.state_set(self.DELETE_IN_PROGRESS)
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
# self.state_set(self.DELETE_COMPLETE)
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
# self.properties.validate()
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
# self.state_set(self.CREATE_IN_PROGRESS)
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
|
||||
# mock to make the replace fail when creating the second
|
||||
# replacement resource
|
||||
resource.GenericResource.handle_create().AndReturn(None)
|
||||
resource.GenericResource.handle_create().AndRaise(Exception)
|
||||
|
||||
# mocks for second rollback update
|
||||
resource.GenericResource.handle_update(
|
||||
tmpl['Resources']['AResource']).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
br2_snip = {'Type': 'GenericResourceType',
|
||||
'Properties': {'Foo': 'AResource'}}
|
||||
resource.GenericResource.handle_update(
|
||||
br2_snip).AndReturn(
|
||||
resource.Resource.UPDATE_REPLACE)
|
||||
|
||||
# self.state_set(self.DELETE_IN_PROGRESS)
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
# self.state_set(self.DELETE_IN_PROGRESS)
|
||||
resource.GenericResource.FnGetRefId().AndReturn(
|
||||
'inst-007')
|
||||
|
||||
resource.GenericResource.handle_create().AndReturn(None)
|
||||
resource.GenericResource.handle_create().AndReturn(None)
|
||||
|
||||
# reverting to AResource
|
||||
resource.GenericResource.FnGetRefId().MultipleTimes().AndReturn(
|
||||
'AResource')
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
updated_stack = parser.Stack(self.ctx, 'updated_stack',
|
||||
template.Template(tmpl2),
|
||||
disable_rollback=False)
|
||||
self.stack.update(updated_stack)
|
||||
self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE)
|
||||
self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc')
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
|
Loading…
Reference in New Issue