Add way to collect map of needed attributes
Change-Id: I36cd2d418d570da3a0aa9df5408442251a400978 Implements: blueprint convergence-push-data
This commit is contained in:
parent
9dc904fddc
commit
401fd430d1
@ -171,6 +171,14 @@ class GetAtt(function.Function):
|
||||
raise exception.InvalidTemplateReference(resource=resource_name,
|
||||
key=path)
|
||||
|
||||
def dep_attrs(self, resource_name):
|
||||
if self._resource().name == resource_name:
|
||||
attrs = [function.resolve(self._attribute)]
|
||||
else:
|
||||
attrs = []
|
||||
return itertools.chain(super(GetAtt, self).dep_attrs(resource_name),
|
||||
attrs)
|
||||
|
||||
def dependencies(self, path):
|
||||
return itertools.chain(super(GetAtt, self).dependencies(path),
|
||||
[self._resource(path)])
|
||||
|
@ -59,6 +59,9 @@ class Function(object):
|
||||
def dependencies(self, path):
|
||||
return dependencies(self.args, '.'.join([path, self.fn_name]))
|
||||
|
||||
def dep_attrs(self, resource_name):
|
||||
return dep_attrs(self.args, resource_name)
|
||||
|
||||
def __reduce__(self):
|
||||
"""
|
||||
Return a representation of the function suitable for pickling.
|
||||
@ -167,3 +170,25 @@ def dependencies(snippet, path=''):
|
||||
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def dep_attrs(snippet, resource_name):
|
||||
"""
|
||||
Return an iterator over dependent attributes for specified resource_name
|
||||
in a template snippet.
|
||||
|
||||
The snippet should be already parsed to insert Function objects where
|
||||
appropriate.
|
||||
"""
|
||||
|
||||
if isinstance(snippet, Function):
|
||||
return snippet.dep_attrs(resource_name)
|
||||
|
||||
elif isinstance(snippet, collections.Mapping):
|
||||
attrs = (dep_attrs(value, resource_name) for value in snippet.items())
|
||||
return itertools.chain.from_iterable(attrs)
|
||||
elif (not isinstance(snippet, six.string_types) and
|
||||
isinstance(snippet, collections.Iterable)):
|
||||
attrs = (dep_attrs(value, resource_name) for value in snippet)
|
||||
return itertools.chain.from_iterable(attrs)
|
||||
return []
|
||||
|
@ -419,6 +419,9 @@ class Resource(object):
|
||||
text = '%s "%s"' % (self.__class__.__name__, self.name)
|
||||
return encodeutils.safe_decode(text)
|
||||
|
||||
def dep_attrs(self, resource_name):
|
||||
return self.t.dep_attrs(resource_name)
|
||||
|
||||
def add_dependencies(self, deps):
|
||||
for dep in self.t.dependencies(self.stack):
|
||||
deps += (self, dep)
|
||||
|
@ -141,6 +141,16 @@ class ResourceDefinitionCore(object):
|
||||
deletion_policy=reparse_snippet(self._deletion_policy),
|
||||
update_policy=reparse_snippet(self._update_policy))
|
||||
|
||||
def dep_attrs(self, resource_name):
|
||||
"""
|
||||
Return an iterator over dependent attributes for specified
|
||||
resource_name in resources' properties and metadata fields.
|
||||
"""
|
||||
return itertools.chain(function.dep_attrs(self._properties,
|
||||
resource_name),
|
||||
function.dep_attrs(self._metadata,
|
||||
resource_name))
|
||||
|
||||
def dependencies(self, stack):
|
||||
"""
|
||||
Return the Resource objects in the given stack on which this depends.
|
||||
|
@ -14,6 +14,7 @@
|
||||
import collections
|
||||
import copy
|
||||
import datetime
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
|
||||
@ -279,6 +280,19 @@ class Stack(collections.Mapping):
|
||||
if not self.parameters.set_stack_id(self.identifier()):
|
||||
LOG.warn(_LW("Unable to set parameters StackId identifier"))
|
||||
|
||||
@staticmethod
|
||||
def get_dep_attrs(resources, outputs, resource_name):
|
||||
'''
|
||||
Return the set of dependent attributes for specified resource name by
|
||||
inspecting all resources and outputs in template.
|
||||
'''
|
||||
attr_lists = itertools.chain((res.dep_attrs(resource_name)
|
||||
for res in resources),
|
||||
(function.dep_attrs(out.get('Value', ''),
|
||||
resource_name)
|
||||
for out in six.itervalues(outputs)))
|
||||
return set(itertools.chain.from_iterable(attr_lists))
|
||||
|
||||
@staticmethod
|
||||
def _get_dependencies(resources):
|
||||
'''Return the dependency graph for a list of resources.'''
|
||||
|
182
heat/tests/test_stack_collect_attributes.py
Normal file
182
heat/tests/test_stack_collect_attributes.py
Normal file
@ -0,0 +1,182 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from heat.common import template_format
|
||||
from heat.engine import parser
|
||||
from heat.engine import resource
|
||||
from heat.engine import template
|
||||
from heat.tests import common
|
||||
from heat.tests import generic_resource as generic_rsrc
|
||||
from heat.tests import utils
|
||||
|
||||
|
||||
tmpl1 = """
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
"""
|
||||
|
||||
tmpl2 = """
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
BResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
metadata:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
outputs:
|
||||
out1:
|
||||
value: {get_attr: [AResource, attr_A1]}
|
||||
"""
|
||||
|
||||
tmpl3 = """
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
BResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
Doo: {get_attr: [AResource, attr_A2]}
|
||||
Bar: {get_attr: [AResource, attr_A3]}
|
||||
metadata:
|
||||
first: {get_attr: [AResource, meta_A1]}
|
||||
second: {get_attr: [AResource, meta_A2]}
|
||||
third: {get_attr: [AResource, attr_A3]}
|
||||
outputs:
|
||||
out1:
|
||||
value: {get_attr: [AResource, out_A1]}
|
||||
out2:
|
||||
value: {get_attr: [AResource, out_A2]}
|
||||
"""
|
||||
|
||||
tmpl4 = """
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
BResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
CResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
DResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
Doo: {get_attr: [BResource, attr_B1]}
|
||||
metadata:
|
||||
Doo: {get_attr: [CResource, attr_C1]}
|
||||
outputs:
|
||||
out1:
|
||||
value: [{get_attr: [AResource, attr_A1]},
|
||||
{get_attr: [BResource, attr_B1]},
|
||||
{get_attr: [CResource, attr_C1]}]
|
||||
"""
|
||||
|
||||
tmpl5 = """
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: 'abc'
|
||||
BResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
Doo: {get_attr: [AResource, attr_A2]}
|
||||
metadata:
|
||||
first: {get_attr: [AResource, meta_A1]}
|
||||
CResource:
|
||||
type: ResourceWithPropsType
|
||||
properties:
|
||||
Foo: {get_attr: [AResource, attr_A1]}
|
||||
Doo: {get_attr: [BResource, attr_B2]}
|
||||
metadata:
|
||||
Doo: {get_attr: [BResource, attr_B1]}
|
||||
first: {get_attr: [AResource, meta_A1]}
|
||||
second: {get_attr: [BResource, meta_B2]}
|
||||
outputs:
|
||||
out1:
|
||||
value: [{get_attr: [AResource, attr_A3]},
|
||||
{get_attr: [AResource, attr_A4]},
|
||||
{get_attr: [BResource, attr_B3]}]
|
||||
"""
|
||||
|
||||
|
||||
class DepAttrsTest(common.HeatTestCase):
|
||||
|
||||
scenarios = [
|
||||
('no_attr',
|
||||
dict(tmpl=tmpl1,
|
||||
expected={'AResource': set()})),
|
||||
('one_res_one_attr',
|
||||
dict(tmpl=tmpl2,
|
||||
expected={'AResource': {'attr_A1'},
|
||||
'BResource': set()})),
|
||||
('one_res_several_attrs',
|
||||
dict(tmpl=tmpl3,
|
||||
expected={'AResource': {'attr_A1', 'attr_A2', 'attr_A3',
|
||||
'meta_A1', 'meta_A2', 'out_A1',
|
||||
'out_A2'},
|
||||
'BResource': set()})),
|
||||
('several_res_one_attr',
|
||||
dict(tmpl=tmpl4,
|
||||
expected={'AResource': {'attr_A1'},
|
||||
'BResource': {'attr_B1'},
|
||||
'CResource': {'attr_C1'},
|
||||
'DResource': set()})),
|
||||
('several_res_several_attrs',
|
||||
dict(tmpl=tmpl5,
|
||||
expected={'AResource': {'attr_A1', 'attr_A2', 'meta_A1',
|
||||
'attr_A3', 'attr_A4'},
|
||||
'BResource': {'attr_B1', 'attr_B2', 'meta_B2',
|
||||
'attr_B3'},
|
||||
'CResource': set()}))
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(DepAttrsTest, self).setUp()
|
||||
self.ctx = utils.dummy_context()
|
||||
resource._register_class('ResourceWithPropsType',
|
||||
generic_rsrc.ResourceWithProps)
|
||||
|
||||
def test_dep_attrs(self):
|
||||
|
||||
parsed_tmpl = template_format.parse(self.tmpl)
|
||||
self.stack = parser.Stack(self.ctx, 'test_stack',
|
||||
template.Template(parsed_tmpl))
|
||||
resources = self.stack.resources.values()
|
||||
outputs = self.stack.outputs
|
||||
|
||||
for res in resources:
|
||||
self.assertEqual(self.expected[res.name],
|
||||
self.stack.get_dep_attrs(resources, outputs,
|
||||
res.name))
|
Loading…
Reference in New Issue
Block a user