# 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.

from unittest import mock

from oslo_serialization import jsonutils
from oslo_utils import uuidutils

from senlin.common import consts
from senlin.common import exception
from senlin.engine import node as nodem
from senlin.objects import node as node_obj
from senlin.profiles import base as pb
from senlin.tests.unit.common import base
from senlin.tests.unit.common import utils

PROFILE_ID = 'aa5f86b8-e52b-4f2b-828a-4c14c770938d'
CLUSTER_ID = '2c5139a6-24ba-4a6f-bd53-a268f61536de'
NODE_ID = '60efdaa1-06c2-4fcf-ae44-17a2d85ff3ea'


class TestNode(base.SenlinTestCase):

    def setUp(self):
        super(TestNode, self).setUp()
        self.context = utils.dummy_context(project='node_test_project')
        self.profile = utils.create_profile(self.context, PROFILE_ID)
        self.cluster = utils.create_cluster(self.context, CLUSTER_ID,
                                            PROFILE_ID)

    def test_node_init(self):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, role='first_node')
        self.assertIsNone(node.id)
        self.assertEqual('node1', node.name)
        self.assertIsNone(node.physical_id)
        self.assertEqual(PROFILE_ID, node.profile_id)
        self.assertEqual('', node.user)
        self.assertEqual('', node.project)
        self.assertEqual('', node.domain)
        self.assertEqual(CLUSTER_ID, node.cluster_id)
        self.assertEqual(-1, node.index)
        self.assertEqual('first_node', node.role)

        self.assertIsNone(node.init_at)
        self.assertIsNone(node.created_at)
        self.assertIsNone(node.updated_at)

        self.assertEqual('INIT', node.status)
        self.assertEqual('Initializing', node.status_reason)
        self.assertEqual({}, node.data)
        self.assertEqual({}, node.metadata)
        self.assertEqual({}, node.rt)

    def test_node_init_random_name(self):
        node = nodem.Node(None, PROFILE_ID, None)
        self.assertIsNotNone(node.name)
        self.assertEqual(13, len(node.name))

    def test_node_store_init(self):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context,
                          role='first_node', index=1)
        self.assertIsNone(node.id)
        node_id = node.store(self.context)
        self.assertIsNotNone(node_id)

        node_info = node_obj.Node.get(self.context, node_id)
        self.assertIsNotNone(node_info)
        self.assertEqual('node1', node_info.name)
        self.assertIsNone(node_info.physical_id)
        self.assertEqual(CLUSTER_ID, node_info.cluster_id)
        self.assertEqual(PROFILE_ID, node_info.profile_id)
        self.assertEqual(self.context.user_id, node_info.user)
        self.assertEqual(self.context.project_id, node_info.project)
        self.assertEqual(self.context.domain_id, node_info.domain)
        self.assertEqual(1, node_info.index)
        self.assertEqual('first_node', node.role)

        self.assertIsNotNone(node_info.init_at)
        self.assertIsNone(node_info.created_at)
        self.assertIsNone(node_info.updated_at)

        self.assertEqual('INIT', node_info.status)
        self.assertEqual('Initializing', node_info.status_reason)
        self.assertEqual({}, node_info.metadata)
        self.assertEqual({}, node_info.data)

    def test_node_store_update(self):
        node = nodem.Node('node1', PROFILE_ID, "", user=self.context.user_id,
                          project=self.context.project_id)
        node_id = node.store(self.context)

        node.name = 'new_name'
        new_node_id = node.store(self.context)

        self.assertEqual(node_id, new_node_id)

    def test_node_load(self):
        ex = self.assertRaises(exception.ResourceNotFound,
                               nodem.Node.load,
                               self.context, 'non-existent', None)
        self.assertEqual("The node 'non-existent' could not be found.",
                         str(ex))

        x_node_id = 'ee96c490-2dee-40c8-8919-4c64b89e326c'
        node = utils.create_node(self.context, x_node_id, PROFILE_ID,
                                 CLUSTER_ID)
        node_info = nodem.Node.load(self.context, x_node_id)

        self.assertEqual(node.id, node_info.id)
        self.assertEqual(node.name, node_info.name)
        self.assertEqual(node.physical_id, node_info.physical_id)
        self.assertEqual(node.cluster_id, node_info.cluster_id)
        self.assertEqual(node.profile_id, node_info.profile_id)
        self.assertEqual(node.user, node_info.user)
        self.assertEqual(node.project, node_info.project)
        self.assertEqual(node.domain, node_info.domain)
        self.assertEqual(node.index, node_info.index)
        self.assertEqual(node.role, node_info.role)

        self.assertEqual(node.init_at, node_info.init_at)
        self.assertEqual(node.created_at, node_info.created_at)
        self.assertEqual(node.updated_at, node_info.updated_at)

        self.assertEqual(node.status, node_info.status)
        self.assertEqual(node.status_reason, node_info.status_reason)
        self.assertEqual(node.metadata, node_info.metadata)
        self.assertEqual(node.data, node_info.data)
        self.assertEqual(self.profile.name, node_info.rt['profile'].name)

    def test_node_load_diff_project(self):
        x_node_id = 'c06840c5-f4e4-49ae-8143-9da5b4c73f38'
        utils.create_node(self.context, x_node_id, PROFILE_ID, CLUSTER_ID)

        new_ctx = utils.dummy_context(project='a-different-project')
        ex = self.assertRaises(exception.ResourceNotFound,
                               nodem.Node.load,
                               new_ctx, x_node_id, None)
        self.assertEqual("The node '%s' could not be found." % x_node_id,
                         str(ex))

        res = nodem.Node.load(new_ctx, x_node_id, project_safe=False)
        self.assertIsNotNone(res)
        self.assertEqual(x_node_id, res.id)

    @mock.patch.object(nodem.Node, '_from_object')
    @mock.patch.object(node_obj.Node, 'get_all')
    def test_node_load_all(self, mock_get, mock_init):
        x_obj_1 = mock.Mock()
        x_obj_2 = mock.Mock()
        mock_get.return_value = [x_obj_1, x_obj_2]

        x_node_1 = mock.Mock()
        x_node_2 = mock.Mock()
        mock_init.side_effect = [x_node_1, x_node_2]

        result = nodem.Node.load_all(self.context)

        self.assertEqual([x_node_1, x_node_2], [n for n in result])
        mock_get.assert_called_once_with(self.context, cluster_id=None,
                                         limit=None, marker=None,
                                         sort=None, filters=None,
                                         project_safe=True)
        mock_init.assert_has_calls([
            mock.call(self.context, x_obj_1),
            mock.call(self.context, x_obj_2)])

    def test_node_set_status(self):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.store(self.context)
        self.assertEqual(nodem.consts.NS_INIT, node.status)
        self.assertIsNotNone(node.init_at)
        self.assertIsNone(node.created_at)
        self.assertIsNone(node.updated_at)

        # create
        node.set_status(self.context, consts.NS_CREATING,
                        reason='Creation in progress')
        self.assertEqual('CREATING', node.status)
        self.assertEqual('Creation in progress', node.status_reason)
        self.assertIsNone(node.created_at)
        self.assertIsNone(node.updated_at)

        node.set_status(self.context, consts.NS_ACTIVE,
                        reason='Creation succeeded')
        self.assertEqual('ACTIVE', node.status)
        self.assertEqual('Creation succeeded', node.status_reason)
        self.assertIsNotNone(node.created_at)
        self.assertIsNotNone(node.updated_at)

        # update
        node.set_status(self.context, consts.NS_UPDATING,
                        reason='Update in progress')
        self.assertEqual('UPDATING', node.status)
        self.assertEqual('Update in progress', node.status_reason)
        self.assertIsNotNone(node.created_at)

        node.set_status(self.context, consts.NS_ACTIVE,
                        reason='Update succeeded')
        self.assertEqual('ACTIVE', node.status)
        self.assertEqual('Update succeeded', node.status_reason)
        self.assertIsNotNone(node.created_at)
        self.assertIsNotNone(node.updated_at)

        node.set_status(self.context, consts.NS_ACTIVE)
        self.assertEqual('ACTIVE', node.status)
        self.assertIsNotNone(node.created_at)
        self.assertIsNotNone(node.updated_at)

        # delete
        node.set_status(self.context, consts.NS_DELETING,
                        reason='Deletion in progress')
        self.assertEqual('DELETING', node.status)
        self.assertEqual('Deletion in progress', node.status_reason)
        self.assertIsNotNone(node.created_at)

    @mock.patch.object(pb.Profile, 'get_details')
    def test_node_get_details(self, mock_details):
        node = nodem.Node('node1', CLUSTER_ID, None)
        for physical_id in (None, ''):
            node.physical_id = physical_id
            self.assertEqual({}, node.get_details(self.context))
            self.assertEqual(0, mock_details.call_count)

        fake_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        node.physical_id = fake_id
        mock_details.return_value = {'foo': 'bar'}
        res = node.get_details(self.context)
        mock_details.assert_called_once_with(self.context, node)
        self.assertEqual({'foo': 'bar'}, res)

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'create_object')
    def test_node_create(self, mock_create, mock_status):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        mock_create.return_value = physical_id
        res = node.do_create(self.context)
        self.assertTrue(res)
        mock_status.assert_any_call(self.context, consts.NS_CREATING,
                                    'Creation in progress')
        mock_status.assert_any_call(self.context, consts.NS_ACTIVE,
                                    'Creation succeeded',
                                    physical_id=physical_id)

    @mock.patch.object(nodem.Node, 'set_status')
    def test_node_create_not_init(self, mock_status):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.status = 'NOT_INIT'
        res, reason = node.do_create(self.context)
        self.assertFalse(res)
        self.assertEqual('Node must be in INIT status', reason)
        mock_status.assert_any_call(self.context, consts.NS_ERROR,
                                    'Node must be in INIT status')

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'create_object')
    def test_node_create_not_created(self, mock_create, mock_status):

        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        mock_create.side_effect = exception.EResourceCreation(
            type='PROFILE', message='Boom', resource_id='test_id')

        res, reason = node.do_create(self.context)

        self.assertFalse(res)
        self.assertEqual(str(reason), 'Failed in creating PROFILE: Boom.')
        mock_status.assert_any_call(self.context, consts.NS_CREATING,
                                    'Creation in progress')
        mock_status.assert_any_call(self.context, consts.NS_ERROR,
                                    'Failed in creating PROFILE: Boom.',
                                    physical_id='test_id')

    @mock.patch.object(node_obj.Node, 'delete')
    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'delete_object')
    def test_node_delete(self, mock_delete, mock_status, mock_db_delete):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.physical_id = uuidutils.generate_uuid()
        node.id = uuidutils.generate_uuid()
        mock_db_delete.return_value = True

        res = node.do_delete(self.context)

        self.assertTrue(res)
        mock_delete.assert_called_once_with(self.context, node)
        mock_db_delete.assert_called_once_with(self.context, node.id)
        mock_status.assert_called_once_with(self.context, consts.NS_DELETING,
                                            'Deletion in progress')

    @mock.patch.object(node_obj.Node, 'delete')
    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'delete_object')
    def test_node_delete_no_physical_id(self, mock_delete, mock_status,
                                        mock_db_delete):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.id = uuidutils.generate_uuid()
        self.assertIsNone(node.physical_id)
        mock_db_delete.return_value = True

        res = node.do_delete(self.context)

        self.assertTrue(res)
        mock_status.assert_called_once_with(self.context, consts.NS_DELETING,
                                            "Deletion in progress")
        self.assertTrue(mock_delete.called)
        mock_db_delete.assert_called_once_with(self.context, node.id)

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'delete_object')
    def test_node_delete_EResourceDeletion(self, mock_delete, mock_status):
        ex = exception.EResourceDeletion(type='PROFILE', id='NODE_ID',
                                         message='Too Bad')
        mock_delete.side_effect = ex
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.physical_id = uuidutils.generate_uuid()

        res = node.do_delete(self.context)

        self.assertFalse(res)
        mock_delete.assert_called_once_with(self.context, node)
        mock_status.assert_has_calls([
            mock.call(self.context, consts.NS_DELETING,
                      "Deletion in progress"),
            mock.call(self.context, consts.NS_ERROR,
                      "Failed in deleting PROFILE 'NODE_ID': Too Bad.")
        ])

    @mock.patch.object(node_obj.Node, 'update')
    @mock.patch.object(pb.Profile, 'update_object')
    def test_node_update_new_profile(self, mock_update, mock_db):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context,
                          physical_id=uuidutils.generate_uuid())
        node.id = node.store(self.context)
        new_id = uuidutils.generate_uuid()
        utils.create_profile(self.context, new_id)
        mock_update.return_value = True

        res = node.do_update(self.context, {'new_profile_id': new_id})

        self.assertTrue(res)
        mock_update.assert_called_once_with(self.context, node, new_id)
        self.assertEqual(new_id, node.profile_id)
        self.assertEqual(new_id, node.rt['profile'].id)
        mock_db.assert_has_calls([
            mock.call(self.context, node.id,
                      {'status': consts.NS_UPDATING,
                       'status_reason': 'Update in progress'}),
            mock.call(self.context, node.id,
                      {'status': consts.NS_ACTIVE,
                       'status_reason': 'Update succeeded',
                       'profile_id': new_id,
                       'updated_at': mock.ANY})
        ])

    @mock.patch.object(pb.Profile, 'update_object')
    @mock.patch.object(node_obj.Node, 'update')
    def test_node_update_name(self, mock_db, mock_update):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.store(self.context)

        physical_id = uuidutils.generate_uuid()
        node.physical_id = physical_id

        res = node.do_update(self.context, {'name': 'new_name',
                                            'role': 'new_role',
                                            'metadata': {'k': {'m': 'v'}},
                                            'bogus': 'foo'})

        self.assertTrue(res)
        self.assertEqual('new_name', node.name)
        mock_db.assert_has_calls([
            mock.call(self.context, node.id,
                      {'status': 'UPDATING',
                       'status_reason': 'Update in progress'}),
            mock.call(self.context, node.id,
                      {'status': 'ACTIVE',
                       'status_reason': 'Update succeeded',
                       'name': 'new_name',
                       'role': 'new_role',
                       'metadata': {'k': {'m': 'v'}},
                       'updated_at': mock.ANY})
        ])
        self.assertEqual(0, mock_update.call_count)

    def test_node_update_not_created(self):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        self.assertIsNone(node.physical_id)
        new_profile_id = '71d8f4dd-1ef9-4308-b7ae-03298b04449e'
        res = node.do_update(self.context, new_profile_id)
        self.assertFalse(res)

    @mock.patch.object(pb.Profile, 'update_object')
    @mock.patch.object(node_obj.Node, 'update')
    def test_node_update_EResourceUpdate(self, mock_db, mock_update):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.physical_id = uuidutils.generate_uuid()
        node.id = uuidutils.generate_uuid()

        ex = exception.EResourceUpdate(type='PROFILE', id='ID',
                                       message='reason')
        mock_update.side_effect = ex
        new_id = uuidutils.generate_uuid()
        utils.create_profile(self.context, new_id)

        res = node.do_update(self.context, {'new_profile_id': new_id})

        self.assertFalse(res)
        self.assertNotEqual(new_id, node.profile_id)
        mock_db.assert_has_calls([
            mock.call(
                self.context, node.id,
                {"status": "UPDATING", "status_reason": "Update in progress"}
            ),
            mock.call(
                self.context, node.id,
                {"status": "ERROR",
                 "status_reason": "Failed in updating PROFILE 'ID': reason.",
                 "updated_at": mock.ANY}
            )
        ])
        self.assertEqual(1, mock_update.call_count)

    @mock.patch.object(node_obj.Node, 'migrate')
    def test_node_join_same_cluster(self, mock_migrate):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        node.index = 1
        res = node.do_join(self.context, CLUSTER_ID)
        self.assertTrue(res)
        self.assertEqual(1, node.index)
        self.assertIsNone(node.updated_at)
        self.assertFalse(mock_migrate.called)

    @mock.patch.object(pb.Profile, 'join_cluster')
    @mock.patch.object(node_obj.Node, 'migrate')
    def test_node_join(self, mock_migrate, mock_join_cluster):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        mock_join_cluster.return_value = True
        cluster_id = 'fb8bca7a-a82b-4442-a40f-92d3e3cfb0b9'

        res = node.do_join(self.context, cluster_id)

        self.assertTrue(res)
        mock_migrate.assert_called_once_with(self.context, node.id,
                                             cluster_id, mock.ANY)
        mock_join_cluster.assert_called_once_with(self.context, node,
                                                  cluster_id)
        self.assertEqual(cluster_id, node.cluster_id)
        self.assertEqual(mock_migrate.return_value.index, node.index)
        self.assertIsNotNone(node.updated_at)

    @mock.patch.object(pb.Profile, 'join_cluster')
    def test_node_join_fail_profile_call(self, mock_join):
        node = nodem.Node('node1', PROFILE_ID, None, self.context)
        node.id = uuidutils.generate_uuid()
        mock_join.return_value = False
        cluster_id = 'fb8bca7a-a82b-4442-a40f-92d3e3cfb0b9'

        res = node.do_join(self.context, cluster_id)

        self.assertFalse(res)
        mock_join.assert_called_once_with(self.context, node, cluster_id)
        self.assertEqual(-1, node.index)

    @mock.patch.object(node_obj.Node, 'migrate')
    def test_node_leave_no_cluster(self, mock_migrate):
        node = nodem.Node('node1', PROFILE_ID, '', self.context)
        self.assertTrue(node.do_leave(self.context))
        self.assertFalse(mock_migrate.called)
        self.assertEqual('', node.cluster_id)
        self.assertIsNone(node.updated_at)

    @mock.patch.object(pb.Profile, 'leave_cluster')
    @mock.patch.object(node_obj.Node, 'migrate')
    def test_node_leave(self, mock_migrate, mock_leave_cluster):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context)
        mock_leave_cluster.return_value = True
        res = node.do_leave(self.context)
        self.assertTrue(res)
        self.assertEqual('', node.cluster_id)
        self.assertIsNotNone(node.updated_at)
        self.assertEqual(-1, node.index)
        mock_migrate.assert_called_once_with(self.context, node.id,
                                             None, mock.ANY)
        mock_leave_cluster.assert_called_once_with(self.context, node)

    @mock.patch.object(pb.Profile, 'leave_cluster')
    def test_node_leave_fail_update_server_metadata(self, mock_leave):
        node = nodem.Node('node1', PROFILE_ID, CLUSTER_ID, self.context,
                          index=1)
        mock_leave.return_value = False
        res = node.do_leave(self.context)
        self.assertFalse(res)
        self.assertNotEqual('', node.cluster_id)
        self.assertIsNone(node.updated_at)
        self.assertEqual(1, node.index)

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'check_object')
    def test_node_check(self, mock_check, mock_status):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.status = consts.NS_ACTIVE
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        mock_check.return_value = True
        res = node.do_check(self.context)

        self.assertTrue(res)
        mock_check.assert_called_once_with(self.context, node)
        mock_status.assert_called_once_with(self.context, consts.NS_ACTIVE,
                                            'Check: Node is ACTIVE.')

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'check_object')
    def test_node_check_warning(self, mock_check, mock_status):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.status = consts.NS_WARNING
        node.status_reason = 'bad news'
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        mock_check.return_value = True

        res = node.do_check(self.context)

        self.assertTrue(res)
        mock_check.assert_called_once_with(self.context, node)
        msg = ("Check: Physical object is ACTIVE but the node status was "
               "WARNING. %s") % node.status_reason
        mock_status.assert_called_once_with(self.context, consts.NS_WARNING,
                                            msg)

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'check_object')
    def test_node_check_not_active(self, mock_check, mock_status):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.status = consts.NS_WARNING
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        mock_check.return_value = False

        res = node.do_check(self.context)

        self.assertTrue(res)
        mock_status.assert_called_once_with(self.context, consts.NS_ERROR,
                                            'Check: Node is not ACTIVE.')

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'check_object')
    def test_node_check_check_with_exc(self, mock_check, mock_status):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        err = exception.EResourceOperation(op='checking', type='server',
                                           id=node.physical_id,
                                           message='failed get')
        mock_check.side_effect = err

        res = node.do_check(self.context)

        self.assertFalse(res)
        mock_status.assert_called_once_with(
            self.context,
            consts.NS_ERROR,
            "Failed in checking server '%s': failed get." % node.physical_id)

    def test_node_check_no_physical_id(self):
        node = nodem.Node('node1', PROFILE_ID, '')

        res = node.do_check(self.context)

        self.assertFalse(res)

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'check_object')
    def test_node_check_no_server(self, mock_check, mock_status):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'

        err = exception.EServerNotFound(type='server',
                                        id=node.physical_id,
                                        message='No Server found')
        mock_check.side_effect = err

        res = node.do_check(self.context)

        self.assertTrue(res)
        mock_status.assert_called_once_with(
            self.context, consts.NS_ERROR,
            "Failed in found server '%s': No Server found."
            % node.physical_id,
            physical_id=None)

    @mock.patch.object(pb.Profile, 'healthcheck_object')
    def test_node_healthcheck(self, mock_healthcheck):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.status = consts.NS_ACTIVE
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        mock_healthcheck.return_value = True
        res = node.do_healthcheck(self.context, consts.NODE_STATUS_POLLING)

        self.assertTrue(res)
        mock_healthcheck.assert_called_once_with(self.context, node,
                                                 consts.NODE_STATUS_POLLING)

    def test_node_healthcheck_no_physical_id(self):
        node = nodem.Node('node1', PROFILE_ID, '')

        res = node.do_healthcheck(self.context, consts.NODE_STATUS_POLLING)

        self.assertFalse(res)

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_new_object(self, mock_recover, mock_status):
        def set_status(*args, **kwargs):
            if args[1] == 'ACTIVE':
                node.physical_id = new_id
                node.data = {'recovery': 'RECREATE'}

        node = nodem.Node('node1', PROFILE_ID, '')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        new_id = '166db83b-b4a4-49ef-96a8-6c0fdd882d1a'

        # action = node_action.NodeAction(node.id, 'ACTION', self.ctx)

        mock_recover.return_value = new_id, True
        mock_status.side_effect = set_status
        action = mock.Mock()
        action.inputs = {'operation': 'SWIM'}

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        self.assertEqual('node1', node.name)
        self.assertEqual(new_id, node.physical_id)
        self.assertEqual(PROFILE_ID, node.profile_id)
        self.assertEqual({'recovery': 'RECREATE'}, node.data)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason='Recovery succeeded',
                      physical_id=new_id,
                      data={'recovery': 'RECREATE'})])

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_in_place(self, mock_recover, mock_status):
        node = nodem.Node('node1', PROFILE_ID, None)
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        mock_recover.return_value = node.physical_id, True
        action = mock.Mock(inputs={})

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_recover.assert_called_once_with(self.context, node)
        self.assertEqual('node1', node.name)
        self.assertEqual(PROFILE_ID, node.profile_id)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason='Recovery succeeded')])

    @mock.patch.object(nodem.Node, 'set_status')
    def test_node_recover_check_active(self, mock_status):
        node = nodem.Node('node1', PROFILE_ID, None)
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        node.status = 'ACTIVE'
        mock_check = self.patchobject(pb.Profile, 'check_object')
        mock_check.return_value = True
        action = mock.Mock(inputs={'check': True})

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_check.assert_called_once_with(self.context, node)
        mock_status.assert_called_once_with(self.context, consts.NS_ACTIVE,
                                            reason='Recover: Node is ACTIVE.')

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_check_error(self, mock_recover, mock_status):
        def set_status(*args, **kwargs):
            if args[1] == 'ACTIVE':
                node.physical_id = new_id
                node.data = {'recovery': 'recreate'}

        node = nodem.Node('node1', PROFILE_ID, '')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        new_id = '166db83b-b4a4-49ef-96a8-6c0fdd882d1a'
        mock_recover.return_value = new_id, True
        mock_status.side_effect = set_status
        mock_check = self.patchobject(pb.Profile, 'check_object')
        mock_check.return_value = False
        action = mock.Mock(inputs={'check': True})

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_check.assert_called_once_with(self.context, node)
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        self.assertEqual('node1', node.name)
        self.assertEqual(new_id, node.physical_id)
        self.assertEqual(PROFILE_ID, node.profile_id)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason='Recovery succeeded',
                      physical_id=new_id,
                      data={'recovery': 'RECREATE'})])

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_recreate(self, mock_recover, mock_status):
        def set_status(*args, **kwargs):
            if args[1] == 'ACTIVE':
                node.physical_id = new_id
                node.data = {'recovery': 'RECREATE'}

        node = nodem.Node('node1', PROFILE_ID, '', id='fake')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        new_id = '166db83b-b4a4-49ef-96a8-6c0fdd882d1a'
        mock_recover.return_value = new_id, True
        mock_status.side_effect = set_status
        mock_check = self.patchobject(pb.Profile, 'check_object')
        mock_check.return_value = False
        action = mock.Mock(
            outputs={}, inputs={'operation': 'RECREATE',
                                'check': True})

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_check.assert_called_once_with(self.context, node)
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        self.assertEqual('node1', node.name)
        self.assertEqual(new_id, node.physical_id)
        self.assertEqual(PROFILE_ID, node.profile_id)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason='Recovery succeeded',
                      physical_id=new_id,
                      data={'recovery': 'RECREATE'})])

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_check_exception(self, mock_recover, mock_status):
        def set_status(*args, **kwargs):
            if args[1] == 'ACTIVE':
                node.physical_id = new_id
                node.data = {'recovery': 'RECREATE'}

        node = nodem.Node('node1', PROFILE_ID, '')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        new_id = '166db83b-b4a4-49ef-96a8-6c0fdd882d1a'
        mock_recover.return_value = new_id, True
        mock_status.side_effect = set_status
        mock_check = self.patchobject(pb.Profile, 'check_object')
        mock_check.side_effect = exception.EResourceOperation(
            op='checking',
            type='server',
            id=node.physical_id,
            reason='Boom!'
        )
        action = mock.Mock(inputs={'operation': 'boom',
                                   'check': True})

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_check.assert_called_once_with(self.context, node)
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        self.assertEqual('node1', node.name)
        self.assertEqual(new_id, node.physical_id)
        self.assertEqual(PROFILE_ID, node.profile_id)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason='Recovery succeeded',
                      physical_id=new_id,
                      data={'recovery': 'RECREATE'})])

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_failed_recover(self, mock_recover, mock_status):
        node = nodem.Node('node1', PROFILE_ID, None)
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        mock_recover.return_value = node.physical_id, None
        action = mock.Mock(inputs={'operation': 'RECREATE'})

        res = node.do_recover(self.context, action)

        self.assertFalse(res)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ERROR,
                      reason='Recovery failed')])

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_failed_recover_with_old_physical_id(self,
                                                              mock_recover,
                                                              mock_status):
        node = nodem.Node('node1', PROFILE_ID, None)
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'

        action = mock.Mock(
            inputs={'operation': consts.RECOVER_RECREATE, 'check': True})
        mock_recover.side_effect = exception.EResourceOperation(
            op=consts.RECOVER_RECREATE,
            type='server',
            id=node.physical_id,
            resource_id=node.physical_id,
            reason='Recovery failed',
        )
        res = node.do_recover(self.context, action)

        self.assertFalse(res)
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        reason = ("Failed in RECREATE server 'd94d6333-82e6-4f87-b7ab-b786776d"
                  "f9d1': Internal error happened.")
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ERROR,
                      reason=str(reason),
                      physical_id=node.physical_id)])

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_failed_recover_with_new_physical_id(self,
                                                              mock_recover,
                                                              mock_status):
        def set_status(*args, **kwargs):
            if args[1] == consts.NS_ERROR:
                node.physical_id = new_id

        node = nodem.Node('node1', PROFILE_ID, None)
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        new_id = '166db83b-b4a4-49ef-96a8-6c0fdd882d1a'

        mock_status.side_effect = set_status
        action = mock.Mock(inputs={'operation': consts.RECOVER_RECREATE,
                                   'check': True})
        mock_recover.side_effect = exception.EResourceOperation(
            op=consts.RECOVER_RECREATE,
            type='server',
            id=node.physical_id,
            resource_id=new_id,
            reason='Recovery failed',
        )
        res = node.do_recover(self.context, action)

        self.assertFalse(res)
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        reason = ("Failed in RECREATE server 'd94d6333-82e6-4f87-b7ab-b786776d"
                  "f9d1': Internal error happened.")
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ERROR,
                      reason=str(reason),
                      physical_id=new_id)])

    def test_node_recover_no_physical_id_reboot_op(self):
        node = nodem.Node('node1', PROFILE_ID, None)
        action = mock.Mock(inputs={'operation': 'REBOOT'})

        res = node.do_recover(self.context, action)

        self.assertFalse(res)

    def test_node_recover_no_physical_id_rebuild_op(self):
        node = nodem.Node('node1', PROFILE_ID, None)
        action = mock.Mock(inputs={'operation': 'REBUILD'})

        res = node.do_recover(self.context, action)

        self.assertFalse(res)

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_no_physical_id_no_op(self, mock_recover,
                                               mock_status):
        def set_status(*args, **kwargs):
            if args[1] == 'ACTIVE':
                node.physical_id = new_id
                node.data = {'recovery': 'RECREATE'}

        node = nodem.Node('node1', PROFILE_ID, '', id='fake')
        new_id = '166db83b-b4a4-49ef-96a8-6c0fdd882d1a'
        mock_recover.return_value = new_id, True
        mock_status.side_effect = set_status
        mock_check = self.patchobject(pb.Profile, 'check_object')
        mock_check.return_value = False
        action = mock.Mock(
            outputs={}, inputs={})

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_check.assert_not_called()
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        self.assertEqual('node1', node.name)
        self.assertEqual(new_id, node.physical_id)
        self.assertEqual(PROFILE_ID, node.profile_id)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason='Recovery succeeded',
                      physical_id=new_id,
                      data={'recovery': 'RECREATE'})])

    @mock.patch.object(nodem.Node, 'set_status')
    @mock.patch.object(pb.Profile, 'recover_object')
    def test_node_recover_no_physical_id_recreate_op(self, mock_recover,
                                                     mock_status):
        def set_status(*args, **kwargs):
            if args[1] == 'ACTIVE':
                node.physical_id = new_id
                node.data = {'recovery': 'RECREATE'}

        node = nodem.Node('node1', PROFILE_ID, '', id='fake')
        new_id = '166db83b-b4a4-49ef-96a8-6c0fdd882d1a'
        mock_recover.return_value = new_id, True
        mock_status.side_effect = set_status
        mock_check = self.patchobject(pb.Profile, 'check_object')
        mock_check.return_value = False
        action = mock.Mock(
            outputs={}, inputs={'operation': 'RECREATE',
                                'check': True})

        res = node.do_recover(self.context, action)

        self.assertTrue(res)
        mock_check.assert_called_once_with(self.context, node)
        mock_recover.assert_called_once_with(
            self.context, node, **action.inputs)
        self.assertEqual('node1', node.name)
        self.assertEqual(new_id, node.physical_id)
        self.assertEqual(PROFILE_ID, node.profile_id)
        mock_status.assert_has_calls([
            mock.call(self.context, 'RECOVERING',
                      reason='Recovery in progress'),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason='Recovery succeeded',
                      physical_id=new_id,
                      data={'recovery': 'RECREATE'})])

    @mock.patch.object(nodem.Node, 'set_status')
    def test_node_recover_operation_not_support(self, mock_set_status):
        node = nodem.Node('node1', PROFILE_ID, None)
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        action = mock.Mock(
            outputs={}, inputs={'operation': 'foo'})

        res = node.do_recover(self.context, action)
        self.assertEqual({}, action.outputs)
        self.assertFalse(res)

    @mock.patch.object(nodem.Node, 'set_status')
    def test_node_recover_operation_not_string(self, mock_set_status):
        node = nodem.Node('node1', PROFILE_ID, None)
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        action = mock.Mock(
            outputs={}, inputs={'operation': 'foo'})

        res = node.do_recover(self.context, action)
        self.assertEqual({}, action.outputs)
        self.assertFalse(res)

    @mock.patch.object(nodem.Node, 'set_status')
    def test_node_operation(self, mock_set_status):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        x_profile = mock.Mock()
        x_profile.handle_dance = mock.Mock(return_value=True)
        node.rt['profile'] = x_profile

        inputs = {'operation': 'dance', 'params': {'style': 'tango'}}
        res = node.do_operation(self.context, **inputs)

        self.assertTrue(res)
        mock_set_status.assert_has_calls([
            mock.call(self.context, consts.NS_OPERATING,
                      reason="Operation 'dance' in progress"),
            mock.call(self.context, consts.NS_ACTIVE,
                      reason="Operation 'dance' succeeded")
        ])
        x_profile.handle_dance.assert_called_once_with(node, style='tango')

    def test_node_operation_no_physical_id(self):
        node = nodem.Node('node1', PROFILE_ID, None)
        inputs = {'operation': 'dance', 'params': {'style': 'tango'}}

        res = node.do_operation(self.context, **inputs)

        self.assertFalse(res)

    @mock.patch.object(nodem.Node, 'set_status')
    def test_node_operation_failed_op(self, mock_set_status):
        node = nodem.Node('node1', PROFILE_ID, '')
        node.physical_id = 'd94d6333-82e6-4f87-b7ab-b786776df9d1'
        x_profile = mock.Mock()
        err = exception.EResourceOperation(
            op='dance', type='container', id='test_id', message='Boom')
        x_profile.handle_dance = mock.Mock(side_effect=err)
        node.rt['profile'] = x_profile

        inputs = {'operation': 'dance', 'params': {'style': 'tango'}}
        res = node.do_operation(self.context, **inputs)

        self.assertFalse(res)
        mock_set_status.assert_has_calls([
            mock.call(self.context, consts.NS_OPERATING,
                      reason="Operation 'dance' in progress"),
            mock.call(self.context, consts.NS_ERROR,
                      reason="Failed in dance container 'test_id': Boom.")
        ])
        x_profile.handle_dance.assert_called_once_with(node, style='tango')

    def _verify_execution_create_args(self, expected_name,
                                      expected_inputs_dict, wfc):
        wfc.execution_create.assert_called_once_with(mock.ANY, mock.ANY)
        actual_call_args, call_kwargs = wfc.execution_create.call_args

        # execution_create parameters are name and inputs
        actual_call_name, actual_call_inputs = actual_call_args

        # actual_call_inputs is string representation of a dictionary.
        # convert actual_call_inputs to json, then dump it back as string
        # sorted by key
        final_actual_call_inputs = jsonutils.dumps(
            jsonutils.loads(actual_call_inputs), sort_keys=True)

        # dump expected_inputs_dict as string sorted by key
        final_expected_inputs = jsonutils.dumps(
            expected_inputs_dict, sort_keys=True)

        # compare the sorted input strings along with the names
        self.assertEqual(actual_call_name, expected_name)
        self.assertEqual(final_actual_call_inputs, final_expected_inputs)

    def test_run_workflow(self):
        node = nodem.Node('node1', PROFILE_ID, 'FAKE_CLUSTER')
        node.physical_id = 'FAKE_NODE'

        wfc = mock.Mock()
        wfc.workflow_find.return_value = None
        wfc.workflow_create = mock.Mock()
        wfc.execution_create = mock.Mock()
        x_profile = mock.Mock()
        x_profile.workflow = mock.Mock(return_value=wfc)
        node.rt['profile'] = x_profile

        options = {
            'workflow_name': 'foo',
            'inputs': {
                'definition': {
                    'bar': 'baz'
                },
                'FAKE_KEY1': 'FAKE_VALUE1',
                'FAKE_KEY2': 'FAKE_VALUE2',
            }
        }

        res = node.run_workflow(**options)

        self.assertTrue(res)
        x_profile.workflow.assert_called_once_with(node)
        wfc.workflow_find.assert_called_once_with('foo')
        wfc.workflow_create.assert_called_once_with(
            {'bar': 'baz'}, scope='private')

        final_dict = {
            'cluster_id': 'FAKE_CLUSTER',
            'node_id': 'FAKE_NODE',
            'FAKE_KEY1': 'FAKE_VALUE1',
            'FAKE_KEY2': 'FAKE_VALUE2',
        }
        self._verify_execution_create_args('foo', final_dict, wfc)

    def test_run_workflow_no_physical_id(self):
        node = nodem.Node('node1', PROFILE_ID, 'FAKE_CLUSTER')
        node.physical_id = None

        res = node.run_workflow()

        self.assertFalse(res)

    def test_run_workflow_workflow_is_found(self):
        node = nodem.Node('node1', PROFILE_ID, 'FAKE_CLUSTER')
        node.physical_id = 'FAKE_NODE'

        wfc = mock.Mock()
        wfc.workflow_find.return_value = mock.Mock(definition={'bar': 'baz'})
        wfc.workflow_create = mock.Mock()
        wfc.execution_create = mock.Mock()
        x_profile = mock.Mock()
        x_profile.workflow = mock.Mock(return_value=wfc)
        node.rt['profile'] = x_profile

        options = {
            'workflow_name': 'foo',
            'inputs': {
                'FAKE_KEY1': 'FAKE_VALUE1',
                'FAKE_KEY2': 'FAKE_VALUE2',
            }
        }

        res = node.run_workflow(**options)

        self.assertTrue(res)
        x_profile.workflow.assert_called_once_with(node)
        wfc.workflow_find.assert_called_once_with('foo')
        self.assertEqual(0, wfc.workflow_create.call_count)
        final_dict = {
            'cluster_id': 'FAKE_CLUSTER',
            'node_id': 'FAKE_NODE',
            'FAKE_KEY1': 'FAKE_VALUE1',
            'FAKE_KEY2': 'FAKE_VALUE2',
        }
        self._verify_execution_create_args('foo', final_dict, wfc)

    def test_run_workflow_failed_creation(self):
        node = nodem.Node('node1', PROFILE_ID, 'FAKE_CLUSTER')
        node.physical_id = 'FAKE_NODE'

        wfc = mock.Mock()
        wfc.workflow_find.return_value = None
        err = exception.InternalError(message='boom')
        wfc.workflow_create.side_effect = err
        wfc.execution_create = mock.Mock()
        x_profile = mock.Mock()
        x_profile.workflow = mock.Mock(return_value=wfc)
        node.rt['profile'] = x_profile

        options = {
            'workflow_name': 'foo',
            'inputs': {
                'definition': {'bar': 'baz'},
                'FAKE_KEY1': 'FAKE_VALUE1',
                'FAKE_KEY2': 'FAKE_VALUE2',
            }
        }

        ex = self.assertRaises(exception.EResourceOperation,
                               node.run_workflow,
                               **options)

        self.assertEqual("Failed in executing workflow 'foo': boom.",
                         str(ex))
        x_profile.workflow.assert_called_once_with(node)
        wfc.workflow_find.assert_called_once_with('foo')
        wfc.workflow_create.assert_called_once_with(
            {'bar': 'baz'}, scope='private')
        self.assertEqual(0, wfc.execution_create.call_count)

    def test_run_workflow_failed_execution(self):
        node = nodem.Node('node1', PROFILE_ID, 'FAKE_CLUSTER')
        node.physical_id = 'FAKE_NODE'

        wfc = mock.Mock()
        wfc.workflow_find.return_value = None
        wfc.workflow_create = mock.Mock()
        err = exception.InternalError(message='boom')
        wfc.execution_create.side_effect = err
        x_profile = mock.Mock()
        x_profile.workflow = mock.Mock(return_value=wfc)
        node.rt['profile'] = x_profile

        options = {
            'workflow_name': 'foo',
            'inputs': {
                'definition': {'bar': 'baz'},
                'FAKE_KEY1': 'FAKE_VALUE1',
                'FAKE_KEY2': 'FAKE_VALUE2',
            }
        }

        ex = self.assertRaises(exception.EResourceOperation,
                               node.run_workflow,
                               **options)

        self.assertEqual("Failed in executing workflow 'foo': boom.",
                         str(ex))
        x_profile.workflow.assert_called_once_with(node)
        wfc.workflow_find.assert_called_once_with('foo')
        wfc.workflow_create.assert_called_once_with(
            {'bar': 'baz'}, scope='private')
        final_dict = {
            'cluster_id': 'FAKE_CLUSTER',
            'node_id': 'FAKE_NODE',
            'FAKE_KEY1': 'FAKE_VALUE1',
            'FAKE_KEY2': 'FAKE_VALUE2',
        }
        self._verify_execution_create_args('foo', final_dict, wfc)
