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.engine import template
from heat.objects import resource as resource_objects from heat.objects import resource as resource_objects
from heat.objects import resource_data as resource_data_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 from heat.rpc import client as rpc_client
cfg.CONF.import_opt('action_retry_limit', 'heat.common.config') 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): def load(cls, context, resource_id, is_update, data):
from heat.engine import stack as stack_mod from heat.engine import stack as stack_mod
db_res = resource_objects.Resource.get_obj(context, resource_id) 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 resource_owning_stack = curr_stack
def special_stack(tmpl, swap_template): if db_res.current_template_id != curr_stack.t.id:
stk = stack_mod.Stack.load(context, db_res.stack_id, # load stack with template owning the resource
cache_data=data) 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 # Load only the resource in question; don't load all resources
# update with another template, the stack object won't have the # by invoking stack.resources. Maintain light-weight stack.
# template of the previous stack-run. res_defn = resource_owning_stack.t.resource_definitions(
if swap_template: resource_owning_stack)[db_res.name]
prev_tmpl = stk.t resource = cls(db_res.name, res_defn, resource_owning_stack)
stk.t = tmpl resource._load_data(db_res)
stk.resources
yield stk
if swap_template:
stk.t = prev_tmpl
tmpl = template.Template.load(context, db_res.current_template_id) # assign current stack to the resource for updates
with special_stack(tmpl, not is_update) as stack: if is_update:
stack_res = tmpl.resource_definitions(stack)[db_res.name] resource.stack = curr_stack
resource = cls(db_res.name, stack_res, stack)
resource._load_data(db_res)
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): def make_replacement(self, new_tmpl_id):
# 1. create the replacement with "replaces" = self.id # 1. create the replacement with "replaces" = self.id
@ -886,7 +889,7 @@ class Resource(object):
return True return True
def update_convergence(self, template_id, resource_data, engine_id, def update_convergence(self, template_id, resource_data, engine_id,
timeout): timeout, new_stack):
"""Update the resource synchronously. """Update the resource synchronously.
Persist the resource's current_template_id to template_id and Persist the resource's current_template_id to template_id and
@ -903,7 +906,7 @@ class Resource(object):
with self.lock(engine_id): with self.lock(engine_id):
new_temp = template.Template.load(self.context, template_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 \ if self.stack.action == self.stack.ROLLBACK and \
self.stack.status == self.stack.IN_PROGRESS \ self.stack.status == self.stack.IN_PROGRESS \
and self.replaced_by: and self.replaced_by:

View File

@ -466,7 +466,7 @@ class Port(neutron.NeutronResource):
props = {'fixed_ips': []} props = {'fixed_ips': []}
if convergence: 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.context, prev_port.replaced_by, True,
prev_port.stack.cache_data prev_port.stack.cache_data
) )

View File

@ -397,7 +397,7 @@ class ServerNetworkMixin(object):
self.stack._backup_stack().resources.get(self.name) self.stack._backup_stack().resources.get(self.name)
if convergence: 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.context, prev_server.replaced_by, True,
prev_server.stack.cache_data prev_server.stack.cache_data
) )

View File

