22# Licensed under the MIT license.
33import json
44import re
5- from typing import Any , Dict , List , Optional , Type , cast
5+ from typing import Dict , List , Optional , Type , cast
66
77from lisa .executable import Tool
88from 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
192312class BSDNvmecli (Nvmecli ):
0 commit comments