Do static template validation for nested stacks

Currently we don't fail fast, even if there is an error
in the nested template which should fail validation.
This change to recurse and do static template validation
for nested stack resources.

Change-Id: I572ded640582419e0888e4b9f8eed3a3432d6121
Closes-Bug: #1388140
Closes-Bug: #1389104
This commit is contained in:
huangtianhua 2014-11-04 14:46:39 +08:00
parent 1cb2efc172
commit c31c34f8df
7 changed files with 136 additions and 4 deletions

View File

@ -48,6 +48,18 @@ class StackResource(resource.Resource):
super(StackResource, self).__init__(name, json_snippet, stack)
self._nested = None
def validate(self):
super(StackResource, self).validate()
try:
nested_stack = self._parse_nested_stack(
self.stack.name,
self.child_template(),
self.child_params())
nested_stack.validate()
except Exception as ex:
msg = _("Failed to validate: %s") % ex
raise exception.StackValidationFailed(message=msg)
def _outputs_to_attribs(self, json_snippet):
outputs = json_snippet.get('Outputs')
if not self.attributes and outputs:

View File

@ -179,6 +179,9 @@ class TestGroupAdjust(common.HeatTestCase):
t = template_format.parse(as_template)
stack = utils.parse_stack(t, params=inline_templates.as_params)
self.group = stack['WebServerGroup']
self.stub_ImageConstraint_validate()
self.stub_FlavorConstraint_validate()
self.stub_SnapshotConstraint_validate()
self.assertIsNone(self.group.validate())
def test_scaling_policy_cooldown_toosoon(self):

View File

