mypy: Add type annotations to 'nova.pci'
The 'nova.pci' module is poorly understood and difficult to debug. Start adding type hints to make this a little easier to parse and catch dumb errors. Some code needs to be reworked to make 'mypy' happy, but it's mostly just type annotations. Note that because of how the 'nova.objects' module works, we need to delay interpolation by using forward references, or expressing the type as string literals to be resolved later [1]. [1] https://www.python.org/dev/peps/pep-0484/#forward-references Change-Id: I2a609606806c6cabdf95d53339335f61743fc5b0 Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
parent
eba9d596da
commit
51d16adda6
|
@ -1,6 +1,7 @@
|
||||||
nova/compute/manager.py
|
nova/compute/manager.py
|
||||||
nova/crypto.py
|
nova/crypto.py
|
||||||
nova/network/neutron.py
|
nova/network/neutron.py
|
||||||
|
nova/pci
|
||||||
nova/privsep/path.py
|
nova/privsep/path.py
|
||||||
nova/scheduler/client/report.py
|
nova/scheduler/client/report.py
|
||||||
nova/scheduler/request_filter.py
|
nova/scheduler/request_filter.py
|
||||||
|
|
|
@ -14,9 +14,11 @@
|
||||||
import abc
|
import abc
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
|
from nova import objects
|
||||||
from nova.pci import utils
|
from nova.pci import utils
|
||||||
|
|
||||||
MAX_VENDOR_ID = 0xFFFF
|
MAX_VENDOR_ID = 0xFFFF
|
||||||
|
@ -29,24 +31,35 @@ ANY = '*'
|
||||||
REGEX_ANY = '.*'
|
REGEX_ANY = '.*'
|
||||||
|
|
||||||
|
|
||||||
|
PCISpecAddressType = ty.Union[ty.Dict[str, str], str]
|
||||||
|
|
||||||
|
|
||||||
class PciAddressSpec(metaclass=abc.ABCMeta):
|
class PciAddressSpec(metaclass=abc.ABCMeta):
|
||||||
"""Abstract class for all PCI address spec styles
|
"""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.passthrough_whitelist
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pci_addr: str) -> None:
|
||||||
|
self.domain = ''
|
||||||
|
self.bus = ''
|
||||||
|
self.slot = ''
|
||||||
|
self.func = ''
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def match(self, pci_addr):
|
def match(self, pci_addr):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def is_single_address(self):
|
def is_single_address(self) -> bool:
|
||||||
return all([
|
return all([
|
||||||
all(c in string.hexdigits for c in self.domain),
|
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.bus),
|
||||||
all(c in string.hexdigits for c in self.slot),
|
all(c in string.hexdigits for c in self.slot),
|
||||||
all(c in string.hexdigits for c in self.func)])
|
all(c in string.hexdigits for c in self.func)])
|
||||||
|
|
||||||
def _set_pci_dev_info(self, prop, maxval, hex_value):
|
def _set_pci_dev_info(
|
||||||
|
self, prop: str, maxval: int, hex_value: str
|
||||||
|
) -> None:
|
||||||
a = getattr(self, prop)
|
a = getattr(self, prop)
|
||||||
if a == ANY:
|
if a == ANY:
|
||||||
return
|
return
|
||||||
|
@ -70,8 +83,10 @@ class PhysicalPciAddress(PciAddressSpec):
|
||||||
This function class will validate the address fields for a single
|
This function class will validate the address fields for a single
|
||||||
PCI device.
|
PCI device.
|
||||||
"""
|
"""
|
||||||
def __init__(self, pci_addr):
|
|
||||||
|
def __init__(self, pci_addr: PCISpecAddressType) -> None:
|
||||||
try:
|
try:
|
||||||
|
# TODO(stephenfin): Is this ever actually a string?
|
||||||
if isinstance(pci_addr, dict):
|
if isinstance(pci_addr, dict):
|
||||||
self.domain = pci_addr['domain']
|
self.domain = pci_addr['domain']
|
||||||
self.bus = pci_addr['bus']
|
self.bus = pci_addr['bus']
|
||||||
|
@ -87,7 +102,7 @@ class PhysicalPciAddress(PciAddressSpec):
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
||||||
|
|
||||||
def match(self, phys_pci_addr):
|
def match(self, phys_pci_addr: PciAddressSpec) -> bool:
|
||||||
conditions = [
|
conditions = [
|
||||||
self.domain == phys_pci_addr.domain,
|
self.domain == phys_pci_addr.domain,
|
||||||
self.bus == phys_pci_addr.bus,
|
self.bus == phys_pci_addr.bus,
|
||||||
|
@ -104,7 +119,7 @@ class PciAddressGlobSpec(PciAddressSpec):
|
||||||
check for wildcards, and insert wildcards where the field is left blank.
|
check for wildcards, and insert wildcards where the field is left blank.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, pci_addr):
|
def __init__(self, pci_addr: str) -> None:
|
||||||
self.domain = ANY
|
self.domain = ANY
|
||||||
self.bus = ANY
|
self.bus = ANY
|
||||||
self.slot = ANY
|
self.slot = ANY
|
||||||
|
@ -129,7 +144,7 @@ class PciAddressGlobSpec(PciAddressSpec):
|
||||||
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
|
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
|
||||||
self._set_pci_dev_info('slot', MAX_SLOT, '%02x')
|
self._set_pci_dev_info('slot', MAX_SLOT, '%02x')
|
||||||
|
|
||||||
def match(self, phys_pci_addr):
|
def match(self, phys_pci_addr: PciAddressSpec) -> bool:
|
||||||
conditions = [
|
conditions = [
|
||||||
self.domain in (ANY, phys_pci_addr.domain),
|
self.domain in (ANY, phys_pci_addr.domain),
|
||||||
self.bus in (ANY, phys_pci_addr.bus),
|
self.bus in (ANY, phys_pci_addr.bus),
|
||||||
|
@ -146,7 +161,8 @@ class PciAddressRegexSpec(PciAddressSpec):
|
||||||
The validation includes check for all PCI address attributes and validate
|
The validation includes check for all PCI address attributes and validate
|
||||||
their regex.
|
their regex.
|
||||||
"""
|
"""
|
||||||
def __init__(self, pci_addr):
|
|
||||||
|
def __init__(self, pci_addr: dict) -> None:
|
||||||
try:
|
try:
|
||||||
self.domain = pci_addr.get('domain', REGEX_ANY)
|
self.domain = pci_addr.get('domain', REGEX_ANY)
|
||||||
self.bus = pci_addr.get('bus', REGEX_ANY)
|
self.bus = pci_addr.get('bus', REGEX_ANY)
|
||||||
|
@ -159,7 +175,7 @@ class PciAddressRegexSpec(PciAddressSpec):
|
||||||
except re.error:
|
except re.error:
|
||||||
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
|
||||||
|
|
||||||
def match(self, phys_pci_addr):
|
def match(self, phys_pci_addr: PciAddressSpec) -> bool:
|
||||||
conditions = [
|
conditions = [
|
||||||
bool(self.domain_regex.match(phys_pci_addr.domain)),
|
bool(self.domain_regex.match(phys_pci_addr.domain)),
|
||||||
bool(self.bus_regex.match(phys_pci_addr.bus)),
|
bool(self.bus_regex.match(phys_pci_addr.bus)),
|
||||||
|
@ -187,11 +203,13 @@ class WhitelistPciAddress(object):
|
||||||
| passthrough_whitelist = {"vendor_id":"1137","product_id":"0071"}
|
| passthrough_whitelist = {"vendor_id":"1137","product_id":"0071"}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, pci_addr, is_physical_function):
|
def __init__(
|
||||||
|
self, pci_addr: PCISpecAddressType, is_physical_function: bool
|
||||||
|
) -> None:
|
||||||
self.is_physical_function = is_physical_function
|
self.is_physical_function = is_physical_function
|
||||||
self._init_address_fields(pci_addr)
|
self._init_address_fields(pci_addr)
|
||||||
|
|
||||||
def _check_physical_function(self):
|
def _check_physical_function(self) -> None:
|
||||||
if self.pci_address_spec.is_single_address():
|
if self.pci_address_spec.is_single_address():
|
||||||
self.is_physical_function = (
|
self.is_physical_function = (
|
||||||
utils.is_physical_function(
|
utils.is_physical_function(
|
||||||
|
@ -200,7 +218,8 @@ class WhitelistPciAddress(object):
|
||||||
self.pci_address_spec.slot,
|
self.pci_address_spec.slot,
|
||||||
self.pci_address_spec.func))
|
self.pci_address_spec.func))
|
||||||
|
|
||||||
def _init_address_fields(self, pci_addr):
|
def _init_address_fields(self, pci_addr: PCISpecAddressType) -> None:
|
||||||
|
self.pci_address_spec: PciAddressSpec
|
||||||
if not self.is_physical_function:
|
if not self.is_physical_function:
|
||||||
if isinstance(pci_addr, str):
|
if isinstance(pci_addr, str):
|
||||||
self.pci_address_spec = PciAddressGlobSpec(pci_addr)
|
self.pci_address_spec = PciAddressGlobSpec(pci_addr)
|
||||||
|
@ -212,10 +231,12 @@ class WhitelistPciAddress(object):
|
||||||
else:
|
else:
|
||||||
self.pci_address_spec = PhysicalPciAddress(pci_addr)
|
self.pci_address_spec = PhysicalPciAddress(pci_addr)
|
||||||
|
|
||||||
def match(self, pci_addr, pci_phys_addr):
|
def match(self, pci_addr: str, pci_phys_addr: ty.Optional[str]) -> bool:
|
||||||
"""Match a device to this PciAddress. Assume this is called given
|
"""Match a device to this PciAddress.
|
||||||
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.
|
Assume this is called with a ``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_addr: PCI address of the device to match.
|
||||||
:param pci_phys_addr: PCI address of the parent of the device to match
|
:param pci_phys_addr: PCI address of the parent of the device to match
|
||||||
|
@ -237,51 +258,57 @@ class WhitelistPciAddress(object):
|
||||||
|
|
||||||
|
|
||||||
class PciDeviceSpec(PciAddressSpec):
|
class PciDeviceSpec(PciAddressSpec):
|
||||||
def __init__(self, dev_spec):
|
def __init__(self, dev_spec: ty.Dict[str, str]) -> None:
|
||||||
self.tags = dev_spec
|
self.tags = dev_spec
|
||||||
self._init_dev_details()
|
self._init_dev_details()
|
||||||
|
|
||||||
def _init_dev_details(self):
|
def _init_dev_details(self) -> None:
|
||||||
self.vendor_id = self.tags.pop("vendor_id", ANY)
|
self.vendor_id = self.tags.pop("vendor_id", ANY)
|
||||||
self.product_id = self.tags.pop("product_id", ANY)
|
self.product_id = self.tags.pop("product_id", ANY)
|
||||||
|
self.dev_name = self.tags.pop("devname", None)
|
||||||
|
self.address: ty.Optional[WhitelistPciAddress] = None
|
||||||
# Note(moshele): The address attribute can be a string or a dict.
|
# 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
|
# For glob syntax or specific pci it is a string and for regex syntax
|
||||||
# it is a dict. The WhitelistPciAddress class handles both types.
|
# it is a dict. The WhitelistPciAddress class handles both types.
|
||||||
self.address = self.tags.pop("address", None)
|
address = self.tags.pop("address", None)
|
||||||
self.dev_name = self.tags.pop("devname", None)
|
|
||||||
|
|
||||||
self.vendor_id = self.vendor_id.strip()
|
self.vendor_id = self.vendor_id.strip()
|
||||||
self._set_pci_dev_info('vendor_id', MAX_VENDOR_ID, '%04x')
|
self._set_pci_dev_info('vendor_id', MAX_VENDOR_ID, '%04x')
|
||||||
self._set_pci_dev_info('product_id', MAX_PRODUCT_ID, '%04x')
|
self._set_pci_dev_info('product_id', MAX_PRODUCT_ID, '%04x')
|
||||||
|
|
||||||
if self.address and self.dev_name:
|
if address and self.dev_name:
|
||||||
raise exception.PciDeviceInvalidDeviceName()
|
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 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:
|
if self.dev_name:
|
||||||
address_str, pf = utils.get_function_by_ifname(
|
address_str, pf = utils.get_function_by_ifname(self.dev_name)
|
||||||
self.dev_name)
|
|
||||||
if not address_str:
|
if not address_str:
|
||||||
return False
|
return False
|
||||||
# Note(moshele): In this case we always passing a string
|
# Note(moshele): In this case we always passing a string
|
||||||
# of the PF pci address
|
# of the PF pci address
|
||||||
address_obj = WhitelistPciAddress(address_str, pf)
|
address_obj = WhitelistPciAddress(address_str, pf)
|
||||||
elif self.address:
|
else: # use self.address
|
||||||
address_obj = self.address
|
address_obj = self.address
|
||||||
|
|
||||||
|
if not address_obj:
|
||||||
|
return False
|
||||||
|
|
||||||
return all([
|
return all([
|
||||||
self.vendor_id in (ANY, dev_dict['vendor_id']),
|
self.vendor_id in (ANY, dev_dict['vendor_id']),
|
||||||
self.product_id in (ANY, dev_dict['product_id']),
|
self.product_id in (ANY, dev_dict['product_id']),
|
||||||
address_obj.match(dev_dict['address'],
|
address_obj.match(dev_dict['address'],
|
||||||
dev_dict.get('parent_addr'))])
|
dev_dict.get('parent_addr'))])
|
||||||
|
|
||||||
def match_pci_obj(self, pci_obj):
|
def match_pci_obj(self, pci_obj: 'objects.PciDevice') -> bool:
|
||||||
return self.match({'vendor_id': pci_obj.vendor_id,
|
return self.match({'vendor_id': pci_obj.vendor_id,
|
||||||
'product_id': pci_obj.product_id,
|
'product_id': pci_obj.product_id,
|
||||||
'address': pci_obj.address,
|
'address': pci_obj.address,
|
||||||
'parent_addr': pci_obj.parent_addr})
|
'parent_addr': pci_obj.parent_addr})
|
||||||
|
|
||||||
def get_tags(self):
|
def get_tags(self) -> ty.Dict[str, str]:
|
||||||
return self.tags
|
return self.tags
|
||||||
|
|
|
@ -15,11 +15,13 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from nova import context as ctx
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import objects
|
from nova import objects
|
||||||
from nova.objects import fields
|
from nova.objects import fields
|
||||||
|
@ -29,6 +31,9 @@ from nova.pci import whitelist
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MappingType = ty.Dict[str, ty.List['objects.PciDevice']]
|
||||||
|
PCIInvType = ty.DefaultDict[str, ty.List['objects.PciDevice']]
|
||||||
|
|
||||||
|
|
||||||
class PciDevTracker(object):
|
class PciDevTracker(object):
|
||||||
"""Manage pci devices in a compute node.
|
"""Manage pci devices in a compute node.
|
||||||
|
@ -51,17 +56,19 @@ class PciDevTracker(object):
|
||||||
are saved.
|
are saved.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, context, compute_node):
|
def __init__(
|
||||||
|
self,
|
||||||
|
context: ctx.RequestContext,
|
||||||
|
compute_node: 'objects.ComputeNode',
|
||||||
|
):
|
||||||
"""Create a pci device tracker.
|
"""Create a pci device tracker.
|
||||||
|
|
||||||
:param context: The request context.
|
:param context: The request context.
|
||||||
:param compute_node: The object.ComputeNode whose PCI devices we're
|
:param compute_node: The object.ComputeNode whose PCI devices we're
|
||||||
tracking.
|
tracking.
|
||||||
"""
|
"""
|
||||||
|
self.stale: ty.Dict[str, objects.PciDevice] = {}
|
||||||
super(PciDevTracker, self).__init__()
|
self.node_id: str = compute_node.id
|
||||||
self.stale = {}
|
|
||||||
self.node_id = compute_node.id
|
|
||||||
self.dev_filter = whitelist.Whitelist(CONF.pci.passthrough_whitelist)
|
self.dev_filter = whitelist.Whitelist(CONF.pci.passthrough_whitelist)
|
||||||
numa_topology = compute_node.numa_topology
|
numa_topology = compute_node.numa_topology
|
||||||
if numa_topology:
|
if numa_topology:
|
||||||
|
@ -76,9 +83,10 @@ class PciDevTracker(object):
|
||||||
self._build_device_tree(self.pci_devs)
|
self._build_device_tree(self.pci_devs)
|
||||||
self._initial_instance_usage()
|
self._initial_instance_usage()
|
||||||
|
|
||||||
def _initial_instance_usage(self):
|
def _initial_instance_usage(self) -> None:
|
||||||
self.allocations = collections.defaultdict(list)
|
self.allocations: PCIInvType = collections.defaultdict(list)
|
||||||
self.claims = collections.defaultdict(list)
|
self.claims: PCIInvType = collections.defaultdict(list)
|
||||||
|
|
||||||
for dev in self.pci_devs:
|
for dev in self.pci_devs:
|
||||||
uuid = dev.instance_uuid
|
uuid = dev.instance_uuid
|
||||||
if dev.status == fields.PciDeviceStatus.CLAIMED:
|
if dev.status == fields.PciDeviceStatus.CLAIMED:
|
||||||
|
@ -88,7 +96,7 @@ class PciDevTracker(object):
|
||||||
elif dev.status == fields.PciDeviceStatus.AVAILABLE:
|
elif dev.status == fields.PciDeviceStatus.AVAILABLE:
|
||||||
self.stats.add_device(dev)
|
self.stats.add_device(dev)
|
||||||
|
|
||||||
def save(self, context):
|
def save(self, context: ctx.RequestContext) -> None:
|
||||||
for dev in self.pci_devs:
|
for dev in self.pci_devs:
|
||||||
if dev.obj_what_changed():
|
if dev.obj_what_changed():
|
||||||
with dev.obj_alternate_context(context):
|
with dev.obj_alternate_context(context):
|
||||||
|
@ -97,10 +105,12 @@ class PciDevTracker(object):
|
||||||
self.pci_devs.objects.remove(dev)
|
self.pci_devs.objects.remove(dev)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pci_stats(self):
|
def pci_stats(self) -> stats.PciDeviceStats:
|
||||||
return self.stats
|
return self.stats
|
||||||
|
|
||||||
def update_devices_from_hypervisor_resources(self, devices_json):
|
def update_devices_from_hypervisor_resources(
|
||||||
|
self, devices_json: str,
|
||||||
|
) -> None:
|
||||||
"""Sync the pci device tracker with hypervisor information.
|
"""Sync the pci device tracker with hypervisor information.
|
||||||
|
|
||||||
To support pci device hot plug, we sync with the hypervisor
|
To support pci device hot plug, we sync with the hypervisor
|
||||||
|
@ -159,7 +169,7 @@ class PciDevTracker(object):
|
||||||
self._set_hvdevs(devices)
|
self._set_hvdevs(devices)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_device_tree(all_devs):
|
def _build_device_tree(all_devs: ty.List['objects.PciDevice']) -> None:
|
||||||
"""Build a tree of devices that represents parent-child relationships.
|
"""Build a tree of devices that represents parent-child relationships.
|
||||||
|
|
||||||
We need to have the relationships set up so that we can easily make
|
We need to have the relationships set up so that we can easily make
|
||||||
|
@ -196,7 +206,7 @@ class PciDevTracker(object):
|
||||||
if dev.parent_device:
|
if dev.parent_device:
|
||||||
parents[dev.parent_addr].child_devices.append(dev)
|
parents[dev.parent_addr].child_devices.append(dev)
|
||||||
|
|
||||||
def _set_hvdevs(self, devices):
|
def _set_hvdevs(self, devices: ty.List[ty.Dict[str, ty.Any]]) -> None:
|
||||||
exist_addrs = set([dev.address for dev in self.pci_devs])
|
exist_addrs = set([dev.address for dev in self.pci_devs])
|
||||||
new_addrs = set([dev['address'] for dev in devices])
|
new_addrs = set([dev['address'] for dev in devices])
|
||||||
|
|
||||||
|
@ -243,6 +253,7 @@ class PciDevTracker(object):
|
||||||
self.stats.remove_device(existed)
|
self.stats.remove_device(existed)
|
||||||
else:
|
else:
|
||||||
# Update tracked devices.
|
# Update tracked devices.
|
||||||
|
new_value: ty.Dict[str, ty.Any]
|
||||||
new_value = next((dev for dev in devices if
|
new_value = next((dev for dev in devices if
|
||||||
dev['address'] == existed.address))
|
dev['address'] == existed.address))
|
||||||
new_value['compute_node_id'] = self.node_id
|
new_value['compute_node_id'] = self.node_id
|
||||||
|
@ -276,7 +287,12 @@ class PciDevTracker(object):
|
||||||
|
|
||||||
self._build_device_tree(self.pci_devs)
|
self._build_device_tree(self.pci_devs)
|
||||||
|
|
||||||
def _claim_instance(self, context, pci_requests, instance_numa_topology):
|
def _claim_instance(
|
||||||
|
self,
|
||||||
|
context: ctx.RequestContext,
|
||||||
|
pci_requests: 'objects.InstancePCIRequests',
|
||||||
|
instance_numa_topology: 'objects.InstanceNUMATopology',
|
||||||
|
) -> ty.List['objects.PciDevice']:
|
||||||
instance_cells = None
|
instance_cells = None
|
||||||
if instance_numa_topology:
|
if instance_numa_topology:
|
||||||
instance_cells = instance_numa_topology.cells
|
instance_cells = instance_numa_topology.cells
|
||||||
|
@ -284,7 +300,7 @@ class PciDevTracker(object):
|
||||||
devs = self.stats.consume_requests(pci_requests.requests,
|
devs = self.stats.consume_requests(pci_requests.requests,
|
||||||
instance_cells)
|
instance_cells)
|
||||||
if not devs:
|
if not devs:
|
||||||
return None
|
return []
|
||||||
|
|
||||||
instance_uuid = pci_requests.instance_uuid
|
instance_uuid = pci_requests.instance_uuid
|
||||||
for dev in devs:
|
for dev in devs:
|
||||||
|
@ -296,18 +312,15 @@ class PciDevTracker(object):
|
||||||
{'instance': instance_uuid})
|
{'instance': instance_uuid})
|
||||||
return devs
|
return devs
|
||||||
|
|
||||||
def _allocate_instance(self, instance, devs):
|
def claim_instance(
|
||||||
for dev in devs:
|
self,
|
||||||
dev.allocate(instance)
|
context: ctx.RequestContext,
|
||||||
|
pci_requests: 'objects.InstancePCIRequests',
|
||||||
|
instance_numa_topology: 'objects.InstanceNUMATopology',
|
||||||
|
) -> ty.List['objects.PciDevice']:
|
||||||
|
|
||||||
def allocate_instance(self, instance):
|
|
||||||
devs = self.claims.pop(instance['uuid'], [])
|
|
||||||
self._allocate_instance(instance, devs)
|
|
||||||
if devs:
|
|
||||||
self.allocations[instance['uuid']] += devs
|
|
||||||
|
|
||||||
def claim_instance(self, context, pci_requests, instance_numa_topology):
|
|
||||||
devs = []
|
devs = []
|
||||||
|
|
||||||
if self.pci_devs and pci_requests.requests:
|
if self.pci_devs and pci_requests.requests:
|
||||||
instance_uuid = pci_requests.instance_uuid
|
instance_uuid = pci_requests.instance_uuid
|
||||||
devs = self._claim_instance(context, pci_requests,
|
devs = self._claim_instance(context, pci_requests,
|
||||||
|
@ -316,7 +329,21 @@ class PciDevTracker(object):
|
||||||
self.claims[instance_uuid] = devs
|
self.claims[instance_uuid] = devs
|
||||||
return devs
|
return devs
|
||||||
|
|
||||||
def free_device(self, dev, instance):
|
def _allocate_instance(
|
||||||
|
self, instance: 'objects.Instance', devs: ty.List['objects.PciDevice'],
|
||||||
|
) -> None:
|
||||||
|
for dev in devs:
|
||||||
|
dev.allocate(instance)
|
||||||
|
|
||||||
|
def allocate_instance(self, instance: 'objects.Instance') -> None:
|
||||||
|
devs = self.claims.pop(instance['uuid'], [])
|
||||||
|
self._allocate_instance(instance, devs)
|
||||||
|
if devs:
|
||||||
|
self.allocations[instance['uuid']] += devs
|
||||||
|
|
||||||
|
def free_device(
|
||||||
|
self, dev: 'objects.PciDevice', instance: 'objects.Instance'
|
||||||
|
) -> None:
|
||||||
"""Free device from pci resource tracker
|
"""Free device from pci resource tracker
|
||||||
|
|
||||||
:param dev: cloned pci device object that needs to be free
|
:param dev: cloned pci device object that needs to be free
|
||||||
|
@ -335,7 +362,11 @@ class PciDevTracker(object):
|
||||||
break
|
break
|
||||||
|
|
||||||
def _remove_device_from_pci_mapping(
|
def _remove_device_from_pci_mapping(
|
||||||
self, instance_uuid, pci_device, pci_mapping):
|
self,
|
||||||
|
instance_uuid: str,
|
||||||
|
pci_device: 'objects.PciDevice',
|
||||||
|
pci_mapping: MappingType,
|
||||||
|
) -> None:
|
||||||
"""Remove a PCI device from allocations or claims.
|
"""Remove a PCI device from allocations or claims.
|
||||||
|
|
||||||
If there are no more PCI devices, pop the uuid.
|
If there are no more PCI devices, pop the uuid.
|
||||||
|
@ -346,7 +377,9 @@ class PciDevTracker(object):
|
||||||
if len(pci_devices) == 0:
|
if len(pci_devices) == 0:
|
||||||
pci_mapping.pop(instance_uuid, None)
|
pci_mapping.pop(instance_uuid, None)
|
||||||
|
|
||||||
def _free_device(self, dev, instance=None):
|
def _free_device(
|
||||||
|
self, dev: 'objects.PciDevice', instance: 'objects.Instance' = None,
|
||||||
|
) -> None:
|
||||||
freed_devs = dev.free(instance)
|
freed_devs = dev.free(instance)
|
||||||
stale = self.stale.pop(dev.address, None)
|
stale = self.stale.pop(dev.address, None)
|
||||||
if stale:
|
if stale:
|
||||||
|
@ -354,31 +387,41 @@ class PciDevTracker(object):
|
||||||
for dev in freed_devs:
|
for dev in freed_devs:
|
||||||
self.stats.add_device(dev)
|
self.stats.add_device(dev)
|
||||||
|
|
||||||
def free_instance_allocations(self, context, instance):
|
def free_instance_allocations(
|
||||||
|
self, context: ctx.RequestContext, instance: 'objects.Instance',
|
||||||
|
) -> None:
|
||||||
"""Free devices that are in ALLOCATED state for instance.
|
"""Free devices that are in ALLOCATED state for instance.
|
||||||
|
|
||||||
:param context: user request context (nova.context.RequestContext)
|
:param context: user request context
|
||||||
:param instance: instance object
|
:param instance: instance object
|
||||||
"""
|
"""
|
||||||
if self.allocations.pop(instance['uuid'], None):
|
if not self.allocations.pop(instance['uuid'], None):
|
||||||
for dev in self.pci_devs:
|
return
|
||||||
if (dev.status == fields.PciDeviceStatus.ALLOCATED and
|
|
||||||
dev.instance_uuid == instance['uuid']):
|
|
||||||
self._free_device(dev)
|
|
||||||
|
|
||||||
def free_instance_claims(self, context, instance):
|
for dev in self.pci_devs:
|
||||||
|
if (dev.status == fields.PciDeviceStatus.ALLOCATED and
|
||||||
|
dev.instance_uuid == instance['uuid']):
|
||||||
|
self._free_device(dev)
|
||||||
|
|
||||||
|
def free_instance_claims(
|
||||||
|
self, context: ctx.RequestContext, instance: 'objects.Instance',
|
||||||
|
) -> None:
|
||||||
"""Free devices that are in CLAIMED state for instance.
|
"""Free devices that are in CLAIMED state for instance.
|
||||||
|
|
||||||
:param context: user request context (nova.context.RequestContext)
|
:param context: user request context (nova.context.RequestContext)
|
||||||
:param instance: instance object
|
:param instance: instance object
|
||||||
"""
|
"""
|
||||||
if self.claims.pop(instance['uuid'], None):
|
if not self.claims.pop(instance['uuid'], None):
|
||||||
for dev in self.pci_devs:
|
return
|
||||||
if (dev.status == fields.PciDeviceStatus.CLAIMED and
|
|
||||||
dev.instance_uuid == instance['uuid']):
|
|
||||||
self._free_device(dev)
|
|
||||||
|
|
||||||
def free_instance(self, context, instance):
|
for dev in self.pci_devs:
|
||||||
|
if (dev.status == fields.PciDeviceStatus.CLAIMED and
|
||||||
|
dev.instance_uuid == instance['uuid']):
|
||||||
|
self._free_device(dev)
|
||||||
|
|
||||||
|
def free_instance(
|
||||||
|
self, context: ctx.RequestContext, instance: 'objects.Instance',
|
||||||
|
) -> None:
|
||||||
"""Free devices that are in CLAIMED or ALLOCATED state for instance.
|
"""Free devices that are in CLAIMED or ALLOCATED state for instance.
|
||||||
|
|
||||||
:param context: user request context (nova.context.RequestContext)
|
:param context: user request context (nova.context.RequestContext)
|
||||||
|
@ -392,9 +435,13 @@ class PciDevTracker(object):
|
||||||
self.free_instance_allocations(context, instance)
|
self.free_instance_allocations(context, instance)
|
||||||
self.free_instance_claims(context, instance)
|
self.free_instance_claims(context, instance)
|
||||||
|
|
||||||
def update_pci_for_instance(self, context, instance, sign):
|
def update_pci_for_instance(
|
||||||
"""Update PCI usage information if devices are de/allocated.
|
self,
|
||||||
"""
|
context: ctx.RequestContext,
|
||||||
|
instance: 'objects.Instance',
|
||||||
|
sign: int,
|
||||||
|
) -> None:
|
||||||
|
"""Update PCI usage information if devices are de/allocated."""
|
||||||
if not self.pci_devs:
|
if not self.pci_devs:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -403,7 +450,11 @@ class PciDevTracker(object):
|
||||||
if sign == 1:
|
if sign == 1:
|
||||||
self.allocate_instance(instance)
|
self.allocate_instance(instance)
|
||||||
|
|
||||||
def clean_usage(self, instances, migrations):
|
def clean_usage(
|
||||||
|
self,
|
||||||
|
instances: 'objects.InstanceList',
|
||||||
|
migrations: 'objects.MigrationList',
|
||||||
|
) -> None:
|
||||||
"""Remove all usages for instances not passed in the parameter.
|
"""Remove all usages for instances not passed in the parameter.
|
||||||
|
|
||||||
The caller should hold the COMPUTE_RESOURCE_SEMAPHORE lock
|
The caller should hold the COMPUTE_RESOURCE_SEMAPHORE lock
|
||||||
|
@ -425,7 +476,9 @@ class PciDevTracker(object):
|
||||||
self._free_device(dev)
|
self._free_device(dev)
|
||||||
|
|
||||||
|
|
||||||
def get_instance_pci_devs(inst, request_id=None):
|
def get_instance_pci_devs(
|
||||||
|
inst: 'objects.Instance', request_id: str = None,
|
||||||
|
) -> ty.List['objects.PciDevice']:
|
||||||
"""Get the devices allocated to one or all requests for an instance.
|
"""Get the devices allocated to one or all requests for an instance.
|
||||||
|
|
||||||
- For generic PCI request, the request id is None.
|
- For generic PCI request, the request id is None.
|
||||||
|
@ -437,5 +490,8 @@ def get_instance_pci_devs(inst, request_id=None):
|
||||||
pci_devices = inst.pci_devices
|
pci_devices = inst.pci_devices
|
||||||
if pci_devices is None:
|
if pci_devices is None:
|
||||||
return []
|
return []
|
||||||
return [device for device in pci_devices if
|
|
||||||
device.request_id == request_id or request_id == 'all']
|
return [
|
||||||
|
device for device in pci_devices if
|
||||||
|
device.request_id == request_id or request_id == 'all'
|
||||||
|
]
|
||||||
|
|
|
@ -38,11 +38,14 @@
|
||||||
product_id is "0442" or "0443".
|
product_id is "0442" or "0443".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
import nova.conf
|
import nova.conf
|
||||||
|
from nova import context as ctx
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova.network import model as network_model
|
from nova.network import model as network_model
|
||||||
|
@ -50,7 +53,8 @@ from nova import objects
|
||||||
from nova.objects import fields as obj_fields
|
from nova.objects import fields as obj_fields
|
||||||
from nova.pci import utils
|
from nova.pci import utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
Alias = ty.Dict[str, ty.Tuple[str, ty.List[ty.Dict[str, str]]]]
|
||||||
|
|
||||||
PCI_NET_TAG = 'physical_network'
|
PCI_NET_TAG = 'physical_network'
|
||||||
PCI_TRUSTED_TAG = 'trusted'
|
PCI_TRUSTED_TAG = 'trusted'
|
||||||
PCI_DEVICE_TYPE_TAG = 'dev_type'
|
PCI_DEVICE_TYPE_TAG = 'dev_type'
|
||||||
|
@ -61,6 +65,7 @@ DEVICE_TYPE_FOR_VNIC_TYPE = {
|
||||||
}
|
}
|
||||||
|
|
||||||
CONF = nova.conf.CONF
|
CONF = nova.conf.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
_ALIAS_SCHEMA = {
|
_ALIAS_SCHEMA = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -104,18 +109,19 @@ _ALIAS_SCHEMA = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _get_alias_from_config():
|
def _get_alias_from_config() -> Alias:
|
||||||
"""Parse and validate PCI aliases from the nova config.
|
"""Parse and validate PCI aliases from the nova config.
|
||||||
|
|
||||||
:returns: A dictionary where the keys are device names and the values are
|
:returns: A dictionary where the keys are device names and the values are
|
||||||
tuples of form ``(specs, numa_policy)``. ``specs`` is a list of PCI
|
tuples of form ``(numa_policy, specs)``. ``numa_policy`` describes the
|
||||||
device specs, while ``numa_policy`` describes the required NUMA
|
required NUMA affinity of the device(s), while ``specs`` is a list of
|
||||||
affinity of the device(s).
|
PCI device specs.
|
||||||
:raises: exception.PciInvalidAlias if two aliases with the same name have
|
:raises: exception.PciInvalidAlias if two aliases with the same name have
|
||||||
different device types or different NUMA policies.
|
different device types or different NUMA policies.
|
||||||
"""
|
"""
|
||||||
jaliases = CONF.pci.alias
|
jaliases = CONF.pci.alias
|
||||||
aliases = {} # map alias name to alias spec list
|
# map alias name to alias spec list
|
||||||
|
aliases: Alias = {}
|
||||||
try:
|
try:
|
||||||
for jsonspecs in jaliases:
|
for jsonspecs in jaliases:
|
||||||
spec = jsonutils.loads(jsonspecs)
|
spec = jsonutils.loads(jsonspecs)
|
||||||
|
@ -153,17 +159,18 @@ def _get_alias_from_config():
|
||||||
return aliases
|
return aliases
|
||||||
|
|
||||||
|
|
||||||
def _translate_alias_to_requests(alias_spec, affinity_policy=None):
|
def _translate_alias_to_requests(
|
||||||
|
alias_spec: str, affinity_policy: str = None,
|
||||||
|
) -> ty.List['objects.InstancePCIRequest']:
|
||||||
"""Generate complete pci requests from pci aliases in extra_spec."""
|
"""Generate complete pci requests from pci aliases in extra_spec."""
|
||||||
pci_aliases = _get_alias_from_config()
|
pci_aliases = _get_alias_from_config()
|
||||||
|
|
||||||
pci_requests = []
|
pci_requests: ty.List[objects.InstancePCIRequest] = []
|
||||||
for name, count in [spec.split(':') for spec in alias_spec.split(',')]:
|
for name, count in [spec.split(':') for spec in alias_spec.split(',')]:
|
||||||
name = name.strip()
|
name = name.strip()
|
||||||
if name not in pci_aliases:
|
if name not in pci_aliases:
|
||||||
raise exception.PciRequestAliasNotDefined(alias=name)
|
raise exception.PciRequestAliasNotDefined(alias=name)
|
||||||
|
|
||||||
count = int(count)
|
|
||||||
numa_policy, spec = pci_aliases[name]
|
numa_policy, spec = pci_aliases[name]
|
||||||
policy = affinity_policy or numa_policy
|
policy = affinity_policy or numa_policy
|
||||||
|
|
||||||
|
@ -172,14 +179,18 @@ def _translate_alias_to_requests(alias_spec, affinity_policy=None):
|
||||||
# handling for InstancePCIRequests created from the flavor. So it is
|
# handling for InstancePCIRequests created from the flavor. So it is
|
||||||
# left empty.
|
# left empty.
|
||||||
pci_requests.append(objects.InstancePCIRequest(
|
pci_requests.append(objects.InstancePCIRequest(
|
||||||
count=count,
|
count=int(count),
|
||||||
spec=spec,
|
spec=spec,
|
||||||
alias_name=name,
|
alias_name=name,
|
||||||
numa_policy=policy))
|
numa_policy=policy))
|
||||||
return pci_requests
|
return pci_requests
|
||||||
|
|
||||||
|
|
||||||
def get_instance_pci_request_from_vif(context, instance, vif):
|
def get_instance_pci_request_from_vif(
|
||||||
|
context: ctx.RequestContext,
|
||||||
|
instance: 'objects.Instance',
|
||||||
|
vif: network_model.VIF,
|
||||||
|
) -> ty.Optional['objects.InstancePCIRequest']:
|
||||||
"""Given an Instance, return the PCI request associated
|
"""Given an Instance, return the PCI request associated
|
||||||
to the PCI device related to the given VIF (if any) on the
|
to the PCI device related to the given VIF (if any) on the
|
||||||
compute node the instance is currently running.
|
compute node the instance is currently running.
|
||||||
|
@ -233,7 +244,9 @@ def get_instance_pci_request_from_vif(context, instance, vif):
|
||||||
node_id=cn_id)
|
node_id=cn_id)
|
||||||
|
|
||||||
|
|
||||||
def get_pci_requests_from_flavor(flavor, affinity_policy=None):
|
def get_pci_requests_from_flavor(
|
||||||
|
flavor: 'objects.Flavor', affinity_policy: str = None,
|
||||||
|
) -> 'objects.InstancePCIRequests':
|
||||||
"""Validate and return PCI requests.
|
"""Validate and return PCI requests.
|
||||||
|
|
||||||
The ``pci_passthrough:alias`` extra spec describes the flavor's PCI
|
The ``pci_passthrough:alias`` extra spec describes the flavor's PCI
|
||||||
|
@ -279,7 +292,7 @@ def get_pci_requests_from_flavor(flavor, affinity_policy=None):
|
||||||
:raises: exception.PciInvalidAlias if the configuration contains invalid
|
:raises: exception.PciInvalidAlias if the configuration contains invalid
|
||||||
aliases.
|
aliases.
|
||||||
"""
|
"""
|
||||||
pci_requests = []
|
pci_requests: ty.List[objects.InstancePCIRequest] = []
|
||||||
if ('extra_specs' in flavor and
|
if ('extra_specs' in flavor and
|
||||||
'pci_passthrough:alias' in flavor['extra_specs']):
|
'pci_passthrough:alias' in flavor['extra_specs']):
|
||||||
pci_requests = _translate_alias_to_requests(
|
pci_requests = _translate_alias_to_requests(
|
||||||
|
|
|
@ -14,20 +14,29 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
from nova import objects
|
||||||
from nova.objects import fields
|
from nova.objects import fields
|
||||||
from nova.objects import pci_device_pool
|
from nova.objects import pci_device_pool
|
||||||
from nova.pci import utils
|
from nova.pci import utils
|
||||||
from nova.pci import whitelist
|
from nova.pci import whitelist
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(stephenfin): We might want to use TypedDict here. Refer to
|
||||||
|
# https://mypy.readthedocs.io/en/latest/kinds_of_types.html#typeddict for
|
||||||
|
# more information.
|
||||||
|
Pool = ty.Dict[str, ty.Any]
|
||||||
|
|
||||||
|
|
||||||
class PciDeviceStats(object):
|
class PciDeviceStats(object):
|
||||||
|
|
||||||
"""PCI devices summary information.
|
"""PCI devices summary information.
|
||||||
|
@ -54,32 +63,42 @@ class PciDeviceStats(object):
|
||||||
|
|
||||||
pool_keys = ['product_id', 'vendor_id', 'numa_node', 'dev_type']
|
pool_keys = ['product_id', 'vendor_id', 'numa_node', 'dev_type']
|
||||||
|
|
||||||
def __init__(self, numa_topology, stats=None, dev_filter=None):
|
def __init__(
|
||||||
super(PciDeviceStats, self).__init__()
|
self,
|
||||||
|
numa_topology: 'objects.NUMATopology',
|
||||||
|
stats: 'objects.PCIDevicePoolList' = None,
|
||||||
|
dev_filter: whitelist.Whitelist = None,
|
||||||
|
) -> None:
|
||||||
self.numa_topology = numa_topology
|
self.numa_topology = numa_topology
|
||||||
# NOTE(sbauza): Stats are a PCIDevicePoolList object
|
self.pools = (
|
||||||
self.pools = [pci_pool.to_dict()
|
[pci_pool.to_dict() for pci_pool in stats] if stats else []
|
||||||
for pci_pool in stats] if stats else []
|
)
|
||||||
self.pools.sort(key=lambda item: len(item))
|
self.pools.sort(key=lambda item: len(item))
|
||||||
self.dev_filter = dev_filter or whitelist.Whitelist(
|
self.dev_filter = dev_filter or whitelist.Whitelist(
|
||||||
CONF.pci.passthrough_whitelist)
|
CONF.pci.passthrough_whitelist)
|
||||||
|
|
||||||
def _equal_properties(self, dev, entry, matching_keys):
|
def _equal_properties(
|
||||||
|
self, dev: Pool, entry: Pool, matching_keys: ty.List[str],
|
||||||
|
) -> bool:
|
||||||
return all(dev.get(prop) == entry.get(prop)
|
return all(dev.get(prop) == entry.get(prop)
|
||||||
for prop in matching_keys)
|
for prop in matching_keys)
|
||||||
|
|
||||||
def _find_pool(self, dev_pool):
|
def _find_pool(self, dev_pool: Pool) -> ty.Optional[Pool]:
|
||||||
"""Return the first pool that matches dev."""
|
"""Return the first pool that matches dev."""
|
||||||
for pool in self.pools:
|
for pool in self.pools:
|
||||||
pool_keys = pool.copy()
|
pool_keys = pool.copy()
|
||||||
del pool_keys['count']
|
del pool_keys['count']
|
||||||
del pool_keys['devices']
|
del pool_keys['devices']
|
||||||
if (len(pool_keys.keys()) == len(dev_pool.keys()) and
|
if (len(pool_keys.keys()) == len(dev_pool.keys()) and
|
||||||
self._equal_properties(dev_pool, pool_keys, dev_pool.keys())):
|
self._equal_properties(dev_pool, pool_keys, list(dev_pool))):
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def _create_pool_keys_from_dev(self, dev):
|
return None
|
||||||
"""create a stats pool dict that this dev is supposed to be part of
|
|
||||||
|
def _create_pool_keys_from_dev(
|
||||||
|
self, dev: 'objects.PciDevice',
|
||||||
|
) -> ty.Optional[Pool]:
|
||||||
|
"""Create a stats pool dict that this dev is supposed to be part of
|
||||||
|
|
||||||
Note that this pool dict contains the stats pool's keys and their
|
Note that this pool dict contains the stats pool's keys and their
|
||||||
values. 'count' and 'devices' are not included.
|
values. 'count' and 'devices' are not included.
|
||||||
|
@ -88,7 +107,7 @@ class PciDeviceStats(object):
|
||||||
# This can happen during initial sync up with the controller
|
# This can happen during initial sync up with the controller
|
||||||
devspec = self.dev_filter.get_devspec(dev)
|
devspec = self.dev_filter.get_devspec(dev)
|
||||||
if not devspec:
|
if not devspec:
|
||||||
return
|
return None
|
||||||
tags = devspec.get_tags()
|
tags = devspec.get_tags()
|
||||||
pool = {k: getattr(dev, k) for k in self.pool_keys}
|
pool = {k: getattr(dev, k) for k in self.pool_keys}
|
||||||
if tags:
|
if tags:
|
||||||
|
@ -103,7 +122,9 @@ class PciDeviceStats(object):
|
||||||
pool['parent_ifname'] = dev.extra_info['parent_ifname']
|
pool['parent_ifname'] = dev.extra_info['parent_ifname']
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def _get_pool_with_device_type_mismatch(self, dev):
|
def _get_pool_with_device_type_mismatch(
|
||||||
|
self, dev: 'objects.PciDevice',
|
||||||
|
) -> ty.Optional[ty.Tuple[Pool, 'objects.PciDevice']]:
|
||||||
"""Check for device type mismatch in the pools for a given device.
|
"""Check for device type mismatch in the pools for a given device.
|
||||||
|
|
||||||
Return (pool, device) if device type does not match or a single None
|
Return (pool, device) if device type does not match or a single None
|
||||||
|
@ -118,18 +139,18 @@ class PciDeviceStats(object):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def update_device(self, dev):
|
def update_device(self, dev: 'objects.PciDevice') -> None:
|
||||||
"""Update a device to its matching pool."""
|
"""Update a device to its matching pool."""
|
||||||
pool_device_info = self._get_pool_with_device_type_mismatch(dev)
|
pool_device_info = self._get_pool_with_device_type_mismatch(dev)
|
||||||
if pool_device_info is None:
|
if pool_device_info is None:
|
||||||
return
|
return None
|
||||||
|
|
||||||
pool, device = pool_device_info
|
pool, device = pool_device_info
|
||||||
pool['devices'].remove(device)
|
pool['devices'].remove(device)
|
||||||
self._decrease_pool_count(self.pools, pool)
|
self._decrease_pool_count(self.pools, pool)
|
||||||
self.add_device(dev)
|
self.add_device(dev)
|
||||||
|
|
||||||
def add_device(self, dev):
|
def add_device(self, dev: 'objects.PciDevice') -> None:
|
||||||
"""Add a device to its matching pool."""
|
"""Add a device to its matching pool."""
|
||||||
dev_pool = self._create_pool_keys_from_dev(dev)
|
dev_pool = self._create_pool_keys_from_dev(dev)
|
||||||
if dev_pool:
|
if dev_pool:
|
||||||
|
@ -144,7 +165,9 @@ class PciDeviceStats(object):
|
||||||
pool['devices'].append(dev)
|
pool['devices'].append(dev)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _decrease_pool_count(pool_list, pool, count=1):
|
def _decrease_pool_count(
|
||||||
|
pool_list: ty.List[Pool], pool: Pool, count: int = 1,
|
||||||
|
) -> int:
|
||||||
"""Decrement pool's size by count.
|
"""Decrement pool's size by count.
|
||||||
|
|
||||||
If pool becomes empty, remove pool from pool_list.
|
If pool becomes empty, remove pool from pool_list.
|
||||||
|
@ -157,7 +180,7 @@ class PciDeviceStats(object):
|
||||||
pool_list.remove(pool)
|
pool_list.remove(pool)
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def remove_device(self, dev):
|
def remove_device(self, dev: 'objects.PciDevice') -> None:
|
||||||
"""Remove one device from the first pool that it matches."""
|
"""Remove one device from the first pool that it matches."""
|
||||||
dev_pool = self._create_pool_keys_from_dev(dev)
|
dev_pool = self._create_pool_keys_from_dev(dev)
|
||||||
if dev_pool:
|
if dev_pool:
|
||||||
|
@ -168,14 +191,20 @@ class PciDeviceStats(object):
|
||||||
pool['devices'].remove(dev)
|
pool['devices'].remove(dev)
|
||||||
self._decrease_pool_count(self.pools, pool)
|
self._decrease_pool_count(self.pools, pool)
|
||||||
|
|
||||||
def get_free_devs(self):
|
def get_free_devs(self) -> ty.List['objects.PciDevice']:
|
||||||
free_devs = []
|
free_devs: ty.List[objects.PciDevice] = []
|
||||||
for pool in self.pools:
|
for pool in self.pools:
|
||||||
free_devs.extend(pool['devices'])
|
free_devs.extend(pool['devices'])
|
||||||
return free_devs
|
return free_devs
|
||||||
|
|
||||||
def consume_requests(self, pci_requests, numa_cells=None):
|
def consume_requests(
|
||||||
alloc_devices = []
|
self,
|
||||||
|
pci_requests: 'objects.InstancePCIRequests',
|
||||||
|
numa_cells: ty.Optional[ty.List['objects.InstanceNUMACell']] = None,
|
||||||
|
) -> ty.Optional[ty.List['objects.PciDevice']]:
|
||||||
|
|
||||||
|
alloc_devices: ty.List[objects.PciDevice] = []
|
||||||
|
|
||||||
for request in pci_requests:
|
for request in pci_requests:
|
||||||
count = request.count
|
count = request.count
|
||||||
|
|
||||||
|
@ -212,7 +241,7 @@ class PciDeviceStats(object):
|
||||||
|
|
||||||
return alloc_devices
|
return alloc_devices
|
||||||
|
|
||||||
def _handle_device_dependents(self, pci_dev):
|
def _handle_device_dependents(self, pci_dev: 'objects.PciDevice') -> None:
|
||||||
"""Remove device dependents or a parent from pools.
|
"""Remove device dependents or a parent from pools.
|
||||||
|
|
||||||
In case the device is a PF, all of it's dependent VFs should
|
In case the device is a PF, all of it's dependent VFs should
|
||||||
|
@ -238,7 +267,9 @@ class PciDeviceStats(object):
|
||||||
except exception.PciDeviceNotFound:
|
except exception.PciDeviceNotFound:
|
||||||
return
|
return
|
||||||
|
|
||||||
def _filter_pools_for_spec(self, pools, request):
|
def _filter_pools_for_spec(
|
||||||
|
self, pools: ty.List[Pool], request: 'objects.InstancePCIRequest',
|
||||||
|
) -> ty.List[Pool]:
|
||||||
"""Filter out pools that don't match the request's device spec.
|
"""Filter out pools that don't match the request's device spec.
|
||||||
|
|
||||||
Exclude pools that do not match the specified ``vendor_id``,
|
Exclude pools that do not match the specified ``vendor_id``,
|
||||||
|
@ -257,7 +288,12 @@ class PciDeviceStats(object):
|
||||||
if utils.pci_device_prop_match(pool, request_specs)
|
if utils.pci_device_prop_match(pool, request_specs)
|
||||||
]
|
]
|
||||||
|
|
||||||
def _filter_pools_for_numa_cells(self, pools, request, numa_cells):
|
def _filter_pools_for_numa_cells(
|
||||||
|
self,
|
||||||
|
pools: ty.List[Pool],
|
||||||
|
request: 'objects.InstancePCIRequest',
|
||||||
|
numa_cells: ty.Optional[ty.List['objects.InstanceNUMACell']],
|
||||||
|
) -> ty.List[Pool]:
|
||||||
"""Filter out pools with the wrong NUMA affinity, if required.
|
"""Filter out pools with the wrong NUMA affinity, if required.
|
||||||
|
|
||||||
Exclude pools that do not have *suitable* PCI NUMA affinity.
|
Exclude pools that do not have *suitable* PCI NUMA affinity.
|
||||||
|
@ -335,7 +371,11 @@ class PciDeviceStats(object):
|
||||||
return sorted(
|
return sorted(
|
||||||
pools, key=lambda pool: pool.get('numa_node') not in numa_cell_ids)
|
pools, key=lambda pool: pool.get('numa_node') not in numa_cell_ids)
|
||||||
|
|
||||||
def _filter_pools_for_socket_affinity(self, pools, numa_cells):
|
def _filter_pools_for_socket_affinity(
|
||||||
|
self,
|
||||||
|
pools: ty.List[Pool],
|
||||||
|
numa_cells: ty.List['objects.InstanceNUMACell'],
|
||||||
|
) -> ty.List[Pool]:
|
||||||
host_cells = self.numa_topology.cells
|
host_cells = self.numa_topology.cells
|
||||||
# bail early if we don't have socket information for all host_cells.
|
# bail early if we don't have socket information for all host_cells.
|
||||||
# This could happen if we're running on an weird older system with
|
# This could happen if we're running on an weird older system with
|
||||||
|
@ -368,7 +408,9 @@ class PciDeviceStats(object):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
def _filter_pools_for_unrequested_pfs(self, pools, request):
|
def _filter_pools_for_unrequested_pfs(
|
||||||
|
self, pools: ty.List[Pool], request: 'objects.InstancePCIRequest',
|
||||||
|
) -> ty.List[Pool]:
|
||||||
"""Filter out pools with PFs, unless these are required.
|
"""Filter out pools with PFs, unless these are required.
|
||||||
|
|
||||||
This is necessary in cases where PFs and VFs have the same product_id
|
This is necessary in cases where PFs and VFs have the same product_id
|
||||||
|
@ -390,7 +432,11 @@ class PciDeviceStats(object):
|
||||||
]
|
]
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def _filter_pools_for_unrequested_vdpa_devices(self, pools, request):
|
def _filter_pools_for_unrequested_vdpa_devices(
|
||||||
|
self,
|
||||||
|
pools: ty.List[Pool],
|
||||||
|
request: 'objects.InstancePCIRequest',
|
||||||
|
) -> ty.List[Pool]:
|
||||||
"""Filter out pools with VDPA devices, unless these are required.
|
"""Filter out pools with VDPA devices, unless these are required.
|
||||||
|
|
||||||
This is necessary as vdpa devices require special handling and
|
This is necessary as vdpa devices require special handling and
|
||||||
|
@ -412,7 +458,12 @@ class PciDeviceStats(object):
|
||||||
]
|
]
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def _filter_pools(self, pools, request, numa_cells):
|
def _filter_pools(
|
||||||
|
self,
|
||||||
|
pools: ty.List[Pool],
|
||||||
|
request: 'objects.InstancePCIRequest',
|
||||||
|
numa_cells: ty.Optional[ty.List['objects.InstanceNUMACell']],
|
||||||
|
) -> ty.Optional[ty.List[Pool]]:
|
||||||
"""Determine if an individual PCI request can be met.
|
"""Determine if an individual PCI request can be met.
|
||||||
|
|
||||||
Filter pools, which are collections of devices with similar traits, to
|
Filter pools, which are collections of devices with similar traits, to
|
||||||
|
@ -502,7 +553,11 @@ class PciDeviceStats(object):
|
||||||
|
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def support_requests(self, requests, numa_cells=None):
|
def support_requests(
|
||||||
|
self,
|
||||||
|
requests: ty.List['objects.InstancePCIRequest'],
|
||||||
|
numa_cells: ty.Optional[ty.List['objects.InstanceNUMACell']] = None,
|
||||||
|
) -> bool:
|
||||||
"""Determine if the PCI requests can be met.
|
"""Determine if the PCI requests can be met.
|
||||||
|
|
||||||
Determine, based on a compute node's PCI stats, if an instance can be
|
Determine, based on a compute node's PCI stats, if an instance can be
|
||||||
|
@ -524,7 +579,12 @@ class PciDeviceStats(object):
|
||||||
self._filter_pools(self.pools, r, numa_cells) for r in requests
|
self._filter_pools(self.pools, r, numa_cells) for r in requests
|
||||||
)
|
)
|
||||||
|
|
||||||
def _apply_request(self, pools, request, numa_cells=None):
|
def _apply_request(
|
||||||
|
self,
|
||||||
|
pools: ty.List[Pool],
|
||||||
|
request: 'objects.InstancePCIRequest',
|
||||||
|
numa_cells: ty.Optional[ty.List['objects.InstanceNUMACell']] = None,
|
||||||
|
) -> bool:
|
||||||
"""Apply an individual PCI request.
|
"""Apply an individual PCI request.
|
||||||
|
|
||||||
Apply a PCI request against a given set of PCI device pools, which are
|
Apply a PCI request against a given set of PCI device pools, which are
|
||||||
|
@ -558,7 +618,11 @@ class PciDeviceStats(object):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def apply_requests(self, requests, numa_cells=None):
|
def apply_requests(
|
||||||
|
self,
|
||||||
|
requests: ty.List['objects.InstancePCIRequest'],
|
||||||
|
numa_cells: ty.Optional[ty.List['objects.InstanceNUMACell']] = None,
|
||||||
|
) -> None:
|
||||||
"""Apply PCI requests to the PCI stats.
|
"""Apply PCI requests to the PCI stats.
|
||||||
|
|
||||||
This is used in multiple instance creation, when the scheduler has to
|
This is used in multiple instance creation, when the scheduler has to
|
||||||
|
@ -580,22 +644,26 @@ class PciDeviceStats(object):
|
||||||
):
|
):
|
||||||
raise exception.PciDeviceRequestFailed(requests=requests)
|
raise exception.PciDeviceRequestFailed(requests=requests)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> ty.Iterator[Pool]:
|
||||||
# 'devices' shouldn't be part of stats
|
pools: ty.List[Pool] = []
|
||||||
pools = []
|
|
||||||
for pool in self.pools:
|
for pool in self.pools:
|
||||||
tmp = {k: v for k, v in pool.items() if k != 'devices'}
|
pool = copy.deepcopy(pool)
|
||||||
pools.append(tmp)
|
# 'devices' shouldn't be part of stats
|
||||||
|
if 'devices' in pool:
|
||||||
|
del pool['devices']
|
||||||
|
pools.append(pool)
|
||||||
return iter(pools)
|
return iter(pools)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
"""Clear all the stats maintained."""
|
"""Clear all the stats maintained."""
|
||||||
self.pools = []
|
self.pools = []
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, PciDeviceStats):
|
||||||
|
return NotImplemented
|
||||||
return self.pools == other.pools
|
return self.pools == other.pools
|
||||||
|
|
||||||
def to_device_pools_obj(self):
|
def to_device_pools_obj(self) -> 'objects.PciDevicePoolList':
|
||||||
"""Return the contents of the pools as a PciDevicePoolList object."""
|
"""Return the contents of the pools as a PciDevicePoolList object."""
|
||||||
stats = [x for x in self]
|
stats = [x for x in self]
|
||||||
return pci_device_pool.from_pci_stats(stats)
|
return pci_device_pool.from_pci_stats(stats)
|
||||||
|
|
|
@ -14,15 +14,19 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
|
||||||
|
if ty.TYPE_CHECKING:
|
||||||
|
# avoid circular import
|
||||||
|
from nova.pci import stats
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
PCI_VENDOR_PATTERN = "^(hex{4})$".replace("hex", r"[\da-fA-F]")
|
PCI_VENDOR_PATTERN = "^(hex{4})$".replace("hex", r"[\da-fA-F]")
|
||||||
|
@ -30,11 +34,12 @@ _PCI_ADDRESS_PATTERN = ("^(hex{4}):(hex{2}):(hex{2}).(oct{1})$".
|
||||||
replace("hex", r"[\da-fA-F]").
|
replace("hex", r"[\da-fA-F]").
|
||||||
replace("oct", "[0-7]"))
|
replace("oct", "[0-7]"))
|
||||||
_PCI_ADDRESS_REGEX = re.compile(_PCI_ADDRESS_PATTERN)
|
_PCI_ADDRESS_REGEX = re.compile(_PCI_ADDRESS_PATTERN)
|
||||||
|
|
||||||
_SRIOV_TOTALVFS = "sriov_totalvfs"
|
_SRIOV_TOTALVFS = "sriov_totalvfs"
|
||||||
|
|
||||||
|
|
||||||
def pci_device_prop_match(pci_dev, specs):
|
def pci_device_prop_match(
|
||||||
|
pci_dev: 'stats.Pool', specs: ty.List[ty.Dict[str, str]],
|
||||||
|
) -> bool:
|
||||||
"""Check if the pci_dev meet spec requirement
|
"""Check if the pci_dev meet spec requirement
|
||||||
|
|
||||||
Specs is a list of PCI device property requirements.
|
Specs is a list of PCI device property requirements.
|
||||||
|
@ -47,7 +52,8 @@ def pci_device_prop_match(pci_dev, specs):
|
||||||
"capabilities_network": ["rx", "tx", "tso", "gso"]}]
|
"capabilities_network": ["rx", "tx", "tso", "gso"]}]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def _matching_devices(spec):
|
|
||||||
|
def _matching_devices(spec: ty.Dict[str, str]) -> bool:
|
||||||
for k, v in spec.items():
|
for k, v in spec.items():
|
||||||
pci_dev_v = pci_dev.get(k)
|
pci_dev_v = pci_dev.get(k)
|
||||||
if isinstance(v, list) and isinstance(pci_dev_v, list):
|
if isinstance(v, list) and isinstance(pci_dev_v, list):
|
||||||
|
@ -69,8 +75,10 @@ def pci_device_prop_match(pci_dev, specs):
|
||||||
return any(_matching_devices(spec) for spec in specs)
|
return any(_matching_devices(spec) for spec in specs)
|
||||||
|
|
||||||
|
|
||||||
def parse_address(address):
|
def parse_address(address: str) -> ty.Sequence[str]:
|
||||||
"""Returns (domain, bus, slot, function) from PCI address that is stored in
|
"""Parse a PCI address.
|
||||||
|
|
||||||
|
Returns (domain, bus, slot, function) from PCI address that is stored in
|
||||||
PciDevice DB table.
|
PciDevice DB table.
|
||||||
"""
|
"""
|
||||||
m = _PCI_ADDRESS_REGEX.match(address)
|
m = _PCI_ADDRESS_REGEX.match(address)
|
||||||
|
@ -79,7 +87,7 @@ def parse_address(address):
|
||||||
return m.groups()
|
return m.groups()
|
||||||
|
|
||||||
|
|
||||||
def get_pci_address_fields(pci_addr):
|
def get_pci_address_fields(pci_addr: str) -> ty.Tuple[str, str, str, str]:
|
||||||
"""Parse a fully-specified PCI device address.
|
"""Parse a fully-specified PCI device address.
|
||||||
|
|
||||||
Does not validate that the components are valid hex or wildcard values.
|
Does not validate that the components are valid hex or wildcard values.
|
||||||
|
@ -92,7 +100,7 @@ def get_pci_address_fields(pci_addr):
|
||||||
return domain, bus, slot, func
|
return domain, bus, slot, func
|
||||||
|
|
||||||
|
|
||||||
def get_pci_address(domain, bus, slot, func):
|
def get_pci_address(domain: str, bus: str, slot: str, func: str) -> str:
|
||||||
"""Assembles PCI address components into a fully-specified PCI address.
|
"""Assembles PCI address components into a fully-specified PCI address.
|
||||||
|
|
||||||
Does not validate that the components are valid hex or wildcard values.
|
Does not validate that the components are valid hex or wildcard values.
|
||||||
|
@ -103,7 +111,7 @@ def get_pci_address(domain, bus, slot, func):
|
||||||
return '%s:%s:%s.%s' % (domain, bus, slot, func)
|
return '%s:%s:%s.%s' % (domain, bus, slot, func)
|
||||||
|
|
||||||
|
|
||||||
def get_function_by_ifname(ifname):
|
def get_function_by_ifname(ifname: str) -> ty.Tuple[ty.Optional[str], bool]:
|
||||||
"""Given the device name, returns the PCI address of a device
|
"""Given the device name, returns the PCI address of a device
|
||||||
and returns True if the address is in a physical function.
|
and returns True if the address is in a physical function.
|
||||||
"""
|
"""
|
||||||
|
@ -121,7 +129,9 @@ def get_function_by_ifname(ifname):
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
def is_physical_function(domain, bus, slot, function):
|
def is_physical_function(
|
||||||
|
domain: str, bus: str, slot: str, function: str,
|
||||||
|
) -> bool:
|
||||||
dev_path = "/sys/bus/pci/devices/%(d)s:%(b)s:%(s)s.%(f)s/" % {
|
dev_path = "/sys/bus/pci/devices/%(d)s:%(b)s:%(s)s.%(f)s/" % {
|
||||||
"d": domain, "b": bus, "s": slot, "f": function}
|
"d": domain, "b": bus, "s": slot, "f": function}
|
||||||
if os.path.isdir(dev_path):
|
if os.path.isdir(dev_path):
|
||||||
|
@ -134,7 +144,7 @@ def is_physical_function(domain, bus, slot, function):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _get_sysfs_netdev_path(pci_addr, pf_interface):
|
def _get_sysfs_netdev_path(pci_addr: str, pf_interface: bool) -> str:
|
||||||
"""Get the sysfs path based on the PCI address of the device.
|
"""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.
|
Assumes a networking device - will not check for the existence of the path.
|
||||||
|
@ -144,7 +154,9 @@ def _get_sysfs_netdev_path(pci_addr, pf_interface):
|
||||||
return "/sys/bus/pci/devices/%s/net" % pci_addr
|
return "/sys/bus/pci/devices/%s/net" % pci_addr
|
||||||
|
|
||||||
|
|
||||||
def get_ifname_by_pci_address(pci_addr, pf_interface=False):
|
def get_ifname_by_pci_address(
|
||||||
|
pci_addr: str, pf_interface: bool = False,
|
||||||
|
) -> str:
|
||||||
"""Get the interface name based on a VF's pci address.
|
"""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
|
The returned interface name is either the parent PF's or that of the VF
|
||||||
|
@ -158,7 +170,7 @@ def get_ifname_by_pci_address(pci_addr, pf_interface=False):
|
||||||
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
||||||
|
|
||||||
|
|
||||||
def get_mac_by_pci_address(pci_addr, pf_interface=False):
|
def get_mac_by_pci_address(pci_addr: str, pf_interface: bool = False) -> str:
|
||||||
"""Get the MAC address of the nic based on its PCI address.
|
"""Get the MAC address of the nic based on its PCI address.
|
||||||
|
|
||||||
Raises PciDeviceNotFoundById in case the pci device is not a NIC
|
Raises PciDeviceNotFoundById in case the pci device is not a NIC
|
||||||
|
@ -179,7 +191,7 @@ def get_mac_by_pci_address(pci_addr, pf_interface=False):
|
||||||
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
||||||
|
|
||||||
|
|
||||||
def get_vf_num_by_pci_address(pci_addr):
|
def get_vf_num_by_pci_address(pci_addr: str) -> str:
|
||||||
"""Get the VF number based on a VF's pci address
|
"""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
|
A VF is associated with an VF number, which ip link command uses to
|
||||||
|
@ -188,14 +200,14 @@ def get_vf_num_by_pci_address(pci_addr):
|
||||||
VIRTFN_RE = re.compile(r"virtfn(\d+)")
|
VIRTFN_RE = re.compile(r"virtfn(\d+)")
|
||||||
virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
|
virtfns_path = "/sys/bus/pci/devices/%s/physfn/virtfn*" % (pci_addr)
|
||||||
vf_num = None
|
vf_num = None
|
||||||
try:
|
|
||||||
for vf_path in glob.iglob(virtfns_path):
|
for vf_path in glob.iglob(virtfns_path):
|
||||||
if re.search(pci_addr, os.readlink(vf_path)):
|
if re.search(pci_addr, os.readlink(vf_path)):
|
||||||
t = VIRTFN_RE.search(vf_path)
|
t = VIRTFN_RE.search(vf_path)
|
||||||
|
if t:
|
||||||
vf_num = t.group(1)
|
vf_num = t.group(1)
|
||||||
break
|
break
|
||||||
except Exception:
|
else:
|
||||||
pass
|
|
||||||
if vf_num is None:
|
|
||||||
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
raise exception.PciDeviceNotFoundById(id=pci_addr)
|
||||||
|
|
||||||
return vf_num
|
return vf_num
|
||||||
|
|
|
@ -14,10 +14,13 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
|
from nova import objects
|
||||||
from nova.pci import devspec
|
from nova.pci import devspec
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,7 +33,7 @@ class Whitelist(object):
|
||||||
assignable.
|
assignable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, whitelist_spec=None):
|
def __init__(self, whitelist_spec: str = None) -> None:
|
||||||
"""White list constructor
|
"""White list constructor
|
||||||
|
|
||||||
For example, the following json string specifies that devices whose
|
For example, the following json string specifies that devices whose
|
||||||
|
@ -50,7 +53,9 @@ class Whitelist(object):
|
||||||
self.specs = []
|
self.specs = []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_white_list_from_config(whitelists):
|
def _parse_white_list_from_config(
|
||||||
|
whitelists: str,
|
||||||
|
) -> ty.List[devspec.PciDeviceSpec]:
|
||||||
"""Parse and validate the pci whitelist from the nova config."""
|
"""Parse and validate the pci whitelist from the nova config."""
|
||||||
specs = []
|
specs = []
|
||||||
for jsonspec in whitelists:
|
for jsonspec in whitelists:
|
||||||
|
@ -77,7 +82,7 @@ class Whitelist(object):
|
||||||
|
|
||||||
return specs
|
return specs
|
||||||
|
|
||||||
def device_assignable(self, dev):
|
def device_assignable(self, dev: ty.Dict[str, str]) -> bool:
|
||||||
"""Check if a device can be assigned to a guest.
|
"""Check if a device can be assigned to a guest.
|
||||||
|
|
||||||
:param dev: A dictionary describing the device properties
|
:param dev: A dictionary describing the device properties
|
||||||
|
@ -87,7 +92,11 @@ class Whitelist(object):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_devspec(self, pci_dev):
|
def get_devspec(
|
||||||
|
self, pci_dev: 'objects.PciDevice',
|
||||||
|
) -> ty.Optional[devspec.PciDeviceSpec]:
|
||||||
for spec in self.specs:
|
for spec in self.specs:
|
||||||
if spec.match_pci_obj(pci_dev):
|
if spec.match_pci_obj(pci_dev):
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
|
@ -448,10 +448,11 @@ class PciDevTrackerTestCase(test.NoDBTestCase):
|
||||||
self.inst.numa_topology = objects.InstanceNUMATopology(
|
self.inst.numa_topology = objects.InstanceNUMATopology(
|
||||||
cells=[objects.InstanceNUMACell(
|
cells=[objects.InstanceNUMACell(
|
||||||
id=1, cpuset=set([1, 2]), memory=512)])
|
id=1, cpuset=set([1, 2]), memory=512)])
|
||||||
self.assertIsNone(self.tracker.claim_instance(
|
claims = self.tracker.claim_instance(
|
||||||
mock.sentinel.context,
|
mock.sentinel.context,
|
||||||
pci_requests_obj,
|
pci_requests_obj,
|
||||||
self.inst.numa_topology))
|
self.inst.numa_topology)
|
||||||
|
self.assertEqual([], claims)
|
||||||
|
|
||||||
def test_update_pci_for_instance_deleted(self):
|
def test_update_pci_for_instance_deleted(self):
|
||||||
pci_requests_obj = self._create_pci_requests_object(fake_pci_requests)
|
pci_requests_obj = self._create_pci_requests_object(fake_pci_requests)
|
||||||
|
|
|
@ -251,14 +251,3 @@ class GetVfNumByPciAddressTestCase(test.NoDBTestCase):
|
||||||
utils.get_vf_num_by_pci_address,
|
utils.get_vf_num_by_pci_address,
|
||||||
self.pci_address
|
self.pci_address
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch.object(os, 'readlink')
|
|
||||||
@mock.patch.object(glob, 'iglob')
|
|
||||||
def test_exception(self, mock_iglob, mock_readlink):
|
|
||||||
mock_iglob.return_value = self.paths
|
|
||||||
mock_readlink.side_effect = OSError('No such file or directory')
|
|
||||||
self.assertRaises(
|
|
||||||
exception.PciDeviceNotFoundById,
|
|
||||||
utils.get_vf_num_by_pci_address,
|
|
||||||
self.pci_address
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in New Issue