pci unify management

bp pcie-unified-management
Change-Id: I2a285b61c4f41e999670715dfa2cef8c910467b9

Change-Id: Iaddcd2d5dfef987b3d11d30a76e2775c8e60ba38
This commit is contained in:
songwenping 2024-06-28 17:10:31 +08:00
parent 63c239bf3e
commit 5f896d2931
12 changed files with 841 additions and 0 deletions

View File

@ -0,0 +1,29 @@
# Copyright 2024 Inspur.
#
# 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.
"""
Cyborg Pci driver implementation.
"""
class PciDriver(object):
"""Base class for Pci drivers.
"""
def __init__(self, *args, **kwargs):
pass
def discover(self):
raise NotImplementedError()

View File

@ -0,0 +1,290 @@
#
# 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 abc
import re
import string
import six
from cyborg.accelerator.drivers.pci import utils
from cyborg.common import exception
from cyborg.common.i18n import _
MAX_VENDOR_ID = 0xFFFF
MAX_PRODUCT_ID = 0xFFFF
MAX_FUNC = 0x7
MAX_DOMAIN = 0xFFFF
MAX_BUS = 0xFF
MAX_SLOT = 0x1F
ANY = '*'
REGEX_ANY = '.*'
@six.add_metaclass(abc.ABCMeta)
class PciAddressSpec(object):
"""Abstract class for all PCI address spec styles
This class checks the address fields of the pci.passthrough_whitelist
"""
@abc.abstractmethod
def match(self, pci_addr):
pass
def is_single_address(self):
return all([
all(c in string.hexdigits for c in self.domain),
all(c in string.hexdigits for c in self.bus),
all(c in string.hexdigits for c in self.slot),
all(c in string.hexdigits for c in self.func)])
def _set_pci_dev_info(self, prop, maxval, hex_value):
a = getattr(self, prop)
if a == ANY:
return
try:
v = int(a, 16)
except ValueError:
raise exception.PciConfigInvalidWhitelist(
reason=_("property %(property)s ('%(attr)s') does not parse "
"as a hex number.") % {'property': prop, 'attr': a})
if v > maxval:
raise exception.PciConfigInvalidWhitelist(
reason=_("property %(property)s (%(attr)s) is greater than "
"the maximum allowable value (%(max)X).") % {
'property': prop, 'attr': a, 'max': maxval})
setattr(self, prop, hex_value % v)
class PhysicalPciAddress(PciAddressSpec):
"""Manages the address fields for a fully-qualified PCI address.
This function class will validate the address fields for a single
PCI device.
"""
def __init__(self, pci_addr):
try:
if isinstance(pci_addr, dict):
self.domain = pci_addr['domain']
self.bus = pci_addr['bus']
self.slot = pci_addr['slot']
self.func = pci_addr['function']
else:
self.domain, self.bus, self.slot, self.func = (
utils.get_pci_address_fields(pci_addr))
self._set_pci_dev_info('func', MAX_FUNC, '%1x')
self._set_pci_dev_info('domain', MAX_DOMAIN, '%04x')
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
self._set_pci_dev_info('slot', MAX_SLOT, '%02x')
except (KeyError, ValueError):
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
def match(self, phys_pci_addr):
conditions = [
self.domain == phys_pci_addr.domain,
self.bus == phys_pci_addr.bus,
self.slot == phys_pci_addr.slot,
self.func == phys_pci_addr.func,
]
return all(conditions)
class PciAddressGlobSpec(PciAddressSpec):
"""Manages the address fields with glob style.
This function class will validate the address fields with glob style,
check for wildcards, and insert wildcards where the field is left blank.
"""
def __init__(self, pci_addr):
self.domain = ANY
self.bus = ANY
self.slot = ANY
self.func = ANY
dbs, sep, func = pci_addr.partition('.')
if func:
self.func = func.strip()
self._set_pci_dev_info('func', MAX_FUNC, '%01x')
if dbs:
dbs_fields = dbs.split(':')
if len(dbs_fields) > 3:
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
# If we got a partial address like ":00.", we need to turn this
# into a domain of ANY, a bus of ANY, and a slot of 00. This code
# allows the address bus and/or domain to be left off
dbs_all = [ANY] * (3 - len(dbs_fields))
dbs_all.extend(dbs_fields)
dbs_checked = [s.strip() or ANY for s in dbs_all]
self.domain, self.bus, self.slot = dbs_checked
self._set_pci_dev_info('domain', MAX_DOMAIN, '%04x')
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
self._set_pci_dev_info('slot', MAX_SLOT, '%02x')
def match(self, phys_pci_addr):
conditions = [
self.domain in (ANY, phys_pci_addr.domain),
self.bus in (ANY, phys_pci_addr.bus),
self.slot in (ANY, phys_pci_addr.slot),
self.func in (ANY, phys_pci_addr.func)
]
return all(conditions)
class PciAddressRegexSpec(PciAddressSpec):
"""Manages the address fields with regex style.
This function class will validate the address fields with regex style.
The validation includes check for all PCI address attributes and validate
their regex.
"""
def __init__(self, pci_addr):
try:
self.domain = pci_addr.get('domain', REGEX_ANY)
self.bus = pci_addr.get('bus', REGEX_ANY)
self.slot = pci_addr.get('slot', REGEX_ANY)
self.func = pci_addr.get('function', REGEX_ANY)
self.domain_regex = re.compile(self.domain)
self.bus_regex = re.compile(self.bus)
self.slot_regex = re.compile(self.slot)
self.func_regex = re.compile(self.func)
except re.error:
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
def match(self, phys_pci_addr):
conditions = [
bool(self.domain_regex.match(phys_pci_addr.domain)),
bool(self.bus_regex.match(phys_pci_addr.bus)),
bool(self.slot_regex.match(phys_pci_addr.slot)),
bool(self.func_regex.match(phys_pci_addr.func))
]
return all(conditions)
class WhitelistPciAddress(object):
"""Manages the address fields of the whitelist.
This class checks the address fields of the pci.passthrough_whitelist
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"}
"""
def __init__(self, pci_addr, is_physical_function):
self.is_physical_function = is_physical_function
self._init_address_fields(pci_addr)
def _check_physical_function(self):
if self.pci_address_spec.is_single_address():
self.is_physical_function = (
utils.is_physical_function(
self.pci_address_spec.domain,
self.pci_address_spec.bus,
self.pci_address_spec.slot,
self.pci_address_spec.func))
def _init_address_fields(self, pci_addr):
if not self.is_physical_function:
if isinstance(pci_addr, six.string_types):
self.pci_address_spec = PciAddressGlobSpec(pci_addr)
elif isinstance(pci_addr, dict):
self.pci_address_spec = PciAddressRegexSpec(pci_addr)
else:
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
self._check_physical_function()
else:
self.pci_address_spec = PhysicalPciAddress(pci_addr)
def match(self, pci_addr, pci_phys_addr):
"""Match a device to this PciAddress. Assume this is called given
pci_addr and pci_phys_addr reported by libvirt, no attempt is made to
verify if pci_addr is a VF of pci_phys_addr.
:param pci_addr: PCI address of the device to match.
:param pci_phys_addr: PCI address of the parent of the device to match
(or None if the device is not a VF).
"""
# 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.
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):
return True
# Try to match on the device PCI address only.
pci_addr_obj = PhysicalPciAddress(pci_addr)
return self.pci_address_spec.match(pci_addr_obj)
class PciDeviceSpec(PciAddressSpec):
def __init__(self, dev_spec):
self.tags = dev_spec
self._init_dev_details()
def _init_dev_details(self):
self.vendor_id = self.tags.pop("vendor_id", ANY)
self.product_id = self.tags.pop("product_id", ANY)
# Note(moshele): The address attribute can be a string or a dict.
# For glob syntax or specific pci it is a string and for regex syntax
# it is a dict. The WhitelistPciAddress class handles both types.
self.address = self.tags.pop("address", None)
self.dev_name = self.tags.pop("devname", None)
self.vendor_id = self.vendor_id.strip()
self._set_pci_dev_info('vendor_id', MAX_VENDOR_ID, '%04x')
self._set_pci_dev_info('product_id', MAX_PRODUCT_ID, '%04x')
if self.address and self.dev_name:
raise exception.PciDeviceInvalidDeviceName()
if not self.dev_name:
pci_address = self.address or "*:*:*.*"
self.address = WhitelistPciAddress(pci_address, False)
def match(self, dev_dict):
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)
elif self.address:
address_obj = self.address
return all([
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'))])
def match_pci_obj(self, pci_obj):
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})
def get_tags(self):
return self.tags

