Add a node_data() method to Resource

This will allow the snapshotting of attribute/refid values to occur on both
the legacy and convergence paths (currently it is used only for
convergence).

Change-Id: I9a8fce9c6d22d84ec967087b62bff77f5a6de3db
Partially-Implements: blueprint stack-definition
changes/65/435165/4
Zane Bitter 6 years ago
parent bc4fde4dce
commit 39269d2aa0
  1. 27
      heat/engine/check_resource.py
  2. 18
      heat/engine/resource.py
  3. 88
      heat/tests/engine/test_check_resource.py
  4. 11
      heat/tests/test_hot.py

@ -23,7 +23,6 @@ from oslo_log import log as logging
from heat.common import exception
from heat.common.i18n import _LE
from heat.common.i18n import _LI
from heat.engine import node_data
from heat.engine import resource
from heat.engine import scheduler
from heat.engine import stack as parser
@ -209,7 +208,7 @@ class CheckResource(object):
def _get_input_data(req, fwd, input_forward_data=None):
if fwd:
if input_forward_data is None:
return construct_input_data(rsrc, stack)
return rsrc.node_data().as_dict()
else:
# do not re-resolve attrs
return input_forward_data
@ -297,30 +296,6 @@ def load_resource(cnxt, resource_id, resource_data, is_update):
return None, None, None
def _resolve_attributes(dep_attrs, rsrc):
resolved_attributes = {}
for attr in dep_attrs:
try:
if isinstance(attr, six.string_types):
resolved_attributes[attr] = rsrc.get_attribute(attr)
else:
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),
rsrc.name)
input_data = node_data.NodeData(rsrc.id, rsrc.name, rsrc.uuid,
rsrc.get_reference_id(),
_resolve_attributes(dep_attrs, rsrc),
rsrc.action, rsrc.status)
return input_data.as_dict()
def check_stack_complete(cnxt, stack, current_traversal, sender_id, deps,
is_update):
"""Mark the stack complete if the update is complete.

@ -37,6 +37,7 @@ from heat.engine import environment
from heat.engine import event
from heat.engine import function
from heat.engine.hot import template as hot_tmpl
from heat.engine import node_data
from heat.engine import properties
from heat.engine import resources
from heat.engine import rsrc_defn
@ -871,6 +872,23 @@ class Resource(object):
if self._stored_properties_data != old_props:
self._rsrc_prop_data = None
def node_data(self):
def get_attrs(attrs):
for attr in attrs:
path = (attr,) if isinstance(attr, six.string_types) else attr
try:
yield attr, self.get_attribute(*path)
except exception.InvalidTemplateAttribute as ita:
LOG.info('%s', ita)
dep_attrs = self.stack.get_dep_attrs(
six.itervalues(self.stack.resources),
self.name)
return node_data.NodeData(self.id, self.name, self.uuid,
self.get_reference_id(),
dict(get_attrs(dep_attrs)),
self.action, self.status)
def preview(self):
"""Default implementation of Resource.preview.

