Skip to content

Commit feba6aa

Browse files
committed
utils/nvme: enhance get_ns_status with show-topology fallback
The 'nvme show-topology /dev/<controller>' form fails on some nvme-cli builds, returning {"error": "Invalid device name"} instead of a valid JSON topology. Additionally, in multi-path subsystems where two controllers (e.g. nvme0 and nvme3) share a namespace (e.g. nvme3n1), the namespace block device is named after only one of the controllers, making a namespace name constructed from controller_name unreliable. Enhance get_ns_status with a two-stage approach and unified parsing: - Introduce _iter_topology_paths() generator helper to normalize and flatten the topology traversal across differing JSON schemas: - Targeted schema: where Name and State reside directly on Path - Whole-system schema: where Name and State are nested under Controller[] - Primary path: nvme show-topology /dev/<controller_name> -o json Queries targeted controller and extracts state using the helper. Returns immediately on match. - Fallback path: nvme show-topology -o json (whole-system, no device argument) Triggered when primary path returns an error payload, raises JSONDecodeError, or yields no match. Uses the same helper to match NSID and Controller.Name unambiguously across multi-controller subsystems. Both paths return [State, ANAState], preserving existing API and behavior. Signed-off-by: Maram Srimannarayana Murthy <msmurthy@linux.vnet.ibm.com>
1 parent 2454cd2 commit feba6aa

2 files changed

Lines changed: 75 additions & 13 deletions

File tree

avocado/utils/nvme.py

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
Nvme utilities
2222
"""
2323

24-
2524
import json
2625
import logging
2726
import os
@@ -650,26 +649,83 @@ def create_namespaces(controller_name, ns_count, shared_ns=False):
650649
create_one_ns(ns_id, controller_name, ns_size, shared_ns=shared_ns)
651650

652651

652+
def _iter_topology_paths(json_data, ns_id):
653+
"""
654+
Yields (name, state, ana_state) for each path matching the namespace ID.
655+
656+
Handles both targeted query format (where Name and State reside directly
657+
on Path) and whole-system query format (where Name and State are nested
658+
under Controller).
659+
660+
:param json_data: Parsed JSON topology data (list)
661+
:param ns_id: Target namespace ID (int)
662+
"""
663+
if not isinstance(json_data, list):
664+
return
665+
666+
for entry in json_data:
667+
for subsystem in entry.get("Subsystems", []):
668+
for namespace in subsystem.get("Namespaces", []):
669+
if namespace.get("NSID") != ns_id:
670+
continue
671+
for path in namespace.get("Paths", []):
672+
ana_state = path.get("ANAState")
673+
controllers = path.get("Controller", [])
674+
if controllers:
675+
for ctrl in controllers:
676+
yield ctrl.get("Name"), ctrl.get("State"), ana_state
677+
else:
678+
yield path.get("Name"), path.get("State"), ana_state
679+
680+
653681
def get_ns_status(controller_name, ns_id):
654682
"""
655-
Returns the status of namespaces on the specified controller
683+
Returns the status of namespaces on the specified controller.
656684
657-
:param controller_name: name of the controller like nvme0
658-
:param ns_id: ID of namespace for which we need the status
685+
Tries ``nvme show-topology /dev/<controller_name>`` first; on error
686+
or no match falls back to ``nvme show-topology -o json``
687+
(whole-system), locating via ``NSID`` and ``Controller.Name``
688+
(multi-path safe, e.g. ``nvme0``/``nvme3`` serving ``nvme3n1``).
659689
660-
:rtype: list
690+
:param controller_name: name of the controller, e.g. ``nvme0``
691+
:param ns_id: NSID of the namespace whose status is required (int)
692+
693+
:rtype: list -- ``[State, ANAState]`` for the matching path, or ``[]``
661694
"""
662695
stat = []
696+
663697
cmd = f"nvme show-topology /dev/{controller_name} -o json"
664698
data = process.run(cmd, ignore_status=True, sudo=True, shell=True).stdout_text
665-
json_data = json.loads(data)
666-
for data in json_data:
667-
for subsystem in data["Subsystems"]:
668-
for namespace in subsystem["Namespaces"]:
669-
nsid = namespace["NSID"]
670-
for paths in namespace["Paths"]:
671-
if nsid == ns_id and paths["Name"] == controller_name:
672-
stat.extend([paths["State"], paths["ANAState"]])
699+
try:
700+
json_data = json.loads(data)
701+
except json.JSONDecodeError:
702+
json_data = None
703+
704+
for name, state, ana_state in _iter_topology_paths(json_data, ns_id):
705+
if name == controller_name:
706+
stat.extend([state, ana_state])
707+
if stat:
708+
return stat
709+
710+
LOGGER.debug(
711+
"get_ns_status: primary show-topology for %s failed or returned no "
712+
"match; retrying with whole-system show-topology",
713+
controller_name,
714+
)
715+
cmd = "nvme show-topology -o json"
716+
data = process.run(cmd, ignore_status=True, sudo=True, shell=True).stdout_text
717+
try:
718+
json_data = json.loads(data)
719+
except json.JSONDecodeError:
720+
LOGGER.warning(
721+
"get_ns_status: could not parse whole-system show-topology output"
722+
)
723+
return stat
724+
725+
for name, state, ana_state in _iter_topology_paths(json_data, ns_id):
726+
if name == controller_name:
727+
stat.extend([state, ana_state])
728+
673729
return stat
674730

675731

docs/source/releases/next.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ Bug Fixes
2323

2424
* The Podman spawner now resolves the default ``podman`` binary through
2525
``PATH``, supporting installations outside ``/usr/bin``.
26+
* :func:`avocado.utils.nvme.get_ns_status` now handles nvme-cli builds
27+
that reject a controller node argument to ``nvme show-topology``, and
28+
correctly resolves namespace status for peer controllers in multi-path
29+
subsystems (e.g. ``nvme0`` and ``nvme3`` both serving ``nvme3n1``).
30+
A whole-system ``nvme show-topology -o json`` fallback is used when
31+
the controller-addressed form fails or yields no match.
2632

2733
Internal changes
2834
================

0 commit comments

Comments
 (0)