Add converge flag in stack update for observing on reality

Add converge parameter for stack update API and RPC call,
that allow triggering observe on reality. This will be
triggered by API call with converge argument (with True
or False value) within. This flag also works for resources
within nested stack.
Implements bp get-reality-for-resources

Change-Id: I151b575b714dcc9a5971a1573c126152ecd7ea93
This commit is contained in:
ricolin 2017-07-19 21:34:46 +08:00 committed by Rico Lin
parent d5f78c2b28
commit 552f94b928
19 changed files with 138 additions and 69 deletions

View File

@ -476,6 +476,14 @@ config_name:
in: body in: body
required: true required: true
type: string type: string
converge:
description: |
Set to ``true`` to force the stack to observe the reality
before update.
in: body
required: false
default: false
type: boolean
created_at: created_at:
description: | description: |
The date and time when the service engine was created. The date and time when the service engine was created.

View File

@ -374,6 +374,7 @@ Request Parameters
- template: template - template: template
- template_url: template_url - template_url: template_url
- timeout_mins: timeout_mins - timeout_mins: timeout_mins
- converge: converge
Request Example Request Example
--------------- ---------------
@ -440,6 +441,7 @@ Request Parameters
- template: template - template: template
- template_url: template_url - template_url: template_url
- timeout_mins: timeout_mins - timeout_mins: timeout_mins
- converge: converge
Request Example Request Example
--------------- ---------------
@ -505,6 +507,7 @@ Request Parameters
- template: template - template: template
- template_url: template_url - template_url: template_url
- timeout_mins: timeout_mins - timeout_mins: timeout_mins
- converge: converge
Request Example Request Example
--------------- ---------------
@ -575,6 +578,7 @@ Request Parameters
- template: template - template: template
- template_url: template_url - template_url: template_url
- timeout_mins: timeout_mins - timeout_mins: timeout_mins
- converge: converge
Request Example Request Example
--------------- ---------------

View File

