Skip to content

Commit 2ca6c93

Browse files
committed
Validated that PCI alias has proper ids
Either the vendor_id and product_id needs to be set or the resource_class needs to be set in each alias. This is now validated when the alias is parsed to avoid late failure during placement allocation_candidates query. Conflicts: nova/conf/pci.py due to I62ec475988eab8de948498f50d8d4c0d47321102 not on stable/2025.1 Closes-Bug: #2111440 Change-Id: I7fd43b3d6faac8c4098b0983e8adc596414823a1 (cherry picked from commit acc6221)
1 parent 265f492 commit 2ca6c93

5 files changed

Lines changed: 158 additions & 6 deletions

File tree

doc/source/admin/pci-passthrough.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,9 @@ configuration option supports requesting devices by Placement resource class
494494
name via the ``resource_class`` field and also support requesting traits to
495495
be present on the selected devices via the ``traits`` field in the alias. If
496496
the ``resource_class`` field is not specified in the alias then it is defaulted
497-
by nova to ``CUSTOM_PCI_<vendor_id>_<product_id>``.
497+
by nova to ``CUSTOM_PCI_<vendor_id>_<product_id>``. Either the ``product_id``
498+
and ``vendor_id`` or the ``resource_class`` field must be provided in each
499+
alias.
498500

499501
For deeper technical details please read the `nova specification. <https://specs.openstack.org/openstack/nova-specs/specs/zed/approved/pci-device-tracking-in-placement.html>`_
500502

nova/conf/pci.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
alias = {
7171
"name": "A16_16A",
7272
"device_type": "type-VF",
73-
resource_class: "CUSTOM_A16_16A",
73+
"resource_class": "GPU_VF",
74+
"traits": "blue, big"
7475
}
7576
7677
Valid key values are :
@@ -108,6 +109,8 @@
108109
in the alias is matched against the ``resource_class`` defined in the
109110
``[pci]device_spec``. This field can only be used only if
110111
``[filter_scheduler]pci_in_placement`` is enabled.
112+
Either the product_id and vendor_id or the resource_class field must be
113+
provided in each alias.
111114
112115
``traits``
113116
An optional comma separated list of Placement trait names requested to be

nova/pci/request.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,7 @@
121121
}
122122

123123

124-
def _validate_aliases(aliases):
125-
"""Checks the parsed aliases for common mistakes and raise easy to parse
126-
error messages
127-
"""
124+
def _validate_multispec(aliases):
128125
if CONF.filter_scheduler.pci_in_placement:
129126
alias_with_multiple_specs = [
130127
name for name, spec in aliases.items() if len(spec[1]) > 1]
@@ -138,6 +135,44 @@ def _validate_aliases(aliases):
138135
% ",".join(alias_with_multiple_specs))
139136

140137

