Save previous template on non convergence updates

We already store the previous template for convergence based
updates, but for non-convergence updates we can reuse the same
column to enable recovery from a failed update when using
PATCH updates, e.g where the error is in the environment or
nested templates and thus the top-level template may not be
provided & it's valid to use the previously stored one.

Change-Id: I79248bedb01260a0c4b1edf183db55fc0bffbdf6
This commit is contained in:
Steven Hardy 2015-09-07 16:09:58 +01:00 committed by Steve Baker
parent fc77e43297
commit 53c4707bf6
4 changed files with 64 additions and 9 deletions

View File

@ -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

View File

@ -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(),

View File

@ -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': {

View File

@ -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()