From 312f331348ca5ec23b2d6316a02ad08331c249d5 Mon Sep 17 00:00:00 2001 From: Sergey Kraynev Date: Mon, 24 Mar 2014 07:48:34 -0400 Subject: [PATCH] Adding validation algorithm for get attr functions Turn on validation for functions Fn::GetAtt and get_attr. Validation will be skipped if resource has overwritten FnGetAtt method. Related-Bug: #1273490 Change-Id: I15b6b7447face0cf2fe1d6059d3fb900778b66cc --- heat/engine/cfn/functions.py | 10 +++++ heat/tests/test_function.py | 72 ++++++++++++++++++++++++++++++++++++ heat/tests/test_parser.py | 40 ++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/heat/engine/cfn/functions.py b/heat/engine/cfn/functions.py index c976439ef..0f763d3ed 100644 --- a/heat/engine/cfn/functions.py +++ b/heat/engine/cfn/functions.py @@ -20,6 +20,7 @@ import six from heat.api.aws import utils as aws_utils from heat.common import exception from heat.engine import function +from heat.engine import resource class FindInMap(function.Function): @@ -173,6 +174,15 @@ class GetAtt(function.Function): return itertools.chain(super(GetAtt, self).dependencies(path), [self._resource(path)]) + def validate(self): + super(GetAtt, self).validate() + res = self._resource() + attr = function.resolve(self._attribute) + if (type(res).FnGetAtt == resource.Resource.FnGetAtt and + attr not in res.attributes_schema.keys()): + raise exception.InvalidTemplateAttribute( + resource=self._resource_name, key=attr) + def result(self): attribute = function.resolve(self._attribute) diff --git a/heat/tests/test_function.py b/heat/tests/test_function.py index ac3d24873..3793e6126 100644 --- a/heat/tests/test_function.py +++ b/heat/tests/test_function.py @@ -13,9 +13,18 @@ import copy import six +import uuid +from heat.common import exception +from heat.engine.cfn import functions +from heat.engine import environment from heat.engine import function +from heat.engine import parser +from heat.engine import resource +from heat.engine import rsrc_defn from heat.tests.common import HeatTestCase +from heat.tests import generic_resource as generic_rsrc +from heat.tests import utils class TestFunction(function.Function): @@ -143,3 +152,66 @@ class DependenciesTest(HeatTestCase): self.assertIn('foo', deps) self.assertIn('bar', deps) self.assertEqual(2, len(deps)) + + +class ValidateGetAttTest(HeatTestCase): + def setUp(self): + super(ValidateGetAttTest, self).setUp() + + resource._register_class('GenericResourceType', + generic_rsrc.GenericResource) + + env = environment.Environment() + env.load({u'resource_registry': + {u'OS::Test::GenericResource': u'GenericResourceType'}}) + + class FakeResource(generic_rsrc.GenericResource): + def FnGetAtt(self, name): + pass + + resource._register_class('OverwrittenFnGetAttType', FakeResource) + env.load({u'resource_registry': + {u'OS::Test::FakeResource': u'OverwrittenFnGetAttType'}}) + + self.stack = parser.Stack( + utils.dummy_context(), 'test_stack', + parser.Template({"HeatTemplateFormatVersion": "2012-12-12"}), + env=env, stack_id=str(uuid.uuid4())) + res_defn = rsrc_defn.ResourceDefinition('test_rsrc', + 'OS::Test::GenericResource') + self.rsrc = resource.Resource('test_rsrc', res_defn, self.stack) + self.stack.add_resource(self.rsrc) + + def test_resource_is_appear_in_stack(self): + func = functions.GetAtt(self.stack, 'Fn::GetAtt', + [self.rsrc.name, 'Foo']) + self.assertIsNone(func.validate()) + + def test_resource_is_not_appear_in_stack(self): + self.stack.remove_resource(self.rsrc.name) + + func = functions.GetAtt(self.stack, 'Fn::GetAtt', + [self.rsrc.name, 'Foo']) + ex = self.assertRaises(exception.InvalidTemplateReference, + func.validate) + self.assertEqual('The specified reference "test_rsrc" (in unknown) ' + 'is incorrect.', str(ex)) + + def test_resource_no_attribute_with_default_fn_get_att(self): + func = functions.GetAtt(self.stack, 'Fn::GetAtt', + [self.rsrc.name, 'Bar']) + ex = self.assertRaises(exception.InvalidTemplateAttribute, + func.validate) + self.assertEqual('The Referenced Attribute (test_rsrc Bar) ' + 'is incorrect.', str(ex)) + + def test_resource_no_attribute_with_overwritten_fn_get_att(self): + res_defn = rsrc_defn.ResourceDefinition('test_rsrc', + 'OS::Test::FakeResource') + self.rsrc = resource.Resource('test_rsrc', res_defn, self.stack) + self.stack.add_resource(self.rsrc) + self.rsrc.attributes_schema = {} + + func = functions.GetAtt(self.stack, 'Fn::GetAtt', + [self.rsrc.name, 'Foo']) + self.assertIsNone(func.validate()) diff --git a/heat/tests/test_parser.py b/heat/tests/test_parser.py index d841bd6bf..b271ff29a 100644 --- a/heat/tests/test_parser.py +++ b/heat/tests/test_parser.py @@ -3684,3 +3684,43 @@ class StackTest(HeatTestCase): fake_snapshot = collections.namedtuple('Snapshot', ('data',))(data) self.stack.delete_snapshot(fake_snapshot) self.assertEqual([data['resources']['AResource']], snapshots) + + def test_incorrect_outputs_cfn_get_attr(self): + tmpl = {'HeatTemplateFormatVersion': '2012-12-12', + 'Resources': { + 'AResource': {'Type': 'ResourceWithPropsType', + 'Properties': {'Foo': 'abc'}}}, + 'Outputs': { + 'Resource_attr': { + 'Value': { + 'Fn::GetAtt': ['AResource', 'Bar']}}}} + + self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs', + template.Template(tmpl)) + + ex = self.assertRaises(exception.StackValidationFailed, + self.stack.validate) + + self.assertEqual('Output validation error: The Referenced Attribute ' + '(AResource Bar) is incorrect.', + str(ex)) + + def test_incorrect_outputs_hot_get_attr(self): + tmpl = {'heat_template_version': '2013-05-23', + 'resources': { + 'AResource': {'type': 'ResourceWithPropsType', + 'properties': {'Foo': 'abc'}}}, + 'outputs': { + 'resource_attr': { + 'value': { + 'get_attr': ['AResource', 'Bar']}}}} + + self.stack = parser.Stack(self.ctx, 'stack_with_correct_outputs', + template.Template(tmpl)) + + ex = self.assertRaises(exception.StackValidationFailed, + self.stack.validate) + + self.assertEqual('Output validation error: The Referenced Attribute ' + '(AResource Bar) is incorrect.', + str(ex))