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,
|
raise exception.InvalidTemplateReference(resource=resource_name,
|
||||||
key=path)
|
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):
|
def dependencies(self, path):
|
||||||
return itertools.chain(super(GetAtt, self).dependencies(path),
|
return itertools.chain(super(GetAtt, self).dependencies(path),
|
||||||
[self._resource(path)])
|
[self._resource(path)])
|
||||||
|
@ -59,6 +59,9 @@ class Function(object):
|
|||||||
def dependencies(self, path):
|
def dependencies(self, path):
|
||||||
return dependencies(self.args, '.'.join([path, self.fn_name]))
|
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):
|
def __reduce__(self):
|
||||||
"""
|
"""
|
||||||
Return a representation of the function suitable for pickling.
|
Return a representation of the function suitable for pickling.
|
||||||
@ -167,3 +170,25 @@ def dependencies(snippet, path=''):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
return []
|
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)
|
text = '%s "%s"' % (self.__class__.__name__, self.name)
|
||||||
return encodeutils.safe_decode(text)
|
return encodeutils.safe_decode(text)
|
||||||
|
|
||||||
|
def dep_attrs(self, resource_name):
|
||||||
|
return self.t.dep_attrs(resource_name)
|
||||||
|
|
||||||
def add_dependencies(self, deps):
|
def add_dependencies(self, deps):
|
||||||
for dep in self.t.dependencies(self.stack):
|
for dep in self.t.dependencies(self.stack):
|
||||||
deps += (self, dep)
|
deps += (self, dep)
|
||||||
|
@ -141,6 +141,16 @@ class ResourceDefinitionCore(object):
|
|||||||
deletion_policy=reparse_snippet(self._deletion_policy),
|
deletion_policy=reparse_snippet(self._deletion_policy),
|
||||||
update_policy=reparse_snippet(self._update_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):
|
def dependencies(self, stack):
|
||||||
"""
|
"""
|
||||||
Return the Resource objects in the given stack on which this depends.
|
Return the Resource objects in the given stack on which this depends.
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
import collections
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
|
import itertools
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@ -279,6 +280,19 @@ class Stack(collections.Mapping):
|
|||||||
if not self.parameters.set_stack_id(self.identifier()):
|
if not self.parameters.set_stack_id(self.identifier()):
|
||||||
LOG.warn(_LW("Unable to set parameters StackId 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
|
@staticmethod
|
||||||
def _get_dependencies(resources):
|
def _get_dependencies(resources):
|
||||||
'''Return the dependency graph for a list of 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