View File

@ -0,0 +1,30 @@
# Copyright 2024 Inspur.
#
# 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.
"""
Cyborg PCI driver implementation.
"""
from cyborg.accelerator.drivers.pci.base import PciDriver
from cyborg.accelerator.drivers.pci.pci import sysinfo
class PCIDriver(PciDriver):
"""Class for Pci drivers.
Vendor should implement their specific drivers in this class.
"""
def discover(self):
return sysinfo.discover()

View File

@ -0,0 +1,147 @@
# Copyright 2024 Inspur.
#
# 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.
"""
Cyborg PCI driver implementation.
"""
from oslo_log import log as logging
from oslo_serialization import jsonutils
from cyborg.accelerator.common import utils
from cyborg.accelerator.drivers.pci import utils as pci_utils
from cyborg.accelerator.drivers.pci import whitelist
from cyborg.common import constants
import cyborg.conf
from cyborg.objects.driver_objects import driver_attach_handle
from cyborg.objects.driver_objects import driver_attribute
from cyborg.objects.driver_objects import driver_controlpath_id
from cyborg.objects.driver_objects import driver_deployable
from cyborg.objects.driver_objects import driver_device
LOG = logging.getLogger(__name__)
CONF = cyborg.conf.CONF
def _get_traits(vendor_id, product_id):
"""Generate traits for PCIs.
: param vendor_id: vendor_id of PCI, eg."10de"
: param product_id: product_id of PCI, eg."1eb8".
Example PGPU traits:
{traits:["OWNER_CYBORG", "CUSTOM_PCI_1EB8"]}
"""
vendor_name = pci_utils.VENDOR_MAPS.get(vendor_id).upper()
traits = ["CUSTOM_PCI_" + vendor_name]
# PCIE trait
product_trait = "_".join(('CUSTOM_PCI_PRODUCT_ID', product_id.upper()))
traits.append(product_trait)
return {"traits": traits}
def _generate_attribute_list(pci):
attr_list = []
index = 0
for k, v in pci.items():
if k == "rc":
driver_attr = driver_attribute.DriverAttribute()
driver_attr.key, driver_attr.value = k, v
attr_list.append(driver_attr)
if k == "traits":
values = pci.get(k, [])
for val in values:
driver_attr = driver_attribute.DriverAttribute(
key="trait" + str(index), value=val)
index = index + 1
attr_list.append(driver_attr)
return attr_list
def _generate_attach_handle(pci):
driver_ah = driver_attach_handle.DriverAttachHandle()
driver_ah.in_use = False
driver_ah.attach_type = constants.AH_TYPE_PCI
driver_ah.attach_info = utils.pci_str_to_json(pci["devices"])
return driver_ah
def _generate_dep_list(pci):
dep_list = []
driver_dep = driver_deployable.DriverDeployable()
driver_dep.attribute_list = _generate_attribute_list(pci)
driver_dep.attach_handle_list = []
# NOTE(wangzhh): The name of deployable should be unique, its format is
# under disscussion, may looks like
# <ComputeNodeName>_<NumaNodeName>_<CyborgName>_<NumInHost>
# NOTE(yumeng) Since Wallaby release, the deplpyable_name is named as
# <Compute_hostname>_<Device_address>
driver_dep.name = pci.get('hostname', '') + '_' + pci["devices"]
driver_dep.driver_name = \
pci_utils.VENDOR_MAPS.get(pci["vendor_id"]).upper()
driver_dep.num_accelerators = 1
driver_dep.attach_handle_list = [_generate_attach_handle(pci)]
dep_list.append(driver_dep)
return dep_list, driver_dep.num_accelerators
def _generate_controlpath_id(pci):
driver_cpid = driver_controlpath_id.DriverControlPathID()
driver_cpid.cpid_type = "PCI"
driver_cpid.cpid_info = utils.pci_str_to_json(pci["devices"])
return driver_cpid
def _generate_driver_device(pci):
driver_device_obj = driver_device.DriverDevice()
driver_device_obj.vendor = pci['vendor_id']
driver_device_obj.model = pci['product_id']
std_board_info = {'product_id': pci.get('product_id'),
'controller': pci.get('controller'),
}
driver_device_obj.std_board_info = jsonutils.dumps(std_board_info)
driver_device_obj.type = constants.DEVICE_GPU
driver_device_obj.stub = pci.get('stub', False)
driver_device_obj.controlpath_id = _generate_controlpath_id(pci)
driver_device_obj.deployable_list, ais = _generate_dep_list(pci)
driver_device_obj.vendor_board_info = pci.get('vendor_board_info',
"miss_vb_info")
return driver_device_obj
def _discover_pcis():
cyborg.conf.devices.register_dynamic_opts(CONF)
# discover pci devices by "lspci"
pci_list = []
pcis = pci_utils.get_pci_devices()
LOG.info('pcis:%s', pcis)
# report trait,rc and generate driver object
dev_filter = whitelist.Whitelist(CONF.pci.passthrough_whitelist)
for pci in pcis:
m = dev_filter.device_assignable(pci)
if m:
pci_dict = m.groupdict()
# get hostname for deployable_name usage
pci_dict['hostname'] = CONF.host
pci_dict["rc"] = constants.RESOURCES["PCI"]
traits = _get_traits(pci_dict["vendor_id"],
pci_dict["product_id"])
pci_dict.update(traits)
pci_list.append(_generate_driver_device(pci_dict))
LOG.info('pci_list:%s', pci_list)
return pci_list
def discover():
devs = _discover_pcis()
return devs

