diff --git a/heat/engine/resource.py b/heat/engine/resource.py index bc58591208..2491e62906 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -259,6 +259,7 @@ class Resource(object): db_res = resource_objects.Resource.get_obj(context, resource_id) # TODO(sirushtim): Load stack from cache stack = stack_mod.Stack.load(context, db_res.stack_id) + stack.adopt_stack_data = data.get('adopt_stack_data') # NOTE(sirushtim): Because on delete/cleanup operations, we simply # update with another template, the stack object won't have the # template of the previous stack-run. @@ -676,7 +677,11 @@ class Resource(object): set(data[u'id'] for data in resource_data.values() if data is not None) ) - runner = scheduler.TaskRunner(self.create) + adopt_data = self.stack._adopt_kwargs(self) + if adopt_data['resource_data'] is None: + runner = scheduler.TaskRunner(self.create) + else: + runner = scheduler.TaskRunner(self.adopt, **adopt_data) runner() @scheduler.wrappertask diff --git a/heat/engine/service.py b/heat/engine/service.py index 939dff17f6..f0b7d5c794 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -688,7 +688,6 @@ class EngineService(service.Service): six.text_type(ex)) def _stack_create(stack): - _create_stack_user(stack) # Create/Adopt a stack, and create the periodic task if successful if stack.adopt_stack_data: stack.adopt() @@ -710,14 +709,15 @@ class EngineService(service.Service): nested_depth, user_creds_id, stack_user_project_id, convergence, parent_resource_name) - # once validations are done - # if convergence is enabled, take convergence path + stack.store() + _create_stack_user(stack) if convergence: - # TODO(later): call _create_stack_user(stack) - # call stack.converge_stack(template=stack.t, action=stack.CREATE) raise exception.NotSupported(feature=_('Convergence engine')) + action = stack.CREATE + if stack.adopt_stack_data: + action = stack.ADOPT + stack.converge_stack(template=stack.t, action=action) else: - stack.store() self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id, _stack_create, stack) @@ -782,8 +782,6 @@ class EngineService(service.Service): self._validate_deferred_auth_context(cnxt, updated_stack) updated_stack.validate() - # Once all the validations are done - # if convergence is enabled, take the convergence path if current_kwargs['convergence']: current_stack.converge_stack(template=tmpl, new_stack=updated_stack) diff --git a/heat/engine/stack.py b/heat/engine/stack.py index 7facc45f13..118b903f30 100755 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -939,7 +939,7 @@ class Stack(collections.Mapping): """ Updates the stack and triggers convergence for resources """ - if action is not self.CREATE: + if action not in [self.CREATE, self.ADOPT]: # no back-up template for create action self.prev_raw_template_id = getattr(self.t, 'id', None) @@ -995,9 +995,11 @@ class Stack(collections.Mapping): LOG.info(_LI("Triggering resource %(rsrc_id)s " "for %(is_update)s update"), {'rsrc_id': rsrc_id, 'is_update': is_update}) + input_data = {'input_data': {}, + 'adopt_stack_data': self.adopt_stack_data} self.worker_client.check_resource(self.context, rsrc_id, self.current_traversal, - {}, is_update) + input_data, is_update) def _get_best_existing_rsrc_db(self, rsrc_name): candidate = None diff --git a/heat/engine/worker.py b/heat/engine/worker.py index 28b313d273..f1d9d1a076 100644 --- a/heat/engine/worker.py +++ b/heat/engine/worker.py @@ -123,7 +123,7 @@ class WorkerService(service.Service): stack.state_set(stack.action, stack.FAILED, failure_reason) if (not stack.disable_rollback and - stack.action in (stack.CREATE, stack.UPDATE)): + stack.action in (stack.CREATE, stack.ADOPT, stack.UPDATE)): self._trigger_rollback(cnxt, stack) else: stack.purge_db() @@ -137,11 +137,13 @@ class WorkerService(service.Service): The node may be associated with either an update or a cleanup of its associated resource. ''' + adopt_data = data.get('adopt_stack_data') data = dict(sync_point.deserialize_input_data(data)) try: cache_data = {in_data.get( 'name'): in_data for in_data in data.values() if in_data is not None} + cache_data['adopt_stack_data'] = adopt_data rsrc, stack = resource.Resource.load(cnxt, resource_id, cache_data) except (exception.ResourceNotFound, exception.NotFound): return diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 454ba942e7..207e306a7a 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -212,7 +212,8 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): for rsrc_id, is_update in leaves: expected_calls.append( mock.call.worker_client.WorkerClient.check_resource( - stack.context, rsrc_id, stack.current_traversal, {}, + stack.context, rsrc_id, stack.current_traversal, + {'input_data': {}, 'adopt_stack_data': None}, is_update)) self.assertEqual(expected_calls, mock_cr.mock_calls) @@ -268,7 +269,8 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): for rsrc_id, is_update in leaves: expected_calls.append( mock.call.worker_client.WorkerClient.check_resource( - stack.context, rsrc_id, stack.current_traversal, {}, + stack.context, rsrc_id, stack.current_traversal, + {'input_data': {}, 'adopt_stack_data': None}, is_update)) self.assertEqual(expected_calls, mock_cr.mock_calls) @@ -403,7 +405,8 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): for rsrc_id, is_update in leaves: expected_calls.append( mock.call.worker_client.WorkerClient.check_resource( - stack.context, rsrc_id, stack.current_traversal, {}, + stack.context, rsrc_id, stack.current_traversal, + {'input_data': {}, 'adopt_stack_data': None}, is_update)) leaves = curr_stack.convergence_dependencies.leaves() @@ -411,7 +414,8 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): expected_calls.append( mock.call.worker_client.WorkerClient.check_resource( curr_stack.context, rsrc_id, curr_stack.current_traversal, - {}, is_update)) + {'input_data': {}, 'adopt_stack_data': None}, + is_update)) self.assertEqual(expected_calls, mock_cr.mock_calls) def test_conv_empty_template_stack_update_delete(self, mock_cr): @@ -482,7 +486,8 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): for rsrc_id, is_update in leaves: expected_calls.append( mock.call.worker_client.WorkerClient.check_resource( - stack.context, rsrc_id, stack.current_traversal, {}, + stack.context, rsrc_id, stack.current_traversal, + {'input_data': {}, 'adopt_stack_data': None}, is_update)) leaves = curr_stack.convergence_dependencies.leaves() @@ -490,7 +495,8 @@ class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase): expected_calls.append( mock.call.worker_client.WorkerClient.check_resource( curr_stack.context, rsrc_id, curr_stack.current_traversal, - {}, is_update)) + {'input_data': {}, 'adopt_stack_data': None}, + is_update)) self.assertEqual(expected_calls, mock_cr.mock_calls) def test_mark_complete_purges_db(self, mock_cr): diff --git a/heat/tests/test_resource.py b/heat/tests/test_resource.py index 592da4220b..fbee242717 100644 --- a/heat/tests/test_resource.py +++ b/heat/tests/test_resource.py @@ -1430,6 +1430,7 @@ class ResourceTest(common.HeatTestCase): def test_create_convergence(self, mock_create): tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo') res = generic_rsrc.GenericResource('test_res', tmpl, self.stack) + res.action = res.CREATE res._store() self._assert_resource_lock(res.id, None, None) res_data = {(1, True): {u'id': 1, u'name': 'A', 'attrs': {}}, @@ -1459,6 +1460,24 @@ class ResourceTest(common.HeatTestCase): self.assertItemsEqual([5, 3], res.requires) self._assert_resource_lock(res.id, None, 2) + @mock.patch.object(resource.Resource, 'adopt') + def test_adopt_convergence(self, mock_adopt): + tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo') + res = generic_rsrc.GenericResource('test_res', tmpl, self.stack) + res.action = res.ADOPT + res._store() + self.stack.adopt_stack_data = {'resources': {'test_res': { + 'resource_id': 'fluffy'}}} + self._assert_resource_lock(res.id, None, None) + res_data = {(1, True): {u'id': 5, u'name': 'A', 'attrs': {}}, + (2, True): {u'id': 3, u'name': 'B', 'attrs': {}}} + res.create_convergence(res_data, 'engine-007') + + mock_adopt.assert_called_once_with( + resource_data={'resource_id': 'fluffy'}) + self.assertItemsEqual([5, 3], res.requires) + self._assert_resource_lock(res.id, None, 2) + @mock.patch.object(resource.Resource, 'update') def test_update_convergence(self, mock_update): tmpl = rsrc_defn.ResourceDefinition('test_res',