# Copyright 2015 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 mock
from oslo_utils import units

from nova import exception
from nova import test
from nova.tests.unit import fake_instance
from nova.virt.virtualbox import constants
from nova.virt.virtualbox import exception as vbox_exception
from nova.virt.virtualbox import vmops


class VBoxOperationTestCase(test.NoDBTestCase):

    _FAKE_VM_NAME = 'fake-vm'
    _FAKE_VM_UUID = 'fake-vm-uuid'
    _FAKE_POWER_STATE = constants.POWER_STATE['running']
    _FAKE_MAC_ADDRESS = 'aa:bb:cc:dd'

    def setUp(self):
        super(VBoxOperationTestCase, self).setUp()
        instance_values = {
            'id': 1,
            'name': self._FAKE_VM_NAME,
            'uuid': self._FAKE_VM_UUID,
            'power_state': self._FAKE_POWER_STATE,
            'root_gb': 8,
            'ephemeral_gb': 1,
        }

        self._context = 'fake-context'
        self._instance = fake_instance.fake_instance_obj(self._context,
                                                         **instance_values)
        self._vbox_ops = vmops.VBoxOperation()

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.list')
    def test_inaccessible_vms(self, mock_list):
        mock_list.side_effect = [
            '"<inaccessible>" %(fake_uuid)s' % {
                'fake_uuid': self._FAKE_VM_UUID},
            'invalid-line']

        self.assertEqual([self._FAKE_VM_UUID],
                         self._vbox_ops._inaccessible_vms())
        self.assertEqual([], self._vbox_ops._inaccessible_vms())

    @mock.patch('os.path.exists')
    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.close_medium')
    @mock.patch('nova.virt.virtualbox.vhdutils.get_hard_disks')
    def test_init_host(self, mock_get_hard_disks, mock_close_medium,
                       mock_exists):
        mock_exists.return_value = False
        mock_get_hard_disks.return_value = {
            mock.sentinel.uuid: {
                constants.VHD_PATH: mock.sentinel.path,
                constants.VHD_STATE: constants.VHD_STATE_INACCESSIBLE,
            }
        }
        self._vbox_ops.init_host()

        mock_close_medium.assert_called_once_with(constants.MEDIUM_DISK,
                                                  mock.sentinel.uuid)

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.list')
    def test_list_vms(self, mock_list):
        mock_list.side_effect = [
            '"{fake_vm}" {{{fake_uuid}}}'.format(fake_vm=self._FAKE_VM_NAME,
                                                 fake_uuid=self._FAKE_VM_UUID),
            'invalid-line']

        self.assertEqual({self._FAKE_VM_NAME: self._FAKE_VM_UUID},
                         self._vbox_ops._list_vms())
        self.assertEqual({}, self._vbox_ops._list_vms())

    @mock.patch('nova.virt.virtualbox.vmops.VBoxOperation._list_vms')
    def test_list_instances(self, mock_list_vms):
        mock_list_vms.return_value = {self._FAKE_VM_NAME: self._FAKE_VM_UUID}

        self.assertEqual([self._FAKE_VM_NAME], self._vbox_ops.list_instances())

    @mock.patch('nova.virt.virtualbox.vmops.VBoxOperation._list_vms')
    def test_list_instance_uuids(self, mock_list_vms):
        mock_list_vms.return_value = {self._FAKE_VM_NAME: self._FAKE_VM_UUID}

        self.assertEqual([self._FAKE_VM_UUID],
                         self._vbox_ops.list_instance_uuids())

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.list')
    def test_instance_exists(self, mock_list):
        mock_list.side_effect = [
            '"fake_vm1" {fake_uuid1}\n"fake_vm2": {fake_uuid2}\n',
            'invalid-line',
            '"{fake_vm}" {{{fake_uuid}}}'.format(fake_vm=self._instance.name,
                                                 fake_uuid=self._FAKE_VM_UUID)
        ]
        self.assertFalse(self._vbox_ops.instance_exists(self._instance))
        self.assertFalse(self._vbox_ops.instance_exists(self._instance))
        self.assertTrue(self._vbox_ops.instance_exists(self._instance))

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.show_vm_info')
    def test_get_info(self, mock_vm_info):
        mock_vm_info.return_value = {
            constants.VM_POWER_STATE: constants.STATE_POWER_OFF,
            constants.VM_CPUS: mock.sentinel.cpus,
            constants.VM_MEMORY: mock.sentinel.memory
        }
        response = self._vbox_ops.get_info(self._instance)

        mock_vm_info.assert_called_once_with(self._instance)
        self.assertEqual(mock.sentinel.cpus, response.num_cpu)
        self.assertEqual(mock.sentinel.memory, response.mem_kb)

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.control_vm')
    def test_pause(self, mock_control_vm):
        self._vbox_ops.pause(self._instance)

        mock_control_vm.assert_called_once_with(self._instance,
                                                constants.STATE_PAUSE)

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.control_vm')
    def test_unpause(self, mock_control_vm):
        self._vbox_ops.unpause(self._instance)

        mock_control_vm.assert_called_once_with(self._instance,
                                                constants.STATE_RESUME)

  
  @mock.patch('nova.virt.virtualbox.manage.VBoxManage.control_vm')
    def test_suspend(self, mock_control_vm):
        self._vbox_ops.suspend(self._instance)


        mock_control_vm.assert_called_once_with(self._instance,
                                                constants.STATE_SUSPEND)

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.start_vm')
    def test_resume(self, mock_start_vm):
        self._vbox_ops.resume(self._instance)

        mock_start_vm.assert_called_once_with(self._instance)

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.control_vm')
    @mock.patch('nova.virt.virtualbox.vmutils.soft_shutdown')
    def test_power_off(self, mock_shutdown, mock_control_vm):
        mock_shutdown.return_value = True

        self._vbox_ops.power_off(self._instance)
        self.assertEqual(0, mock_shutdown.call_count)
        mock_control_vm.assert_called_once_with(self._instance,
                                                constants.STATE_POWER_OFF)
        mock_control_vm.reset_mock()

        self._vbox_ops.power_off(self._instance, timeout=1)
        self.assertEqual(0, mock_control_vm.call_count)
        self.assertEqual(1, mock_shutdown.call_count)

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.control_vm')
    @mock.patch('nova.virt.virtualbox.vmutils.soft_shutdown')
    def test_power_off_fail_shutdown(self, mock_shutdown, mock_control_vm):
        mock_shutdown.side_effect = [
            exception.InstanceInvalidState(
                instance_uuid=self._instance.uuid,
                attr=None, state=None, method=None),
            False]

        self._vbox_ops.power_off(self._instance, timeout=1)
        mock_control_vm.assert_called_once_with(self._instance,
                                                constants.STATE_POWER_OFF)
        self._vbox_ops.power_off(self._instance, timeout=1)
        self.assertEqual(2, mock_control_vm.call_count)

    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.start_vm')
    def test_power_on(self, mock_start_vm):
        self._vbox_ops.power_on(self._instance)

        mock_start_vm.assert_called_once_with(self._instance)

    @mock.patch('nova.virt.virtualbox.vmops.VBoxOperation.power_on')
    @mock.patch('nova.virt.virtualbox.vmutils.soft_shutdown')
    @mock.patch('nova.virt.virtualbox.manage.VBoxManage.control_vm')
    def test_reboot(self, mock_control_vm, mock_shutdown, mock_power_on)