# Copyright 2013 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.

from eventlet import greenthread
import mock

from nova.compute import power_state
from nova.compute import task_states
from nova import context
from nova import exception
from nova import objects
from nova.objects import fields
from nova.pci import manager as pci_manager
from nova import test
from nova.tests.unit import fake_flavor
from nova.tests.unit import fake_instance
from nova.tests.unit.virt.xenapi import stubs
from nova.virt import fake
from nova.virt.xenapi import agent as xenapi_agent
from nova.virt.xenapi.client import session as xenapi_session
from nova.virt.xenapi import fake as xenapi_fake
from nova.virt.xenapi import vm_utils
from 
nova.virt.xenapi import vmops
from nova.virt.xenapi import volume_utils
from nova.virt.xenapi import volumeops


class VMOpsTestBase(stubs.
XenAPITestBaseNoDB):
    def setUp(self):
        super(VMOpsTestBase, self).setUp()
        self._setup_mock_vmops()
        self.vms = []

    def _setup_mock_vmops(self, product_brand=None, product_version=None):
        stubs.stubout_session(self.stubs, xenapi_fake.SessionBase)
        self._session = xenapi_session.XenAPISession('test_url', 'root',
                                                     'test_pass')
        self.vmops = vmops.VMOps(self._session, fake.FakeVirtAPI())

    def create_vm(self, name, state="Running"):
        vm_ref = xenapi_fake.create_vm(name, state)
        self.vms.append(vm_ref)
        vm = xenapi_fake.get_record("VM", vm_ref)
        return vm, vm_ref

    def tearDown(self):
        super(VMOpsTestBase, self).tearDown()
        for vm in self.vms:
            xenapi_fake.destroy_vm(vm)