View File

@ -0,0 +1,232 @@
# Copyright 2024 Inspur.
#
# 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 glob
import os
import re
from oslo_concurrency import processutils
from oslo_log import log as logging
import six
from cyborg.common import exception
import cyborg.privsep
LOG = logging.getLogger(__name__)
PCI_VENDOR_PATTERN = "^(hex{4})$".replace("hex", r"[\da-fA-F]")
_PCI_ADDRESS_PATTERN = ("^(hex{4}):(hex{2}):(hex{2}).(oct{1})$".
replace("hex", r"[\da-fA-F]").
replace("oct", "[0-7]"))
_PCI_ADDRESS_REGEX = re.compile(_PCI_ADDRESS_PATTERN)
_SRIOV_TOTALVFS = "sriov_totalvfs"
VENDOR_MAPS = {"10de": "nvidia", "102b": "matrox"}
@cyborg.privsep.sys_admin_pctxt.entrypoint
def get_pci_devices():
cmd = ['lspci', '-nnn', '-D']
return processutils.execute(*cmd)
def pci_device_prop_match(pci_dev, specs):
"""Check if the pci_dev meet spec requirement
Specs is a list of PCI device property requirements.
An example of device requirement that the PCI should be either:
a) Device with vendor_id as 0x8086 and product_id as 0x8259, or
b) Device with vendor_id as 0x10de and product_id as 0x10d8:
[{"vendor_id":"8086", "product_id":"8259"},
{"vendor_id":"10de", "product_id":"10d8",
"capabilities_network": ["rx", "tx", "tso", "gso"]}]
"""
def _matching_devices(spec):
for k, v in spec.items():
pci_dev_v = pci_dev.get(k)
if isinstance(v, list) and isinstance(pci_dev_v, list):
if not all(x in pci_dev.get(k) for x in v):
return False
else:
# We don't need to check case for tags in order to avoid any
# mismatch with the tags provided by users for port
# binding profile and the ones configured by operators
# with pci whitelist option.
if isinstance(v, six.string_types):
v = v.lower()
if isinstance(pci_dev_v, six.string_types):
pci_dev_v = pci_dev_v.lower()
if pci_dev_v != v:
return False
return True
return any(_matching_devices(spec) for spec in specs)
def parse_address(address):
"""Returns (domain, bus, slot, function) from PCI address that is stored in
PciDevice DB table.
"""
m = _PCI_ADDRESS_REGEX.match(address)
if not m:
raise exception.PciDeviceWrongAddressFormat(address=address)
return m.groups()
def get_pci_address_fields(pci_addr):
"""Parse a fully-specified PCI device address.
Does not validate that the components are valid hex or wildcard values.
:param pci_addr: A string of the form "<domain>:<bus>:<slot>.<function>".
:return: A 4-tuple of strings ("<domain>", "<bus>", "<slot>", "<function>")
"""
dbs, sep, func = pci_addr.partition('.')
domain, bus, slot = dbs.split(':')
return domain, bus, slot, func
def get_pci_address(domain, bus, slot, func):
"""Assembles PCI address components into a fully-specified PCI address.
Does not validate that the components are valid hex or wildcard values.
:param domain, bus, slot, func: Hex or wildcard strings.
:return: A string of the form "<domain>:<bus>:<slot>.<function>".
"""
return '%s:%s:%s.%s' % (domain, bus, slot, func)
def get_function_by_ifname(ifname):
"""Given the device name, returns the PCI address of a device
and returns True if the address is in a physical function.
"""
dev_path = "/sys/class/net/%s/device" % ifname
if os.path.isdir(dev_path):
try:
# sriov_totalvfs contains the maximum possible VFs for this PF
with open(os.path.join(dev_path, _SRIOV_TOTALVFS)) as fd:
sriov_totalvfs = int(fd.read())
return (os.readlink(dev_path).strip("./"),
sriov_totalvfs > 0)
except (IOError, ValueError):
return os.readlink(dev_path).strip("./"), False
return None, False
def is_physical_function(domain, bus, slot, function):
dev_path = "/sys/bus/pci/devices/%(d)s:%(b)s:%(s)s.%(f)s/" % {
"d": domain, "b": bus, "s": slot, "f": function}
if os.path.isdir(dev_path):
try:
with open(dev_path + _SRIOV_TOTALVFS) as fd:
sriov_totalvfs = int(fd.read())
return sriov_totalvfs > 0
except (IOError, ValueError):
pass
return False
def _get_sysfs_netdev_path(pci_addr, pf_interface):
"""Get the sysfs path based on the PCI address of the device.
Assumes a networking device - will not check for the existence of the path.
"""
if pf_interface:
return "/sys/bus/pci/devices/%s/physfn/net" % pci_addr
return "/sys/bus/pci/devices/%s/net" % pci_addr
def get_ifname_by_pci_address(pci_addr, pf_interface=False):
"""Get the interface name based on a VF's pci address.
The returned interface name is either the parent PF's or that of the VF
itself based on the argument of pf_interface.
"""
dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface)
try:
dev_info = os.listdir(dev_path)
return dev_info.pop()
except Exception:
raise exception.PciDeviceNotFoundById(id=pci_addr)
def get_mac_by_pci_address(pci_addr, pf_interface=False):
"""Get the MAC address of the nic based on its PCI address.
Raises PciDeviceNotFoundById in case the pci device is not a NIC
"""
dev_path = _get_sysfs_netdev_path(pci_addr, pf_interface)
if_name = get_ifname_by_pci_address(pci_addr, pf_interface)
addr_file = os.path.join(dev_path, if_name, 'address')
try:
with open(addr_file) as f:
mac = next(f).strip()
return mac
except (IOError, StopIteration) as e:
LOG.warning("Could not find the expected sysfs file for "
"determining the MAC address of the PCI device "
"%(addr)s. May not be a NIC. Error: %(e)s",
{'addr': pci_addr, 'e': e})
raise exception.PciDeviceNotFoundById(id=pci_addr)
def get_vf_num_by_pci_address(pci_addr):
"""Get the VF number based on a VF's pci address
A VF is associated with an VF number, which ip link command uses to
configure it. This number can be obtained from the PCI device filesystem.
"""
VIRTFN_RE = re.compile(r"virtfn(\d+)")
virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
vf_num = None
try:
for vf_path in glob.iglob(virtfns_path):
if re.search(pci_addr, os.readlink(vf_path)):
t = VIRTFN_RE.search(vf_path)
vf_num = t.group(1)
break
except Exception:
pass
if vf_num is None:
raise exception.PciDeviceNotFoundById(id=pci_addr)
return vf_num
def get_net_name_by_vf_pci_address(vfaddress):
"""Given the VF PCI address, returns the net device name.
Every VF is associated to a PCI network device. This function
returns the libvirt name given to this network device; e.g.:
<device>
<name>net_enp8s0f0_90_e2_ba_5e_a6_40</name>
...
In the libvirt parser information tree, the network device stores the
network capabilities associated to this device.
"""
try:
mac = get_mac_by_pci_address(vfaddress).split(':')
ifname = get_ifname_by_pci_address(vfaddress)
return ("net_%(ifname)s_%(mac)s" %
{'ifname': ifname, 'mac': '_'.join(mac)})
except Exception:
LOG.warning("No net device was found for VF %(vfaddress)s",
{'vfaddress': vfaddress})
return

