Convergence: Load resource stack with correct template

When loading a resource, load the stack with template of the resource.
Appropriate stack needs to be assigned to resource(resource.stack), else
resource actions will fail.

Co-Authored-By: Anant Patil <anant.patil@hp.com>
Partial-Bug: #1512343

Change-Id: Ic4526152c8fd027049514b71554036321a61efd2
This commit is contained in:
Rakesh H S 2015-11-04 19:00:37 +05:30 committed by Anant Patil
parent 7252e659ee
commit 77c11d037c
6 changed files with 73 additions and 62 deletions

View File

@ -44,6 +44,7 @@ from heat.engine import support
from heat.engine import template
from heat.objects import resource as resource_objects
from heat.objects import resource_data as resource_data_objects
from heat.objects import stack as stack_objects
from heat.rpc import client as rpc_client
cfg.CONF.import_opt('action_retry_limit', 'heat.common.config')
@ -263,30 +264,32 @@ class Resource(object):
def load(cls, context, resource_id, is_update, data):
from heat.engine import stack as stack_mod
db_res = resource_objects.Resource.get_obj(context, resource_id)
curr_stack = stack_mod.Stack.load(context, stack_id=db_res.stack_id,
cache_data=data)
@contextlib.contextmanager
def special_stack(tmpl, swap_template):
stk = stack_mod.Stack.load(context, db_res.stack_id,
cache_data=data)
resource_owning_stack = curr_stack
if db_res.current_template_id != curr_stack.t.id:
# load stack with template owning the resource
db_stack = stack_objects.Stack.get_by_id(context, db_res.stack_id)
db_stack.raw_template = None
db_stack.raw_template_id = db_res.current_template_id
resource_owning_stack = stack_mod.Stack.load(context,
stack=db_stack)
# NOTE(sirushtim): Because on delete/cleanup operations, we simply
# update with another template, the stack object won't have the
# template of the previous stack-run.
if swap_template:
prev_tmpl = stk.t
stk.t = tmpl
stk.resources
yield stk
if swap_template:
stk.t = prev_tmpl
# Load only the resource in question; don't load all resources
# by invoking stack.resources. Maintain light-weight stack.
res_defn = resource_owning_stack.t.resource_definitions(
resource_owning_stack)[db_res.name]
resource = cls(db_res.name, res_defn, resource_owning_stack)
resource._load_data(db_res)
tmpl = template.Template.load(context, db_res.current_template_id)
with special_stack(tmpl, not is_update) as stack:
stack_res = tmpl.resource_definitions(stack)[db_res.name]
resource = cls(db_res.name, stack_res, stack)
resource._load_data(db_res)
# assign current stack to the resource for updates
if is_update:
resource.stack = curr_stack
return resource, stack
# return resource owning stack so that it is not GCed since it
# is the only stack instance with a weak-ref from resource
return resource, resource_owning_stack, curr_stack
def make_replacement(self, new_tmpl_id):
# 1. create the replacement with "replaces" = self.id
@ -886,7 +889,7 @@ class Resource(object):
return True
def update_convergence(self, template_id, resource_data, engine_id,
timeout):
timeout, new_stack):
"""Update the resource synchronously.
Persist the resource's current_template_id to template_id and
@ -903,7 +906,7 @@ class Resource(object):
with self.lock(engine_id):
new_temp = template.Template.load(self.context, template_id)
new_res_def = new_temp.resource_definitions(self.stack)[self.name]
new_res_def = new_temp.resource_definitions(new_stack)[self.name]
if self.stack.action == self.stack.ROLLBACK and \
self.stack.status == self.stack.IN_PROGRESS \
and self.replaced_by:

View File

@ -466,7 +466,7 @@ class Port(neutron.NeutronResource):
props = {'fixed_ips': []}
if convergence:
existing_port, stack = resource.Resource.load(
existing_port, rsrc_owning_stack, stack = resource.Resource.load(
prev_port.context, prev_port.replaced_by, True,
prev_port.stack.cache_data
)

View File

@ -397,7 +397,7 @@ class ServerNetworkMixin(object):
self.stack._backup_stack().resources.get(self.name)
if convergence:
rsrc, stack = resource.Resource.load(
rsrc, rsrc_owning_stack, stack = resource.Resource.load(
prev_server.context, prev_server.replaced_by, True,
prev_server.stack.cache_data
)

View File

@ -140,14 +140,13 @@ class WorkerService(service.Service):
# no data to resolve in cleanup phase
cache_data = {}
rsrc, stack = None, None
try:
rsrc, stack = resource.Resource.load(cnxt, resource_id, is_update,
cache_data)
return resource.Resource.load(cnxt, resource_id,
is_update, cache_data)
except (exception.ResourceNotFound, exception.NotFound):
pass # can be ignored
return rsrc, stack
return None, None, None
def _do_check_resource(self, cnxt, current_traversal, tmpl, resource_data,
is_update, rsrc, stack, adopt_stack_data):
@ -156,7 +155,7 @@ class WorkerService(service.Service):
try:
check_resource_update(rsrc, tmpl.id, resource_data,
self.engine_id,
stack.time_remaining())
stack)
except exception.UpdateReplace:
new_res_id = rsrc.make_replacement(tmpl.id)
LOG.info("Replacing resource with new id %s", new_res_id)
@ -229,7 +228,7 @@ class WorkerService(service.Service):
def _get_input_data(req, fwd):
if fwd:
return construct_input_data(rsrc)
return construct_input_data(rsrc, stack)
else:
# Don't send data if initiating clean-up for self i.e.
# initiating delete of a replaced resource
@ -272,8 +271,9 @@ class WorkerService(service.Service):
associated resource.
"""
resource_data = dict(sync_point.deserialize_input_data(data))
rsrc, stack = self._load_resource(cnxt, resource_id, resource_data,
is_update)
rsrc, rsrc_owning_stack, stack = self._load_resource(cnxt, resource_id,
resource_data,
is_update)
if rsrc is None:
return
@ -307,10 +307,10 @@ class WorkerService(service.Service):
rsrc, stack)
def construct_input_data(rsrc):
attributes = rsrc.stack.get_dep_attrs(
six.itervalues(rsrc.stack.resources),
rsrc.stack.outputs,
def construct_input_data(rsrc, curr_stack):
attributes = curr_stack.get_dep_attrs(
six.itervalues(curr_stack.resources),
curr_stack.outputs,
rsrc.name)
resolved_attributes = {}
for attr in attributes:
@ -366,12 +366,14 @@ def propagate_check_resource(cnxt, rpc_client, next_res_id,
def check_resource_update(rsrc, template_id, resource_data, engine_id,
timeout):
stack):
"""Create or update the Resource if appropriate."""
if rsrc.action == resource.Resource.INIT:
rsrc.create_convergence(template_id, resource_data, engine_id, timeout)
rsrc.create_convergence(template_id, resource_data, engine_id,
stack.time_remaining())
else:
rsrc.update_convergence(template_id, resource_data, engine_id, timeout)
rsrc.update_convergence(template_id, resource_data, engine_id,
stack.time_remaining(), stack)
def check_resource_cleanup(rsrc, template_id, resource_data, engine_id,

View File

@ -179,7 +179,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
mock_cru.assert_called_once_with(self.resource,
self.resource.stack.t.id,
{}, self.worker.engine_id,
tr())
mock.ANY)
self.assertTrue(mock_mr.called)
self.assertFalse(mock_crc.called)
self.assertFalse(mock_pcr.called)
@ -200,7 +200,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
mock_cru.assert_called_once_with(self.resource,
self.resource.stack.t.id,
{}, self.worker.engine_id,
tr())
mock.ANY)
self.assertFalse(mock_crc.called)
self.assertFalse(mock_pcr.called)
self.assertFalse(mock_csc.called)
@ -547,7 +547,8 @@ class MiscMethodsTest(common.HeatTestCase):
'uuid': mock.ANY,
'action': mock.ANY,
'status': mock.ANY}
actual_input_data = worker.construct_input_data(self.resource)
actual_input_data = worker.construct_input_data(self.resource,
self.stack)
self.assertEqual(expected_input_data, actual_input_data)
def test_construct_input_data_exception(self):
@ -561,7 +562,8 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.FnGetAtt = mock.Mock(
side_effect=exception.InvalidTemplateAttribute(resource='A',
key='value'))
actual_input_data = worker.construct_input_data(self.resource)
actual_input_data = worker.construct_input_data(self.resource,
self.stack)
self.assertEqual(expected_input_data, actual_input_data)
@mock.patch.object(sync_point, 'sync')
@ -608,7 +610,7 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.action = 'INIT'
worker.check_resource_update(self.resource, self.resource.stack.t.id,
{}, 'engine-id',
self.stack.timeout_secs())
self.stack)
self.assertTrue(mock_create.called)
self.assertFalse(mock_update.called)
@ -619,7 +621,7 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.action = 'CREATE'
worker.check_resource_update(self.resource, self.resource.stack.t.id,
{}, 'engine-id',
self.stack.timeout_secs())
self.stack)
self.assertFalse(mock_create.called)
self.assertTrue(mock_update.called)
@ -630,7 +632,7 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.action = 'UPDATE'
worker.check_resource_update(self.resource, self.resource.stack.t.id,
{}, 'engine-id',
self.stack.timeout_secs())
self.stack)
self.assertFalse(mock_create.called)
self.assertTrue(mock_update.called)