@ -508,7 +508,7 @@ class AutoScalingTest(common.HeatTestCase):
u'AvailabilityZones': ['abc', 'xyz']}}
self.m.StubOutWithMock(short_id, 'generate_id')
short_id.generate_id().AndReturn('aaaabbbbcccc')
short_id.generate_id().MultipleTimes().AndReturn('aaaabbbbcccc')
now = timeutils.utcnow()
self._stub_meta_expected(now, 'ExactCapacity : 1')
@ -556,7 +556,7 @@ class AutoScalingTest(common.HeatTestCase):
}
self.m.StubOutWithMock(short_id, 'generate_id')
short_id.generate_id().AndReturn('aaaabbbbcccc')
short_id.generate_id().MultipleTimes().AndReturn('aaaabbbbcccc')
self.m.StubOutWithMock(neutron_lb.LoadBalancer, 'handle_update')
neutron_lb.LoadBalancer.handle_update(expected,

View File

@ -75,7 +75,7 @@ class InstanceGroupTest(common.HeatTestCase):
instead of instance.Instance.
"""
self.m.StubOutWithMock(parser.Stack, 'validate')
parser.Stack.validate()
parser.Stack.validate().MultipleTimes().AndReturn(None)
self.stub_KeypairConstraint_validate()
self.stub_ImageConstraint_validate()
self.stub_FlavorConstraint_validate()
@ -244,7 +244,7 @@ class InstanceGroupTest(common.HeatTestCase):
stack = utils.parse_stack(t)
self.m.StubOutWithMock(parser.Stack, 'validate')
parser.Stack.validate()
parser.Stack.validate().MultipleTimes().AndReturn(None)
self.stub_ImageConstraint_validate()
self.stub_KeypairConstraint_validate()
self.stub_FlavorConstraint_validate()
@ -327,6 +327,7 @@ class InstanceGroupTest(common.HeatTestCase):
stack = utils.parse_stack(t)
self._stub_create(2)
self.m.ReplayAll()
self.create_resource(t, stack, 'JobServerConfig')
rsrc = self.create_resource(t, stack, 'JobServerGroup')

View File

@ -438,6 +438,8 @@ Resources:
urlfetch.get(
'https://server.test/depth3.template').AndReturn(
self.nested_template)
self.m.StubOutWithMock(parser.Stack, 'validate')
parser.Stack.validate().MultipleTimes().AndReturn(None)
self.m.ReplayAll()
self.create_stack(root_template)
self.m.VerifyAll()

View File

@ -161,6 +161,7 @@ class ResourceGroupTest(common.HeatTestCase):
AttributeResource = generic_resource.ResourceWithComplexAttributes
resource._register_class("dummyattr.resource",
AttributeResource)
self.m.StubOutWithMock(stackm.Stack, 'validate')
def test_build_resource_definition(self):
stack = utils.parse_stack(template)

View File

@ -70,6 +70,63 @@ simple_template = '''
}
'''
main_template = '''
heat_template_version: 2013-05-23
resources:
volume_server:
type: nested.yaml
'''
my_wrong_nested_template = '''
heat_template_version: 2013-05-23
resources:
server:
type: OS::Nova::Server
properties:
image: F17-x86_64-gold
flavor: m1.small
volume:
type: OS::Cinder::Volume
properties:
size: 10
description: Volume for stack
volume_attachment:
type: OS::Cinder::VolumeAttachment
properties:
volume_id: { get_resource: volume }
instance_uuid: { get_resource: instance }
'''
resource_group_template = '''
heat_template_version: 2013-05-23
resources:
my_resource_group:
type: OS::Heat::ResourceGroup
properties:
resource_def:
type: idontexist
'''
heat_autoscaling_group_template = '''
heat_template_version: 2013-05-23
resources:
my_autoscaling_group:
type: OS::Heat::AutoScalingGroup
properties:
resource:
type: idontexist
desired_capacity: 2
max_size: 4
min_size: 1
'''
nova_server_template = '''
heat_template_version: 2013-05-23
resources:
group_server:
type: idontexist
'''
class MyStackResource(stack_resource.StackResource,
generic_rsrc.GenericResource):
@ -288,6 +345,62 @@ class StackResourceTest(common.HeatTestCase):
self.assertFalse(nested_stack.validate.called)
self.assertTrue(nested_stack.preview_resources.called)
def test_validate_error_reference(self):
stack_name = 'validate_error_reference'
tmpl = template_format.parse(main_template)
files = {'nested.yaml': my_wrong_nested_template}
stack = parser.Stack(utils.dummy_context(), stack_name,
templatem.Template(tmpl, files=files))
rsrc = stack['volume_server']
raise_exc_msg = ('The specified reference "instance" ('
'in volume_attachment.Properties.instance_uuid) '
'is incorrect')
exc = self.assertRaises(exception.StackValidationFailed,
rsrc.validate)
self.assertIn(raise_exc_msg, six.text_type(exc))
def _test_validate_unknown_resource_type(self, stack_name,
tmpl, resource_name):
raise_exc_msg = ('Unknown resource Type : idontexist')
stack = parser.Stack(utils.dummy_context(), stack_name, tmpl)
rsrc = stack[resource_name]
exc = self.assertRaises(exception.StackValidationFailed,
rsrc.validate)
self.assertIn(raise_exc_msg, six.text_type(exc))
def test_validate_resource_group(self):
# test validate without nested template
stack_name = 'validate_resource_group_template'
t = template_format.parse(resource_group_template)
tmpl = templatem.Template(t)
self._test_validate_unknown_resource_type(stack_name, tmpl,
'my_resource_group')
# validate with nested template
res_prop = t['resources']['my_resource_group']['properties']
res_prop['resource_def']['type'] = 'nova_server.yaml'
files = {'nova_server.yaml': nova_server_template}
tmpl = templatem.Template(t, files=files)
self._test_validate_unknown_resource_type(stack_name, tmpl,
'my_resource_group')
def test_validate_heat_autoscaling_group(self):
# test validate without nested template
stack_name = 'validate_heat_autoscaling_group_template'
t = template_format.parse(heat_autoscaling_group_template)
tmpl = templatem.Template(t)
self._test_validate_unknown_resource_type(stack_name, tmpl,
'my_autoscaling_group')
# validate with nested template
res_prop = t['resources']['my_autoscaling_group']['properties']
res_prop['resource']['type'] = 'nova_server.yaml'
files = {'nova_server.yaml': nova_server_template}
tmpl = templatem.Template(t, files=files)
self._test_validate_unknown_resource_type(stack_name, tmpl,
'my_autoscaling_group')
def test__validate_nested_resources_checks_num_of_resources(self):
stack_resource.cfg.CONF.set_override('max_resources_per_stack', 2)
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',