Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 184 additions & 64 deletions lisa/tools/nvmecli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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):
Expand Down
Loading