@ -374,7 +374,7 @@ class StackController(object):
formatted_stack = stacks_view.format_stack(req, result) formatted_stack = stacks_view.format_stack(req, result)
return {'stack': formatted_stack} return {'stack': formatted_stack}
def prepare_args(self, data): def prepare_args(self, data, is_update=False):
args = data.args() args = data.args()
key = rpc_api.PARAM_TIMEOUT key = rpc_api.PARAM_TIMEOUT
if key in args: if key in args:
@ -382,6 +382,11 @@ class StackController(object):
key = rpc_api.PARAM_TAGS key = rpc_api.PARAM_TAGS
if args.get(key) is not None: if args.get(key) is not None:
args[key] = self._extract_tags_param(args[key]) args[key] = self._extract_tags_param(args[key])
key = rpc_api.PARAM_CONVERGE
if not is_update and key in args:
msg = _("%s flag only supported in stack update (or update "
"preview) request.") % key
raise exc.HTTPBadRequest(six.text_type(msg))
return args return args
@util.policy_enforce @util.policy_enforce
@ -472,7 +477,7 @@ class StackController(object):
"""Update an existing stack with a new template and/or parameters.""" """Update an existing stack with a new template and/or parameters."""
data = InstantiationData(body) data = InstantiationData(body)
args = self.prepare_args(data) args = self.prepare_args(data, is_update=True)
self.rpc_client.update_stack( self.rpc_client.update_stack(
req.context, req.context,
identity, identity,
@ -493,7 +498,7 @@ class StackController(object):
""" """
data = InstantiationData(body, patch=True) data = InstantiationData(body, patch=True)
args = self.prepare_args(data) args = self.prepare_args(data, is_update=True)
self.rpc_client.update_stack( self.rpc_client.update_stack(
req.context, req.context,
identity, identity,
@ -518,7 +523,7 @@ class StackController(object):
"""Preview update for existing stack with a new template/parameters.""" """Preview update for existing stack with a new template/parameters."""
data = InstantiationData(body) data = InstantiationData(body)
args = self.prepare_args(data) args = self.prepare_args(data, is_update=True)
show_nested = self._param_show_nested(req) show_nested = self._param_show_nested(req)
if show_nested is not None: if show_nested is not None:
args[rpc_api.PARAM_SHOW_NESTED] = show_nested args[rpc_api.PARAM_SHOW_NESTED] = show_nested
@ -538,7 +543,7 @@ class StackController(object):
"""Preview PATCH update for existing stack.""" """Preview PATCH update for existing stack."""
data = InstantiationData(body, patch=True) data = InstantiationData(body, patch=True)
args = self.prepare_args(data) args = self.prepare_args(data, is_update=True)
show_nested = self._param_show_nested(req) show_nested = self._param_show_nested(req)
if show_nested is not None: if show_nested is not None:
args['show_nested'] = show_nested args['show_nested'] = show_nested

View File

@ -46,11 +46,10 @@ def extract_args(params):
kwargs[rpc_api.PARAM_TIMEOUT] = timeout kwargs[rpc_api.PARAM_TIMEOUT] = timeout
else: else:
raise ValueError(_('Invalid timeout value %s') % timeout) raise ValueError(_('Invalid timeout value %s') % timeout)
for name in [rpc_api.PARAM_CONVERGE, rpc_api.PARAM_DISABLE_ROLLBACK]:
name = rpc_api.PARAM_DISABLE_ROLLBACK if name in params:
if name in params: bool_value = param_utils.extract_bool(name, params[name])
disable_rollback = param_utils.extract_bool(name, params[name]) kwargs[name] = bool_value
kwargs[name] = disable_rollback
name = rpc_api.PARAM_SHOW_DELETED name = rpc_api.PARAM_SHOW_DELETED
if name in params: if name in params:

View File

@ -247,6 +247,7 @@ class Resource(status.ResourceStatus):
self.root_stack_id = None self.root_stack_id = None
self._calling_engine_id = None self._calling_engine_id = None
self._atomic_key = None self._atomic_key = None
self.converge = False
if not self.stack.in_convergence_check: if not self.stack.in_convergence_check:
resource = stack.db_resource_get(name) resource = stack.db_resource_get(name)
@ -1438,7 +1439,7 @@ class Resource(status.ResourceStatus):
self.translate_properties(after_props) self.translate_properties(after_props)
self.translate_properties(before_props) self.translate_properties(before_props)
if cfg.CONF.observe_on_update and before_props: if (cfg.CONF.observe_on_update or self.converge) and before_props:
if not self.resource_id: if not self.resource_id:
raise UpdateReplace(self) raise UpdateReplace(self)

View File

@ -497,7 +497,8 @@ class StackResource(resource.Resource):
kwargs.update({ kwargs.update({
'stack_identity': dict(self.nested_identifier()), 'stack_identity': dict(self.nested_identifier()),
'args': {rpc_api.PARAM_TIMEOUT: timeout_mins} 'args': {rpc_api.PARAM_TIMEOUT: timeout_mins,
rpc_api.PARAM_CONVERGE: self.converge}
}) })
with self.translate_remote_exceptions: with self.translate_remote_exceptions:
try: try:

View File

@ -874,7 +874,7 @@ class EngineService(service.ServiceBase):
""" """
# 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 and attempt # any environment provided into the existing one and attempt
# to use the existing stack template, if one is not provided. # to use the existing stack template, if one is not provided.
if args.get(rpc_api.PARAM_EXISTING): if args.get(rpc_api.PARAM_EXISTING):
@ -947,6 +947,8 @@ class EngineService(service.ServiceBase):
current_stack.timeout_mins) current_stack.timeout_mins)
common_params.setdefault(rpc_api.PARAM_DISABLE_ROLLBACK, common_params.setdefault(rpc_api.PARAM_DISABLE_ROLLBACK,
current_stack.disable_rollback) current_stack.disable_rollback)
common_params.setdefault(rpc_api.PARAM_CONVERGE,
current_stack.converge)
if args.get(rpc_api.PARAM_EXISTING): if args.get(rpc_api.PARAM_EXISTING):
common_params.setdefault(rpc_api.STACK_TAGS, common_params.setdefault(rpc_api.STACK_TAGS,

View File

@ -121,7 +121,7 @@ class Stack(collections.Mapping):
nested_depth=0, strict_validate=True, convergence=False, nested_depth=0, strict_validate=True, convergence=False,
current_traversal=None, tags=None, prev_raw_template_id=None, current_traversal=None, tags=None, prev_raw_template_id=None,
current_deps=None, cache_data=None, current_deps=None, cache_data=None,
deleted_time=None): deleted_time=None, converge=False):
"""Initialise the Stack. """Initialise the Stack.
@ -181,6 +181,7 @@ class Stack(collections.Mapping):
self._worker_client = None self._worker_client = None
self._convg_deps = None self._convg_deps = None
self.thread_group_mgr = None self.thread_group_mgr = None
self.converge = converge
# strict_validate can be used to disable value validation # strict_validate can be used to disable value validation
# in the resource properties schema, this is useful when # in the resource properties schema, this is useful when
@ -1266,6 +1267,7 @@ class Stack(collections.Mapping):
if new_stack is not None: if new_stack is not None:
self.disable_rollback = new_stack.disable_rollback self.disable_rollback = new_stack.disable_rollback
self.timeout_mins = new_stack.timeout_mins self.timeout_mins = new_stack.timeout_mins
self.converge = new_stack.converge
self.defn = new_stack.defn self.defn = new_stack.defn
self._set_param_stackid() self._set_param_stackid()
@ -1345,7 +1347,8 @@ class Stack(collections.Mapping):
self.worker_client.check_resource(self.context, rsrc_id, self.worker_client.check_resource(self.context, rsrc_id,
self.current_traversal, self.current_traversal,
input_data, is_update, input_data, is_update,
self.adopt_stack_data) self.adopt_stack_data,
self.converge)
if scheduler.ENABLE_SLEEP: if scheduler.ENABLE_SLEEP:
eventlet.sleep(1) eventlet.sleep(1)

View File

@ -213,6 +213,7 @@ class StackUpdate(object):
existing_res.stack) existing_res.stack)
existing_res.stack.resources[existing_res.name] = substitute existing_res.stack.resources[existing_res.name] = substitute
existing_res = substitute existing_res = substitute
existing_res.converge = self.new_stack.converge
return existing_res.update(new_snippet, existing_snippet, return existing_res.update(new_snippet, existing_snippet,
prev_resource=prev_res) prev_resource=prev_res)

