From ce9a99ab7953f75f021676f9f88707138844abc8 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Tue, 29 Apr 2014 13:07:29 -0400 Subject: [PATCH] Allow functions to calculate dependencies Change-Id: Ic58f54612d3707abc8727a65a28b9c4502eae31a --- heat/engine/cfn/functions.py | 9 +++++++++ heat/engine/function.py | 36 ++++++++++++++++++++++++++++++++++++ heat/tests/test_function.py | 20 ++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/heat/engine/cfn/functions.py b/heat/engine/cfn/functions.py index 40b9251b6c..b47229c10a 100644 --- a/heat/engine/cfn/functions.py +++ b/heat/engine/cfn/functions.py @@ -12,6 +12,7 @@ # under the License. import collections +import itertools import json import six @@ -108,6 +109,10 @@ class ResourceRef(function.Function): raise exception.InvalidTemplateReference(resource=resource_name, key=path) + def dependencies(self, path): + return itertools.chain(super(ResourceRef, self).dependencies(path), + [self._resource(path)]) + def result(self): return self._resource().FnGetRefId() @@ -164,6 +169,10 @@ class GetAtt(function.Function): raise exception.InvalidTemplateReference(resource=resource_name, key=path) + def dependencies(self, path): + return itertools.chain(super(GetAtt, self).dependencies(path), + [self._resource(path)]) + def result(self): attribute = function.resolve(self._attribute) diff --git a/heat/engine/function.py b/heat/engine/function.py index c7ada92786..50eff93863 100644 --- a/heat/engine/function.py +++ b/heat/engine/function.py @@ -13,6 +13,7 @@ import abc import collections +import itertools class Function(object): @@ -54,6 +55,9 @@ class Function(object): """ return {self.fn_name: self.args} + def dependencies(self, path): + return dependencies(self.args, '.'.join([path, self.fn_name])) + def __reduce__(self): """ Return a representation of the function suitable for pickling. @@ -130,3 +134,35 @@ def validate(snippet): isinstance(snippet, collections.Iterable)): for v in snippet: validate(v) + + +def dependencies(snippet, path=''): + """ + Return an iterator over Resource dependencies in a template snippet. + + The snippet should be already parsed to insert Function objects where + appropriate. + """ + + if isinstance(snippet, Function): + return snippet.dependencies(path) + + elif isinstance(snippet, collections.Mapping): + def mkpath(key): + return '.'.join([path, unicode(key)]) + + deps = (dependencies(value, + mkpath(key)) for key, value in snippet.items()) + return itertools.chain.from_iterable(deps) + + elif (not isinstance(snippet, basestring) and + isinstance(snippet, collections.Iterable)): + def mkpath(idx): + return ''.join([path, '[%d]' % idx]) + + deps = (dependencies(value, + mkpath(i)) for i, value in enumerate(snippet)) + return itertools.chain.from_iterable(deps) + + else: + return [] diff --git a/heat/tests/test_function.py b/heat/tests/test_function.py index 0efca3a763..4fac79be3a 100644 --- a/heat/tests/test_function.py +++ b/heat/tests/test_function.py @@ -22,6 +22,9 @@ class TestFunction(function.Function): if len(self.args) < 2: raise Exception(_('Need more arguments')) + def dependencies(self, path): + return ['foo', 'bar'] + def result(self): return 'wibble' @@ -122,3 +125,20 @@ class ValidateTest(HeatTestCase): snippet = {'foo': 'bar', 'blarg': self.func} ex = self.assertRaises(Exception, function.validate, snippet) self.assertEqual('Need more arguments', str(ex)) + + +class DependenciesTest(HeatTestCase): + func = TestFunction(None, 'test', None) + + scenarios = [ + ('function', dict(snippet=func)), + ('nested_map', dict(snippet={'wibble': func})), + ('nested_list', dict(snippet=['wibble', func])), + ('deep_nested', dict(snippet=[{'wibble': ['wibble', func]}])), + ] + + def test_dependencies(self): + deps = list(function.dependencies(self.snippet)) + self.assertIn('foo', deps) + self.assertIn('bar', deps) + self.assertEqual(2, len(deps))