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

from mock import MagicMock
from mock import patch
from testtools.matchers import Is
import trove_testtools

from trove.common.context import TroveContext
from trove.common.exception import DatastoreOperationNotSupported
from trove.common import instance as rd_instance
from trove.guestagent.datastore.experimental.vertica.manager import Manager
from trove.guestagent.datastore.experimental.vertica.service import (
    VerticaAppStatus)
from trove.guestagent.datastore.experimental.vertica.service import VerticaApp
from trove.guestagent import dbaas
from trove.guestagent import volume
from trove.guestagent.volume import VolumeDevice


class GuestAgentManagerTest(trove_testtools.TestCase):
    def setUp(self):
        super(GuestAgentManagerTest, self).setUp()
        self.context = TroveContext()
        self.manager = Manager()
        self.origin_format = volume.VolumeDevice.format
        self.origin_migrate_data = volume.VolumeDevice.migrate_data
        self.origin_mount = volume.VolumeDevice.mount
        self.origin_unmount = volume.VolumeDevice.unmount
        self.origin_mount_points = volume.VolumeDevice.mount_points
        self.origin_set_read = volume.VolumeDevice.set_readahead_size
        self.origin_install_vertica = VerticaApp.install_vertica
        self.origin_create_db = VerticaApp.create_db
        self.origin_stop_db = VerticaApp.stop_db
        self.origin_start_db = VerticaApp.start_db
        self.origin_restart = VerticaApp.restart
        self.origin_install_if = VerticaApp.install_if_needed
        self.origin_complete_install = VerticaApp.complete_install_or_restart
        self.origin_enable_root = VerticaApp.enable_root
        self.origin_is_root_enabled = VerticaApp.is_root_enabled
        self.origin_prepare_for_install_vertica = (
            VerticaApp.prepare_for_install_vertica)

    def tearDown(self):
        super(GuestAgentManagerTest, self).tearDown()
        volume.VolumeDevice.format = self.origin_format
        volume.VolumeDevice.migrate_data = self.origin_migrate_data
        volume.VolumeDevice.mount = self.origin_mount
        volume.VolumeDevice.unmount = self.origin_unmount
        volume.VolumeDevice.mount_points = self.origin_mount_points
        volume.VolumeDevice.set_readahead_size = self.origin_set_read
        VerticaApp.create_db = self.origin_create_db
        VerticaApp.install_vertica = self.origin_install_vertica
        VerticaApp.stop_db = self.origin_stop_db
        VerticaApp.start_db = self.origin_start_db
        VerticaApp.restart = self.origin_restart
        VerticaApp.install_if_needed = self.origin_install_if
        VerticaApp.complete_install_or_restart = self.origin_complete_install
        VerticaApp.enable_root = self.origin_enable_root
        VerticaApp.is_root_enabled = self.origin_is_root_enabled
        VerticaApp.prepare_for_install_vertica = (
            self.origin_prepare_for_install_vertica)

    def test_update_status(self):
        mock_status = MagicMock()
        self.manager.appStatus = mock_status
        self.manager.update_status(self.context)
        mock_status.update.assert_any_call()

    def _prepare_dynamic(self, packages,
                         config_content='MockContent', device_path='/dev/vdb',
                         backup_id=None,
                         overrides=None, is_mounted=False):
        # covering all outcomes is starting to cause trouble here
        expected_vol_count = 1 if device_path else 0
        if not backup_id:
            backup_info = {'id': backup_id,
                           'location': 'fake-location',
                           'type': 'InnoBackupEx',
                           'checksum': 'fake-checksum',
                           }

        mock_status = MagicMock()
        self.manager.appStatus = mock_status

        mock_status.begin_install = MagicMock(return_value=None)
        path_exists_function = MagicMock(return_value=True)
        volume.VolumeDevice.format = MagicMock(return_value=None)
        volume.VolumeDevice.migrate_data = MagicMock(return_value=None)
        volume.VolumeDevice.mount = MagicMock(return_value=None)
        mount_points = []
        if is_mounted:
            mount_points = ['/mnt']
        VolumeDevice.mount_points = MagicMock(return_value=mount_points)
        VolumeDevice.unmount = MagicMock(return_value=None)

        VerticaApp.install_if_needed = MagicMock(return_value=None)
        VerticaApp.install_vertica = MagicMock(return_value=None)
        VerticaApp.create_db = MagicMock(return_value=None)
        VerticaApp.prepare_for_install_vertica = MagicMock(return_value=None)
        VerticaApp.complete_install_or_restart = MagicMock(return_value=None)
        # invocation
        self.manager.prepare(context=self.context, packages=packages,
                             config_contents=config_content,
                             databases=None,
                             memory_mb='2048', users=None,
                             device_path=device_path,
                             mount_point="/var/lib/vertica",
                             backup_info=backup_info,
                             overrides=None,
                             cluster_config=None,
                             path_exists_function=path_exists_function)

        self.assertEqual(expected_vol_count, VolumeDevice.format.call_count)
        self.assertEqual(expected_vol_count,
                         VolumeDevice.migrate_data.call_count)
        self.assertEqual(expected_vol_count,
                         VolumeDevice.mount_points.call_count)
        if is_mounted:
            self.assertEqual(1, VolumeDevice.unmount.call_count)
        else:
            self.assertEqual(0, VolumeDevice.unmount.call_count)

        VerticaApp.install_if_needed.assert_any_call(packages)
        VerticaApp.prepare_for_install_vertica.assert_any_call()
        VerticaApp.install_vertica.assert_any_call()
        VerticaApp.create_db.assert_any_call()
        VerticaApp.complete_install_or_restart.assert_any_call()

    def test_prepare_pkg(self):
        self._prepare_dynamic(['vertica'])

    def test_prepare_no_pkg(self):
        self._prepare_dynamic([])

    def test_restart(self):
        mock_status = MagicMock()
        self.manager.appStatus = mock_status
        VerticaApp.restart = MagicMock(return_value=None)
        # invocation
        self.manager.restart(self.context)
        # verification/assertion
        VerticaApp.restart.assert_any_call()

    def test_stop_db(self):
        mock_status = MagicMock()
        self.manager.appStatus = mock_status
        VerticaApp.stop_db = MagicMock(return_value=None)
        # invocation
        self.manager.stop_db(self.context)
        # verification/assertion
        VerticaApp.stop_db.assert_any_call(do_not_start_on_reboot=False)

    @patch.object(VerticaApp, 'install_vertica')
    @patch.object(VerticaApp, '_export_conf_to_members')
    @patch.object(VerticaApp, 'create_db')
    def test_install_cluster(self, mock_install, mock_export, mock_create_db):
        members = ['test1', 'test2']
        self.manager.install_cluster(self.context, members)
        mock_install.assert_called_with('test1,test2')
        mock_export.assert_called_with(members)
        mock_create_db.assert_called_with('test1,test2')

    @patch.object(VerticaAppStatus, 'set_status')
    @patch.object(VerticaApp, 'install_cluster',
                  side_effect=RuntimeError("Boom!"))
    def test_install_cluster_failure(self, mock_install, mock_set_status):
        members = ["test1", "test2"]
        self.assertRaises(RuntimeError, self.manager.install_cluster,
                          self.context, members)
        mock_set_status.assert_called_with(rd_instance.ServiceStatuses.FAILED)

    @patch.object(volume.VolumeDevice, 'mount_points', return_value=[])
    @patch.object(volume.VolumeDevice, 'unmount_device', return_value=None)
    @patch.object(volume.VolumeDevice, 'mount', return_value=None)
    @patch.object(volume.VolumeDevice, 'migrate_data', return_value=None)
    @patch.object(volume.VolumeDevice, 'format', return_value=None)
    @patch.object(VerticaApp, 'prepare_for_install_vertica')
    @patch.object(VerticaApp, 'install_if_needed')
    @patch.object(VerticaAppStatus, 'begin_install')
    def _prepare_method(self, instance_id, instance_type, *args):
        cluster_config = {"id": instance_id,
                          "instance_type": instance_type}

        # invocation
        self.manager.prepare(context=self.context, databases=None,
                             packages=['vertica'],
                             memory_mb='2048', users=None,
                             mount_point='/var/lib/vertica',
                             overrides=None,
                             cluster_config=cluster_config)

    @patch.object(VerticaAppStatus, 'set_status')
    def test_prepare_member(self, mock_set_status):
        self._prepare_method("test-instance-3", "member")
        mock_set_status.assert_called_with(
            rd_instance.ServiceStatuses.BUILD_PENDING)

    def test_reset_configuration(self):
        try:
            configuration = {'config_contents': 'some junk'}
            self.manager.reset_configuration(self.context, configuration)
        except Exception:
            self.fail("reset_configuration raised exception unexpectedly.")

    def test_rpc_ping(self):
        output = self.manager.rpc_ping(self.context)
        self.assertTrue(output)

    @patch.object(VerticaAppStatus, 'set_status')
    def test_prepare_invalid_cluster_config(self, mock_set_status):
        self._prepare_method("test-instance-3", "query_router")
        mock_set_status.assert_called_with(
            rd_instance.ServiceStatuses.FAILED)

    def test_get_filesystem_stats(self):
        with patch.object(dbaas, 'get_filesystem_volume_stats'):
            self.manager.get_filesystem_stats(self.context, '/var/lib/vertica')
            dbaas.get_filesystem_volume_stats.assert_any_call(
                '/var/lib/vertica')

    def test_mount_volume(self):
        with patch.object(volume.VolumeDevice, 'mount', return_value=None):
            self.manager.mount_volume(self.context,
                                      device_path='/dev/vdb',
                                      mount_point='/var/lib/vertica')
            test_mount = volume.VolumeDevice.mount.call_args_list[0]
            test_mount.assert_called_with('/var/lib/vertica', False)

    def test_unmount_volume(self):
        with patch.object(volume.VolumeDevice, 'unmount', return_value=None):
            self.manager.unmount_volume(self.context, device_path='/dev/vdb')
            test_unmount = volume.VolumeDevice.unmount.call_args_list[0]
            test_unmount.assert_called_with('/var/lib/vertica')

    def test_resize_fs(self):
        with patch.object(volume.VolumeDevice, 'resize_fs', return_value=None):
            self.manager.resize_fs(self.context, device_path='/dev/vdb')
            test_resize_fs = volume.VolumeDevice.resize_fs.call_args_list[0]
            test_resize_fs.assert_called_with('/var/lib/vertica')

    def test_cluster_complete(self):
        mock_status = MagicMock()
        mock_status.set_status = MagicMock()
        self.manager.appStatus = mock_status
        mock_status._get_actual_db_status = MagicMock(
            return_value=rd_instance.ServiceStatuses.RUNNING)
        self.manager.cluster_complete(self.context)
        mock_status.set_status.assert_called_with(
            rd_instance.ServiceStatuses.RUNNING)

    def test_get_public_keys(self):
        with patch.object(VerticaApp, 'get_public_keys',
                          return_value='some_key'):
            test_key = self.manager.get_public_keys(self.context, 'test_user')
            self.assertEqual('some_key', test_key)

    def test_authorize_public_keys(self):
        with patch.object(VerticaApp, 'authorize_public_keys',
                          return_value=None):
            self.manager.authorize_public_keys(self.context,
                                               'test_user',
                                               'some_key')
            VerticaApp.authorize_public_keys.assert_any_call(
                'test_user', 'some_key')

    def test_start_db_with_conf_changes(self):
        with patch.object(VerticaApp, 'start_db_with_conf_changes'):
            self.manager.start_db_with_conf_changes(self.context, 'something')
            VerticaApp.start_db_with_conf_changes.assert_any_call('something')

    def test_change_passwords(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.change_passwords,
                          self.context, None)

    def test_update_attributes(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.update_attributes,
                          self.context, 'test_user', '%', {'name': 'new_user'})

    def test_create_database(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.create_database,
                          self.context, [{'name': 'test_db'}])

    def test_create_user(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.create_user,
                          self.context, [{'name': 'test_user'}])

    def test_delete_database(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.delete_database,
                          self.context, [{'name': 'test_db'}])

    def test_delete_user(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.delete_user,
                          self.context, [{'name': 'test_user'}])

    def test_get_user(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.get_user,
                          self.context, 'test_user', '%')

    def test_grant_access(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.grant_access,
                          self.context, 'test_user', '%', [{'name': 'test_db'}]
                          )

    def test_revoke_access(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.revoke_access,
                          self.context, 'test_user', '%', [{'name': 'test_db'}]
                          )

    def test_list_access(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.list_access,
                          self.context, 'test_user', '%')

    def test_list_databases(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.list_databases,
                          self.context)

    def test_list_users(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.list_users,
                          self.context)

    def test_enable_root(self):
        VerticaApp.enable_root = MagicMock(return_value='user_id_stuff')
        user_id = self.manager.enable_root_with_password(self.context)
        self.assertThat(user_id, Is('user_id_stuff'))

    def test_is_root_enabled(self):
        VerticaApp.is_root_enabled = MagicMock(return_value=True)
        is_enabled = self.manager.is_root_enabled(self.context)
        self.assertThat(is_enabled, Is(True))

    def test_create_backup(self):
        self.assertRaises(DatastoreOperationNotSupported,
                          self.manager.create_backup,
                          self.context, {})
