# Copyright [2015] Hewlett-Packard Development Company, L.P.
# 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 datetime

from unittest.mock import Mock
from unittest.mock import patch

from trove.cluster.models import ClusterTasks as ClusterTaskStatus
from trove.cluster.models import DBCluster
from trove.common.exception import GuestError
from trove.common.strategies.cluster.experimental.galera_common.taskmanager \
    import GaleraCommonClusterTasks
from trove.common.strategies.cluster.experimental.galera_common.taskmanager \
    import GaleraCommonTaskManagerStrategy
from trove.datastore import models as datastore_models
from trove.instance.models import BaseInstance
from trove.instance.models import DBInstance
from trove.instance.models import Instance
from trove.instance.models import InstanceServiceStatus
from trove.instance.models import InstanceTasks
from trove.instance.service_status import ServiceStatuses
from trove.tests.unittests import trove_testtools
from trove.tests.unittests.util import util


class GaleraClusterTasksTest(trove_testtools.TestCase):
    def setUp(self):
        super(GaleraClusterTasksTest, self).setUp()
        util.init_db()
        self.cluster_id = "1232"
        self.cluster_name = "Cluster-1234"
        self.tenant_id = "6789"
        self.db_cluster = DBCluster(ClusterTaskStatus.NONE,
                                    id=self.cluster_id,
                                    created=str(datetime.date),
                                    updated=str(datetime.date),
                                    name=self.cluster_name,
                                    task_id=ClusterTaskStatus.NONE._code,
                                    tenant_id=self.tenant_id,
                                    datastore_version_id="1",
                                    deleted=False)
        self.dbinst1 = DBInstance(InstanceTasks.NONE, id="1", name="member1",
                                  compute_instance_id="compute-1",
                                  task_id=InstanceTasks.NONE._code,
                                  task_description=InstanceTasks.NONE._db_text,
                                  volume_id="volume-1",
                                  datastore_version_id="1",
                                  cluster_id=self.cluster_id,
                                  type="member")
        self.dbinst2 = DBInstance(InstanceTasks.NONE, id="2", name="member2",
                                  compute_instance_id="compute-2",
                                  task_id=InstanceTasks.NONE._code,
                                  task_description=InstanceTasks.NONE._db_text,
                                  volume_id="volume-2",
                                  datastore_version_id="1",
                                  cluster_id=self.cluster_id,
                                  type="member")
        self.dbinst3 = DBInstance(InstanceTasks.NONE, id="3", name="member3",
                                  compute_instance_id="compute-3",
                                  task_id=InstanceTasks.NONE._code,
                                  task_description=InstanceTasks.NONE._db_text,
                                  volume_id="volume-3",
                                  datastore_version_id="1",
                                  cluster_id=self.cluster_id,
                                  type="member")
        mock_ds1 = Mock()
        mock_ds1.name = 'pxc'
        mock_dv1 = Mock()
        mock_dv1.name = '7.1'
        self.clustertasks = GaleraCommonClusterTasks(
            Mock(), self.db_cluster, datastore=mock_ds1,
            datastore_version=mock_dv1)
        self.cluster_context = {
            'replication_user': {
                'name': "name",
                'password': "password",
            },
            'cluster_name': self.cluster_name,
            'admin_password': "admin_password"
        }

    @patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
    @patch.object(DBInstance, 'find_by')
    @patch.object(InstanceServiceStatus, 'find_by')
    @patch('trove.taskmanager.models.LOG')
    def test_all_instances_ready_with_server_error(self,
                                                   mock_logging, mock_find,
                                                   mock_db_find, mock_update):
        (mock_find.return_value.
         get_status.return_value) = ServiceStatuses.NEW
        (mock_db_find.return_value.
         get_task_status.return_value) = InstanceTasks.BUILDING_ERROR_SERVER
        ret_val = self.clustertasks._all_instances_ready(["1", "2", "3", "4"],
                                                         self.cluster_id)
        mock_update.assert_called_with(self.cluster_id, None)
        self.assertFalse(ret_val)

    @patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
    @patch.object(DBInstance, 'find_by')
    @patch.object(InstanceServiceStatus, 'find_by')
    @patch('trove.taskmanager.models.LOG')
    def test_all_instances_ready_bad_status(self, mock_logging,
                                            mock_find, mock_db_find,
                                            mock_update):
        (mock_find.return_value.
         get_status.return_value) = ServiceStatuses.FAILED
        (mock_db_find.return_value.
         get_task_status.return_value) = InstanceTasks.NONE
        ret_val = self.clustertasks._all_instances_ready(["1", "2", "3", "4"],
                                                         self.cluster_id)
        mock_update.assert_called_with(self.cluster_id, None)
        self.assertFalse(ret_val)

    @patch.object(DBInstance, 'find_by')
    @patch.object(InstanceServiceStatus, 'find_by')
    def test_all_instances_ready(self, mock_find, mock_db_find):
        (mock_find.return_value.
         get_status.return_value) = ServiceStatuses.INSTANCE_READY
        (mock_db_find.return_value.
         get_task_status.return_value) = InstanceTasks.NONE
        ret_val = self.clustertasks._all_instances_ready(["1", "2", "3", "4"],
                                                         self.cluster_id)
        self.assertTrue(ret_val)

    @patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
    @patch.object(GaleraCommonClusterTasks, '_all_instances_ready',
                  return_value=False)
    @patch.object(Instance, 'load')
    @patch.object(DBInstance, 'find_all')
    @patch.object(datastore_models.Datastore, 'load')
    @patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
    @patch('trove.common.strategies.cluster.experimental.galera_common.'
           'taskmanager.LOG')
    def test_create_cluster_instance_not_ready(self, mock_logging, mock_dv,
                                               mock_ds, mock_find_all,
                                               mock_load, mock_ready,
                                               mock_update):
        mock_find_all.return_value.all.return_value = [self.dbinst1]
        mock_load.return_value = BaseInstance(Mock(),
                                              self.dbinst1, Mock(),
                                              InstanceServiceStatus(
                                                  ServiceStatuses.NEW))
        self.clustertasks.create_cluster(Mock(), self.cluster_id)
        mock_update.assert_called_with(self.cluster_id)

    @patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
    @patch.object(GaleraCommonClusterTasks, 'reset_task')
    @patch.object(GaleraCommonClusterTasks, 'get_ip')
    @patch.object(GaleraCommonClusterTasks, '_all_instances_ready')
    @patch.object(Instance, 'load')
    @patch.object(DBInstance, 'find_all')
    @patch.object(datastore_models.Datastore, 'load')
    @patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
    @patch('trove.common.strategies.cluster.experimental.galera_common.'
           'taskmanager.LOG')
    def test_create_cluster_fail(self, mock_logging, mock_dv, mock_ds,
                                 mock_find_all, mock_load, mock_ready, mock_ip,
                                 mock_reset_task, mock_update_status):
        mock_find_all.return_value.all.return_value = [self.dbinst1]
        mock_load.return_value = BaseInstance(Mock(),
                                              self.dbinst1, Mock(),
                                              InstanceServiceStatus(
                                                  ServiceStatuses.NEW))
        mock_ip.return_value = "10.0.0.2"
        guest_client = Mock()
        guest_client.install_cluster = Mock(side_effect=GuestError("Error"))
        with patch.object(GaleraCommonClusterTasks, 'get_guest',
                          return_value=guest_client):
            self.clustertasks.create_cluster(Mock(), self.cluster_id)
            mock_update_status.assert_called_with('1232')
            mock_reset_task.assert_called_with()

    @patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
    @patch('trove.common.strategies.cluster.experimental.galera_common.'
           'taskmanager.LOG')
    def test_grow_cluster_does_not_exist(self, mock_logging,
                                         mock_update_status):
        context = Mock()
        bad_cluster_id = '1234'
        new_instances = [Mock(), Mock()]
        self.clustertasks.grow_cluster(context, bad_cluster_id, new_instances)
        mock_update_status.assert_called_with(
            '1234',
            status=InstanceTasks.GROWING_ERROR)

    @patch.object(GaleraCommonClusterTasks, '_check_cluster_for_root')
    @patch.object(GaleraCommonClusterTasks, 'reset_task')
    @patch.object(GaleraCommonClusterTasks, '_render_cluster_config')
    @patch.object(GaleraCommonClusterTasks, 'get_ip')
    @patch.object(GaleraCommonClusterTasks, 'get_guest')
    @patch.object(GaleraCommonClusterTasks, '_all_instances_ready',
                  return_value=True)
    @patch.object(Instance, 'load')
    @patch.object(DBInstance, 'find_all')
    @patch.object(datastore_models.Datastore, 'load')
    @patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
    def test_grow_cluster_successs(self, mock_dv, mock_ds, mock_find_all,
                                   mock_load, mock_ready, mock_guest, mock_ip,
                                   mock_render, mock_reset_task,
                                   mock_check_root):
        mock_find_all.return_value.all.return_value = [self.dbinst1]

        mock_ip.return_value = "10.0.0.2"
        context = Mock()
        new_instances = [Mock(), Mock()]
        mock_guest.get_cluster_context = Mock(
            return_value=self.cluster_context)
        mock_guest.reset_admin_password = Mock()
        self.clustertasks.grow_cluster(context, self.cluster_id,
                                       new_instances)
        mock_reset_task.assert_called_with()

    @patch.object(GaleraCommonClusterTasks, 'reset_task')
    @patch.object(Instance, 'load')
    @patch.object(Instance, 'delete')
    @patch.object(DBInstance, 'find_all')
    @patch.object(GaleraCommonClusterTasks, 'get_guest')
    @patch.object(GaleraCommonClusterTasks, 'get_ip')
    @patch.object(GaleraCommonClusterTasks, '_render_cluster_config')
    def test_shrink_cluster_success(self, mock_render, mock_ip, mock_guest,
                                    mock_find_all, mock_delete, mock_load,
                                    mock_reset_task):
        mock_find_all.return_value.all.return_value = [self.dbinst1]
        context = Mock()
        remove_instances = [Mock()]
        mock_ip.return_value = "10.0.0.2"
        mock_guest.get_cluster_context = Mock(
            return_value=self.cluster_context)
        self.clustertasks.shrink_cluster(context, self.cluster_id,
                                         remove_instances)
        mock_reset_task.assert_called_with()

    @patch.object(Instance, 'load')
    @patch.object(GaleraCommonClusterTasks, 'update_statuses_on_failure')
    @patch('trove.common.strategies.cluster.experimental.galera_common.'
           'taskmanager.LOG')
    def test_shrink_cluster_does_not_exist(self, mock_logging,
                                           mock_update_status,
                                           mock_load):
        context = Mock()
        bad_cluster_id = '1234'
        remove_instances = [Mock()]
        self.clustertasks.shrink_cluster(context, bad_cluster_id,
                                         remove_instances)
        mock_update_status.assert_called_with(
            '1234',
            status=InstanceTasks.SHRINKING_ERROR)


class GaleraTaskManagerStrategyTest(trove_testtools.TestCase):

    def test_task_manager_cluster_tasks_class(self):
        strategy = GaleraCommonTaskManagerStrategy()
        self.assertFalse(
            hasattr(strategy.task_manager_cluster_tasks_class,
                    'rebuild_cluster'))
        self.assertTrue(callable(
            strategy.task_manager_cluster_tasks_class.create_cluster))

    def test_task_manager_api_class(self):
        strategy = GaleraCommonTaskManagerStrategy()
        self.assertFalse(hasattr(strategy.task_manager_api_class,
                                 'add_new_node'))
