Add a preview endpoint for stack updates
Allow users to see what resources will be changed during a stack-update. Docs change here https://review.openstack.org/132870/ Client change here https://review.openstack.org/#/c/126957/ BP: update-dry-run Co-Authored-By: Jason Dunsmore <jasondunsmore@gmail.com> Change-Id: If58bdcccfef6f5d36c0367c5267f95014232015e
This commit is contained in:
parent
2dbcd9064d
commit
6513d3944c
@ -53,6 +53,7 @@
|
||||
"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:validate_template": "rule:deny_stack_user",
|
||||
"stacks:snapshot": "rule:deny_stack_user",
|
||||
|
@ -208,6 +208,12 @@ class API(wsgi.Router):
|
||||
'action': 'update_patch',
|
||||
'method': 'PATCH'
|
||||
},
|
||||
{
|
||||
'name': 'preview_stack_update',
|
||||
'url': '/stacks/{stack_name}/{stack_id}/preview',
|
||||
'action': 'preview_update',
|
||||
'method': 'PUT'
|
||||
},
|
||||
{
|
||||
'name': 'stack_delete',
|
||||
'url': '/stacks/{stack_name}/{stack_id}',
|
||||
|
@ -458,6 +458,24 @@ class StackController(object):
|
||||
|
||||
raise exc.HTTPAccepted()
|
||||
|
||||
@util.identified_stack
|
||||
def preview_update(self, req, identity, body):
|
||||
"""
|
||||
Preview an update to an existing stack with a new template/parameters
|
||||
"""
|
||||
data = InstantiationData(body)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -865,8 +865,11 @@ class Resource(object):
|
||||
resource_data.get('metadata'))
|
||||
|
||||
def _needs_update(self, after, before, after_props, before_props,
|
||||
prev_resource):
|
||||
if self.status == self.FAILED or \
|
||||
prev_resource, check_init_complete=True):
|
||||
if self.status == self.FAILED:
|
||||
raise UpdateReplace(self)
|
||||
|
||||
if check_init_complete and \
|
||||
(self.action == self.INIT and self.status == self.COMPLETE):
|
||||
raise UpdateReplace(self)
|
||||
|
||||
|
@ -34,7 +34,7 @@ class NoneResource(resource.Resource):
|
||||
attributes_schema = {}
|
||||
|
||||
def _needs_update(self, after, before, after_props, before_props,
|
||||
prev_resource):
|
||||
prev_resource, check_init_complete=True):
|
||||
return False
|
||||
|
||||
def reparse(self):
|
||||
|
@ -212,7 +212,7 @@ class RemoteStack(resource.Resource):
|
||||
self.heat().actions.check(stack_id=self.resource_id)
|
||||
|
||||
def _needs_update(self, after, before, after_props, before_props,
|
||||
prev_resource):
|
||||
prev_resource, check_init_complete=True):
|
||||
# Always issue an update to the remote stack and let the individual
|
||||
# resources in it decide if they need updating.
|
||||
return True
|
||||
|
@ -413,13 +413,14 @@ class Port(neutron.NeutronResource):
|
||||
return super(Port, self)._resolve_attribute(name)
|
||||
|
||||
def _needs_update(self, after, before, after_props, before_props,
|
||||
prev_resource):
|
||||
prev_resource, check_init_complete=True):
|
||||
|
||||
if after_props.get(self.REPLACEMENT_POLICY) == 'REPLACE_ALWAYS':
|
||||
raise resource.UpdateReplace(self.name)
|
||||
|
||||
return super(Port, self)._needs_update(
|
||||
after, before, after_props, before_props, prev_resource)
|
||||
after, before, after_props, before_props, prev_resource,
|
||||
check_init_complete)
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
props = self.prepare_update_properties(json_snippet)
|
||||
|
@ -86,16 +86,18 @@ class StackResource(resource.Resource):
|
||||
self._resolve_all_attributes)
|
||||
|
||||
def _needs_update(self, after, before, after_props, before_props,
|
||||
prev_resource):
|
||||
prev_resource, check_init_complete=True):
|
||||
# Issue an update to the nested stack if the stack resource
|
||||
# is able to update. If return true, let the individual
|
||||
# resources in it decide if they need updating.
|
||||
|
||||
# FIXME (ricolin): seems currently can not call super here
|
||||
if self.nested() is None and (
|
||||
self.status == self.FAILED
|
||||
or (self.action == self.INIT
|
||||
and self.status == self.COMPLETE)):
|
||||
if self.nested() is None and self.status == self.FAILED:
|
||||
raise resource.UpdateReplace(self)
|
||||
|
||||
if (check_init_complete and
|
||||
self.nested() is None and
|
||||
self.action == self.INIT and self.status == self.COMPLETE):
|
||||
raise resource.UpdateReplace(self)
|
||||
|
||||
return True
|
||||
|
@ -57,6 +57,7 @@ from heat.engine import stack as parser
|
||||
from heat.engine import stack_lock
|
||||
from heat.engine import support
|
||||
from heat.engine import template as templatem
|
||||
from heat.engine import update
|
||||
from heat.engine import watchrule
|
||||
from heat.engine import worker
|
||||
from heat.objects import event as event_object
|
||||
@ -272,7 +273,7 @@ class EngineService(service.Service):
|
||||
by the RPC caller.
|
||||
"""
|
||||
|
||||
RPC_API_VERSION = '1.14'
|
||||
RPC_API_VERSION = '1.15'
|
||||
|
||||
def __init__(self, host, topic):
|
||||
super(EngineService, self).__init__()
|
||||
@ -717,6 +718,46 @@ class EngineService(service.Service):
|
||||
|
||||
return dict(stack.identifier())
|
||||
|
||||
def _prepare_stack_updates(self, cnxt, current_stack, tmpl, params,
|
||||
files, args):
|
||||
"""
|
||||
Given a stack and update context, return the current and updated stack.
|
||||
|
||||
Changes *will not* be persisted, this is a helper method for
|
||||
update_stack and preview_update_stack.
|
||||
|
||||
:param cnxt: RPC context.
|
||||
:param stack: A stack to be updated.
|
||||
:param tmpl: Template object 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):
|
||||
@ -767,29 +808,11 @@ class EngineService(service.Service):
|
||||
new_env = environment.Environment(params)
|
||||
new_files = files
|
||||
tmpl = templatem.Template(template, files=new_files, env=new_env)
|
||||
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_stack, updated_stack = self._prepare_stack_updates(
|
||||
cnxt, current_stack, tmpl, params, files, args)
|
||||
|
||||
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()
|
||||
|
||||
if current_kwargs['convergence']:
|
||||
if current_stack.get_kwargs_for_cloning()['convergence']:
|
||||
current_stack.converge_stack(template=tmpl,
|
||||
new_stack=updated_stack)
|
||||
else:
|
||||
@ -804,6 +827,57 @@ class EngineService(service.Service):
|
||||
self.thread_group_mgr.add_event(current_stack.id, event)
|
||||
return dict(current_stack.identifier())
|
||||
|
||||
@context.request_context
|
||||
def preview_update_stack(self, cnxt, stack_identity, template, params,
|
||||
files, args):
|
||||
"""
|
||||
The preview_update_stack method shows the resources that would be
|
||||
changed with an update to an existing stack based on the provided
|
||||
template and parameters. See update_stack for description of
|
||||
parameters.
|
||||
|
||||
This method *cannot* guarantee that an update will have the actions
|
||||
specified because resource plugins can influence changes/replacements
|
||||
at runtime.
|
||||
|
||||
Note that at this stage the template has already been fetched from the
|
||||
heat-api process if using a template-url.
|
||||
"""
|
||||
# Get the database representation of the existing stack
|
||||
db_stack = self._get_stack(cnxt, stack_identity)
|
||||
LOG.info(_LI('Previewing update of stack %s'), db_stack.name)
|
||||
|
||||
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)
|
||||
|
||||
update_task = update.StackUpdate(current_stack, updated_stack, None)
|
||||
|
||||
actions = update_task.preview()
|
||||
|
||||
fmt_updated_res = lambda k: api.format_stack_resource(
|
||||
updated_stack.resources.get(k))
|
||||
fmt_current_res = lambda k: api.format_stack_resource(
|
||||
current_stack.resources.get(k))
|
||||
|
||||
return {
|
||||
'unchanged': map(fmt_updated_res, actions['unchanged']),
|
||||
'updated': map(fmt_current_res, actions['updated']),
|
||||
'replaced': map(fmt_updated_res, actions['replaced']),
|
||||
'added': map(fmt_updated_res, actions['added']),
|
||||
'deleted': map(fmt_current_res, actions['deleted']),
|
||||
}
|
||||
|
||||
@context.request_context
|
||||
def stack_cancel_update(self, cnxt, stack_identity,
|
||||
cancel_with_rollback=True):
|
||||
|
@ -219,3 +219,43 @@ class StackUpdate(object):
|
||||
yield (res, self.new_stack[name])
|
||||
|
||||
return dependencies.Dependencies(edges())
|
||||
|
||||
def preview(self):
|
||||
upd_keys = set(self.new_stack.resources.keys())
|
||||
cur_keys = set(self.existing_stack.resources.keys())
|
||||
|
||||
common_keys = cur_keys.intersection(upd_keys)
|
||||
deleted_keys = cur_keys.difference(upd_keys)
|
||||
added_keys = upd_keys.difference(cur_keys)
|
||||
|
||||
updated_keys = []
|
||||
replaced_keys = []
|
||||
|
||||
for key in common_keys:
|
||||
current_res = self.existing_stack.resources[key]
|
||||
updated_res = self.new_stack.resources[key]
|
||||
|
||||
current_props = current_res.frozen_definition().properties(
|
||||
current_res.properties_schema, current_res.context)
|
||||
updated_props = updated_res.frozen_definition().properties(
|
||||
updated_res.properties_schema, updated_res.context)
|
||||
|
||||
try:
|
||||
if current_res._needs_update(updated_res.frozen_definition(),
|
||||
current_res.frozen_definition(),
|
||||
updated_props, current_props,
|
||||
None, check_init_complete=False):
|
||||
current_res.update_template_diff_properties(updated_props,
|
||||
current_props)
|
||||
updated_keys.append(key)
|
||||
except resource.UpdateReplace:
|
||||
replaced_keys.append(key)
|
||||
|
||||
return {
|
||||
'unchanged': list(set(common_keys).difference(
|
||||
set(updated_keys + replaced_keys))),
|
||||
'updated': updated_keys,
|
||||
'replaced': replaced_keys,
|
||||
'added': added_keys,
|
||||
'deleted': deleted_keys,
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ class EngineClient(object):
|
||||
1.12 - Add with_detail option for stack resources list
|
||||
1.13 - Add support for template functions list
|
||||
1.14 - Add cancel_with_rollback option to stack_cancel_update
|
||||
1.15 - Add preview_update_stack() call
|
||||
'''
|
||||
|
||||
BASE_RPC_API_VERSION = '1.0'
|
||||
@ -264,6 +265,32 @@ class EngineClient(object):
|
||||
files=files,
|
||||
args=args))
|
||||
|
||||
def preview_update_stack(self, ctxt, stack_identity, template, params,
|
||||
files, args):
|
||||
"""
|
||||
The preview_update_stack method returns the resources that would be
|
||||
changed in an update of an existing stack based on the provided
|
||||
template and parameters.
|
||||
|
||||
Requires RPC version 1.15 or above.
|
||||
|
||||
:param ctxt: RPC context.
|
||||
:param stack_identity: Name of the stack you wish to update.
|
||||
:param template: New template for the stack.
|
||||
:param params: Stack Input Params/Environment
|
||||
:param files: files referenced from the environment.
|
||||
:param args: Request parameters/args passed from API
|
||||
"""
|
||||
return self.call(ctxt,
|
||||
self.make_msg('preview_update_stack',
|
||||
stack_identity=stack_identity,
|
||||
template=template,
|
||||
params=params,
|
||||
files=files,
|
||||
args=args,
|
||||
),
|
||||
version='1.15')
|
||||
|
||||
def validate_template(self, ctxt, template, params=None):
|
||||
"""
|
||||
The validate_template method uses the stack parser to check
|
||||
|
@ -1101,6 +1101,47 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
|
||||
|
||||
self.assertEqual({'stack': 'formatted_stack'}, result)
|
||||
|
||||
def test_preview_update_stack(self, mock_enforce):
|
||||
self._mock_enforce_setup(mock_enforce, 'preview_update', True)
|
||||
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
|
||||
template = {u'Foo': u'bar'}
|
||||
parameters = {u'InstanceType': u'm1.xlarge'}
|
||||
body = {'template': template,
|
||||
'parameters': parameters,
|
||||
'files': {},
|
||||
'timeout_mins': 30}
|
||||
|
||||
req = self._put('/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': template,
|
||||
'params': {'parameters': parameters,
|
||||
'encrypted_param_names': [],
|
||||
'parameter_defaults': {},
|
||||
'resource_registry': {}},
|
||||
'files': {},
|
||||
'args': {'timeout_mins': 30}}),
|
||||
version='1.15'
|
||||
).AndReturn(resource_changes)
|
||||
self.m.ReplayAll()
|
||||
|
||||
result = self.controller.preview_update(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')
|
||||
|
@ -39,7 +39,7 @@ class ServiceEngineTest(common.HeatTestCase):
|
||||
|
||||
def test_make_sure_rpc_version(self):
|
||||
self.assertEqual(
|
||||
'1.14',
|
||||
'1.15',
|
||||
service.EngineService.RPC_API_VERSION,
|
||||
('RPC version is changed, please update this test to new version '
|
||||
'and make sure additional test cases are added for RPC APIs '
|
||||
|
@ -894,6 +894,209 @@ class StackServiceAdoptUpdateTest(common.HeatTestCase):
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def _test_stack_update_preview(self, orig_template, new_template):
|
||||
stack_name = 'service_update_test_stack'
|
||||
params = {'foo': 'bar'}
|
||||
old_stack = tools.get_stack(stack_name, self.ctx,
|
||||
template=orig_template)
|
||||
sid = old_stack.store()
|
||||
old_stack.set_stack_user_project_id('1234')
|
||||
s = stack_object.Stack.get_by_id(self.ctx, sid)
|
||||
|
||||
stack = tools.get_stack(stack_name, self.ctx, template=new_template)
|
||||
|
||||
self._stub_update_mocks(s, old_stack)
|
||||
|
||||
templatem.Template(new_template, files=None,
|
||||
env=stack.env).AndReturn(stack.t)
|
||||
environment.Environment(params).AndReturn(stack.env)
|
||||
parser.Stack(self.ctx, stack.name,
|
||||
stack.t,
|
||||
convergence=False,
|
||||
current_traversal=None,
|
||||
prev_raw_template_id=None,
|
||||
current_deps=None,
|
||||
disable_rollback=True,
|
||||
nested_depth=0,
|
||||
owner_id=None,
|
||||
parent_resource=None,
|
||||
stack_user_project_id='1234',
|
||||
strict_validate=True,
|
||||
tenant_id='test_tenant_id',
|
||||
timeout_mins=60,
|
||||
user_creds_id=u'1',
|
||||
username='test_username').AndReturn(stack)
|
||||
|
||||
self.m.StubOutWithMock(stack, 'validate')
|
||||
stack.validate().AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
api_args = {'timeout_mins': 60}
|
||||
result = self.man.preview_update_stack(self.ctx,
|
||||
old_stack.identifier(),
|
||||
new_template, params, None,
|
||||
api_args)
|
||||
self.m.VerifyAll()
|
||||
|
||||
return result
|
||||
|
||||
def test_stack_update_preview_added_unchanged(self):
|
||||
orig_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.large
|
||||
key_name: test
|
||||
user_data: wordpress
|
||||
'''
|
||||
|
||||
new_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.large
|
||||
key_name: test
|
||||
user_data: wordpress
|
||||
password:
|
||||
type: OS::Heat::RandomString
|
||||
properties:
|
||||
length: 8
|
||||
'''
|
||||
|
||||
result = self._test_stack_update_preview(orig_template, new_template)
|
||||
|
||||
added = [x for x in result['added']][0]
|
||||
self.assertEqual(added['resource_name'], 'password')
|
||||
unchanged = [x for x in result['unchanged']][0]
|
||||
self.assertEqual(unchanged['resource_name'], 'web_server')
|
||||
|
||||
empty_sections = ('deleted', 'replaced', 'updated')
|
||||
for section in empty_sections:
|
||||
section_contents = [x for x in result[section]]
|
||||
self.assertEqual(section_contents, [])
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_stack_update_preview_replaced(self):
|
||||
orig_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.large
|
||||
key_name: test
|
||||
user_data: wordpress
|
||||
'''
|
||||
|
||||
new_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.large
|
||||
key_name: test2
|
||||
user_data: wordpress
|
||||
'''
|
||||
|
||||
result = self._test_stack_update_preview(orig_template, new_template)
|
||||
|
||||
replaced = [x for x in result['replaced']][0]
|
||||
self.assertEqual(replaced['resource_name'], 'web_server')
|
||||
empty_sections = ('added', 'deleted', 'unchanged', 'updated')
|
||||
for section in empty_sections:
|
||||
section_contents = [x for x in result[section]]
|
||||
self.assertEqual(section_contents, [])
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_stack_update_preview_updated(self):
|
||||
orig_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.large
|
||||
key_name: test
|
||||
user_data: wordpress
|
||||
'''
|
||||
|
||||
new_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.small
|
||||
key_name: test
|
||||
user_data: wordpress
|
||||
'''
|
||||
|
||||
result = self._test_stack_update_preview(orig_template, new_template)
|
||||
|
||||
updated = [x for x in result['updated']][0]
|
||||
self.assertEqual(updated['resource_name'], 'web_server')
|
||||
empty_sections = ('added', 'deleted', 'unchanged', 'replaced')
|
||||
for section in empty_sections:
|
||||
section_contents = [x for x in result[section]]
|
||||
self.assertEqual(section_contents, [])
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_stack_update_preview_deleted(self):
|
||||
orig_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.large
|
||||
key_name: test
|
||||
user_data: wordpress
|
||||
password:
|
||||
type: OS::Heat::RandomString
|
||||
properties:
|
||||
length: 8
|
||||
'''
|
||||
|
||||
new_template = '''
|
||||
heat_template_version: 2014-10-16
|
||||
resources:
|
||||
web_server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: F17-x86_64-gold
|
||||
flavor: m1.large
|
||||
key_name: test
|
||||
user_data: wordpress
|
||||
'''
|
||||
|
||||
result = self._test_stack_update_preview(orig_template, new_template)
|
||||
|
||||
deleted = [x for x in result['deleted']][0]
|
||||
self.assertEqual(deleted['resource_name'], 'password')
|
||||
unchanged = [x for x in result['unchanged']][0]
|
||||
self.assertEqual(unchanged['resource_name'], 'web_server')
|
||||
empty_sections = ('added', 'updated', 'replaced')
|
||||
for section in empty_sections:
|
||||
section_contents = [x for x in result[section]]
|
||||
self.assertEqual(section_contents, [])
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
|
||||
class StackConvergenceServiceCreateUpdateTest(common.HeatTestCase):
|
||||
|
||||
|
@ -175,6 +175,14 @@ class EngineRpcAPITestCase(common.HeatTestCase):
|
||||
files={},
|
||||
args=mock.ANY)
|
||||
|
||||
def test_preview_update_stack(self):
|
||||
self._test_engine_api('preview_update_stack', 'call',
|
||||
stack_identity=self.identity,
|
||||
template={u'Foo': u'bar'},
|
||||
params={u'InstanceType': u'm1.xlarge'},
|
||||
files={},
|
||||
args=mock.ANY)
|
||||
|
||||
def test_get_template(self):
|
||||
self._test_engine_api('get_template', 'call',
|
||||
stack_identity=self.identity)
|
||||
|
Loading…
Reference in New Issue
Block a user