Skip to content

Commit 51d16ad

Browse files
committed
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 <[email protected]>
1 parent eba9d59 commit 51d16ad

File tree

9 files changed

+346
-170
lines changed

9 files changed

+346
-170
lines changed

mypy-files.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
nova/compute/manager.py
22
nova/crypto.py
33
nova/network/neutron.py
4+
nova/pci
45
nova/privsep/path.py
56
nova/scheduler/client/report.py
67
nova/scheduler/request_filter.py

nova/pci/devspec.py

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
import abc
1515
import re
1616
import string
17+
import typing as ty
1718

1819
from nova import exception
1920
from nova.i18n import _
21+
from nova import objects
2022
from nova.pci import utils
2123

2224
MAX_VENDOR_ID = 0xFFFF
@@ -29,24 +31,35 @@
2931
REGEX_ANY = '.*'
3032

3133

34+
PCISpecAddressType = ty.Union[ty.Dict[str, str], str]
35+
36+
3237
class PciAddressSpec(metaclass=abc.ABCMeta):
3338
"""Abstract class for all PCI address spec styles
3439
3540
This class checks the address fields of the pci.passthrough_whitelist
3641
"""
3742

43+
def __init__(self, pci_addr: str) -> None:
44+
self.domain = ''
45+
self.bus = ''
46+
self.slot = ''
47+
self.func = ''
48+
3849
@abc.abstractmethod
3950
def match(self, pci_addr):
4051
pass
4152

42-
def is_single_address(self):
53+
def is_single_address(self) -> bool:
4354
return all([
4455
all(c in string.hexdigits for c in self.domain),
4556
all(c in string.hexdigits for c in self.bus),
4657
all(c in string.hexdigits for c in self.slot),
4758
all(c in string.hexdigits for c in self.func)])
4859

49-
def _set_pci_dev_info(self, prop, maxval, hex_value):
60+
def _set_pci_dev_info(
61+
self, prop: str, maxval: int, hex_value: str
62+
) -> None:
5063
a = getattr(self, prop)
5164
if a == ANY:
5265
return
@@ -70,8 +83,10 @@ class PhysicalPciAddress(PciAddressSpec):
7083
This function class will validate the address fields for a single
7184
PCI device.
7285
"""
73-
def __init__(self, pci_addr):
86+
87+
def __init__(self, pci_addr: PCISpecAddressType) -> None:
7488
try:
89+
# TODO(stephenfin): Is this ever actually a string?
7590
if isinstance(pci_addr, dict):
7691
self.domain = pci_addr['domain']
7792
self.bus = pci_addr['bus']
@@ -87,7 +102,7 @@ def __init__(self, pci_addr):
87102
except (KeyError, ValueError):
88103
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
89104

90-
def match(self, phys_pci_addr):
105+
def match(self, phys_pci_addr: PciAddressSpec) -> bool:
91106
conditions = [
92107
self.domain == phys_pci_addr.domain,
93108
self.bus == phys_pci_addr.bus,
@@ -104,7 +119,7 @@ class PciAddressGlobSpec(PciAddressSpec):
104119
check for wildcards, and insert wildcards where the field is left blank.
105120
"""
106121

107-
def __init__(self, pci_addr):
122+
def __init__(self, pci_addr: str) -> None:
108123
self.domain = ANY
109124
self.bus = ANY
110125
self.slot = ANY
@@ -129,7 +144,7 @@ def __init__(self, pci_addr):
129144
self._set_pci_dev_info('bus', MAX_BUS, '%02x')
130145
self._set_pci_dev_info('slot', MAX_SLOT, '%02x')
131146

