Skip to content

Commit 3751b9a

Browse files
Fix nvme device path fetch logic
1 parent a65190f commit 3751b9a

File tree

1 file changed

+184
-64
lines changed

1 file changed

+184
-64
lines changed

lisa/tools/nvmecli.py

Lines changed: 184 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Licensed under the MIT license.
33
import json
44
import re
5-
from typing import Any, Dict, List, Optional, Type, cast
5+
from typing import Dict, List, Optional, Type, cast
66

77
from lisa.executable import Tool
88
from lisa.operating_system import Posix
@@ -106,7 +106,155 @@ def get_namespaces(self, force_run: bool = False) -> List[str]:
106106
namespaces_cli.append(matched_result.group("namespace"))
107107
return namespaces_cli
108108

109-
def get_devices(self, force_run: bool = False) -> Any:
109+
def get_devices(self, force_run: bool = False) -> Dict[str, int]:
110+
"""
111+
Return NVMe device nodes/paths (`/dev/...`) robustly across nvme-cli schemas.
112+
113+
Upstream change context:
114+
- nvme-cli reworked `nvme list -o json` around v2.11, removing the
115+
legacy top-level `.Devices[].DevicePath` and nesting device info under:
116+
Subsystems → Controllers → Namespaces.
117+
- Reference discussion and breakage report:
118+
https://github.com/linux-nvme/nvme-cli/issues/2749
119+
(thread points to commit 929f461 as the change introducing the new JSON)
120+
- Some distro builds (e.g., certain RHEL package revisions) may still
121+
emit `DevicePath`. This logic supports both.
122+
123+
Returns:
124+
Dict[str, int]: Mapping of NVMe device paths to their namespace IDs.
125+
"""
126+
# NVME namespace ids are unique for each disk under any NVME controller.
127+
# These are useful in detecting the lun id of the remote azure disk disks.
128+
# Example output of nvme -list -o json and nvme -list
129+
# root@lisa--170-e0-n0:/home/lisa# nvme -list -o json
130+
# {
131+
# "Devices" : [
132+
# {
133+
# "NameSpace" : 1,
134+
# "DevicePath" : "/dev/nvme0n1",
135+
# "Firmware" : "v1.00000",
136+
# "Index" : 0,
137+
# "ModelNumber" : "MSFT NVMe Accelerator v1.0",
138+
# "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501
139+
# "SerialNumber" : "SN: 000001",
140+
# "UsedBytes" : 536870912000,
141+
# "MaximumLBA" : 1048576000,
142+
# "PhysicalSize" : 536870912000,
143+
# "SectorSize" : 512
144+
# },
145+
# {
146+
# "NameSpace" : 2,
147+
# "DevicePath" : "/dev/nvme0n2",
148+
# "Firmware" : "v1.00000",
149+
# "Index" : 0,
150+
# "ModelNumber" : "MSFT NVMe Accelerator v1.0",
151+
# "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501
152+
# "SerialNumber" : "SN: 000001",
153+
# "UsedBytes" : 4294967296,
154+
# "MaximumLBA" : 8388608,
155+
# "PhysicalSize" : 4294967296,
156+
# "SectorSize" : 512
157+
# }
158+
# ]
159+
# }
160+
# root@lisa--170-e0-n0:/home/lisa# nvme -list
161+
# Node SN Model Namespace Usage Format FW Rev # noqa: E501
162+
# --------------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- ------- # noqa: E501
163+
# /dev/nvme0n1 SN: 000001 MSFT NVMe Accelerator v1.0 1 536.87 GB / 536.87 GB 512 B + 0 B v1.0000 # noqa: E501
164+
# /dev/nvme0n2 SN: 000001 MSFT NVMe Accelerator v1.0 2 4.29 GB / 4.29 GB 512 B + 0 B v1.0000 # noqa: E501
165+
# /dev/nvme0n3 SN: 000001 MSFT NVMe Accelerator v1.0 15 44.02 GB / 44.02 GB 512 B + 0 B v1.0000 # noqa: E501
166+
# /dev/nvme0n4 SN: 000001 MSFT NVMe Accelerator v1.0 14 6.44 GB / 6.44 GB 512 B + 0 B v1.0000 # noqa: E501
167+
# /dev/nvme1n1 68e8d42a7ed4e5f90002 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501
168+
# /dev/nvme2n1 68e8d42a7ed4e5f90001 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501
169+
#
170+
# 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
171+
# root@lisa--170-e0-n0:/home/lisa# nvme list
172+
# Node Generic SN Model Namespace Usage Format FW Rev # noqa: E501
173+
# --------------------- --------------------- -------------------- ---------------------------------------- ---------- -------------------------- ---------------- -------- # noqa: E501
174+
# /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
175+
# /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
176+
# /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
177+
# /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
178+
# /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
179+
#
180+
# root@lisa--170-e0-n0:/home/lisa# nvme list -o json 2>/dev/null
181+
# {
182+
# "Devices":[
183+
# {
184+
# "HostNQN":"nqn.2014-08.org.nvmexpress:uuid:ec2bfbbc-632e-0494-048e-31ebc97bd499",
185+
# "HostID":"ec2bfbbc-632e-0494-048e-31ebc97bd499",
186+
# "Subsystems":[
187+
# {
188+
# "Subsystem":"nvme-subsys0",
189+
# "SubsystemNQN":"nqn.2014-08.org.nvmexpress:uuid:7ad35d50-c05b-47ab-b3a0-56a9a845852b",
190+
# "Controllers":[
191+
# {
192+
# "Controller":"nvme0",
193+
# "Cntlid":"0",
194+
# "SerialNumber":"SN: 00000",
195+
# "ModelNumber":"MSFT NVMe Accelerator v1.0",
196+
# "Firmware":"v1.00000",
197+
# "Transport":"pcie",
198+
# "Address":"c05b:00:00.0",
199+
# "Slot":"2060672336",
200+
# "Namespaces":[
201+
# {
202+
# "NameSpace":"nvme0n1",
203+
# "Generic":"ng0n1",
204+
# "NSID":1,
205+
# "UsedBytes":68719476736,
206+
# "MaximumLBA":134217728,
207+
# "PhysicalSize":68719476736,
208+
# "SectorSize":512
209+
# },
210+
# {
211+
# "NameSpace":"nvme0n2",
212+
# "Generic":"ng0n2",
213+
# "NSID":2,
214+
# "UsedBytes":21474836480,
215+
# "MaximumLBA":41943040,
216+
# "PhysicalSize":21474836480,
217+
# "SectorSize":512
218+
# },
219+
# {
220+
# "NameSpace":"nvme0n3",
221+
# "Generic":"ng0n3",
222+
# "NSID":3,
223+
# "UsedBytes":21474836480,
224+
# "MaximumLBA":41943040,
225+
# "PhysicalSize":21474836480,
226+
# "SectorSize":512
227+
# },
228+
# {
229+
# "NameSpace":"nvme0n4",
230+
# "Generic":"ng0n4",
231+
# "NSID":4,
232+
# "UsedBytes":21474836480,
233+
# "MaximumLBA":41943040,
234+
# "PhysicalSize":21474836480,
235+
# "SectorSize":512
236+
# },
237+
# {
238+
# "NameSpace":"nvme0n5",
239+
# "Generic":"ng0n5",
240+
# "NSID":5,
241+
# "UsedBytes":21474836480,
242+
# "MaximumLBA":41943040,
243+
# "PhysicalSize":21474836480,
244+
# "SectorSize":512
245+
# }
246+
# ],
247+
# "Paths":[
248+
# ]
249+
# }
250+
# ],
251+
# "Namespaces":[
252+
# ]
253+
# }
254+
# ]
255+
# }
256+
# ]
257+
# }
110258
# get nvme devices information ignoring stderror
111259
nvme_list = self.run(
112260
"list -o json 2>/dev/null",
@@ -121,72 +269,44 @@ def get_devices(self, force_run: bool = False) -> Any:
121269
"No NVMe devices found. "
122270
"The 'nvme list' command returned an empty string."
123271
)
124-
nvme_devices = json.loads(nvme_list.stdout)
125-
return nvme_devices["Devices"]
272+
nvme_devices = json.loads(nvme_list.stdout)["Devices"]
273+
device_paths_namespace_ids = {}
274+
275+
def _add(device_path: str, namespace_id: int) -> None:
276+
if (
277+
isinstance(device_path, str)
278+
and device_path.startswith("/dev/")
279+
and len(device_path) > 5
280+
and isinstance(namespace_id, int)
281+
):
282+
device_paths_namespace_ids[device_path] = namespace_id
283+
284+
for nvme_device in nvme_devices or []:
285+
# Legacy schema (flat fields):
286+
_add(nvme_device.get("DevicePath"), nvme_device.get("NameSpace"))
287+
288+
# New schema: Subsystems → Controllers → Namespaces
289+
for subsystem in nvme_device.get("Subsystems") or []:
290+
for controller in (subsystem or {}).get("Controllers") or []:
291+
for namespace in (controller or {}).get("Namespaces") or []:
292+
namespace_name = namespace.get("NameSpace") # e.g., "nvme0n1"
293+
namespace_id = namespace.get("NSID") # e.g., 1, 2, ...
294+
if isinstance(namespace_name, str) and namespace_name:
295+
_add(f"/dev/{namespace_name}", namespace_id)
296+
297+
if not device_paths_namespace_ids:
298+
raise LisaException(
299+
"No NVMe device nodes could be derived from 'nvme list -o json'."
300+
)
301+
return device_paths_namespace_ids
126302

