diff options
Diffstat (limited to 'staslib/udev.py')
-rw-r--r-- | staslib/udev.py | 263 |
1 files changed, 128 insertions, 135 deletions
diff --git a/staslib/udev.py b/staslib/udev.py index b9bd258..48b7d1f 100644 --- a/staslib/udev.py +++ b/staslib/udev.py @@ -11,6 +11,7 @@ import os import time import logging +from functools import partial import pyudev from gi.repository import GLib from staslib import defs, iputil, trid @@ -154,7 +155,7 @@ class Udev: return False @staticmethod - def _cid_matches_tcp_tid_legacy(tid, cid): # pylint: disable=too-many-return-statements,too-many-branches + def _cid_matches_tcp_tid_legacy(tid, cid, ifaces): # pylint: disable=too-many-branches '''On kernels older than 6.1, the src_addr parameter is not available from the sysfs. Therefore, we need to infer a match based on other parameters. And there are a few cases where we're simply not sure @@ -164,76 +165,66 @@ class Udev: cid_host_iface = cid['host-iface'] cid_host_traddr = iputil.get_ipaddress_obj(cid['host-traddr'], ipv4_mapped_convert=True) - if not cid_host_iface: # cid.host_iface is undefined - if not cid_host_traddr: # cid.host_traddr is undefined - # When the existing cid.src_addr, cid.host_traddr, and cid.host_iface - # are all undefined (which can only happen on kernels prior to 6.1), - # we can't know for sure on which interface an existing connection - # was made. In this case, we can only declare a match if both - # tid.host_iface and tid.host_traddr are undefined as well. - logging.debug( - 'Udev._cid_matches_tcp_tid_legacy() - cid=%s, tid=%s - Not enough info. Assume "match" but this could be wrong.', - cid, - tid, - ) - return True + # Only check host_traddr if candidate cares about it + if tid.host_traddr: + tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True) - # cid.host_traddr is defined. If tid.host_traddr is also - # defined, then it must match the existing cid.host_traddr. - if tid.host_traddr: - tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True) + if cid_host_traddr: if tid_host_traddr != cid_host_traddr: return False - # If tid.host_iface is defined, then the interface where - # the connection is located must match. If tid.host_iface - # is not defined, then we don't really care on which - # interface the connection was made and we can skip this test. - if tid.host_iface: - # With the existing cid.host_traddr, we can find the - # interface of the exisiting connection. - connection_iface = iputil.get_interface(str(cid_host_traddr)) - if tid.host_iface != connection_iface: + else: + # If c->cfg.host_traddr is unknown, then the controller (c) + # uses the interface's primary address as the source + # address. If c->cfg.host_iface is defined we can + # determine the primary address associated with that + # interface and compare that to the candidate->host_traddr. + if cid_host_iface: + if_addrs = ifaces.get(cid_host_iface, {4: [], 6: []}) + source_addrs = if_addrs[tid_host_traddr.version] + if len(source_addrs): # Make sure it's not empty + primary_addr = iputil.get_ipaddress_obj(source_addrs[0], ipv4_mapped_convert=True) + if primary_addr != tid_host_traddr: + return False + + else: + # If both c->cfg.host_traddr and c->cfg.host_iface are + # unknown, we don't have enough information to make a + # 100% positive match. Regardless, let's be optimistic + # and assume that we have a match. + logging.debug( + 'Udev._cid_matches_tcp_tid_legacy() - [1] cid=%s, tid=%s - Not enough info. Assume "match" but this could be wrong.', + cid, + tid, + ) + + # Only check host_iface if candidate cares about it + if tid.host_iface: + if cid_host_iface: + if tid.host_iface != cid_host_iface: return False - return True - - # cid.host_iface is defined - if not cid_host_traddr: # cid.host_traddr is undefined - if tid.host_iface and tid.host_iface != cid_host_iface: - return False - - if tid.host_traddr: - # It's impossible to tell the existing connection source - # address. So, we can't tell if it matches tid.host_traddr. - # However, if the existing host_iface has only one source - # address assigned to it, we can assume that the source - # address used for the existing connection is that address. - if_addrs = iputil.net_if_addrs().get(cid_host_iface, {4: [], 6: []}) - tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True) - source_addrs = if_addrs[tid_host_traddr.version] - if len(source_addrs) != 1: - return False - - src_addr0 = iputil.get_ipaddress_obj(source_addrs[0], ipv4_mapped_convert=True) - if src_addr0 != tid_host_traddr: - return False - - return True - - # cid.host_traddr is defined - if tid.host_iface and tid.host_iface != cid_host_iface: - return False + else: + if cid_host_traddr: + connection_iface = iputil.get_interface(ifaces, cid_host_traddr) + if tid.host_iface != connection_iface: + return False - if tid.host_traddr: - tid_host_traddr = iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True) - if tid_host_traddr != cid_host_traddr: - return False + else: + # If both c->cfg.host_traddr and c->cfg.host_iface are + # unknown, we don't have enough information to make a + # 100% positive match. Regardless, let's be optimistic + # and assume that we have a match. + logging.debug( + 'Udev._cid_matches_tcp_tid_legacy() - [2] cid=%s, tid=%s - Not enough info. Assume "match" but this could be wrong.', + cid, + tid, + ) return True @staticmethod - def _cid_matches_tid(tid, cid): # pylint: disable=too-many-return-statements,too-many-branches + def _cid_matches_tid(tid, cid, ifaces): # pylint: disable=too-many-return-statements,too-many-branches '''Check if existing controller's cid matches candidate controller's tid. @param cid: The Connection ID of an existing controller (from the sysfs). @param tid: The Transport ID of a candidate controller. @@ -242,8 +233,8 @@ class Udev: be re-used for the candidate controller (specified by tid). We do not have a match if the candidate's tid.transport, tid.traddr, - tid.trsvcid, and tid.subsysnqn are not identical to those of the cid. - These 4 parameters are mandatory for a match. + tid.trsvcid, tid.subsysnqn, and tid.host_nqn are not identical to those + of the cid. These 5 parameters are mandatory for a match. The tid.host_traddr and tid.host_iface depend on the transport type. These parameters may not apply or have a different syntax/meaning @@ -274,8 +265,9 @@ class Udev: tid_traddr = iputil.get_ipaddress_obj(tid.traddr, ipv4_mapped_convert=True) cid_traddr = iputil.get_ipaddress_obj(cid['traddr'], ipv4_mapped_convert=True) else: - cid_traddr = cid['traddr'] - tid_traddr = tid.traddr + # For FC and loop we can do a case-insensitive comparison + tid_traddr = tid.traddr.lower() + cid_traddr = cid['traddr'].lower() if cid_traddr != tid_traddr: return False @@ -290,7 +282,7 @@ class Udev: # For legacy kernels (i.e. older than 6.1), the existing cid.src_addr # is always undefined. We need to use advanced logic to determine # whether cid and tid match. - return Udev._cid_matches_tcp_tid_legacy(tid, cid) + return Udev._cid_matches_tcp_tid_legacy(tid, cid, ifaces) # The existing controller's cid.src_addr is always defined for kernel # 6.1 and later. We can use the existing controller's cid.src_addr to @@ -303,12 +295,12 @@ class Udev: return False # host-iface is an optional tcp-only parameter. - if tid.host_iface and tid.host_iface != iputil.get_interface(str(src_addr)): + if tid.host_iface and tid.host_iface != iputil.get_interface(ifaces, src_addr): return False elif tid.transport == 'fc': # host-traddr is mandatory for FC. - if tid.host_traddr != cid['host-traddr']: + if tid.host_traddr.lower() != cid['host-traddr'].lower(): return False elif tid.transport == 'rdma': @@ -326,17 +318,20 @@ class Udev: Discovery Controller. @return The device if a match is found, None otherwise. ''' - for device in self._context.list_devices( + devices = self._context.list_devices( subsystem='nvme', NVME_TRADDR=tid.traddr, NVME_TRSVCID=tid.trsvcid, NVME_TRTYPE=tid.transport - ): - if not self.is_dc_device(device): - continue + ) + if devices: + ifaces = iputil.net_if_addrs() + for device in devices: + if not self.is_dc_device(device): + continue - cid = self.get_cid(device) - if not self._cid_matches_tid(tid, cid): - continue + cid = self.get_cid(device) + if not self._cid_matches_tid(tid, cid, ifaces): + continue - return device + return device return None @@ -345,17 +340,20 @@ class Udev: I/O Controller. @return The device if a match is found, None otherwise. ''' - for device in self._context.list_devices( + devices = self._context.list_devices( subsystem='nvme', NVME_TRADDR=tid.traddr, NVME_TRSVCID=tid.trsvcid, NVME_TRTYPE=tid.transport - ): - if not self.is_ioc_device(device): - continue + ) + if devices: + ifaces = iputil.net_if_addrs() + for device in devices: + if not self.is_ioc_device(device): + continue - cid = self.get_cid(device) - if not self._cid_matches_tid(tid, cid): - continue + cid = self.get_cid(device) + if not self._cid_matches_tid(tid, cid, ifaces): + continue - return device + return device return None @@ -364,68 +362,63 @@ class Udev: @return A list of pyudev.device._device.Device objects ''' tids = [] - for device in self._context.list_devices(subsystem='nvme'): - if device.properties.get('NVME_TRTYPE', '') not in transports: - continue + devices = self._context.list_devices(subsystem='nvme') + if devices: + ifaces = iputil.net_if_addrs() + for device in devices: + if device.properties.get('NVME_TRTYPE', '') not in transports: + continue - if not self.is_ioc_device(device): - continue + if not self.is_ioc_device(device): + continue - tids.append(self.get_tid(device)) + tids.append(self.get_tid(device, ifaces)) return tids def _process_udev_event(self, event_source, condition): # pylint: disable=unused-argument if condition == GLib.IO_IN: - event_count = 0 - while True: - try: - device = self._monitor.poll(timeout=0) - except EnvironmentError as ex: - device = None - # This event seems to happen in bursts. So, let's suppress - # logging for 2 seconds to avoid filling the syslog. - self._log_event_count += 1 - now = time.time() - if now > self._log_event_soak_time: - logging.debug('Udev._process_udev_event() - %s [%s]', ex, self._log_event_count) - self._log_event_soak_time = now + 2 - self._log_event_count = 0 - - if device is None: - break - - event_count += 1 - self._device_event(device, event_count) + try: + self.__handle_events() + + except EnvironmentError as ex: + # Exceptions seem to happen in bursts. So, let's suppress + # logging for 2 seconds to avoid filling the syslog. + self._log_event_count += 1 + now = time.time() + if now > self._log_event_soak_time: + logging.debug('Udev._process_udev_event() - %s [%s]', ex, self._log_event_count) + self._log_event_soak_time = now + 2 + self._log_event_count = 0 return GLib.SOURCE_CONTINUE - @staticmethod - def __cback_names(action_cbacks, device_cback): - names = [] - for cback in action_cbacks: - names.append(cback.__name__ + '()') - if device_cback: - names.append(device_cback.__name__ + '()') - return names - - def _device_event(self, device, event_count): - action_cbacks = self._action_event_registry.get(device.action, set()) - device_cback = self._device_event_registry.get(device.sys_name, None) - - logging.debug( - 'Udev._device_event() - %-8s %-6s %-8s %s', - f'{device.sys_name}:', - device.action, - f'{event_count:2}:{device.sequence_number}', - self.__cback_names(action_cbacks, device_cback), - ) + def __handle_events(self): + event_count = 0 + read_device = partial(self._monitor.poll, timeout=0) + for device in iter(read_device, None): + if device is None: # This should never happen,... + break # ...but better safe than sorry. + + event_count += 1 + + action_cbacks = self._action_event_registry.get(device.action, None) + device_cback = self._device_event_registry.get(device.sys_name, None) + if action_cbacks or device_cback: + logging.debug( + 'Udev.__handle_events() - %-7s %-6s %2s:%s', + device.sys_name, + device.action, + event_count, + device.sequence_number, + ) - for action_cback in action_cbacks: - GLib.idle_add(action_cback, device) + if action_cbacks: + for action_cback in action_cbacks: + GLib.idle_add(action_cback, device) - if device_cback is not None: - GLib.idle_add(device_cback, device) + if device_cback is not None: + GLib.idle_add(device_cback, device) @staticmethod def _get_property(device, prop, default=''): @@ -470,7 +463,7 @@ class Udev: return attr_str[start:end] @staticmethod - def get_tid(device): + def get_tid(device, ifaces): '''@brief return the Transport ID associated with a udev device''' cid = Udev.get_cid(device) if cid['transport'] == 'tcp': @@ -479,7 +472,7 @@ class Udev: # We'll try to find the interface from the source address on # the connection. Only available if kernel exposes the source # address (src_addr) in the "address" attribute. - cid['host-iface'] = iputil.get_interface(src_addr) + cid['host-iface'] = iputil.get_interface(ifaces, iputil.get_ipaddress_obj(src_addr)) return trid.TID(cid) |