Fix GetAttThenSelect for convergence

The GetAttThenSelect dep_attrs implementaion is changed to return the
attribute only instead of (attribute + path component). It is wrong to
return the (attribute + path component) and that was breaking the
GetAttThenSelect for convergence.

Change-Id: I117dc3e587386f4d48e70ef89c61bb857c751717
Closes-Bug: #1582649
This commit is contained in:
Anant Patil 2016-05-23 06:02:06 +00:00
parent b7cbfef696
commit e585676170
4 changed files with 193 additions and 22 deletions

View File

@ -264,13 +264,9 @@ def load_resource(cnxt, resource_id, resource_data, is_update):
return None, None, None
def construct_input_data(rsrc, curr_stack):
attributes = curr_stack.get_dep_attrs(
six.itervalues(curr_stack.resources),
curr_stack.outputs,
rsrc.name)
def _resolve_attributes(dep_attrs, rsrc):
resolved_attributes = {}
for attr in attributes:
for attr in dep_attrs:
try:
if isinstance(attr, six.string_types):
resolved_attributes[attr] = rsrc.get_attribute(attr)
@ -278,11 +274,18 @@ def construct_input_data(rsrc, curr_stack):
resolved_attributes[attr] = rsrc.get_attribute(*attr)
except exception.InvalidTemplateAttribute as ita:
LOG.info(six.text_type(ita))
return resolved_attributes
def construct_input_data(rsrc, curr_stack):
dep_attrs = curr_stack.get_dep_attrs(
six.itervalues(curr_stack.resources),
curr_stack.outputs,
rsrc.name)
input_data = {'id': rsrc.id,
'name': rsrc.name,
'reference_id': rsrc.get_reference_id(),
'attrs': resolved_attributes,
'attrs': _resolve_attributes(dep_attrs, rsrc),
'status': rsrc.status,
'action': rsrc.action,
'uuid': rsrc.uuid}

View File

@ -133,19 +133,6 @@ class GetAttThenSelect(cfn_funcs.GetAtt):
path_components = function.resolve(self._path_components)
return attributes.select_from_attribute(attribute, path_components)
def dep_attrs(self, resource_name):
if self._resource().name == resource_name:
path = function.resolve(self._path_components)
attr = [function.resolve(self._attribute)]
if path:
attrs = [tuple(attr + path)]
else:
attrs = attr
else:
attrs = []
return itertools.chain(function.dep_attrs(self.args, resource_name),
attrs)
class GetAtt(GetAttThenSelect):
"""A function for resolving resource attributes.
@ -171,6 +158,19 @@ class GetAtt(GetAttThenSelect):
else:
return None
def dep_attrs(self, resource_name):
if self._resource().name == resource_name:
path = function.resolve(self._path_components)
attr = [function.resolve(self._attribute)]
if path:
attrs = [tuple(attr + path)]
else:
attrs = attr
else:
attrs = []
return itertools.chain(function.dep_attrs(self.args, resource_name),
attrs)
class GetAttAllAttributes(GetAtt):
"""A function for resolving resource attributes.

View File

@ -124,7 +124,7 @@ resources:
'''
attr_cache_template = '''
heat_template_version: 2013-05-23
heat_template_version: 2016-04-08
resources:
A:
type: ResourceWithComplexAttributesType

View File

