Cache names of required resources in ResourceDefinition

In order to calculate the dependencies of a ResourceDefinition, we have to
pass it a Stack object. This means we can't infer anything about the
resource's dependencies without instantiating the whole stack, and it means
we can't easily cache the result so we have to walk the entire
properties/metadata every time we need the dependencies.

This change splits a required_resource_names() method (the same as added to
OutputDefinition in the previous patch) out of the dependencies() method.
This new method calculates as much as we can know from the resource
definition alone and caches the result, while the dependencies() method
uses the (cached) result to calculate the graph dependencies for a given
Stack.

This also opens the way in future to changing the Function API to return
dependencies by name instead of as ResourceProxy objects.

Change-Id: Icdd0b2dd41116adae5e57ff3ef0e662ba75e6e2c
This commit is contained in:
Zane Bitter 2017-07-11 10:59:26 -04:00
parent 6b6d3779cd
commit b0916ad5bb
2 changed files with 46 additions and 27 deletions

View File

@ -99,6 +99,7 @@ class ResourceDefinition(object):
self._hash = hash(self.resource_type)
self._rendering = None
self._dep_names = None
assert isinstance(self.description, six.string_types)
@ -202,11 +203,44 @@ class ResourceDefinition(object):
function.dep_attrs(self._metadata,
resource_name))
def required_resource_names(self):
"""Return a set of names of all resources on which this depends.
Note that this is done entirely in isolation from the rest of the
template, so the resource names returned may refer to resources that
don't actually exist, or would have strict_dependency=False. Use the
dependencies() method to get validated dependencies.
"""
if self._dep_names is None:
explicit_depends = [] if self._depends is None else self._depends
def path(section):
return '.'.join([self.name, section])
prop_deps = function.dependencies(self._properties,
path('Properties'))
metadata_deps = function.dependencies(self._metadata,
path('Metadata'))
implicit_depends = six.moves.map(lambda rp: rp.name,
itertools.chain(prop_deps,
metadata_deps))
# (ricolin) External resource should not depend on any other
# resources. This operation is not allowed for now.
if self.external_id():
if explicit_depends:
raise exception.InvalidExternalResourceDependency(
external_id=self.external_id(),
resource_type=self.resource_type
)
self._dep_names = set()
else:
self._dep_names = set(itertools.chain(explicit_depends,
implicit_depends))
return self._dep_names
def dependencies(self, stack):
"""Return the Resource objects in given stack on which this depends."""
def path(section):
return '.'.join([self.name, section])
def get_resource(res_name):
if res_name not in stack:
if res_name in stack.t.get(stack.t.RESOURCES):
@ -215,31 +249,13 @@ class ResourceDefinition(object):
return
raise exception.InvalidTemplateReference(resource=res_name,
key=self.name)
return stack[res_name]
res = stack[res_name]
if getattr(res, 'strict_dependency', True):
return res
def strict_func_deps(data, datapath):
return six.moves.filter(
lambda r: getattr(r, 'strict_dependency', True),
six.moves.map(lambda rp: stack[rp.name],
function.dependencies(data, datapath)))
explicit_depends = [] if self._depends is None else self._depends
prop_deps = strict_func_deps(self._properties, path('Properties'))
metadata_deps = strict_func_deps(self._metadata, path('Metadata'))
# (ricolin) External resource should not depend on any other resources.
# This operation is not allowed for now.
if self.external_id():
if explicit_depends:
raise exception.InvalidExternalResourceDependency(
external_id=self.external_id(),
resource_type=self.resource_type
)
return itertools.chain()
return itertools.chain(
filter(None, (get_resource(dep) for dep in explicit_depends)),
prop_deps, metadata_deps)
return six.moves.filter(None,
six.moves.map(get_resource,
self.required_resource_names()))
def set_translation_rules(self, rules=None, client_resolve=True):
"""Helper method to update properties with translation rules."""

View File

@ -92,11 +92,13 @@ class ResourceDefinitionTest(common.HeatTestCase):
def test_dependencies_default(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType')
stack = {'foo': 'FOO', 'bar': 'BAR'}
self.assertEqual(set(), rd.required_resource_names())
self.assertEqual([], list(rd.dependencies(stack)))
def test_dependencies_explicit(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType', depends=['foo'])
stack = {'foo': 'FOO', 'bar': 'BAR'}
self.assertEqual({'foo'}, rd.required_resource_names())
self.assertEqual(['FOO'], list(rd.dependencies(stack)))
def test_dependencies_explicit_ext(self):
@ -117,6 +119,7 @@ class ResourceDefinitionTest(common.HeatTestCase):
t = template_format.parse(TEMPLATE_WITH_INVALID_EXPLICIT_DEPEND)
stack = utils.parse_stack(t)
rd = stack.t.resource_definitions(stack)['test3']
self.assertEqual({'test2'}, rd.required_resource_names())
self.assertRaises(exception.InvalidTemplateReference,
lambda: list(rd.dependencies(stack)))