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:
parent
6b6d3779cd
commit
b0916ad5bb
|
@ -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."""
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
Loading…
Reference in New Issue