# Copyright 2012 OpenStack Foundation
# 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 tempfile
from unittest import mock

from oslo_concurrency import processutils
from oslo_utils import units

from nova import test
from nova.virt.disk import api
from nova.virt.disk.mount import api as mount
from nova.virt.image import model as imgmodel


class FakeMount(object):
    device = None

    @staticmethod
    def instance_for_format(image, mountdir, partition):
        return FakeMount()

    def get_dev(self):
        pass

    def unget_dev(self):
        pass


class APITestCase(test.NoDBTestCase):
    @mock.patch('nova.virt.disk.vfs.guestfs.VFSGuestFS', new=mock.Mock())
    def test_can_resize_need_fs_type_specified(self):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)
        image = imgmodel.LocalFileImage(imgfile.name, imgmodel.FORMAT_QCOW2)
        self.assertFalse(api.is_image_extendable(image))

    @mock.patch('oslo_concurrency.processutils.execute', autospec=True)
    def test_is_image_extendable_raw(self, mock_exec):
        imgfile = tempfile.NamedTemporaryFile()
        image = imgmodel.LocalFileImage(imgfile, imgmodel.FORMAT_RAW)
        self.addCleanup(imgfile.close)
        self.assertTrue(api.is_image_extendable(image))
        mock_exec.assert_called_once_with('e2label', imgfile)

    @mock.patch('oslo_concurrency.processutils.execute', autospec=True)
    def test_resize2fs_success(self, mock_exec):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)

        api.resize2fs(imgfile)

        mock_exec.assert_has_calls(
            [mock.call('e2fsck',
                       '-fp',
                       imgfile,
                       check_exit_code=[0, 1, 2]),
             mock.call('resize2fs',
                       imgfile,
                       check_exit_code=False)])

    @mock.patch('oslo_concurrency.processutils.execute')
    @mock.patch('nova.privsep.fs.resize2fs')
    @mock.patch('nova.privsep.fs.e2fsck')
    def test_resize2fs_success_as_root(self, mock_fsck, mock_resize,
                                       mock_exec):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)

        api.resize2fs(imgfile, run_as_root=True)

        mock_exec.assert_not_called()
        mock_resize.assert_called()
        mock_fsck.assert_called()

    @mock.patch('oslo_concurrency.processutils.execute', autospec=True,
                side_effect=processutils.ProcessExecutionError("fs error"))
    def test_resize2fs_e2fsck_fails(self, mock_exec):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)
        api.resize2fs(imgfile)

        mock_exec.assert_called_once_with('e2fsck',
                                          '-fp',
                                          imgfile,
                                          check_exit_code=[0, 1, 2])

    @mock.patch.object(api, 'can_resize_image', autospec=True,
                       return_value=True)
    @mock.patch.object(api, 'is_image_extendable', autospec=True,
                       return_value=True)
    @mock.patch.object(api, 'resize2fs', autospec=True)
    @mock.patch.object(mount.Mount, 'instance_for_format')
    @mock.patch('oslo_concurrency.processutils.execute', autospec=True)
    def test_extend_qcow_success(self, mock_exec, mock_inst, mock_resize,
                                 mock_extendable, mock_can_resize):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)
        imgsize = 10
        device = "/dev/sdh"
        image = imgmodel.LocalFileImage(imgfile, imgmodel.FORMAT_QCOW2)

        self.flags(resize_fs_using_block_device=True)
        mounter = FakeMount.instance_for_format(
            image, None, None)
        mounter.device = device
        mock_inst.return_value = mounter

        with test.nested(
            mock.patch.object(mounter, 'get_dev', autospec=True,
                              return_value=True),
            mock.patch.object(mounter, 'unget_dev', autospec=True),
        ) as (mock_get_dev, mock_unget_dev):

            api.extend(image, imgsize)

            mock_can_resize.assert_called_once_with(imgfile, imgsize)
            mock_exec.assert_called_once_with('qemu-img', 'resize',
                                              imgfile, imgsize)
            mock_extendable.assert_called_once_with(image)
            mock_inst.assert_called_once_with(image, None, None)
            mock_resize.assert_called_once_with(mounter.device,
                                                run_as_root=True,
                                                check_exit_code=[0])

            mock_get_dev.assert_called_once_with()
            mock_unget_dev.assert_called_once_with()

    @mock.patch.object(api, 'can_resize_image', autospec=True,
                       return_value=True)
    @mock.patch.object(api, 'is_image_extendable', autospec=True)
    @mock.patch('oslo_concurrency.processutils.execute', autospec=True)
    def test_extend_qcow_no_resize(self, mock_execute, mock_extendable,
                                   mock_can_resize_image):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)
        imgsize = 10
        image = imgmodel.LocalFileImage(imgfile, imgmodel.FORMAT_QCOW2)

        self.flags(resize_fs_using_block_device=False)

        api.extend(image, imgsize)

        mock_can_resize_image.assert_called_once_with(imgfile, imgsize)
        mock_execute.assert_called_once_with('qemu-img', 'resize', imgfile,
                                             imgsize)
        self.assertFalse(mock_extendable.called)

    @mock.patch.object(api, 'can_resize_image', autospec=True,
                       return_value=True)
    @mock.patch('nova.privsep.libvirt.ploop_resize')
    def test_extend_ploop(self, mock_ploop_resize, mock_can_resize_image):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)
        imgsize = 10 * units.Gi
        image = imgmodel.LocalFileImage(imgfile, imgmodel.FORMAT_PLOOP)

        api.extend(image, imgsize)
        mock_can_resize_image.assert_called_once_with(image.path,
                                                      imgsize)
        mock_ploop_resize.assert_called_once_with(imgfile, imgsize)

    @mock.patch.object(api, 'can_resize_image', autospec=True,
                       return_value=True)
    @mock.patch.object(api, 'resize2fs', autospec=True)
    @mock.patch('oslo_concurrency.processutils.execute', autospec=True)
    def test_extend_raw_success(self, mock_exec, mock_resize,
                                mock_can_resize):
        imgfile = tempfile.NamedTemporaryFile()
        self.addCleanup(imgfile.close)
        imgsize = 10
        image = imgmodel.LocalFileImage(imgfile, imgmodel.FORMAT_RAW)

        api.extend(image, imgsize)

        mock_exec.assert_has_calls(
            [mock.call('qemu-img', 'resize', imgfile, imgsize),
             mock.call('e2label', image.path)])
        mock_resize.assert_called_once_with(imgfile, run_as_root=False,
                                            check_exit_code=[0])
        mock_can_resize.assert_called_once_with(imgfile, imgsize)