View File

@ -0,0 +1,91 @@
# Copyright 2024 Inspur.
#
# 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 oslo_serialization import jsonutils
from cyborg.accelerator.drivers.pci import devspec
from cyborg.common import exception
from cyborg.common.i18n import _
class Whitelist(object):
"""White list class to represent assignable pci devices.
Not all devices on a compute node can be assigned to a guest. The cloud
administrator decides which devices can be assigned based on ``vendor_id``
or ``product_id``, etc. If no white list is specified, no devices will be
assignable.
"""
def __init__(self, whitelist_spec=None):
"""White list constructor
For example, the following json string specifies that devices whose
vendor_id is '8086' and product_id is '1520' can be assigned
to guests. ::
'[{"product_id":"1520", "vendor_id":"8086"}]'
:param whitelist_spec: A JSON string for a dictionary or list thereof.
Each dictionary specifies the pci device properties requirement.
See the definition of ``passthrough_whitelist`` in
``nova.conf.pci`` for details and examples.
"""
if whitelist_spec:
self.specs = self._parse_white_list_from_config(whitelist_spec)
else:
self.specs = []
@staticmethod
def _parse_white_list_from_config(whitelists):
"""Parse and validate the pci whitelist from the nova config."""
specs = []
for jsonspec in whitelists:
try:
dev_spec = jsonutils.loads(jsonspec)
except ValueError:
raise exception.PciConfigInvalidWhitelist(
reason=_("Invalid entry: '%s'") % jsonspec)
if isinstance(dev_spec, dict):
dev_spec = [dev_spec]
elif not isinstance(dev_spec, list):
raise exception.PciConfigInvalidWhitelist(
reason=_("Invalid entry: '%s'; "
"Expecting list or dict") % jsonspec)
for ds in dev_spec:
if not isinstance(ds, dict):
raise exception.PciConfigInvalidWhitelist(
reason=_("Invalid entry: '%s'; "
"Expecting dict") % ds)
spec = devspec.PciDeviceSpec(ds)
specs.append(spec)
return specs
def device_assignable(self, dev):
"""Check if a device can be assigned to a guest.
:param dev: A dictionary describing the device properties
"""
for spec in self.specs:
if spec.match(dev):
return True
return False
def get_devspec(self, pci_dev):
for spec in self.specs:
if spec.match_pci_obj(pci_dev):
return spec