View File

@ -61,7 +61,7 @@ class WorkerService(object):
or expect replies from these messages. or expect replies from these messages.
""" """
RPC_API_VERSION = '1.3' RPC_API_VERSION = '1.4'
def __init__(self, def __init__(self,
host, host,
@ -161,7 +161,7 @@ class WorkerService(object):
@context.request_context @context.request_context
@log_exceptions @log_exceptions
def check_resource(self, cnxt, resource_id, current_traversal, data, def check_resource(self, cnxt, resource_id, current_traversal, data,
is_update, adopt_stack_data): is_update, adopt_stack_data, converge=False):
"""Process a node in the dependency graph. """Process a node in the dependency graph.
The node may be associated with either an update or a cleanup of its The node may be associated with either an update or a cleanup of its
@ -176,6 +176,8 @@ class WorkerService(object):
if rsrc is None: if rsrc is None:
return return
rsrc.converge = converge
msg_queue = eventlet.queue.LightQueue() msg_queue = eventlet.queue.LightQueue()
try: try:
self.thread_group_mgr.add_msg_queue(stack.id, msg_queue) self.thread_group_mgr.add_msg_queue(stack.id, msg_queue)

View File

@ -20,14 +20,14 @@ PARAM_KEYS = (
PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT, PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT,
PARAM_NESTED_DEPTH, PARAM_TAGS, PARAM_SHOW_HIDDEN, PARAM_TAGS_ANY, PARAM_NESTED_DEPTH, PARAM_TAGS, PARAM_SHOW_HIDDEN, PARAM_TAGS_ANY,
PARAM_NOT_TAGS, PARAM_NOT_TAGS_ANY, TEMPLATE_TYPE, PARAM_WITH_DETAIL, PARAM_NOT_TAGS, PARAM_NOT_TAGS_ANY, TEMPLATE_TYPE, PARAM_WITH_DETAIL,
RESOLVE_OUTPUTS, PARAM_IGNORE_ERRORS RESOLVE_OUTPUTS, PARAM_IGNORE_ERRORS, PARAM_CONVERGE
) = ( ) = (
'timeout_mins', 'disable_rollback', 'adopt_stack_data', 'timeout_mins', 'disable_rollback', 'adopt_stack_data',
'show_deleted', 'show_nested', 'existing', 'show_deleted', 'show_nested', 'existing',
'clear_parameters', 'global_tenant', 'limit', 'clear_parameters', 'global_tenant', 'limit',
'nested_depth', 'tags', 'show_hidden', 'tags_any', 'nested_depth', 'tags', 'show_hidden', 'tags_any',
'not_tags', 'not_tags_any', 'template_type', 'with_detail', 'not_tags', 'not_tags_any', 'template_type', 'with_detail',
'resolve_outputs', 'ignore_errors' 'resolve_outputs', 'ignore_errors', 'converge'
) )
STACK_KEYS = ( STACK_KEYS = (

View File

@ -28,6 +28,7 @@ class WorkerClient(object):
1.1 - Added check_resource. 1.1 - Added check_resource.
1.2 - Add adopt data argument to check_resource. 1.2 - Add adopt data argument to check_resource.
1.3 - Added cancel_check_resource API. 1.3 - Added cancel_check_resource API.
1.4 - Add converge argument to check_resource
""" """
BASE_RPC_API_VERSION = '1.0' BASE_RPC_API_VERSION = '1.0'
@ -50,13 +51,16 @@ class WorkerClient(object):
client.cast(ctxt, method, **kwargs) client.cast(ctxt, method, **kwargs)
def check_resource(self, ctxt, resource_id, def check_resource(self, ctxt, resource_id,
current_traversal, data, is_update, adopt_stack_data): current_traversal, data, is_update, adopt_stack_data,
converge=False):
self.cast(ctxt, self.cast(ctxt,
self.make_msg( self.make_msg(
'check_resource', resource_id=resource_id, 'check_resource', resource_id=resource_id,
current_traversal=current_traversal, data=data, current_traversal=current_traversal, data=data,
is_update=is_update, adopt_stack_data=adopt_stack_data), is_update=is_update, adopt_stack_data=adopt_stack_data,
version='1.2') converge=converge
),
version='1.4')
def cancel_check_resource(self, ctxt, stack_id, engine_id): def cancel_check_resource(self, ctxt, stack_id, engine_id):
"""Send check-resource cancel message. """Send check-resource cancel message.

View File

@ -28,13 +28,14 @@ class Worker(message_processor.MessageProcessor):
@message_processor.asynchronous @message_processor.asynchronous
def check_resource(self, ctxt, resource_id, def check_resource(self, ctxt, resource_id,
current_traversal, data, current_traversal, data,
is_update, adopt_stack_data): is_update, adopt_stack_data, converge=False):
worker.WorkerService("fake_host", "fake_topic", worker.WorkerService("fake_host", "fake_topic",
"fake_engine", mock.Mock()).check_resource( "fake_engine", mock.Mock()).check_resource(
ctxt, resource_id, ctxt, resource_id,
current_traversal, current_traversal,
data, is_update, data, is_update,
adopt_stack_data) adopt_stack_data,
converge)
def stop_all_workers(self, current_stack): def stop_all_workers(self, current_stack):
pass pass

View File

@ -71,7 +71,7 @@ class ServiceStackUpdateTest(common.HeatTestCase):
self.patchobject(eventlet.queue, 'LightQueue', return_value=msgq_mock) self.patchobject(eventlet.queue, 'LightQueue', return_value=msgq_mock)
# do update # do update
api_args = {'timeout_mins': 60} api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: True}
result = self.man.update_stack(self.ctx, old_stack.identifier(), result = self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, api_args) template, params, None, api_args)
@ -97,7 +97,9 @@ class ServiceStackUpdateTest(common.HeatTestCase):
tenant_id='test_tenant_id', tenant_id='test_tenant_id',
timeout_mins=60, timeout_mins=60,
user_creds_id=u'1', user_creds_id=u'1',
username='test_username') username='test_username',
converge=True
)
mock_load.assert_called_once_with(self.ctx, stack=s) mock_load.assert_called_once_with(self.ctx, stack=s)
mock_validate.assert_called_once_with() mock_validate.assert_called_once_with()
@ -127,7 +129,8 @@ class ServiceStackUpdateTest(common.HeatTestCase):
# Test # Test
environment_files = ['env_1'] environment_files = ['env_1']
self.man.update_stack(self.ctx, old_stack.identifier(), self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, {}, template, params, None,
{rpc_api.PARAM_CONVERGE: False},
environment_files=environment_files) environment_files=environment_files)
# Verify # Verify
@ -160,7 +163,7 @@ class ServiceStackUpdateTest(common.HeatTestCase):
self.patchobject(eventlet.queue, 'LightQueue', return_value=msgq_mock) self.patchobject(eventlet.queue, 'LightQueue', return_value=msgq_mock)
# do update # do update
api_args = {'timeout_mins': 60} api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: False}
result = self.man.update_stack(self.ctx, old_stack.identifier(), result = self.man.update_stack(self.ctx, old_stack.identifier(),
None, None, None, api_args, None, None, None, api_args,
template_id=tmpl_id) template_id=tmpl_id)
@ -186,7 +189,9 @@ class ServiceStackUpdateTest(common.HeatTestCase):
tenant_id='test_tenant_id', tenant_id='test_tenant_id',
timeout_mins=60, timeout_mins=60,
user_creds_id=u'1', user_creds_id=u'1',
username='test_username') username='test_username',
converge=False
)
mock_load.assert_called_once_with(self.ctx, stack=s) mock_load.assert_called_once_with(self.ctx, stack=s)
mock_validate.assert_called_once_with() mock_validate.assert_called_once_with()
@ -202,7 +207,8 @@ class ServiceStackUpdateTest(common.HeatTestCase):
'parameters': {'newparam': 123}, 'parameters': {'newparam': 123},
'resource_registry': {'resources': {}}} 'resource_registry': {'resources': {}}}
api_args = {rpc_api.PARAM_TIMEOUT: 60, api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True} rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CONVERGE: False}
t = template_format.parse(tools.wp_template) t = template_format.parse(tools.wp_template)
stk = tools.get_stack(stack_name, self.ctx, with_params=True) stk = tools.get_stack(stack_name, self.ctx, with_params=True)
@ -265,7 +271,8 @@ resources:
'parameters': {}, 'parameters': {},
'resource_registry': {'resources': {}}} 'resource_registry': {'resources': {}}}
api_args = {rpc_api.PARAM_TIMEOUT: 60, api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True} rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CONVERGE: False}
with mock.patch('heat.engine.stack.Stack') as mock_stack: with mock.patch('heat.engine.stack.Stack') as mock_stack:
stk.update = mock.Mock() stk.update = mock.Mock()
@ -297,7 +304,8 @@ resources:
'resource_registry': {'resources': {}}} 'resource_registry': {'resources': {}}}
api_args = {rpc_api.PARAM_TIMEOUT: 60, api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True, rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CLEAR_PARAMETERS: ['removeme']} rpc_api.PARAM_CLEAR_PARAMETERS: ['removeme'],
rpc_api.PARAM_CONVERGE: False}
t = template_format.parse(tools.wp_template) t = template_format.parse(tools.wp_template)
t['parameters']['removeme'] = {'type': 'string'} t['parameters']['removeme'] = {'type': 'string'}
@ -383,7 +391,8 @@ resources:
update_files = {'newfoo2.yaml': 'newfoo', update_files = {'newfoo2.yaml': 'newfoo',
'myother.yaml': 'myother'} 'myother.yaml': 'myother'}
api_args = {rpc_api.PARAM_TIMEOUT: 60, api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True} rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CONVERGE: False}
t = template_format.parse(tools.wp_template) t = template_format.parse(tools.wp_template)
stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params, stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params,
@ -439,7 +448,8 @@ resources:
'parameters': {}, 'parameters': {},
'resource_registry': {}} 'resource_registry': {}}
api_args = {rpc_api.PARAM_TIMEOUT: 60, api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True} rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CONVERGE: False}
t = template_format.parse(tools.wp_template) t = template_format.parse(tools.wp_template)
stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params) stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params)
@ -489,7 +499,8 @@ resources:
# do update # do update
result = self.man.update_stack(self.ctx, old_stack.identifier(), result = self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, {}) template, params, None,
{rpc_api.PARAM_CONVERGE: False})
# assertions # assertions
self.assertEqual(old_stack.identifier(), result) self.assertEqual(old_stack.identifier(), result)
@ -511,7 +522,9 @@ resources:
strict_validate=True, strict_validate=True,
tenant_id='test_tenant_id', timeout_mins=1, tenant_id='test_tenant_id', timeout_mins=1,
user_creds_id=u'1', user_creds_id=u'1',
username='test_username') username='test_username',
converge=False
)
def test_stack_cancel_update_same_engine(self): def test_stack_cancel_update_same_engine(self):
stack_name = 'service_update_stack_test_cancel_same_engine' stack_name = 'service_update_stack_test_cancel_same_engine'
@ -619,7 +632,7 @@ resources:
# do update # do update
cfg.CONF.set_override('max_resources_per_stack', 3) cfg.CONF.set_override('max_resources_per_stack', 3)
api_args = {'timeout_mins': 60} api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: False}
result = self.man.update_stack(self.ctx, old_stack.identifier(), result = self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, api_args) template, params, None, api_args)
@ -642,7 +655,9 @@ resources:
stack_user_project_id='1234', strict_validate=True, stack_user_project_id='1234', strict_validate=True,
tenant_id='test_tenant_id', tenant_id='test_tenant_id',
timeout_mins=60, user_creds_id=u'1', timeout_mins=60, user_creds_id=u'1',
username='test_username') username='test_username',
converge=False
)
mock_load.assert_called_once_with(self.ctx, stack=s) mock_load.assert_called_once_with(self.ctx, stack=s)
mock_validate.assert_called_once_with() mock_validate.assert_called_once_with()
@ -678,7 +693,8 @@ resources:
return_value=old_stack) return_value=old_stack)
result = self.man.update_stack(self.ctx, create_stack.identifier(), result = self.man.update_stack(self.ctx, create_stack.identifier(),
tpl, {}, None, {}) tpl, {}, None,
{rpc_api.PARAM_CONVERGE: False})
old_stack._persist_state() old_stack._persist_state()
self.assertEqual((old_stack.UPDATE, old_stack.COMPLETE), self.assertEqual((old_stack.UPDATE, old_stack.COMPLETE),
@ -710,7 +726,7 @@ resources:
ex = self.assertRaises(dispatcher.ExpectedException, ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack, self.ctx, self.man.update_stack, self.ctx,
old_stack.identifier(), tpl, params, old_stack.identifier(), tpl, params,
None, {}) None, {rpc_api.PARAM_CONVERGE: False})
self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0]) self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0])
self.assertIn(exception.StackResourceLimitExceeded.msg_fmt, self.assertIn(exception.StackResourceLimitExceeded.msg_fmt,
six.text_type(ex.exc_info[1])) six.text_type(ex.exc_info[1]))
@ -738,7 +754,7 @@ resources:
mock_validate = self.patchobject(stk, 'validate', mock_validate = self.patchobject(stk, 'validate',
side_effect=ex_expected) side_effect=ex_expected)
# do update # do update
api_args = {'timeout_mins': 60} api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: False}
ex = self.assertRaises(dispatcher.ExpectedException, ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack, self.man.update_stack,
self.ctx, old_stack.identifier(), self.ctx, old_stack.identifier(),
@ -758,7 +774,9 @@ resources:
stack_user_project_id='1234', strict_validate=True, stack_user_project_id='1234', strict_validate=True,
tenant_id='test_tenant_id', tenant_id='test_tenant_id',
timeout_mins=60, user_creds_id=u'1', timeout_mins=60, user_creds_id=u'1',
username='test_username') username='test_username',
converge=False
)
mock_load.assert_called_once_with(self.ctx, stack=s) mock_load.assert_called_once_with(self.ctx, stack=s)
mock_validate.assert_called_once_with() mock_validate.assert_called_once_with()
@ -771,7 +789,7 @@ resources:
ex = self.assertRaises(dispatcher.ExpectedException, ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack, self.man.update_stack,
self.ctx, stk.identifier(), template, self.ctx, stk.identifier(), template,
params, None, {}) params, None, {rpc_api.PARAM_CONVERGE: False})
self.assertEqual(exception.EntityNotFound, ex.exc_info[0]) self.assertEqual(exception.EntityNotFound, ex.exc_info[0])
def test_stack_update_no_credentials(self): def test_stack_update_no_credentials(self):
@ -798,7 +816,7 @@ resources:
mock_env = self.patchobject(environment, 'Environment', mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env) return_value=stk.env)
api_args = {'timeout_mins': 60} api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: False}
ex = self.assertRaises(dispatcher.ExpectedException, ex = self.assertRaises(dispatcher.ExpectedException,
self.man.update_stack, self.ctx, self.man.update_stack, self.ctx,
stk.identifier(), stk.identifier(),
@ -820,14 +838,17 @@ resources:
stack_user_project_id='1234', stack_user_project_id='1234',
strict_validate=True, strict_validate=True,
tenant_id='test_tenant_id', timeout_mins=60, tenant_id='test_tenant_id', timeout_mins=60,
user_creds_id=u'1', username='test_username') user_creds_id=u'1', username='test_username',
converge=False
)
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): def test_stack_update_existing_template(self):
'''Update a stack using the same template.''' '''Update a stack using the same template.'''
stack_name = 'service_update_test_stack_existing_template' stack_name = 'service_update_test_stack_existing_template'
api_args = {rpc_api.PARAM_TIMEOUT: 60, api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True} rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CONVERGE: False}
t = template_format.parse(tools.wp_template) t = template_format.parse(tools.wp_template)
# Don't actually run the update as the mocking breaks it, instead # Don't actually run the update as the mocking breaks it, instead
# we just ensure the expected template is passed in to the updated # we just ensure the expected template is passed in to the updated
@ -859,7 +880,8 @@ resources:
'''Update a stack using the same template doesn't work when FAILED.''' '''Update a stack using the same template doesn't work when FAILED.'''
stack_name = 'service_update_test_stack_existing_template' stack_name = 'service_update_test_stack_existing_template'
api_args = {rpc_api.PARAM_TIMEOUT: 60, api_args = {rpc_api.PARAM_TIMEOUT: 60,
rpc_api.PARAM_EXISTING: True} rpc_api.PARAM_EXISTING: True,
rpc_api.PARAM_CONVERGE: False}
t = template_format.parse(tools.wp_template) t = template_format.parse(tools.wp_template)
# Don't actually run the update as the mocking breaks it, instead # Don't actually run the update as the mocking breaks it, instead
# we just ensure the expected template is passed in to the updated # we just ensure the expected template is passed in to the updated
@ -912,7 +934,7 @@ parameters:
self.man.update_stack, self.man.update_stack,
self.ctx, old_stack.identifier(), self.ctx, old_stack.identifier(),
old_stack.t.t, params, old_stack.t.t, params,
None, {}) None, {rpc_api.PARAM_CONVERGE: False})
self.assertEqual(exception.ImmutableParameterModified, exc.exc_info[0]) self.assertEqual(exception.ImmutableParameterModified, exc.exc_info[0])
self.assertEqual('The following parameters are immutable and may not ' self.assertEqual('The following parameters are immutable and may not '
'be updated: param1', exc.exc_info[1].message) 'be updated: param1', exc.exc_info[1].message)
@ -948,7 +970,7 @@ parameters:
params = {'param1': 'bar'} params = {'param1': 'bar'}
result = self.man.update_stack(self.ctx, old_stack.identifier(), result = self.man.update_stack(self.ctx, old_stack.identifier(),
templatem.Template(template), params, templatem.Template(template), params,
None, {}) None, {rpc_api.PARAM_CONVERGE: False})
self.assertEqual(s.id, result['stack_id']) self.assertEqual(s.id, result['stack_id'])
def test_update_immutable_parameter_same_value(self): def test_update_immutable_parameter_same_value(self):
@ -982,7 +1004,7 @@ parameters:
params = {'param1': 'foo'} params = {'param1': 'foo'}
result = self.man.update_stack(self.ctx, old_stack.identifier(), result = self.man.update_stack(self.ctx, old_stack.identifier(),
templatem.Template(template), params, templatem.Template(template), params,
None, {}) None, {rpc_api.PARAM_CONVERGE: False})
self.assertEqual(s.id, result['stack_id']) self.assertEqual(s.id, result['stack_id'])
@ -1057,7 +1079,7 @@ resources:
return_value=None) return_value=None)
# do preview_update_stack # do preview_update_stack
api_args = {'timeout_mins': 60} api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: False}
result = self.man.preview_update_stack( result = self.man.preview_update_stack(
self.ctx, self.ctx,
old_stack.identifier(), old_stack.identifier(),
@ -1073,7 +1095,9 @@ resources:
disable_rollback=True, nested_depth=0, owner_id=None, disable_rollback=True, nested_depth=0, owner_id=None,
parent_resource=None, stack_user_project_id='1234', parent_resource=None, stack_user_project_id='1234',
strict_validate=True, tenant_id='test_tenant_id', timeout_mins=60, strict_validate=True, tenant_id='test_tenant_id', timeout_mins=60,
user_creds_id=u'1', username='test_username') user_creds_id=u'1', username='test_username',
converge=False
)
mock_load.assert_called_once_with(self.ctx, stack=s) mock_load.assert_called_once_with(self.ctx, stack=s)
mock_tmpl.assert_called_once_with(new_template, files=None) mock_tmpl.assert_called_once_with(new_template, files=None)
mock_env.assert_called_once_with(params) mock_env.assert_called_once_with(params)

