1
0
Fork 0

Merging upstream version 2.3~rc4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-16 12:55:12 +01:00
parent 0bb8b81ada
commit 3cb8664662
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
16 changed files with 708 additions and 276 deletions

View file

@ -38,13 +38,15 @@ jobs:
python3 -m pip install --upgrade dasbus pylint pyflakes PyGObject python3 -m pip install --upgrade dasbus pylint pyflakes PyGObject
python3 -m pip install --upgrade vermin pyfakefs importlib-resources python3 -m pip install --upgrade vermin pyfakefs importlib-resources
- name: "INSTALL: libnvme" - name: "INSTALL: libnvme dependencies"
run: |
sudo apt-get install --yes --quiet swig
sudo apt-get install --yes --quiet libjson-c-dev
- name: "SETUP: [nvme-stas, libnvme]"
run: | run: |
sudo apt-get install --yes --quiet swig libjson-c-dev
meson subprojects download meson subprojects download
meson setup .build subprojects/libnvme -Dlibdbus=disabled -Dopenssl=disabled -Dbuildtype=release -Dprefix=/usr -Dpython=enabled meson setup --buildtype=release --sysconfdir=/etc --prefix=/usr -Dman=true -Dhtml=true -Dlibnvme:python=enabled -Dlibnvme:libdbus=disabled -Dlibnvme:openssl=disabled -Dlibnvme:json-c=disabled -Dlibnvme:keyutils=disabled .build
ninja -C .build
sudo meson install -C .build
- name: "CONFIG: PYTHONPATH" - name: "CONFIG: PYTHONPATH"
run: | run: |
@ -55,7 +57,7 @@ jobs:
with: with:
action: test action: test
directory: .build directory: .build
setup-options: -Dman=true -Dhtml=true setup-options: --buildtype=release --sysconfdir=/etc --prefix=/usr -Dman=true -Dhtml=true -Dlibnvme:python=enabled -Dlibnvme:libdbus=disabled -Dlibnvme:openssl=disabled -Dlibnvme:json-c=disabled -Dlibnvme:keyutils=disabled
options: --print-errorlogs --suite nvme-stas options: --print-errorlogs --suite nvme-stas
# Preserve meson's log file on failure # Preserve meson's log file on failure
@ -67,9 +69,11 @@ jobs:
- name: "Generate coverage report" - name: "Generate coverage report"
run: | run: |
python3 -m pip install pytest python3 -m pip install --upgrade pytest
python3 -m pip install pytest-cov python3 -m pip install --upgrade pytest-cov
PYTHONPATH=.build:.build/subprojects/libnvme:/usr/lib/python3/dist-packages/ pytest --cov=./staslib --cov-report=xml test/test-*.py echo $( pwd )
cp -r .build/staslib/* ./staslib/.
pytest --cov=./staslib --cov-report=xml test/test-*.py
- uses: codecov/codecov-action@v3 - uses: codecov/codecov-action@v3
with: with:

View file

@ -7,6 +7,7 @@ New features:
- Support for nBFT (NVMe-oF Boot Table). - Support for nBFT (NVMe-oF Boot Table).
- The Avahi driver will now verify reachability of services discovered through mDNS to make sure all discovered IP addresses can be connected to. This avoids invoking the NVMe kernel driver with invalid IP addresses and getting error messages in the syslog. - The Avahi driver will now verify reachability of services discovered through mDNS to make sure all discovered IP addresses can be connected to. This avoids invoking the NVMe kernel driver with invalid IP addresses and getting error messages in the syslog.
- The Avahi driver will now print an error message if the same IP address is found on multiple interfaces. This indicates a misconfiguration of the network. - The Avahi driver will now print an error message if the same IP address is found on multiple interfaces. This indicates a misconfiguration of the network.
- Simplify algorithm that determines if an existing connection (is sysfs) can be reused by stafd/stacd instead of creating a duplicate connection.
Bug fixes: Bug fixes:

View file

@ -17,10 +17,10 @@ components = [
'environment.txt', 'environment.txt',
'installation.rst', 'installation.rst',
'nvme-stas.rst', 'nvme-stas.rst',
'org.nvmexpress.stac.debug.rst', conf.get('STACD_DBUS_NAME') + '.debug.rst',
'org.nvmexpress.stac.rst', conf.get('STACD_DBUS_NAME') + '.rst',
'org.nvmexpress.staf.debug.rst', conf.get('STAFD_DBUS_NAME') + '.debug.rst',
'org.nvmexpress.staf.rst', conf.get('STAFD_DBUS_NAME') + '.rst',
'stacctl.rst', 'stacctl.rst',
'stacd-index.rst', 'stacd-index.rst',
'stacd.conf.rst', 'stacd.conf.rst',

View file

@ -9,15 +9,15 @@
dbus_conf_dir = datadir / 'dbus-1' / 'system.d' dbus_conf_dir = datadir / 'dbus-1' / 'system.d'
configure_file( configure_file(
input: 'org.nvmexpress.staf.in.conf', input: conf.get('STAFD_DBUS_NAME') + '.in.conf',
output: 'org.nvmexpress.staf.conf', output: conf.get('STAFD_DBUS_NAME') + '.conf',
configuration: conf, configuration: conf,
install_dir: dbus_conf_dir, install_dir: dbus_conf_dir,
) )
configure_file( configure_file(
input: 'org.nvmexpress.stac.in.conf', input: conf.get('STACD_DBUS_NAME') + '.in.conf',
output: 'org.nvmexpress.stac.conf', output: conf.get('STACD_DBUS_NAME') + '.conf',
configuration: conf, configuration: conf,
install_dir: dbus_conf_dir, install_dir: dbus_conf_dir,
) )

View file

@ -9,7 +9,7 @@
project( project(
'nvme-stas', 'nvme-stas',
meson_version: '>= 0.53.0', meson_version: '>= 0.53.0',
version: '2.3-rc3', version: '2.3-rc4',
license: 'Apache-2.0', license: 'Apache-2.0',
default_options: [ default_options: [
'buildtype=release', 'buildtype=release',
@ -78,6 +78,7 @@ conf.set('STAFD_DBUS_NAME', 'org.nvmexpress.staf')
conf.set('STAFD_DBUS_PATH', '/org/nvmexpress/staf') conf.set('STAFD_DBUS_PATH', '/org/nvmexpress/staf')
conf.set('STACD_DBUS_NAME', 'org.nvmexpress.stac') conf.set('STACD_DBUS_NAME', 'org.nvmexpress.stac')
conf.set('STACD_DBUS_PATH', '/org/nvmexpress/stac') conf.set('STACD_DBUS_PATH', '/org/nvmexpress/stac')
conf.set('ETC', etcdir)
#=============================================================================== #===============================================================================
stafd = configure_file( stafd = configure_file(

View file

@ -36,18 +36,25 @@ KERNEL_IFACE_MIN_VERSION = KernelVersion('5.14')
KERNEL_TP8013_MIN_VERSION = KernelVersion('5.16') KERNEL_TP8013_MIN_VERSION = KernelVersion('5.16')
KERNEL_HOSTKEY_MIN_VERSION = KernelVersion('5.20') KERNEL_HOSTKEY_MIN_VERSION = KernelVersion('5.20')
KERNEL_CTRLKEY_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' WELL_KNOWN_DISC_NQN = 'nqn.2014-08.org.nvmexpress.discovery'
PROG_NAME = os.path.basename(sys.argv[0]) PROG_NAME = os.path.basename(sys.argv[0])
NVME_HOSTID = '/etc/nvme/hostid' NVME_HOSTID = '@ETC@/nvme/hostid'
NVME_HOSTNQN = '/etc/nvme/hostnqn' NVME_HOSTNQN = '@ETC@/nvme/hostnqn'
NVME_HOSTKEY = '/etc/nvme/hostkey' NVME_HOSTKEY = '@ETC@/nvme/hostkey'
SYS_CONF_FILE = '/etc/stas/sys.conf' SYS_CONF_FILE = '@ETC@/stas/sys.conf'
STAFD_CONF_FILE = '/etc/stas/stafd.conf' STAFD_CONF_FILE = '@ETC@/stas/stafd.conf'
STACD_CONF_FILE = '/etc/stas/stacd.conf' STACD_CONF_FILE = '@ETC@/stas/stacd.conf'
HAS_NBFT_SUPPORT = hasattr(nvme, 'nbft_get') HAS_NBFT_SUPPORT = hasattr(nvme, 'nbft_get')
NBFT_SYSFS_PATH = "/sys/firmware/acpi/tables" NBFT_SYSFS_PATH = "/sys/firmware/acpi/tables"

View file

@ -6,8 +6,8 @@
# #
# Authors: Martin Belanger <Martin.Belanger@dell.com> # Authors: Martin Belanger <Martin.Belanger@dell.com>
# #
'''This module provides utility functions/classes to provide easier to use '''This module provides utility functions (or classes) that simplify
access to GLib/Gio/Gobject resources. the use of certain GLib/Gio/Gobject functions/resources.
''' '''
import logging import logging
@ -443,7 +443,7 @@ class TcpChecker: # pylint: disable=too-many-instance-attributes
# the GLib context. # the GLib context.
family = socket.AF_INET if self._traddr.version == 4 else socket.AF_INET6 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) 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')) self._native_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, self._host_iface.encode('utf-8'))
# Convert socket.socket() to a Gio.Socket() object # Convert socket.socket() to a Gio.Socket() object

View file

@ -131,66 +131,22 @@ def mac2iface(mac: str): # pylint: disable=too-many-locals
# ****************************************************************************** # ******************************************************************************
def _data_matches_ip(data_family, data, ip): def ip_equal(ip1, ip2):
if data_family == socket.AF_INET: '''Check whther two IP addresses are equal.
try: @param ip1: IPv4Address or IPv6Address object
other_ip = ipaddress.IPv4Address(data) @param ip2: IPv4Address or IPv6Address object
except ValueError: '''
return False if not isinstance(ip1, ipaddress._BaseAddress): # pylint: disable=protected-access
if ip.version == 6: return False
ip = ip.ipv4_mapped if not isinstance(ip2, ipaddress._BaseAddress): # pylint: disable=protected-access
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 False
return other_ip == ip if ip1.version == 4 and ip2.version == 6:
ip2 = ip2.ipv4_mapped
elif ip1.version == 6 and ip2.version == 4:
ip1 = ip1.ipv4_mapped
return ip1 == ip2
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
'''
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)
rta_len = RTA_ALIGN(rta_len) # Round up to multiple of 4
rtattr_indx += rta_len # Move to next rtattr
nlmsg_idx += nlmsg_len # Move to next Netlink message
return ''
# ****************************************************************************** # ******************************************************************************
@ -221,21 +177,21 @@ def net_if_addrs(): # pylint: disable=too-many-locals
source address. source address.
@example: { @example: {
'wlp0s20f3': { 'wlp0s20f3': {
4: ['10.0.0.28'], 4: [IPv4Address('10.0.0.28')],
6: [ 6: [
'fd5e:9a9e:c5bd:0:5509:890c:1848:3843', IPv6Address('fd5e:9a9e:c5bd:0:5509:890c:1848:3843'),
'fd5e:9a9e:c5bd:0:1fd5:e527:8df7:7912', IPv6Address('fd5e:9a9e:c5bd:0:1fd5:e527:8df7:7912'),
'2605:59c8:6128:fb00:c083:1b8:c467:81d2', IPv6Address('2605:59c8:6128:fb00:c083:1b8:c467:81d2'),
'2605:59c8:6128:fb00:e99d:1a02:38e0:ad52', IPv6Address('2605:59c8:6128:fb00:e99d:1a02:38e0:ad52'),
'fe80::d71b:e807:d5ee:7614' IPv6Address('fe80::d71b:e807:d5ee:7614'),
], ],
}, },
'lo': { 'lo': {
4: ['127.0.0.1'], 4: [IPv4Address('127.0.0.1')],
6: ['::1'], 6: [IPv6Address('::1')],
}, },
'docker0': { 'docker0': {
4: ['172.17.0.1'], 4: [IPv4Address('172.17.0.1')],
6: [] 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 '''Get interface for given source address
@param src_addr: The source address @param ifaces: Interface info previously returned by @net_if_addrs()
@type src_addr: str @param src_addr: IPv4Address or IPv6Address object
''' '''
if not src_addr: if not isinstance(src_addr, ipaddress._BaseAddress): # pylint: disable=protected-access
return '' return ''
src_addr = src_addr.split('%')[0] # remove scope-id (if any) for iface, addr_map in ifaces.items():
src_addr = get_ipaddress_obj(src_addr) for addrs in addr_map.values():
return '' if src_addr is None else _iface_of(src_addr) for addr in addrs:
if ip_equal(src_addr, addr):
return iface
return ''

View file

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

View file

@ -31,14 +31,17 @@ class Test(unittest.TestCase):
def test_get_interface(self): def test_get_interface(self):
'''Check that get_interface() returns the right info''' '''Check that get_interface() returns the right info'''
ifaces = iputil.net_if_addrs()
for iface in self.ifaces: for iface in self.ifaces:
for addr_entry in iface['addr_info']: for addr_entry in iface['addr_info']:
addr = ipaddress.ip_address(addr_entry['local']) addr = ipaddress.ip_address(addr_entry['local'])
# Link local addresses may appear on more than one interface and therefore cannot be used. # Link local addresses may appear on more than one interface and therefore cannot be used.
if not addr.is_link_local: if not addr.is_link_local:
self.assertEqual(iface['ifname'], iputil.get_interface(str(addr))) self.assertEqual(iface['ifname'], iputil.get_interface(ifaces, addr))
self.assertEqual('', iputil.get_interface('255.255.255.255')) self.assertEqual('', iputil.get_interface(ifaces, iputil.get_ipaddress_obj('255.255.255.255')))
self.assertEqual('', iputil.get_interface(ifaces, ''))
self.assertEqual('', iputil.get_interface(ifaces, None))
def test_mac2iface(self): def test_mac2iface(self):
for iface in self.ifaces: for iface in self.ifaces:
@ -69,9 +72,7 @@ class Test(unittest.TestCase):
self.assertNotIn(bad_trtype, l2) self.assertNotIn(bad_trtype, l2)
def test__data_matches_ip(self): def test__data_matches_ip(self):
self.assertFalse(iputil._data_matches_ip(None, None, None)) self.assertFalse(iputil.ip_equal(None, None))
self.assertFalse(iputil._data_matches_ip(socket.AF_INET, None, None))
self.assertFalse(iputil._data_matches_ip(socket.AF_INET6, None, None))
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -48,11 +48,12 @@ class Test(unittest.TestCase):
self.assertEqual(nbft_conf.iocs, EXPECTED_IOCS) self.assertEqual(nbft_conf.iocs, EXPECTED_IOCS)
def test_dir_without_nbft_files(self): def test_dir_without_nbft_files(self):
conf.NbftConf.destroy() # Make sure singleton does not exist if hasattr(self, 'assertNoLogs'): # assertNoLogs only in Python 3.10 or later
with self.assertNoLogs(logger=logging.getLogger(), level='DEBUG'): conf.NbftConf.destroy() # Make sure singleton does not exist
nbft_conf = conf.NbftConf('/tmp') with self.assertNoLogs(logger=logging.getLogger(), level='DEBUG'):
self.assertEqual(nbft_conf.dcs, []) nbft_conf = conf.NbftConf('/tmp')
self.assertEqual(nbft_conf.iocs, []) self.assertEqual(nbft_conf.dcs, [])
self.assertEqual(nbft_conf.iocs, [])
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -2,7 +2,7 @@
import os import os
import logging import logging
import unittest import unittest
from staslib import conf, log from staslib import defs, conf, log
from pyfakefs.fake_filesystem_unittest import TestCase from pyfakefs.fake_filesystem_unittest import TestCase
@ -10,7 +10,7 @@ class TestStandardNvmeFabricsFile(unittest.TestCase):
def test_regular_user(self): def test_regular_user(self):
conf.NvmeOptions.destroy() # Make sure singleton does not exist conf.NvmeOptions.destroy() # Make sure singleton does not exist
if os.path.exists('/dev/nvme-fabrics'): if os.path.exists('/dev/nvme-fabrics'):
if os.geteuid() != 0: if os.geteuid() != 0 and defs.KERNEL_VERSION < defs.KERNEL_ALL_MIN_VERSION:
with self.assertRaises(PermissionError): with self.assertRaises(PermissionError):
nvme_options = conf.NvmeOptions() nvme_options = conf.NvmeOptions()
else: else:

View file

@ -3,7 +3,6 @@ import json
import shutil import shutil
import logging import logging
import unittest import unittest
import ipaddress
import subprocess import subprocess
from staslib import defs, iputil, log, trid, udev from staslib import defs, iputil, log, trid, udev
@ -294,6 +293,7 @@ class Test(unittest.TestCase):
self.assertTrue(udev.UDEV.is_ioc_device(device)) self.assertTrue(udev.UDEV.is_ioc_device(device))
def test__cid_matches_tid(self): def test__cid_matches_tid(self):
ifaces = iputil.net_if_addrs()
for ifname, addrs in self.ifaces.items(): for ifname, addrs in self.ifaces.items():
############################################## ##############################################
# IPV4 # IPV4
@ -321,10 +321,14 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
for case_id, tid, match in get_tids_to_test(4, src_ipv4, ifname): for case_id, tid, match in get_tids_to_test(4, src_ipv4, ifname):
self.assertEqual(match, udev.UDEV._cid_matches_tid(tid, cid), msg=f'Test Case {case_id} failed') self.assertEqual(
match, udev.UDEV._cid_matches_tid(tid, cid, ifaces), msg=f'Test Case {case_id} failed'
)
if case_id != 8: if case_id != 8:
self.assertEqual( self.assertEqual(
match, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case {case_id} failed' match,
udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces),
msg=f'Legacy Test Case {case_id} failed',
) )
cid_legacy = { cid_legacy = {
@ -347,7 +351,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case A4.1 failed') self.assertEqual(
True, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case A4.1 failed'
)
cid_legacy = { cid_legacy = {
'transport': 'tcp', 'transport': 'tcp',
@ -368,9 +374,13 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case A4.2 failed')
self.assertEqual( self.assertEqual(
True, udev.UDEV._cid_matches_tcp_tid_legacy(tid, cid_legacy), msg=f'Legacy Test Case A4.3 failed' True, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case A4.2 failed'
)
self.assertEqual(
True,
udev.UDEV._cid_matches_tcp_tid_legacy(tid, cid_legacy, ifaces),
msg=f'Legacy Test Case A4.3 failed',
) )
cid_legacy = { cid_legacy = {
@ -393,7 +403,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case B4 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case B4 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -405,7 +417,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case C4 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case C4 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -417,7 +431,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case D4 failed') self.assertEqual(
True, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case D4 failed'
)
cid_legacy = { cid_legacy = {
'transport': 'tcp', 'transport': 'tcp',
@ -440,7 +456,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case E4 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case E4 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -452,7 +470,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case F4 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case F4 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -467,7 +487,9 @@ class Test(unittest.TestCase):
match = len(ipv4_addrs) == 1 and iputil.get_ipaddress_obj( match = len(ipv4_addrs) == 1 and iputil.get_ipaddress_obj(
ipv4_addrs[0], ipv4_mapped_convert=True ipv4_addrs[0], ipv4_mapped_convert=True
) == iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True) ) == iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True)
self.assertEqual(match, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case G4 failed') self.assertEqual(
match, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case G4 failed'
)
############################################## ##############################################
# IPV6 # IPV6
@ -495,9 +517,13 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
for case_id, tid, match in get_tids_to_test(6, src_ipv6, ifname): for case_id, tid, match in get_tids_to_test(6, src_ipv6, ifname):
self.assertEqual(match, udev.UDEV._cid_matches_tid(tid, cid), msg=f'Test Case {case_id} failed')
self.assertEqual( self.assertEqual(
match, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case {case_id} failed' match, udev.UDEV._cid_matches_tid(tid, cid, ifaces), msg=f'Test Case {case_id} failed'
)
self.assertEqual(
match,
udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces),
msg=f'Legacy Test Case {case_id} failed',
) )
cid_legacy = { cid_legacy = {
@ -520,7 +546,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case A6.1 failed') self.assertEqual(
True, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case A6.1 failed'
)
cid_legacy = { cid_legacy = {
'transport': 'tcp', 'transport': 'tcp',
@ -541,9 +569,13 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case A6.2 failed')
self.assertEqual( self.assertEqual(
True, udev.UDEV._cid_matches_tcp_tid_legacy(tid, cid_legacy), msg=f'Legacy Test Case A6.3 failed' True, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case A6.2 failed'
)
self.assertEqual(
True,
udev.UDEV._cid_matches_tcp_tid_legacy(tid, cid_legacy, ifaces),
msg=f'Legacy Test Case A6.3 failed',
) )
cid_legacy = { cid_legacy = {
@ -566,7 +598,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case B6 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case B6 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -578,7 +612,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case C6 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case C6 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -590,7 +626,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case D6 failed') self.assertEqual(
True, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case D6 failed'
)
cid_legacy = { cid_legacy = {
'transport': 'tcp', 'transport': 'tcp',
@ -613,7 +651,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case E6 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case E6 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -625,7 +665,9 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case F6 failed') self.assertEqual(
False, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case F6 failed'
)
tid = trid.TID( tid = trid.TID(
{ {
@ -640,7 +682,9 @@ class Test(unittest.TestCase):
match = len(ipv6_addrs) == 1 and iputil.get_ipaddress_obj( match = len(ipv6_addrs) == 1 and iputil.get_ipaddress_obj(
ipv6_addrs[0], ipv4_mapped_convert=True ipv6_addrs[0], ipv4_mapped_convert=True
) == iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True) ) == iputil.get_ipaddress_obj(tid.host_traddr, ipv4_mapped_convert=True)
self.assertEqual(match, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case G6 failed') self.assertEqual(
match, udev.UDEV._cid_matches_tid(tid, cid_legacy, ifaces), msg=f'Legacy Test Case G6 failed'
)
############################################## ##############################################
# FC # FC
@ -664,7 +708,7 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid), msg=f'Test Case FC-1 failed') self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid, ifaces), msg=f'Test Case FC-1 failed')
tid = trid.TID( tid = trid.TID(
{ {
@ -676,7 +720,7 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid), msg=f'Test Case FC-2 failed') self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid, ifaces), msg=f'Test Case FC-2 failed')
############################################## ##############################################
# RDMA # RDMA
@ -700,7 +744,7 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid), msg=f'Test Case RDMA-1 failed') self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid, ifaces), msg=f'Test Case RDMA-1 failed')
tid = trid.TID( tid = trid.TID(
{ {
@ -712,7 +756,7 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid), msg=f'Test Case RDMA-2 failed') self.assertEqual(False, udev.UDEV._cid_matches_tid(tid, cid, ifaces), msg=f'Test Case RDMA-2 failed')
tid = trid.TID( tid = trid.TID(
{ {
@ -723,7 +767,7 @@ class Test(unittest.TestCase):
'host-nqn': '', 'host-nqn': '',
} }
) )
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid), msg=f'Test Case RDMA-3 failed') self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid, ifaces), msg=f'Test Case RDMA-3 failed')
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -26,7 +26,7 @@ configure_file(
input: 'stas-config@.service', input: 'stas-config@.service',
output: 'stas-config@.service', output: 'stas-config@.service',
install_dir: sd_unit_dir, install_dir: sd_unit_dir,
copy: true, configuration: conf,
) )
configure_file( configure_file(

View file

@ -5,13 +5,13 @@
# This file is part of NVMe STorage Appliance Services (nvme-stas). # This file is part of NVMe STorage Appliance Services (nvme-stas).
# #
[Unit] [Unit]
Description=nvme-stas /etc/nvme/%i auto-generation Description=nvme-stas @ETC@/nvme/%i auto-generation
Documentation=man:stas-config@.service(8) Documentation=man:stas-config@.service(8)
ConditionFileNotEmpty=|!/etc/nvme/%i ConditionFileNotEmpty=|!@ETC@/nvme/%i
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/usr/bin/stasadm %i -f /etc/nvme/%i ExecStart=/usr/bin/stasadm %i -f @ETC@/nvme/%i
[Install] [Install]
WantedBy=stas-config.target WantedBy=stas-config.target

View file

@ -0,0 +1,420 @@
# Config file format: Python, i.e. dict(), list(), int, str, etc...
# port ids (id) are integers 0...N
# namespaces are integers 0..N
# subsysnqn can be integers or strings
{
'ports': [
{
'id': 1,
'adrfam': 'ipv6',
'traddr': '::',
#'adrfam': 'ipv4',
#'traddr': '0.0.0.0',
'trsvcid': 8009,
'trtype': 'tcp',
}
],
'subsystems': [
{
'subsysnqn': 'subsystem1',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem2',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem3',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem4',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem5',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem6',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem7',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem8',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem9',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem10',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem11',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem12',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem13',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem14',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem15',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem16',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem17',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem18',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem19',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem20',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem21',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem22',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem23',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem24',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem25',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem26',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem27',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem28',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem29',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem30',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem31',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem32',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem33',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem34',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem35',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem36',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem37',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem38',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem39',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem40',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem41',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem42',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem43',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem44',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem45',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem46',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem47',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem48',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem49',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem50',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem51',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem52',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem53',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem54',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem55',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem56',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem57',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem58',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem59',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem60',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem61',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem62',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem63',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem64',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem65',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem66',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem67',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem68',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem69',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem70',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem71',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem72',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem73',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem74',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem75',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem76',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem77',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem78',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem79',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
{
'subsysnqn': 'subsystem80',
'port': 1,
'namespaces': [1, 2, 3, 4, 5, 6],
},
],
}