]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Allow in-place update of nested stack
authorLiang Chen <cbjchen@cn.ibm.com>
Thu, 22 Aug 2013 05:42:18 +0000 (13:42 +0800)
committerLiang Chen <cbjchen@cn.ibm.com>
Sat, 24 Aug 2013 03:14:56 +0000 (11:14 +0800)
Not raising UpdateReplace exception anymore for nested stack update.
Instead, propagating the update request to the nested stack, so that
the nested stack itself will decide the best way to handle the update
request for every of its resources.

blueprint nested-stack-updates

Change-Id: Ibfe7bee71fe57a601b93fa05a4a0c1cda972290e

heat/engine/resources/stack.py
heat/tests/test_nested_stack.py

index 4960f49b06899e2bbd377aa3607b295a6d9f293f..dc031ab6818b23664b8cd6d20ae7b7f807b16bc1 100644 (file)
@@ -16,6 +16,7 @@
 from heat.common import exception
 from heat.common import template_format
 from heat.common import urlfetch
+from heat.engine.properties import Properties
 from heat.engine import stack_resource
 
 from heat.openstack.common import log as logging
@@ -38,6 +39,10 @@ class NestedStack(stack_resource.StackResource):
                          PROP_TIMEOUT_MINS: {'Type': 'Number'},
                          PROP_PARAMETERS: {'Type': 'Map'}}
 
+    update_allowed_keys = ('Properties',)
+    update_allowed_properties = (PROP_TEMPLATE_URL, PROP_TIMEOUT_MINS,
+                                 PROP_PARAMETERS)
+
     def handle_create(self):
         template_data = urlfetch.get(self.properties[PROP_TEMPLATE_URL])
         template = template_format.parse(template_data)
@@ -58,6 +63,20 @@ class NestedStack(stack_resource.StackResource):
     def FnGetRefId(self):
         return self.nested().identifier().arn()
 
+    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
+        # Nested stack template may be changed even if the prop_diff is empty.
+        self.properties = Properties(self.properties_schema,
+                                     json_snippet.get('Properties', {}),
+                                     self.stack.resolve_runtime_data,
+                                     self.name)
+
+        template_data = urlfetch.get(self.properties[PROP_TEMPLATE_URL])
+        template = template_format.parse(template_data)
+
+        self.update_with_template(template,
+                                  self.properties[PROP_PARAMETERS],
+                                  self.properties[PROP_TIMEOUT_MINS])
+
 
 def resource_mapping():
     return {
index 3346ca2bdc6a748f05b330b9a54363f4bff4eae9..68a9bcfa5a9625b86a91b684cc03f137e76169c3 100644 (file)
@@ -13,6 +13,8 @@
 #    under the License.
 
 
+import copy
+
 from heat.common import exception
 from heat.common import template_format
 from heat.common import urlfetch
@@ -47,6 +49,16 @@ Outputs:
     Value: bar
 '''
 
+    update_template = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Parameters:
+  KeyName:
+    Type: String
+Outputs:
+  Bar:
+    Value: foo
+'''
+
     def setUp(self):
         super(NestedStackTest, self).setUp()
         self.m.StubOutWithMock(urlfetch, 'get')
@@ -67,9 +79,9 @@ Outputs:
         stack.store()
         return stack
 
-    def test_nested_stack(self):
-        urlfetch.get('https://localhost/the.template').AndReturn(
-            self.nested_template)
+    def test_nested_stack_create(self):
+        urlfetch.get('https://localhost/the.template').MultipleTimes().\
+            AndReturn(self.nested_template)
         self.m.ReplayAll()
 
         stack = self.create_stack(self.test_template)
@@ -80,9 +92,6 @@ Outputs:
                       rsrc.physical_resource_name())
         self.assertTrue(rsrc.FnGetRefId().startswith(arn_prefix))
 
-        self.assertRaises(resource.UpdateReplace,
-                          rsrc.handle_update, {}, {}, {})
-
         self.assertEqual('bar', rsrc.FnGetAtt('Outputs.Foo'))
         self.assertRaises(
             exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Foo')
@@ -96,6 +105,44 @@ Outputs:
 
         self.m.VerifyAll()
 
+    def test_nested_stack_update(self):
+        urlfetch.get('https://localhost/the.template').MultipleTimes().\
+            AndReturn(self.nested_template)
+        urlfetch.get('https://localhost/new.template').MultipleTimes().\
+            AndReturn(self.update_template)
+
+        self.m.ReplayAll()
+
+        stack = self.create_stack(self.test_template)
+        rsrc = stack['the_nested']
+
+        original_nested_id = rsrc.resource_id
+        t = template_format.parse(self.test_template)
+        new_res = copy.deepcopy(t['Resources']['the_nested'])
+        new_res['Properties']['TemplateURL'] = 'https://localhost/new.template'
+        prop_diff = {'TemplateURL': 'https://localhost/new.template'}
+        rsrc.handle_update(new_res, {}, prop_diff)
+
+        # Expect the physical resource name staying the same after update,
+        # so that the nested was actually updated instead of replaced.
+        self.assertEqual(original_nested_id, rsrc.resource_id)
+        db_nested = db_api.stack_get(stack.context,
+                                     rsrc.resource_id)
+        # Owner_id should be preserved during the update process.
+        self.assertEqual(stack.id, db_nested.owner_id)
+
+        self.assertEqual('foo', rsrc.FnGetAtt('Outputs.Bar'))
+        self.assertRaises(
+            exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Foo')
+        self.assertRaises(
+            exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Outputs.Foo')
+        self.assertRaises(
+            exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Bar')
+
+        rsrc.delete()
+
+        self.m.VerifyAll()
+
     def test_nested_stack_suspend_resume(self):
         urlfetch.get('https://localhost/the.template').AndReturn(
             self.nested_template)