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:
parent
182034904f
commit
4b30adeae1
@ -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",
|
||||
|
@ -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}',
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user