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
This commit is contained in:
Sergey Kraynev 2014-03-24 07:48:34 -04:00
parent b5f498654c
commit 312f331348
3 changed files with 122 additions and 0 deletions

View File

@ -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)

View File

@ -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())

View File

@ -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))