From d4fa4d25f322ac87efd5235b45c26cf814faea53 Mon Sep 17 00:00:00 2001 From: Christopher Armstrong Date: Thu, 8 Aug 2013 23:00:29 +0000 Subject: [PATCH] Validate created/updated stacks in StackResource StackResource.create_with_template and update_with_template now validate the resulting stacks. Change-Id: I52a81edb2af6bee70146f6dbd2179900e580d69b --- heat/engine/stack_resource.py | 18 +++++----- heat/tests/test_autoscaling.py | 15 +++++++- heat/tests/test_instance_group.py | 6 ++++ heat/tests/test_stack_resource.py | 57 +++++++++++++++++++++---------- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/heat/engine/stack_resource.py b/heat/engine/stack_resource.py index 5c436361..edc94805 100644 --- a/heat/engine/stack_resource.py +++ b/heat/engine/stack_resource.py @@ -68,14 +68,15 @@ class StackResource(resource.Resource): # Note we disable rollback for nested stacks, since they # should be rolled back by the parent stack on failure - self._nested = parser.Stack(self.context, - self.physical_resource_name(), - template, - environment.Environment(user_params), - timeout_mins=timeout_mins, - disable_rollback=True, - parent_resource=self) - + nested = parser.Stack(self.context, + self.physical_resource_name(), + template, + environment.Environment(user_params), + timeout_mins=timeout_mins, + disable_rollback=True, + parent_resource=self) + nested.validate() + self._nested = nested nested_id = self._nested.store(self.stack) self.resource_id_set(nested_id) @@ -112,6 +113,7 @@ class StackResource(resource.Resource): timeout_mins=timeout_mins, disable_rollback=True, parent_resource=self) + stack.validate() return self._nested.update(stack) def delete_nested(self): diff --git a/heat/tests/test_autoscaling.py b/heat/tests/test_autoscaling.py index 110e41bf..e45d7ed3 100644 --- a/heat/tests/test_autoscaling.py +++ b/heat/tests/test_autoscaling.py @@ -124,8 +124,12 @@ class AutoScalingTest(HeatTestCase): self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) return rsrc - def _stub_create(self, num): + def _stub_validate(self): + self.m.StubOutWithMock(parser.Stack, 'validate') + parser.Stack.validate().MultipleTimes() + def _stub_create(self, num): + self._stub_validate() self.m.StubOutWithMock(instance.Instance, 'handle_create') self.m.StubOutWithMock(instance.Instance, 'check_create_complete') cookie = object() @@ -450,6 +454,7 @@ class AutoScalingTest(HeatTestCase): t = template_format.parse(as_template) stack = utils.parse_stack(t, params=self.params) + self._stub_validate() self.m.StubOutWithMock(instance.Instance, 'handle_create') self.m.StubOutWithMock(instance.Instance, 'check_create_complete') instance.Instance.handle_create().AndRaise(Exception) @@ -661,6 +666,7 @@ class AutoScalingTest(HeatTestCase): # reduce to 1 self._stub_lb_reload(1) + self._stub_validate() self._stub_meta_expected(now, 'ChangeInCapacity : -2') self.m.ReplayAll() rsrc.adjust(-2) @@ -678,6 +684,7 @@ class AutoScalingTest(HeatTestCase): # set to 2 self._stub_lb_reload(2) + self._stub_validate() self._stub_meta_expected(now, 'ExactCapacity : 2') self.m.ReplayAll() rsrc.adjust(2, 'ExactCapacity') @@ -704,6 +711,7 @@ class AutoScalingTest(HeatTestCase): self.m.StubOutWithMock(instance.Instance, 'handle_create') instance.Instance.handle_create().AndRaise(Exception) self._stub_lb_reload(1, unset=False, nochange=True) + self._stub_validate() self.m.ReplayAll() rsrc.adjust(1) @@ -765,6 +773,7 @@ class AutoScalingTest(HeatTestCase): # reduce by 50% self._stub_lb_reload(1) self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') + self._stub_validate() self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') self.assertEqual(['WebServerGroup-0'], @@ -802,6 +811,7 @@ class AutoScalingTest(HeatTestCase): # reduce by 50% self._stub_lb_reload(1) + self._stub_validate() self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') @@ -855,6 +865,7 @@ class AutoScalingTest(HeatTestCase): # reduce by 50% self._stub_lb_reload(1) + self._stub_validate() self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') @@ -909,6 +920,7 @@ class AutoScalingTest(HeatTestCase): # reduce by 50% self._stub_lb_reload(1) self._stub_meta_expected(now, 'PercentChangeInCapacity : -50') + self._stub_validate() self.m.ReplayAll() rsrc.adjust(-50, 'PercentChangeInCapacity') self.assertEqual(['WebServerGroup-0'], @@ -994,6 +1006,7 @@ class AutoScalingTest(HeatTestCase): # Scale down one self._stub_lb_reload(1) + self._stub_validate() self._stub_meta_expected(now, 'ChangeInCapacity : -1', 2) self.m.StubOutWithMock(asc.ScalingPolicy, 'keystone') diff --git a/heat/tests/test_instance_group.py b/heat/tests/test_instance_group.py index 37109fa9..c4f554e8 100644 --- a/heat/tests/test_instance_group.py +++ b/heat/tests/test_instance_group.py @@ -21,6 +21,7 @@ from heat.engine.resources import instance from heat.engine import resource from heat.engine import resources from heat.engine import scheduler +from heat.engine import parser from heat.tests.common import HeatTestCase from heat.tests import utils @@ -67,10 +68,13 @@ class InstanceGroupTest(HeatTestCase): :param instance_class: The resource class to expect to be created instead of instance.Instance. """ + self.m.StubOutWithMock(parser.Stack, 'validate') + parser.Stack.validate() self.m.StubOutWithMock(instance_class, 'handle_create') self.m.StubOutWithMock(instance_class, 'check_create_complete') cookie = object() + for x in range(num): instance_class.handle_create().AndReturn(cookie) instance_class.check_create_complete(cookie).AndReturn(False) @@ -147,6 +151,8 @@ class InstanceGroupTest(HeatTestCase): self.m.StubOutWithMock(instance.Instance, 'handle_create') not_found = exception.ImageNotFound(image_name='bla') instance.Instance.handle_create().AndRaise(not_found) + self.m.StubOutWithMock(parser.Stack, 'validate') + parser.Stack.validate() self.m.ReplayAll() diff --git a/heat/tests/test_stack_resource.py b/heat/tests/test_stack_resource.py index 2819de04..7eb7e759 100644 --- a/heat/tests/test_stack_resource.py +++ b/heat/tests/test_stack_resource.py @@ -25,15 +25,14 @@ from heat.tests.common import HeatTestCase from heat.tests import generic_resource as generic_rsrc from heat.tests import utils + ws_res_snippet = {"Type": "some_magic_type", "metadata": { "key": "value", "some": "more stuff"}} -wp_template = ''' +param_template = ''' { - "AWSTemplateFormatVersion" : "2010-09-09", - "Description" : "WordPress", "Parameters" : { "KeyName" : { "Description" : "KeyName", @@ -43,24 +42,16 @@ wp_template = ''' }, "Resources" : { "WebServer": { - "Type": "AWS::EC2::Instance", - "metadata": {"Fn::ResourceFacade": "Metadata"}, - "Properties": { - "ImageId" : "F17-x86_64-gold", - "InstanceType" : "m1.large", - "KeyName" : "test", - "UserData" : "wordpress" - } + "Type": "GenericResource", + "Properties": {} } } } ''' -generic_template = ''' +simple_template = ''' { - "AWSTemplateFormatVersion" : "2010-09-09", - "Description" : "WordPress", "Parameters" : {}, "Resources" : { "WebServer": { @@ -105,8 +96,8 @@ class StackResourceTest(HeatTestCase): self.parent_resource = MyStackResource('test', ws_res_snippet, self.parent_stack) - self.templ = template_format.parse(wp_template) - self.generic_template = template_format.parse(generic_template) + self.templ = template_format.parse(param_template) + self.simple_template = template_format.parse(simple_template) @utils.stack_delete_after def test_create_with_template_ok(self): @@ -120,6 +111,36 @@ class StackResourceTest(HeatTestCase): self.assertEqual(self.templ, self.stack.t.t) self.assertEqual(self.stack.id, self.parent_resource.resource_id) + @utils.stack_delete_after + def test_create_with_template_validates(self): + """ + Creating a stack with a template validates the created stack, so that + an invalid template will cause an error to be raised. + """ + # Make a parameter key with the same name as the resource to cause a + # simple validation error + template = self.simple_template.copy() + template['Parameters']['WebServer'] = {'Type': 'String'} + self.assertRaises( + exception.StackValidationFailed, + self.parent_resource.create_with_template, + template, {'WebServer': 'foo'}) + + @utils.stack_delete_after + def test_update_with_template_validates(self): + """Updating a stack with a template validates the created stack.""" + create_result = self.parent_resource.create_with_template( + self.simple_template, {}) + while not create_result.step(): + pass + + template = self.simple_template.copy() + template['Parameters']['WebServer'] = {'Type': 'String'} + self.assertRaises( + exception.StackValidationFailed, + self.parent_resource.update_with_template, + template, {'WebServer': 'foo'}) + @utils.stack_delete_after def test_update_with_template_ok(self): """ @@ -127,12 +148,12 @@ class StackResourceTest(HeatTestCase): given template and user parameters. """ create_result = self.parent_resource.create_with_template( - self.generic_template, {}) + self.simple_template, {}) while not create_result.step(): pass self.stack = self.parent_resource.nested() - new_templ = self.generic_template.copy() + new_templ = self.simple_template.copy() inst_snippet = new_templ["Resources"]["WebServer"].copy() new_templ["Resources"]["WebServer2"] = inst_snippet update_result = self.parent_resource.update_with_template( -- 2.45.2