View File

@ -88,6 +88,7 @@ RESOURCES = {
"QAT": "CUSTOM_QAT", "QAT": "CUSTOM_QAT",
"NIC": "CUSTOM_NIC", "NIC": "CUSTOM_NIC",
"SSD": 'CUSTOM_SSD', "SSD": 'CUSTOM_SSD',
"PCI": 'CUSTOM_PCI',
} }

View File

@ -425,3 +425,12 @@ class FPGAProgramError(CyborgException):
class PciDeviceNotFoundById(NotFound): class PciDeviceNotFoundById(NotFound):
_msg_fmt = _("PCI device %(id)s not found") _msg_fmt = _("PCI device %(id)s not found")
class PciConfigInvalidWhitelist(Invalid):
_msg_fmt = _("Invalid PCI devices whitelist config: %(reason)s.")
class PciDeviceInvalidDeviceName(CyborgException):
_msg_fmt = _("Invalid PCI whitelist: The PCI whitelist can specify "
"devname or address, but not both.")

View File

@ -14,6 +14,15 @@
from oslo_config import cfg from oslo_config import cfg
pci_group = cfg.OptGroup(
name='pci',
title='PCI passthrough options')
pci_opts = [
cfg.MultiStrOpt('passthrough_whitelist',
default=[],
help=" ")
]
nic_group = cfg.OptGroup( nic_group = cfg.OptGroup(
name='nic_devices', name='nic_devices',
@ -72,6 +81,8 @@ def register_opts(conf):
conf.register_opts(nic_opts, group=nic_group) conf.register_opts(nic_opts, group=nic_group)
conf.register_group(gpu_group) conf.register_group(gpu_group)
conf.register_opts(vgpu_opts, group=gpu_group) conf.register_opts(vgpu_opts, group=gpu_group)
conf.register_group(pci_group)
conf.register_opts(pci_opts, group=pci_group)
def register_dynamic_opts(conf): def register_dynamic_opts(conf):

View File

@ -57,6 +57,7 @@ cyborg.accelerator.driver =
intel_qat_driver = cyborg.accelerator.drivers.qat.intel.driver:IntelQATDriver intel_qat_driver = cyborg.accelerator.drivers.qat.intel.driver:IntelQATDriver
intel_nic_driver = cyborg.accelerator.drivers.nic.intel.driver:IntelNICDriver intel_nic_driver = cyborg.accelerator.drivers.nic.intel.driver:IntelNICDriver
inspur_nvme_ssd_driver = cyborg.accelerator.drivers.ssd.inspur.driver:InspurNVMeSSDDriver inspur_nvme_ssd_driver = cyborg.accelerator.drivers.ssd.inspur.driver:InspurNVMeSSDDriver
pci_driver = cyborg.accelerator.drivers.pci.pci.driver:PCIDriver
oslo.config.opts = oslo.config.opts =
cyborg = cyborg.conf.opts:list_opts cyborg = cyborg.conf.opts:list_opts