From 3751b9a554d290af4c7aaa30e6f12a359f47cab0 Mon Sep 17 00:00:00 2001 From: Kanchan Sen Laskar Date: Sat, 25 Oct 2025 16:56:27 +0530 Subject: [PATCH] Fix nvme device path fetch logic --- lisa/tools/nvmecli.py | 248 +++++++++++++++++++++++++++++++----------- 1 file changed, 184 insertions(+), 64 deletions(-) diff --git a/lisa/tools/nvmecli.py b/lisa/tools/nvmecli.py index a9eb0d0018..ecab5f7fe7 100644 --- a/lisa/tools/nvmecli.py +++ b/lisa/tools/nvmecli.py @@ -2,7 +2,7 @@ # Licensed under the MIT license. import json import re -from typing import Any, Dict, List, Optional, Type, cast +from typing import Dict, List, Optional, Type, cast from lisa.executable import Tool from lisa.operating_system import Posix @@ -106,7 +106,155 @@ def get_namespaces(self, force_run: bool = False) -> List[str]: namespaces_cli.append(matched_result.group("namespace")) return namespaces_cli - def get_devices(self, force_run: bool = False) -> Any: + def get_devices(self, force_run: bool = False) -> Dict[str, int]: + """ + Return NVMe device nodes/paths (`/dev/...`) robustly across nvme-cli schemas. + + Upstream change context: + - nvme-cli reworked `nvme list -o json` around v2.11, removing the + legacy top-level `.Devices[].DevicePath` and nesting device info under: + Subsystems → Controllers → Namespaces. + - Reference discussion and breakage report: + https://github.com/linux-nvme/nvme-cli/issues/2749 + (thread points to commit 929f461 as the change introducing the new JSON) + - Some distro builds (e.g., certain RHEL package revisions) may still + emit `DevicePath`. This logic supports both. + + Returns: + Dict[str, int]: Mapping of NVMe device paths to their namespace IDs. + """ + # NVME namespace ids are unique for each disk under any NVME controller. + # These are useful in detecting the lun id of the remote azure disk disks. + # Example output of nvme -list -o json and nvme -list + # root@lisa--170-e0-n0:/home/lisa# nvme -list -o json + # { + # "Devices" : [ + # { + # "NameSpace" : 1, + # "DevicePath" : "/dev/nvme0n1", + # "Firmware" : "v1.00000", + # "Index" : 0, + # "ModelNumber" : "MSFT NVMe Accelerator v1.0", + # "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501 + # "SerialNumber" : "SN: 000001", + # "UsedBytes" : 536870912000, + # "MaximumLBA" : 1048576000, + # "PhysicalSize" : 536870912000, + # "SectorSize" : 512 + # }, + # { + # "NameSpace" : 2, + # "DevicePath" : "/dev/nvme0n2", + # "Firmware" : "v1.00000", + # "Index" : 0, + # "ModelNumber" : "MSFT NVMe Accelerator v1.0", + # "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501 + # "SerialNumber" : "SN: 000001", + # "UsedBytes" : 4294967296, + # "MaximumLBA" : 8388608, + # "PhysicalSize" : 4294967296, + # "SectorSize" : 512 + # } + # ] + # } + # root@lisa--170-e0-n0:/home/lisa# nvme -list + # Node SN Model Namespace Usage Format FW Rev # noqa: E501 + # --------------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- ------- # noqa: E501 + # /dev/nvme0n1 SN: 000001 MSFT NVMe Accelerator v1.0 1 536.87 GB / 536.87 GB 512 B + 0 B v1.0000 # noqa: E501 + # /dev/nvme0n2 SN: 000001 MSFT NVMe Accelerator v1.0 2 4.29 GB / 4.29 GB 512 B + 0 B v1.0000 # noqa: E501 + # /dev/nvme0n3 SN: 000001 MSFT NVMe Accelerator v1.0 15 44.02 GB / 44.02 GB 512 B + 0 B v1.0000 # noqa: E501 + # /dev/nvme0n4 SN: 000001 MSFT NVMe Accelerator v1.0 14 6.44 GB / 6.44 GB 512 B + 0 B v1.0000 # noqa: E501 + # /dev/nvme1n1 68e8d42a7ed4e5f90002 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501 + # /dev/nvme2n1 68e8d42a7ed4e5f90001 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501 + # + # Another example output of nvme -list -o json without DevicePath key as this is the new schema with the newer version of nvme-cli: # noqa: E501 + # root@lisa--170-e0-n0:/home/lisa# nvme list + # Node Generic SN Model Namespace Usage Format FW Rev # noqa: E501 + # --------------------- --------------------- -------------------- ---------------------------------------- ---------- -------------------------- ---------------- -------- # noqa: E501 + # /dev/nvme0n1 /dev/ng0n1 SN: 00000 MSFT NVMe Accelerator v1.0 0x1 68.72 GB / 68.72 GB 512 B + 0 B v1.00000 # noqa: E501 + # /dev/nvme0n2 /dev/ng0n2 SN: 00000 MSFT NVMe Accelerator v1.0 0x2 21.47 GB / 21.47 GB 512 B + 0 B v1.00000 # noqa: E501 + # /dev/nvme0n3 /dev/ng0n3 SN: 00000 MSFT NVMe Accelerator v1.0 0x3 21.47 GB / 21.47 GB 512 B + 0 B v1.00000 # noqa: E501 + # /dev/nvme0n4 /dev/ng0n4 SN: 00000 MSFT NVMe Accelerator v1.0 0x4 21.47 GB / 21.47 GB 512 B + 0 B v1.00000 # noqa: E501 + # /dev/nvme0n5 /dev/ng0n5 SN: 00000 MSFT NVMe Accelerator v1.0 0x5 21.47 GB / 21.47 GB 512 B + 0 B v1.00000 # noqa: E501 + # + # root@lisa--170-e0-n0:/home/lisa# nvme list -o json 2>/dev/null + # { + # "Devices":[ + # { + # "HostNQN":"nqn.2014-08.org.nvmexpress:uuid:ec2bfbbc-632e-0494-048e-31ebc97bd499", + # "HostID":"ec2bfbbc-632e-0494-048e-31ebc97bd499", + # "Subsystems":[ + # { + # "Subsystem":"nvme-subsys0", + # "SubsystemNQN":"nqn.2014-08.org.nvmexpress:uuid:7ad35d50-c05b-47ab-b3a0-56a9a845852b", + # "Controllers":[ + # { + # "Controller":"nvme0", + # "Cntlid":"0", + # "SerialNumber":"SN: 00000", + # "ModelNumber":"MSFT NVMe Accelerator v1.0", + # "Firmware":"v1.00000", + # "Transport":"pcie", + # "Address":"c05b:00:00.0", + # "Slot":"2060672336", + # "Namespaces":[ + # { + # "NameSpace":"nvme0n1", + # "Generic":"ng0n1", + # "NSID":1, + # "UsedBytes":68719476736, + # "MaximumLBA":134217728, + # "PhysicalSize":68719476736, + # "SectorSize":512 + # }, + # { + # "NameSpace":"nvme0n2", + # "Generic":"ng0n2", + # "NSID":2, + # "UsedBytes":21474836480, + # "MaximumLBA":41943040, + # "PhysicalSize":21474836480, + # "SectorSize":512 + # }, + # { + # "NameSpace":"nvme0n3", + # "Generic":"ng0n3", + # "NSID":3, + # "UsedBytes":21474836480, + # "MaximumLBA":41943040, + # "PhysicalSize":21474836480, + # "SectorSize":512 + # }, + # { + # "NameSpace":"nvme0n4", + # "Generic":"ng0n4", + # "NSID":4, + # "UsedBytes":21474836480, + # "MaximumLBA":41943040, + # "PhysicalSize":21474836480, + # "SectorSize":512 + # }, + # { + # "NameSpace":"nvme0n5", + # "Generic":"ng0n5", + # "NSID":5, + # "UsedBytes":21474836480, + # "MaximumLBA":41943040, + # "PhysicalSize":21474836480, + # "SectorSize":512 + # } + # ], + # "Paths":[ + # ] + # } + # ], + # "Namespaces":[ + # ] + # } + # ] + # } + # ] + # } # get nvme devices information ignoring stderror nvme_list = self.run( "list -o json 2>/dev/null", @@ -121,72 +269,44 @@ def get_devices(self, force_run: bool = False) -> Any: "No NVMe devices found. " "The 'nvme list' command returned an empty string." ) - nvme_devices = json.loads(nvme_list.stdout) - return nvme_devices["Devices"] + nvme_devices = json.loads(nvme_list.stdout)["Devices"] + device_paths_namespace_ids = {} + + def _add(device_path: str, namespace_id: int) -> None: + if ( + isinstance(device_path, str) + and device_path.startswith("/dev/") + and len(device_path) > 5 + and isinstance(namespace_id, int) + ): + device_paths_namespace_ids[device_path] = namespace_id + + for nvme_device in nvme_devices or []: + # Legacy schema (flat fields): + _add(nvme_device.get("DevicePath"), nvme_device.get("NameSpace")) + + # New schema: Subsystems → Controllers → Namespaces + for subsystem in nvme_device.get("Subsystems") or []: + for controller in (subsystem or {}).get("Controllers") or []: + for namespace in (controller or {}).get("Namespaces") or []: + namespace_name = namespace.get("NameSpace") # e.g., "nvme0n1" + namespace_id = namespace.get("NSID") # e.g., 1, 2, ... + if isinstance(namespace_name, str) and namespace_name: + _add(f"/dev/{namespace_name}", namespace_id) + + if not device_paths_namespace_ids: + raise LisaException( + "No NVMe device nodes could be derived from 'nvme list -o json'." + ) + return device_paths_namespace_ids def get_disks(self, force_run: bool = False) -> List[str]: - nvme_devices = self.get_devices(force_run=force_run) - return [device["DevicePath"] for device in nvme_devices] - - # NVME namespace ids are unique for each disk under any NVME controller. - # These are useful in detecting the lun id of the remote azure disk disks. - # Example output of nvme -list -o json and nvme -list - # root@lisa--170-e0-n0:/home/lisa# nvme -list -o json - # { - # "Devices" : [ - # { - # "NameSpace" : 1, - # "DevicePath" : "/dev/nvme0n1", - # "Firmware" : "v1.00000", - # "Index" : 0, - # "ModelNumber" : "MSFT NVMe Accelerator v1.0", - # "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501 - # "SerialNumber" : "SN: 000001", - # "UsedBytes" : 536870912000, - # "MaximumLBA" : 1048576000, - # "PhysicalSize" : 536870912000, - # "SectorSize" : 512 - # }, - # { - # "NameSpace" : 2, - # "DevicePath" : "/dev/nvme0n2", - # "Firmware" : "v1.00000", - # "Index" : 0, - # "ModelNumber" : "MSFT NVMe Accelerator v1.0", - # "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501 - # "SerialNumber" : "SN: 000001", - # "UsedBytes" : 4294967296, - # "MaximumLBA" : 8388608, - # "PhysicalSize" : 4294967296, - # "SectorSize" : 512 - # } - # ] - # } - # root@lisa--170-e0-n0:/home/lisa# nvme -list - # Node SN Model Namespace Usage Format FW Rev # noqa: E501 - # --------------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- ------- # noqa: E501 - # /dev/nvme0n1 SN: 000001 MSFT NVMe Accelerator v1.0 1 536.87 GB / 536.87 GB 512 B + 0 B v1.0000 # noqa: E501 - # /dev/nvme0n2 SN: 000001 MSFT NVMe Accelerator v1.0 2 4.29 GB / 4.29 GB 512 B + 0 B v1.0000 # noqa: E501 - # /dev/nvme0n3 SN: 000001 MSFT NVMe Accelerator v1.0 15 44.02 GB / 44.02 GB 512 B + 0 B v1.0000 # noqa: E501 - # /dev/nvme0n4 SN: 000001 MSFT NVMe Accelerator v1.0 14 6.44 GB / 6.44 GB 512 B + 0 B v1.0000 # noqa: E501 - # /dev/nvme1n1 68e8d42a7ed4e5f90002 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501 - # /dev/nvme2n1 68e8d42a7ed4e5f90001 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501 + device_paths = sorted(self.get_devices(force_run=force_run).keys()) + return device_paths def get_namespace_ids(self, force_run: bool = False) -> List[Dict[str, int]]: - nvme_devices = self.get_devices(force_run=force_run) - # Older versions of nvme-cli do not have the NameSpace key in the output - # skip the test if NameSpace key is not available - if not nvme_devices: - raise LisaException("No NVMe devices found. Unable to get namespace ids.") - if "NameSpace" not in nvme_devices[0]: - raise LisaException( - "The version of nvme-cli is too old," - " it doesn't support to get namespace ids." - ) - - return [ - {device["DevicePath"]: int(device["NameSpace"])} for device in nvme_devices - ] + device_paths_namespace_ids_map = self.get_devices(force_run=force_run) + return [{path: nsid} for path, nsid in device_paths_namespace_ids_map.items()] class BSDNvmecli(Nvmecli):