Add way to collect map of needed attributes

Change-Id: I36cd2d418d570da3a0aa9df5408442251a400978
Implements: blueprint convergence-push-data
This commit is contained in:
Sergey Kraynev 2015-03-04 11:39:31 -05:00
parent 9dc904fddc
commit 401fd430d1
6 changed files with 242 additions and 0 deletions

View File

@ -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)])

View File

@ -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 []

View File

@ -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)

View File

@ -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.

View File

@ -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.'''

View 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))