127303
def get_disks(self, force_run: bool = False) -> List[str]:
128-
nvme_devices = self.get_devices(force_run=force_run)
129-
return [device["DevicePath"] for device in nvme_devices]
130-
131-
# NVME namespace ids are unique for each disk under any NVME controller.
132-
# These are useful in detecting the lun id of the remote azure disk disks.
133-
# Example output of nvme -list -o json and nvme -list
134-
# root@lisa--170-e0-n0:/home/lisa# nvme -list -o json
135-
# {
136-
# "Devices" : [
137-
# {
138-
# "NameSpace" : 1,
139-
# "DevicePath" : "/dev/nvme0n1",
140-
# "Firmware" : "v1.00000",
141-
# "Index" : 0,
142-
# "ModelNumber" : "MSFT NVMe Accelerator v1.0",
143-
# "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501
144-
# "SerialNumber" : "SN: 000001",
145-
# "UsedBytes" : 536870912000,
146-
# "MaximumLBA" : 1048576000,
147-
# "PhysicalSize" : 536870912000,
148-
# "SectorSize" : 512
149-
# },
150-
# {
151-
# "NameSpace" : 2,
152-
# "DevicePath" : "/dev/nvme0n2",
153-
# "Firmware" : "v1.00000",
154-
# "Index" : 0,
155-
# "ModelNumber" : "MSFT NVMe Accelerator v1.0",
156-
# "ProductName" : "Non-Volatile memory controller: Microsoft Corporation Device 0x00a9", # noqa: E501
157-
# "SerialNumber" : "SN: 000001",
158-
# "UsedBytes" : 4294967296,
159-
# "MaximumLBA" : 8388608,
160-
# "PhysicalSize" : 4294967296,
161-
# "SectorSize" : 512
162-
# }
163-
# ]
164-
# }
165-
# root@lisa--170-e0-n0:/home/lisa# nvme -list
166-
# Node SN Model Namespace Usage Format FW Rev # noqa: E501
167-
# --------------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- ------- # noqa: E501
168-
# /dev/nvme0n1 SN: 000001 MSFT NVMe Accelerator v1.0 1 536.87 GB / 536.87 GB 512 B + 0 B v1.0000 # noqa: E501
169-
# /dev/nvme0n2 SN: 000001 MSFT NVMe Accelerator v1.0 2 4.29 GB / 4.29 GB 512 B + 0 B v1.0000 # noqa: E501
170-
# /dev/nvme0n3 SN: 000001 MSFT NVMe Accelerator v1.0 15 44.02 GB / 44.02 GB 512 B + 0 B v1.0000 # noqa: E501
171-
# /dev/nvme0n4 SN: 000001 MSFT NVMe Accelerator v1.0 14 6.44 GB / 6.44 GB 512 B + 0 B v1.0000 # noqa: E501
172-
# /dev/nvme1n1 68e8d42a7ed4e5f90002 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501
173-
# /dev/nvme2n1 68e8d42a7ed4e5f90001 Microsoft NVMe Direct Disk v2 1 472.45 GB / 472.45 GB 512 B + 0 B NVMDV00 # noqa: E501
304+
device_paths = sorted(self.get_devices(force_run=force_run).keys())
305+
return device_paths
174306

175307
def get_namespace_ids(self, force_run: bool = False) -> List[Dict[str, int]]:
176-
nvme_devices = self.get_devices(force_run=force_run)
177-
# Older versions of nvme-cli do not have the NameSpace key in the output
178-
# skip the test if NameSpace key is not available
179-
if not nvme_devices:
180-
raise LisaException("No NVMe devices found. Unable to get namespace ids.")
181-
if "NameSpace" not in nvme_devices[0]:
182-
raise LisaException(
183-
"The version of nvme-cli is too old,"
184-
" it doesn't support to get namespace ids."
185-
)
186-
187-
return [
188-
{device["DevicePath"]: int(device["NameSpace"])} for device in nvme_devices
189-
]
308+
device_paths_namespace_ids_map = self.get_devices(force_run=force_run)
309+
return [{path: nsid} for path, nsid in device_paths_namespace_ids_map.items()]
190310

191311

192312
class BSDNvmecli(Nvmecli):

0 commit comments

Comments
 (0)