]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: align nvmeof cli with missing parameters and functions from the old... 69339/head
authorTomer Haskalovitch <tomer.haska@ibm.com>
Tue, 2 Jun 2026 10:20:28 +0000 (13:20 +0300)
committerTomer Haskalovitch <tomer.haska@ibm.com>
Tue, 16 Jun 2026 07:41:53 +0000 (10:41 +0300)
fixes: https://tracker.ceph.com/issues/77108
Signed-off-by: Tomer Haskalovitch <tomer.haska@ibm.com>
src/pybind/mgr/dashboard/controllers/nvmeof.py
src/pybind/mgr/dashboard/model/nvmeof.py
src/pybind/mgr/dashboard/openapi.yaml

index d6f7628803c3390f53ca9aad6f93116f322b0090..557ea546ef11299d20dd60c79849ba37532ae2ee 100644 (file)
@@ -2,7 +2,7 @@
 # pylint: disable=too-many-lines
 import logging
 from functools import partial
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, Optional, Tuple
 
 import cherrypy
 from orchestrator import OrchestratorError
@@ -299,6 +299,37 @@ else:
             )
             return io_stats
 
+        @ReadPermission
+        @Endpoint('GET', '/thread_stats')
+        @NvmeofCLICommand(
+            "nvmeof gateway get_thread_stats", model.ThreadStatsInfo,
+            alias="nvmeof gw get_thread_stats")
+        @EndpointDoc(
+            "Get NVMeoF thread statistics for the gateway",
+            parameters={
+                "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                "server_address": Param(str, "NVMeoF gateway address", True, None),
+                "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+            }
+        )
+        @convert_to_model(model.ThreadStatsInfo)
+        @handle_nvmeof_error
+        def get_thread_stats(
+            self, gw_group: Optional[str] = None,
+            server_address: Optional[str] = None,
+            traddr: Optional[str] = None
+        ):
+            server_address = resolve_nvmeof_server_address(
+                server_address=server_address,
+                traddr=traddr
+            )
+            return NVMeoFClient(
+                gw_group=gw_group,
+                server_address=server_address
+            ).stub.get_thread_stats(
+                NVMeoFClient.pb2.get_thread_stats_req()
+            )
+
     @APIRouter("/nvmeof/spdk", Scope.NVME_OF)
     @APIDoc("NVMe-oF SPDK Management API", "NVMe-oF SPDK")
     class NVMeoFSpdk(RESTController):
@@ -416,6 +447,8 @@ else:
         @EndpointDoc(
             "List all NVMeoF subsystems",
             parameters={
+                "nqn": Param(str, "NVMeoF subsystem NQN to filter by", True, None),
+                "serial_number": Param(str, "Serial number to filter by", True, None),
                 "gw_group": Param(str, "NVMeoF gateway group", True, None),
                 "server_address": Param(str, "NVMeoF gateway address", True, None),
                 "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
@@ -423,7 +456,8 @@ else:
         )
         @convert_to_model(model.SubsystemList)
         @handle_nvmeof_error
-        def list(self, gw_group: Optional[str] = None, server_address: Optional[str] = None,
+        def list(self, nqn: Optional[str] = None, serial_number: Optional[str] = None,
+                 gw_group: Optional[str] = None, server_address: Optional[str] = None,
                  traddr: Optional[str] = None):
             server_address = resolve_nvmeof_server_address(
                 server_address=server_address,
@@ -433,7 +467,8 @@ else:
                 gw_group=gw_group,
                 server_address=server_address
             ).stub.list_subsystems(
-                NVMeoFClient.pb2.list_subsystems_req()
+                NVMeoFClient.pb2.list_subsystems_req(subsystem_nqn=nqn,
+                                                     serial_number=serial_number)
             )
 
         @pick(field="subsystems", first=True)
@@ -860,6 +895,12 @@ else:
                 "gw_group": Param(str, "NVMeoF gateway group", True, None),
                 "server_address": Param(str, "NVMeoF gateway address", True, None),
                 "secure": Param(bool, "Use a secure channel", True, False),
+                "force": Param(
+                    bool,
+                    "Allow contradiction in security with existing listeners",
+                    True,
+                    False
+                ),
                 "verify_host_name": Param(bool,
                                           "Fail if the host name doesn't match the "
                                           "gateway's host name",
@@ -878,6 +919,7 @@ else:
             gw_group: Optional[str] = None,
             server_address: Optional[str] = None,
             secure: Optional[bool] = False,
+            force: Optional[bool] = False,
             verify_host_name: Optional[bool] = False,
         ):
             client = NVMeoFClient(
@@ -892,6 +934,7 @@ else:
                     trsvcid=int(trsvcid) if trsvcid is not None else None,
                     adrfam=int(adrfam),
                     secure=str_to_bool(secure),
+                    force=str_to_bool(force),
                     verify_host_name=str_to_bool(verify_host_name),
                 )
             )
@@ -919,6 +962,15 @@ else:
                 "traddr": Param(str, "NVMeoF transport address"),
                 "trsvcid": Param(int, "NVMeoF transport service port"),
                 "adrfam": Param(int, "NVMeoF address family (0 - IPv4, 1 - IPv6)", True, 0),
+                "force": Param(
+                    bool,
+                    (
+                        "Delete listener even if there are active connections "
+                        "or host name doesn't match"
+                    ),
+                    True,
+                    False
+                ),
                 "gw_group": Param(str, "NVMeoF gateway group", True, None),
                 "server_address": Param(str, "NVMeoF gateway address", True, None),
             },
@@ -963,6 +1015,7 @@ else:
             parameters={
                 "nqn": Param(str, "NVMeoF subsystem NQN", True, None),
                 "nsid": Param(str, "NVMeoF Namespace ID to filter by", True, None),
+                "uuid": Param(str, "NVMeoF Namespace UUID to filter by", True, None),
                 "gw_group": Param(str, "NVMeoF gateway group", True, None),
                 "server_address": Param(str, "NVMeoF gateway address", True, None),
                 "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
@@ -971,6 +1024,7 @@ else:
         @convert_to_model(model.NamespaceList)
         @handle_nvmeof_error
         def list(self, nqn: Optional[str] = None, nsid: Optional[str] = None,
+                 uuid: Optional[str] = None,
                  gw_group: Optional[str] = None, server_address: Optional[str] = None,
                  traddr: Optional[str] = None):
             server_address = resolve_nvmeof_server_address(
@@ -982,7 +1036,8 @@ else:
                 server_address=server_address
             ).stub.list_namespaces(
                 NVMeoFClient.pb2.list_namespaces_req(subsystem=nqn,
-                                                     nsid=int(nsid) if nsid else None)
+                                                     nsid=int(nsid) if nsid else None,
+                                                     uuid=uuid)
             )
 
         @pick("namespaces", first=True)
@@ -1055,7 +1110,8 @@ else:
                 "rados_namespace": Param(str, "RADOS namespace name", True, None),
                 "rbd_pool": Param(str, "RBD pool name"),
                 "rbd_data_pool": Param(str, "RBD data pool name", True, None),
-                "nsid": Param(str, "Create RBD image", True, None),
+                "nsid": Param(str, "Namespace ID", True, None),
+                "uuid": Param(str, "UUID", True, None),
                 "create_image": Param(bool, "Create RBD image"),
                 "size": Param(int, "Deprecated. Use `rbd_image_size` instead"),
                 "rbd_image_size": Param(int, "RBD image size"),
@@ -1098,6 +1154,7 @@ else:
             rbd_pool: str = "rbd",
             rbd_data_pool: Optional[str] = None,
             nsid: Optional[str] = None,
+            uuid: Optional[str] = None,
             create_image: Optional[bool] = False,
             size: Optional[int] = None,
             rbd_image_size: Optional[int] = None,
@@ -1143,6 +1200,7 @@ else:
                 NVMeoFClient.pb2.namespace_add_req(
                     subsystem_nqn=nqn,
                     nsid=int(nsid) if nsid else None,
+                    uuid=uuid,
                     rbd_image_name=rbd_image_name,
                     rados_namespace_name=rados_namespace,
                     rbd_pool_name=rbd_pool,
@@ -1176,6 +1234,8 @@ else:
                 "rbd_data_pool": Param(str, "RBD data pool name", True, None),
                 "rados_namespace": Param(str, "RADOS namespace name", True, None),
                 "rbd_image_name": Param(str, "RBD image name"),
+                "nsid": Param(str, "Namespace ID", True, None),
+                "uuid": Param(str, "UUID", True, None),
                 "create_image": Param(bool, "Create RBD image"),
                 "size": Param(str, "Deprecated. Use `rbd_image_size` instead", True, None),
                 "rbd_image_size": Param(str, "RBD image size", True, None),
@@ -1218,6 +1278,7 @@ else:
             rbd_pool: str = "rbd",
             rbd_data_pool: Optional[str] = None,
             nsid: Optional[str] = None,
+            uuid: Optional[str] = None,
             create_image: Optional[bool] = False,
             size: Optional[str] = None,
             rbd_image_size: Optional[str] = None,
@@ -1278,6 +1339,7 @@ else:
                 NVMeoFClient.pb2.namespace_add_req(
                     subsystem_nqn=nqn,
                     nsid=int(nsid) if nsid else None,
+                    uuid=uuid,
                     rbd_image_name=rbd_image_name,
                     rados_namespace_name=rados_namespace,
                     rbd_pool_name=rbd_pool,
@@ -1638,6 +1700,170 @@ else:
                 )
             )
 
+        @ReadPermission
+        @Endpoint('PUT', '{nsid}/change_location')
+        @NvmeofCLICommand(
+            "nvmeof namespace change_location",
+            model=model.RequestStatus,
+            alias="nvmeof ns change_location",
+            success_message_template=(
+                'Setting location for namespace {nsid} in {nqn} to "{location}": Successful'
+            )
+        )
+        @EndpointDoc(
+            "Change the location of the specified NVMeoF namespace",
+            parameters={
+                "nqn": Param(str, "NVMeoF subsystem NQN"),
+                "nsid": Param(str, "NVMeoF Namespace ID"),
+                "location": Param(str, "Gateway location for namespace"),
+                "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                "server_address": Param(str, "NVMeoF gateway address", True, None),
+                "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+            },
+        )
+        @convert_to_model(model.RequestStatus)
+        @handle_nvmeof_error
+        def change_location(
+            self,
+            nqn: str,
+            nsid: str,
+            location: str,
+            gw_group: Optional[str] = None,
+            server_address: Optional[str] = None,
+            traddr: Optional[str] = None
+        ):
+            server_address = resolve_nvmeof_server_address(
+                server_address=server_address,
+                traddr=traddr
+            )
+            return NVMeoFClient(
+                gw_group=gw_group,
+                server_address=server_address
+            ).stub.namespace_change_location(
+                NVMeoFClient.pb2.namespace_change_location_req(
+                    subsystem_nqn=nqn,
+                    nsid=int(nsid),
+                    location=location,
+                )
+            )
+
+        @ReadPermission
+        @Endpoint('GET', 'list_hosts')
+        @NvmeofCLICommand(
+            "nvmeof namespace list_hosts",
+            model=model.NamespaceHostsList,
+            alias="nvmeof ns list_hosts"
+        )
+        @EndpointDoc(
+            "List all NVMeoF namespaces with their allowed hosts",
+            parameters={
+                "nqn": Param(str, "NVMeoF subsystem NQN", True, None),
+                "nsid": Param(str, "NVMeoF Namespace ID to filter by", True, None),
+                "uuid": Param(str, "NVMeoF Namespace UUID to filter by", True, None),
+                "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                "server_address": Param(str, "NVMeoF gateway address", True, None),
+                "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+            },
+        )
+        @handle_nvmeof_error
+        def list_hosts(
+            self, nqn: Optional[str] = None, nsid: Optional[str] = None,
+            uuid: Optional[str] = None,
+            gw_group: Optional[str] = None, server_address: Optional[str] = None,
+            traddr: Optional[str] = None
+        ):
+            server_address = resolve_nvmeof_server_address(
+                server_address=server_address,
+                traddr=traddr
+            )
+            ns_list = NVMeoFClient(
+                gw_group=gw_group,
+                server_address=server_address
+            ).stub.list_namespaces(
+                NVMeoFClient.pb2.list_namespaces_req(subsystem=nqn,
+                                                     nsid=int(nsid) if nsid else None,
+                                                     uuid=uuid)
+            )
+
+            # Transform to NamedTuple with only NQN, NSID, and Hosts
+            host_infos = []
+            for ns in ns_list.namespaces:
+                host_infos.append(model.NamespaceHostInfo(
+                    nqn=ns.ns_subsystem_nqn or nqn or "",
+                    nsid=ns.nsid,
+                    hosts=list(ns.hosts)
+                ))
+
+            return model.NamespaceHostsList(
+                status=ns_list.status,
+                error_message=ns_list.error_message,
+                namespaces=host_infos
+            )
+
+        @ReadPermission
+        @Endpoint('GET', 'list_locations')
+        @NvmeofCLICommand(
+            "nvmeof namespace list_locations",
+            model=model.NamespaceLocationsList,
+            alias="nvmeof ns list_locations"
+        )
+        @EndpointDoc(
+            "List namespace distribution per site locations",
+            parameters={
+                "nqn": Param(str, "NVMeoF subsystem NQN", True, None),
+                "nsid": Param(str, "NVMeoF Namespace ID to filter by", True, None),
+                "uuid": Param(str, "NVMeoF Namespace UUID to filter by", True, None),
+                "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                "server_address": Param(str, "NVMeoF gateway address", True, None),
+                "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+            },
+        )
+        @handle_nvmeof_error
+        def list_locations(
+            self, nqn: Optional[str] = None, nsid: Optional[str] = None,
+            uuid: Optional[str] = None,
+            gw_group: Optional[str] = None, server_address: Optional[str] = None,
+            traddr: Optional[str] = None
+        ):
+            server_address = resolve_nvmeof_server_address(
+                server_address=server_address,
+                traddr=traddr
+            )
+            ns_list = NVMeoFClient(
+                gw_group=gw_group,
+                server_address=server_address
+            ).stub.list_namespaces(
+                NVMeoFClient.pb2.list_namespaces_req(subsystem=nqn,
+                                                     nsid=int(nsid) if nsid else None,
+                                                     uuid=uuid)
+            )
+
+            # Aggregate namespaces by subsystem, load balancing group, and location
+            location_counts: Dict[Tuple[str, int, str], int] = {}
+            for ns in ns_list.namespaces:
+                subsystem_nqn = ns.ns_subsystem_nqn or nqn or ""
+                lb_group = ns.load_balancing_group if ns.load_balancing_group else 0
+                location = ns.location if ns.location else "<default>"
+
+                key = (subsystem_nqn, lb_group, location)
+                location_counts[key] = location_counts.get(key, 0) + 1
+
+            # Convert to NamedTuple list
+            location_infos = []
+            for (subsystem_nqn, lb_group, location), count in sorted(location_counts.items()):
+                location_infos.append(model.NamespaceLocationInfo(
+                    subsystem=subsystem_nqn,
+                    load_balancing_group=lb_group,
+                    location=location,
+                    namespace_count=count
+                ))
+
+            return model.NamespaceLocationsList(
+                status=ns_list.status,
+                error_message=ns_list.error_message,
+                locations=location_infos
+            )
+
         @ReadPermission
         @Endpoint('PUT', '{nsid}/set_auto_resize')
         @NvmeofCLICommand(
@@ -2064,6 +2290,10 @@ else:
             parameters={
                 "nqn": Param(str, "NVMeoF subsystem NQN"),
                 "host_nqn": Param(str, 'NVMeoF host NQN. Use "*" to disallow any host.'),
+                "force": Param(
+                    bool,
+                    "Delete the host even if it used in a namespace netmask",
+                    True, False),
                 "gw_group": Param(str, "NVMeoF gateway group", True, None),
                 "server_address": Param(str, "NVMeoF gateway address", True, None),
                 "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
@@ -2071,7 +2301,8 @@ else:
         )
         @convert_to_model(model.RequestStatus)
         @handle_nvmeof_error
-        def delete(self, nqn: str, host_nqn: str, gw_group: Optional[str] = None,
+        def delete(self, nqn: str, host_nqn: str, force: Optional[bool] = False,
+                   gw_group: Optional[str] = None,
                    server_address: Optional[str] = None,
                    traddr: Optional[str] = None):
             server_address = resolve_nvmeof_server_address(
@@ -2082,7 +2313,7 @@ else:
                 gw_group=gw_group,
                 server_address=server_address
             ).stub.remove_host(
-                NVMeoFClient.pb2.remove_host_req(subsystem_nqn=nqn, host_nqn=host_nqn)
+                NVMeoFClient.pb2.remove_host_req(subsystem_nqn=nqn, host_nqn=host_nqn, force=force)
             )
 
         @empty_response
index d832eb26140cb90bb28fbd498afd44137363c574..f4b7327514e24c9ce8476a41ea0c14de50066a1f 100644 (file)
@@ -207,6 +207,35 @@ class NamespaceList(NamedTuple):
     namespaces: Annotated[List[Namespace], CliFlags.EXCLUSIVE_LIST]
 
 
+class NamespaceHostInfo(NamedTuple):
+    nqn: Annotated[str, CliHeader("NQN")]
+    nsid: Annotated[int, CliHeader("NSID")]
+    hosts: Annotated[
+        List[str],
+        CliHeader("Hosts"),
+        CliFieldTransformer(lambda v: "\n".join(v) if v else "None")
+    ]
+
+
+class NamespaceHostsList(NamedTuple):
+    status: int
+    error_message: str
+    namespaces: Annotated[List[NamespaceHostInfo], CliFlags.EXCLUSIVE_LIST]
+
+
+class NamespaceLocationInfo(NamedTuple):
+    subsystem: Annotated[str, CliHeader("Subsystem")]
+    load_balancing_group: Annotated[int, CliHeader("Load Balancing Group")]
+    location: Annotated[str, CliHeader("Location")]
+    namespace_count: Annotated[int, CliHeader("Count")]
+
+
+class NamespaceLocationsList(NamedTuple):
+    status: int
+    error_message: str
+    locations: Annotated[List[NamespaceLocationInfo], CliFlags.EXCLUSIVE_LIST]
+
+
 class NamespaceIOStats(NamedTuple):
     status: Annotated[int, CliFlags.DROP]
     error_message: Annotated[str, CliFlags.DROP]
@@ -292,6 +321,19 @@ class GatewayStatsInfo(NamedTuple):
     poll_groups: Annotated[List[PollGroupInfo], CliFlags.EXCLUSIVE_LIST]
 
 
+class SpdkThreadInfo(NamedTuple):
+    name: str
+    busy: int
+    idle: int
+
+
+class ThreadStatsInfo(NamedTuple):
+    status: int
+    error_message: str
+    tick_rate: int
+    threads: Annotated[List[SpdkThreadInfo], CliFlags.EXCLUSIVE_LIST]
+
+
 class AnaState(Enum):
     UNSET = 0
     OPTIMIZED = 1
index abb7a7351ff5a1f2aa8c9c58aec6380ce0e22e99..2ddd1190b47e51fd49eb4de01170e8bd513ee935 100644 (file)
@@ -12961,6 +12961,51 @@ paths:
       summary: Get NVMeoF statistics for the gateway
       tags:
       - NVMe-oF Gateway
+  /api/nvmeof/gateway/thread_stats:
+    get:
+      parameters:
+      - allowEmptyValue: true
+        description: NVMeoF gateway group
+        in: query
+        name: gw_group
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway address
+        in: query
+        name: server_address
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway address (deprecated)
+        in: query
+        name: traddr
+        schema:
+          type: string
+      responses:
+        '200':
+          content:
+            application/json:
+              schema:
+                type: object
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Get NVMeoF thread statistics for the gateway
+      tags:
+      - NVMe-oF Gateway
   /api/nvmeof/gateway/version:
     get:
       parameters:
@@ -13177,6 +13222,18 @@ paths:
   /api/nvmeof/subsystem:
     get:
       parameters:
+      - allowEmptyValue: true
+        description: NVMeoF subsystem NQN to filter by
+        in: query
+        name: nqn
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: Serial number to filter by
+        in: query
+        name: serial_number
+        schema:
+          type: string
       - allowEmptyValue: true
         description: NVMeoF gateway group
         in: query
@@ -13599,6 +13656,12 @@ paths:
         required: true
         schema:
           type: string
+      - default: false
+        description: Delete the host even if it used in a namespace netmask
+        in: query
+        name: force
+        schema:
+          type: boolean
       - allowEmptyValue: true
         description: NVMeoF gateway group
         in: query
@@ -13980,6 +14043,10 @@ paths:
                   default: 0
                   description: NVMeoF address family (0 - IPv4, 1 - IPv6)
                   type: integer
+                force:
+                  default: false
+                  description: Allow contradiction in security with existing listeners
+                  type: boolean
                 gw_group:
                   description: NVMeoF gateway group
                   type: string
@@ -14075,6 +14142,8 @@ paths:
         schema:
           type: integer
       - default: false
+        description: Delete listener even if there are active connections or host
+          name doesn't match
         in: query
         name: force
         schema:
@@ -14139,6 +14208,12 @@ paths:
         name: nsid
         schema:
           type: string
+      - allowEmptyValue: true
+        description: NVMeoF Namespace UUID to filter by
+        in: query
+        name: uuid
+        schema:
+          type: string
       - allowEmptyValue: true
         description: NVMeoF gateway group
         in: query
@@ -14240,7 +14315,7 @@ paths:
                   description: Namespace will be visible only for the allowed hosts
                   type: boolean
                 nsid:
-                  description: Create RBD image
+                  description: Namespace ID
                   type: string
                 rados_namespace:
                   description: RADOS namespace name
@@ -14275,6 +14350,9 @@ paths:
                   default: false
                   description: Trash the RBD image when namespace is removed
                   type: boolean
+                uuid:
+                  description: UUID
+                  type: string
               required:
               - rbd_image_name
               type: object
@@ -14311,6 +14389,132 @@ paths:
       summary: Create a new NVMeoF namespace.
       tags:
       - NVMe-oF Subsystem Namespace
+  /api/nvmeof/subsystem/{nqn}/namespace/list_hosts:
+    get:
+      parameters:
+      - allowEmptyValue: true
+        description: NVMeoF subsystem NQN
+        in: path
+        name: nqn
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF Namespace ID to filter by
+        in: query
+        name: nsid
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF Namespace UUID to filter by
+        in: query
+        name: uuid
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway group
+        in: query
+        name: gw_group
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway address
+        in: query
+        name: server_address
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway address (deprecated)
+        in: query
+        name: traddr
+        schema:
+          type: string
+      responses:
+        '200':
+          content:
+            application/json:
+              schema:
+                type: object
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: List all NVMeoF namespaces with their allowed hosts
+      tags:
+      - NVMe-oF Subsystem Namespace
+  /api/nvmeof/subsystem/{nqn}/namespace/list_locations:
+    get:
+      parameters:
+      - allowEmptyValue: true
+        description: NVMeoF subsystem NQN
+        in: path
+        name: nqn
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF Namespace ID to filter by
+        in: query
+        name: nsid
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF Namespace UUID to filter by
+        in: query
+        name: uuid
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway group
+        in: query
+        name: gw_group
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway address
+        in: query
+        name: server_address
+        schema:
+          type: string
+      - allowEmptyValue: true
+        description: NVMeoF gateway address (deprecated)
+        in: query
+        name: traddr
+        schema:
+          type: string
+      responses:
+        '200':
+          content:
+            application/json:
+              schema:
+                type: object
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: List namespace distribution per site locations
+      tags:
+      - NVMe-oF Subsystem Namespace
   /api/nvmeof/subsystem/{nqn}/namespace/{nsid}:
     delete:
       parameters:
@@ -14654,6 +14858,74 @@ paths:
       summary: set the load balancing group for specified NVMeoF namespace
       tags:
       - NVMe-oF Subsystem Namespace
+  /api/nvmeof/subsystem/{nqn}/namespace/{nsid}/change_location:
+    put:
+      parameters:
+      - description: NVMeoF subsystem NQN
+        in: path
+        name: nqn
+        required: true
+        schema:
+          type: string
+      - description: NVMeoF Namespace ID
+        in: path
+        name: nsid
+        required: true
+        schema:
+          type: string
+      requestBody:
+        content:
+          application/json:
+            schema:
+              properties:
+                gw_group:
+                  description: NVMeoF gateway group
+                  type: string
+                location:
+                  description: Gateway location for namespace
+                  type: string
+                server_address:
+                  description: NVMeoF gateway address
+                  type: string
+                traddr:
+                  description: NVMeoF gateway address (deprecated)
+                  type: string
+              required:
+              - location
+              type: object
+      responses:
+        '200':
+          content:
+            application/json:
+              schema:
+                type: object
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                type: object
+          description: Resource updated.
+        '202':
+          content:
+            application/json:
+              schema:
+                type: object
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                type: object
+          description: Operation is still executing. Please check the task queue.
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Change the location of the specified NVMeoF namespace
+      tags:
+      - NVMe-oF Subsystem Namespace
   /api/nvmeof/subsystem/{nqn}/namespace/{nsid}/change_visibility:
     put:
       parameters: