]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
make parsed template snapshots before updating
authorAngus Salkeld <asalkeld@redhat.com>
Wed, 6 Mar 2013 05:28:00 +0000 (16:28 +1100)
committerAngus Salkeld <asalkeld@redhat.com>
Wed, 6 Mar 2013 11:54:35 +0000 (22:54 +1100)
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

heat/engine/parser.py
heat/engine/resource.py
heat/tests/test_parser.py

index fa1c6e77e079b841a2b8429ade8bdb08d4e79f6a..72c0bf94551557bf364ce9c583a1cc4a72d9baaf 100644 (file)
@@ -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:
index 7458e517ecdd096f9413aec03c96dfc7b7882b18..ee9c93f08b3254426f829b6fe4a6a42f40e43e65 100644 (file)
@@ -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 cached and self.cached_t:
+            t = self.cached_t
+        else:
+            t = self.t
         if section is None:
-            template = self.t
+            template = t
         else:
-            template = self.t.get(section, default)
+            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()))
index 5191ae4a991929c334560ff1f8965b8ff7dc4c28..0e21d1f2b929e76754b3244ce17c66e30c4430be 100644 (file)
@@ -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()