f37345
From 9383855c8a15e6d7c4033cd8d7ae8310b462d166 Mon Sep 17 00:00:00 2001
f37345
From: Vojtech Trefny <vtrefny@redhat.com>
f37345
Date: Tue, 18 Oct 2022 10:38:00 +0200
f37345
Subject: [PATCH 1/3] Add a basic support for NVMe and NVMe Fabrics devices
f37345
f37345
This adds two new device types: NVMeNamespaceDevice and
f37345
NVMeFabricsNamespaceDevice mostly to allow to differentiate
f37345
between "local" and "remote" NVMe devices. The new libblockdev
f37345
NVMe plugin is required for full functionality.
f37345
---
f37345
 blivet/__init__.py                   |   6 +-
f37345
 blivet/devices/__init__.py           |   2 +-
f37345
 blivet/devices/disk.py               | 101 ++++++++++++++++++++++
f37345
 blivet/devices/lib.py                |   1 +
f37345
 blivet/populator/helpers/__init__.py |   2 +-
f37345
 blivet/populator/helpers/disk.py     |  64 ++++++++++++++
f37345
 blivet/udev.py                       |  33 +++++++
f37345
 blivet/util.py                       |   9 ++
f37345
 tests/unit_tests/populator_test.py   | 124 +++++++++++++++++++++++++++
f37345
 9 files changed, 339 insertions(+), 3 deletions(-)
f37345
f37345
diff --git a/blivet/__init__.py b/blivet/__init__.py
f37345
index bbc7ea3a..3b9e659e 100644
f37345
--- a/blivet/__init__.py
f37345
+++ b/blivet/__init__.py
f37345
@@ -67,6 +67,10 @@ if arch.is_s390():
f37345
 else:
f37345
     _REQUESTED_PLUGIN_NAMES = set(("swap", "crypto", "loop", "mdraid", "mpath", "dm", "nvdimm"))
f37345
 
f37345
+# nvme plugin is not generally available
f37345
+if hasattr(blockdev.Plugin, "NVME"):
f37345
+    _REQUESTED_PLUGIN_NAMES.add("nvme")
f37345
+
f37345
 _requested_plugins = blockdev.plugin_specs_from_names(_REQUESTED_PLUGIN_NAMES)
f37345
 # XXX force non-dbus LVM plugin
f37345
 lvm_plugin = blockdev.PluginSpec()
f37345
@@ -74,7 +78,7 @@ lvm_plugin.name = blockdev.Plugin.LVM
f37345
 lvm_plugin.so_name = "libbd_lvm.so.2"
f37345
 _requested_plugins.append(lvm_plugin)
f37345
 try:
f37345
-    # do not check for dependencies during libblockdev initializtion, do runtime
f37345
+    # do not check for dependencies during libblockdev initialization, do runtime
f37345
     # checks instead
f37345
     blockdev.switch_init_checks(False)
f37345
     succ_, avail_plugs = blockdev.try_reinit(require_plugins=_requested_plugins, reload=False, log_func=log_bd_message)
f37345
diff --git a/blivet/devices/__init__.py b/blivet/devices/__init__.py
f37345
index 8bb0a979..4d16466e 100644
f37345
--- a/blivet/devices/__init__.py
f37345
+++ b/blivet/devices/__init__.py
f37345
@@ -22,7 +22,7 @@
f37345
 from .lib import device_path_to_name, device_name_to_disk_by_path, ParentList
f37345
 from .device import Device
f37345
 from .storage import StorageDevice
f37345
-from .disk import DiskDevice, DiskFile, DMRaidArrayDevice, MultipathDevice, iScsiDiskDevice, FcoeDiskDevice, DASDDevice, ZFCPDiskDevice, NVDIMMNamespaceDevice
f37345
+from .disk import DiskDevice, DiskFile, DMRaidArrayDevice, MultipathDevice, iScsiDiskDevice, FcoeDiskDevice, DASDDevice, ZFCPDiskDevice, NVDIMMNamespaceDevice, NVMeNamespaceDevice, NVMeFabricsNamespaceDevice
f37345
 from .partition import PartitionDevice
f37345
 from .dm import DMDevice, DMLinearDevice, DMCryptDevice, DMIntegrityDevice, DM_MAJORS