138+
def _validate_required_ids(aliases):
139+
if CONF.filter_scheduler.pci_in_placement:
140+
alias_without_ids_or_rc = set()
141+
for name, alias in aliases.items():
142+
for spec in alias[1]:
143+
ids = "vendor_id" in spec and "product_id" in spec
144+
rc = "resource_class" in spec
145+
if not ids and not rc:
146+
alias_without_ids_or_rc.add(name)
147+
148+
if alias_without_ids_or_rc:
149+
raise exception.PciInvalidAlias(
150+
"The PCI alias(es) %s does not have vendor_id and product_id "
151+
"fields set or resource_class field set."
152+
% ",".join(sorted(alias_without_ids_or_rc)))
153+
else:
154+
alias_without_ids = set()
155+
for name, alias in aliases.items():
156+
for spec in alias[1]:
157+
ids = "vendor_id" in spec and "product_id" in spec
158+
if not ids:
159+
alias_without_ids.add(name)
160+
161+
if alias_without_ids:
162+
raise exception.PciInvalidAlias(
163+
"The PCI alias(es) %s does not have vendor_id and product_id "
164+
"fields set."
165+
% ",".join(sorted(alias_without_ids)))
166+
167+
168+
def _validate_aliases(aliases):
169+
"""Checks the parsed aliases for common mistakes and raise easy to parse
170+
error messages
171+
"""
172+
_validate_multispec(aliases)
173+
_validate_required_ids(aliases)
174+
175+
141176
def _get_alias_from_config() -> Alias:
142177
"""Parse and validate PCI aliases from the nova config.
143178
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
from nova.tests.functional.api import client
14+
from nova.tests.functional.libvirt import test_pci_in_placement as base
15+
16+
17+
class MissingRCAndIdAliasWithPCIInPlacementTest(
18+
base.PlacementPCIReportingTests
19+
):
20+
21+
def test_alias_without_rc_or_vendor_product_id_is_not_supported(self):
22+
self.flags(group='filter_scheduler', pci_in_placement=True)
23+
24+
pci_alias = [
25+
{
26+
"device_type": "type-VF",
27+
"name": "a-vf",
28+
"traits": "foo"
29+
},
30+
]
31+
self.flags(
32+
group="pci",
33+
alias=self._to_list_of_json_str(pci_alias),
34+
)
35+
extra_spec = {"pci_passthrough:alias": "a-vf:1"}
36+
flavor_id = self._create_flavor(extra_spec=extra_spec)
37+
38+
exc = self.assertRaises(
39+
client.OpenStackApiException,
40+
self._create_server,
41+
flavor_id=flavor_id,
42+
networks=[],
43+
)
44+
self.assertEqual(400, exc.response.status_code)
45+
self.assertIn(
46+
"The PCI alias(es) a-vf does not have vendor_id and product_id "
47+
"fields set or resource_class field set.",
48+
exc.response.text)

nova/tests/unit/pci/test_request.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def test_get_alias_from_config_valid_rc_and_traits(self):
226226
"resource_class": "foo",
227227
"traits": "bar,baz",
228228
})
229+
self.flags(pci_in_placement=True, group='filter_scheduler')
229230
self.flags(alias=[fake_alias], group='pci')
230231
aliases = request._get_alias_from_config()
231232
self.assertIsNotNone(aliases)
@@ -278,6 +279,69 @@ def test_get_alias_from_config_conflicting_numa_policy(self):
278279
exception.PciInvalidAlias,
279280
request._get_alias_from_config)
280281

282+
def test_get_alias_from_config_missing_ids(self):
283+
a1 = jsonutils.dumps({
284+
"name": "a1",
285+
"product_id": "4444",
286+
})
287+
a2 = jsonutils.dumps({
288+
"name": "a2",
289+
"vendor_id": "4444",
290+
})
291+
a3 = jsonutils.dumps({
292+
"name": "a3",
293+
})
294+
a4 = jsonutils.dumps({
295+
"name": "a4",
296+
# ignored as PCI in Placement is not enabled
297+
"resource_class": "foo",
298+
})
299+
a5 = jsonutils.dumps({
300+
"name": "a5",
301+
"vendor_id": "4444",
302+
"product_id": "4444",
303+
})
304+
self.flags(alias=[a1, a2, a3, a4, a5], group='pci')
305+
306+
ex = self.assertRaises(
307+
exception.PciInvalidAlias, request._get_alias_from_config)
308+
self.assertEqual(
309+
"The PCI alias(es) a1,a2,a3,a4 does not have vendor_id and "
310+
"product_id fields set.",
311+
str(ex))
312+
313+
def test_get_alias_from_config_missing_ids_or_rc_pci_in_placement(self):
314+
a1 = jsonutils.dumps({
315+
"name": "a1",
316+
"product_id": "4444",
317+
})
318+
a2 = jsonutils.dumps({
319+
"name": "a2",
320+
"vendor_id": "4444",
321+
})
322+
a3 = jsonutils.dumps({
323+
"name": "a3",
324+
})
325+
a4 = jsonutils.dumps({
326+
"name": "a4",
327+
"resource_class": "foo",
328+
})
329+
a5 = jsonutils.dumps({
330+
"name": "a5",
331+
"vendor_id": "4444",
332+
"product_id": "4444",
333+
})
334+
335+
self.flags(pci_in_placement=True, group='filter_scheduler')
336+
self.flags(alias=[a1, a2, a3, a4, a5], group='pci')
337+
338+
ex = self.assertRaises(
339+
exception.PciInvalidAlias, request._get_alias_from_config)
340+
self.assertEqual(
341+
"The PCI alias(es) a1,a2,a3 does not have vendor_id and "
342+
"product_id fields set or resource_class field set.",
343+
str(ex))
344+
281345
def _verify_result(self, expected, real):
282346
exp_real = zip(expected, real)
283347
for exp, real in exp_real:

0 commit comments

Comments
 (0)