class VMOpsTestCase(VMOpsTestBase):
    def setUp(self):
        super(VMOpsTestCase, self).setUp()
        self._setup_mock_vmops()
        self.context = context.RequestContext('user', 'project')
        self.instance = fake_instance.fake_instance_obj(self.context)

    def _setup_mock_vmops(self, product_brand=None, product_version=None):
        self._session = self._get_mock_session(product_brand, product_version)
        self._vmops = vmops.VMOps(self._session, fake.FakeVirtAPI())

    def _get_mock_session(self, product_brand, product_version):
        class Mock(object):
            pass

        mock_session = Mock()
        mock_session.product_brand = product_brand
        mock_session.product_version = product_version
        return mock_session

    def _test_finish_revert_migration_after_crash(self, backup_made, new_made,
                                                  vm_shutdown=True):
        instance = {'name': 'foo',
                    'task_state': task_states.RESIZE_MIGRATING}
        context = 'fake_context'

        self.mox.StubOutWithMock(vm_utils, 'lookup')
        self.mox.StubOutWithMock(self._vmops, '_destroy')
        self.mox.StubOutWithMock(vm_utils, 'set_vm_name_label')
        self.mox.StubOutWithMock(self._vmops, '_attach_mapped_block_devices')
        self.mox.StubOutWithMock(self._vmops, '_start')
        self.mox.StubOutWithMock(vm_utils, 'is_vm_shutdown')

        vm_utils.lookup(self._session, 'foo-orig').AndReturn(
            backup_made and 'foo' or None)
        vm_utils.lookup(self._session, 'foo').AndReturn(
            (not backup_made or new_made) and 'foo' or None)
        if backup_made:
            if new_made:
                self._vmops._destroy(instance, 'foo')
            vm_utils.set_vm_name_label(self._session, 'foo', 'foo')
            self._vmops._attach_mapped_block_devices(instance, [])

        vm_utils.is_vm_shutdown(self._session, 'foo').AndReturn(vm_shutdown)
        if vm_shutdown:
            self._vmops._start(instance, 'foo')

        self.mox.ReplayAll()

        self._vmops.finish_revert_migration(context, instance, [])

    def test_finish_revert_migration_after_crash(self):
        self._test_finish_revert_migration_after_crash(True, True)

    def test_finish_revert_migration_after_crash_before_new(self):
        self._test_finish_revert_migration_after_crash(True, False)

    def test_finish_revert_migration_after_crash_before_backup(self):
        self._test_finish_revert_migration_after_crash(False, False)

    def test_xsm_sr_check_relaxed_cached(self):
        self.make_plugin_call_count = 0

        def fake_make_plugin_call(plugin, method, **args):
            self.make_plugin_call_count = self.make_plugin_call_count + 1
            return "true"

        self.stubs.Set(self._vmops, "_make_plugin_call",
                       fake_make_plugin_call)

        self.assertTrue(self._vmops._is_xsm_sr_check_relaxed())
        self.assertTrue(self._vmops._is_xsm_sr_check_relaxed())

        self.assertEqual(self.make_plugin_call_count, 1)

    def test_get_vm_opaque_ref_raises_instance_not_found(self):
        instance = {"name": "dummy"}
        self.mox.StubOutWithMock(vm_utils, 'lookup')
        vm_utils.lookup(self._session, instance['name'], False).AndReturn(None)
        self.mox.ReplayAll()

        self.assertRaises(exception.InstanceNotFound,
                self._vmops._get_vm_opaque_ref, instance)

    @mock.patch.object(vm_utils, 'destroy_vm')
    @mock.patch.object(vm_utils, 'clean_shutdown_vm')
    @mock.patch.object(vm_utils, 'hard_shutdown_vm')
    def test_clean_shutdown_no_bdm_on_destroy(self, hard_shutdown_vm,
            clean_shutdown_vm, destroy_vm):
        vm_ref = 'vm_ref'
        self._vmops._destroy(self.instance, vm_ref, destroy_disks=False)
        hard_shutdown_vm.assert_called_once_with(self._vmops._session,
                self.instance, vm_ref)
        self.assertEqual(0, clean_shutdown_vm.call_count)

    @mock.patch.object(vm_utils, 'destroy_vm')
    @mock.patch.object(vm_utils, 'clean_shutdown_vm')
    @mock.patch.object(vm_utils, 'hard_shutdown_vm')
    def test_clean_shutdown_with_bdm_on_destroy(self, hard_shutdown_vm,
            clean_shutdown_vm, destroy_vm):
        vm_ref = 'vm_ref'
        block_device_info = {'block_device_mapping': ['fake']}
        self._vmops._destroy(self.instance, vm_ref, destroy_disks=False,
                block_device_info=block_device_info)
        clean_shutdown_vm.assert_called_once_with(self._vmops._session,
                self.instance, vm_ref)
        self.assertEqual(0, hard_shutdown_vm.call_count)

    @mock.patch.object(vm_utils, 'destroy_vm')
    @mock.patch.object(vm_utils, 'clean_shutdown_vm', return_value=False)
    @mock.patch.object(vm_utils, 'hard_shutdown_vm')
    def test_clean_shutdown_with_bdm_failed_on_destroy(self, hard_shutdown_vm,
            clean_shutdown_vm, destroy_vm):
        vm_ref = 'vm_ref'
        block_device_info = {'block_device_mapping': ['fake']}
        self._vmops._destroy(self.instance, vm_ref, destroy_disks=False,
                block_device_info=block_device_info)
        clean_shutdown_vm.assert_called_once_with(self._vmops._session,
                self.instance, vm_ref)
        hard_shutdown_vm.assert_called_once_with(self._vmops._session,
                self.instance, vm_ref)

    @mock.patch.object(vm_utils, 'try_auto_configure_disk')
    @mock.patch.object(vm_utils, 'create_vbd',
            side_effect=test.TestingException)
    def test_attach_disks_rescue_auto_disk_config_false(self, create_vbd,
            try_auto_config):
        ctxt = context.RequestContext('user', 'project')
        instance = fake_instance.fake_instance_obj(ctxt)
        image_meta = objects.ImageMeta.from_dict(
            {'properties': {'auto_disk_config': 'false'}})
        vdis = {'root': {'ref': 'fake-ref'}}
        self.