f37345
 from .luks import LUKSDevice, IntegrityDevice
f37345
diff --git a/blivet/devices/disk.py b/blivet/devices/disk.py
f37345
index bc4a1b5e..b5e25939 100644
f37345
--- a/blivet/devices/disk.py
f37345
+++ b/blivet/devices/disk.py
f37345
@@ -22,10 +22,13 @@
f37345
 
f37345
 import gi
f37345
 gi.require_version("BlockDev", "2.0")
f37345
+gi.require_version("GLib", "2.0")
f37345
 
f37345
 from gi.repository import BlockDev as blockdev
f37345
+from gi.repository import GLib
f37345
 
f37345
 import os
f37345
+from collections import namedtuple
f37345
 
f37345
 from .. import errors
f37345
 from .. import util
f37345
@@ -725,3 +728,101 @@ class NVDIMMNamespaceDevice(DiskDevice):
f37345
     @property
f37345
     def sector_size(self):
f37345
         return self._sector_size
f37345
+
f37345
+
f37345
+NVMeController = namedtuple("NVMeController", ["name", "serial", "nvme_ver", "id", "subsysnqn"])
f37345
+
f37345
+
f37345
+class NVMeNamespaceDevice(DiskDevice):
f37345
+
f37345
+    """ NVMe namespace """
f37345
+    _type = "nvme"
f37345
+    _packages = ["nvme-cli"]
f37345
+
f37345
+    def __init__(self, device, **kwargs):
f37345
+        """
f37345
+            :param name: the device name (generally a device node's basename)
f37345
+            :type name: str
f37345
+            :keyword exists: does this device exist?
f37345
+            :type exists: bool
f37345
+            :keyword size: the device's size
f37345
+            :type size: :class:`~.size.Size`
f37345
+            :keyword parents: a list of parent devices
f37345
+            :type parents: list of :class:`StorageDevice`
f37345
+            :keyword format: this device's formatting
f37345
+            :type format: :class:`~.formats.DeviceFormat` or a subclass of it
f37345
+            :keyword nsid: namespace ID
f37345
+            :type nsid: int
f37345
+        """
f37345
+        self.nsid = kwargs.pop("nsid", 0)
f37345
+
f37345
+        DiskDevice.__init__(self, device, **kwargs)
f37345
+
f37345
+        self._clear_local_tags()
f37345
+        self.tags.add(Tags.local)
f37345
+        self.tags.add(Tags.nvme)
f37345
+
f37345
+        self._controllers = None
f37345
+
f37345
+    @property
f37345
+    def controllers(self):
f37345
+        if self._controllers is not None:
f37345
+            return self._controllers
f37345
+
f37345
+        self._controllers = []
f37345
+        if not hasattr(blockdev.Plugin, "NVME"):
f37345
+            # the nvme plugin is not generally available
f37345
+            log.debug("Failed to get controllers for %s: libblockdev NVME plugin is not available", self.name)
f37345
+            return self._controllers
f37345
+
f37345
+        try:
f37345
+            controllers = blockdev.nvme_find_ctrls_for_ns(self.sysfs_path)
f37345
+        except GLib.GError as err:
f37345
+            log.debug("Failed to get controllers for %s: %s", self.name, str(err))
f37345
+            return self._controllers
f37345
+
f37345
+        for controller in controllers:
f37345
+            try:
f37345
+                cpath = util.get_path_by_sysfs_path(controller, "char")
f37345
+            except RuntimeError as err:
f37345
+                log.debug("Failed to find controller %s: %s", controller, str(err))
f37345
+                continue
f37345
+            try:
f37345
+                cinfo = blockdev.nvme_get_controller_info(cpath)
f37345
+            except GLib.GError as err:
f37345
+                log.debug("Failed to get controller info for %s: %s", cpath, str(err))
f37345
+                continue
f37345
+            self._controllers.append(NVMeController(name=os.path.basename(cpath),
f37345
+                                                    serial=cinfo.serial_number,
f37345
+                                                    nvme_ver=cinfo.nvme_ver,
f37345
+                                                    id=cinfo.ctrl_id,
f37345
+                                                    subsysnqn=cinfo.subsysnqn))
f37345
+
f37345
+        return self._controllers
f37345
+
f37345
+
f37345
+class NVMeFabricsNamespaceDevice(NVMeNamespaceDevice, NetworkStorageDevice):
f37345
+
f37345
+    """ NVMe fabrics namespace """
f37345
+    _type = "nvme-fabrics"
f37345
+    _packages = ["nvme-cli"]
f37345
+
f37345
+    def __init__(self, device, **kwargs):
f37345
+        """
f37345
+            :param name: the device name (generally a device node's basename)
f37345
+            :type name: str
f37345
+            :keyword exists: does this device exist?
f37345
+            :type exists: bool
f37345
+            :keyword size: the device's size
f37345
+            :type size: :class:`~.size.Size`
f37345
+            :keyword parents: a list of parent devices
f37345
+            :type parents: list of :class:`StorageDevice`
f37345
+            :keyword format: this device's formatting
f37345
+            :type format: :class:`~.formats.DeviceFormat` or a subclass of it
f37345
+        """
f37345
+        NVMeNamespaceDevice.__init__(self, device, **kwargs)
f37345
+        NetworkStorageDevice.__init__(self)
f37345
+
f37345
+        self._clear_local_tags()
f37345
+        self.tags.add(Tags.remote)
f37345
+        self.tags.add(Tags.nvme)
f37345
diff --git a/blivet/devices/lib.py b/blivet/devices/lib.py
f37345
index 1bda0bab..b3c4c5b0 100644
f37345
--- a/blivet/devices/lib.py
f37345
+++ b/blivet/devices/lib.py
f37345
@@ -32,6 +32,7 @@ class Tags(str, Enum):
f37345
     """Tags that describe various classes of disk."""
