210 lines
7.8 KiB
Python
210 lines
7.8 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.
|
|
import ddt
|
|
from unittest import mock
|
|
|
|
from nova.compute import pci_placement_translator as ppt
|
|
from nova import exception
|
|
from nova.objects import fields
|
|
from nova.objects import pci_device
|
|
from nova import test
|
|
|
|
|
|
def dev(v, p):
|
|
return pci_device.PciDevice(vendor_id=v, product_id=p)
|
|
|
|
|
|
# NOTE(gibi): Most of the nova.compute.pci_placement_translator module is
|
|
# covered with functional tests in
|
|
# nova.tests.functional.libvirt.test_pci_in_placement
|
|
@ddt.ddt
|
|
class TestTranslator(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
patcher = mock.patch(
|
|
"nova.compute.pci_placement_translator."
|
|
"_is_placement_tracking_enabled")
|
|
self.addCleanup(patcher.stop)
|
|
patcher.start()
|
|
|
|
def test_translator_skips_devices_without_matching_spec(self):
|
|
"""As every PCI device in the PciTracker is created by matching a
|
|
PciDeviceSpec the translator should always be able to look up the spec
|
|
for a device. But if cannot then the device will be skipped and warning
|
|
will be emitted.
|
|
"""
|
|
pci_tracker = mock.Mock()
|
|
pci_tracker.pci_devs = pci_device.PciDeviceList(
|
|
objects=[pci_device.PciDevice(address="0000:81:00.0")]
|
|
)
|
|
# So we have a device but there is no spec for it
|
|
pci_tracker.dev_filter.get_devspec = mock.Mock(return_value=None)
|
|
pci_tracker.dev_filter.specs = []
|
|
# we expect that the provider_tree is not touched as the device without
|
|
# spec is skipped, we assert that with the NonCallableMock
|
|
provider_tree = mock.NonCallableMock()
|
|
|
|
ppt.update_provider_tree_for_pci(
|
|
provider_tree, "fake-node", pci_tracker, {})
|
|
|
|
self.assertIn(
|
|
"Device spec is not found for device 0000:81:00.0 in "
|
|
"[pci]device_spec. Ignoring device in Placement resource view. "
|
|
"This should not happen. Please file a bug.",
|
|
self.stdlog.logger.output
|
|
)
|
|
|
|
@ddt.unpack
|
|
@ddt.data(
|
|
(None, set()),
|
|
("", set()),
|
|
("a", {"CUSTOM_A"}),
|
|
("a,b", {"CUSTOM_A", "CUSTOM_B"}),
|
|
("HW_GPU_API_VULKAN", {"HW_GPU_API_VULKAN"}),
|
|
("CUSTOM_FOO", {"CUSTOM_FOO"}),
|
|
("custom_bar", {"CUSTOM_BAR"}),
|
|
("custom-bar", {"CUSTOM_CUSTOM_BAR"}),
|
|
("CUSTOM_a", {"CUSTOM_A"}),
|
|
("a@!#$b123X", {"CUSTOM_A_B123X"}),
|
|
# Note that both trait names are normalized to the same trait
|
|
("a!@b,a###b", {"CUSTOM_A_B"}),
|
|
)
|
|
def test_trait_normalization(self, trait_names, expected_traits):
|
|
self.assertEqual(
|
|
expected_traits, ppt._get_traits_for_dev({"traits": trait_names}))
|
|
|
|
@ddt.unpack
|
|
@ddt.data(
|
|
(dev(v='1234', p='5678'), None, "CUSTOM_PCI_1234_5678"),
|
|
(dev(v='1234', p='5678'), "", "CUSTOM_PCI_1234_5678"),
|
|
(dev(v='1234', p='5678'), "PGPU", "PGPU"),
|
|
(dev(v='1234', p='5678'), "pgpu", "PGPU"),
|
|
(dev(v='1234', p='5678'), "foobar", "CUSTOM_FOOBAR"),
|
|
(dev(v='1234', p='5678'), "custom_foo", "CUSTOM_FOO"),
|
|
(dev(v='1234', p='5678'), "CUSTOM_foo", "CUSTOM_FOO"),
|
|
(dev(v='1234', p='5678'), "custom_FOO", "CUSTOM_FOO"),
|
|
(dev(v='1234', p='5678'), "CUSTOM_FOO", "CUSTOM_FOO"),
|
|
(dev(v='1234', p='5678'), "custom-foo", "CUSTOM_CUSTOM_FOO"),
|
|
(dev(v='1234', p='5678'), "a###b", "CUSTOM_A_B"),
|
|
(dev(v='123a', p='567b'), "", "CUSTOM_PCI_123A_567B"),
|
|
)
|
|
def test_resource_class_normalization(self, pci_dev, rc_name, expected_rc):
|
|
self.assertEqual(
|
|
expected_rc,
|
|
ppt._get_rc_for_dev(pci_dev, {"resource_class": rc_name})
|
|
)
|
|
|
|
def test_dependent_device_pf_then_vf(self):
|
|
pv = ppt.PlacementView("fake-node")
|
|
pf = pci_device.PciDevice(
|
|
address="0000:81:00.0",
|
|
dev_type=fields.PciDeviceType.SRIOV_PF
|
|
)
|
|
vf = pci_device.PciDevice(
|
|
address="0000:81:00.1",
|
|
parent_addr=pf.address,
|
|
dev_type=fields.PciDeviceType.SRIOV_VF
|
|
)
|
|
|
|
pv.add_dev(pf, {"resource_class": "foo"})
|
|
ex = self.assertRaises(
|
|
exception.PlacementPciDependentDeviceException,
|
|
pv.add_dev,
|
|
vf,
|
|
{"resource_class": "bar"}
|
|
)
|
|
|
|
self.assertEqual(
|
|
"Configuring both 0000:81:00.1 and 0000:81:00.0 in "
|
|
"[pci]device_spec is not supported. Either the parent PF or its "
|
|
"children VFs can be configured.",
|
|
str(ex),
|
|
)
|
|
|
|
def test_dependent_device_vf_then_pf(self):
|
|
pv = ppt.PlacementView("fake-node")
|
|
pf = pci_device.PciDevice(
|
|
address="0000:81:00.0",
|
|
dev_type=fields.PciDeviceType.SRIOV_PF
|
|
)
|
|
vf = pci_device.PciDevice(
|
|
address="0000:81:00.1",
|
|
parent_addr=pf.address,
|
|
dev_type=fields.PciDeviceType.SRIOV_VF
|
|
)
|
|
vf2 = pci_device.PciDevice(
|
|
address="0000:81:00.2",
|
|
parent_addr=pf.address,
|
|
dev_type=fields.PciDeviceType.SRIOV_VF
|
|
)
|
|
|
|
pv.add_dev(vf, {"resource_class": "foo"})
|
|
pv.add_dev(vf2, {"resource_class": "foo"})
|
|
ex = self.assertRaises(
|
|
exception.PlacementPciDependentDeviceException,
|
|
pv.add_dev,
|
|
pf,
|
|
{"resource_class": "bar"}
|
|
)
|
|
|
|
self.assertEqual(
|
|
"Configuring both 0000:81:00.0 and 0000:81:00.1,0000:81:00.2 in "
|
|
"[pci]device_spec is not supported. Either the parent PF or its "
|
|
"children VFs can be configured.",
|
|
str(ex),
|
|
)
|
|
|
|
def test_mixed_rc_for_sibling_vfs(self):
|
|
pv = ppt.PlacementView("fake-node")
|
|
vf1, vf2, vf3, vf4 = [
|
|
pci_device.PciDevice(
|
|
address="0000:81:00.%d" % f,
|
|
parent_addr="0000:71:00.0",
|
|
dev_type=fields.PciDeviceType.SRIOV_VF)
|
|
for f in range(0, 4)
|
|
]
|
|
|
|
pv.add_dev(vf1, {"resource_class": "a", "traits": "foo,bar,baz"})
|
|
# order is irrelevant
|
|
pv.add_dev(vf2, {"resource_class": "a", "traits": "foo,baz,bar"})
|
|
# but missing trait is rejected
|
|
ex = self.assertRaises(
|
|
exception.PlacementPciMixedTraitsException,
|
|
pv.add_dev,
|
|
vf3,
|
|
{"resource_class": "a", "traits": "foo,bar"},
|
|
)
|
|
self.assertEqual(
|
|
"VFs from the same PF cannot be configured with different set of "
|
|
"'traits' in [pci]device_spec. We got CUSTOM_BAR,CUSTOM_FOO for "
|
|
"0000:81:00.2 and CUSTOM_BAR,CUSTOM_BAZ,CUSTOM_FOO for "
|
|
"0000:81:00.0,0000:81:00.1.",
|
|
str(ex),
|
|
)
|
|
# as well as additional trait
|
|
ex = self.assertRaises(
|
|
exception.PlacementPciMixedTraitsException,
|
|
pv.add_dev,
|
|
vf4,
|
|
{"resource_class": "a", "traits": "foo,bar,baz,extra"}
|
|
)
|
|
self.assertEqual(
|
|
"VFs from the same PF cannot be configured with different set of "
|
|
"'traits' in [pci]device_spec. We got "
|
|
"CUSTOM_BAR,CUSTOM_BAZ,CUSTOM_EXTRA,CUSTOM_FOO for 0000:81:00.3 "
|
|
"and CUSTOM_BAR,CUSTOM_BAZ,CUSTOM_FOO for "
|
|
"0000:81:00.0,0000:81:00.1.",
|
|
str(ex),
|
|
)
|