Merge "PATCH update reuse existing template"

This commit is contained in:
Jenkins 2015-09-17 13:33:53 +00:00 committed by Gerrit Code Review
commit 8616c992ca
4 changed files with 136 additions and 5 deletions

View File

@ -66,6 +66,7 @@ class InstantiationData(object):
to distinguish.
"""
self.data = data
self.patch = patch
if patch:
self.data[rpc_api.PARAM_EXISTING] = True
@ -92,6 +93,7 @@ class InstantiationData(object):
Get template file contents, either inline, from stack adopt data or
from a URL, in JSON or YAML format.
"""
template_data = None
if rpc_api.PARAM_ADOPT_STACK_DATA in self.data:
adopt_data = self.data[rpc_api.PARAM_ADOPT_STACK_DATA]
try:
@ -112,8 +114,12 @@ class InstantiationData(object):
except IOError as ex:
err_reason = _('Could not retrieve template: %s') % ex
raise exc.HTTPBadRequest(err_reason)
else:
raise exc.HTTPBadRequest(_("No template specified"))
if template_data is None:
if self.patch:
return None
else:
raise exc.HTTPBadRequest(_("No template specified"))
with self.parse_error_check('Template'):
return template_format.parse(template_data)

View File

@ -791,7 +791,8 @@ class EngineService(service.Service):
# Now parse the template and any parameters for the updated
# stack definition. If PARAM_EXISTING is specified, we merge
# any environment provided into the existing one.
# any environment provided into the existing one and attempt
# to use the existing stack template, if one is not provided.
if args.get(rpc_api.PARAM_EXISTING, None):
existing_env = current_stack.env.user_env_as_dict()
existing_params = existing_env[env_fmt.PARAMETERS]
@ -804,15 +805,42 @@ class EngineService(service.Service):
new_files = current_stack.t.files.copy()
new_files.update(files or {})
if template is not None:
new_template = template
elif (current_stack.convergence or
current_stack.status == current_stack.COMPLETE):
# If convergence is enabled, or the stack is complete, we can
# just use the current template...
new_template = current_stack.t.t
else:
# ..but if it's FAILED without convergence things may be in an
# inconsistent state, so we try to fall back on a stored copy
# of the previous template
if current_stack.prev_raw_template_id is not None:
# Use the stored previous template
prev_t = templatem.Template.load(
cnxt, current_stack.prev_raw_template_id)
new_template = prev_t.t
else:
# Nothing we can do, the failed update happened before
# we started storing prev_raw_template_id
LOG.error("PATCH update to FAILED stack only possible if "
"convergence enabled or previous template "
"stored")
msg = _('PATCH update to non-COMPLETE stack')
raise exception.NotSupported(feature=msg)
else:
new_env = environment.Environment(params)
new_files = files
tmpl = templatem.Template(template, files=new_files, env=new_env)
new_template = template
tmpl = templatem.Template(new_template, files=new_files, env=new_env)
current_stack, updated_stack = self._prepare_stack_updates(
cnxt, current_stack, tmpl, params, files, args)
if current_stack.get_kwargs_for_cloning()['convergence']:
if current_stack.convergence:
current_stack.converge_stack(template=tmpl,
new_stack=updated_stack)
else:

View File

@ -1661,6 +1661,41 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
self.assertEqual(403, resp.status_int)
self.assertIn('403 Forbidden', six.text_type(resp))
def test_update_with_existing_template(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'update_patch', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
body = {'template': None,
'parameters': {},
'files': {},
'timeout_mins': 30}
req = self._patch('/stacks/%(stack_name)s/%(stack_id)s' % identity,
json.dumps(body))
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
rpc_client.EngineClient.call(
req.context,
('update_stack',
{'stack_identity': dict(identity),
'template': None,
'params': {'parameters': {},
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {rpc_api.PARAM_EXISTING: True,
'timeout_mins': 30}})
).AndReturn(dict(identity))
self.m.ReplayAll()
self.assertRaises(webob.exc.HTTPAccepted,
self.controller.update_patch,
req, tenant_id=identity.tenant,
stack_name=identity.stack_name,
stack_id=identity.stack_id,
body=body)
self.m.VerifyAll()
def test_update_with_existing_parameters(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'update_patch', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')

View File

@ -601,6 +601,68 @@ class ServiceStackUpdateTest(common.HeatTestCase):
user_creds_id=u'1', username='test_username')
mock_load.assert_called_once_with(self.ctx, stack=s)
def test_stack_update_existing_template(self):
'''Update a stack using the same template.'''
stack_name = 'service_update_test_stack_existing_template'
api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True}
t = template_format.parse(tools.wp_template)
# Don't actually run the update as the mocking breaks it, instead
# we just ensure the expected template is passed in to the updated
# template, and that the update task is scheduled.
self.man.thread_group_mgr = tools.DummyThreadGroupMgrLogStart()
params = {}
stack = utils.parse_stack(t, stack_name=stack_name,
params=params)
stack.set_stack_user_project_id('1234')
self.assertEqual(stack.t.t,
t)
stack.action = stack.CREATE
stack.status = stack.COMPLETE
with mock.patch('heat.engine.stack.Stack') as mock_stack:
mock_stack.load.return_value = stack
mock_stack.validate.return_value = None
result = self.man.update_stack(self.ctx, stack.identifier(),
None,
params,
None, api_args)
tmpl = mock_stack.call_args[0][2]
self.assertEqual(t,
tmpl.t)
self.assertEqual(stack.identifier(), result)
self.assertEqual(1, len(self.man.thread_group_mgr.started))
def test_stack_update_existing_failed(self):
'''Update a stack using the same template doesn't work when FAILED.'''
stack_name = 'service_update_test_stack_existing_template'
api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True}
t = template_format.parse(tools.wp_template)
# Don't actually run the update as the mocking breaks it, instead
# we just ensure the expected template is passed in to the updated
# template, and that the update task is scheduled.
self.man.thread_group_mgr = tools.DummyThreadGroupMgrLogStart()
params = {}
stack = utils.parse_stack(t, stack_name=stack_name,
params=params)
stack.set_stack_user_project_id('1234')
self.assertEqual(stack.t.t,
t)
stack.action = stack.UPDATE
stack.status = stack.FAILED
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack,
self.ctx, stack.identifier(),
None, params, None, api_args)
self.assertEqual(exception.NotSupported, ex.exc_info[0])
self.assertIn("PATCH update to non-COMPLETE stack",
six.text_type(ex.exc_info[1]))
class ServiceStackUpdatePreviewTest(common.HeatTestCase):