Merge "PATCH update reuse existing template"
This commit is contained in:
commit
8616c992ca
@ -66,6 +66,7 @@ class InstantiationData(object):
|
|||||||
to distinguish.
|
to distinguish.
|
||||||
"""
|
"""
|
||||||
self.data = data
|
self.data = data
|
||||||
|
self.patch = patch
|
||||||
if patch:
|
if patch:
|
||||||
self.data[rpc_api.PARAM_EXISTING] = True
|
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
|
Get template file contents, either inline, from stack adopt data or
|
||||||
from a URL, in JSON or YAML format.
|
from a URL, in JSON or YAML format.
|
||||||
"""
|
"""
|
||||||
|
template_data = None
|
||||||
if rpc_api.PARAM_ADOPT_STACK_DATA in self.data:
|
if rpc_api.PARAM_ADOPT_STACK_DATA in self.data:
|
||||||
adopt_data = self.data[rpc_api.PARAM_ADOPT_STACK_DATA]
|
adopt_data = self.data[rpc_api.PARAM_ADOPT_STACK_DATA]
|
||||||
try:
|
try:
|
||||||
@ -112,8 +114,12 @@ class InstantiationData(object):
|
|||||||
except IOError as ex:
|
except IOError as ex:
|
||||||
err_reason = _('Could not retrieve template: %s') % ex
|
err_reason = _('Could not retrieve template: %s') % ex
|
||||||
raise exc.HTTPBadRequest(err_reason)
|
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'):
|
with self.parse_error_check('Template'):
|
||||||
return template_format.parse(template_data)
|
return template_format.parse(template_data)
|
||||||
|
@ -791,7 +791,8 @@ class EngineService(service.Service):
|
|||||||
|
|
||||||
# Now parse the template and any parameters for the updated
|
# Now parse the template and any parameters for the updated
|
||||||
# stack definition. If PARAM_EXISTING is specified, we merge
|
# 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):
|
if args.get(rpc_api.PARAM_EXISTING, None):
|
||||||
existing_env = current_stack.env.user_env_as_dict()
|
existing_env = current_stack.env.user_env_as_dict()
|
||||||
existing_params = existing_env[env_fmt.PARAMETERS]
|
existing_params = existing_env[env_fmt.PARAMETERS]
|
||||||
@ -804,15 +805,42 @@ class EngineService(service.Service):
|
|||||||
|
|
||||||
new_files = current_stack.t.files.copy()
|
new_files = current_stack.t.files.copy()
|
||||||
new_files.update(files or {})
|
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:
|
else:
|
||||||
new_env = environment.Environment(params)
|
new_env = environment.Environment(params)
|
||||||
new_files = files
|
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(
|
current_stack, updated_stack = self._prepare_stack_updates(
|
||||||
cnxt, current_stack, tmpl, params, files, args)
|
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,
|
current_stack.converge_stack(template=tmpl,
|
||||||
new_stack=updated_stack)
|
new_stack=updated_stack)
|
||||||
else:
|
else:
|
||||||
|
@ -1661,6 +1661,41 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
|
|||||||
self.assertEqual(403, resp.status_int)
|
self.assertEqual(403, resp.status_int)
|
||||||
self.assertIn('403 Forbidden', six.text_type(resp))
|
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):
|
def test_update_with_existing_parameters(self, mock_enforce):
|
||||||
self._mock_enforce_setup(mock_enforce, 'update_patch', True)
|
self._mock_enforce_setup(mock_enforce, 'update_patch', True)
|
||||||
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
|
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
|
||||||
|
@ -601,6 +601,68 @@ class ServiceStackUpdateTest(common.HeatTestCase):
|
|||||||
user_creds_id=u'1', username='test_username')
|
user_creds_id=u'1', username='test_username')
|
||||||
mock_load.assert_called_once_with(self.ctx, stack=s)
|
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):
|
class ServiceStackUpdatePreviewTest(common.HeatTestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user