@ -12,19 +12,21 @@
# under the License.
import copy
import mock
import six
from heat.common import exception
from heat.common import identifier
from heat.common import template_format
from heat.engine.cfn import functions as cfn_functions
from heat.engine import check_resource as cr
from heat.engine import environment
from heat.engine import function
from heat.engine.hot import functions as hot_functions
from heat.engine.hot import parameters as hot_param
from heat.engine.hot import template as hot_template
from heat.engine import parameters
from heat.engine import resource
from heat.engine import resources
from heat.engine import rsrc_defn
from heat.engine import stack as parser
@ -1604,6 +1606,172 @@ class StackAttributesTest(common.HeatTestCase):
self.assertEqual(self.expected, resolved)
class StackGetAttributesTestConvergence(common.HeatTestCase):
def setUp(self):
super(StackGetAttributesTestConvergence, self).setUp()
self.ctx = utils.dummy_context()
self.m.ReplayAll()
scenarios = [
# for hot template 2013-05-23, get_attr: hot_funcs.GetAttThenSelect
('get_flat_attr',
dict(hot_tpl=hot_tpl_generic_resource,
snippet={'Value': {'get_attr': ['resource1', 'foo']}},
resource_name='resource1',
expected={'Value': 'resource1'})),
('get_list_attr',
dict(hot_tpl=hot_tpl_complex_attrs,
snippet={'Value': {'get_attr': ['resource1', 'list', 0]}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.list[0]})),
('get_flat_dict_attr',
dict(hot_tpl=hot_tpl_complex_attrs,
snippet={'Value': {'get_attr': ['resource1',
'flat_dict',
'key2']}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.
flat_dict['key2']})),
('get_nested_attr_list',
dict(hot_tpl=hot_tpl_complex_attrs,
snippet={'Value': {'get_attr': ['resource1',
'nested_dict',
'list',
0]}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.
nested_dict['list'][0]})),
('get_nested_attr_dict',
dict(hot_tpl=hot_tpl_complex_attrs,
snippet={'Value': {'get_attr': ['resource1',
'nested_dict',
'dict',
'a']}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.
nested_dict['dict']['a']})),
('get_attr_none',
dict(hot_tpl=hot_tpl_complex_attrs,
snippet={'Value': {'get_attr': ['resource1',
'none',
'who_cares']}},
resource_name='resource1',
expected={'Value': None})),
# for hot template version 2014-10-16 and 2015-04-30,
# get_attr: hot_funcs.GetAtt
('get_flat_attr',
dict(hot_tpl=hot_tpl_generic_resource_20141016,
snippet={'Value': {'get_attr': ['resource1', 'foo']}},
resource_name='resource1',
expected={'Value': 'resource1'})),
('get_list_attr',
dict(hot_tpl=hot_tpl_complex_attrs_20141016,
snippet={'Value': {'get_attr': ['resource1', 'list', 0]}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.list[0]})),
('get_flat_dict_attr',
dict(hot_tpl=hot_tpl_complex_attrs_20141016,
snippet={'Value': {'get_attr': ['resource1',
'flat_dict',
'key2']}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.
flat_dict['key2']})),
('get_nested_attr_list',
dict(hot_tpl=hot_tpl_complex_attrs_20141016,
snippet={'Value': {'get_attr': ['resource1',
'nested_dict',
'list',
0]}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.
nested_dict['list'][0]})),
('get_nested_attr_dict',
dict(hot_tpl=hot_tpl_complex_attrs_20141016,
snippet={'Value': {'get_attr': ['resource1',
'nested_dict',
'dict',
'a']}},
resource_name='resource1',
expected={
'Value':
generic_rsrc.ResourceWithComplexAttributes.
nested_dict['dict']['a']})),
('get_attr_none',
dict(hot_tpl=hot_tpl_complex_attrs_20141016,
snippet={'Value': {'get_attr': ['resource1',
'none',
'who_cares']}},
resource_name='resource1',
expected={'Value': None}))
]
def _prepare_cache_data(self, rsrc):
attributes = function.dep_attrs(
self.stack.t.parse(self.stack, self.snippet),
self.resource_name)
# store as cache data
self.stack.cache_data = {
rsrc.name: {
'attrs': cr._resolve_attributes(attributes, rsrc)
}
}
def test_get_attr_convergence(self):
"""Test resolution of get_attr occurrences with convergence."""
self.stack = parser.Stack(self.ctx, 'test_get_attr',
template.Template(self.hot_tpl))
self.stack.store()
self.stack.create()
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
self.stack.state)
rsrc = self.stack[self.resource_name]
self._prepare_cache_data(rsrc)
with mock.patch.object(resource.Resource, 'get_attribute') as mock_ga:
for action, status in (
(rsrc.CREATE, rsrc.IN_PROGRESS),
(rsrc.CREATE, rsrc.COMPLETE),
(rsrc.RESUME, rsrc.IN_PROGRESS),
(rsrc.RESUME, rsrc.COMPLETE),
(rsrc.SUSPEND, rsrc.IN_PROGRESS),
(rsrc.SUSPEND, rsrc.COMPLETE),
(rsrc.UPDATE, rsrc.IN_PROGRESS),
(rsrc.UPDATE, rsrc.COMPLETE),
(rsrc.SNAPSHOT, rsrc.IN_PROGRESS),
(rsrc.SNAPSHOT, rsrc.COMPLETE),
(rsrc.CHECK, rsrc.IN_PROGRESS),
(rsrc.CHECK, rsrc.COMPLETE),
(rsrc.ADOPT, rsrc.IN_PROGRESS),
(rsrc.ADOPT, rsrc.COMPLETE)):
rsrc.state_set(action, status)
resolved = function.resolve(self.stack.t.parse(self.stack,
self.snippet))
self.assertEqual(self.expected, resolved)
# get_attribute should never be called, everything
# should be resolved from cache data
self.assertFalse(mock_ga.called)
class StackGetAttrValidationTest(common.HeatTestCase):
def setUp(self):