diff --git a/heat/engine/resources/stack.py b/heat/engine/resources/stack.py index 4960f49b0..dc031ab68 100644 --- a/heat/engine/resources/stack.py +++ b/heat/engine/resources/stack.py @@ -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 { diff --git a/heat/tests/test_nested_stack.py b/heat/tests/test_nested_stack.py index 3346ca2bd..68a9bcfa5 100644 --- a/heat/tests/test_nested_stack.py +++ b/heat/tests/test_nested_stack.py @@ -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)