@ -33,7 +33,6 @@ from heat.tests.engine import tools
from heat.tests import utils
@mock.patch.object(check_resource, 'construct_input_data')
@mock.patch.object(check_resource, 'check_stack_complete')
@mock.patch.object(check_resource, 'propagate_check_resource')
@mock.patch.object(check_resource, 'check_resource_cleanup')
@ -70,22 +69,22 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
stack.Stack.load = self.orig_load_method
def test_resource_not_available(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
self.worker.check_resource(
self.ctx, 'non-existant-id', self.stack.current_traversal, {},
True, None)
for mocked in [mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid]:
for mocked in [mock_cru, mock_crc, mock_pcr, mock_csc]:
self.assertFalse(mocked.called)
@mock.patch.object(worker.WorkerService, '_retrigger_replaced')
def test_stale_traversal(
self, mock_rnt, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_rnt, mock_cru, mock_crc, mock_pcr, mock_csc):
self.worker.check_resource(self.ctx, self.resource.id,
'stale-traversal', {}, True, None)
self.assertTrue(mock_rnt.called)
def test_is_update_traversal(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
self.worker.check_resource(
self.ctx, self.resource.id, self.stack.current_traversal, {},
self.is_update, None)
@ -113,7 +112,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(stack.Stack, 'time_remaining')
def test_is_update_traversal_raise_update_replace(
self, tr, mock_mr, mock_load, mock_cru, mock_crc, mock_pcr,
mock_csc, mock_cid):
mock_csc):
mock_load.return_value = self.resource, self.stack, self.stack
mock_cru.side_effect = resource.UpdateReplace
tr.return_value = 317
@ -134,7 +133,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(resource.Resource, 'state_set')
def test_is_update_traversal_raise_update_inprogress(
self, mock_ss, tr, mock_tsl, mock_cru, mock_crc, mock_pcr,
mock_csc, mock_cid):
mock_csc):
mock_cru.side_effect = exception.UpdateInProgress
self.worker.engine_id = 'some-thing-else'
mock_tsl.return_value = True
@ -154,7 +153,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.assertFalse(mock_csc.called)
def test_try_steal_lock_alive(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
res = self.cr._try_steal_engine_lock(self.ctx,
self.resource.id)
self.assertFalse(res)
@ -163,7 +162,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.resource_objects.Resource, 'get_obj')
def test_try_steal_lock_dead(
self, mock_get, mock_elc, mock_cru, mock_crc, mock_pcr,
mock_csc, mock_cid):
mock_csc):
fake_res = mock.Mock()
fake_res.engine_id = 'some-thing-else'
mock_get.return_value = fake_res
@ -176,7 +175,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.resource_objects.Resource, 'get_obj')
def test_try_steal_lock_not_dead(
self, mock_get, mock_elc, mock_cru, mock_crc, mock_pcr,
mock_csc, mock_cid):
mock_csc):
fake_res = mock.Mock()
fake_res.engine_id = self.worker.engine_id
mock_get.return_value = fake_res
@ -186,7 +185,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_resource_update_failure_sets_stack_state_as_failed(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.state_set(self.stack.UPDATE, self.stack.IN_PROGRESS, '')
self.resource.state_set(self.resource.UPDATE,
self.resource.IN_PROGRESS)
@ -205,7 +204,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_resource_cleanup_failure_sets_stack_state_as_failed(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.is_update = False # invokes check_resource_cleanup
self.stack.state_set(self.stack.UPDATE, self.stack.IN_PROGRESS, '')
self.resource.state_set(self.resource.UPDATE,
@ -225,7 +224,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_resource_update_failure_triggers_rollback_if_enabled(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.disable_rollback = False
self.stack.store()
dummy_ex = exception.ResourceNotAvailable(
@ -243,7 +242,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_resource_cleanup_failure_triggers_rollback_if_enabled(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.is_update = False # invokes check_resource_cleanup
self.stack.disable_rollback = False
self.stack.store()
@ -262,7 +261,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_rollback_is_not_triggered_on_rollback_disabled_stack(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.disable_rollback = True
self.stack.store()
dummy_ex = exception.ResourceNotAvailable(
@ -276,7 +275,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_rollback_not_re_triggered_for_a_rolling_back_stack(
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_tr, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.disable_rollback = False
self.stack.action = self.stack.ROLLBACK
self.stack.status = self.stack.IN_PROGRESS
@ -291,7 +290,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.assertFalse(mock_tr.called)
def test_resource_update_failure_purges_db_for_stack_failure(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
self.stack.disable_rollback = True
self.stack.store()
self.stack.purge_db = mock.Mock()
@ -305,7 +304,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.assertTrue(self.stack.purge_db.called)
def test_resource_cleanup_failure_purges_db_for_stack_failure(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
self.is_update = False
self.stack.disable_rollback = True
self.stack.store()
@ -324,7 +323,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(stack.Stack, 'load')
def test_initiate_propagate_rsrc_retriggers_check_rsrc_on_new_stack_update(
self, mock_stack_load, mock_rcr, mock_cru, mock_crc, mock_pcr,
mock_csc, mock_cid):
mock_csc):
key = sync_point.make_key(self.resource.id,
self.stack.current_traversal,
self.is_update)
@ -342,7 +341,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.resource.id, updated_stack)
def test_check_stack_complete_is_invoked_for_replaced_resource(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
resC = self.stack['C']
# lets say C is update-replaced
is_update = True
@ -363,7 +362,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(sync_point, 'sync')
def test_retrigger_check_resource(self, mock_sync, mock_cru, mock_crc,
mock_pcr, mock_csc, mock_cid):
mock_pcr, mock_csc):
resC = self.stack['C']
# A, B are predecessors to C when is_update is True
expected_predecessors = {(self.stack['A'].id, True),
@ -379,7 +378,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.assertItemsEqual(expected_predecessors, actual_predecessors)
def test_update_retrigger_check_resource_new_traversal_deletes_rsrc(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
# mock dependencies to indicate a rsrc with id 2 is not present
# in latest traversal
self.stack._convg_deps = dependencies.Dependencies([
@ -394,7 +393,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
False, None)
def test_delete_retrigger_check_resource_new_traversal_updates_rsrc(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
# mock dependencies to indicate a rsrc with id 2 has an update
# in latest traversal
self.stack._convg_deps = dependencies.Dependencies([
@ -410,14 +409,14 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(stack.Stack, 'purge_db')
def test_handle_failure(self, mock_purgedb, mock_cru, mock_crc, mock_pcr,
mock_csc, mock_cid):
mock_csc):
self.cr._handle_failure(self.ctx, self.stack, 'dummy-reason')
mock_purgedb.assert_called_once_with()
self.assertEqual('dummy-reason', self.stack.status_reason)
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_handle_failure_rollback(self, mock_tr, mock_cru, mock_crc,
mock_pcr, mock_csc, mock_cid):
mock_pcr, mock_csc):
self.stack.disable_rollback = False
self.stack.state_set(self.stack.UPDATE, self.stack.IN_PROGRESS, '')
self.cr._handle_failure(self.ctx, self.stack, 'dummy-reason')
@ -430,7 +429,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_handle_rsrc_failure_when_update_fails(
self, mock_tr, mock_rcr, mock_ss, mock_pdb, mock_cru, mock_crc,
mock_pcr, mock_csc, mock_cid):
mock_pcr, mock_csc):
# Emulate failure
mock_ss.return_value = False
self.cr._handle_resource_failure(self.ctx, self.is_update,
@ -448,7 +447,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_trigger_rollback')
def test_handle_rsrc_failure_when_update_fails_different_traversal(
self, mock_tr, mock_rcr, mock_ss, mock_pdb, mock_cru, mock_crc,
mock_pcr, mock_csc, mock_cid):
mock_pcr, mock_csc):
# Emulate failure
mock_ss.return_value = False
@ -470,14 +469,14 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource, '_handle_failure')
def test_handle_stack_timeout(self, mock_hf, mock_cru, mock_crc, mock_pcr,
mock_csc, mock_cid):
mock_csc):
self.cr._handle_stack_timeout(self.ctx, self.stack)
mock_hf.assert_called_once_with(self.ctx, self.stack, u'Timed out')
@mock.patch.object(check_resource.CheckResource,
'_handle_stack_timeout')
def test_do_check_resource_marks_stack_as_failed_if_stack_timesout(
self, mock_hst, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_hst, mock_cru, mock_crc, mock_pcr, mock_csc):
mock_cru.side_effect = scheduler.Timeout(None, 60)
self.is_update = True
self.cr._do_check_resource(self.ctx, self.stack.current_traversal,
@ -488,7 +487,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource,
'_handle_stack_timeout')
def test_do_check_resource_ignores_timeout_for_new_update(
self, mock_hst, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_hst, mock_cru, mock_crc, mock_pcr, mock_csc):
# Ensure current_traversal is check before marking the stack as
# failed due to time-out.
mock_cru.side_effect = scheduler.Timeout(None, 60)
@ -504,8 +503,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
@mock.patch.object(check_resource.CheckResource,
'_handle_stack_timeout')
def test_check_resource_handles_timeout(self, mock_hst, mock_to, mock_cru,
mock_crc, mock_pcr, mock_csc,
mock_cid):
mock_crc, mock_pcr, mock_csc):
mock_to.return_value = True
self.worker.check_resource(self.ctx, self.resource.id,
self.stack.current_traversal,
@ -513,7 +511,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
self.assertTrue(mock_hst.called)
def test_check_resource_does_not_propagate_on_cancel(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
# ensure when check_resource is cancelled, the next set of
# resources are not propagated.
mock_cru.side_effect = check_resource.CancelOperation
@ -522,10 +520,8 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
{}, self.is_update, {})
self.assertFalse(mock_pcr.called)
self.assertFalse(mock_csc.called)
self.assertFalse(mock_cid.called)
@mock.patch.object(check_resource, 'construct_input_data')
@mock.patch.object(check_resource, 'check_stack_complete')
@mock.patch.object(check_resource, 'propagate_check_resource')
@mock.patch.object(check_resource, 'check_resource_cleanup')
@ -555,8 +551,7 @@ class CheckWorkflowCleanupTest(common.HeatTestCase):
@mock.patch.object(resource.Resource, 'load')
@mock.patch.object(stack.Stack, 'time_remaining')
def test_is_cleanup_traversal(
self, tr, mock_load, mock_cru, mock_crc, mock_pcr, mock_csc,
mock_cid):
self, tr, mock_load, mock_cru, mock_crc, mock_pcr, mock_csc):
tr.return_value = 317
mock_load.return_value = self.resource, self.stack, self.stack
self.worker.check_resource(
@ -570,7 +565,7 @@ class CheckWorkflowCleanupTest(common.HeatTestCase):
@mock.patch.object(stack.Stack, 'time_remaining')
def test_is_cleanup_traversal_raise_update_inprogress(
self, tr, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, tr, mock_cru, mock_crc, mock_pcr, mock_csc):
mock_crc.side_effect = exception.UpdateInProgress
tr.return_value = 317
self.worker.check_resource(
@ -585,7 +580,7 @@ class CheckWorkflowCleanupTest(common.HeatTestCase):
self.assertFalse(mock_csc.called)
def test_check_resource_does_not_propagate_on_cancelling_cleanup(
self, mock_cru, mock_crc, mock_pcr, mock_csc, mock_cid):
self, mock_cru, mock_crc, mock_pcr, mock_csc):
# ensure when check_resource is cancelled, the next set of
# resources are not propagated.
mock_crc.side_effect = check_resource.CancelOperation
@ -594,7 +589,6 @@ class CheckWorkflowCleanupTest(common.HeatTestCase):
{}, self.is_update, {})
self.assertFalse(mock_pcr.called)
self.assertFalse(mock_csc.called)
self.assertFalse(mock_cid.called)
class MiscMethodsTest(common.HeatTestCase):
@ -608,7 +602,7 @@ class MiscMethodsTest(common.HeatTestCase):
self.stack.converge_stack(self.stack.t)
self.resource = self.stack['A']
def test_construct_input_data_ok(self):
def test_node_data_ok(self):
expected_input_data = {'attrs': {(u'flat_dict', u'key2'): 'val2',
(u'flat_dict', u'key3'): 'val3',
(u'nested_dict', u'dict', u'a'): 1,
@ -619,11 +613,10 @@ class MiscMethodsTest(common.HeatTestCase):
'uuid': mock.ANY,
'action': mock.ANY,
'status': mock.ANY}
actual_input_data = check_resource.construct_input_data(self.resource,
self.stack)
self.assertEqual(expected_input_data, actual_input_data)
actual_input_data = self.resource.node_data()
self.assertEqual(expected_input_data, actual_input_data.as_dict())
def test_construct_input_data_exception(self):
def test_node_data_exception(self):
expected_input_data = {'attrs': {},
'id': mock.ANY,
'reference_id': 'A',
@ -634,9 +627,8 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.get_attribute = mock.Mock(
side_effect=exception.InvalidTemplateAttribute(resource='A',
key='value'))
actual_input_data = check_resource.construct_input_data(self.resource,
self.stack)
self.assertEqual(expected_input_data, actual_input_data)
actual_input_data = self.resource.node_data()
self.assertEqual(expected_input_data, actual_input_data.as_dict())
@mock.patch.object(sync_point, 'sync')
def test_check_stack_complete_root(self, mock_sync):

@ -20,14 +20,12 @@ from heat.common import identifier
from heat.common import template_format
from heat.engine.cfn import functions as cfn_functions
from heat.engine.cfn import parameters as cfn_param
from heat.engine import check_resource as cr
from heat.engine import conditions
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 node_data
from heat.engine import resource
from heat.engine import resources
from heat.engine import rsrc_defn
@ -2349,14 +2347,15 @@ class StackGetAttributesTestConvergence(common.HeatTestCase):
]
def _prepare_cache_data(self, rsrc):
attributes = function.dep_attrs(
dep_attrs = function.dep_attrs(
self.stack.t.parse(self.stack, self.snippet),
self.resource_name)
with mock.patch.object(rsrc.stack, 'get_dep_attrs') as mock_da:
mock_da.return_value = dep_attrs
rsrc_data = rsrc.node_data()
# store as cache data
self.stack.cache_data = {
rsrc.name: node_data.NodeData.from_dict({
'attrs': cr._resolve_attributes(attributes, rsrc)
})
rsrc.name: rsrc_data
}
def test_get_attr_convergence(self):

Loading…
Cancel
Save