nova/nova/tests/functional/libvirt/test_pci_in_placement.py

341 lines
12 KiB
Python

# 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 unittest import mock
import fixtures
import os_resource_classes
import os_traits
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from nova import exception
from nova.tests.fixtures import libvirt as fakelibvirt
from nova.tests.functional.libvirt import test_pci_sriov_servers
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class PlacementPCIReportingTests(test_pci_sriov_servers._PCIServersTestBase):
PCI_RC = f"CUSTOM_PCI_{fakelibvirt.PCI_VEND_ID}_{fakelibvirt.PCI_PROD_ID}"
PF_RC = f"CUSTOM_PCI_{fakelibvirt.PCI_VEND_ID}_{fakelibvirt.PF_PROD_ID}"
VF_RC = f"CUSTOM_PCI_{fakelibvirt.PCI_VEND_ID}_{fakelibvirt.VF_PROD_ID}"
# Just placeholders to satisfy the base class. The real value will be
# redefined by the tests
PCI_DEVICE_SPEC = []
PCI_ALIAS = None
def setUp(self):
super().setUp()
patcher = mock.patch(
"nova.compute.pci_placement_translator."
"_is_placement_tracking_enabled",
return_value=True
)
self.addCleanup(patcher.stop)
patcher.start()
# 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)
)
)
@staticmethod
def _to_device_spec_conf(spec_list):
return [jsonutils.dumps(x) for x in spec_list]
def test_new_compute_init_with_pci_devs(self):
"""A brand new compute is started with multiple pci devices configured
for nova.
"""
# The fake libvirt will emulate on the host:
# * two type-PCI devs (slot 0 and 1)
# * two type-PFs (slot 2 and 3) with two type-VFs each
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=2, num_pfs=2, num_vfs=4)
# the emulated devices will then be filtered by the device_spec:
device_spec = self._to_device_spec_conf(
[
# PCI_PROD_ID will match two type-PCI devs (slot 0, 1)
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
"traits": ",".join(
[os_traits.HW_GPU_API_VULKAN, "CUSTOM_GPU", "purple"]
)
},
# PF_PROD_ID + slot 2 will match one PF but not their children
# VFs
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PF_PROD_ID,
"address": "0000:81:02.0",
"traits": ",".join(
[os_traits.HW_NIC_SRIOV, "CUSTOM_PF", "pf-white"]
),
},
# VF_PROD_ID + slot 3 will match two VFs but not their parent
# PF
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.VF_PROD_ID,
"address": "0000:81:03.*",
"traits": ",".join(
[os_traits.HW_NIC_SRIOV_TRUSTED, "CUSTOM_VF", "vf-red"]
),
},
]
)
self.flags(group='pci', device_spec=device_spec)
self.start_compute(hostname="compute1", pci_info=pci_info)
# Finally we assert that only the filtered devices are reported to
# placement.
self.assert_placement_pci_view(
"compute1",
inventories={
"0000:81:00.0": {self.PCI_RC: 1},
"0000:81:01.0": {self.PCI_RC: 1},
"0000:81:02.0": {self.PF_RC: 1},
# Note that the VF inventory is reported on the parent PF
"0000:81:03.0": {self.VF_RC: 2},
},
traits={
"0000:81:00.0": [
"HW_GPU_API_VULKAN",
"CUSTOM_GPU",
"CUSTOM_PURPLE",
],
"0000:81:01.0": [
"HW_GPU_API_VULKAN",
"CUSTOM_GPU",
"CUSTOM_PURPLE",
],
"0000:81:02.0": [
"HW_NIC_SRIOV",
"CUSTOM_PF",
"CUSTOM_PF_WHITE",
],
"0000:81:03.0": [
"HW_NIC_SRIOV_TRUSTED",
"CUSTOM_VF",
"CUSTOM_VF_RED",
],
},
)
def test_new_compute_init_with_pci_dev_custom_rc(self):
# The fake libvirt will emulate on the host:
# * one type-PCI devs slot 0
# * one type-PF dev in slot 1 with a single type-VF under it
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=1, num_pfs=1, num_vfs=1)
device_spec = self._to_device_spec_conf(
[
# PCI_PROD_ID will match the type-PCI in slot 0
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PCI_PROD_ID,
"resource_class": os_resource_classes.PGPU,
"traits": os_traits.HW_GPU_API_VULKAN,
},
# slot 1 func 0 is the type-PF dev. The child VF is ignored
{
"product_id": fakelibvirt.PF_PROD_ID,
"address": "0000:81:01.0",
"resource_class": "crypto",
"traits": "to-the-moon,hodl"
},
]
)
self.flags(group='pci', device_spec=device_spec)
self.start_compute(hostname="compute1", pci_info=pci_info)
self.assert_placement_pci_view(
"compute1",
inventories={
"0000:81:00.0": {os_resource_classes.PGPU: 1},
"0000:81:01.0": {"CUSTOM_CRYPTO": 1},
},
traits={
"0000:81:00.0": [
"HW_GPU_API_VULKAN",
],
"0000:81:01.0": [
"CUSTOM_TO_THE_MOON",
"CUSTOM_HODL",
],
},
)
def test_dependent_device_config_is_rejected(self):
"""Configuring both the PF and its children VFs is not supported.
Only either of them can be given to nova.
"""
# The fake libvirt will emulate on the host:
# * one type-PF dev in slot 0 with a single type-VF under it
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=0, num_pfs=1, num_vfs=1)
# both device will be matched by our config
device_spec = self._to_device_spec_conf(
[
# PF
{
"address": "0000:81:00.0"
},
# Its child VF
{
"address": "0000:81:00.1"
},
]
)
self.flags(group='pci', device_spec=device_spec)
ex = self.assertRaises(
exception.PlacementPciException,
self.start_compute,
hostname="compute1",
pci_info=pci_info
)
self.assertIn(
"Configuring both 0000:81:00.1 and 0000:81:00.0 in "
"[pci]device_spec is not supported",
str(ex)
)
def test_sibling_vfs_with_contradicting_resource_classes_rejected(self):
# The fake libvirt will emulate on the host:
# * one type-PF dev in slot 0 with two type-VF under it
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=0, num_pfs=1, num_vfs=2)
# the config matches the two VFs separately and tries to configure
# them with different resource class
device_spec = self._to_device_spec_conf(
[
{
"address": "0000:81:00.1",
"resource_class": "vf1"
},
{
"address": "0000:81:00.2",
"resource_class": "vf2"
},
]
)
self.flags(group='pci', device_spec=device_spec)
ex = self.assertRaises(
exception.PlacementPciMixedResourceClassException,
self.start_compute,
hostname="compute1",
pci_info=pci_info
)
self.assertIn(
"VFs from the same PF cannot be configured with different "
"'resource_class' values in [pci]device_spec. We got "
"CUSTOM_VF2 for 0000:81:00.2 and CUSTOM_VF1 for 0000:81:00.1.",
str(ex)
)
def test_sibling_vfs_with_contradicting_traits_rejected(self):
# The fake libvirt will emulate on the host:
# * one type-PF dev in slot 0 with two type-VF under it
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=0, num_pfs=1, num_vfs=2)
# the config matches the two VFs separately and tries to configure
# them with different trait list
device_spec = self._to_device_spec_conf(
[
{
"address": "0000:81:00.1",
"traits": "foo",
},
{
"address": "0000:81:00.2",
"traits": "bar",
},
]
)
self.flags(group='pci', device_spec=device_spec)
ex = self.assertRaises(
exception.PlacementPciMixedTraitsException,
self.start_compute,
hostname="compute1",
pci_info=pci_info
)
self.assertIn(
"VFs from the same PF cannot be configured with different set of "
"'traits' in [pci]device_spec. We got CUSTOM_BAR for 0000:81:00.2 "
"and CUSTOM_FOO for 0000:81:00.1.",
str(ex)
)
def test_neutron_sriov_devs_ignored(self):
# The fake libvirt will emulate on the host:
# * one type-PF dev in slot 0 with one type-VF under it
pci_info = fakelibvirt.HostPCIDevicesInfo(
num_pci=0, num_pfs=1, num_vfs=1)
# then the config assigns physnet to the dev
device_spec = self._to_device_spec_conf(
[
{
"vendor_id": fakelibvirt.PCI_VEND_ID,
"product_id": fakelibvirt.PF_PROD_ID,
"physical_network": "physnet0",
},
]
)
self.flags(group='pci', device_spec=device_spec)
self.start_compute(hostname="compute1", pci_info=pci_info)
# As every matching dev has physnet configured they are ignored
self.assert_placement_pci_view(
"compute1",
inventories={},
traits={},
)
def test_devname_based_dev_spec_rejected(self):
device_spec = self._to_device_spec_conf(
[
{
"devname": "eth0",
},
]
)
self.flags(group='pci', device_spec=device_spec)
ex = self.assertRaises(
exception.PlacementPciException,
self.start_compute,
hostname="compute1",
)
self.assertIn(
" Invalid [pci]device_spec configuration. PCI Placement reporting "
"does not support 'devname' based device specification but we got "
"{'devname': 'eth0'}. Please use PCI address in the configuration "
"instead.",
str(ex)
)