diff --git a/heat/engine/stack.py b/heat/engine/stack.py index a2c35acc32..044d32fcf1 100644 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -893,6 +893,7 @@ class Stack(collections.Mapping): elif create_if_missing: kwargs = self.get_kwargs_for_cloning() kwargs['owner_id'] = self.id + del(kwargs['prev_raw_template_id']) prev = type(self)(self.context, self.name, copy.deepcopy(self.t), **kwargs) prev.store(backup=True) @@ -1126,8 +1127,19 @@ class Stack(collections.Mapping): 'State invalid for %s' % action) return - self.state_set(action, self.IN_PROGRESS, - 'Stack %s started' % action) + # Save a copy of the new template. To avoid two DB writes + # we store the ID at the same time as the action/status + prev_tmpl_id = self.prev_raw_template_id + bu_tmpl = copy.deepcopy(newstack.t) + self.prev_raw_template_id = bu_tmpl.store() + self.action = action + self.status = self.IN_PROGRESS + self.status_reason = 'Stack %s started' % action + notification.send(self) + self._add_event(self.action, self.status, self.status_reason) + self.store() + if prev_tmpl_id is not None: + raw_template_object.RawTemplate.delete(self.context, prev_tmpl_id) if action == self.UPDATE: # Oldstack is useless when the action is not UPDATE , so we don't diff --git a/heat/tests/engine/service/test_stack_update.py b/heat/tests/engine/service/test_stack_update.py index 47e0c5378c..56260a6dc2 100644 --- a/heat/tests/engine/service/test_stack_update.py +++ b/heat/tests/engine/service/test_stack_update.py @@ -114,6 +114,7 @@ class ServiceStackUpdateTest(common.HeatTestCase): self.assertEqual({'KeyName': 'test'}, stk.t.env.params) with mock.patch('heat.engine.stack.Stack') as mock_stack: + stk.update = mock.Mock() mock_stack.load.return_value = stk mock_stack.validate.return_value = None result = self.man.update_stack(self.ctx, stk.identifier(), @@ -148,6 +149,7 @@ class ServiceStackUpdateTest(common.HeatTestCase): stk.t.env.params) with mock.patch('heat.engine.stack.Stack') as mock_stack: + stk.update = mock.Mock() mock_stack.load.return_value = stk mock_stack.validate.return_value = None result = self.man.update_stack(self.ctx, stk.identifier(), @@ -209,6 +211,7 @@ class ServiceStackUpdateTest(common.HeatTestCase): 'newfoo2.yaml': 'newfoo', 'myother.yaml': 'myother'} with mock.patch('heat.engine.stack.Stack') as mock_stack: + stk.update = mock.Mock() mock_stack.load.return_value = stk mock_stack.validate.return_value = None result = self.man.update_stack(self.ctx, stk.identifier(), @@ -250,6 +253,7 @@ class ServiceStackUpdateTest(common.HeatTestCase): 'parameters': {}, 'resource_registry': {'resources': {}}} with mock.patch('heat.engine.stack.Stack') as mock_stack: + stk.update = mock.Mock() mock_stack.load.return_value = stk mock_stack.validate.return_value = None result = self.man.update_stack(self.ctx, stk.identifier(), diff --git a/heat/tests/test_stack.py b/heat/tests/test_stack.py index b8aceaf31d..a2ff01d17d 100644 --- a/heat/tests/test_stack.py +++ b/heat/tests/test_stack.py @@ -523,6 +523,39 @@ class StackTest(common.HeatTestCase): self.stack.update(newstack) self.assertIsNotNone(self.stack.updated_time) + def test_update_prev_raw_template(self): + self.stack = stack.Stack(self.ctx, 'updated_time_test', + self.tmpl) + self.assertIsNone(self.stack.updated_time) + self.stack.store() + self.stack.create() + + self.assertIsNone(self.stack.prev_raw_template_id) + + tmpl = {'HeatTemplateFormatVersion': '2012-12-12', + 'Resources': {'R1': {'Type': 'GenericResourceType'}}} + newstack = stack.Stack(self.ctx, 'updated_time_test', + template.Template(tmpl)) + self.stack.update(newstack) + self.assertIsNotNone(self.stack.prev_raw_template_id) + prev_t = template.Template.load(self.ctx, + self.stack.prev_raw_template_id) + self.assertEqual(tmpl, prev_t.t) + prev_id = self.stack.prev_raw_template_id + + tmpl2 = {'HeatTemplateFormatVersion': '2012-12-12', + 'Resources': {'R2': {'Type': 'GenericResourceType'}}} + newstack2 = stack.Stack(self.ctx, 'updated_time_test', + template.Template(tmpl2)) + self.stack.update(newstack2) + self.assertIsNotNone(self.stack.prev_raw_template_id) + self.assertNotEqual(prev_id, self.stack.prev_raw_template_id) + prev_t2 = template.Template.load(self.ctx, + self.stack.prev_raw_template_id) + self.assertEqual(tmpl2, prev_t2.t) + self.assertRaises(exception.NotFound, + template.Template.load, self.ctx, prev_id) + def test_access_policy_update(self): tmpl = {'HeatTemplateFormatVersion': '2012-12-12', 'Resources': { diff --git a/heat/tests/test_stack_update.py b/heat/tests/test_stack_update.py index 8759e7ec2c..88a6329776 100644 --- a/heat/tests/test_stack_update.py +++ b/heat/tests/test_stack_update.py @@ -23,11 +23,13 @@ from heat.engine import scheduler from heat.engine import service from heat.engine import stack from heat.engine import template +from heat.objects import stack as stack_object from heat.rpc import api as rpc_api from heat.tests import common from heat.tests import generic_resource as generic_rsrc from heat.tests import utils + empty_template = template_format.parse('''{ "HeatTemplateFormatVersion" : "2012-12-12", }''') @@ -854,17 +856,21 @@ class StackUpdateTest(common.HeatTestCase): mock_create = self.patchobject(generic_rsrc.ResourceWithProps, 'handle_create', side_effect=Exception) - with mock.patch.object(self.stack, 'state_set', - side_effect=self.stack.state_set) as mock_state: + with mock.patch.object(stack_object.Stack, + 'update_by_id') as mock_db_update: self.stack.update(updated_stack) self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE), self.stack.state) self.assertEqual('abc', self.stack['AResource'].properties['Foo']) - self.assertEqual(2, mock_state.call_count) - self.assertEqual(('UPDATE', 'IN_PROGRESS'), - mock_state.call_args_list[0][0][:2]) - self.assertEqual(('ROLLBACK', 'IN_PROGRESS'), - mock_state.call_args_list[1][0][:2]) + self.assertEqual(5, mock_db_update.call_count) + self.assertEqual('UPDATE', + mock_db_update.call_args_list[0][0][2]['action']) + self.assertEqual('IN_PROGRESS', + mock_db_update.call_args_list[0][0][2]['status']) + self.assertEqual('ROLLBACK', + mock_db_update.call_args_list[1][0][2]['action']) + self.assertEqual('IN_PROGRESS', + mock_db_update.call_args_list[1][0][2]['status']) mock_create.assert_called_once_with()