Merge "Get dep_attrs from StackDefinition"
This commit is contained in:
commit
afd302f17d
@ -14,6 +14,7 @@
|
|||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
import itertools
|
||||||
import pydoc
|
import pydoc
|
||||||
import tenacity
|
import tenacity
|
||||||
import weakref
|
import weakref
|
||||||
@ -659,9 +660,6 @@ class Resource(status.ResourceStatus):
|
|||||||
text = '%s "%s"' % (class_name, self.name)
|
text = '%s "%s"' % (class_name, self.name)
|
||||||
return six.text_type(text)
|
return six.text_type(text)
|
||||||
|
|
||||||
def dep_attrs(self, resource_name):
|
|
||||||
return self.t.dep_attrs(resource_name)
|
|
||||||
|
|
||||||
def add_explicit_dependencies(self, deps):
|
def add_explicit_dependencies(self, deps):
|
||||||
"""Add all dependencies explicitly specified in the template.
|
"""Add all dependencies explicitly specified in the template.
|
||||||
|
|
||||||
@ -949,7 +947,8 @@ class Resource(status.ResourceStatus):
|
|||||||
self._rsrc_prop_data = None
|
self._rsrc_prop_data = None
|
||||||
self.attributes.reset_resolved_values()
|
self.attributes.reset_resolved_values()
|
||||||
|
|
||||||
def referenced_attrs(self, in_resources=True, in_outputs=True):
|
def referenced_attrs(self, stk_defn=None,
|
||||||
|
in_resources=True, in_outputs=True):
|
||||||
"""Return the set of all attributes referenced in the template.
|
"""Return the set of all attributes referenced in the template.
|
||||||
|
|
||||||
This enables the resource to calculate which of its attributes will
|
This enables the resource to calculate which of its attributes will
|
||||||
@ -958,22 +957,30 @@ class Resource(status.ResourceStatus):
|
|||||||
`in_resources` or `in_outputs` parameters to False. To limit to a
|
`in_resources` or `in_outputs` parameters to False. To limit to a
|
||||||
subset of outputs, pass an iterable of the output names to examine
|
subset of outputs, pass an iterable of the output names to examine
|
||||||
for the `in_outputs` parameter.
|
for the `in_outputs` parameter.
|
||||||
|
|
||||||
|
The set of referenced attributes is calculated from the
|
||||||
|
StackDefinition object provided, or from the stack's current
|
||||||
|
definition if none is passed.
|
||||||
"""
|
"""
|
||||||
def get_dep_attrs(source_dict):
|
if stk_defn is None:
|
||||||
return set(self.stack.get_dep_attrs(six.itervalues(source_dict),
|
stk_defn = self.stack.defn
|
||||||
self.name))
|
|
||||||
|
def get_dep_attrs(source):
|
||||||
|
return set(itertools.chain.from_iterable(s.dep_attrs(self.name)
|
||||||
|
for s in source))
|
||||||
|
|
||||||
refd_attrs = set()
|
refd_attrs = set()
|
||||||
if in_resources:
|
if in_resources:
|
||||||
refd_attrs |= get_dep_attrs(self.stack.resources)
|
enabled_resources = stk_defn.enabled_rsrc_names()
|
||||||
|
refd_attrs |= get_dep_attrs(stk_defn.resource_definition(r_name)
|
||||||
|
for r_name in enabled_resources)
|
||||||
|
|
||||||
subset_outputs = isinstance(in_outputs, collections.Iterable)
|
subset_outputs = isinstance(in_outputs, collections.Iterable)
|
||||||
if subset_outputs or in_outputs:
|
if subset_outputs or in_outputs:
|
||||||
if subset_outputs:
|
if not subset_outputs:
|
||||||
outputs = {k: self.stack.outputs[k] for k in in_outputs}
|
in_outputs = stk_defn.enabled_output_names()
|
||||||
else:
|
refd_attrs |= get_dep_attrs(stk_defn.output_definition(op_name)
|
||||||
outputs = self.stack.outputs
|
for op_name in in_outputs)
|
||||||
refd_attrs |= get_dep_attrs(outputs)
|
|
||||||
|
|
||||||
if attributes.ALL_ATTRIBUTES in refd_attrs:
|
if attributes.ALL_ATTRIBUTES in refd_attrs:
|
||||||
refd_attrs.remove(attributes.ALL_ATTRIBUTES)
|
refd_attrs.remove(attributes.ALL_ATTRIBUTES)
|
||||||
@ -981,7 +988,7 @@ class Resource(status.ResourceStatus):
|
|||||||
|
|
||||||
return refd_attrs
|
return refd_attrs
|
||||||
|
|
||||||
def node_data(self, for_resources=True, for_outputs=False):
|
def node_data(self, stk_defn=None, for_resources=True, for_outputs=False):
|
||||||
"""Return a NodeData object representing the resource.
|
"""Return a NodeData object representing the resource.
|
||||||
|
|
||||||
The NodeData object returned contains basic data about the resource,
|
The NodeData object returned contains basic data about the resource,
|
||||||
@ -996,6 +1003,10 @@ class Resource(status.ResourceStatus):
|
|||||||
only those attribute values referenced by the specified stack outputs
|
only those attribute values referenced by the specified stack outputs
|
||||||
are included.
|
are included.
|
||||||
|
|
||||||
|
The set of referenced attributes is calculated from the
|
||||||
|
StackDefinition object provided, or from the stack's current
|
||||||
|
definition if none is passed.
|
||||||
|
|
||||||
After calling this method, the resource's attribute cache is
|
After calling this method, the resource's attribute cache is
|
||||||
populated with any cacheable attribute values referenced by stack
|
populated with any cacheable attribute values referenced by stack
|
||||||
outputs, even if they are not also referenced by other resources.
|
outputs, even if they are not also referenced by other resources.
|
||||||
@ -1019,12 +1030,13 @@ class Resource(status.ResourceStatus):
|
|||||||
except exception.InvalidTemplateAttribute as ita:
|
except exception.InvalidTemplateAttribute as ita:
|
||||||
LOG.info('%s', ita)
|
LOG.info('%s', ita)
|
||||||
|
|
||||||
dep_attrs = self.referenced_attrs(in_resources=for_resources,
|
dep_attrs = self.referenced_attrs(stk_defn,
|
||||||
|
in_resources=for_resources,
|
||||||
in_outputs=for_outputs)
|
in_outputs=for_outputs)
|
||||||
|
|
||||||
# Ensure all attributes referenced in outputs get cached
|
# Ensure all attributes referenced in outputs get cached
|
||||||
if for_outputs is False and self.stack.convergence:
|
if for_outputs is False and self.stack.convergence:
|
||||||
out_attrs = self.referenced_attrs(in_resources=False)
|
out_attrs = self.referenced_attrs(stk_defn, in_resources=False)
|
||||||
for e in get_attrs(out_attrs - dep_attrs, cacheable_only=True):
|
for e in get_attrs(out_attrs - dep_attrs, cacheable_only=True):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ import collections
|
|||||||
import copy
|
import copy
|
||||||
import eventlet
|
import eventlet
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@ -213,7 +212,8 @@ class Stack(collections.Mapping):
|
|||||||
owner_id)
|
owner_id)
|
||||||
if tmpl is not None:
|
if tmpl is not None:
|
||||||
self.defn = stk_defn.StackDefinition(context, tmpl,
|
self.defn = stk_defn.StackDefinition(context, tmpl,
|
||||||
self.identifier(), {},
|
self.identifier(),
|
||||||
|
cache_data or {},
|
||||||
parent_info)
|
parent_info)
|
||||||
else:
|
else:
|
||||||
self.defn = None
|
self.defn = None
|
||||||
@ -486,16 +486,6 @@ class Stack(collections.Mapping):
|
|||||||
if not self.parameters.set_stack_id(self.identifier()):
|
if not self.parameters.set_stack_id(self.identifier()):
|
||||||
LOG.warning("Unable to set parameters StackId identifier")
|
LOG.warning("Unable to set parameters StackId identifier")
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_dep_attrs(resources, resource_name):
|
|
||||||
"""Return the attributes of the specified resource that are referenced.
|
|
||||||
|
|
||||||
Return an iterator over any attributes of the specified resource that
|
|
||||||
are referenced in resources.
|
|
||||||
"""
|
|
||||||
return set(itertools.chain.from_iterable(
|
|
||||||
res.dep_attrs(resource_name) for res in resources))
|
|
||||||
|
|
||||||
def _explicit_dependencies(self):
|
def _explicit_dependencies(self):
|
||||||
"""Return dependencies without making any resource plugin calls.
|
"""Return dependencies without making any resource plugin calls.
|
||||||
|
|
||||||
|
@ -135,6 +135,8 @@ class StackUpdate(object):
|
|||||||
|
|
||||||
yield new_res.create()
|
yield new_res.create()
|
||||||
|
|
||||||
|
self._update_resource_data(new_res)
|
||||||
|
|
||||||
def _check_replace_restricted(self, res):
|
def _check_replace_restricted(self, res):
|
||||||
registry = res.stack.env.registry
|
registry = res.stack.env.registry
|
||||||
restricted_actions = registry.get_rsrc_restricted_actions(res.name)
|
restricted_actions = registry.get_rsrc_restricted_actions(res.name)
|
||||||
@ -147,6 +149,19 @@ class StackUpdate(object):
|
|||||||
six.text_type(ex))
|
six.text_type(ex))
|
||||||
raise failure
|
raise failure
|
||||||
|
|
||||||
|
def _update_resource_data(self, resource):
|
||||||
|
# Use the *new* template to determine the attrs to cache
|
||||||
|
node_data = resource.node_data(self.new_stack.defn)
|
||||||
|
stk_defn.update_resource_data(self.existing_stack.defn,
|
||||||
|
resource.name, node_data)
|
||||||
|
|
||||||
|
# Also update the new stack's definition with the data, so that
|
||||||
|
# following resources can calculate dep_attr values correctly (e.g. if
|
||||||
|
# the actual attribute name in a get_attr function also comes from a
|
||||||
|
# get_attr function.)
|
||||||
|
stk_defn.update_resource_data(self.new_stack.defn,
|
||||||
|
resource.name, node_data)
|
||||||
|
|
||||||
@scheduler.wrappertask
|
@scheduler.wrappertask
|
||||||
def _process_new_resource_update(self, new_res):
|
def _process_new_resource_update(self, new_res):
|
||||||
res_name = new_res.name
|
res_name = new_res.name
|
||||||
@ -175,19 +190,13 @@ class StackUpdate(object):
|
|||||||
{'res_name': res_name,
|
{'res_name': res_name,
|
||||||
'stack_name': self.existing_stack.name})
|
'stack_name': self.existing_stack.name})
|
||||||
|
|
||||||
stk_defn.update_resource_data(self.existing_stack.defn,
|
self._update_resource_data(existing_res)
|
||||||
res_name,
|
|
||||||
existing_res.node_data())
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self._check_replace_restricted(new_res)
|
self._check_replace_restricted(new_res)
|
||||||
|
|
||||||
yield self._create_resource(new_res)
|
yield self._create_resource(new_res)
|
||||||
|
|
||||||
node_data = self.existing_stack[res_name].node_data()
|
|
||||||
stk_defn.update_resource_data(self.existing_stack.defn, res_name,
|
|
||||||
node_data)
|
|
||||||
|
|
||||||
def _update_in_place(self, existing_res, new_res, is_substituted=False):
|
def _update_in_place(self, existing_res, new_res, is_substituted=False):
|
||||||
existing_snippet = self.existing_snippets[existing_res.name]
|
existing_snippet = self.existing_snippets[existing_res.name]
|
||||||
prev_res = self.previous_stack.get(new_res.name)
|
prev_res = self.previous_stack.get(new_res.name)
|
||||||
|
@ -2361,7 +2361,10 @@ class HotStackTest(common.HeatTestCase):
|
|||||||
repeat = self.stack.t.parse(self.stack, snippet)
|
repeat = self.stack.t.parse(self.stack, snippet)
|
||||||
|
|
||||||
self.stack.store()
|
self.stack.store()
|
||||||
self.stack.create()
|
with mock.patch.object(rsrc_defn.ResourceDefinition,
|
||||||
|
'dep_attrs') as mock_da:
|
||||||
|
mock_da.return_value = ['list']
|
||||||
|
self.stack.create()
|
||||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||||
self.stack.state)
|
self.stack.state)
|
||||||
self.assertEqual(['this is foo', 'this is bar'],
|
self.assertEqual(['this is foo', 'this is bar'],
|
||||||
@ -2374,7 +2377,10 @@ class HotStackTest(common.HeatTestCase):
|
|||||||
self.stack = parser.Stack(self.ctx, 'test_get_attr',
|
self.stack = parser.Stack(self.ctx, 'test_get_attr',
|
||||||
template.Template(hot_tpl))
|
template.Template(hot_tpl))
|
||||||
self.stack.store()
|
self.stack.store()
|
||||||
self.stack.create()
|
with mock.patch.object(rsrc_defn.ResourceDefinition,
|
||||||
|
'dep_attrs') as mock_da:
|
||||||
|
mock_da.return_value = ['foo']
|
||||||
|
self.stack.create()
|
||||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||||
self.stack.state)
|
self.stack.state)
|
||||||
|
|
||||||
@ -2717,6 +2723,9 @@ class StackAttributesTest(common.HeatTestCase):
|
|||||||
self.stack = parser.Stack(self.ctx, 'test_get_attr',
|
self.stack = parser.Stack(self.ctx, 'test_get_attr',
|
||||||
template.Template(self.hot_tpl))
|
template.Template(self.hot_tpl))
|
||||||
self.stack.store()
|
self.stack.store()
|
||||||
|
|
||||||
|
parsed = self.stack.t.parse(self.stack, self.snippet)
|
||||||
|
|
||||||
self.stack.create()
|
self.stack.create()
|
||||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
|
||||||
self.stack.state)
|
self.stack.state)
|
||||||
@ -2739,9 +2748,7 @@ class StackAttributesTest(common.HeatTestCase):
|
|||||||
(rsrc.ADOPT, rsrc.COMPLETE)):
|
(rsrc.ADOPT, rsrc.COMPLETE)):
|
||||||
rsrc.state_set(action, status)
|
rsrc.state_set(action, status)
|
||||||
|
|
||||||
resolved = function.resolve(self.stack.t.parse(self.stack,
|
self.assertEqual(self.expected, function.resolve(parsed))
|
||||||
self.snippet))
|
|
||||||
self.assertEqual(self.expected, resolved)
|
|
||||||
|
|
||||||
|
|
||||||
class StackGetAttributesTestConvergence(common.HeatTestCase):
|
class StackGetAttributesTestConvergence(common.HeatTestCase):
|
||||||
@ -2865,8 +2872,9 @@ class StackGetAttributesTestConvergence(common.HeatTestCase):
|
|||||||
dep_attrs = function.dep_attrs(
|
dep_attrs = function.dep_attrs(
|
||||||
self.stack.t.parse(self.stack, self.snippet),
|
self.stack.t.parse(self.stack, self.snippet),
|
||||||
self.resource_name)
|
self.resource_name)
|
||||||
with mock.patch.object(rsrc.stack, 'get_dep_attrs') as mock_da:
|
with mock.patch.object(rsrc_defn.ResourceDefinition,
|
||||||
mock_da.return_value = list(dep_attrs)
|
'dep_attrs') as mock_da:
|
||||||
|
mock_da.return_value = dep_attrs
|
||||||
rsrc_data = rsrc.node_data()
|
rsrc_data = rsrc.node_data()
|
||||||
# store as cache data
|
# store as cache data
|
||||||
self.stack.cache_data = {
|
self.stack.cache_data = {
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import itertools
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from heat.common import template_format
|
from heat.common import template_format
|
||||||
@ -231,9 +232,8 @@ class DepAttrsTest(common.HeatTestCase):
|
|||||||
for res in six.itervalues(self.stack):
|
for res in six.itervalues(self.stack):
|
||||||
resources = six.itervalues(self.stack.resources)
|
resources = six.itervalues(self.stack.resources)
|
||||||
self.assertEqual(self.expected[res.name],
|
self.assertEqual(self.expected[res.name],
|
||||||
self.stack.get_dep_attrs(
|
set(itertools.chain.from_iterable(
|
||||||
resources,
|
r.t.dep_attrs(res.name) for r in resources)))
|
||||||
res.name))
|
|
||||||
|
|
||||||
|
|
||||||
class ReferencedAttrsTest(common.HeatTestCase):
|
class ReferencedAttrsTest(common.HeatTestCase):
|
||||||
|
@ -61,3 +61,41 @@ outputs:
|
|||||||
stack_identifier, 'resource_output_b')['output']
|
stack_identifier, 'resource_output_b')['output']
|
||||||
self.assertEqual(expected_output_a, actual_output_a)
|
self.assertEqual(expected_output_a, actual_output_a)
|
||||||
self.assertEqual(expected_output_b, actual_output_b)
|
self.assertEqual(expected_output_b, actual_output_b)
|
||||||
|
|
||||||
|
before_template = '''
|
||||||
|
heat_template_version: 2015-10-15
|
||||||
|
resources:
|
||||||
|
test_resource_a:
|
||||||
|
type: OS::Heat::TestResource
|
||||||
|
properties:
|
||||||
|
value: 'foo'
|
||||||
|
outputs:
|
||||||
|
'''
|
||||||
|
|
||||||
|
after_template = '''
|
||||||
|
heat_template_version: 2015-10-15
|
||||||
|
resources:
|
||||||
|
test_resource_a:
|
||||||
|
type: OS::Heat::TestResource
|
||||||
|
properties:
|
||||||
|
value: 'foo'
|
||||||
|
test_resource_b:
|
||||||
|
type: OS::Heat::TestResource
|
||||||
|
properties:
|
||||||
|
value: {get_attr: [test_resource_a, output]}
|
||||||
|
outputs:
|
||||||
|
output_value:
|
||||||
|
description: 'Output of resource b'
|
||||||
|
value: {get_attr: [test_resource_b, output]}
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_outputs_update_new_resource(self):
|
||||||
|
stack_identifier = self.stack_create(template=self.before_template)
|
||||||
|
self.update_stack(stack_identifier, template=self.after_template)
|
||||||
|
|
||||||
|
expected_output_value = {
|
||||||
|
u'output_value': u'foo', u'output_key': u'output_value',
|
||||||
|
u'description': u'Output of resource b'}
|
||||||
|
actual_output_value = self.client.stacks.output_show(
|
||||||
|
stack_identifier, 'output_value')['output']
|
||||||
|
self.assertEqual(expected_output_value, actual_output_value)
|
||||||
|
Loading…
Reference in New Issue
Block a user