deb-heat/heat/tests/engine/service/test_stack_update.py
Steven Hardy b646c6b4ab Fix mocking in several tests causing excessive run-times
Several tests have missing mocking resulting in many minutes wasted
waiting for either RPC or client validation to time out.

Strangely this doesn't cause the tests to fail, only waste a lot of
time (tox -e py27 was taking over 10 mins for me, now nearer 5)

There are more broken tests to fix, but some have logical errors
so will be handled via subsequent patches.

Change-Id: I2021505930d6547a16531de6b2f9fbf33855fa3a
Partial-Bug: #1524047
2015-12-08 22:40:39 +00:00

809 lines
35 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from eventlet import event as grevent
import mock
from oslo_config import cfg
from oslo_messaging.rpc import dispatcher
import six
from heat.common import exception
from heat.common import messaging
from heat.common import template_format
from heat.engine import environment
from heat.engine import resource
from heat.engine import service
from heat.engine import stack
from heat.engine import stack_lock
from heat.engine import template as templatem
from heat.objects import stack as stack_object
from heat.rpc import api as rpc_api
from heat.tests import common
from heat.tests.engine import tools
from heat.tests import utils
class ServiceStackUpdateTest(common.HeatTestCase):
def setUp(self):
super(ServiceStackUpdateTest, self).setUp()
self.ctx = utils.dummy_context()
self.man = service.EngineService('a-host', 'a-topic')
self.man.thread_group_mgr = tools.DummyThreadGroupManager()
def test_stack_update(self):
stack_name = 'service_update_test_stack'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
old_stack = tools.get_stack(stack_name, self.ctx)
sid = old_stack.store()
old_stack.set_stack_user_project_id('1234')
s = stack_object.Stack.get_by_id(self.ctx, sid)
stk = tools.get_stack(stack_name, self.ctx)
# prepare mocks
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_load = self.patchobject(stack.Stack, 'load',
return_value=old_stack)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_validate = self.patchobject(stk, 'validate', return_value=None)
event_mock = mock.Mock()
self.patchobject(grevent, 'Event', return_value=event_mock)
# do update
api_args = {'timeout_mins': 60}
result = self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, api_args)
# assertions
self.assertEqual(old_stack.identifier(), result)
self.assertIsInstance(result, dict)
self.assertTrue(result['stack_id'])
self.assertEqual([event_mock], self.man.thread_group_mgr.events)
mock_tmpl.assert_called_once_with(template, files=None, env=stk.env)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
self.ctx, stk.name, stk.t,
convergence=False,
current_traversal=old_stack.current_traversal,
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')
mock_load.assert_called_once_with(self.ctx, stack=s)
mock_validate.assert_called_once_with()
def test_stack_update_existing_parameters(self):
# Use a template with existing parameters, then update the stack
# with a template containing additional parameters and ensure all
# are preserved.
stack_name = 'service_update_test_stack_existing_parameters'
update_params = {'encrypted_param_names': [],
'parameter_defaults': {},
'parameters': {'newparam': 123},
'resource_registry': {'resources': {}}}
api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True}
t = template_format.parse(tools.wp_template)
stk = tools.get_stack(stack_name, self.ctx, with_params=True)
stk.store()
stk.set_stack_user_project_id('1234')
self.assertEqual({'KeyName': 'test'}, stk.t.env.params)
with mock.patch('heat.engine.stack.Stack') as mock_stack:
stk.update = mock.Mock()
mock_stack.load.return_value = stk
mock_stack.validate.return_value = None
result = self.man.update_stack(self.ctx, stk.identifier(),
t,
update_params,
None, api_args)
tmpl = mock_stack.call_args[0][2]
self.assertEqual({'KeyName': 'test', 'newparam': 123},
tmpl.env.params)
self.assertEqual(stk.identifier(), result)
def test_stack_update_existing_parameters_remove(self):
"""Test case for updating stack with changed parameters.
Use a template with existing parameters, then update with a
template containing additional parameters and a list of
parameters to be removed.
"""
stack_name = 'service_update_test_stack_existing_parameters_remove'
update_params = {'encrypted_param_names': [],
'parameter_defaults': {},
'parameters': {'newparam': 123},
'resource_registry': {'resources': {}}}
api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CLEAR_PARAMETERS: ['removeme']}
t = template_format.parse(tools.wp_template)
t['parameters']['removeme'] = {'type': 'string'}
stk = utils.parse_stack(t, stack_name=stack_name,
params={'KeyName': 'test', 'removeme': 'foo'})
stk.set_stack_user_project_id('1234')
self.assertEqual({'KeyName': 'test', 'removeme': 'foo'},
stk.t.env.params)
with mock.patch('heat.engine.stack.Stack') as mock_stack:
stk.update = mock.Mock()
mock_stack.load.return_value = stk
mock_stack.validate.return_value = None
result = self.man.update_stack(self.ctx, stk.identifier(),
t,
update_params,
None, api_args)
tmpl = mock_stack.call_args[0][2]
self.assertEqual({'KeyName': 'test', 'newparam': 123},
tmpl.env.params)
self.assertEqual(stk.identifier(), result)
def test_stack_update_existing_registry(self):
# Use a template with existing flag and ensure the
# environment registry is preserved.
stack_name = 'service_update_test_stack_existing_registry'
intital_registry = {'OS::Foo': 'foo.yaml',
'OS::Foo2': 'foo2.yaml',
'resources': {
'myserver': {'OS::Server': 'myserver.yaml'}}}
intial_params = {'encrypted_param_names': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': intital_registry}
initial_files = {'foo.yaml': 'foo',
'foo2.yaml': 'foo2',
'myserver.yaml': 'myserver'}
update_registry = {'OS::Foo2': 'newfoo2.yaml',
'resources': {
'myother': {'OS::Other': 'myother.yaml'}}}
update_params = {'encrypted_param_names': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': update_registry}
update_files = {'newfoo2.yaml': 'newfoo',
'myother.yaml': 'myother'}
api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True}
t = template_format.parse(tools.wp_template)
stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params,
files=initial_files)
stk.set_stack_user_project_id('1234')
self.assertEqual(intial_params, stk.t.env.user_env_as_dict())
expected_reg = {'OS::Foo': 'foo.yaml',
'OS::Foo2': 'newfoo2.yaml',
'resources': {
'myother': {'OS::Other': 'myother.yaml'},
'myserver': {'OS::Server': 'myserver.yaml'}}}
expected_env = {'encrypted_param_names': [],
'parameter_defaults': {},
'parameters': {},
'resource_registry': expected_reg}
# FIXME(shardy): Currently we don't prune unused old files
expected_files = {'foo.yaml': 'foo',
'foo2.yaml': 'foo2',
'myserver.yaml': 'myserver',
'newfoo2.yaml': 'newfoo',
'myother.yaml': 'myother'}
with mock.patch('heat.engine.stack.Stack') as mock_stack:
stk.update = mock.Mock()
mock_stack.load.return_value = stk
mock_stack.validate.return_value = None
result = self.man.update_stack(self.ctx, stk.identifier(),
t,
update_params,
update_files,
api_args)
tmpl = mock_stack.call_args[0][2]
self.assertEqual(expected_env,
tmpl.env.user_env_as_dict())
self.assertEqual(expected_files,
tmpl.files)
self.assertEqual(stk.identifier(), result)
def test_stack_update_existing_parameter_defaults(self):
"""Ensure the environment parameter_defaults are preserved.
Use a template with existing flag and ensure the environment
parameter_defaults are preserved.
"""
stack_name = 'service_update_test_stack_existing_param_defaults'
intial_params = {'encrypted_param_names': [],
'parameter_defaults': {'mydefault': 123},
'parameters': {},
'resource_registry': {}}
update_params = {'encrypted_param_names': [],
'parameter_defaults': {'default2': 456},
'parameters': {},
'resource_registry': {}}
api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True}
t = template_format.parse(tools.wp_template)
stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params)
stk.set_stack_user_project_id('1234')
expected_env = {'encrypted_param_names': [],
'parameter_defaults': {
'mydefault': 123,
'default2': 456},
'parameters': {},
'resource_registry': {'resources': {}}}
with mock.patch('heat.engine.stack.Stack') as mock_stack:
stk.update = mock.Mock()
mock_stack.load.return_value = stk
mock_stack.validate.return_value = None
result = self.man.update_stack(self.ctx, stk.identifier(),
t,
update_params,
None, api_args)
tmpl = mock_stack.call_args[0][2]
self.assertEqual(expected_env,
tmpl.env.user_env_as_dict())
self.assertEqual(stk.identifier(), result)
def test_stack_update_reuses_api_params(self):
stack_name = 'service_update_stack_reuses_api_params'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
old_stack = tools.get_stack(stack_name, self.ctx)
old_stack.timeout_mins = 1
old_stack.disable_rollback = False
sid = old_stack.store()
old_stack.set_stack_user_project_id('1234')
s = stack_object.Stack.get_by_id(self.ctx, sid)
stk = tools.get_stack(stack_name, self.ctx)
# prepare mocks
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_load = self.patchobject(stack.Stack, 'load',
return_value=old_stack)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_validate = self.patchobject(stk, 'validate', return_value=None)
# do update
result = self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, {})
# assertions
self.assertEqual(old_stack.identifier(), result)
self.assertIsInstance(result, dict)
self.assertTrue(result['stack_id'])
mock_validate.assert_called_once_with()
mock_tmpl.assert_called_once_with(template, files=None, env=stk.env)
mock_env.assert_called_once_with(params)
mock_load.assert_called_once_with(self.ctx, stack=s)
mock_stack.assert_called_once_with(
self.ctx, stk.name, stk.t,
convergence=False,
current_traversal=old_stack.current_traversal,
prev_raw_template_id=None, current_deps=None,
disable_rollback=False, nested_depth=0,
owner_id=None, parent_resource=None,
stack_user_project_id='1234',
strict_validate=True,
tenant_id='test_tenant_id', timeout_mins=1,
user_creds_id=u'1',
username='test_username')
def test_stack_cancel_update_same_engine(self):
stack_name = 'service_update_stack_test_cancel_same_engine'
stk = tools.get_stack(stack_name, self.ctx)
stk.state_set(stk.UPDATE, stk.IN_PROGRESS, 'test_override')
stk.disable_rollback = False
stk.store()
self.patchobject(stack.Stack, 'load', return_value=stk)
self.patchobject(stack_lock.StackLock, 'try_acquire',
return_value=self.man.engine_id)
self.patchobject(self.man.thread_group_mgr, 'send')
self.man.stack_cancel_update(self.ctx, stk.identifier(),
cancel_with_rollback=False)
self.man.thread_group_mgr.send.assert_called_once_with(stk.id,
'cancel')
def test_stack_cancel_update_different_engine(self):
stack_name = 'service_update_stack_test_cancel_different_engine'
stk = tools.get_stack(stack_name, self.ctx)
stk.state_set(stk.UPDATE, stk.IN_PROGRESS, 'test_override')
stk.disable_rollback = False
stk.store()
self.patchobject(stack.Stack, 'load', return_value=stk)
self.patchobject(stack_lock.StackLock, 'try_acquire',
return_value=str(uuid.uuid4()))
self.patchobject(stack_lock.StackLock, 'engine_alive',
return_value=True)
self.man.listener = mock.Mock()
self.man.listener.SEND = 'send'
self.man._client = messaging.get_rpc_client(
version=self.man.RPC_API_VERSION)
# In fact the another engine is not alive, so the call will timeout
self.assertRaises(dispatcher.ExpectedException,
self.man.stack_cancel_update,
self.ctx, stk.identifier())
def test_stack_cancel_update_wrong_state_fails(self):
stack_name = 'service_update_cancel_test_stack'
stk = tools.get_stack(stack_name, self.ctx)
stk.state_set(stk.UPDATE, stk.COMPLETE, 'test_override')
stk.store()
self.patchobject(stack.Stack, 'load', return_value=stk)
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.stack_cancel_update,
self.ctx, stk.identifier())
self.assertEqual(exception.NotSupported, ex.exc_info[0])
self.assertIn("Cancelling update when stack is "
"('UPDATE', 'COMPLETE')",
six.text_type(ex.exc_info[1]))
@mock.patch.object(stack_object.Stack, 'count_total_resources')
def test_stack_update_equals(self, ctr):
stack_name = 'test_stack_update_equals_resource_limit'
params = {}
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {
'A': {'Type': 'GenericResourceType'},
'B': {'Type': 'GenericResourceType'},
'C': {'Type': 'GenericResourceType'}}}
template = templatem.Template(tpl)
old_stack = stack.Stack(self.ctx, stack_name, template)
sid = old_stack.store()
old_stack.set_stack_user_project_id('1234')
s = stack_object.Stack.get_by_id(self.ctx, sid)
ctr.return_value = 3
stk = stack.Stack(self.ctx, stack_name, template)
# prepare mocks
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_load = self.patchobject(stack.Stack, 'load',
return_value=old_stack)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_validate = self.patchobject(stk, 'validate', return_value=None)
# do update
cfg.CONF.set_override('max_resources_per_stack', 3)
api_args = {'timeout_mins': 60}
result = self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, api_args)
# assertions
self.assertEqual(old_stack.identifier(), result)
self.assertIsInstance(result, dict)
self.assertTrue(result['stack_id'])
root_stack_id = old_stack.root_stack_id()
self.assertEqual(3, old_stack.total_resources(root_stack_id))
mock_tmpl.assert_called_once_with(template, files=None, env=stk.env)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
self.ctx, stk.name, stk.t,
convergence=False,
current_traversal=old_stack.current_traversal,
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')
mock_load.assert_called_once_with(self.ctx, stack=s)
mock_validate.assert_called_once_with()
def test_stack_update_stack_id_equal(self):
stack_name = 'test_stack_update_stack_id_equal'
tpl = {
'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {
'A': {
'Type': 'ResourceWithPropsType',
'Properties': {
'Foo': {'Ref': 'AWS::StackId'}
}
}
}
}
template = templatem.Template(tpl)
create_stack = stack.Stack(self.ctx, stack_name, template)
sid = create_stack.store()
create_stack.create()
self.assertEqual((create_stack.CREATE, create_stack.COMPLETE),
create_stack.state)
create_stack._persist_state()
s = stack_object.Stack.get_by_id(self.ctx, sid)
old_stack = stack.Stack.load(self.ctx, stack=s)
self.assertEqual((old_stack.CREATE, old_stack.COMPLETE),
old_stack.state)
self.assertEqual(create_stack.identifier().arn(),
old_stack['A'].properties['Foo'])
mock_load = self.patchobject(stack.Stack, 'load',
return_value=old_stack)
result = self.man.update_stack(self.ctx, create_stack.identifier(),
tpl, {}, None, {})
old_stack._persist_state()
self.assertEqual((old_stack.UPDATE, old_stack.COMPLETE),
old_stack.state)
self.assertEqual(create_stack.identifier(), result)
self.assertIsNotNone(create_stack.identifier().stack_id)
self.assertEqual(create_stack.identifier().arn(),
old_stack['A'].properties['Foo'])
self.assertEqual(create_stack['A'].id, old_stack['A'].id)
mock_load.assert_called_once_with(self.ctx, stack=s)
def test_stack_update_exceeds_resource_limit(self):
stack_name = 'test_stack_update_exceeds_resource_limit'
params = {}
tpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {
'A': {'Type': 'GenericResourceType'},
'B': {'Type': 'GenericResourceType'},
'C': {'Type': 'GenericResourceType'}}}
template = templatem.Template(tpl)
old_stack = stack.Stack(self.ctx, stack_name, template)
sid = old_stack.store()
self.assertIsNotNone(sid)
cfg.CONF.set_override('max_resources_per_stack', 2)
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack, self.ctx,
old_stack.identifier(), tpl, params,
None, {})
self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0])
self.assertIn(exception.StackResourceLimitExceeded.msg_fmt,
six.text_type(ex.exc_info[1]))
def test_stack_update_verify_err(self):
stack_name = 'service_update_verify_err_test_stack'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
old_stack = tools.get_stack(stack_name, self.ctx)
old_stack.store()
sid = old_stack.store()
old_stack.set_stack_user_project_id('1234')
s = stack_object.Stack.get_by_id(self.ctx, sid)
stk = tools.get_stack(stack_name, self.ctx)
# prepare mocks
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_load = self.patchobject(stack.Stack, 'load',
return_value=old_stack)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
ex_expected = exception.StackValidationFailed(message='fubar')
mock_validate = self.patchobject(stk, 'validate',
side_effect=ex_expected)
# do update
api_args = {'timeout_mins': 60}
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack,
self.ctx, old_stack.identifier(),
template, params, None, api_args)
# assertions
self.assertEqual(exception.StackValidationFailed, ex.exc_info[0])
mock_tmpl.assert_called_once_with(template, files=None, env=stk.env)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
self.ctx, stk.name, stk.t,
convergence=False,
current_traversal=old_stack.current_traversal,
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')
mock_load.assert_called_once_with(self.ctx, stack=s)
mock_validate.assert_called_once_with()
def test_stack_update_nonexist(self):
stack_name = 'service_update_nonexist_test_stack'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
stk = tools.get_stack(stack_name, self.ctx)
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack,
self.ctx, stk.identifier(), template,
params, None, {})
self.assertEqual(exception.EntityNotFound, ex.exc_info[0])
def test_stack_update_no_credentials(self):
cfg.CONF.set_default('deferred_auth_method', 'password')
stack_name = 'test_stack_update_no_credentials'
params = {'foo': 'bar'}
template = '{ "Template": "data" }'
stk = tools.get_stack(stack_name, self.ctx)
# force check for credentials on create
stk['WebServer'].requires_deferred_auth = True
sid = stk.store()
stk.set_stack_user_project_id('1234')
s = stack_object.Stack.get_by_id(self.ctx, sid)
self.ctx = utils.dummy_context(password=None)
# prepare mocks
mock_get = self.patchobject(self.man, '_get_stack', return_value=s)
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_load = self.patchobject(stack.Stack, 'load', return_value=stk)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
api_args = {'timeout_mins': 60}
ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack, self.ctx,
stk.identifier(),
template, params, None, api_args)
self.assertEqual(exception.MissingCredentialError, ex.exc_info[0])
self.assertEqual('Missing required credential: X-Auth-Key',
six.text_type(ex.exc_info[1]))
mock_get.assert_called_once_with(self.ctx, stk.identifier())
mock_tmpl.assert_called_once_with(template, files=None, env=stk.env)
mock_env.assert_called_once_with(params)
mock_stack.assert_called_once_with(
self.ctx, stk.name, stk.t,
convergence=False, current_traversal=stk.current_traversal,
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')
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(t, stack.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(t, stack.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):
old_tmpl = """
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_tmpl = """
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
"""
def setUp(self):
super(ServiceStackUpdatePreviewTest, self).setUp()
self.ctx = utils.dummy_context()
self.man = service.EngineService('a-host', 'a-topic')
self.man.thread_group_mgr = tools.DummyThreadGroupManager()
def _test_stack_update_preview(self, orig_template, new_template):
stack_name = 'service_update_test_stack_preview'
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)
stk = tools.get_stack(stack_name, self.ctx, template=new_template)
# prepare mocks
mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_load = self.patchobject(stack.Stack, 'load',
return_value=old_stack)
mock_tmpl = self.patchobject(templatem, 'Template', return_value=stk.t)
mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env)
mock_validate = self.patchobject(stk, 'validate', return_value=None)
# Patch _resolve_all_attributes or it tries to call novaclient
self.patchobject(resource.Resource, '_resolve_all_attributes',
return_value=None)
# do preview_update_stack
api_args = {'timeout_mins': 60}
result = self.man.preview_update_stack(self.ctx,
old_stack.identifier(),
new_template, params, None,
api_args)
# assertions
mock_stack.assert_called_once_with(
self.ctx, stk.name, stk.t, convergence=False,
current_traversal=old_stack.current_traversal,
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')
mock_load.assert_called_once_with(self.ctx, stack=s)
mock_tmpl.assert_called_once_with(new_template, files=None,
env=stk.env)
mock_env.assert_called_once_with(params)
mock_validate.assert_called_once_with()
return result
def test_stack_update_preview_added_unchanged(self):
result = self._test_stack_update_preview(self.old_tmpl, self.new_tmpl)
added = [x for x in result['added']][0]
self.assertEqual('password', added['resource_name'])
unchanged = [x for x in result['unchanged']][0]
self.assertEqual('web_server', unchanged['resource_name'])
self.assertNotEqual('None', unchanged['resource_identity']['stack_id'])
empty_sections = ('deleted', 'replaced', 'updated')
for section in empty_sections:
section_contents = [x for x in result[section]]
self.assertEqual([], section_contents)
def test_stack_update_preview_replaced(self):
# new template with a different key_name
new_tmpl = self.old_tmpl.replace('test', 'test2')
result = self._test_stack_update_preview(self.old_tmpl, new_tmpl)
replaced = [x for x in result['replaced']][0]
self.assertEqual('web_server', replaced['resource_name'])
empty_sections = ('added', 'deleted', 'unchanged', 'updated')
for section in empty_sections:
section_contents = [x for x in result[section]]
self.assertEqual([], section_contents)
def test_stack_update_preview_updated(self):
# new template changes to flavor of server
new_tmpl = self.old_tmpl.replace('m1.large', 'm1.small')
result = self._test_stack_update_preview(self.old_tmpl, new_tmpl)
updated = [x for x in result['updated']][0]
self.assertEqual('web_server', updated['resource_name'])
empty_sections = ('added', 'deleted', 'unchanged', 'replaced')
for section in empty_sections:
section_contents = [x for x in result[section]]
self.assertEqual([], section_contents)
def test_stack_update_preview_deleted(self):
# do the reverse direction, i.e. delete resources
result = self._test_stack_update_preview(self.new_tmpl, self.old_tmpl)
deleted = [x for x in result['deleted']][0]
self.assertEqual('password', deleted['resource_name'])
unchanged = [x for x in result['unchanged']][0]
self.assertEqual('web_server', unchanged['resource_name'])
empty_sections = ('added', 'updated', 'replaced')
for section in empty_sections:
section_contents = [x for x in result[section]]
self.assertEqual([], section_contents)