132-
def match(self, phys_pci_addr):
147+
def match(self, phys_pci_addr: PciAddressSpec) -> bool:
133148
conditions = [
134149
self.domain in (ANY, phys_pci_addr.domain),
135150
self.bus in (ANY, phys_pci_addr.bus),
@@ -146,7 +161,8 @@ class PciAddressRegexSpec(PciAddressSpec):
146161
The validation includes check for all PCI address attributes and validate
147162
their regex.
148163
"""
149-
def __init__(self, pci_addr):
164+
165+
def __init__(self, pci_addr: dict) -> None:
150166
try:
151167
self.domain = pci_addr.get('domain', REGEX_ANY)
152168
self.bus = pci_addr.get('bus', REGEX_ANY)
@@ -159,7 +175,7 @@ def __init__(self, pci_addr):
159175
except re.error:
160176
raise exception.PciDeviceWrongAddressFormat(address=pci_addr)
161177

162-
def match(self, phys_pci_addr):
178+
def match(self, phys_pci_addr: PciAddressSpec) -> bool:
163179
conditions = [
164180
bool(self.domain_regex.match(phys_pci_addr.domain)),
165181
bool(self.bus_regex.match(phys_pci_addr.bus)),
@@ -187,11 +203,13 @@ class WhitelistPciAddress(object):
187203
| passthrough_whitelist = {"vendor_id":"1137","product_id":"0071"}
188204
189205
"""
190-
def __init__(self, pci_addr, is_physical_function):
206+
def __init__(
207+
self, pci_addr: PCISpecAddressType, is_physical_function: bool
208+
) -> None:
191209
self.is_physical_function = is_physical_function
192210
self._init_address_fields(pci_addr)
193211

194-
def _check_physical_function(self):
212+
def _check_physical_function(self) -> None:
195213
if self.pci_address_spec.is_single_address():
196214
self.is_physical_function = (
197215
utils.is_physical_function(
@@ -200,7 +218,8 @@ def _check_physical_function(self):
200218
self.pci_address_spec.slot,
201219
self.pci_address_spec.func))
202220

203-
def _init_address_fields(self, pci_addr):
221+
def _init_address_fields(self, pci_addr: PCISpecAddressType) -> None:
222+
self.pci_address_spec: PciAddressSpec
204223
if not self.is_physical_function:
205224
if isinstance(pci_addr, str):
206225
self.pci_address_spec = PciAddressGlobSpec(pci_addr)
@@ -212,10 +231,12 @@ def _init_address_fields(self, pci_addr):
212231
else:
213232
self.pci_address_spec = PhysicalPciAddress(pci_addr)
214233

215-
def match(self, pci_addr, pci_phys_addr):
216-
"""Match a device to this PciAddress. Assume this is called given
217-
pci_addr and pci_phys_addr reported by libvirt, no attempt is made to
218-
verify if pci_addr is a VF of pci_phys_addr.
234+
def match(self, pci_addr: str, pci_phys_addr: ty.Optional[str]) -> bool:
235+
"""Match a device to this PciAddress.
236+
237+
Assume this is called with a ``pci_addr`` and ``pci_phys_addr``
238+
reported by libvirt. No attempt is made to verify if ``pci_addr`` is a
239+
VF of ``pci_phys_addr``.
219240
220241
:param pci_addr: PCI address of the device to match.
221242
:param pci_phys_addr: PCI address of the parent of the device to match
@@ -237,51 +258,57 @@ def match(self, pci_addr, pci_phys_addr):
237258

238259

239260
class PciDeviceSpec(PciAddressSpec):
240-
def __init__(self, dev_spec):
261+
def __init__(self, dev_spec: ty.Dict[str, str]) -> None:
241262
self.tags = dev_spec
242263
self._init_dev_details()
243264

244-
def _init_dev_details(self):
265+
def _init_dev_details(self) -> None:
245266
self.vendor_id = self.tags.pop("vendor_id", ANY)
246267
self.product_id = self.tags.pop("product_id", ANY)
268+
self.dev_name = self.tags.pop("devname", None)
269+
self.address: ty.Optional[WhitelistPciAddress] = None
247270
# Note(moshele): The address attribute can be a string or a dict.
248271
# For glob syntax or specific pci it is a string and for regex syntax
249272
# it is a dict. The WhitelistPciAddress class handles both types.
250-
self.address = self.tags.pop("address", None)
251-
self.dev_name = self.tags.pop("devname", None)
273+
address = self.tags.pop("address", None)
252274

253275
self.vendor_id = self.vendor_id.strip()
254276
self._set_pci_dev_info('vendor_id', MAX_VENDOR_ID, '%04x')
255277
self._set_pci_dev_info('product_id', MAX_PRODUCT_ID, '%04x')
256278

257-
if self.address and self.dev_name:
279+
if address and self.dev_name:
258280
raise exception.PciDeviceInvalidDeviceName()
281+
259282
if not self.dev_name:
260-
pci_address = self.address or "*:*:*.*"
261-
self.address = WhitelistPciAddress(pci_address, False)
283+
self.address = WhitelistPciAddress(address or '*:*:*.*', False)
284+
285+
def match(self, dev_dict: ty.Dict[str, str]) -> bool:
286+
address_obj: ty.Optional[WhitelistPciAddress]
262287

263-
def match(self, dev_dict):
264288
if self.dev_name:
265-
address_str, pf = utils.get_function_by_ifname(
266-
self.dev_name)
289+
address_str, pf = utils.get_function_by_ifname(self.dev_name)
267290
if not address_str:
268291
return False
269292
# Note(moshele): In this case we always passing a string
270293
# of the PF pci address
271294
address_obj = WhitelistPciAddress(address_str, pf)
272-
elif self.address:
295+
else: # use self.address
273296
address_obj = self.address
297+
298+
if not address_obj:
299+
return False
300+
274301
return all([
275302
self.vendor_id in (ANY, dev_dict['vendor_id']),
276303
self.product_id in (ANY, dev_dict['product_id']),
277304
address_obj.match(dev_dict['address'],
278305
dev_dict.get('parent_addr'))])
279306

280-
def match_pci_obj(self, pci_obj):
307+
def match_pci_obj(self, pci_obj: 'objects.PciDevice') -> bool:
281308
return self.match({'vendor_id': pci_obj.vendor_id,
282309
'product_id': pci_obj.product_id,
283310
'address': pci_obj.address,
284311
'parent_addr': pci_obj.parent_addr})
285312

286-
def get_tags(self):
313+
def get_tags(self) -> ty.Dict[str, str]:
287314
return self.tags

0 commit comments

Comments
 (0)