Coverage for nova/pci/request.py: 98%
94 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
1# Copyright 2013 Intel Corporation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16""" Example of a PCI alias::
18 | [pci]
19 | alias = '{
20 | "name": "QuickAssist",
21 | "product_id": "0443",
22 | "vendor_id": "8086",
23 | "device_type": "type-PCI",
24 | "numa_policy": "legacy"
25 | }'
27 Aliases with the same name, device_type and numa_policy are ORed::
29 | [pci]
30 | alias = '{
31 | "name": "QuickAssist",
32 | "product_id": "0442",
33 | "vendor_id": "8086",
34 | "device_type": "type-PCI",
35 | }'
37 These two aliases define a device request meaning: vendor_id is "8086" and
38 product_id is "0442" or "0443".
39 """
41import typing as ty
43import jsonschema
44from oslo_log import log as logging
45from oslo_serialization import jsonutils
46from oslo_utils import uuidutils
48import nova.conf
49from nova import context as ctx
50from nova import exception
51from nova.i18n import _
52from nova.network import model as network_model
53from nova import objects
54from nova.objects import fields as obj_fields
55from nova.pci import utils
56from oslo_utils import strutils
58Alias = ty.Dict[str, ty.Tuple[str, ty.List[ty.Dict[str, str]]]]
60PCI_NET_TAG = 'physical_network'
61PCI_TRUSTED_TAG = 'trusted'
62PCI_DEVICE_TYPE_TAG = 'dev_type'
63PCI_REMOTE_MANAGED_TAG = 'remote_managed'
65DEVICE_TYPE_FOR_VNIC_TYPE = {
66 network_model.VNIC_TYPE_DIRECT_PHYSICAL: obj_fields.PciDeviceType.SRIOV_PF,
67 network_model.VNIC_TYPE_VDPA: obj_fields.PciDeviceType.VDPA,
68}
70CONF = nova.conf.CONF
71LOG = logging.getLogger(__name__)
73_ALIAS_SCHEMA = {
74 "type": "object",
75 "additionalProperties": False,
76 "properties": {
77 "name": {
78 "type": "string",
79 "minLength": 1,
80 "maxLength": 256,
81 },
82 # TODO(stephenfin): This isn't used anywhere outside of tests and
83 # should probably be removed.
84 "capability_type": {
85 "type": "string",
86 "enum": ['pci'],
87 },
88 "product_id": {
89 "type": "string",
90 "pattern": utils.PCI_VENDOR_PATTERN,
91 },
92 "vendor_id": {
93 "type": "string",
94 "pattern": utils.PCI_VENDOR_PATTERN,
95 },
96 "device_type": {
97 "type": "string",
98 # NOTE(sean-k-mooney): vDPA devices cannot currently be used with
99 # alias-based PCI passthrough so we exclude it here
100 "enum": [
101 obj_fields.PciDeviceType.STANDARD,
102 obj_fields.PciDeviceType.SRIOV_PF,
103 obj_fields.PciDeviceType.SRIOV_VF,
104 ],
105 },
106 "numa_policy": {
107 "type": "string",
108 "enum": list(obj_fields.PCINUMAAffinityPolicy.ALL),
109 },
110 "resource_class": {
111 "type": "string",
112 },
113 "traits": {
114 "type": "string",
115 },
116 "live_migratable": {
117 "type": "string",
118 },
119 },
120 "required": ["name"],
121}
124def _get_alias_from_config() -> Alias:
125 """Parse and validate PCI aliases from the nova config.
127 :returns: A dictionary where the keys are alias names and the values are
128 tuples of form ``(numa_policy, specs)``. ``numa_policy`` describes the
129 required NUMA affinity of the device(s), while ``specs`` is a list of
130 PCI device specs.
131 :raises: exception.PciInvalidAlias if two aliases with the same name have
132 different device types or different NUMA policies.
133 """
134 jaliases = CONF.pci.alias
135 # map alias name to alias spec list
136 aliases: Alias = {}
137 try:
138 for jsonspecs in jaliases:
139 spec = jsonutils.loads(jsonspecs)
140 jsonschema.validate(spec, _ALIAS_SCHEMA)
142 name = spec.pop('name').strip()
143 numa_policy = spec.pop('numa_policy', None)
144 if not numa_policy:
145 numa_policy = obj_fields.PCINUMAAffinityPolicy.LEGACY
147 dev_type = spec.pop('device_type', None)
148 if dev_type:
149 spec['dev_type'] = dev_type
151 live_migratable = spec.pop('live_migratable', None)
152 if live_migratable is not None:
153 live_migratable = (
154 "true"
155 if strutils.bool_from_string(live_migratable, strict=True)
156 else "false"
157 )
158 spec['live_migratable'] = live_migratable
160 if name not in aliases:
161 aliases[name] = (numa_policy, [spec])
162 continue
164 if aliases[name][0] != numa_policy:
165 reason = _("NUMA policy mismatch for alias '%s'") % name
166 raise exception.PciInvalidAlias(reason=reason)
168 if aliases[name][1][0]['dev_type'] != spec['dev_type']:
169 reason = _("Device type mismatch for alias '%s'") % name
170 raise exception.PciInvalidAlias(reason=reason)
172 aliases[name][1].append(spec)
173 except exception.PciInvalidAlias:
174 raise
175 except jsonschema.exceptions.ValidationError as exc:
176 raise exception.PciInvalidAlias(reason=exc.message)
177 except Exception as exc:
178 raise exception.PciInvalidAlias(reason=str(exc))
180 return aliases
183def _translate_alias_to_requests(
184 alias_spec: str, affinity_policy: ty.Optional[str] = None,
185) -> ty.List['objects.InstancePCIRequest']:
186 """Generate complete pci requests from pci aliases in extra_spec."""
187 pci_aliases = _get_alias_from_config()
189 pci_requests: ty.List[objects.InstancePCIRequest] = []
190 for name, count in [spec.split(':') for spec in alias_spec.split(',')]:
191 name = name.strip()
192 if name not in pci_aliases:
193 raise exception.PciRequestAliasNotDefined(alias=name)
195 numa_policy, spec = pci_aliases[name]
196 policy = affinity_policy or numa_policy
198 # NOTE(gibi): InstancePCIRequest has a requester_id field that could
199 # be filled with the flavor.flavorid but currently there is no special
200 # handling for InstancePCIRequests created from the flavor. So it is
201 # left empty.
202 pci_requests.append(objects.InstancePCIRequest(
203 count=int(count),
204 spec=spec,
205 alias_name=name,
206 numa_policy=policy,
207 request_id=uuidutils.generate_uuid(),
208 ))
209 return pci_requests
212def get_instance_pci_request_from_vif(
213 context: ctx.RequestContext,
214 instance: 'objects.Instance',
215 vif: network_model.VIF,
216) -> ty.Optional['objects.InstancePCIRequest']:
217 """Given an Instance, return the PCI request associated
218 to the PCI device related to the given VIF (if any) on the
219 compute node the instance is currently running.
221 In this method we assume a VIF is associated with a PCI device
222 if 'pci_slot' attribute exists in the vif 'profile' dict.
224 :param context: security context
225 :param instance: instance object
226 :param vif: network VIF model object
227 :raises: raises PciRequestFromVIFNotFound if a pci device is requested
228 but not found on current host
229 :return: instance's PCIRequest object associated with the given VIF
230 or None if no PCI device is requested
231 """
233 # Get PCI device address for VIF if exists
234 vif_pci_dev_addr = vif['profile'].get('pci_slot') \
235 if vif['profile'] else None
237 if not vif_pci_dev_addr:
238 return None
240 try:
241 cn_id = objects.ComputeNode.get_by_host_and_nodename(
242 context,
243 instance.host,
244 instance.node).id
245 except exception.NotFound:
246 LOG.warning("expected to find compute node with host %s "
247 "and node %s when getting instance PCI request "
248 "from VIF", instance.host, instance.node)
249 return None
250 # Find PCIDevice associated with vif_pci_dev_addr on the compute node
251 # the instance is running on.
252 found_pci_dev = None
253 for pci_dev in instance.pci_devices:
254 if (pci_dev.compute_node_id == cn_id and
255 pci_dev.address == vif_pci_dev_addr):
256 found_pci_dev = pci_dev
257 break
258 if not found_pci_dev:
259 return None
260 # Find PCIRequest associated with the given PCIDevice in instance
261 for pci_req in instance.pci_requests.requests:
262 if pci_req.request_id == found_pci_dev.request_id:
263 return pci_req
265 raise exception.PciRequestFromVIFNotFound(
266 pci_slot=vif_pci_dev_addr,
267 node_id=cn_id)
270def get_pci_requests_from_flavor(
271 flavor: 'objects.Flavor', affinity_policy: ty.Optional[str] = None,
272) -> 'objects.InstancePCIRequests':
273 """Validate and return PCI requests.
275 The ``pci_passthrough:alias`` extra spec describes the flavor's PCI
276 requests. The extra spec's value is a comma-separated list of format
277 ``alias_name_x:count, alias_name_y:count, ... ``, where ``alias_name`` is
278 defined in ``pci.alias`` configurations.
280 The flavor's requirement is translated into a PCI requests list. Each
281 entry in the list is an instance of nova.objects.InstancePCIRequests with
282 four keys/attributes.
284 - 'spec' states the PCI device properties requirement
285 - 'count' states the number of devices
286 - 'alias_name' (optional) is the corresponding alias definition name
287 - 'numa_policy' (optional) states the required NUMA affinity of the devices
289 For example, assume alias configuration is::
291 {
292 'vendor_id':'8086',
293 'device_id':'1502',
294 'name':'alias_1'
295 }
297 While flavor extra specs includes::
299 'pci_passthrough:alias': 'alias_1:2'
301 The returned ``pci_requests`` are::
303 [{
304 'count':2,
305 'specs': [{'vendor_id':'8086', 'device_id':'1502'}],
306 'alias_name': 'alias_1'
307 }]
309 :param flavor: The flavor to be checked
310 :param affinity_policy: pci numa affinity policy
311 :returns: A list of PCI requests
312 :rtype: nova.objects.InstancePCIRequests
313 :raises: exception.PciRequestAliasNotDefined if an invalid PCI alias is
314 provided
315 :raises: exception.PciInvalidAlias if the configuration contains invalid
316 aliases.
317 """
318 pci_requests: ty.List[objects.InstancePCIRequest] = []
319 if ('extra_specs' in flavor and
320 'pci_passthrough:alias' in flavor['extra_specs']):
321 pci_requests = _translate_alias_to_requests(
322 flavor['extra_specs']['pci_passthrough:alias'],
323 affinity_policy=affinity_policy)
325 return objects.InstancePCIRequests(requests=pci_requests)