# Copyright 2015, Hitachi Data Systems.
#
#    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.

"""
Tests For Data Manager
"""

from unittest import mock

import ddt

from manila.common import constants
from manila import context
from manila.data import helper
from manila.data import manager
from manila.data import utils as data_utils
from manila import db
from manila import exception
from manila.share import rpcapi as share_rpc
from manila import test
from manila.tests import db_utils
from manila import utils


@ddt.ddt
class DataManagerTestCase(test.TestCase):
    """Test case for data manager."""

    def setUp(self):
        super(DataManagerTestCase, self).setUp()
        self.manager = manager.DataManager()
        self.context = context.get_admin_context()
        self.topic = 'fake_topic'
        self.share = db_utils.create_share()
        manager.CONF.set_default('mount_tmp_location', '/tmp/')

    def test_init(self):
        manager = self.manager
        self.assertIsNotNone(manager)

    @ddt.data(constants.TASK_STATE_DATA_COPYING_COMPLETING,
              constants.TASK_STATE_DATA_COPYING_STARTING,
              constants.TASK_STATE_DATA_COPYING_IN_PROGRESS)
    def test_init_host(self, status):

        share = db_utils.create_share(
            task_state=status)

        # mocks
        self.mock_object(db, 'share_get_all', mock.Mock(
            return_value=[share]))
        self.mock_object(db, 'share_update')

        # run
        self.manager.init_host()

        # asserts
        db.share_get_all.assert_called_once_with(
            utils.IsAMatcher(context.RequestContext))

        db.share_update.assert_called_with(
            utils.IsAMatcher(context.RequestContext), share['id'],
            {'task_state': constants.TASK_STATE_DATA_COPYING_ERROR})

    @ddt.data(None, Exception('fake'), exception.ShareDataCopyCancelled(
        src_instance='ins1',
        dest_instance='ins2'))
    def test_migration_start(self, exc):

        # mocks
        self.mock_object(db, 'share_get', mock.Mock(return_value=self.share))
        self.mock_object(db, 'share_instance_get', mock.Mock(
            return_value=self.share.instance))

        self.mock_object(data_utils, 'Copy',
                         mock.Mock(return_value='fake_copy'))

        if exc is None:
            self.manager.busy_tasks_shares[self.share['id']] = 'fake_copy'

        self.mock_object(self.manager, '_copy_share_data',
                         mock.Mock(side_effect=exc))

        self.mock_object(share_rpc.ShareAPI, 'migration_complete')

        if exc is not None and not isinstance(
                exc, exception.ShareDataCopyCancelled):
            self.mock_object(db, 'share_update')

        # run
        if exc is None or isinstance(exc, exception.ShareDataCopyCancelled):
            self.manager.migration_start(
                self.context, [], self.share['id'],
                'ins1_id', 'ins2_id', 'info_src', 'info_dest')
        else:
            self.assertRaises(
                exception.ShareDataCopyFailed, self.manager.migration_start,
                self.context, [], self.share['id'], 'ins1_id', 'ins2_id',
                'info_src', 'info_dest')

            db.share_update.assert_called_once_with(
                self.context, self.share['id'],
                {'task_state': constants.TASK_STATE_DATA_COPYING_ERROR})

        # asserts
        self.assertFalse(self.manager.busy_tasks_shares.get(self.share['id']))

        self.manager._copy_share_data.assert_called_once_with(
            self.context, 'fake_copy', self.share, 'ins1_id', 'ins2_id',
            'info_src', 'info_dest')

        if exc:
            share_rpc.ShareAPI.migration_complete.assert_called_once_with(
                self.context, self.share.instance, 'ins2_id')

    @ddt.data({'cancelled': False, 'exc': None},
              {'cancelled': False, 'exc': Exception('fake')},
              {'cancelled': True, 'exc': None})
    @ddt.unpack
    def test__copy_share_data(self, cancelled, exc):

        access = db_utils.create_access(share_id=self.share['id'])

        connection_info_src = {'mount': 'mount_cmd_src',
                               'unmount': 'unmount_cmd_src'}
        connection_info_dest = {'mount': 'mount_cmd_dest',
                                'unmount': 'unmount_cmd_dest'}

        get_progress = {'total_progress': 100}

        # mocks
        fake_copy = mock.MagicMock(cancelled=cancelled)

        self.mock_object(db, 'share_update')
        self.mock_object(db, 'share_instance_get',
                         mock.Mock(side_effect=[self.share['instance'],
                                                self.share['instance']]))
        self.mock_object(helper.DataServiceHelper,
                         'allow_access_to_data_service',
                         mock.Mock(return_value=[access]))

        self.mock_object(helper.DataServiceHelper, 'mount_share_instance')

        self.mock_object(fake_copy, 'run', mock.Mock(side_effect=exc))

        self.mock_object(fake_copy, 'get_progress',
                         mock.Mock(return_value=get_progress))

        self.mock_object(helper.DataServiceHelper, 'unmount_share_instance',
                         mock.Mock(side_effect=Exception('fake')))

        self.mock_object(helper.DataServiceHelper,
                         'deny_access_to_data_service',
                         mock.Mock(side_effect=Exception('fake')))

        extra_updates = None

        # run
        if cancelled:
            self.assertRaises(
                exception.ShareDataCopyCancelled,
                self.manager._copy_share_data, self.context, fake_copy,
                self.share, 'ins1_id', 'ins2_id', connection_info_src,
                connection_info_dest)
            extra_updates = [
                mock.call(
                    self.context, self.share['id'],
                    {'task_state':
                     constants.TASK_STATE_DATA_COPYING_COMPLETING}),
                mock.call(
                    self.context, self.share['id'],
                    {'task_state':
                     constants.TASK_STATE_DATA_COPYING_CANCELLED})
            ]

        elif exc:
            self.assertRaises(
                exception.ShareDataCopyFailed, self.manager._copy_share_data,
                self.context, fake_copy, self.share, 'ins1_id',
                'ins2_id', connection_info_src, connection_info_dest)

        else:
            self.manager._copy_share_data(
                self.context, fake_copy, self.share, 'ins1_id',
                'ins2_id', connection_info_src, connection_info_dest)
            extra_updates = [
                mock.call(
                    self.context, self.share['id'],
                    {'task_state':
                     constants.TASK_STATE_DATA_COPYING_COMPLETING}),
                mock.call(
                    self.context, self.share['id'],
                    {'task_state':
                     constants.TASK_STATE_DATA_COPYING_COMPLETED})
            ]

        # asserts
        self.assertEqual(
            self.manager.busy_tasks_shares[self.share['id']], fake_copy)

        update_list = [
            mock.call(
                self.context, self.share['id'],
                {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING}),
            mock.call(
                self.context, self.share['id'],
                {'task_state': constants.TASK_STATE_DATA_COPYING_IN_PROGRESS}),
        ]
        if extra_updates:
            update_list = update_list + extra_updates

        db.share_update.assert_has_calls(update_list)

        (helper.DataServiceHelper.allow_access_to_data_service.
            assert_called_once_with(
                self.share['instance'], connection_info_src,
                self.share['instance'], connection_info_dest))

        helper.DataServiceHelper.mount_share_instance.assert_has_calls([
            mock.call(connection_info_src['mount'], '/tmp/',
                      self.share['instance']),
            mock.call(connection_info_dest['mount'], '/tmp/',
                      self.share['instance'])])

        fake_copy.run.assert_called_once_with()
        if exc is None:
            fake_copy.get_progress.assert_called_once_with()

        helper.DataServiceHelper.unmount_share_instance.assert_has_calls([
            mock.call(connection_info_src['unmount'], '/tmp/', 'ins1_id'),
            mock.call(connection_info_dest['unmount'], '/tmp/', 'ins2_id')])

        helper.DataServiceHelper.deny_access_to_data_service.assert_has_calls([
            mock.call([access], self.share['instance']),
            mock.call([access], self.share['instance'])])

    def test__copy_share_data_exception_access(self):

        connection_info_src = {'mount': 'mount_cmd_src',
                               'unmount': 'unmount_cmd_src'}
        connection_info_dest = {'mount': 'mount_cmd_src',
                                'unmount': 'unmount_cmd_src'}

        fake_copy = mock.MagicMock(cancelled=False)

        # mocks
        self.mock_object(db, 'share_update')
        self.mock_object(db, 'share_instance_get',
                         mock.Mock(side_effect=[self.share['instance'],
                                                self.share['instance']]))

        self.mock_object(
            helper.DataServiceHelper, 'allow_access_to_data_service',
            mock.Mock(
                side_effect=exception.ShareDataCopyFailed(reason='fake')))

        self.mock_object(helper.DataServiceHelper, 'cleanup_data_access')

        # run
        self.assertRaises(exception.ShareDataCopyFailed,
                          self.manager._copy_share_data, self.context,
                          fake_copy, self.share, 'ins1_id', 'ins2_id',
                          connection_info_src, connection_info_dest)

        # asserts
        db.share_update.assert_called_once_with(
            self.context, self.share['id'],
            {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING})

        (helper.DataServiceHelper.allow_access_to_data_service.
            assert_called_once_with(
                self.share['instance'], connection_info_src,
                self.share['instance'], connection_info_dest))

    def test__copy_share_data_exception_mount_1(self):

        access = db_utils.create_access(share_id=self.share['id'])

        connection_info_src = {'mount': 'mount_cmd_src',
                               'unmount': 'unmount_cmd_src'}
        connection_info_dest = {'mount': 'mount_cmd_src',
                                'unmount': 'unmount_cmd_src'}

        fake_copy = mock.MagicMock(cancelled=False)

        # mocks
        self.mock_object(db, 'share_update')
        self.mock_object(db, 'share_instance_get',
                         mock.Mock(side_effect=[self.share['instance'],
                                                self.share['instance']]))

        self.mock_object(helper.DataServiceHelper,
                         'allow_access_to_data_service',
                         mock.Mock(return_value=[access]))

        self.mock_object(helper.DataServiceHelper, 'mount_share_instance',
                         mock.Mock(side_effect=Exception('fake')))

        self.mock_object(helper.DataServiceHelper, 'cleanup_data_access')
        self.mock_object(helper.DataServiceHelper, 'cleanup_temp_folder')

        # run
        self.assertRaises(exception.ShareDataCopyFailed,
                          self.manager._copy_share_data, self.context,
                          fake_copy, self.share, 'ins1_id', 'ins2_id',
                          connection_info_src, connection_info_dest)

        # asserts
        db.share_update.assert_called_once_with(
            self.context, self.share['id'],
            {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING})

        (helper.DataServiceHelper.allow_access_to_data_service.
            assert_called_once_with(
                self.share['instance'], connection_info_src,
                self.share['instance'], connection_info_dest))

        helper.DataServiceHelper.mount_share_instance.assert_called_once_with(
            connection_info_src['mount'], '/tmp/', self.share['instance'])

        helper.DataServiceHelper.cleanup_temp_folder.assert_called_once_with(
            'ins1_id', '/tmp/')

        helper.DataServiceHelper.cleanup_data_access.assert_has_calls([
            mock.call([access], 'ins2_id'), mock.call([access], 'ins1_id')])

    def test__copy_share_data_exception_mount_2(self):

        access = db_utils.create_access(share_id=self.share['id'])

        connection_info_src = {'mount': 'mount_cmd_src',
                               'unmount': 'unmount_cmd_src'}
        connection_info_dest = {'mount': 'mount_cmd_src',
                                'unmount': 'unmount_cmd_src'}

        fake_copy = mock.MagicMock(cancelled=False)

        # mocks
        self.mock_object(db, 'share_update')
        self.mock_object(db, 'share_instance_get',
                         mock.Mock(side_effect=[self.share['instance'],
                                                self.share['instance']]))

        self.mock_object(helper.DataServiceHelper,
                         'allow_access_to_data_service',
                         mock.Mock(return_value=[access]))

        self.mock_object(helper.DataServiceHelper, 'mount_share_instance',
                         mock.Mock(side_effect=[None, Exception('fake')]))

        self.mock_object(helper.DataServiceHelper, 'cleanup_data_access')
        self.mock_object(helper.DataServiceHelper, 'cleanup_temp_folder')
        self.mock_object(helper.DataServiceHelper,
                         'cleanup_unmount_temp_folder')

        # run
        self.assertRaises(exception.ShareDataCopyFailed,
                          self.manager._copy_share_data, self.context,
                          fake_copy, self.share, 'ins1_id', 'ins2_id',
                          connection_info_src, connection_info_dest)

        # asserts
        db.share_update.assert_called_once_with(
            self.context, self.share['id'],
            {'task_state': constants.TASK_STATE_DATA_COPYING_STARTING})

        (helper.DataServiceHelper.allow_access_to_data_service.
            assert_called_once_with(
                self.share['instance'], connection_info_src,
                self.share['instance'], connection_info_dest))

        helper.DataServiceHelper.mount_share_instance.assert_has_calls([
            mock.call(connection_info_src['mount'], '/tmp/',
                      self.share['instance']),
            mock.call(connection_info_dest['mount'], '/tmp/',
                      self.share['instance'])])

        (helper.DataServiceHelper.cleanup_unmount_temp_folder.
            assert_called_once_with(
                connection_info_src['unmount'], '/tmp/', 'ins1_id'))

        helper.DataServiceHelper.cleanup_temp_folder.assert_has_calls([
            mock.call('ins2_id', '/tmp/'), mock.call('ins1_id', '/tmp/')])

        helper.DataServiceHelper.cleanup_data_access.assert_has_calls([
            mock.call([access], 'ins2_id'), mock.call([access], 'ins1_id')])

    def test_data_copy_cancel(self):

        share = db_utils.create_share()

        self.manager.busy_tasks_shares[share['id']] = data_utils.Copy

        # mocks
        self.mock_object(data_utils.Copy, 'cancel')

        # run
        self.manager.data_copy_cancel(self.context, share['id'])

        # asserts
        data_utils.Copy.cancel.assert_called_once_with()

    def test_data_copy_cancel_not_copying(self):

        self.assertRaises(exception.InvalidShare,
                          self.manager.data_copy_cancel, self.context,
                          'fake_id')

    def test_data_copy_get_progress(self):

        share = db_utils.create_share()

        self.manager.busy_tasks_shares[share['id']] = data_utils.Copy

        expected = 'fake_progress'

        # mocks
        self.mock_object(data_utils.Copy, 'get_progress',
                         mock.Mock(return_value=expected))

        # run
        result = self.manager.data_copy_get_progress(self.context, share['id'])

        # asserts
        self.assertEqual(expected, result)

        data_utils.Copy.get_progress.assert_called_once_with()

    def test_data_copy_get_progress_not_copying(self):

        self.assertRaises(exception.InvalidShare,
                          self.manager.data_copy_get_progress, self.context,
                          'fake_id')
