# 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 fixtures

from oslo_serialization import jsonutils

from nova.tests.fixtures import libvirt as fakelibvirt
from nova.tests.functional.libvirt import test_pci_sriov_servers


class TestPciResize(test_pci_sriov_servers._PCIServersTestBase):
    # these tests use multiple different configs so the whitelist is set by
    # each testcase individually
    PCI_DEVICE_SPEC = []
    PCI_ALIAS = [
        jsonutils.dumps(x)
        for x in [
            {
                "vendor_id": fakelibvirt.PCI_VEND_ID,
                "product_id": fakelibvirt.PCI_PROD_ID,
                "name": "a-pci-dev",
            },
            {
                "vendor_id": fakelibvirt.PCI_VEND_ID,
                "product_id": fakelibvirt.PF_PROD_ID,
                "device_type": "type-PF",
                "name": "a-pf",
            },
            {
                "vendor_id": fakelibvirt.PCI_VEND_ID,
                "product_id": fakelibvirt.VF_PROD_ID,
                "device_type": "type-VF",
                "name": "a-vf",
            },
        ]
    ]

    def setUp(self):
        super().setUp()
        self.useFixture(
            fixtures.MockPatch(
                'nova.virt.libvirt.driver.LibvirtDriver.'
                'migrate_disk_and_power_off',
                return_value='{}'
            )
        )
        # These tests should not depend on the host's sysfs
        self.useFixture(
            fixtures.MockPatch('nova.pci.utils.is_physical_function'))
        self.useFixture(
            fixtures.MockPatch(
                'nova.pci.utils.get_function_by_ifname',
                return_value=(None, False)
            )
        )

    def _test_resize_from_two_devs_to_one_dev(self, num_pci_on_dest):
        # The fake libvirt will emulate on the host:
        # * two type-PCI in slot 0, 1
        compute1_pci_info = fakelibvirt.HostPCIDevicesInfo(num_pci=2)
        # the config matches the PCI dev
        compute1_device_spec = [
            jsonutils.dumps(x)
            for x in [
                {
                    "vendor_id": fakelibvirt.PCI_VEND_ID,
                    "product_id": fakelibvirt.PCI_PROD_ID,
                },
            ]
        ]
        self.flags(group='pci', passthrough_whitelist=compute1_device_spec)
        self.start_compute(hostname="compute1", pci_info=compute1_pci_info)
        self.assertPCIDeviceCounts("compute1", total=2, free=2)

        # create a server that requests two PCI devs
        extra_spec = {"pci_passthrough:alias": "a-pci-dev:2"}
        flavor_id = self._create_flavor(extra_spec=extra_spec)
        server = self._create_server(flavor_id=flavor_id, networks=[])
        self.assertPCIDeviceCounts("compute1", total=2, free=0)

        # start another compute with a different amount of PCI dev available
        compute2_pci_info = fakelibvirt.HostPCIDevicesInfo(
            num_pci=num_pci_on_dest)
        # the config matches the PCI dev
        compute2_device_spec = [
            jsonutils.dumps(x)
            for x in [
                {
                    "vendor_id": fakelibvirt.PCI_VEND_ID,
                    "product_id": fakelibvirt.PCI_PROD_ID,
                },
            ]
        ]
        self.flags(group='pci', passthrough_whitelist=compute2_device_spec)
        self.start_compute(hostname="compute2", pci_info=compute2_pci_info)
        self.assertPCIDeviceCounts(
            "compute2", total=num_pci_on_dest, free=num_pci_on_dest)

        # resize the server to request only one PCI dev instead of the current
        # two. This should fit to compute2 having at least one dev
        extra_spec = {"pci_passthrough:alias": "a-pci-dev:1"}
        flavor_id = self._create_flavor(extra_spec=extra_spec)
        self._resize_server(server, flavor_id=flavor_id)
        self._confirm_resize(server)
        self.assertPCIDeviceCounts("compute1", total=2, free=2)
        self.assertPCIDeviceCounts(
            "compute2", total=num_pci_on_dest, free=num_pci_on_dest - 1)

    def test_resize_from_two_devs_to_one_dev_dest_has_two_devs(self):
        self._test_resize_from_two_devs_to_one_dev(num_pci_on_dest=2)

    def test_resize_from_two_devs_to_one_dev_dest_has_one_dev(self):
        self._test_resize_from_two_devs_to_one_dev(num_pci_on_dest=1)

    def test_resize_from_vf_to_pf(self):
        # The fake libvirt will emulate on the host:
        # * one type-PF in slot 0 with one VF
        compute1_pci_info = fakelibvirt.HostPCIDevicesInfo(
            num_pfs=1, num_vfs=1)
        # the config matches only the VF
        compute1_device_spec = [
            jsonutils.dumps(x)
            for x in [
                {
                    "vendor_id": fakelibvirt.PCI_VEND_ID,
                    "product_id": fakelibvirt.VF_PROD_ID,
                },
            ]
        ]
        self.flags(group='pci', passthrough_whitelist=compute1_device_spec)
        self.start_compute(hostname="compute1", pci_info=compute1_pci_info)
        self.assertPCIDeviceCounts("compute1", total=1, free=1)

        # create a server that requests one Vf
        extra_spec = {"pci_passthrough:alias": "a-vf:1"}
        flavor_id = self._create_flavor(extra_spec=extra_spec)
        server = self._create_server(flavor_id=flavor_id, networks=[])
        self.assertPCIDeviceCounts("compute1", total=1, free=0)

        # start another compute with a single PF dev available
        # The fake libvirt will emulate on the host:
        # * one type-PF in slot 0 with 1 VF
        compute2_pci_info = fakelibvirt.HostPCIDevicesInfo(
            num_pfs=1, num_vfs=1)
        # the config matches the PF dev but not the VF
        compute2_device_spec = [
            jsonutils.dumps(x)
            for x in [
                {
                    "vendor_id": fakelibvirt.PCI_VEND_ID,
                    "product_id": fakelibvirt.PF_PROD_ID,
                },
            ]
        ]
        self.flags(group='pci', passthrough_whitelist=compute2_device_spec)
        self.start_compute(hostname="compute2", pci_info=compute2_pci_info)
        self.assertPCIDeviceCounts("compute2", total=1, free=1)

        # resize the server to request on PF dev instead of the current VF
        # dev. This should fit to compute2 having exactly one PF dev.
        extra_spec = {"pci_passthrough:alias": "a-pf:1"}
        flavor_id = self._create_flavor(extra_spec=extra_spec)
        self._resize_server(server, flavor_id=flavor_id)
        self._confirm_resize(server)
        self.assertPCIDeviceCounts("compute1", total=1, free=1)
        self.assertPCIDeviceCounts("compute2", total=1, free=0)
