@@ -125,8 +125,108 @@ def get_devices(self, force_run: bool = False) -> Any:
125125 return nvme_devices ["Devices" ]
126126
127127 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 ]
128+ """
129+ Return NVMe device nodes/paths (`/dev/...`) robustly across nvme-cli schemas.
130+
131+ Upstream change context:
132+ - nvme-cli reworked `nvme list -o json` around v2.11, removing the
133+ legacy top-level `.Devices[].DevicePath` and nesting device info under:
134+ Subsystems → Controllers → Namespaces.
135+ - Reference discussion and breakage report:
136+ https://github.com/linux-nvme/nvme-cli/issues/2749
137+ (thread points to commit 929f461 as the change introducing the new JSON)
138+ - Some distro builds (e.g., certain RHEL package revisions) may still
139+ emit `DevicePath`. This logic supports both.
140+
141+ - jq option is implemented to simplify parsing, but a pure-Python
142+ fallback is also provided if `jq` is not available on the target system.
143+ jq option is more efficient and robust, so it is preferred when possible.
144+ Returns:
145+ List[str]: device nodes/paths like `/dev/nvme0n1`
146+ """
147+
148+ check = self .node .execute (
149+ "command -v jq" , shell = True , sudo = True , no_error_log = True
150+ )
151+ # a new tool for jq can also be written if used frequently
152+ if check .exit_code != 0 or not (check .stdout or "" ).strip ():
153+ use_jq = False
154+ else :
155+ use_jq = True
156+
157+ if use_jq :
158+ # -------------------------------
159+ # jq pipeline (more efficient)
160+ # -------------------------------
161+ # Rationale: First prefer legacy `.DevicePath` if present, else build
162+ # `/dev/<NameSpace>` from the new nested schema.
163+ # Reference: https://github.com/linux-nvme/nvme-cli/issues/2749
164+
165+ cmd = r"""list -o json 2>/dev/null | jq -r '
166+ .Devices[]? as $d |
167+ if ($d | has("DevicePath")) and ($d.DevicePath != null) then
168+ # Legacy / RHEL-patched builds: use the flat field directly
169+ $d.DevicePath
170+ else
171+ # Newer schema: Subsystems → Controllers → Namespaces → NameSpace
172+ [$d.Subsystems[]? | .Controllers[]? | .Namespaces[]?
173+ | "/dev/" + (.NameSpace // "")]
174+ | map(select(length > 5)) # drop empties like "/dev/"
175+ | .[]
176+ end
177+ '"""
178+
179+ result = self .run (
180+ cmd ,
181+ shell = True ,
182+ sudo = True ,
183+ force_run = force_run ,
184+ no_error_log = True ,
185+ )
186+
187+ device_paths = [
188+ ln .strip () for ln in (result .stdout or "" ).splitlines () if ln .strip ()
189+ ]
190+ if not device_paths :
191+ raise LisaException (
192+ "No NVMe devices found. The jq-based pipeline returned no paths."
193+ )
194+ return device_paths
195+ else :
196+ # -------------------------------
197+ # Pure Python (when jq is not available)
198+ # -------------------------------
199+ nvme_devices = self .get_devices (force_run = force_run ) # raw ["Devices"]
200+ device_paths = []
201+
202+ def _add (path : str ) -> None :
203+ if isinstance (path , str ) and path .startswith ("/dev/" ) and len (path ) > 5 :
204+ device_paths .append (path )
205+
206+ for nvme_device in nvme_devices or []:
207+ # Legacy schema (flat fields):
208+ _add (nvme_device .get ("DevicePath" ))
209+ _add (nvme_device .get ("GenericPath" ))
210+
211+ # New schema: Subsystems → Controllers → Namespaces
212+ for subsystem in nvme_device .get ("Subsystems" ) or []:
213+ for controller in (subsystem or {}).get ("Controllers" ) or []:
214+ for namespace in (controller or {}).get ("Namespaces" ) or []:
215+ namespace_name = namespace .get (
216+ "NameSpace"
217+ ) # e.g., "nvme0n1"
218+ generic_name = namespace .get ("Generic" ) # e.g., "ng0n1"
219+ if isinstance (namespace_name , str ) and namespace_name :
220+ _add (f"/dev/{ namespace_name } " )
221+ if isinstance (generic_name , str ) and generic_name :
222+ _add (f"/dev/{ generic_name } " )
223+
224+ device_paths = sorted (set (device_paths ))
225+ if not device_paths :
226+ raise LisaException (
227+ "No NVMe device nodes could be derived from 'nvme list -o json'."
228+ )
229+ return device_paths
130230
131231 # NVME namespace ids are unique for each disk under any NVME controller.
132232 # These are useful in detecting the lun id of the remote azure disk disks.
0 commit comments