View File

@ -29,7 +29,7 @@ from heat.tests import utils
class WorkerServiceTest(common.HeatTestCase): class WorkerServiceTest(common.HeatTestCase):
def test_make_sure_rpc_version(self): def test_make_sure_rpc_version(self):
self.assertEqual( self.assertEqual(
'1.3', '1.4',
worker.WorkerService.RPC_API_VERSION, worker.WorkerService.RPC_API_VERSION,
('RPC version is changed, please update this test to new version ' ('RPC version is changed, please update this test to new version '
'and make sure additional test cases are added for RPC APIs ' 'and make sure additional test cases are added for RPC APIs '

View File

@ -72,7 +72,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
mock.call.worker_client.WorkerClient.check_resource( mock.call.worker_client.WorkerClient.check_resource(
stack.context, rsrc_id, stack.current_traversal, stack.context, rsrc_id, stack.current_traversal,
{'input_data': {}}, {'input_data': {}},
is_update, None)) is_update, None, False))
self.assertEqual(expected_calls, mock_cr.mock_calls) self.assertEqual(expected_calls, mock_cr.mock_calls)
def test_conv_string_five_instance_stack_create(self, mock_cr): def test_conv_string_five_instance_stack_create(self, mock_cr):
@ -129,7 +129,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
mock.call.worker_client.WorkerClient.check_resource( mock.call.worker_client.WorkerClient.check_resource(
stack.context, rsrc_id, stack.current_traversal, stack.context, rsrc_id, stack.current_traversal,
{'input_data': {}}, {'input_data': {}},
is_update, None)) is_update, None, False))
self.assertEqual(expected_calls, mock_cr.mock_calls) self.assertEqual(expected_calls, mock_cr.mock_calls)
def _mock_convg_db_update_requires(self): def _mock_convg_db_update_requires(self):
@ -269,7 +269,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
mock.call.worker_client.WorkerClient.check_resource( mock.call.worker_client.WorkerClient.check_resource(
stack.context, rsrc_id, stack.current_traversal, stack.context, rsrc_id, stack.current_traversal,
{'input_data': {}}, {'input_data': {}},
is_update, None)) is_update, None, False))
leaves = curr_stack.convergence_dependencies.leaves() leaves = curr_stack.convergence_dependencies.leaves()
for rsrc_id, is_update in leaves: for rsrc_id, is_update in leaves:
@ -277,7 +277,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
mock.call.worker_client.WorkerClient.check_resource( mock.call.worker_client.WorkerClient.check_resource(
curr_stack.context, rsrc_id, curr_stack.current_traversal, curr_stack.context, rsrc_id, curr_stack.current_traversal,
{'input_data': {}}, {'input_data': {}},
is_update, None)) is_update, None, False))
self.assertEqual(expected_calls, mock_cr.mock_calls) self.assertEqual(expected_calls, mock_cr.mock_calls)
def test_conv_empty_template_stack_update_delete(self, mock_cr): def test_conv_empty_template_stack_update_delete(self, mock_cr):
@ -352,7 +352,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
mock.call.worker_client.WorkerClient.check_resource( mock.call.worker_client.WorkerClient.check_resource(
stack.context, rsrc_id, stack.current_traversal, stack.context, rsrc_id, stack.current_traversal,
{'input_data': {}}, {'input_data': {}},
is_update, None)) is_update, None, False))
leaves = curr_stack.convergence_dependencies.leaves() leaves = curr_stack.convergence_dependencies.leaves()
for rsrc_id, is_update in leaves: for rsrc_id, is_update in leaves:
@ -360,7 +360,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
mock.call.worker_client.WorkerClient.check_resource( mock.call.worker_client.WorkerClient.check_resource(
curr_stack.context, rsrc_id, curr_stack.current_traversal, curr_stack.context, rsrc_id, curr_stack.current_traversal,
{'input_data': {}}, {'input_data': {}},
is_update, None)) is_update, None, False))
self.assertEqual(expected_calls, mock_cr.mock_calls) self.assertEqual(expected_calls, mock_cr.mock_calls)
def test_mark_complete_purges_db(self, mock_cr): def test_mark_complete_purges_db(self, mock_cr):

