diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-09-04 09:15:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-09-04 09:15:24 +0000 |
commit | fd6bd8e3ee83881f2f40686b21502926ddf977af (patch) | |
tree | 6093d44e4716db2c1f768c23c78359e34bb8e819 /staslib | |
parent | Adding upstream version 2.3~rc3. (diff) | |
download | nvme-stas-fd6bd8e3ee83881f2f40686b21502926ddf977af.tar.xz nvme-stas-fd6bd8e3ee83881f2f40686b21502926ddf977af.zip |
Adding upstream version 2.3~rc4.upstream/2.3_rc4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'staslib')
-rw-r--r-- | staslib/defs.py | 19 | ||||
-rw-r--r-- | staslib/gutil.py | 6 | ||||
-rw-r--r-- | staslib/iputil.py | 106 | ||||
-rw-r--r-- | staslib/udev.py | 263 |
4 files changed, 177 insertions, 217 deletions
diff --git a/staslib/defs.py b/staslib/defs.py index 877f6e6..fc1135b 100644 --- a/staslib/defs.py +++ b/staslib/defs.py @@ -36,18 +36,25 @@ KERNEL_IFACE_MIN_VERSION = KernelVersion('5.14') KERNEL_TP8013_MIN_VERSION = KernelVersion('5.16') KERNEL_HOSTKEY_MIN_VERSION = KernelVersion('5.20') KERNEL_CTRLKEY_MIN_VERSION = KernelVersion('5.20') +KERNEL_ALL_MIN_VERSION = max( + # Minimum version required to have support for all + KERNEL_IFACE_MIN_VERSION, + KERNEL_TP8013_MIN_VERSION, + KERNEL_HOSTKEY_MIN_VERSION, + KERNEL_CTRLKEY_MIN_VERSION, +) WELL_KNOWN_DISC_NQN = 'nqn.2014-08.org.nvmexpress.discovery' PROG_NAME = os.path.basename(sys.argv[0]) -NVME_HOSTID = '/etc/nvme/hostid' -NVME_HOSTNQN = '/etc/nvme/hostnqn' -NVME_HOSTKEY = '/etc/nvme/hostkey' +NVME_HOSTID = '@ETC@/nvme/hostid' +NVME_HOSTNQN = '@ETC@/nvme/hostnqn' +NVME_HOSTKEY = '@ETC@/nvme/hostkey' -SYS_CONF_FILE = '/etc/stas/sys.conf' -STAFD_CONF_FILE = '/etc/stas/stafd.conf' -STACD_CONF_FILE = '/etc/stas/stacd.conf' +SYS_CONF_FILE = '@ETC@/stas/sys.conf' +STAFD_CONF_FILE = '@ETC@/stas/stafd.conf' +STACD_CONF_FILE = '@ETC@/stas/stacd.conf' HAS_NBFT_SUPPORT = hasattr(nvme, 'nbft_get') NBFT_SYSFS_PATH = "/sys/firmware/acpi/tables" diff --git a/staslib/gutil.py b/staslib/gutil.py index 1730ac0..9aef347 100644 --- a/staslib/gutil.py +++ b/staslib/gutil.py @@ -6,8 +6,8 @@ # # Authors: Martin Belanger <Martin.Belanger@dell.com> # -'''This module provides utility functions/classes to provide easier to use -access to GLib/Gio/Gobject resources. +'''This module provides utility functions (or classes) that simplify +the use of certain GLib/Gio/Gobject functions/resources. ''' import logging @@ -443,7 +443,7 @@ class TcpChecker: # pylint: disable=too-many-instance-attributes # the GLib context. family = socket.AF_INET if self._traddr.version == 4 else socket.AF_INET6 self._native_sock = socket.socket(family, socket.SOCK_STREAM | socket.SOCK_NONBLOCK, socket.IPPROTO_TCP) - if isinstance(self._host_iface, str): + if self._host_iface and isinstance(self._host_iface, str): self._native_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, self._host_iface.encode('utf-8')) # Convert socket.socket() to a Gio.Socket() object diff --git a/staslib/iputil.py b/staslib/iputil.py index 96c5d56..d5e93dd 100644 --- a/staslib/iputil.py +++ b/staslib/iputil.py @@ -131,66 +131,22 @@ def mac2iface(mac: str): # pylint: disable=too-many-locals # ****************************************************************************** -def _data_matches_ip(data_family, data, ip): - if data_family == socket.AF_INET: - try: - other_ip = ipaddress.IPv4Address(data) - except ValueError: - return False - if ip.version == 6: - ip = ip.ipv4_mapped - elif data_family == socket.AF_INET6: - try: - other_ip = ipaddress.IPv6Address(data) - except ValueError: - return False - if ip.version == 4: - other_ip = other_ip.ipv4_mapped - else: - return False - - return other_ip == ip - - -def _iface_of(src_addr): # pylint: disable=too-many-locals - '''@brief Find the interface that has src_addr as one of its assigned IP addresses. - @param src_addr: The IP address to match - @type src_addr: Instance of ipaddress.IPv4Address or ipaddress.IPv6Address +def ip_equal(ip1, ip2): + '''Check whther two IP addresses are equal. + @param ip1: IPv4Address or IPv6Address object + @param ip2: IPv4Address or IPv6Address object ''' - with socket.socket(socket.AF_NETLINK, socket.SOCK_RAW) as sock: - sock.sendall(GETADDRCMD) - nlmsg = sock.recv(8192) - nlmsg_idx = 0 - while True: - if nlmsg_idx >= len(nlmsg): - nlmsg += sock.recv(8192) - - nlmsghdr = nlmsg[nlmsg_idx : nlmsg_idx + NLMSG_HDRLEN] - nlmsg_len, nlmsg_type, _, _, _ = struct.unpack('<LHHLL', nlmsghdr) - - if nlmsg_type == NLMSG_DONE: - break - - if nlmsg_type == RTM_NEWADDR: - msg_indx = nlmsg_idx + NLMSG_HDRLEN - msg = nlmsg[msg_indx : msg_indx + IFADDRMSG_SZ] # ifaddrmsg - ifa_family, _, _, _, ifa_index = struct.unpack('<BBBBL', msg) - - rtattr_indx = msg_indx + IFADDRMSG_SZ - while rtattr_indx < (nlmsg_idx + nlmsg_len): - rtattr = nlmsg[rtattr_indx : rtattr_indx + RTATTR_SZ] - rta_len, rta_type = struct.unpack('<HH', rtattr) - if rta_type == IFLA_ADDRESS: - data = nlmsg[rtattr_indx + RTATTR_SZ : rtattr_indx + rta_len] - if _data_matches_ip(ifa_family, data, src_addr): - return socket.if_indextoname(ifa_index) + if not isinstance(ip1, ipaddress._BaseAddress): # pylint: disable=protected-access + return False + if not isinstance(ip2, ipaddress._BaseAddress): # pylint: disable=protected-access + return False - rta_len = RTA_ALIGN(rta_len) # Round up to multiple of 4 - rtattr_indx += rta_len # Move to next rtattr + if ip1.version == 4 and ip2.version == 6: + ip2 = ip2.ipv4_mapped + elif ip1.version == 6 and ip2.version == 4: + ip1 = ip1.ipv4_mapped - nlmsg_idx += nlmsg_len # Move to next Netlink message - - return '' + return ip1 == ip2 # ****************************************************************************** @@ -221,21 +177,21 @@ def net_if_addrs(): # pylint: disable=too-many-locals source address. @example: { 'wlp0s20f3': { - 4: ['10.0.0.28'], + 4: [IPv4Address('10.0.0.28')], 6: [ - 'fd5e:9a9e:c5bd:0:5509:890c:1848:3843', - 'fd5e:9a9e:c5bd:0:1fd5:e527:8df7:7912', - '2605:59c8:6128:fb00:c083:1b8:c467:81d2', - '2605:59c8:6128:fb00:e99d:1a02:38e0:ad52', - 'fe80::d71b:e807:d5ee:7614' + IPv6Address('fd5e:9a9e:c5bd:0:5509:890c:1848:3843'), + IPv6Address('fd5e:9a9e:c5bd:0:1fd5:e527:8df7:7912'), + IPv6Address('2605:59c8:6128:fb00:c083:1b8:c467:81d2'), + IPv6Address('2605:59c8:6128:fb00:e99d:1a02:38e0:ad52'), + IPv6Address('fe80::d71b:e807:d5ee:7614'), ], }, 'lo': { - 4: ['127.0.0.1'], - 6: ['::1'], + 4: [IPv4Address('127.0.0.1')], + 6: [IPv6Address('::1')], }, 'docker0': { - 4: ['172.17.0.1'], + 4: [IPv4Address('172.17.0.1')], 6: [] }, } @@ -295,14 +251,18 @@ def net_if_addrs(): # pylint: disable=too-many-locals # ****************************************************************************** -def get_interface(src_addr): +def get_interface(ifaces: dict, src_addr): '''Get interface for given source address - @param src_addr: The source address - @type src_addr: str + @param ifaces: Interface info previously returned by @net_if_addrs() + @param src_addr: IPv4Address or IPv6Address object ''' - if not src_addr: + if not isinstance(src_addr, ipaddress._BaseAddress): # pylint: disable=protected-access return '' - src_addr = src_addr.split('%')[0] # remove scope-id (if any) - src_addr = get_ipaddress_obj(src_addr) - return '' if src_addr is None else _iface_of(src_addr) + for iface, addr_map in ifaces.items(): + for addrs in addr_map.values(): + for addr in addrs: + if ip_equal(src_addr, addr): + return iface + + return '' 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) |