text: #
  #    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 mock
  from oslo_config import cfg
  
  from heat.common import template_format
  from heat.engine import environment
  from heat.engine import resource as res
  from heat.engine import stack as parser
  from heat.engine import template as templatem
  from heat.objects import raw_template as raw_template_object
  from heat.objects import resource as resource_objects
  from heat.objects import snapshot as snapshot_objects
  from heat.objects import stack as stack_object
  from heat.objects import sync_point as sync_point_object
  from heat.rpc import worker_client
  from heat.tests import common
  from heat.tests.engine import tools
  from heat.tests import utils
  
  
  @mock.patch.object(worker_client.WorkerClient, 'check_resource')
  class StackConvergenceCreateUpdateDeleteTest(common.HeatTestCase):
      def setUp(self):
          super(StackConvergenceCreateUpdateDeleteTest, self).setUp()
          cfg.CONF.set_override('convergence_engine', True, enforce_type=True)
          self.stack = None
  
      @mock.patch.object(parser.Stack, 'mark_complete')
      def test_converge_empty_template(self, mock_mc, mock_cr):
          empty_tmpl = templatem.Template.create_empty_template()
          stack = parser.Stack(utils.dummy_context(), 'empty_tmpl_stack',
                               empty_tmpl, convergence=True)
          stack.store()
          stack.converge_stack(template=stack.t, action=stack.CREATE)
          self.assertFalse(mock_cr.called)
          mock_mc.assert_called_once_with()
  
      def test_conv_wordpress_single_instance_stack_create(self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  convergence=True)
          stack.store()  # usually, stack is stored before converge is called
  
          stack.converge_stack(template=stack.t, action=stack.CREATE)
          self.assertIsNone(stack.ext_rsrcs_db)
          self.assertEqual('Dependencies([((1, True), None)])',
                           repr(stack.convergence_dependencies))
  
          stack_db = stack_object.Stack.get_by_id(stack.context, stack.id)
          self.assertIsNotNone(stack_db.current_traversal)
          self.assertIsNotNone(stack_db.raw_template_id)
  
          self.assertIsNone(stack_db.prev_raw_template_id)
  
          self.assertTrue(stack_db.convergence)
          self.assertEqual({'edges': [[[1, True], None]]}, stack_db.current_deps)
          leaves = stack.convergence_dependencies.leaves()
          expected_calls = []
          for rsrc_id, is_update in leaves:
              expected_calls.append(
                  mock.call.worker_client.WorkerClient.check_resource(
                      stack.context, rsrc_id, stack.current_traversal,
                      {'input_data': {}},
                      is_update, None))
          self.assertEqual(expected_calls, mock_cr.mock_calls)
  
      def test_conv_string_five_instance_stack_create(self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          stack.converge_stack(template=stack.t, action=stack.CREATE)
          self.assertIsNone(stack.ext_rsrcs_db)
          self.assertEqual('Dependencies(['
                           '((1, True), (3, True)), '
                           '((2, True), (3, True)), '
                           '((3, True), (4, True)), '
                           '((3, True), (5, True))])',
                           repr(stack.convergence_dependencies))
  
          stack_db = stack_object.Stack.get_by_id(stack.context, stack.id)
          self.assertIsNotNone(stack_db.current_traversal)
          self.assertIsNotNone(stack_db.raw_template_id)
          self.assertIsNone(stack_db.prev_raw_template_id)
          self.assertTrue(stack_db.convergence)
          self.assertEqual(sorted([[[3, True], [5, True]],    # C, A
                                   [[3, True], [4, True]],    # C, B
                                   [[1, True], [3, True]],    # E, C
                                   [[2, True], [3, True]]]),  # D, C
                           sorted(stack_db.current_deps['edges']))
  
          # check if needed_by is stored properly
          expected_needed_by = {'A': [3], 'B': [3],
                                'C': [1, 2],
                                'D': [], 'E': []}
          rsrcs_db = resource_objects.Resource.get_all_by_stack(
              stack_db._context, stack_db.id
          )
          self.assertEqual(5, len(rsrcs_db))
          for rsrc_name, rsrc_obj in rsrcs_db.items():
              self.assertEqual(sorted(expected_needed_by[rsrc_name]),
                               sorted(rsrc_obj.needed_by))
              self.assertEqual(stack_db.raw_template_id,
                               rsrc_obj.current_template_id)
  
          # check if sync_points were stored
          for entity_id in [5, 4, 3, 2, 1, stack_db.id]:
              sync_point = sync_point_object.SyncPoint.get_by_key(
                  stack_db._context, entity_id, stack_db.current_traversal, True
              )
              self.assertIsNotNone(sync_point)
              self.assertEqual(stack_db.id, sync_point.stack_id)
  
          leaves = stack.convergence_dependencies.leaves()
          expected_calls = []
          for rsrc_id, is_update in leaves:
              expected_calls.append(
                  mock.call.worker_client.WorkerClient.check_resource(
                      stack.context, rsrc_id, stack.current_traversal,
                      {'input_data': {}},
                      is_update, None))
          self.assertEqual(expected_calls, mock_cr.mock_calls)
  
      def _mock_convg_db_update_requires(self):
          """Updates requires column of resources.
  
          Required for testing the generation of convergence dependency graph
          on an update.
          """
          requires = dict()
          for rsrc_id, is_update in self.stack.convergence_dependencies:
              if is_update:
                  reqs = self.stack.convergence_dependencies.requires((
                      rsrc_id, is_update))
                  requires[rsrc_id] = list({id for id, is_update in reqs})
  
          rsrcs_db = resource_objects.Resource.get_all_active_by_stack(
              self.stack.context, self.stack.id)
  
          for rsrc_id, rsrc in rsrcs_db.items():
              if rsrc.id in requires:
                  rsrcs_db[rsrc_id].requires = requires[rsrc.id]
          return rsrcs_db
  
      def test_conv_string_five_instance_stack_update(self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          # create stack
          stack.converge_stack(template=stack.t, action=stack.CREATE)
  
          curr_stack_db = stack_object.Stack.get_by_id(stack.context, stack.id)
          curr_stack = parser.Stack.load(curr_stack_db._context,
                                         stack=curr_stack_db)
          # update stack with new template
          t2 = template_format.parse(tools.string_template_five_update)
          template2 = templatem.Template(
              t2, env=environment.Environment({'KeyName2': 'test2'}))
  
          # on our previous create_complete, worker would have updated the
          # rsrc.requires. Mock the same behavior here.
          self.stack = stack
          with mock.patch.object(
                  parser.Stack, 'db_active_resources_get',
                  side_effect=self._mock_convg_db_update_requires):
              curr_stack.converge_stack(template=template2, action=stack.UPDATE)
  
          self.assertIsNotNone(curr_stack.ext_rsrcs_db)
          self.assertEqual('Dependencies(['
                           '((3, False), (1, False)), '
                           '((3, False), (2, False)), '
                           '((4, False), (3, False)), '
                           '((4, False), (4, True)), '
                           '((5, False), (3, False)), '
                           '((5, False), (5, True)), '
                           '((6, True), (8, True)), '
                           '((7, True), (8, True)), '
                           '((8, True), (4, True)), '
                           '((8, True), (5, True))])',
                           repr(curr_stack.convergence_dependencies))
  
          stack_db = stack_object.Stack.get_by_id(curr_stack.context,
                                                  curr_stack.id)
          self.assertIsNotNone(stack_db.raw_template_id)
          self.assertIsNotNone(stack_db.current_traversal)
          self.assertIsNotNone(stack_db.prev_raw_template_id)
          self.assertTrue(stack_db.convergence)
          self.assertEqual(sorted([[[7, True], [8, True]],
                                   [[8, True], [5, True]],
                                   [[8, True], [4, True]],
                                   [[6, True], [8, True]],
                                   [[3, False], [2, False]],
                                   [[3, False], [1, False]],
                                   [[5, False], [3, False]],
                                   [[5, False], [5, True]],
                                   [[4, False], [3, False]],
                                   [[4, False], [4, True]]]),
                           sorted(stack_db.current_deps['edges']))
          '''
          To visualize:
  
          G(7, True)       H(6, True)
              \                 /
                \             /           B(4, False)   A(5, False)
                  \         /               /       \  /    /
                    \     /            /           /
                 F(8, True)       /             /     \  /
                      /  \    /             /     C(3, False)
                    /      \            /            /    \
                  /     /    \      /
                /    /         \ /                /          \
          B(4, True)      A(5, True)       D(2, False)    E(1, False)
  
          Leaves are at the bottom
          '''
  
          # check if needed_by are stored properly
          # For A & B:
          # needed_by=C, F
  
          expected_needed_by = {'A': [3, 8], 'B': [3, 8],
                                'C': [1, 2],
                                'D': [], 'E': [],
                                'F': [6, 7],
                                'G': [], 'H': []}
          rsrcs_db = resource_objects.Resource.get_all_by_stack(
              stack_db._context, stack_db.id
          )
          self.assertEqual(8, len(rsrcs_db))
          for rsrc_name, rsrc_obj in rsrcs_db.items():
              self.assertEqual(sorted(expected_needed_by[rsrc_name]),
                               sorted(rsrc_obj.needed_by))
  
          # check if sync_points are created for forward traversal
          # [F, H, G, A, B, Stack]
          for entity_id in [8, 7, 6, 5, 4, stack_db.id]:
              sync_point = sync_point_object.SyncPoint.get_by_key(
                  stack_db._context, entity_id, stack_db.current_traversal, True
              )
              self.assertIsNotNone(sync_point)
              self.assertEqual(stack_db.id, sync_point.stack_id)
  
          # check if sync_points are created for cleanup traversal
          # [A, B, C, D, E]
          for entity_id in [5, 4, 3, 2, 1]:
              sync_point = sync_point_object.SyncPoint.get_by_key(
                  stack_db._context, entity_id, stack_db.current_traversal, False
              )
              self.assertIsNotNone(sync_point)
              self.assertEqual(stack_db.id, sync_point.stack_id)
  
          leaves = stack.convergence_dependencies.leaves()
          expected_calls = []
          for rsrc_id, is_update in leaves:
              expected_calls.append(
                  mock.call.worker_client.WorkerClient.check_resource(
                      stack.context, rsrc_id, stack.current_traversal,
                      {'input_data': {}},
                      is_update, None))
  
          leaves = curr_stack.convergence_dependencies.leaves()
          for rsrc_id, is_update in leaves:
              expected_calls.append(
                  mock.call.worker_client.WorkerClient.check_resource(
                      curr_stack.context, rsrc_id, curr_stack.current_traversal,
                      {'input_data': {}},
                      is_update, None))
          self.assertEqual(expected_calls, mock_cr.mock_calls)
  
      def test_conv_empty_template_stack_update_delete(self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          # create stack
          stack.converge_stack(template=stack.t, action=stack.CREATE)
  
          # update stack with new template
          template2 = templatem.Template.create_empty_template(
              version=stack.t.version)
  
          curr_stack_db = stack_object.Stack.get_by_id(stack.context, stack.id)
          curr_stack = parser.Stack.load(curr_stack_db._context,
                                         stack=curr_stack_db)
          # on our previous create_complete, worker would have updated the
          # rsrc.requires. Mock the same behavior here.
          self.stack = stack
          with mock.patch.object(
                  parser.Stack, 'db_active_resources_get',
                  side_effect=self._mock_convg_db_update_requires):
              curr_stack.converge_stack(template=template2, action=stack.DELETE)
  
          self.assertIsNotNone(curr_stack.ext_rsrcs_db)
          self.assertEqual('Dependencies(['
                           '((3, False), (1, False)), '
                           '((3, False), (2, False)), '
                           '((4, False), (3, False)), '
                           '((5, False), (3, False))])',
                           repr(curr_stack.convergence_dependencies))
  
          stack_db = stack_object.Stack.get_by_id(curr_stack.context,
                                                  curr_stack.id)
          self.assertIsNotNone(stack_db.current_traversal)
          self.assertIsNotNone(stack_db.prev_raw_template_id)
          self.assertEqual(sorted([[[3, False], [2, False]],
                                   [[3, False], [1, False]],
                                   [[5, False], [3, False]],
                                   [[4, False], [3, False]]]),
                           sorted(stack_db.current_deps['edges']))
  
          expected_needed_by = {'A': [3], 'B': [3],
                                'C': [1, 2],
                                'D': [], 'E': []}
          rsrcs_db = resource_objects.Resource.get_all_by_stack(
              stack_db._context, stack_db.id
          )
          self.assertEqual(5, len(rsrcs_db))
          for rsrc_name, rsrc_obj in rsrcs_db.items():
              self.assertEqual(sorted(expected_needed_by[rsrc_name]),
                               sorted(rsrc_obj.needed_by))
  
          # check if sync_points are created for cleanup traversal
          # [A, B, C, D, E, Stack]
          for entity_id in [5, 4, 3, 2, 1, stack_db.id]:
              is_update = False
              if entity_id == stack_db.id:
                  is_update = True
              sync_point = sync_point_object.SyncPoint.get_by_key(
                  stack_db._context, entity_id, stack_db.current_traversal,
                  is_update)
              self.assertIsNotNone(sync_point, 'entity %s' % entity_id)
              self.assertEqual(stack_db.id, sync_point.stack_id)
  
          leaves = stack.convergence_dependencies.leaves()
          expected_calls = []
          for rsrc_id, is_update in leaves:
              expected_calls.append(
                  mock.call.worker_client.WorkerClient.check_resource(
                      stack.context, rsrc_id, stack.current_traversal,
                      {'input_data': {}},
                      is_update, None))
  
          leaves = curr_stack.convergence_dependencies.leaves()
          for rsrc_id, is_update in leaves:
              expected_calls.append(
                  mock.call.worker_client.WorkerClient.check_resource(
                      curr_stack.context, rsrc_id, curr_stack.current_traversal,
                      {'input_data': {}},
                      is_update, None))
          self.assertEqual(expected_calls, mock_cr.mock_calls)
  
      def test_mark_complete_purges_db(self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          stack.purge_db = mock.Mock()
          stack.mark_complete()
          self.assertTrue(stack.purge_db.called)
  
      def test_state_set_sets_empty_curr_trvsl_for_failed_stack(
              self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          stack.state_set(stack.action, stack.FAILED, 'test-reason')
          self.assertEqual('', stack.current_traversal)
  
      @mock.patch.object(raw_template_object.RawTemplate, 'delete')
      def test_purge_db_deletes_previous_template(self, mock_tmpl_delete,
                                                  mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.prev_raw_template_id = 10
          stack.purge_db()
          self.assertTrue(mock_tmpl_delete.called)
  
      @mock.patch.object(parser.Stack, '_delete_credentials')
      @mock.patch.object(stack_object.Stack, 'delete')
      def test_purge_db_deletes_creds(self, mock_delete_stack,
                                      mock_creds_delete, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          reason = 'stack delete complete'
          mock_creds_delete.return_value = (stack.COMPLETE, reason)
          stack.state_set(stack.DELETE, stack.COMPLETE, reason)
          stack.purge_db()
          self.assertTrue(mock_creds_delete.called)
          self.assertTrue(mock_delete_stack.called)
  
      @mock.patch.object(parser.Stack, '_delete_credentials')
      @mock.patch.object(stack_object.Stack, 'delete')
      def test_purge_db_deletes_creds_failed(self, mock_delete_stack,
                                             mock_creds_delete, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
  
          reason = 'stack delete complete'
          failed_reason = 'Error deleting trust'
          mock_creds_delete.return_value = (stack.FAILED, failed_reason)
          stack.state_set(stack.DELETE, stack.COMPLETE, reason)
          stack.purge_db()
          self.assertTrue(mock_creds_delete.called)
          self.assertFalse(mock_delete_stack.called)
          self.assertEqual((stack.DELETE, stack.FAILED), stack.state)
  
      @mock.patch.object(raw_template_object.RawTemplate, 'delete')
      def test_purge_db_does_not_delete_previous_template_when_stack_fails(
              self, mock_tmpl_delete, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.status = stack.FAILED
          stack.purge_db()
          self.assertFalse(mock_tmpl_delete.called)
  
      def test_purge_db_deletes_sync_points(self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          stack.purge_db()
          rows = sync_point_object.SyncPoint.delete_all_by_stack_and_traversal(
              stack.context, stack.id, stack.current_traversal)
          self.assertEqual(0, rows)
  
      @mock.patch.object(stack_object.Stack, 'delete')
      def test_purge_db_deletes_stack_for_deleted_stack(self, mock_stack_delete,
                                                        mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          stack.state_set(stack.DELETE, stack.COMPLETE, 'test reason')
          stack.purge_db()
          self.assertTrue(mock_stack_delete.called)
  
      @mock.patch.object(resource_objects.Resource, 'purge_deleted')
      def test_purge_db_calls_rsrc_purge_deleted(self, mock_rsrc_purge_delete,
                                                 mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          stack.purge_db()
          self.assertTrue(mock_rsrc_purge_delete.called)
  
      def test_get_best_existing_db_resource(self, mock_cr):
          stack = tools.get_stack('test_stack', utils.dummy_context(),
                                  template=tools.string_template_five,
                                  convergence=True)
          stack.store()
          stack.prev_raw_template_id = 2
          stack.t.id = 3
          dummy_res = stack.resources['A']
          a_res_2 = res.Resource('A', dummy_res.t, stack)
          a_res_2.current_template_id = 2
          a_res_2.id = 2
          a_res_3 = res.Resource('A', dummy_res.t, stack)
          a_res_3.current_template_id = 3
          a_res_3.id = 3
          a_res_1 = res.Resource('A', dummy_res.t, stack)
          a_res_1.current_template_id = 1
          a_res_1.id = 1
          existing_res = {2: a_res_2,
                          3: a_res_3,
                          1: a_res_1}
          stack.ext_rsrcs_db = existing_res
          best_res = stack._get_best_existing_rsrc_db('A')
          # should return resource with template id 3 which is current template
          self.assertEqual(a_res_3.id, best_res.id)
  
          # no resource with current template id as 3
          existing_res = {1: a_res_1,
                          2: a_res_2}
          stack.ext_rsrcs_db = existing_res
          best_res = stack._get_best_existing_rsrc_db('A')
          # should return resource with template id 2 which is prev template
          self.assertEqual(a_res_2.id, best_res.id)
  
          # no resource with current template id as 3 or 2
          existing_res = {1: a_res_1}
          stack.ext_rsrcs_db = existing_res
          best_res = stack._get_best_existing_rsrc_db('A')
          # should return resource with template id 1 existing in DB
          self.assertEqual(a_res_1.id, best_res.id)
  
      @mock.patch.object(parser.Stack, '_converge_create_or_update')
      def test_updated_time_stack_create(self, mock_ccu, mock_cr):
          stack = parser.Stack(utils.dummy_context(), 'convg_updated_time_test',
                               templatem.Template.create_empty_template(),
                               convergence=True)
          stack.converge_stack(template=stack.t, action=stack.CREATE)
          self.assertIsNone(stack.updated_time)
          self.assertTrue(mock_ccu.called)
  
      @mock.patch.object(parser.Stack, '_converge_create_or_update')
      def test_updated_time_stack_update(self, mock_ccu, mock_cr):
          tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
                  'Resources': {'R1': {'Type': 'GenericResourceType'}}}
          stack = parser.Stack(utils.dummy_context(), 'updated_time_test',
                               templatem.Template(tmpl), convergence=True)
          stack.converge_stack(template=stack.t, action=stack.UPDATE)
          self.assertIsNotNone(stack.updated_time)
          self.assertTrue(mock_ccu.called)
  
      @mock.patch.object(parser.Stack, '_converge_create_or_update')
      @mock.patch.object(sync_point_object.SyncPoint,
                         'delete_all_by_stack_and_traversal')
      def test_sync_point_delete_stack_create(self, mock_syncpoint_del,
                                              mock_ccu, mock_cr):
          stack = parser.Stack(utils.dummy_context(), 'convg_updated_time_test',
                               templatem.Template.create_empty_template())
          stack.converge_stack(template=stack.t, action=stack.CREATE)
          self.assertFalse(mock_syncpoint_del.called)
          self.assertTrue(mock_ccu.called)
  
      @mock.patch.object(parser.Stack, '_converge_create_or_update')
      @mock.patch.object(sync_point_object.SyncPoint,
                         'delete_all_by_stack_and_traversal')
      def test_sync_point_delete_stack_update(self, mock_syncpoint_del,
                                              mock_ccu, mock_cr):
          tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
                  'Resources': {'R1': {'Type': 'GenericResourceType'}}}
          stack = parser.Stack(utils.dummy_context(), 'updated_time_test',
                               templatem.Template(tmpl))
          stack.current_traversal = 'prev_traversal'
          stack.converge_stack(template=stack.t, action=stack.UPDATE)
          self.assertTrue(mock_syncpoint_del.called)
          self.assertTrue(mock_ccu.called)
  
      def test_snapshot_delete(self, mock_cr):
          tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
                  'Resources': {'R1': {'Type': 'GenericResourceType'}}}
          stack = parser.Stack(utils.dummy_context(), 'updated_time_test',
                               templatem.Template(tmpl))
          stack.current_traversal = 'prev_traversal'
          stack.action, stack.status = stack.CREATE, stack.COMPLETE
          stack.store()
          snapshot_values = {
              'stack_id': stack.id,
              'name': 'fake_snapshot',
              'tenant': stack.context.tenant_id,
              'status': 'COMPLETE',
              'data': None
          }
          snapshot_objects.Snapshot.create(stack.context, snapshot_values)
  
          # Ensure that snapshot is not deleted on stack update
          stack.converge_stack(template=stack.t, action=stack.UPDATE)
          db_snapshot_obj = snapshot_objects.Snapshot.get_all(
              stack.context, stack.id)
          self.assertEqual('fake_snapshot', db_snapshot_obj[0].name)
          self.assertEqual(stack.id, db_snapshot_obj[0].stack_id)
  
          # Ensure that snapshot is deleted on stack delete
          stack.converge_stack(template=stack.t, action=stack.DELETE)
          self.assertEqual([], snapshot_objects.Snapshot.get_all(
              stack.context, stack.id))
          self.assertTrue(mock_cr.called)
  
  
  @mock.patch.object(parser.Stack, '_persist_state')
  class TestConvgStackStateSet(common.HeatTestCase):
      def setUp(self):
          super(TestConvgStackStateSet, self).setUp()
          cfg.CONF.set_override('convergence_engine', True, enforce_type=True)
          self.stack = tools.get_stack(
              'test_stack', utils.dummy_context(),
              template=tools.wp_template, convergence=True)
  
      def test_state_set_create_adopt_update_delete_rollback_complete(self,
                                                                      mock_ps):
          mock_ps.return_value = 'updated'
          ret_val = self.stack.state_set(self.stack.CREATE, self.stack.COMPLETE,
                                         'Create complete')
          self.assertTrue(mock_ps.called)
          # Ensure that state_set returns with value for convergence
          self.assertEqual('updated', ret_val)
  
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(self.stack.UPDATE, self.stack.COMPLETE,
                                         'Update complete')
          self.assertTrue(mock_ps.called)
          self.assertEqual('updated', ret_val)
  
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(
              self.stack.ROLLBACK, self.stack.COMPLETE, 'Rollback complete')
          self.assertTrue(mock_ps.called)
          self.assertEqual('updated', ret_val)
  
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(self.stack.DELETE, self.stack.COMPLETE,
                                         'Delete complete')
          self.assertTrue(mock_ps.called)
          self.assertEqual('updated', ret_val)
  
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(self.stack.ADOPT, self.stack.COMPLETE,
                                         'Adopt complete')
          self.assertTrue(mock_ps.called)
          self.assertEqual('updated', ret_val)
  
      def test_state_set_stack_suspend(self, mock_ps):
          mock_ps.return_value = 'updated'
          ret_val = self.stack.state_set(
              self.stack.SUSPEND, self.stack.IN_PROGRESS, 'Suspend started')
          self.assertTrue(mock_ps.called)
          # Ensure that state_set returns None for other actions in convergence
          self.assertIsNone(ret_val)
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(
              self.stack.SUSPEND, self.stack.COMPLETE, 'Suspend complete')
          self.assertFalse(mock_ps.called)
          self.assertIsNone(ret_val)
  
      def test_state_set_stack_resume(self, mock_ps):
          ret_val = self.stack.state_set(
              self.stack.RESUME, self.stack.IN_PROGRESS, 'Resume started')
          self.assertTrue(mock_ps.called)
          self.assertIsNone(ret_val)
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(self.stack.RESUME, self.stack.COMPLETE,
                                         'Resume complete')
          self.assertFalse(mock_ps.called)
          self.assertIsNone(ret_val)
  
      def test_state_set_stack_snapshot(self, mock_ps):
          ret_val = self.stack.state_set(
              self.stack.SNAPSHOT, self.stack.IN_PROGRESS, 'Snapshot started')
          self.assertTrue(mock_ps.called)
          self.assertIsNone(ret_val)
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(
              self.stack.SNAPSHOT, self.stack.COMPLETE, 'Snapshot complete')
          self.assertFalse(mock_ps.called)
          self.assertIsNone(ret_val)
  
      def test_state_set_stack_restore(self, mock_ps):
          ret_val = self.stack.state_set(
              self.stack.RESTORE, self.stack.IN_PROGRESS, 'Restore started')
          self.assertTrue(mock_ps.called)
          self.assertIsNone(ret_val)
          mock_ps.reset_mock()
          ret_val = self.stack.state_set(
              self.stack.RESTORE, self.stack.COMPLETE, 'Restore complete')
          self.assertFalse(mock_ps.called)
          self.assertIsNone(ret_val)
  
  
  class TestConvgStackRollback(common.HeatTestCase):
  
      def setUp(self):
          super(TestConvgStackRollback, self).setUp()
          self.ctx = utils.dummy_context()
          self.stack = tools.get_stack('test_stack_rollback', self.ctx,
                                       template=tools.string_template_five,
                                       convergence=True)
  
      def test_trigger_rollback_uses_old_template_if_available(self):
          # create a template and assign to stack as previous template
          t = template_format.parse(tools.wp_template)
          prev_tmpl = templatem.Template(t)
          prev_tmpl.store(context=self.ctx)
          self.stack.prev_raw_template_id = prev_tmpl.id
          # mock failure
          self.stack.action = self.stack.UPDATE
          self.stack.status = self.stack.FAILED
          self.stack.store()
          # mock converge_stack()
          self.stack.converge_stack = mock.Mock()
          # call trigger_rollbac
          self.stack.rollback()
  
          # Make sure stack converge is called with previous template
          self.assertTrue(self.stack.converge_stack.called)
          self.assertIsNone(self.stack.prev_raw_template_id)
          call_args, call_kwargs = self.stack.converge_stack.call_args
          template_used_for_rollback = call_args[0]
          self.assertEqual(prev_tmpl.id, template_used_for_rollback.id)
  
      def test_trigger_rollback_uses_empty_template_if_prev_tmpl_not_available(
              self):
          # mock create failure with no previous template
          self.stack.prev_raw_template_id = None
          self.stack.action = self.stack.CREATE
          self.stack.status = self.stack.FAILED
          self.stack.store()
          # mock converge_stack()
          self.stack.converge_stack = mock.Mock()
          # call trigger_rollback
          self.stack.rollback()
  
          # Make sure stack converge is called with empty template
          self.assertTrue(self.stack.converge_stack.called)
          call_args, call_kwargs = self.stack.converge_stack.call_args
          template_used_for_rollback = call_args[0]
          self.assertEqual({}, template_used_for_rollback['resources'])
  
  
  class TestConvgComputeDependencies(common.HeatTestCase):
      def setUp(self):
          super(TestConvgComputeDependencies, self).setUp()
          self.ctx = utils.dummy_context()
          self.stack = tools.get_stack('test_stack_convg', self.ctx,
                                       template=tools.string_template_five,
                                       convergence=True)
  
      def _fake_db_resources(self, stack):
          db_resources = {}
          i = 0
          for rsrc_name in ['E', 'D', 'C', 'B', 'A']:
              i += 1
              rsrc = mock.MagicMock()
              rsrc.id = i
              rsrc.name = rsrc_name
              rsrc.current_template_id = stack.prev_raw_template_id
              db_resources[i] = rsrc
          db_resources[3].requires = [4, 5]
          db_resources[1].requires = [3]
          db_resources[2].requires = [3]
          return db_resources
  
      def test_dependencies_create_stack_without_mock(self):
          self.stack.store()
          self.current_resources = self.stack._update_or_store_resources()
          self.stack._compute_convg_dependencies(self.stack.ext_rsrcs_db,
                                                 self.stack.dependencies,
                                                 self.current_resources)
          self.assertEqual('Dependencies(['
                           '((1, True), (3, True)), '
                           '((2, True), (3, True)), '
                           '((3, True), (4, True)), '
                           '((3, True), (5, True))])',
                           repr(self.stack._convg_deps))
  
      def test_dependencies_update_same_template(self):
          t = template_format.parse(tools.string_template_five)
          tmpl = templatem.Template(t)
          self.stack.t = tmpl
          self.stack.t.id = 2
          self.stack.prev_raw_template_id = 1
          db_resources = self._fake_db_resources(self.stack)
          curr_resources = {res.name: res for id, res in db_resources.items()}
          self.stack._compute_convg_dependencies(db_resources,
                                                 self.stack.dependencies,
                                                 curr_resources)
          self.assertEqual('Dependencies(['
                           '((1, False), (1, True)), '
                           '((1, True), (3, True)), '
                           '((2, False), (2, True)), '
                           '((2, True), (3, True)), '
                           '((3, False), (1, False)), '
                           '((3, False), (2, False)), '
                           '((3, False), (3, True)), '
                           '((3, True), (4, True)), '
                           '((3, True), (5, True)), '
                           '((4, False), (3, False)), '
                           '((4, False), (4, True)), '
                           '((5, False), (3, False)), '
                           '((5, False), (5, True))])',
                           repr(self.stack._convg_deps))
  
      def test_dependencies_update_new_template(self):
          t = template_format.parse(tools.string_template_five_update)
          tmpl = templatem.Template(t)
          self.stack.t = tmpl
          self.stack.t.id = 2
          self.stack.prev_raw_template_id = 1
          db_resources = self._fake_db_resources(self.stack)
  
          curr_resources = {res.name: res for id, res in db_resources.items()}
          # 'H', 'G', 'F' are part of new template
          i = len(db_resources)
          for new_rsrc in ['H', 'G', 'F']:
              i += 1
              rsrc = mock.MagicMock()
              rsrc.name = new_rsrc
              rsrc.id = i
              curr_resources[new_rsrc] = rsrc
  
          self.stack._compute_convg_dependencies(db_resources,
                                                 self.stack.dependencies,
                                                 curr_resources)
          self.assertEqual('Dependencies(['
                           '((3, False), (1, False)), '
                           '((3, False), (2, False)), '
                           '((4, False), (3, False)), '
                           '((4, False), (4, True)), '
                           '((5, False), (3, False)), '
                           '((5, False), (5, True)), '
                           '((6, True), (8, True)), '
                           '((7, True), (8, True)), '
                           '((8, True), (4, True)), '
                           '((8, True), (5, True))])',
                           repr(self.stack._convg_deps))
  
      def test_dependencies_update_replace_rollback(self):
          t = template_format.parse(tools.string_template_five)
          tmpl = templatem.Template(t)
          self.stack.t = tmpl
          self.stack.t.id = 1
          self.stack.prev_raw_template_id = 2
          db_resources = self._fake_db_resources(self.stack)
  
          # previous resource E still exists in db.
          db_resources[1].current_template_id = 1
          # resource that replaced E
          res = mock.MagicMock()
          res.id = 6
          res.name = 'E'
          res.requires = [3]
          res.replaces = 1
          res.current_template_id = 2
          db_resources[6] = res
  
          curr_resources = {res.name: res for id, res in db_resources.items()}
          # best existing resource
          curr_resources['E'] = db_resources[1]
          self.stack._compute_convg_dependencies(db_resources,
                                                 self.stack.dependencies,
                                                 curr_resources)
          self.assertEqual('Dependencies(['
                           '((1, False), (1, True)), '
                           '((1, False), (6, False)), '
                           '((1, True), (3, True)), '
                           '((2, False), (2, True)), '
                           '((2, True), (3, True)), '
                           '((3, False), (1, False)), '
                           '((3, False), (2, False)), '
                           '((3, False), (3, True)), '
                           '((3, False), (6, False)), '
                           '((3, True), (4, True)), '
                           '((3, True), (5, True)), '
                           '((4, False), (3, False)), '
                           '((4, False), (4, True)), '
                           '((5, False), (3, False)), '
                           '((5, False), (5, True))])',
                           repr(self.stack._convg_deps))
  
      def test_dependencies_update_delete(self):
          tmpl = templatem.Template.create_empty_template(
              version=self.stack.t.version)
          self.stack.t = tmpl
          self.stack.t.id = 2
          self.stack.prev_raw_template_id = 1
          db_resources = self._fake_db_resources(self.stack)
          curr_resources = {res.name: res for id, res in db_resources.items()}
          self.stack._compute_convg_dependencies(db_resources,
                                                 self.stack.dependencies,
                                                 curr_resources)
          self.assertEqual('Dependencies(['
                           '((3, False), (1, False)), '
                           '((3, False), (2, False)), '
                           '((4, False), (3, False)), '
                           '((5, False), (3, False))])',
                           repr(self.stack._convg_deps))
  
  
  class TestConvergenceMigration(common.HeatTestCase):
      def test_migration_to_convergence_engine(self):
          self.ctx = utils.dummy_context()
          self.stack = tools.get_stack('test_stack_convg', self.ctx,
                                       template=tools.string_template_five)
          self.stack.store()
          for r in self.stack.resources.values():
              r._store()
          self.stack.migrate_to_convergence()
          self.stack = self.stack.load(self.ctx, self.stack.id)
  
          self.assertTrue(self.stack.convergence)
          self.assertIsNone(self.stack.prev_raw_template_id)
          exp_required_by = {'A': ['C'], 'B': ['C'], 'C': ['D', 'E'],
                             'D': [], 'E': []}
          exp_requires = {'A': [], 'B': [], 'C': ['A', 'B'], 'D': ['C'],
                          'E': ['C']}
          exp_tmpl_id = self.stack.t.id
  
          def id_to_name(ids):
              names = []
              for r in self.stack.resources.values():
                  if r.id in ids:
                      names.append(r.name)
              return names
          for r in self.stack.resources.values():
              self.assertEqual(sorted(exp_required_by[r.name]),
                               sorted(r.required_by()))
              self.assertEqual(sorted(exp_requires[r.name]),
                               sorted(id_to_name(r.requires)))
              self.assertEqual(exp_tmpl_id, r.current_template_id)
  