#  Copyright 2014 Cloudbase Solutions Srl
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import mock

from cinder import exception
from cinder import test
from cinder.volume.drivers.windows import constants
from cinder.volume.drivers.windows import vhdutils


class VHDUtilsTestCase(test.TestCase):

    _FAKE_FORMAT = 2
    _FAKE_TYPE = constants.VHD_TYPE_DYNAMIC
    _FAKE_JOB_PATH = 'fake_job_path'
    _FAKE_VHD_PATH = r'C:\fake\vhd.vhd'
    _FAKE_DEST_PATH = r'C:\fake\destination.vhdx'
    _FAKE_FILE_HANDLE = 'fake_file_handle'
    _FAKE_RET_VAL = 0
    _FAKE_VHD_SIZE = 1024

    def setUp(self):
        super(VHDUtilsTestCase, self).setUp()
        self._setup_mocks()
        self._vhdutils = vhdutils.VHDUtils()
        self._vhdutils._msft_vendor_id = 'fake_vendor_id'

        self.addCleanup(mock.patch.stopall)

    def _setup_mocks(self):
        fake_ctypes = mock.Mock()
        # Use this in order to make assertions on the variables parsed by
        # references.
        fake_ctypes.byref = lambda x: x
        fake_ctypes.c_wchar_p = lambda x: x
        fake_ctypes.c_ulong = lambda x: x

        mock.patch.multiple(
            'cinder.volume.drivers.windows.vhdutils', ctypes=fake_ctypes,
            windll=mock.DEFAULT, wintypes=mock.DEFAULT, kernel32=mock.DEFAULT,
            virtdisk=mock.DEFAULT, Win32_GUID=mock.DEFAULT,
            Win32_RESIZE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT,
            Win32_CREATE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT,
            Win32_VIRTUAL_STORAGE_TYPE=mock.DEFAULT,
            Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V1=mock.DEFAULT,
            Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V2=mock.DEFAULT,
            Win32_MERGE_VIRTUAL_DISK_PARAMETERS=mock.DEFAULT,
            Win32_GET_VIRTUAL_DISK_INFO_PARAMETERS=mock.DEFAULT,
            Win32_SET_VIRTUAL_DISK_INFO_PARAMETERS=mock.DEFAULT,
            create=True).start()

    def _test_create_vhd(self, src_path=None, max_internal_size=0,
                         parent_path=None, create_failed=False):
        self._vhdutils._get_device_id_by_path = mock.Mock(
            side_effect=(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
                         vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX))
        self._vhdutils._close = mock.Mock()

        fake_params = (
            vhdutils.Win32_CREATE_VIRTUAL_DISK_PARAMETERS.return_value)
        fake_vst = mock.Mock()
        fake_source_vst = mock.Mock()

        vhdutils.Win32_VIRTUAL_STORAGE_TYPE.side_effect = [
            fake_vst, None, fake_source_vst]
        vhdutils.virtdisk.CreateVirtualDisk.return_value = int(
            create_failed)

        if create_failed:
            self.assertRaises(exception.VolumeBackendAPIException,
                              self._vhdutils._create_vhd,
                              self._FAKE_DEST_PATH,
                              constants.VHD_TYPE_DYNAMIC,
                              src_path=src_path,
                              max_internal_size=max_internal_size,
                              parent_path=parent_path)
        else:
            self._vhdutils._create_vhd(self._FAKE_DEST_PATH,
                                       constants.VHD_TYPE_DYNAMIC,
                                       src_path=src_path,
                                       max_internal_size=max_internal_size,
                                       parent_path=parent_path)

        self.assertEqual(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD,
                         fake_vst.DeviceId)
        self.assertEqual(parent_path, fake_params.ParentPath)
        self.assertEqual(max_internal_size, fake_params.MaximumSize)

        if src_path:
            self.assertEqual(vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX,
                             fake_source_vst.DeviceId)
            self.assertEqual(src_path, fake_params.SourcePath)

        vhdutils.virtdisk.CreateVirtualDisk.assert_called_with(
            vhdutils.ctypes.byref(fake_vst),
            vhdutils.ctypes.c_wchar_p(self._FAKE_DEST_PATH),
            vhdutils.VIRTUAL_DISK_ACCESS_NONE, None,
            vhdutils.CREATE_VIRTUAL_DISK_FLAG_NONE, 0,
            vhdutils.ctypes.byref(fake_params), None,
            vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE()))
        self.assertTrue(self._vhdutils._close.called)

    def test_create_vhd_exception(self):
        self._test_create_vhd(create_failed=True)

    def test_create_dynamic_vhd(self):
        self._test_create_vhd(max_internal_size=1 << 30)

    def test_create_differencing_vhd(self):
        self._test_create_vhd(parent_path=self._FAKE_VHD_PATH)

    def test_convert_vhd(self):
        self._test_create_vhd(src_path=self._FAKE_VHD_PATH)

    def _test_open(self, open_failed=False):
        fake_device_id = vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD

        vhdutils.virtdisk.OpenVirtualDisk.return_value = int(open_failed)
        self._vhdutils._get_device_id_by_path = mock.Mock(
            return_value=fake_device_id)

        fake_vst = vhdutils.Win32_VIRTUAL_STORAGE_TYPE.return_value
        fake_params = 'fake_params'
        fake_access_mask = vhdutils.VIRTUAL_DISK_ACCESS_NONE
        fake_open_flag = vhdutils.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS

        if open_failed:
            self.assertRaises(exception.VolumeBackendAPIException,
                              self._vhdutils._open,
                              self._FAKE_VHD_PATH)
        else:
            self._vhdutils._open(self._FAKE_VHD_PATH,
                                 open_flag=fake_open_flag,
                                 open_access_mask=fake_access_mask,
                                 open_params=fake_params)

            vhdutils.virtdisk.OpenVirtualDisk.assert_called_with(
                vhdutils.ctypes.byref(fake_vst),
                vhdutils.ctypes.c_wchar_p(self._FAKE_VHD_PATH),
                fake_access_mask, fake_open_flag, fake_params,
                vhdutils.ctypes.byref(vhdutils.wintypes.HANDLE()))

            self.assertEqual(fake_device_id, fake_vst.DeviceId)

    def test_open_success(self):
        self._test_open()

    def test_open_failed(self):
        self._test_open(open_failed=True)

    def _test_get_device_id_by_path(self,
                                    get_device_failed=False):
        if get_device_failed:
            self.assertRaises(exception.VolumeBackendAPIException,
                              self._vhdutils._get_device_id_by_path,
                              self._FAKE_VHD_PATH[:-4])
        else:
            ret_val = self._vhdutils._get_device_id_by_path(
                self._FAKE_VHD_PATH)

            self.assertEqual(
                ret_val,
                vhdutils.VIRTUAL_STORAGE_TYPE_DEVICE_VHD)

    def test_get_device_id_by_path_success(self):
        self._test_get_device_id_by_path()

    def test_get_device_id_by_path_failed(self):
        self._test_get_device_id_by_path(get_device_failed=True)

    def _test_resize_vhd(self, resize_failed=False):
        fake_params = (
            vhdutils.Win32_RESIZE_VIRTUAL_DISK_PARAMETERS.return_value)

        self._vhdutils._open = mock.Mock(
            return_value=self._FAKE_FILE_HANDLE)
        self._vhdutils._close = mock.Mock()

        vhdutils.virtdisk.ResizeVirtualDisk.return_value = int(
            resize_failed)

        if resize_failed:
            self.assertRaises(exception.VolumeBackendAPIException,
                              self._vhdutils.resize_vhd,
                              self._FAKE_VHD_PATH,
                              self._FAKE_VHD_SIZE)
        else:
            self._vhdutils.resize_vhd(self._FAKE_VHD_PATH,
                                      self._FAKE_VHD_SIZE)

        vhdutils.virtdisk.ResizeVirtualDisk.assert_called_with(
            self._FAKE_FILE_HANDLE,
            vhdutils.RESIZE_VIRTUAL_DISK_FLAG_NONE,
            vhdutils.ctypes.byref(fake_params),
            None)
        self.assertTrue(self._vhdutils._close.called)

    def test_resize_vhd_success(self):
        self._test_resize_vhd()

    def test_resize_vhd_failed(self):
        self._test_resize_vhd(resize_failed=True)

    def _test_merge_vhd(self, merge_failed=False):
        self._vhdutils._open = mock.Mock(
            return_value=self._FAKE_FILE_HANDLE)
        self._vhdutils._close = mock.Mock()

        fake_open_params = vhdutils.Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V1()
        fake_params = vhdutils.Win32_MERGE_VIRTUAL_DISK_PARAMETERS()

        vhdutils.virtdisk.MergeVirtualDisk.return_value = int(
            merge_failed)
        vhdutils.Win32_RESIZE_VIRTUAL_DISK_PARAMETERS.return_value = (
            fake_params)

        if merge_failed:
            self.assertRaises(exception.VolumeBackendAPIException,
                              self._vhdutils.merge_vhd,
                              self._FAKE_VHD_PATH)
        else:
            self._vhdutils.merge_vhd(self._FAKE_VHD_PATH)

        self._vhdutils._open.assert_called_once_with(
            self._FAKE_VHD_PATH,
            open_params=vhdutils.ctypes.byref(fake_open_params))
        self.assertEqual(vhdutils.OPEN_VIRTUAL_DISK_VERSION_1,
                         fake_open_params.Version)
        self.assertEqual(2, fake_open_params.RWDepth)
        vhdutils.virtdisk.MergeVirtualDisk.assert_called_with(
            self._FAKE_FILE_HANDLE,
            vhdutils.MERGE_VIRTUAL_DISK_FLAG_NONE,
            vhdutils.ctypes.byref(fake_params),
            None)

    def test_merge_vhd_success(self):
        self._test_merge_vhd()

    def test_merge_vhd_failed(self):
        self._test_merge_vhd(merge_failed=True)

    def _test_get_vhd_info_member(self, get_vhd_info_failed=False):
        fake_params = vhdutils.Win32_GET_VIRTUAL_DISK_INFO_PARAMETERS()
        fake_info_size = vhdutils.ctypes.sizeof(fake_params)

        vhdutils.virtdisk.GetVirtualDiskInformation.return_value = (
            get_vhd_info_failed)
        self._vhdutils._close = mock.Mock()

        if get_vhd_info_failed:
            self.assertRaises(exception.VolumeBackendAPIException,
                              self._vhdutils._get_vhd_info_member,
                              self._FAKE_VHD_PATH,
                              vhdutils.GET_VIRTUAL_DISK_INFO_SIZE)
            self._vhdutils._close.assert_called_with(
                self._FAKE_VHD_PATH)
        else:
            self._vhdutils._get_vhd_info_member(
                self._FAKE_VHD_PATH,
                vhdutils.GET_VIRTUAL_DISK_INFO_SIZE)

        vhdutils.virtdisk.GetVirtualDiskInformation.assert_called_with(
            self._FAKE_VHD_PATH,
            vhdutils.ctypes.byref(
                vhdutils.ctypes.c_ulong(fake_info_size)),
            vhdutils.ctypes.byref(fake_params), 0)

    def test_get_vhd_info_member_success(self):
        self._test_get_vhd_info_member()

    def test_get_vhd_info_member_failed(self):
        self._test_get_vhd_info_member(get_vhd_info_failed=True)

    def test_get_vhd_info(self):
        fake_vhd_info = {'VirtualSize': self._FAKE_VHD_SIZE}
        fake_info_member = vhdutils.GET_VIRTUAL_DISK_INFO_SIZE

        self._vhdutils._open = mock.Mock(
            return_value=self._FAKE_FILE_HANDLE)
        self._vhdutils._close = mock.Mock()
        self._vhdutils._get_vhd_info_member = mock.Mock(
            return_value=fake_vhd_info)

        ret_val = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH,
                                              [fake_info_member])

        self.assertEqual(fake_vhd_info, ret_val)
        self._vhdutils._open.assert_called_once_with(
            self._FAKE_VHD_PATH,
            open_access_mask=vhdutils.VIRTUAL_DISK_ACCESS_GET_INFO)
        self._vhdutils._get_vhd_info_member.assert_called_with(
            self._FAKE_FILE_HANDLE, fake_info_member)
        self._vhdutils._close.assert_called_once()

    def test_parse_vhd_info(self):
        fake_physical_size = self._FAKE_VHD_SIZE + 1
        fake_info_member = vhdutils.GET_VIRTUAL_DISK_INFO_SIZE
        fake_info = mock.Mock()
        fake_info.VhdInfo.Size._fields_ = [
            ("VirtualSize", vhdutils.wintypes.ULARGE_INTEGER),
            ("PhysicalSize", vhdutils.wintypes.ULARGE_INTEGER)]
        fake_info.VhdInfo.Size.VirtualSize = self._FAKE_VHD_SIZE
        fake_info.VhdInfo.Size.PhysicalSize = fake_physical_size

        ret_val = self._vhdutils._parse_vhd_info(fake_info, fake_info_member)
        expected = {'VirtualSize': self._FAKE_VHD_SIZE,
                    'PhysicalSize': fake_physical_size}

        self.assertEqual(expected, ret_val)

    def _test_reconnect_parent(self, reconnect_failed=False):
        fake_params = vhdutils.Win32_SET_VIRTUAL_DISK_INFO_PARAMETERS()
        fake_open_params = vhdutils.Win32_OPEN_VIRTUAL_DISK_PARAMETERS_V2()

        self._vhdutils._open = mock.Mock(return_value=self._FAKE_FILE_HANDLE)
        self._vhdutils._close = mock.Mock()
        vhdutils.virtdisk.SetVirtualDiskInformation.return_value = int(
            reconnect_failed)

        if reconnect_failed:
            self.assertRaises(exception.VolumeBackendAPIException,
                              self._vhdutils.reconnect_parent,
                              self._FAKE_VHD_PATH, self._FAKE_DEST_PATH)

        else:
            self._vhdutils.reconnect_parent(self._FAKE_VHD_PATH,
                                            self._FAKE_DEST_PATH)

        self._vhdutils._open.assert_called_once_with(
            self._FAKE_VHD_PATH,
            open_flag=vhdutils.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS,
            open_access_mask=vhdutils.VIRTUAL_DISK_ACCESS_NONE,
            open_params=vhdutils.ctypes.byref(fake_open_params))
        self.assertEqual(vhdutils.OPEN_VIRTUAL_DISK_VERSION_2,
                         fake_open_params.Version)
        self.assertFalse(fake_open_params.GetInfoOnly)
        vhdutils.virtdisk.SetVirtualDiskInformation.assert_called_once_with(
            self._FAKE_FILE_HANDLE, vhdutils.ctypes.byref(fake_params))
        self.assertEqual(self._FAKE_DEST_PATH, fake_params.ParentFilePath)

    def test_reconnect_parent_success(self):
        self._test_reconnect_parent()

    def test_reconnect_parent_failed(self):
        self._test_reconnect_parent(reconnect_failed=True)
