# Copyright 2014 Cloudbase Solutions Srl
# All Rights Reserved.
#
#    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 os

import mock

from nova.compute import task_states
from nova.tests.unit import fake_instance
from nova.tests.unit.virt.hyperv import test_base
from nova.virt.hyperv import snapshotops


class SnapshotOpsTestCase(test_base.HyperVBaseTestCase):
    """Unit tests for the Hyper-V SnapshotOps class."""

    def setUp(self):
        super(SnapshotOpsTestCase, self).setUp()

        self.context = 'fake_context'
        self._snapshotops = snapshotops.SnapshotOps()
        self._snapshotops._pathutils = mock.MagicMock()
        self._snapshotops._vmutils = mock.MagicMock()
        self._snapshotops._vhdutils = mock.MagicMock()

    @mock.patch('nova.image.glance.get_remote_image_service')
    def test_save_glance_image(self, mock_get_remote_image_service):
        image_metadata = {"disk_format": "vhd",
                          "container_format": "bare"}
        glance_image_service = mock.MagicMock()
        mock_get_remote_image_service.return_value = (glance_image_service,
                                                      mock.sentinel.IMAGE_ID)
        self._snapshotops._save_glance_image(context=self.context,
                                             image_id=mock.sentinel.IMAGE_ID,
                                             image_vhd_path=mock.sentinel.PATH)
        mock_get_remote_image_service.assert_called_once_with(
            self.context, mock.sentinel.IMAGE_ID)
        self._snapshotops._pathutils.open.assert_called_with(
            mock.sentinel.PATH, 'rb')
        glance_image_service.update.assert_called_once_with(
            self.context, mock.sentinel.IMAGE_ID, image_metadata,
            self._snapshotops._pathutils.open().__enter__(),
            purge_props=False)

    @mock.patch('nova.virt.hyperv.snapshotops.SnapshotOps._save_glance_image')
    def _test_snapshot(self, mock_save_glance_image, base_disk_path):
        mock_instance = fake_instance.fake_instance_obj(self.context)
        mock_update = mock.MagicMock()
        fake_src_path = os.path.join('fake', 'path')
        self._snapshotops._pathutils.lookup_root_vhd_path.return_value = (
            fake_src_path)
        fake_exp_dir = os.path.join(os.path.join('fake', 'exp'), 'dir')
        self._snapshotops._pathutils.get_export_dir.return_value = fake_exp_dir
        self._snapshotops._vhdutils.get_vhd_parent_path.return_value = (
            base_disk_path)
        fake_snapshot_path = (
            self._snapshotops._vmutils.take_vm_snapshot.return_value)

        self._snapshotops.snapshot(context=self.context,
                                   instance=mock_instance,
                                   image_id=mock.sentinel.IMAGE_ID,
                                   update_task_state=mock_update)

        self._snapshotops._vmutils.take_vm_snapshot.assert_called_once_with(
            mock_instance.name)
        mock_lookup_path = self._snapshotops._pathutils.lookup_root_vhd_path
        mock_lookup_path.assert_called_once_with(mock_instance.name)
        mock_get_vhd_path = self._snapshotops._vhdutils.get_vhd_parent_path
        mock_get_vhd_path.assert_called_once_with(fake_src_path)
        self._snapshotops._pathutils.get_export_dir.assert_called_once_with(
            mock_instance.name)

        expected = [mock.call(fake_src_path,
                              os.path.join(fake_exp_dir,
                                           os.path.basename(fake_src_path)))]
        dest_vhd_path = os.path.join(fake_exp_dir,
                                     os.path.basename(fake_src_path))
        if base_disk_path:
            basename = os.path.basename(base_disk_path)
            base_dest_disk_path = os.path.join(fake_exp_dir, basename)
            expected.append(mock.call(base_disk_path, base_dest_disk_path))
            mock_reconnect = self._snapshotops._vhdutils.reconnect_parent_vhd
            mock_reconnect.assert_called_once_with(dest_vhd_path,
                                                   base_dest_disk_path)
            self._snapshotops._vhdutils.merge_vhd.assert_called_once_with(
                dest_vhd_path)
            mock_save_glance_image.assert_called_once_with(
                self.context, mock.sentinel.IMAGE_ID, base_dest_disk_path)
        else:
            mock_save_glance_image.assert_called_once_with(
                self.context, mock.sentinel.IMAGE_ID, dest_vhd_path)
        self.assertEqual(len(expected),
                         self._snapshotops._pathutils.copyfile.call_count)
        self._snapshotops._pathutils.copyfile.assert_has_calls(expected)
        self.assertEqual(2, mock_update.call_count)
        expected_update = [
            mock.call(task_state=task_states.IMAGE_PENDING_UPLOAD),
            mock.call(task_state=task_states.IMAGE_UPLOADING,
                      expected_state=task_states.IMAGE_PENDING_UPLOAD)]
        mock_update.assert_has_calls(expected_update)
        self._snapshotops._vmutils.remove_vm_snapshot.assert_called_once_with(
            fake_snapshot_path)
        self._snapshotops._pathutils.rmtree.assert_called_once_with(
            fake_exp_dir)

    def test_snapshot(self):
        base_disk_path = os.path.join('fake', 'disk')
        self._test_snapshot(base_disk_path=base_disk_path)

    def test_snapshot_no_base_disk(self):
        self._test_snapshot(base_disk_path=None)
