Update preview_update_stack to align with PATCH updates

Currently attempting to do a preview update call with PATCH fails,
because we didn't align the behavior of preview update with the
actual update in the recent additions to fix bug #1224828

So, refactor to ensure both preview_update & update use the same
code, and add a PATCH path to the update API.

Change-Id: I8ce5c0ea4035a7b9563db10ea10433e7f5f99a4f
Closes-Bug: #1501207
(cherry picked from commit 604595a39c)
This commit is contained in:
Steven Hardy 2015-10-01 17:16:01 +01:00 committed by Steve Baker
parent 182034904f
commit 4b30adeae1
5 changed files with 125 additions and 71 deletions

View File

@ -53,8 +53,9 @@
"stacks:show": "rule:deny_stack_user",
"stacks:template": "rule:deny_stack_user",
"stacks:update": "rule:deny_stack_user",
"stacks:preview_update": "rule:deny_stack_user",
"stacks:update_patch": "rule:deny_stack_user",
"stacks:preview_update": "rule:deny_stack_user",
"stacks:preview_update_patch": "rule:deny_stack_user",
"stacks:validate_template": "rule:deny_stack_user",
"stacks:snapshot": "rule:deny_stack_user",
"stacks:show_snapshot": "rule:deny_stack_user",

View File

@ -214,6 +214,12 @@ class API(wsgi.Router):
'action': 'preview_update',
'method': 'PUT'
},
{
'name': 'preview_stack_update_patch',
'url': '/stacks/{stack_name}/{stack_id}/preview',
'action': 'preview_update_patch',
'method': 'PATCH'
},
{
'name': 'stack_delete',
'url': '/stacks/{stack_name}/{stack_id}',

View File

@ -502,6 +502,22 @@ class StackController(object):
return {'resource_changes': changes}
@util.identified_stack
def preview_update_patch(self, req, identity, body):
"""Preview PATCH update for existing stack."""
data = InstantiationData(body, patch=True)
args = self.prepare_args(data)
changes = self.rpc_client.preview_update_stack(
req.context,
identity,
data.template(),
data.environment(),
data.files(),
args)
return {'resource_changes': changes}
@util.identified_stack
def delete(self, req, identity):
"""

View File

@ -728,7 +728,7 @@ class EngineService(service.Service):
return dict(stack.identifier())
def _prepare_stack_updates(self, cnxt, current_stack, tmpl, params,
def _prepare_stack_updates(self, cnxt, current_stack, template, params,
files, args):
"""Return the current and updated stack.
@ -737,66 +737,11 @@ class EngineService(service.Service):
:param cnxt: RPC context.
:param stack: A stack to be updated.
:param tmpl: Template object of stack you want to update to.
:param template: Template of stack you want to update to.
:param params: Stack Input Params
:param files: Files referenced from the template
:param args: Request parameters/args passed from API
"""
max_resources = cfg.CONF.max_resources_per_stack
if max_resources != -1 and len(tmpl[tmpl.RESOURCES]) > max_resources:
raise exception.RequestLimitExceeded(
message=exception.StackResourceLimitExceeded.msg_fmt)
stack_name = current_stack.name
current_kwargs = current_stack.get_kwargs_for_cloning()
common_params = api.extract_args(args)
common_params.setdefault(rpc_api.PARAM_TIMEOUT,
current_stack.timeout_mins)
common_params.setdefault(rpc_api.PARAM_DISABLE_ROLLBACK,
current_stack.disable_rollback)
current_kwargs.update(common_params)
updated_stack = parser.Stack(cnxt, stack_name, tmpl,
**current_kwargs)
self.resource_enforcer.enforce_stack(updated_stack)
updated_stack.parameters.set_stack_id(current_stack.identifier())
self._validate_deferred_auth_context(cnxt, updated_stack)
updated_stack.validate()
return current_stack, updated_stack
@context.request_context
def update_stack(self, cnxt, stack_identity, template, params,
files, args):
"""Updates an existing stack based on the provided template and params.
Note that at this stage the template has already been fetched from the
heat-api process if using a template-url.
:param cnxt: RPC context.
:param stack_identity: Name of the stack you want to create.
:param template: Template of stack you want to create.
:param params: Stack Input Params
:param files: Files referenced from the template
:param args: Request parameters/args passed from API
"""
# Get the database representation of the existing stack
db_stack = self._get_stack(cnxt, stack_identity)
LOG.info(_LI('Updating stack %s'), db_stack.name)
current_stack = parser.Stack.load(cnxt, stack=db_stack)
self.resource_enforcer.enforce_stack(current_stack)
if current_stack.action == current_stack.SUSPEND:
msg = _('Updating a stack when it is suspended')
raise exception.NotSupported(feature=msg)
if current_stack.action == current_stack.DELETE:
msg = _('Updating a stack when it is deleting')
raise exception.NotSupported(feature=msg)
# 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 and attempt
@ -845,8 +790,63 @@ class EngineService(service.Service):
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)
max_resources = cfg.CONF.max_resources_per_stack
if max_resources != -1 and len(tmpl[tmpl.RESOURCES]) > max_resources:
raise exception.RequestLimitExceeded(
message=exception.StackResourceLimitExceeded.msg_fmt)
stack_name = current_stack.name
current_kwargs = current_stack.get_kwargs_for_cloning()
common_params = api.extract_args(args)
common_params.setdefault(rpc_api.PARAM_TIMEOUT,
current_stack.timeout_mins)
common_params.setdefault(rpc_api.PARAM_DISABLE_ROLLBACK,
current_stack.disable_rollback)
current_kwargs.update(common_params)
updated_stack = parser.Stack(cnxt, stack_name, tmpl,
**current_kwargs)
self.resource_enforcer.enforce_stack(updated_stack)
updated_stack.parameters.set_stack_id(current_stack.identifier())
self._validate_deferred_auth_context(cnxt, updated_stack)
updated_stack.validate()
return tmpl, current_stack, updated_stack
@context.request_context
def update_stack(self, cnxt, stack_identity, template, params,
files, args):
"""Updates an existing stack based on the provided template and params.
Note that at this stage the template has already been fetched from the
heat-api process if using a template-url.
:param cnxt: RPC context.
:param stack_identity: Name of the stack you want to create.
:param template: Template of stack you want to create.
:param params: Stack Input Params
:param files: Files referenced from the template
:param args: Request parameters/args passed from API
"""
# Get the database representation of the existing stack
db_stack = self._get_stack(cnxt, stack_identity)
LOG.info(_LI('Updating stack %s'), db_stack.name)
current_stack = parser.Stack.load(cnxt, stack=db_stack)
self.resource_enforcer.enforce_stack(current_stack)
if current_stack.action == current_stack.SUSPEND:
msg = _('Updating a stack when it is suspended')
raise exception.NotSupported(feature=msg)
if current_stack.action == current_stack.DELETE:
msg = _('Updating a stack when it is deleting')
raise exception.NotSupported(feature=msg)
tmpl, current_stack, updated_stack = self._prepare_stack_updates(
cnxt, current_stack, template, params, files, args)
if current_stack.convergence:
current_stack.converge_stack(template=tmpl,
@ -886,17 +886,8 @@ class EngineService(service.Service):
current_stack = parser.Stack.load(cnxt, stack=db_stack)
# Now parse the template and any parameters for the updated
# stack definition.
env = environment.Environment(params)
if args.get(rpc_api.PARAM_EXISTING, None):
env.patch_previous_parameters(
current_stack.env,
args.get(rpc_api.PARAM_CLEAR_PARAMETERS, []))
tmpl = templatem.Template(template, files=files, env=env)
current_stack, updated_stack = self._prepare_stack_updates(
cnxt, current_stack, tmpl, params, files, args)
tmpl, current_stack, updated_stack = self._prepare_stack_updates(
cnxt, current_stack, template, params, files, args)
update_task = update.StackUpdate(current_stack, updated_stack, None)

View File

@ -1200,6 +1200,46 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
self.assertEqual({'resource_changes': resource_changes}, result)
self.m.VerifyAll()
def test_preview_update_stack_patch(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'preview_update_patch', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
parameters = {u'InstanceType': u'm1.xlarge'}
body = {'template': None,
'parameters': parameters,
'files': {},
'timeout_mins': 30}
req = self._patch('/stacks/%(stack_name)s/%(stack_id)s/preview' %
identity, json.dumps(body))
resource_changes = {'updated': [],
'deleted': [],
'unchanged': [],
'added': [],
'replaced': []}
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
rpc_client.EngineClient.call(
req.context,
('preview_update_stack',
{'stack_identity': dict(identity),
'template': None,
'params': {'parameters': parameters,
'encrypted_param_names': [],
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {rpc_api.PARAM_EXISTING: True,
'timeout_mins': 30}}),
version='1.15'
).AndReturn(resource_changes)
self.m.ReplayAll()
result = self.controller.preview_update_patch(
req, tenant_id=identity.tenant, stack_name=identity.stack_name,
stack_id=identity.stack_id, body=body)
self.assertEqual({'resource_changes': resource_changes}, result)
self.m.VerifyAll()
def test_lookup(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'lookup', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '1')