View File

@ -35,6 +35,7 @@ from heat.engine import service
from heat.engine import stack as parser from heat.engine import stack as parser
from heat.engine import template as templatem from heat.engine import template as templatem
from heat.objects import stack as stack_object from heat.objects import stack as stack_object
from heat.rpc import api as rpc_api
from heat.tests import common from heat.tests import common
from heat.tests.engine import tools from heat.tests.engine import tools
from heat.tests import generic_resource as generic_rsrc from heat.tests import generic_resource as generic_rsrc
@ -230,6 +231,7 @@ class StackConvergenceServiceCreateUpdateTest(common.HeatTestCase):
template=tools.string_template_five, template=tools.string_template_five,
convergence=True) convergence=True)
stack.converge = None
self.m.StubOutWithMock(templatem, 'Template') self.m.StubOutWithMock(templatem, 'Template')
self.m.StubOutWithMock(environment, 'Environment') self.m.StubOutWithMock(environment, 'Environment')
self.m.StubOutWithMock(parser, 'Stack') self.m.StubOutWithMock(parser, 'Stack')
@ -295,14 +297,16 @@ class StackConvergenceServiceCreateUpdateTest(common.HeatTestCase):
convergence=old_stack.convergence, convergence=old_stack.convergence,
current_traversal=old_stack.current_traversal, current_traversal=old_stack.current_traversal,
prev_raw_template_id=old_stack.prev_raw_template_id, prev_raw_template_id=old_stack.prev_raw_template_id,
current_deps=old_stack.current_deps).AndReturn(stack) current_deps=old_stack.current_deps,
converge=False).AndReturn(stack)
self.m.StubOutWithMock(stack, 'validate') self.m.StubOutWithMock(stack, 'validate')
stack.validate().AndReturn(None) stack.validate().AndReturn(None)
self.m.ReplayAll() self.m.ReplayAll()
api_args = {'timeout_mins': 60, 'disable_rollback': False} api_args = {'timeout_mins': 60, 'disable_rollback': False,
rpc_api.PARAM_CONVERGE: False}
result = self.man.update_stack(self.ctx, old_stack.identifier(), result = self.man.update_stack(self.ctx, old_stack.identifier(),
template, params, None, api_args) template, params, None, api_args)
self.assertTrue(old_stack.convergence) self.assertTrue(old_stack.convergence)

