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:
parent
fc77e43297
commit
53c4707bf6
@ -893,6 +893,7 @@ class Stack(collections.Mapping):
|
|||||||
elif create_if_missing:
|
elif create_if_missing:
|
||||||
kwargs = self.get_kwargs_for_cloning()
|
kwargs = self.get_kwargs_for_cloning()
|
||||||
kwargs['owner_id'] = self.id
|
kwargs['owner_id'] = self.id
|
||||||
|
del(kwargs['prev_raw_template_id'])
|
||||||
prev = type(self)(self.context, self.name, copy.deepcopy(self.t),
|
prev = type(self)(self.context, self.name, copy.deepcopy(self.t),
|
||||||
**kwargs)
|
**kwargs)
|
||||||
prev.store(backup=True)
|
prev.store(backup=True)
|
||||||
@ -1126,8 +1127,19 @@ class Stack(collections.Mapping):
|
|||||||
'State invalid for %s' % action)
|
'State invalid for %s' % action)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.state_set(action, self.IN_PROGRESS,
|
# Save a copy of the new template. To avoid two DB writes
|
||||||
'Stack %s started' % action)
|
# 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:
|
if action == self.UPDATE:
|
||||||
# Oldstack is useless when the action is not UPDATE , so we don't
|
# Oldstack is useless when the action is not UPDATE , so we don't
|
||||||
|
@ -114,6 +114,7 @@ class ServiceStackUpdateTest(common.HeatTestCase):
|
|||||||
self.assertEqual({'KeyName': 'test'}, stk.t.env.params)
|
self.assertEqual({'KeyName': 'test'}, stk.t.env.params)
|
||||||
|
|
||||||
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
||||||
|
stk.update = mock.Mock()
|
||||||
mock_stack.load.return_value = stk
|
mock_stack.load.return_value = stk
|
||||||
mock_stack.validate.return_value = None
|
mock_stack.validate.return_value = None
|
||||||
result = self.man.update_stack(self.ctx, stk.identifier(),
|
result = self.man.update_stack(self.ctx, stk.identifier(),
|
||||||
@ -148,6 +149,7 @@ class ServiceStackUpdateTest(common.HeatTestCase):
|
|||||||
stk.t.env.params)
|
stk.t.env.params)
|
||||||
|
|
||||||
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
||||||
|
stk.update = mock.Mock()
|
||||||
mock_stack.load.return_value = stk
|
mock_stack.load.return_value = stk
|
||||||
mock_stack.validate.return_value = None
|
mock_stack.validate.return_value = None
|
||||||
result = self.man.update_stack(self.ctx, stk.identifier(),
|
result = self.man.update_stack(self.ctx, stk.identifier(),
|
||||||
@ -209,6 +211,7 @@ class ServiceStackUpdateTest(common.HeatTestCase):
|
|||||||
'newfoo2.yaml': 'newfoo',
|
'newfoo2.yaml': 'newfoo',
|
||||||
'myother.yaml': 'myother'}
|
'myother.yaml': 'myother'}
|
||||||
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
||||||
|
stk.update = mock.Mock()
|
||||||
mock_stack.load.return_value = stk
|
mock_stack.load.return_value = stk
|
||||||
mock_stack.validate.return_value = None
|
mock_stack.validate.return_value = None
|
||||||
result = self.man.update_stack(self.ctx, stk.identifier(),
|
result = self.man.update_stack(self.ctx, stk.identifier(),
|
||||||
@ -250,6 +253,7 @@ class ServiceStackUpdateTest(common.HeatTestCase):
|
|||||||
'parameters': {},
|
'parameters': {},
|
||||||
'resource_registry': {'resources': {}}}
|
'resource_registry': {'resources': {}}}
|
||||||
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
with mock.patch('heat.engine.stack.Stack') as mock_stack:
|
||||||
|
stk.update = mock.Mock()
|
||||||
mock_stack.load.return_value = stk
|
mock_stack.load.return_value = stk
|
||||||
mock_stack.validate.return_value = None
|
mock_stack.validate.return_value = None
|
||||||
result = self.man.update_stack(self.ctx, stk.identifier(),
|
result = self.man.update_stack(self.ctx, stk.identifier(),
|
||||||
|
@ -523,6 +523,39 @@ class StackTest(common.HeatTestCase):
|
|||||||
self.stack.update(newstack)
|
self.stack.update(newstack)
|
||||||
self.assertIsNotNone(self.stack.updated_time)
|
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):
|
def test_access_policy_update(self):
|
||||||
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||||
'Resources': {
|
'Resources': {
|
||||||
|
@ -23,11 +23,13 @@ from heat.engine import scheduler
|
|||||||
from heat.engine import service
|
from heat.engine import service
|
||||||
from heat.engine import stack
|
from heat.engine import stack
|
||||||
from heat.engine import template
|
from heat.engine import template
|
||||||
|
from heat.objects import stack as stack_object
|
||||||
from heat.rpc import api as rpc_api
|
from heat.rpc import api as rpc_api
|
||||||
from heat.tests import common
|
from heat.tests import common
|
||||||
from heat.tests import generic_resource as generic_rsrc
|
from heat.tests import generic_resource as generic_rsrc
|
||||||
from heat.tests import utils
|
from heat.tests import utils
|
||||||
|
|
||||||
|
|
||||||
empty_template = template_format.parse('''{
|
empty_template = template_format.parse('''{
|
||||||
"HeatTemplateFormatVersion" : "2012-12-12",
|
"HeatTemplateFormatVersion" : "2012-12-12",
|
||||||
}''')
|
}''')
|
||||||
@ -854,17 +856,21 @@ class StackUpdateTest(common.HeatTestCase):
|
|||||||
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
mock_create = self.patchobject(generic_rsrc.ResourceWithProps,
|
||||||
'handle_create', side_effect=Exception)
|
'handle_create', side_effect=Exception)
|
||||||
|
|
||||||
with mock.patch.object(self.stack, 'state_set',
|
with mock.patch.object(stack_object.Stack,
|
||||||
side_effect=self.stack.state_set) as mock_state:
|
'update_by_id') as mock_db_update:
|
||||||
self.stack.update(updated_stack)
|
self.stack.update(updated_stack)
|
||||||
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
||||||
self.stack.state)
|
self.stack.state)
|
||||||
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
|
||||||
self.assertEqual(2, mock_state.call_count)
|
self.assertEqual(5, mock_db_update.call_count)
|
||||||
self.assertEqual(('UPDATE', 'IN_PROGRESS'),
|
self.assertEqual('UPDATE',
|
||||||
mock_state.call_args_list[0][0][:2])
|
mock_db_update.call_args_list[0][0][2]['action'])
|
||||||
self.assertEqual(('ROLLBACK', 'IN_PROGRESS'),
|
self.assertEqual('IN_PROGRESS',
|
||||||
mock_state.call_args_list[1][0][:2])
|
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()
|
mock_create.assert_called_once_with()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user