@ -140,14 +140,13 @@ class WorkerService(service.Service):
# no data to resolve in cleanup phase # no data to resolve in cleanup phase
cache_data = {} cache_data = {}
rsrc, stack = None, None
try: try:
rsrc, stack = resource.Resource.load(cnxt, resource_id, is_update, return resource.Resource.load(cnxt, resource_id,
cache_data) is_update, cache_data)
except (exception.ResourceNotFound, exception.NotFound): except (exception.ResourceNotFound, exception.NotFound):
pass # can be ignored pass # can be ignored
return rsrc, stack return None, None, None
def _do_check_resource(self, cnxt, current_traversal, tmpl, resource_data, def _do_check_resource(self, cnxt, current_traversal, tmpl, resource_data,
is_update, rsrc, stack, adopt_stack_data): is_update, rsrc, stack, adopt_stack_data):
@ -156,7 +155,7 @@ class WorkerService(service.Service):
try: try:
check_resource_update(rsrc, tmpl.id, resource_data, check_resource_update(rsrc, tmpl.id, resource_data,
self.engine_id, self.engine_id,
stack.time_remaining()) stack)
except exception.UpdateReplace: except exception.UpdateReplace:
new_res_id = rsrc.make_replacement(tmpl.id) new_res_id = rsrc.make_replacement(tmpl.id)
LOG.info("Replacing resource with new id %s", new_res_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): def _get_input_data(req, fwd):
if fwd: if fwd:
return construct_input_data(rsrc) return construct_input_data(rsrc, stack)
else: else:
# Don't send data if initiating clean-up for self i.e. # Don't send data if initiating clean-up for self i.e.
# initiating delete of a replaced resource # initiating delete of a replaced resource
@ -272,8 +271,9 @@ class WorkerService(service.Service):
associated resource. associated resource.
""" """
resource_data = dict(sync_point.deserialize_input_data(data)) resource_data = dict(sync_point.deserialize_input_data(data))
rsrc, stack = self._load_resource(cnxt, resource_id, resource_data, rsrc, rsrc_owning_stack, stack = self._load_resource(cnxt, resource_id,
is_update) resource_data,
is_update)
if rsrc is None: if rsrc is None:
return return
@ -307,10 +307,10 @@ class WorkerService(service.Service):
rsrc, stack) rsrc, stack)
def construct_input_data(rsrc): def construct_input_data(rsrc, curr_stack):
attributes = rsrc.stack.get_dep_attrs( attributes = curr_stack.get_dep_attrs(
six.itervalues(rsrc.stack.resources), six.itervalues(curr_stack.resources),
rsrc.stack.outputs, curr_stack.outputs,
rsrc.name) rsrc.name)
resolved_attributes = {} resolved_attributes = {}
for attr in 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, def check_resource_update(rsrc, template_id, resource_data, engine_id,
timeout): stack):
"""Create or update the Resource if appropriate.""" """Create or update the Resource if appropriate."""
if rsrc.action == resource.Resource.INIT: 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: 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, 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, mock_cru.assert_called_once_with(self.resource,
self.resource.stack.t.id, self.resource.stack.t.id,
{}, self.worker.engine_id, {}, self.worker.engine_id,
tr()) mock.ANY)
self.assertTrue(mock_mr.called) self.assertTrue(mock_mr.called)
self.assertFalse(mock_crc.called) self.assertFalse(mock_crc.called)
self.assertFalse(mock_pcr.called) self.assertFalse(mock_pcr.called)
@ -200,7 +200,7 @@ class CheckWorkflowUpdateTest(common.HeatTestCase):
mock_cru.assert_called_once_with(self.resource, mock_cru.assert_called_once_with(self.resource,
self.resource.stack.t.id, self.resource.stack.t.id,
{}, self.worker.engine_id, {}, self.worker.engine_id,
tr()) mock.ANY)
self.assertFalse(mock_crc.called) self.assertFalse(mock_crc.called)
self.assertFalse(mock_pcr.called) self.assertFalse(mock_pcr.called)
self.assertFalse(mock_csc.called) self.assertFalse(mock_csc.called)
@ -547,7 +547,8 @@ class MiscMethodsTest(common.HeatTestCase):
'uuid': mock.ANY, 'uuid': mock.ANY,
'action': mock.ANY, 'action': mock.ANY,
'status': 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) self.assertEqual(expected_input_data, actual_input_data)
def test_construct_input_data_exception(self): def test_construct_input_data_exception(self):
@ -561,7 +562,8 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.FnGetAtt = mock.Mock( self.resource.FnGetAtt = mock.Mock(
side_effect=exception.InvalidTemplateAttribute(resource='A', side_effect=exception.InvalidTemplateAttribute(resource='A',
key='value')) 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) self.assertEqual(expected_input_data, actual_input_data)
@mock.patch.object(sync_point, 'sync') @mock.patch.object(sync_point, 'sync')
@ -608,7 +610,7 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.action = 'INIT' self.resource.action = 'INIT'
worker.check_resource_update(self.resource, self.resource.stack.t.id, worker.check_resource_update(self.resource, self.resource.stack.t.id,
{}, 'engine-id', {}, 'engine-id',
self.stack.timeout_secs()) self.stack)
self.assertTrue(mock_create.called) self.assertTrue(mock_create.called)
self.assertFalse(mock_update.called) self.assertFalse(mock_update.called)
@ -619,7 +621,7 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.action = 'CREATE' self.resource.action = 'CREATE'
worker.check_resource_update(self.resource, self.resource.stack.t.id, worker.check_resource_update(self.resource, self.resource.stack.t.id,
{}, 'engine-id', {}, 'engine-id',
self.stack.timeout_secs()) self.stack)
self.assertFalse(mock_create.called) self.assertFalse(mock_create.called)
self.assertTrue(mock_update.called) self.assertTrue(mock_update.called)
@ -630,7 +632,7 @@ class MiscMethodsTest(common.HeatTestCase):
self.resource.action = 'UPDATE' self.resource.action = 'UPDATE'
worker.check_resource_update(self.resource, self.resource.stack.t.id, worker.check_resource_update(self.resource, self.resource.stack.t.id,
{}, 'engine-id', {}, 'engine-id',
self.stack.timeout_secs()) self.stack)
self.assertFalse(mock_create.called) self.assertFalse(mock_create.called)
self.assertTrue(mock_update.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.current_template_id = self.stack.t.id
res.state_set('CREATE', 'IN_PROGRESS') res.state_set('CREATE', 'IN_PROGRESS')
self.stack.add_resource(res) self.stack.add_resource(res)
loaded_res, stack = resource.Resource.load(self.stack.context, loaded_res, res_owning_stack, stack = resource.Resource.load(
res.id, True, {}) self.stack.context, res.id, True, {})
self.assertEqual(loaded_res.id, res.id) self.assertEqual(loaded_res.id, res.id)
self.assertEqual(self.stack.t, stack.t) 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.current_template_id = self.old_stack.t.id
res.state_set('CREATE', 'IN_PROGRESS') res.state_set('CREATE', 'IN_PROGRESS')
self.old_stack.add_resource(res) self.old_stack.add_resource(res)
loaded_res, stack = resource.Resource.load(self.old_stack.context, loaded_res, res_owning_stack, stack = resource.Resource.load(
res.id, False, {}) self.old_stack.context, res.id, False, {})
self.assertEqual(loaded_res.id, res.id) self.assertEqual(loaded_res.id, res.id)
self.assertEqual(self.old_stack.t, stack.t) self.assertEqual(self.old_stack.t, stack.t)
self.assertNotEqual(self.new_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.current_template_id = self.stack.t.id
res.state_set('CREATE', 'IN_PROGRESS') res.state_set('CREATE', 'IN_PROGRESS')
self.stack.add_resource(res) self.stack.add_resource(res)
origin_resources = self.stack._resources origin_resources = self.stack.resources
self.stack._resources = None self.stack._resources = None
loaded_res, stack = resource.Resource.load(self.stack.context, loaded_res, res_owning_stack, stack = resource.Resource.load(
res.id, False, {}) self.stack.context, res.id, False, {})
self.assertEqual(origin_resources, stack._resources) self.assertEqual(origin_resources, stack.resources)
self.assertEqual(loaded_res.id, res.id) self.assertEqual(loaded_res.id, res.id)
self.assertEqual(self.stack.t, stack.t) 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': {}}, res_data = {(1, True): {u'id': 4, u'name': 'A', 'attrs': {}},
(2, True): {u'id': 3, u'name': 'B', '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] expected_rsrc_def = new_temp.resource_definitions(self.stack)[res.name]
mock_init.assert_called_once_with(res.update, expected_rsrc_def) mock_init.assert_called_once_with(res.update, expected_rsrc_def)
@ -1783,7 +1784,7 @@ class ResourceTest(common.HeatTestCase):
res_data = {} res_data = {}
self.assertRaises(scheduler.Timeout, res.update_convergence, self.assertRaises(scheduler.Timeout, res.update_convergence,
new_temp.id, res_data, 'engine-007', new_temp.id, res_data, 'engine-007',
0) 0, mock.ANY)
def test_update_in_progress_convergence(self): def test_update_in_progress_convergence(self):
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo') tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
@ -1800,7 +1801,8 @@ class ResourceTest(common.HeatTestCase):
res.update_convergence, res.update_convergence,
'template_key', 'template_key',
res_data, 'engine-007', res_data, 'engine-007',
self.dummy_timeout) self.dummy_timeout,
mock.ANY)
msg = ("The resource %s is already being updated." % msg = ("The resource %s is already being updated." %
res.name) res.name)
self.assertEqual(msg, six.text_type(ex)) self.assertEqual(msg, six.text_type(ex))
@ -1831,7 +1833,7 @@ class ResourceTest(common.HeatTestCase):
mock_update.side_effect = dummy_ex mock_update.side_effect = dummy_ex
self.assertRaises(exception.ResourceFailure, self.assertRaises(exception.ResourceFailure,
res.update_convergence, new_temp.id, res_data, 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] expected_rsrc_def = new_temp.resource_definitions(self.stack)[res.name]
mock_update.assert_called_once_with(expected_rsrc_def) mock_update.assert_called_once_with(expected_rsrc_def)
@ -1863,7 +1865,7 @@ class ResourceTest(common.HeatTestCase):
mock_update.side_effect = exception.UpdateReplace mock_update.side_effect = exception.UpdateReplace
self.assertRaises(exception.UpdateReplace, self.assertRaises(exception.UpdateReplace,
res.update_convergence, new_temp.id, res_data, 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] expected_rsrc_def = new_temp.resource_definitions(self.stack)[res.name]
mock_update.assert_called_once_with(expected_rsrc_def) mock_update.assert_called_once_with(expected_rsrc_def)
@ -2096,13 +2098,15 @@ class ResourceTest(common.HeatTestCase):
stack.store() stack.store()
mock_tmpl_load.return_value = tmpl mock_tmpl_load.return_value = tmpl
res = stack['res'] res = stack['res']
res.current_template_id = stack.t.id
res._store() res._store()
data = {'bar': {'atrr1': 'baz', 'attr2': 'baz2'}} data = {'bar': {'atrr1': 'baz', 'attr2': 'baz2'}}
mock_stack_load.return_value = stack mock_stack_load.return_value = stack
resource.Resource.load(stack.context, res.id, True, data) resource.Resource.load(stack.context, res.id, True, data)
mock_stack_load.assert_called_once_with(stack.context, self.assertTrue(mock_stack_load.called)
stack.id, mock_stack_load.assert_called_with(stack.context,
cache_data=data) stack_id=stack.id,
cache_data=data)
self.assertTrue(mock_load_data.called) self.assertTrue(mock_load_data.called)