View File

@ -32,6 +32,7 @@ from heat.engine import template as templatem
from heat.objects import raw_template from heat.objects import raw_template
from heat.objects import stack as stack_object from heat.objects import stack as stack_object
from heat.objects import stack_lock from heat.objects import stack_lock
from heat.rpc import api as rpc_api
from heat.tests import common from heat.tests import common
from heat.tests import generic_resource as generic_rsrc from heat.tests import generic_resource as generic_rsrc
from heat.tests import utils from heat.tests import utils
@ -931,9 +932,9 @@ class WithTemplateTest(StackResourceBaseTest):
rpcc.return_value._create_stack.assert_called_once_with( rpcc.return_value._create_stack.assert_called_once_with(
self.ctx, self.ctx,
stack_name=res_name, stack_name=res_name,
args={'disable_rollback': True, args={rpc_api.PARAM_DISABLE_ROLLBACK: True,
'adopt_stack_data': adopt_data_str, rpc_api.PARAM_ADOPT_STACK_DATA: adopt_data_str,
'timeout_mins': self.timeout_mins}, rpc_api.PARAM_TIMEOUT: self.timeout_mins},
environment_files=None, environment_files=None,
stack_user_project_id='aprojectid', stack_user_project_id='aprojectid',
parent_resource_name='test', parent_resource_name='test',
@ -980,9 +981,9 @@ class WithTemplateTest(StackResourceBaseTest):
rpcc.return_value._create_stack.assert_called_once_with( rpcc.return_value._create_stack.assert_called_once_with(
self.ctx, self.ctx,
stack_name=res_name, stack_name=res_name,
args={'disable_rollback': True, args={rpc_api.PARAM_DISABLE_ROLLBACK: True,
'adopt_stack_data': adopt_data_str, rpc_api.PARAM_ADOPT_STACK_DATA: adopt_data_str,
'timeout_mins': self.timeout_mins}, rpc_api.PARAM_TIMEOUT: self.timeout_mins},
environment_files=None, environment_files=None,
stack_user_project_id='aprojectid', stack_user_project_id='aprojectid',
parent_resource_name='test', parent_resource_name='test',
@ -1025,7 +1026,8 @@ class WithTemplateTest(StackResourceBaseTest):
template=None, template=None,
params=None, params=None,
files=None, files=None,
args={'timeout_mins': self.timeout_mins}) args={rpc_api.PARAM_TIMEOUT: self.timeout_mins,
rpc_api.PARAM_CONVERGE: False})
def test_update_with_template_failure(self): def test_update_with_template_failure(self):
class StackValidationFailed_Remote(exception.StackValidationFailed): class StackValidationFailed_Remote(exception.StackValidationFailed):
@ -1061,7 +1063,8 @@ class WithTemplateTest(StackResourceBaseTest):
template=None, template=None,
params=None, params=None,
files=None, files=None,
args={'timeout_mins': self.timeout_mins}) args={rpc_api.PARAM_TIMEOUT: self.timeout_mins,
rpc_api.PARAM_CONVERGE: False})
self.assertIsNotNone(template_id.match) self.assertIsNotNone(template_id.match)
self.assertRaises(exception.NotFound, self.assertRaises(exception.NotFound,
raw_template.RawTemplate.get_by_id, raw_template.RawTemplate.get_by_id,

View File

@ -0,0 +1,7 @@
---
features:
- Add `converge` parameter for stack update (and update preview) API.
This parameter will force resources to observe the reality of resources
before actually update it. The value of this parameter can be any
boolean value. This will replace config flag `observe_on_update` in
near future.