View File

@ -95,8 +95,8 @@ class ResourceTest(common.HeatTestCase):
res.current_template_id = self.stack.t.id
res.state_set('CREATE', 'IN_PROGRESS')
self.stack.add_resource(res)
loaded_res, stack = resource.Resource.load(self.stack.context,
res.id, True, {})
loaded_res, res_owning_stack, stack = resource.Resource.load(
self.stack.context, res.id, True, {})
self.assertEqual(loaded_res.id, res.id)
self.assertEqual(self.stack.t, stack.t)
@ -119,8 +119,8 @@ class ResourceTest(common.HeatTestCase):
res.current_template_id = self.old_stack.t.id
res.state_set('CREATE', 'IN_PROGRESS')
self.old_stack.add_resource(res)
loaded_res, stack = resource.Resource.load(self.old_stack.context,
res.id, False, {})
loaded_res, res_owning_stack, stack = resource.Resource.load(
self.old_stack.context, res.id, False, {})
self.assertEqual(loaded_res.id, res.id)
self.assertEqual(self.old_stack.t, stack.t)
self.assertNotEqual(self.new_stack.t, stack.t)
@ -141,12 +141,12 @@ class ResourceTest(common.HeatTestCase):
res.current_template_id = self.stack.t.id
res.state_set('CREATE', 'IN_PROGRESS')
self.stack.add_resource(res)
origin_resources = self.stack._resources
origin_resources = self.stack.resources
self.stack._resources = None
loaded_res, stack = resource.Resource.load(self.stack.context,
res.id, False, {})
self.assertEqual(origin_resources, stack._resources)
loaded_res, res_owning_stack, stack = resource.Resource.load(
self.stack.context, res.id, False, {})
self.assertEqual(origin_resources, stack.resources)
self.assertEqual(loaded_res.id, res.id)
self.assertEqual(self.stack.t, stack.t)
@ -1757,7 +1757,8 @@ class ResourceTest(common.HeatTestCase):
res_data = {(1, True): {u'id': 4, u'name': 'A', 'attrs': {}},
(2, True): {u'id': 3, u'name': 'B', 'attrs': {}}}
res.update_convergence(new_temp.id, res_data, 'engine-007', 120)
res.update_convergence(new_temp.id, res_data, 'engine-007', 120,
mock.ANY)
expected_rsrc_def = new_temp.resource_definitions(self.stack)[res.name]
mock_init.assert_called_once_with(res.update, expected_rsrc_def)
@ -1783,7 +1784,7 @@ class ResourceTest(common.HeatTestCase):
res_data = {}
self.assertRaises(scheduler.Timeout, res.update_convergence,
new_temp.id, res_data, 'engine-007',
0)
0, mock.ANY)
def test_update_in_progress_convergence(self):
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
@ -1800,7 +1801,8 @@ class ResourceTest(common.HeatTestCase):
res.update_convergence,
'template_key',
res_data, 'engine-007',
self.dummy_timeout)
self.dummy_timeout,
mock.ANY)
msg = ("The resource %s is already being updated." %
res.name)
self.assertEqual(msg, six.text_type(ex))
@ -1831,7 +1833,7 @@ class ResourceTest(common.HeatTestCase):
mock_update.side_effect = dummy_ex
self.assertRaises(exception.ResourceFailure,
res.update_convergence, new_temp.id, res_data,
'engine-007', 120)
'engine-007', 120, mock.ANY)
expected_rsrc_def = new_temp.resource_definitions(self.stack)[res.name]
mock_update.assert_called_once_with(expected_rsrc_def)
@ -1863,7 +1865,7 @@ class ResourceTest(common.HeatTestCase):
mock_update.side_effect = exception.UpdateReplace
self.assertRaises(exception.UpdateReplace,
res.update_convergence, new_temp.id, res_data,
'engine-007', 120)
'engine-007', 120, mock.ANY)
expected_rsrc_def = new_temp.resource_definitions(self.stack)[res.name]
mock_update.assert_called_once_with(expected_rsrc_def)
@ -2096,13 +2098,15 @@ class ResourceTest(common.HeatTestCase):
stack.store()
mock_tmpl_load.return_value = tmpl
res = stack['res']
res.current_template_id = stack.t.id
res._store()
data = {'bar': {'atrr1': 'baz', 'attr2': 'baz2'}}
mock_stack_load.return_value = stack
resource.Resource.load(stack.context, res.id, True, data)
mock_stack_load.assert_called_once_with(stack.context,
stack.id,
cache_data=data)
self.assertTrue(mock_stack_load.called)
mock_stack_load.assert_called_with(stack.context,
stack_id=stack.id,
cache_data=data)
self.assertTrue(mock_load_data.called)