f37345
     local = 'local'
f37345
     nvdimm = 'nvdimm'
f37345
+    nvme = 'nvme'
f37345
     remote = 'remote'
f37345
     removable = 'removable'
f37345
     ssd = 'ssd'
f37345
diff --git a/blivet/populator/helpers/__init__.py b/blivet/populator/helpers/__init__.py
f37345
index c5ac412f..50ab4de8 100644
f37345
--- a/blivet/populator/helpers/__init__.py
f37345
+++ b/blivet/populator/helpers/__init__.py
f37345
@@ -6,7 +6,7 @@ from .formatpopulator import FormatPopulator
f37345
 
f37345
 from .btrfs import BTRFSFormatPopulator
f37345
 from .boot import AppleBootFormatPopulator, EFIFormatPopulator, MacEFIFormatPopulator
f37345
-from .disk import DiskDevicePopulator, iScsiDevicePopulator, FCoEDevicePopulator, MDBiosRaidDevicePopulator, DASDDevicePopulator, ZFCPDevicePopulator, NVDIMMNamespaceDevicePopulator
f37345
+from .disk import DiskDevicePopulator, iScsiDevicePopulator, FCoEDevicePopulator, MDBiosRaidDevicePopulator, DASDDevicePopulator, ZFCPDevicePopulator, NVDIMMNamespaceDevicePopulator, NVMeNamespaceDevicePopulator, NVMeFabricsNamespaceDevicePopulator
f37345
 from .disklabel import DiskLabelFormatPopulator
f37345
 from .dm import DMDevicePopulator
f37345
 from .dmraid import DMRaidFormatPopulator
f37345
diff --git a/blivet/populator/helpers/disk.py b/blivet/populator/helpers/disk.py
f37345
index 9db7b810..9ed1eebe 100644
f37345
--- a/blivet/populator/helpers/disk.py
f37345
+++ b/blivet/populator/helpers/disk.py
f37345
@@ -22,13 +22,16 @@
f37345
 
f37345
 import gi
f37345
 gi.require_version("BlockDev", "2.0")
f37345
+gi.require_version("GLib", "2.0")
f37345
 
f37345
 from gi.repository import BlockDev as blockdev
f37345
+from gi.repository import GLib
f37345
 
f37345
 from ... import udev
f37345
 from ... import util
f37345
 from ...devices import DASDDevice, DiskDevice, FcoeDiskDevice, iScsiDiskDevice
f37345
 from ...devices import MDBiosRaidArrayDevice, ZFCPDiskDevice, NVDIMMNamespaceDevice
f37345
+from ...devices import NVMeNamespaceDevice, NVMeFabricsNamespaceDevice
f37345
 from ...devices import device_path_to_name
f37345
 from ...storage_log import log_method_call
f37345
 from .devicepopulator import DevicePopulator
f37345
@@ -251,3 +254,64 @@ class NVDIMMNamespaceDevicePopulator(DiskDevicePopulator):
f37345
 
f37345
         log.info("%s is an NVDIMM namespace device", udev.device_get_name(self.data))
f37345
         return kwargs
f37345
+
f37345
+
f37345
+class NVMeNamespaceDevicePopulator(DiskDevicePopulator):
f37345
+    priority = 20
f37345
+
f37345
+    _device_class = NVMeNamespaceDevice
f37345
+
f37345
+    @classmethod
f37345
+    def match(cls, data):
f37345
+        return (super(NVMeNamespaceDevicePopulator, NVMeNamespaceDevicePopulator).match(data) and
f37345
+                udev.device_is_nvme_namespace(data) and not udev.device_is_nvme_fabrics(data))
f37345
+
f37345
+    def _get_kwargs(self):
f37345
+        kwargs = super(NVMeNamespaceDevicePopulator, self)._get_kwargs()
f37345
+
f37345
+        log.info("%s is an NVMe local namespace device", udev.device_get_name(self.data))
f37345
+
f37345
+        if not hasattr(blockdev.Plugin, "NVME"):
f37345
+            # the nvme plugin is not generally available
f37345
+            return kwargs
f37345
+
f37345
+        path = udev.device_get_devname(self.data)
f37345
+        try:
f37345
+            ninfo = blockdev.nvme_get_namespace_info(path)
f37345
+        except GLib.GError as err:
f37345
+            log.debug("Failed to get namespace info for %s: %s", path, str(err))
f37345
+        else:
f37345
+            kwargs["nsid"] = ninfo.nsid
f37345
+
f37345
+        log.info("%s is an NVMe local namespace device", udev.device_get_name(self.data))
f37345
+        return kwargs
f37345
+
f37345
+
f37345
+class NVMeFabricsNamespaceDevicePopulator(DiskDevicePopulator):
f37345
+    priority = 20
f37345
+
f37345
+    _device_class = NVMeFabricsNamespaceDevice
f37345
+
f37345
+    @classmethod
f37345
+    def match(cls, data):
f37345
+        return (super(NVMeFabricsNamespaceDevicePopulator, NVMeFabricsNamespaceDevicePopulator).match(data) and
f37345
+                udev.device_is_nvme_namespace(data) and udev.device_is_nvme_fabrics(data))
f37345
+
f37345
+    def _get_kwargs(self):
f37345
+        kwargs = super(NVMeFabricsNamespaceDevicePopulator, self)._get_kwargs()
f37345
+
f37345
+        log.info("%s is an NVMe fabrics namespace device", udev.device_get_name(self.data))
f37345
+
f37345
+        if not hasattr(blockdev.Plugin, "NVME"):
f37345
+            # the nvme plugin is not generally available
f37345
+            return kwargs
f37345
+
f37345
+        path = udev.device_get_devname(self.data)
f37345
+        try:
f37345
+            ninfo = blockdev.nvme_get_namespace_info(path)
f37345
+        except GLib.GError as err:
f37345
+            log.debug("Failed to get namespace info for %s: %s", path, str(err))
f37345
+        else:
f37345
+            kwargs["nsid"] = ninfo.nsid
f37345
+
f37345
+        return kwargs
f37345
diff --git a/blivet/udev.py b/blivet/udev.py
f37345
index efbc53d6..533a1edc 100644
f37345
--- a/blivet/udev.py
f37345
+++ b/blivet/udev.py
f37345
@@ -1023,6 +1023,39 @@ def device_is_nvdimm_namespace(info):
f37345
     return ninfo is not None
f37345
 
f37345
 
f37345
+def device_is_nvme_namespace(info):
f37345
+    if info.get("DEVTYPE") != "disk":
f37345
+        return False
f37345
+
f37345
+    if not info.get("SYS_PATH"):
f37345
+        return False
f37345
+
f37345
+    device = pyudev.Devices.from_sys_path(global_udev, info.get("SYS_PATH"))
f37345
+    while device:
f37345
+        if device.subsystem and device.subsystem.startswith("nvme"):
f37345
+            return True
f37345
+        device = device.parent
f37345
+
f37345
+    return False
f37345
+
f37345
+
f37345
+def device_is_nvme_fabrics(info):
f37345
+    if not device_is_nvme_namespace(info):
f37345
+        return False
f37345
+
f37345
+    if not hasattr(blockdev.Plugin, "NVME") or not blockdev.is_plugin_available(blockdev.Plugin.NVME):  # pylint: disable=no-member
f37345
+        # nvme plugin is not available -- even if this is an nvme fabrics device we
f37345
+        # don't have tools to work with it, so we should pretend it's just a normal nvme
f37345
+        return False
f37345
+
f37345
+    controllers = blockdev.nvme_find_ctrls_for_ns(info.get("SYS_PATH", ""))
f37345
+    if not controllers:
f37345
+        return False
f37345
+
f37345
+    transport = util.get_sysfs_attr(controllers[0], "transport")
f37345
+    return transport in ("rdma", "fc", "tcp", "loop")
f37345
+
f37345
+
f37345
 def device_is_hidden(info):
f37345
     sysfs_path = device_get_sysfs_path(info)
f37345
     hidden = util.get_sysfs_attr(sysfs_path, "hidden")
f37345
diff --git a/blivet/util.py b/blivet/util.py
f37345
index 0e578aea..3040ee5a 100644
f37345
--- a/blivet/util.py
f37345
+++ b/blivet/util.py
f37345
@@ -432,6 +432,15 @@ def get_sysfs_path_by_name(dev_node, class_name="block"):
f37345
                            "for '%s' (it is not at '%s')" % (dev_node, dev_path))
f37345
 
f37345
 
f37345
+def get_path_by_sysfs_path(sysfs_path, dev_type="block"):
f37345
+    """ Return device path for a given device sysfs path. """
f37345
+
f37345
+    dev = get_sysfs_attr(sysfs_path, "dev")
f37345
+    if not dev or not os.path.exists("/dev/%s/%s" % (dev_type, dev)):
f37345
+        raise RuntimeError("get_path_by_sysfs_path: Could not find device for %s" % sysfs_path)
f37345
+    return os.path.realpath("/dev/%s/%s" % (dev_type, dev))
f37345
+
f37345
+
f37345
 def get_cow_sysfs_path(dev_path, dev_sysfsPath):
f37345
     """ Return sysfs path of cow device for a given device.
f37345
     """
f37345
diff --git a/tests/unit_tests/populator_test.py b/tests/unit_tests/populator_test.py
f37345
index 369fe878..1ee29b57 100644
f37345
--- a/tests/unit_tests/populator_test.py
f37345
+++ b/tests/unit_tests/populator_test.py
f37345
@@ -13,6 +13,7 @@ from gi.repository import BlockDev as blockdev
f37345
 from blivet.devices import DiskDevice, DMDevice, FileDevice, LoopDevice
f37345
 from blivet.devices import MDRaidArrayDevice, MultipathDevice, OpticalDevice
f37345
 from blivet.devices import PartitionDevice, StorageDevice, NVDIMMNamespaceDevice
f37345
+from blivet.devices import NVMeNamespaceDevice, NVMeFabricsNamespaceDevice
f37345
 from blivet.devicelibs import lvm
f37345
 from blivet.devicetree import DeviceTree
f37345
 from blivet.formats import get_device_format_class, get_format, DeviceFormat
f37345
@@ -21,6 +22,7 @@ from blivet.populator.helpers import DiskDevicePopulator, DMDevicePopulator, Loo
f37345
 from blivet.populator.helpers import LVMDevicePopulator, MDDevicePopulator, MultipathDevicePopulator
f37345
 from blivet.populator.helpers import OpticalDevicePopulator, PartitionDevicePopulator
f37345
 from blivet.populator.helpers import LVMFormatPopulator, MDFormatPopulator, NVDIMMNamespaceDevicePopulator
f37345
+from blivet.populator.helpers import NVMeNamespaceDevicePopulator, NVMeFabricsNamespaceDevicePopulator
f37345
 from blivet.populator.helpers import get_format_helper, get_device_helper
f37345
 from blivet.populator.helpers.boot import AppleBootFormatPopulator, EFIFormatPopulator, MacEFIFormatPopulator
f37345
 from blivet.populator.helpers.formatpopulator import FormatPopulator
f37345
@@ -591,6 +593,128 @@ class NVDIMMNamespaceDevicePopulatorTestCase(PopulatorHelperTestCase):
f37345
         self.assertTrue(device in devicetree.devices)
f37345
 
f37345
 
f37345
+class NVMeNamespaceDevicePopulatorTestCase(PopulatorHelperTestCase):
f37345
+    helper_class = NVMeNamespaceDevicePopulator
f37345
+
f37345
+    @patch("os.path.join")
f37345
+    @patch("blivet.udev.device_is_cdrom", return_value=False)
f37345
+    @patch("blivet.udev.device_is_dm", return_value=False)
f37345
+    @patch("blivet.udev.device_is_loop", return_value=False)
f37345
+    @patch("blivet.udev.device_is_md", return_value=False)
f37345
+    @patch("blivet.udev.device_is_partition", return_value=False)
f37345
+    @patch("blivet.udev.device_is_disk", return_value=True)
f37345
+    @patch("blivet.udev.device_is_nvme_fabrics", return_value=False)
f37345
+    @patch("blivet.udev.device_is_nvme_namespace", return_value=True)
f37345
+    def test_match(self, *args):
f37345
+        """Test matching of NVMe namespace device populator."""
f37345
+        device_is_nvme_namespace = args[0]
f37345
+        self.assertTrue(self.helper_class.match(None))
f37345
+        device_is_nvme_namespace.return_value = False
f37345
+        self.assertFalse(self.helper_class.match(None))
f37345
+
f37345
+    @patch("os.path.join")
f37345
+    @patch("blivet.udev.device_is_cdrom", return_value=False)
f37345
+    @patch("blivet.udev.device_is_dm", return_value=False)
f37345
+    @patch("blivet.udev.device_is_loop", return_value=False)
f37345
+    @patch("blivet.udev.device_is_md", return_value=False)
f37345
+    @patch("blivet.udev.device_is_partition", return_value=False)
f37345
+    @patch("blivet.udev.device_is_disk", return_value=True)
f37345
+    @patch("blivet.udev.device_is_nvme_fabrics", return_value=False)
f37345
+    @patch("blivet.udev.device_is_nvme_namespace", return_value=True)
f37345
+    def test_get_helper(self, *args):
f37345
+        """Test get_device_helper for NVMe namespaces."""
f37345
+        device_is_nvme_namespace = args[0]
f37345
+        data = {}
f37345
+        self.assertEqual(get_device_helper(data), self.helper_class)
f37345
+
f37345
+        # verify that setting one of the required True return values to False prevents success
f37345
+        device_is_nvme_namespace.return_value = False
f37345
+        self.assertNotEqual(get_device_helper(data), self.helper_class)
f37345
+        device_is_nvme_namespace.return_value = True
f37345
+
f37345
+    @patch("blivet.udev.device_get_name")
f37345
+    def test_run(self, *args):
f37345
+        """Test disk device populator."""
f37345
+        device_get_name = args[0]
f37345
+
f37345
+        devicetree = DeviceTree()
f37345
+
f37345
+        # set up some fake udev data to verify handling of specific entries
f37345
+        data = {'SYS_PATH': 'dummy', 'DEVNAME': 'dummy', 'ID_PATH': 'dummy'}
f37345
+
f37345
+        device_name = "nop"
f37345
+        device_get_name.return_value = device_name
f37345
+        helper = self.helper_class(devicetree, data)
f37345
+
f37345
+        device = helper.run()
f37345
+
f37345
+        self.assertIsInstance(device, NVMeNamespaceDevice)
f37345
+        self.assertTrue(device.exists)
f37345
+        self.assertTrue(device.is_disk)
f37345
+        self.assertTrue(device in devicetree.devices)
f37345
+
f37345
+
f37345
+class NVMeFabricsNamespaceDevicePopulatorTestCase(PopulatorHelperTestCase):
f37345
+    helper_class = NVMeFabricsNamespaceDevicePopulator
f37345
+
f37345
+    @patch("os.path.join")
f37345
+    @patch("blivet.udev.device_is_cdrom", return_value=False)
f37345
+    @patch("blivet.udev.device_is_dm", return_value=False)
f37345
+    @patch("blivet.udev.device_is_loop", return_value=False)
f37345
+    @patch("blivet.udev.device_is_md", return_value=False)
f37345
+    @patch("blivet.udev.device_is_partition", return_value=False)
f37345
+    @patch("blivet.udev.device_is_disk", return_value=True)
f37345
+    @patch("blivet.udev.device_is_nvme_namespace", return_value=True)
f37345
+    @patch("blivet.udev.device_is_nvme_fabrics", return_value=True)
f37345
+    def test_match(self, *args):
f37345
+        """Test matching of NVMe namespace device populator."""
f37345
+        device_is_nvme_fabrics = args[0]
f37345
+        self.assertTrue(self.helper_class.match(None))
f37345
+        device_is_nvme_fabrics.return_value = False
f37345
+        self.assertFalse(self.helper_class.match(None))
f37345
+
f37345
+    @patch("os.path.join")
f37345
+    @patch("blivet.udev.device_is_cdrom", return_value=False)
f37345
+    @patch("blivet.udev.device_is_dm", return_value=False)
f37345
+    @patch("blivet.udev.device_is_loop", return_value=False)
f37345
+    @patch("blivet.udev.device_is_md", return_value=False)
f37345
+    @patch("blivet.udev.device_is_partition", return_value=False)
f37345
+    @patch("blivet.udev.device_is_disk", return_value=True)
f37345
+    @patch("blivet.udev.device_is_nvme_namespace", return_value=True)
f37345
+    @patch("blivet.udev.device_is_nvme_fabrics", return_value=True)
f37345
+    def test_get_helper(self, *args):
f37345
+        """Test get_device_helper for NVMe namespaces."""
f37345
+        device_is_nvme_fabrics = args[0]
f37345
+        data = {}
f37345
+        self.assertEqual(get_device_helper(data), self.helper_class)
f37345
+
f37345
+        # verify that setting one of the required True return values to False prevents success
f37345
+        device_is_nvme_fabrics.return_value = False
f37345
+        self.assertNotEqual(get_device_helper(data), self.helper_class)
f37345
+        device_is_nvme_fabrics.return_value = True
f37345
+
f37345
+    @patch("blivet.udev.device_get_name")
f37345
+    def test_run(self, *args):
f37345
+        """Test disk device populator."""
f37345
+        device_get_name = args[0]
f37345
+
f37345
+        devicetree = DeviceTree()
f37345
+
f37345
+        # set up some fake udev data to verify handling of specific entries
f37345
+        data = {'SYS_PATH': 'dummy', 'DEVNAME': 'dummy', 'ID_PATH': 'dummy'}
f37345
+
f37345
+        device_name = "nop"
f37345
+        device_get_name.return_value = device_name
f37345
+        helper = self.helper_class(devicetree, data)
f37345
+
f37345
+        device = helper.run()
f37345
+
f37345
+        self.assertIsInstance(device, NVMeFabricsNamespaceDevice)
f37345
+        self.assertTrue(device.exists)
f37345
+        self.assertTrue(device.is_disk)
f37345
+        self.assertTrue(device in devicetree.devices)
f37345
+
f37345
+
f37345
 class MDDevicePopulatorTestCase(PopulatorHelperTestCase):
f37345
     helper_class = MDDevicePopulator
f37345
 
f37345
-- 
f37345
2.38.1
f37345
f37345
f37345
From af6ad7ff2f08180672690910d453158bcd463936 Mon Sep 17 00:00:00 2001
f37345
From: Vojtech Trefny <vtrefny@redhat.com>
f37345
Date: Fri, 2 Dec 2022 12:20:47 +0100
f37345
Subject: [PATCH 2/3] Add transport and address to NVMeController info
f37345
f37345
---
f37345
 blivet/devices/disk.py | 9 +++++++--
f37345
 1 file changed, 7 insertions(+), 2 deletions(-)
f37345
f37345
diff --git a/blivet/devices/disk.py b/blivet/devices/disk.py
f37345
index b5e25939..796b5b03 100644
f37345
--- a/blivet/devices/disk.py
f37345
+++ b/blivet/devices/disk.py
f37345
@@ -730,7 +730,8 @@ class NVDIMMNamespaceDevice(DiskDevice):
f37345
         return self._sector_size
f37345
 
f37345
 
f37345
-NVMeController = namedtuple("NVMeController", ["name", "serial", "nvme_ver", "id", "subsysnqn"])
f37345
+NVMeController = namedtuple("NVMeController", ["name", "serial", "nvme_ver", "id", "subsysnqn",
f37345
+                                               "transport", "transport_address"])
f37345
 
f37345
 
f37345
 class NVMeNamespaceDevice(DiskDevice):
f37345
@@ -792,11 +793,15 @@ class NVMeNamespaceDevice(DiskDevice):
f37345
             except GLib.GError as err:
f37345
                 log.debug("Failed to get controller info for %s: %s", cpath, str(err))
f37345
                 continue
f37345
+            ctrans = util.get_sysfs_attr(controller, "transport")
f37345
+            ctaddr = util.get_sysfs_attr(controller, "address")
f37345
             self._controllers.append(NVMeController(name=os.path.basename(cpath),
f37345
                                                     serial=cinfo.serial_number,
f37345
                                                     nvme_ver=cinfo.nvme_ver,
f37345
                                                     id=cinfo.ctrl_id,
f37345
-                                                    subsysnqn=cinfo.subsysnqn))
f37345
+                                                    subsysnqn=cinfo.subsysnqn,
f37345
+                                                    transport=ctrans,
f37345
+                                                    transport_address=ctaddr))
f37345
 
f37345
         return self._controllers
f37345
 
f37345
-- 
f37345
2.38.1
f37345
f37345
f37345
From a04538936ff62958c272b5e2b2657d177df1ef13 Mon Sep 17 00:00:00 2001
f37345
From: Vojtech Trefny <vtrefny@redhat.com>
f37345
Date: Thu, 8 Dec 2022 13:15:33 +0100
f37345
Subject: [PATCH 3/3] Add additional identifiers to NVMeNamespaceDevice
f37345
f37345
---
f37345
 blivet/devices/disk.py           | 2 ++
f37345
 blivet/populator/helpers/disk.py | 3 +++
f37345
 2 files changed, 5 insertions(+)
f37345
f37345
diff --git a/blivet/devices/disk.py b/blivet/devices/disk.py
f37345
index 796b5b03..8842b4dc 100644
f37345
--- a/blivet/devices/disk.py
f37345
+++ b/blivet/devices/disk.py
f37345
@@ -756,6 +756,8 @@ class NVMeNamespaceDevice(DiskDevice):
f37345
             :type nsid: int
f37345
         """
f37345
         self.nsid = kwargs.pop("nsid", 0)
f37345
+        self.eui64 = kwargs.pop("eui64", "")
f37345
+        self.nguid = kwargs.pop("nguid", "")
f37345
 
f37345
         DiskDevice.__init__(self, device, **kwargs)
f37345
 
f37345
diff --git a/blivet/populator/helpers/disk.py b/blivet/populator/helpers/disk.py
f37345
index 9ed1eebe..cf20d302 100644
f37345
--- a/blivet/populator/helpers/disk.py
f37345
+++ b/blivet/populator/helpers/disk.py
f37345
@@ -282,6 +282,9 @@ class NVMeNamespaceDevicePopulator(DiskDevicePopulator):
f37345
             log.debug("Failed to get namespace info for %s: %s", path, str(err))
f37345
         else:
f37345
             kwargs["nsid"] = ninfo.nsid
f37345
+            kwargs["uuid"] = ninfo.uuid
f37345
+            kwargs["eui64"] = ninfo.eui64
f37345
+            kwargs["nguid"] = ninfo.nguid
f37345
 
f37345
         log.info("%s is an NVMe local namespace device", udev.device_get_name(self.data))
f37345
         return kwargs
f37345
-- 
f37345
2.38.1
f37345