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 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: |
sudo apt-get install --yes --quiet swig libjson-c-dev
meson subprojects download
meson setup .build subprojects/libnvme -Dlibdbus=disabled -Dopenssl=disabled -Dbuildtype=release -Dprefix=/usr -Dpython=enabled
ninja -C .build
sudo meson install -C .build
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
- name: "CONFIG: PYTHONPATH"
run: |
@ -55,7 +57,7 @@ jobs:
with:
action: test
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
# Preserve meson's log file on failure
@ -67,9 +69,11 @@ jobs:
- name: "Generate coverage report"
run: |
python3 -m pip install pytest
python3 -m pip install pytest-cov
PYTHONPATH=.build:.build/subprojects/libnvme:/usr/lib/python3/dist-packages/ pytest --cov=./staslib --cov-report=xml test/test-*.py
python3 -m pip install --upgrade pytest
python3 -m pip install --upgrade pytest-cov
echo $( pwd )
cp -r .build/staslib/* ./staslib/.
pytest --cov=./staslib --cov-report=xml test/test-*.py
- uses: codecov/codecov-action@v3
with:

View file

@ -7,6 +7,7 @@ New features:
- 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 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:

View file

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

View file

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

View file

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

View file

@ -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"

View file

@ -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

View file

@ -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)
if not isinstance(ip1, ipaddress._BaseAddress): # pylint: disable=protected-access
return False
if not isinstance(ip2, ipaddress._BaseAddress): # pylint: disable=protected-access
return False
nlmsghdr = nlmsg[nlmsg_idx : nlmsg_idx + NLMSG_HDRLEN]
nlmsg_len, nlmsg_type, _, _, _ = struct.unpack('<LHHLL', nlmsghdr)
if ip1.version == 4 and ip2.version == 6:
ip2 = ip2.ipv4_mapped
elif ip1.version == 6 and ip2.version == 4:
ip1 = ip1.ipv4_mapped
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 ''
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 ''

View file

@ -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.
# 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)
if cid_host_traddr:
if tid_host_traddr != cid_host_traddr:
return False
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() - cid=%s, tid=%s - Not enough info. Assume "match" but this could be wrong.',
'Udev._cid_matches_tcp_tid_legacy() - [1] 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
# 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:
# 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
# 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))
else:
if cid_host_traddr:
connection_iface = iputil.get_interface(ifaces, cid_host_traddr)
if tid.host_iface != connection_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
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,14 +318,17 @@ 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 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):
if not self._cid_matches_tid(tid, cid, ifaces):
continue
return device
@ -345,14 +340,17 @@ 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 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):
if not self._cid_matches_tid(tid, cid, ifaces):
continue
return device
@ -364,26 +362,27 @@ class Udev:
@return A list of pyudev.device._device.Device objects
'''
tids = []
for device in self._context.list_devices(subsystem='nvme'):
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
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)
self.__handle_events()
except EnvironmentError as ex:
device = None
# This event seems to happen in bursts. So, let's suppress
# 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()
@ -392,35 +391,29 @@ class Udev:
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)
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 __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.
def _device_event(self, device, event_count):
action_cbacks = self._action_event_registry.get(device.action, set())
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._device_event() - %-8s %-6s %-8s %s',
f'{device.sys_name}:',
'Udev.__handle_events() - %-7s %-6s %2s:%s',
device.sys_name,
device.action,
f'{event_count:2}:{device.sequence_number}',
self.__cback_names(action_cbacks, device_cback),
event_count,
device.sequence_number,
)
if action_cbacks:
for action_cback in action_cbacks:
GLib.idle_add(action_cback, device)
@ -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)

View file

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

View file

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

View file

@ -2,7 +2,7 @@
import os
import logging
import unittest
from staslib import conf, log
from staslib import defs, conf, log
from pyfakefs.fake_filesystem_unittest import TestCase
@ -10,7 +10,7 @@ class TestStandardNvmeFabricsFile(unittest.TestCase):
def test_regular_user(self):
conf.NvmeOptions.destroy() # Make sure singleton does not exist
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):
nvme_options = conf.NvmeOptions()
else:

View file

@ -3,7 +3,6 @@ import json
import shutil
import logging
import unittest
import ipaddress
import subprocess
from staslib import defs, iputil, log, trid, udev
@ -294,6 +293,7 @@ class Test(unittest.TestCase):
self.assertTrue(udev.UDEV.is_ioc_device(device))
def test__cid_matches_tid(self):
ifaces = iputil.net_if_addrs()
for ifname, addrs in self.ifaces.items():
##############################################
# IPV4
@ -321,10 +321,14 @@ class Test(unittest.TestCase):
'host-nqn': '',
}
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:
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 = {
@ -347,7 +351,9 @@ class Test(unittest.TestCase):
'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 = {
'transport': 'tcp',
@ -368,9 +374,13 @@ class Test(unittest.TestCase):
'host-nqn': '',
}
)
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case A4.2 failed')
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 = {
@ -393,7 +403,9 @@ class Test(unittest.TestCase):
'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(
{
@ -405,7 +417,9 @@ class Test(unittest.TestCase):
'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(
{
@ -417,7 +431,9 @@ class Test(unittest.TestCase):
'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 = {
'transport': 'tcp',
@ -440,7 +456,9 @@ class Test(unittest.TestCase):
'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(
{
@ -452,7 +470,9 @@ class Test(unittest.TestCase):
'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(
{
@ -467,7 +487,9 @@ class Test(unittest.TestCase):
match = len(ipv4_addrs) == 1 and iputil.get_ipaddress_obj(
ipv4_addrs[0], 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
@ -495,9 +517,13 @@ class Test(unittest.TestCase):
'host-nqn': '',
}
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(
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 = {
@ -520,7 +546,9 @@ class Test(unittest.TestCase):
'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 = {
'transport': 'tcp',
@ -541,9 +569,13 @@ class Test(unittest.TestCase):
'host-nqn': '',
}
)
self.assertEqual(True, udev.UDEV._cid_matches_tid(tid, cid_legacy), msg=f'Legacy Test Case A6.2 failed')
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 = {
@ -566,7 +598,9 @@ class Test(unittest.TestCase):
'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(
{
@ -578,7 +612,9 @@ class Test(unittest.TestCase):
'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(
{
@ -590,7 +626,9 @@ class Test(unittest.TestCase):
'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 = {
'transport': 'tcp',
@ -613,7 +651,9 @@ class Test(unittest.TestCase):
'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(
{
@ -625,7 +665,9 @@ class Test(unittest.TestCase):
'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(
{
@ -640,7 +682,9 @@ class Test(unittest.TestCase):
match = len(ipv6_addrs) == 1 and iputil.get_ipaddress_obj(
ipv6_addrs[0], 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
@ -664,7 +708,7 @@ class Test(unittest.TestCase):
'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(
{
@ -676,7 +720,7 @@ class Test(unittest.TestCase):
'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
@ -700,7 +744,7 @@ class Test(unittest.TestCase):
'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(
{
@ -712,7 +756,7 @@ class Test(unittest.TestCase):
'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(
{
@ -723,7 +767,7 @@ class Test(unittest.TestCase):
'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__':

View file

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

View file

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