Handle errors calculating dep_attrs for nested get_attr

Ensure that in convergence we can calculate the dep_attrs of a resource,
including the attributes referenced in outputs, even if:

- One of the resource's attributes is referenced in an ouput; and
- The attribute path itself also contains a get_attr function; and
- The attribute referenced by the inner get_attr function in the output is
  not referenced by any resources.

Since the attributes referenced only in outputs (and not in resources) are
not passed to dependent resources in the NodeData, the data required to
resolve the inner get_attr function is not available. Therefore getting the
dep_attrs for the relevant resource from the output definition would fail.
The next patch does so during the calculation of the NodeData, and any
resulting exception is not caught (bug 1703043), so the stack would end up
stuck IN_PROGRESS.

To avoid this, ignore any errors resolving attribute path components in the
course of calculating the dep_attrs of a given resource. This may mean that
an attribute value required for the outputs is not stored in the database,
but it can still be resolved live later.

Change-Id: I2ed1e3dacd371e36656559ba92dfada05df0ceba
Related-Bug: #1660831
This commit is contained in:
Zane Bitter 2017-07-10 13:48:01 -04:00
parent 91018c890d
commit b902d93ade

View File

@ -16,6 +16,7 @@ import hashlib
import itertools
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
import six
from six.moves.urllib import parse as urlparse
@ -27,6 +28,9 @@ from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import function
LOG = logging.getLogger(__name__)
opts = [
cfg.IntOpt('limit_iterators',
default=200,
@ -182,9 +186,17 @@ class GetAttThenSelect(function.Function):
raise exception.InvalidTemplateReference(resource=resource_name,
key=path)
def _attr_path(self):
return function.resolve(self._attribute)
def dep_attrs(self, resource_name):
if self._res_name() == resource_name:
attrs = [function.resolve(self._attribute)]
try:
attrs = [self._attr_path()]
except Exception as exc:
LOG.debug("Ignoring exception calculating required attributes"
": %s %s", type(exc).__name__, six.text_type(exc))
attrs = []
else:
attrs = []
return itertools.chain(super(GetAttThenSelect,
@ -266,18 +278,13 @@ class GetAtt(GetAttThenSelect):
else:
return None
def dep_attrs(self, resource_name):
if self._res_name() == resource_name:
path = function.resolve(self._path_components)
attr = [function.resolve(self._attribute)]
if path:
attrs = [tuple(attr + path)]
else:
attrs = attr
def _attr_path(self):
path = function.resolve(self._path_components)
attr = function.resolve(self._attribute)
if path:
return tuple([attr] + path)
else:
attrs = []
return itertools.chain(function.dep_attrs(self.args, resource_name),
attrs)
return attr
class GetAttAllAttributes(GetAtt):
@ -311,16 +318,10 @@ class GetAttAllAttributes(GetAtt):
raise TypeError(_('Argument to "%s" must be a list') %
self.fn_name)
def dep_attrs(self, resource_name):
"""Check if there is no attribute_name defined, return empty chain."""
if self._attribute is not None:
return super(GetAttAllAttributes, self).dep_attrs(resource_name)
elif self._res_name() == resource_name:
attrs = [attributes.ALL_ATTRIBUTES]
else:
attrs = []
return itertools.chain(function.dep_attrs(self.args,
resource_name), attrs)
def _attr_path(self):
if self._attribute is None:
return attributes.ALL_ATTRIBUTES
return super(GetAttAllAttributes, self)._attr_path()
def result(self):
if self._attribute is None: