Blame SOURCES/0010-Add-basic-support-for-NVMe-and-NVMe-Fabrics-devices.patch

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