summaryrefslogtreecommitdiff
path: root/nova/pci/devspec.py
diff options
context:
space:
mode:
Diffstat (limited to 'nova/pci/devspec.py')
-rw-r--r--nova/pci/devspec.py148
1 files changed, 117 insertions, 31 deletions
diff --git a/nova/pci/devspec.py b/nova/pci/devspec.py
index 4a663c81ac..386005c8eb 100644
--- a/nova/pci/devspec.py
+++ b/nova/pci/devspec.py
@@ -12,6 +12,7 @@
# under the License.
import abc
+import copy
import re
import string
import typing as ty
@@ -19,7 +20,10 @@ import typing as ty
from nova import exception
from nova.i18n import _
from nova import objects
+from nova.pci.request import PCI_REMOTE_MANAGED_TAG
from nova.pci import utils
+from oslo_log import log as logging
+from oslo_utils import strutils
MAX_VENDOR_ID = 0xFFFF
MAX_PRODUCT_ID = 0xFFFF
@@ -30,6 +34,7 @@ MAX_SLOT = 0x1F
ANY = '*'
REGEX_ANY = '.*'
+LOG = logging.getLogger(__name__)
PCISpecAddressType = ty.Union[ty.Dict[str, str], str]
@@ -37,7 +42,7 @@ PCISpecAddressType = ty.Union[ty.Dict[str, str], str]
class PciAddressSpec(metaclass=abc.ABCMeta):
"""Abstract class for all PCI address spec styles
- This class checks the address fields of the pci.passthrough_whitelist
+ This class checks the address fields of the pci.device_spec
"""
def __init__(self, pci_addr: str) -> None:
@@ -66,11 +71,11 @@ class PciAddressSpec(metaclass=abc.ABCMeta):
try:
v = int(a, 16)
except ValueError:
- raise exception.PciConfigInvalidWhitelist(
+ raise exception.PciConfigInvalidSpec(
reason=_("property %(property)s ('%(attr)s') does not parse "
"as a hex number.") % {'property': prop, 'attr': a})
if v > maxval:
- raise exception.PciConfigInvalidWhitelist(
+ raise exception.PciConfigInvalidSpec(
reason=_("property %(property)s (%(attr)s) is greater than "
"the maximum allowable value (%(max)X).") %
{'property': prop, 'attr': a, 'max': maxval})
@@ -111,6 +116,9 @@ class PhysicalPciAddress(PciAddressSpec):
]
return all(conditions)
+ def __str__(self):
+ return f'{self.domain}:{self.bus}:{self.slot}.{self.func}'
+
class PciAddressGlobSpec(PciAddressSpec):
"""Manages the address fields with glob style.
@@ -188,19 +196,19 @@ class PciAddressRegexSpec(PciAddressSpec):
class WhitelistPciAddress(object):
"""Manages the address fields of the whitelist.
- This class checks the address fields of the pci.passthrough_whitelist
+ This class checks the address fields of the pci.device_spec
configuration option, validating the address fields.
Example configs:
| [pci]
- | passthrough_whitelist = {"address":"*:0a:00.*",
- | "physical_network":"physnet1"}
- | passthrough_whitelist = {"address": {"domain": ".*",
- "bus": "02",
- "slot": "01",
- "function": "[0-2]"},
- "physical_network":"net1"}
- | passthrough_whitelist = {"vendor_id":"1137","product_id":"0071"}
+ | device_spec = {"address":"*:0a:00.*",
+ | "physical_network":"physnet1"}
+ | device_spec = {"address": {"domain": ".*",
+ "bus": "02",
+ "slot": "01",
+ "function": "[0-2]"},
+ "physical_network":"net1"}
+ | device_spec = {"vendor_id":"1137","product_id":"0071"}
"""
@@ -247,7 +255,7 @@ class WhitelistPciAddress(object):
# Try to match on the parent PCI address if the PciDeviceSpec is a
# PF (sriov is available) and the device to match is a VF. This
# makes it possible to specify the PCI address of a PF in the
- # pci.passthrough_whitelist to match any of its VFs' PCI addresses.
+ # pci.device_spec to match any of its VFs' PCI addresses.
if self.is_physical_function and pci_phys_addr:
pci_phys_addr_obj = PhysicalPciAddress(pci_phys_addr)
if self.pci_address_spec.match(pci_phys_addr_obj):
@@ -260,9 +268,27 @@ class WhitelistPciAddress(object):
class PciDeviceSpec(PciAddressSpec):
def __init__(self, dev_spec: ty.Dict[str, str]) -> None:
+ # stored for better error reporting
+ self.dev_spec_conf = copy.deepcopy(dev_spec)
+ # the non tag fields (i.e. address, devname) will be removed by
+ # _init_dev_details
self.tags = dev_spec
self._init_dev_details()
+ def _address_obj(self) -> ty.Optional[WhitelistPciAddress]:
+ address_obj = None
+ if self.dev_name:
+ address_str, pf = utils.get_function_by_ifname(self.dev_name)
+ if not address_str:
+ return None
+ # Note(moshele): In this case we always passing a string
+ # of the PF pci address
+ address_obj = WhitelistPciAddress(address_str, pf)
+ else: # use self.address
+ address_obj = self.address
+
+ return address_obj
+
def _init_dev_details(self) -> None:
self.vendor_id = self.tags.pop("vendor_id", ANY)
self.product_id = self.tags.pop("product_id", ANY)
@@ -283,19 +309,72 @@ class PciDeviceSpec(PciAddressSpec):
if not self.dev_name:
self.address = WhitelistPciAddress(address or '*:*:*.*', False)
- def match(self, dev_dict: ty.Dict[str, str]) -> bool:
- address_obj: ty.Optional[WhitelistPciAddress]
-
- if self.dev_name:
- address_str, pf = utils.get_function_by_ifname(self.dev_name)
- if not address_str:
- return False
- # Note(moshele): In this case we always passing a string
- # of the PF pci address
- address_obj = WhitelistPciAddress(address_str, pf)
- else: # use self.address
- address_obj = self.address
-
+ # PFs with remote_managed tags are explicitly not supported. If they
+ # are tagged as such by mistake in the whitelist Nova will
+ # raise an exception. The reason for excluding PFs is the lack of a way
+ # for an instance to access the control plane at the remote side (e.g.
+ # on a DPU) for managing the PF representor corresponding to the PF.
+ address_obj = self._address_obj()
+ self._remote_managed = strutils.bool_from_string(
+ self.tags.get(PCI_REMOTE_MANAGED_TAG))
+ if self._remote_managed:
+ if address_obj is None:
+ # Note that this will happen if a netdev was specified in the
+ # whitelist but it is not actually present on a system - in
+ # this case Nova is not able to look up an address by
+ # a netdev name.
+ raise exception.PciDeviceRemoteManagedNotPresent()
+ elif address_obj.is_physical_function:
+ pf_addr = str(address_obj.pci_address_spec)
+ vf_product_id = utils.get_vf_product_id_by_pf_addr(pf_addr)
+ # VF vendor IDs have to match the corresponding PF vendor IDs
+ # per the SR-IOV spec so we use it for matching here.
+ pf_vendor_id, pf_product_id = utils.get_pci_ids_by_pci_addr(
+ pf_addr)
+ # Check the actual vendor ID and VF product ID of an assumed
+ # VF (based on the actual PF). The VF product ID must match
+ # the actual one if this is a VF device spec.
+ if (self.product_id == vf_product_id and
+ self.vendor_id in (pf_vendor_id, ANY)):
+ pass
+ elif (self.product_id in (pf_product_id, ANY) and
+ self.vendor_id in (pf_vendor_id, ANY)):
+ raise exception.PciDeviceInvalidPFRemoteManaged(
+ address_obj.pci_address_spec)
+ else:
+ # The specified product and vendor IDs of what is supposed
+ # to be a VF corresponding to the PF PCI address do not
+ # match the actual ones for this PF. This means that the
+ # whitelist is invalid.
+ raise exception.PciConfigInvalidSpec(
+ reason=_('the specified VF vendor ID %(vendor_id)s and'
+ ' product ID %(product_id)s do not match the'
+ ' expected VF IDs based on the corresponding'
+ ' PF identified by PCI address %(pf_addr)s') %
+ {'vendor_id': self.vendor_id,
+ 'product_id': self.product_id,
+ 'pf_addr': pf_addr})
+
+ def _ensure_remote_managed_dev_vpd_serial(
+ self, dev_dict: ty.Dict[str, ty.Any]) -> bool:
+ """Ensure the presence of a serial number field in PCI VPD.
+
+ A card serial number extracted from PCI VPD is required to allow a
+ networking backend to identify which remote host needs to program a
+ given device. So if a device is tagged as remote_managed, it must
+ have the card serial number or be filtered out.
+ """
+ if not self._remote_managed:
+ return True
+ card_sn = dev_dict.get('capabilities', {}).get(
+ 'vpd', {}).get('card_serial_number')
+ # None or empty card_serial_number should be filtered out. That would
+ # mean either no serial number in the VPD (if present at all) or SN is
+ # an empty string which is not useful for device identification.
+ return bool(card_sn)
+
+ def match(self, dev_dict: ty.Dict[str, ty.Any]) -> bool:
+ address_obj: ty.Optional[WhitelistPciAddress] = self._address_obj()
if not address_obj:
return False
@@ -303,13 +382,20 @@ class PciDeviceSpec(PciAddressSpec):
self.vendor_id in (ANY, dev_dict['vendor_id']),
self.product_id in (ANY, dev_dict['product_id']),
address_obj.match(dev_dict['address'],
- dev_dict.get('parent_addr'))])
+ dev_dict.get('parent_addr')),
+ self._ensure_remote_managed_dev_vpd_serial(dev_dict),
+ ])
def match_pci_obj(self, pci_obj: 'objects.PciDevice') -> bool:
- return self.match({'vendor_id': pci_obj.vendor_id,
- 'product_id': pci_obj.product_id,
- 'address': pci_obj.address,
- 'parent_addr': pci_obj.parent_addr})
+ dev_dict = {
+ 'vendor_id': pci_obj.vendor_id,
+ 'product_id': pci_obj.product_id,
+ 'address': pci_obj.address,
+ 'parent_addr': pci_obj.parent_addr,
+ 'capabilities': {
+ 'vpd': {'card_serial_number': pci_obj.card_serial_number}}
+ }
+ return self.match(dev_dict)
def get_tags(self) -> ty.Dict[str, str]:
return self.tags