diff --git a/api-ref/source/v1/parameters.yaml b/api-ref/source/v1/parameters.yaml index aec388b369..5667839deb 100644 --- a/api-ref/source/v1/parameters.yaml +++ b/api-ref/source/v1/parameters.yaml @@ -476,6 +476,14 @@ config_name: in: body required: true 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: description: | The date and time when the service engine was created. diff --git a/api-ref/source/v1/stacks.inc b/api-ref/source/v1/stacks.inc index 241d21377a..66b1e85d42 100644 --- a/api-ref/source/v1/stacks.inc +++ b/api-ref/source/v1/stacks.inc @@ -374,6 +374,7 @@ Request Parameters - template: template - template_url: template_url - timeout_mins: timeout_mins + - converge: converge Request Example --------------- @@ -440,6 +441,7 @@ Request Parameters - template: template - template_url: template_url - timeout_mins: timeout_mins + - converge: converge Request Example --------------- @@ -505,6 +507,7 @@ Request Parameters - template: template - template_url: template_url - timeout_mins: timeout_mins + - converge: converge Request Example --------------- @@ -575,6 +578,7 @@ Request Parameters - template: template - template_url: template_url - timeout_mins: timeout_mins + - converge: converge Request Example --------------- diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py index 1eef46e83e..11520ac09c 100644 --- a/heat/api/openstack/v1/stacks.py +++ b/heat/api/openstack/v1/stacks.py @@ -374,7 +374,7 @@ class StackController(object): formatted_stack = stacks_view.format_stack(req, result) return {'stack': formatted_stack} - def prepare_args(self, data): + def prepare_args(self, data, is_update=False): args = data.args() key = rpc_api.PARAM_TIMEOUT if key in args: @@ -382,6 +382,11 @@ class StackController(object): key = rpc_api.PARAM_TAGS if args.get(key) is not None: 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 @util.policy_enforce @@ -472,7 +477,7 @@ class StackController(object): """Update an existing stack with a new template and/or parameters.""" data = InstantiationData(body) - args = self.prepare_args(data) + args = self.prepare_args(data, is_update=True) self.rpc_client.update_stack( req.context, identity, @@ -493,7 +498,7 @@ class StackController(object): """ data = InstantiationData(body, patch=True) - args = self.prepare_args(data) + args = self.prepare_args(data, is_update=True) self.rpc_client.update_stack( req.context, identity, @@ -518,7 +523,7 @@ class StackController(object): """Preview update for existing stack with a new template/parameters.""" data = InstantiationData(body) - args = self.prepare_args(data) + args = self.prepare_args(data, is_update=True) show_nested = self._param_show_nested(req) if show_nested is not None: args[rpc_api.PARAM_SHOW_NESTED] = show_nested @@ -538,7 +543,7 @@ class StackController(object): """Preview PATCH update for existing stack.""" 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) if show_nested is not None: args['show_nested'] = show_nested diff --git a/heat/engine/api.py b/heat/engine/api.py index 08f9defd20..a6147a06ab 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -46,11 +46,10 @@ def extract_args(params): kwargs[rpc_api.PARAM_TIMEOUT] = timeout else: raise ValueError(_('Invalid timeout value %s') % timeout) - - name = rpc_api.PARAM_DISABLE_ROLLBACK - if name in params: - disable_rollback = param_utils.extract_bool(name, params[name]) - kwargs[name] = disable_rollback + for name in [rpc_api.PARAM_CONVERGE, rpc_api.PARAM_DISABLE_ROLLBACK]: + if name in params: + bool_value = param_utils.extract_bool(name, params[name]) + kwargs[name] = bool_value name = rpc_api.PARAM_SHOW_DELETED if name in params: diff --git a/heat/engine/resource.py b/heat/engine/resource.py index bc90dcb7a0..e468c3c048 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -247,6 +247,7 @@ class Resource(status.ResourceStatus): self.root_stack_id = None self._calling_engine_id = None self._atomic_key = None + self.converge = False if not self.stack.in_convergence_check: resource = stack.db_resource_get(name) @@ -1438,7 +1439,7 @@ class Resource(status.ResourceStatus): self.translate_properties(after_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: raise UpdateReplace(self) diff --git a/heat/engine/resources/stack_resource.py b/heat/engine/resources/stack_resource.py index 62c1fbb7b3..0970741427 100644 --- a/heat/engine/resources/stack_resource.py +++ b/heat/engine/resources/stack_resource.py @@ -497,7 +497,8 @@ class StackResource(resource.Resource): kwargs.update({ '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: try: diff --git a/heat/engine/service.py b/heat/engine/service.py index 789ba444e1..449cd71965 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -874,7 +874,7 @@ class EngineService(service.ServiceBase): """ # 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 # to use the existing stack template, if one is not provided. if args.get(rpc_api.PARAM_EXISTING): @@ -947,6 +947,8 @@ class EngineService(service.ServiceBase): current_stack.timeout_mins) common_params.setdefault(rpc_api.PARAM_DISABLE_ROLLBACK, current_stack.disable_rollback) + common_params.setdefault(rpc_api.PARAM_CONVERGE, + current_stack.converge) if args.get(rpc_api.PARAM_EXISTING): common_params.setdefault(rpc_api.STACK_TAGS, diff --git a/heat/engine/stack.py b/heat/engine/stack.py index 89cd46abc3..2ffd31cc02 100644 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -121,7 +121,7 @@ class Stack(collections.Mapping): nested_depth=0, strict_validate=True, convergence=False, current_traversal=None, tags=None, prev_raw_template_id=None, current_deps=None, cache_data=None, - deleted_time=None): + deleted_time=None, converge=False): """Initialise the Stack. @@ -181,6 +181,7 @@ class Stack(collections.Mapping): self._worker_client = None self._convg_deps = None self.thread_group_mgr = None + self.converge = converge # strict_validate can be used to disable value validation # in the resource properties schema, this is useful when @@ -1266,6 +1267,7 @@ class Stack(collections.Mapping): if new_stack is not None: self.disable_rollback = new_stack.disable_rollback self.timeout_mins = new_stack.timeout_mins + self.converge = new_stack.converge self.defn = new_stack.defn self._set_param_stackid() @@ -1345,7 +1347,8 @@ class Stack(collections.Mapping): self.worker_client.check_resource(self.context, rsrc_id, self.current_traversal, input_data, is_update, - self.adopt_stack_data) + self.adopt_stack_data, + self.converge) if scheduler.ENABLE_SLEEP: eventlet.sleep(1) diff --git a/heat/engine/update.py b/heat/engine/update.py index 156eeb49af..3bd96cc3ad 100644 --- a/heat/engine/update.py +++ b/heat/engine/update.py @@ -213,6 +213,7 @@ class StackUpdate(object): existing_res.stack) existing_res.stack.resources[existing_res.name] = substitute existing_res = substitute + existing_res.converge = self.new_stack.converge return existing_res.update(new_snippet, existing_snippet, prev_resource=prev_res) diff --git a/heat/engine/worker.py b/heat/engine/worker.py index 2987deb1bf..d625d1b082 100644 --- a/heat/engine/worker.py +++ b/heat/engine/worker.py @@ -61,7 +61,7 @@ class WorkerService(object): or expect replies from these messages. """ - RPC_API_VERSION = '1.3' + RPC_API_VERSION = '1.4' def __init__(self, host, @@ -161,7 +161,7 @@ class WorkerService(object): @context.request_context @log_exceptions 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. 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: return + rsrc.converge = converge + msg_queue = eventlet.queue.LightQueue() try: self.thread_group_mgr.add_msg_queue(stack.id, msg_queue) diff --git a/heat/rpc/api.py b/heat/rpc/api.py index d2d2dd4cfd..bffe783d73 100644 --- a/heat/rpc/api.py +++ b/heat/rpc/api.py @@ -20,14 +20,14 @@ PARAM_KEYS = ( PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT, PARAM_NESTED_DEPTH, PARAM_TAGS, PARAM_SHOW_HIDDEN, PARAM_TAGS_ANY, 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', 'show_deleted', 'show_nested', 'existing', 'clear_parameters', 'global_tenant', 'limit', 'nested_depth', 'tags', 'show_hidden', 'tags_any', 'not_tags', 'not_tags_any', 'template_type', 'with_detail', - 'resolve_outputs', 'ignore_errors' + 'resolve_outputs', 'ignore_errors', 'converge' ) STACK_KEYS = ( diff --git a/heat/rpc/worker_client.py b/heat/rpc/worker_client.py index d0256492e8..5faeb9afb0 100644 --- a/heat/rpc/worker_client.py +++ b/heat/rpc/worker_client.py @@ -28,6 +28,7 @@ class WorkerClient(object): 1.1 - Added check_resource. 1.2 - Add adopt data argument to check_resource. 1.3 - Added cancel_check_resource API. + 1.4 - Add converge argument to check_resource """ BASE_RPC_API_VERSION = '1.0' @@ -50,13 +51,16 @@ class WorkerClient(object): client.cast(ctxt, method, **kwargs) 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.make_msg( 'check_resource', resource_id=resource_id, current_traversal=current_traversal, data=data, - is_update=is_update, adopt_stack_data=adopt_stack_data), - version='1.2') + is_update=is_update, adopt_stack_data=adopt_stack_data, + converge=converge + ), + version='1.4') def cancel_check_resource(self, ctxt, stack_id, engine_id): """Send check-resource cancel message. diff --git a/heat/tests/convergence/framework/worker_wrapper.py b/heat/tests/convergence/framework/worker_wrapper.py index 14d9061e4f..042bc9e07d 100644 --- a/heat/tests/convergence/framework/worker_wrapper.py +++ b/heat/tests/convergence/framework/worker_wrapper.py @@ -28,13 +28,14 @@ class Worker(message_processor.MessageProcessor): @message_processor.asynchronous def check_resource(self, ctxt, resource_id, current_traversal, data, - is_update, adopt_stack_data): + is_update, adopt_stack_data, converge=False): worker.WorkerService("fake_host", "fake_topic", "fake_engine", mock.Mock()).check_resource( ctxt, resource_id, current_traversal, data, is_update, - adopt_stack_data) + adopt_stack_data, + converge) def stop_all_workers(self, current_stack): pass diff --git a/heat/tests/engine/service/test_stack_update.py b/heat/tests/engine/service/test_stack_update.py index d89e2b8ea8..c96f0bb74a 100644 --- a/heat/tests/engine/service/test_stack_update.py +++ b/heat/tests/engine/service/test_stack_update.py @@ -71,7 +71,7 @@ class ServiceStackUpdateTest(common.HeatTestCase): self.patchobject(eventlet.queue, 'LightQueue', return_value=msgq_mock) # 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(), template, params, None, api_args) @@ -97,7 +97,9 @@ class ServiceStackUpdateTest(common.HeatTestCase): tenant_id='test_tenant_id', timeout_mins=60, user_creds_id=u'1', - username='test_username') + username='test_username', + converge=True + ) mock_load.assert_called_once_with(self.ctx, stack=s) mock_validate.assert_called_once_with() @@ -127,7 +129,8 @@ class ServiceStackUpdateTest(common.HeatTestCase): # Test environment_files = ['env_1'] self.man.update_stack(self.ctx, old_stack.identifier(), - template, params, None, {}, + template, params, None, + {rpc_api.PARAM_CONVERGE: False}, environment_files=environment_files) # Verify @@ -160,7 +163,7 @@ class ServiceStackUpdateTest(common.HeatTestCase): self.patchobject(eventlet.queue, 'LightQueue', return_value=msgq_mock) # 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(), None, None, None, api_args, template_id=tmpl_id) @@ -186,7 +189,9 @@ class ServiceStackUpdateTest(common.HeatTestCase): tenant_id='test_tenant_id', 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_validate.assert_called_once_with() @@ -202,7 +207,8 @@ class ServiceStackUpdateTest(common.HeatTestCase): 'parameters': {'newparam': 123}, 'resource_registry': {'resources': {}}} 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) stk = tools.get_stack(stack_name, self.ctx, with_params=True) @@ -265,7 +271,8 @@ resources: 'parameters': {}, 'resource_registry': {'resources': {}}} 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: stk.update = mock.Mock() @@ -297,7 +304,8 @@ resources: 'resource_registry': {'resources': {}}} api_args = {rpc_api.PARAM_TIMEOUT: 60, 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['parameters']['removeme'] = {'type': 'string'} @@ -383,7 +391,8 @@ resources: update_files = {'newfoo2.yaml': 'newfoo', 'myother.yaml': 'myother'} 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) stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params, @@ -439,7 +448,8 @@ resources: 'parameters': {}, 'resource_registry': {}} 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) stk = utils.parse_stack(t, stack_name=stack_name, params=intial_params) @@ -489,7 +499,8 @@ resources: # do update result = self.man.update_stack(self.ctx, old_stack.identifier(), - template, params, None, {}) + template, params, None, + {rpc_api.PARAM_CONVERGE: False}) # assertions self.assertEqual(old_stack.identifier(), result) @@ -511,7 +522,9 @@ resources: strict_validate=True, tenant_id='test_tenant_id', timeout_mins=1, user_creds_id=u'1', - username='test_username') + username='test_username', + converge=False + ) def test_stack_cancel_update_same_engine(self): stack_name = 'service_update_stack_test_cancel_same_engine' @@ -619,7 +632,7 @@ resources: # do update 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(), template, params, None, api_args) @@ -642,7 +655,9 @@ resources: stack_user_project_id='1234', strict_validate=True, tenant_id='test_tenant_id', 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_validate.assert_called_once_with() @@ -678,7 +693,8 @@ resources: return_value=old_stack) result = self.man.update_stack(self.ctx, create_stack.identifier(), - tpl, {}, None, {}) + tpl, {}, None, + {rpc_api.PARAM_CONVERGE: False}) old_stack._persist_state() self.assertEqual((old_stack.UPDATE, old_stack.COMPLETE), @@ -710,7 +726,7 @@ resources: ex = self.assertRaises(dispatcher.ExpectedException, self.man.update_stack, self.ctx, old_stack.identifier(), tpl, params, - None, {}) + None, {rpc_api.PARAM_CONVERGE: False}) self.assertEqual(exception.RequestLimitExceeded, ex.exc_info[0]) self.assertIn(exception.StackResourceLimitExceeded.msg_fmt, six.text_type(ex.exc_info[1])) @@ -738,7 +754,7 @@ resources: mock_validate = self.patchobject(stk, 'validate', side_effect=ex_expected) # do update - api_args = {'timeout_mins': 60} + api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: False} ex = self.assertRaises(dispatcher.ExpectedException, self.man.update_stack, self.ctx, old_stack.identifier(), @@ -758,7 +774,9 @@ resources: stack_user_project_id='1234', strict_validate=True, tenant_id='test_tenant_id', 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_validate.assert_called_once_with() @@ -771,7 +789,7 @@ resources: ex = self.assertRaises(dispatcher.ExpectedException, self.man.update_stack, self.ctx, stk.identifier(), template, - params, None, {}) + params, None, {rpc_api.PARAM_CONVERGE: False}) self.assertEqual(exception.EntityNotFound, ex.exc_info[0]) def test_stack_update_no_credentials(self): @@ -798,7 +816,7 @@ resources: mock_env = self.patchobject(environment, 'Environment', return_value=stk.env) - api_args = {'timeout_mins': 60} + api_args = {'timeout_mins': 60, rpc_api.PARAM_CONVERGE: False} ex = self.assertRaises(dispatcher.ExpectedException, self.man.update_stack, self.ctx, stk.identifier(), @@ -820,14 +838,17 @@ resources: stack_user_project_id='1234', 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) 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} + rpc_api.PARAM_EXISTING: True, + rpc_api.PARAM_CONVERGE: False} 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 @@ -859,7 +880,8 @@ resources: '''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} + rpc_api.PARAM_EXISTING: True, + rpc_api.PARAM_CONVERGE: False} 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 @@ -912,7 +934,7 @@ parameters: self.man.update_stack, self.ctx, old_stack.identifier(), old_stack.t.t, params, - None, {}) + None, {rpc_api.PARAM_CONVERGE: False}) self.assertEqual(exception.ImmutableParameterModified, exc.exc_info[0]) self.assertEqual('The following parameters are immutable and may not ' 'be updated: param1', exc.exc_info[1].message) @@ -948,7 +970,7 @@ parameters: params = {'param1': 'bar'} result = self.man.update_stack(self.ctx, old_stack.identifier(), templatem.Template(template), params, - None, {}) + None, {rpc_api.PARAM_CONVERGE: False}) self.assertEqual(s.id, result['stack_id']) def test_update_immutable_parameter_same_value(self): @@ -982,7 +1004,7 @@ parameters: params = {'param1': 'foo'} result = self.man.update_stack(self.ctx, old_stack.identifier(), templatem.Template(template), params, - None, {}) + None, {rpc_api.PARAM_CONVERGE: False}) self.assertEqual(s.id, result['stack_id']) @@ -1057,7 +1079,7 @@ resources: return_value=None) # 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( self.ctx, old_stack.identifier(), @@ -1073,7 +1095,9 @@ resources: 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') + user_creds_id=u'1', username='test_username', + converge=False + ) mock_load.assert_called_once_with(self.ctx, stack=s) mock_tmpl.assert_called_once_with(new_template, files=None) mock_env.assert_called_once_with(params) diff --git a/heat/tests/engine/test_engine_worker.py b/heat/tests/engine/test_engine_worker.py index 93781179de..eeae9e3261 100644 --- a/heat/tests/engine/test_engine_worker.py +++ b/heat/tests/engine/test_engine_worker.py @@ -29,7 +29,7 @@ from heat.tests import utils class WorkerServiceTest(common.HeatTestCase): def test_make_sure_rpc_version(self): self.assertEqual( - '1.3', + '1.4', worker.WorkerService.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 ' diff --git a/heat/tests/test_convg_stack.py b/heat/tests/test_convg_stack.py index b365073426..bb7adce407 100644 --- a/heat/tests/test_convg_stack.py +++ b/heat/tests/test_convg_stack.py @@ -72,7 +72,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): mock.call.worker_client.WorkerClient.check_resource( stack.context, rsrc_id, stack.current_traversal, {'input_data': {}}, - is_update, None)) + is_update, None, False)) self.assertEqual(expected_calls, mock_cr.mock_calls) 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( stack.context, rsrc_id, stack.current_traversal, {'input_data': {}}, - is_update, None)) + is_update, None, False)) self.assertEqual(expected_calls, mock_cr.mock_calls) def _mock_convg_db_update_requires(self): @@ -269,7 +269,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): mock.call.worker_client.WorkerClient.check_resource( stack.context, rsrc_id, stack.current_traversal, {'input_data': {}}, - is_update, None)) + is_update, None, False)) leaves = curr_stack.convergence_dependencies.leaves() for rsrc_id, is_update in leaves: @@ -277,7 +277,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): mock.call.worker_client.WorkerClient.check_resource( curr_stack.context, rsrc_id, curr_stack.current_traversal, {'input_data': {}}, - is_update, None)) + is_update, None, False)) self.assertEqual(expected_calls, mock_cr.mock_calls) 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( stack.context, rsrc_id, stack.current_traversal, {'input_data': {}}, - is_update, None)) + is_update, None, False)) leaves = curr_stack.convergence_dependencies.leaves() for rsrc_id, is_update in leaves: @@ -360,7 +360,7 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): mock.call.worker_client.WorkerClient.check_resource( curr_stack.context, rsrc_id, curr_stack.current_traversal, {'input_data': {}}, - is_update, None)) + is_update, None, False)) self.assertEqual(expected_calls, mock_cr.mock_calls) def test_mark_complete_purges_db(self, mock_cr): diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index de5eb55cdd..743e97e059 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -35,6 +35,7 @@ from heat.engine import service from heat.engine import stack as parser 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 generic_resource as generic_rsrc @@ -230,6 +231,7 @@ class StackConvergenceServiceCreateUpdateTest(common.HeatTestCase): template=tools.string_template_five, convergence=True) + stack.converge = None self.m.StubOutWithMock(templatem, 'Template') self.m.StubOutWithMock(environment, 'Environment') self.m.StubOutWithMock(parser, 'Stack') @@ -295,14 +297,16 @@ class StackConvergenceServiceCreateUpdateTest(common.HeatTestCase): convergence=old_stack.convergence, current_traversal=old_stack.current_traversal, 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') stack.validate().AndReturn(None) 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(), template, params, None, api_args) self.assertTrue(old_stack.convergence) diff --git a/heat/tests/test_stack_resource.py b/heat/tests/test_stack_resource.py index fab4e570c7..64ece0c0d8 100644 --- a/heat/tests/test_stack_resource.py +++ b/heat/tests/test_stack_resource.py @@ -32,6 +32,7 @@ from heat.engine import template as templatem from heat.objects import raw_template from heat.objects import stack as stack_object from heat.objects import stack_lock +from heat.rpc import api as rpc_api from heat.tests import common from heat.tests import generic_resource as generic_rsrc from heat.tests import utils @@ -931,9 +932,9 @@ class WithTemplateTest(StackResourceBaseTest): rpcc.return_value._create_stack.assert_called_once_with( self.ctx, stack_name=res_name, - args={'disable_rollback': True, - 'adopt_stack_data': adopt_data_str, - 'timeout_mins': self.timeout_mins}, + args={rpc_api.PARAM_DISABLE_ROLLBACK: True, + rpc_api.PARAM_ADOPT_STACK_DATA: adopt_data_str, + rpc_api.PARAM_TIMEOUT: self.timeout_mins}, environment_files=None, stack_user_project_id='aprojectid', parent_resource_name='test', @@ -980,9 +981,9 @@ class WithTemplateTest(StackResourceBaseTest): rpcc.return_value._create_stack.assert_called_once_with( self.ctx, stack_name=res_name, - args={'disable_rollback': True, - 'adopt_stack_data': adopt_data_str, - 'timeout_mins': self.timeout_mins}, + args={rpc_api.PARAM_DISABLE_ROLLBACK: True, + rpc_api.PARAM_ADOPT_STACK_DATA: adopt_data_str, + rpc_api.PARAM_TIMEOUT: self.timeout_mins}, environment_files=None, stack_user_project_id='aprojectid', parent_resource_name='test', @@ -1025,7 +1026,8 @@ class WithTemplateTest(StackResourceBaseTest): template=None, params=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): class StackValidationFailed_Remote(exception.StackValidationFailed): @@ -1061,7 +1063,8 @@ class WithTemplateTest(StackResourceBaseTest): template=None, params=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.assertRaises(exception.NotFound, raw_template.RawTemplate.get_by_id, diff --git a/releasenotes/notes/converge-flag-for-stack-update-e0e92a7fe232f10f.yaml b/releasenotes/notes/converge-flag-for-stack-update-e0e92a7fe232f10f.yaml new file mode 100644 index 0000000000..e49a3a9c7e --- /dev/null +++ b/releasenotes/notes/converge-flag-for-stack-update-e0e92a7fe232f10f.yaml @@ -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.