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 return None, None, None
def construct_input_data(rsrc, curr_stack): def _resolve_attributes(dep_attrs, rsrc):
attributes = curr_stack.get_dep_attrs(
six.itervalues(curr_stack.resources),
curr_stack.outputs,
rsrc.name)
resolved_attributes = {} resolved_attributes = {}
for attr in attributes: for attr in dep_attrs:
try: try:
if isinstance(attr, six.string_types): if isinstance(attr, six.string_types):
resolved_attributes[attr] = rsrc.get_attribute(attr) 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) resolved_attributes[attr] = rsrc.get_attribute(*attr)
except exception.InvalidTemplateAttribute as ita: except exception.InvalidTemplateAttribute as ita:
LOG.info(six.text_type(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, input_data = {'id': rsrc.id,
'name': rsrc.name, 'name': rsrc.name,
'reference_id': rsrc.get_reference_id(), 'reference_id': rsrc.get_reference_id(),
'attrs': resolved_attributes, 'attrs': _resolve_attributes(dep_attrs, rsrc),
'status': rsrc.status, 'status': rsrc.status,
'action': rsrc.action, 'action': rsrc.action,
'uuid': rsrc.uuid} 'uuid': rsrc.uuid}

View File

@ -133,19 +133,6 @@ class GetAttThenSelect(cfn_funcs.GetAtt):
path_components = function.resolve(self._path_components) path_components = function.resolve(self._path_components)
return attributes.select_from_attribute(attribute, 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): class GetAtt(GetAttThenSelect):
"""A function for resolving resource attributes. """A function for resolving resource attributes.
@ -171,6 +158,19 @@ class GetAtt(GetAttThenSelect):
else: else:
return None 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): class GetAttAllAttributes(GetAtt):
"""A function for resolving resource attributes. """A function for resolving resource attributes.

View File

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

View File

@ -12,19 +12,21 @@
# under the License. # under the License.
import copy import copy
import mock
import six import six
from heat.common import exception from heat.common import exception
from heat.common import identifier from heat.common import identifier
from heat.common import template_format from heat.common import template_format
from heat.engine.cfn import functions as cfn_functions 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 environment
from heat.engine import function from heat.engine import function
from heat.engine.hot import functions as hot_functions from heat.engine.hot import functions as hot_functions
from heat.engine.hot import parameters as hot_param from heat.engine.hot import parameters as hot_param
from heat.engine.hot import template as hot_template from heat.engine.hot import template as hot_template
from heat.engine import parameters from heat.engine import parameters
from heat.engine import resource
from heat.engine import resources from heat.engine import resources
from heat.engine import rsrc_defn from heat.engine import rsrc_defn
from heat.engine import stack as parser from heat.engine import stack as parser
@ -1604,6 +1606,172 @@ class StackAttributesTest(common.HeatTestCase):
self.assertEqual(self.expected, resolved) 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): class StackGetAttrValidationTest(common.HeatTestCase):
def setUp(self): def setUp(self):