diff --git a/heat/engine/resource.py b/heat/engine/resource.py index 043a3831d0..ba50b6a1a3 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -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: diff --git a/heat/engine/resources/openstack/neutron/port.py b/heat/engine/resources/openstack/neutron/port.py index c5b5cf0877..29e654d97d 100644 --- a/heat/engine/resources/openstack/neutron/port.py +++ b/heat/engine/resources/openstack/neutron/port.py @@ -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 ) diff --git a/heat/engine/resources/openstack/nova/server_network_mixin.py b/heat/engine/resources/openstack/nova/server_network_mixin.py index b9f972c1d2..1b4e287e87 100644 --- a/heat/engine/resources/openstack/nova/server_network_mixin.py +++ b/heat/engine/resources/openstack/nova/server_network_mixin.py @@ -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 ) diff --git a/heat/engine/worker.py b/heat/engine/worker.py index cd4987ca19..7ab934d531 100644 --- a/heat/engine/worker.py +++ b/heat/engine/worker.py @@ -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, diff --git a/heat/tests/engine/test_engine_worker.py b/heat/tests/engine/test_engine_worker.py index da2af90333..1a5906eb8b 100644 --- a/heat/tests/engine/test_engine_worker.py +++ b/heat/tests/engine/test_engine_worker.py @@ -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) diff --git a/heat/tests/test_resource.py b/heat/tests/test_resource.py index f4000d6ddf..edcbfbf356 100644 --- a/heat/tests/test_resource.py +++ b/heat/tests/test_resource.py @@ -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)