]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add apis for add/del hosts on namespaces 67277/head
authorAfreen Misbah <afreen@ibm.com>
Mon, 9 Feb 2026 16:15:03 +0000 (21:45 +0530)
committerAfreen Misbah <afreen@ibm.com>
Tue, 17 Feb 2026 19:37:00 +0000 (01:07 +0530)
- these are UI APIs
- also removed namespace API and using "*" in existing instead for getting all ns in subsystem

Signed-off-by: Afreen Misbah <afreen@ibm.com>
src/pybind/mgr/dashboard/controllers/nvmeof.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-form/nvmeof-initiators-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts
src/pybind/mgr/dashboard/openapi.yaml

index f6f022d1d14a9f550c82ddd2095d7ad7ee9b87f0..63a4d7fe7207a440e14675b6dde975f44897d346 100644 (file)
@@ -474,27 +474,6 @@ else:
                 )
             )
 
-    @APIRouter("/nvmeof/gateway_group/{gw_group}/namespace", Scope.NVME_OF)
-    @APIDoc("NVMe-oF Gateway Management API", "NVMe-oF Gateway")
-    class NVMeoFGatewayGroup(RESTController):
-        @Endpoint('GET')
-        @EndpointDoc(
-            "List all NVMeoF namespaces in a gateway group",
-            parameters={
-                "gw_group": Param(str, "NVMeoF gateway group", True, None),
-                "server_address": Param(str, "NVMeoF gateway address", True, None),
-            },
-        )
-        @convert_to_model(model.NamespaceList)
-        @handle_nvmeof_error
-        def list(self, gw_group: str, server_address: Optional[str] = None):
-            return NVMeoFClient(
-                gw_group=gw_group,
-                server_address=server_address
-            ).stub.list_namespaces(
-                NVMeoFClient.pb2.list_namespaces_req()
-            )
-
     @APIRouter("/nvmeof/subsystem/{nqn}/namespace", Scope.NVME_OF)
     @APIDoc("NVMe-oF Subsystem Namespace Management API", "NVMe-oF Subsystem Namespace")
     class NVMeoFNamespace(RESTController):
@@ -1423,6 +1402,7 @@ else:
                     status["available"] = False
                     status["message"] = 'An NVMe/TCP service must be created.'
             return status
+        # UI API for adding one or more than one hosts to subsystem
 
         @Endpoint('POST', "/subsystem/{subsystem_nqn}/host")
         @EndpointDoc("Add one or more initiator hosts to an NVMeoF subsystem",
@@ -1434,41 +1414,22 @@ else:
         @empty_response
         @handle_nvmeof_error
         @CreatePermission
-        def add(self, subsystem_nqn: str, host_nqn: str, dhchap_key: Optional[str] = None,
-                psk: Optional[str] = None, gw_group: Optional[str] = None,
-                server_address: Optional[str] = None
-                ):
+        def add(self, subsystem_nqn: str,
+                gw_group: str,
+                host_nqn: str = "",
+                server_address: Optional[str] = None):
             response = None
+            all_host_nqns = host_nqn.split(',')
 
-            if host_nqn != '*':
-                all_host_nqns = host_nqn.split(',')
-                for nqn in all_host_nqns:
-                    response = NVMeoFClient(
-                        gw_group=gw_group,
-                        server_address=server_address
-                    ).stub.add_host(
-                        NVMeoFClient.pb2.add_host_req(
-                            subsystem_nqn=subsystem_nqn,
-                            host_nqn=nqn,
-                            dhchap_key=dhchap_key,
-                            psk=psk)
-                    )
-                    if response.status != 0:
-                        return response
-            else:
-                response = NVMeoFClient(
-                    gw_group=gw_group,
-                    server_address=server_address
-                ).stub.add_host(
-                    NVMeoFClient.pb2.add_host_req(
-                        subsystem_nqn=subsystem_nqn,
-                        host_nqn=host_nqn,
-                        dhchap_key=dhchap_key,
-                        psk=psk)
+            for nqn in all_host_nqns:
+                response = NVMeoFClient(gw_group=gw_group,
+                                        server_address=server_address).stub.add_host(
+                    NVMeoFClient.pb2.add_host_req(subsystem_nqn=subsystem_nqn, host_nqn=nqn)
                 )
                 if response.status != 0:
                     return response
             return response
+        # UI API for deleting one or more than one hosts to subsystem
 
         @Endpoint(method='DELETE', path="/subsystem/{subsystem_nqn}/host/{host_nqn}")
         @EndpointDoc("Remove on or more initiator hosts from an NVMeoF subsystem",
@@ -1480,12 +1441,16 @@ else:
         @empty_response
         @handle_nvmeof_error
         @DeletePermission
-        def remove(self, subsystem_nqn: str, host_nqn: str, gw_group: str):
+        def remove(self, subsystem_nqn: str,
+                   host_nqn: str,
+                   gw_group: str,
+                   server_address: Optional[str] = None):
             response = None
             to_delete_nqns = host_nqn.split(',')
 
             for del_nqn in to_delete_nqns:
-                response = NVMeoFClient(gw_group=gw_group).stub.remove_host(
+                response = NVMeoFClient(gw_group=gw_group,
+                                        server_address=server_address).stub.remove_host(
                     NVMeoFClient.pb2.remove_host_req(subsystem_nqn=subsystem_nqn, host_nqn=del_nqn)
                 )
                 if response.status != 0:
@@ -1493,3 +1458,80 @@ else:
                 logger.info("removed host %s from subsystem %s", del_nqn, subsystem_nqn)
 
             return response
+        # UI API for adding one or more than one hosts to namespace
+
+        @Endpoint('POST', "/namespace/{nsid}/host")
+        @EndpointDoc("Add one or more initiator hosts to an NVMeoF subsystem",
+                     parameters={
+                         "subsystem_nqn": Param(str, "NVMeoF subsystem NQN"),
+                         "nsid": Param(str, "NVMeoF Namespace ID"),
+                         "host_nqn": Param(str, 'Comma separated list of NVMeoF host NQN.'),
+                         "force": Param(
+                             bool,
+                             "Allow adding the host to the namespace even if the host "
+                             "has no access to the subsystem"
+                         ),
+                         "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                         "server_address": Param(str, "NVMeoF gateway address", True, None),
+                     })
+        @empty_response
+        @handle_nvmeof_error
+        @CreatePermission
+        def add_namesapce_initiator(self, nsid: str,
+                                    subsystem_nqn: str,
+                                    gw_group: str,
+                                    host_nqn: str = "",
+                                    force: Optional[bool] = None,
+                                    server_address: Optional[str] = None):
+            response = None
+            all_host_nqns = host_nqn.split(',')
+            force = str_to_bool(force) if force else None
+
+            for nqn in all_host_nqns:
+                response = NVMeoFClient(gw_group=gw_group,
+                                        server_address=server_address).stub.namespace_add_host(
+                    NVMeoFClient.pb2.namespace_add_host_req(subsystem_nqn=subsystem_nqn,
+                                                            nsid=int(nsid),
+                                                            host_nqn=nqn,
+                                                            force=force)
+                )
+                if response.status != 0:
+                    return response
+            return response
+        # UI API for deleting one or more than one hosts to namespace
+
+        @Endpoint(method='DELETE', path="/namespace/{nsid}/host")
+        @EndpointDoc("Remove on or more initiator hosts from an NVMeoF subsystem",
+                     parameters={
+                         "subsystem_nqn": Param(str, "NVMeoF subsystem NQN"),
+                         "nsid": Param(str, "NVMeoF Namespace ID"),
+                         "host_nqn": Param(str, 'Comma separated list of NVMeoF host NQN.'),
+                         "gw_group": Param(str, "NVMeoF gateway group", True, None),
+                         "server_address": Param(str, "NVMeoF gateway address", True, None),
+                     })
+        @empty_response
+        @handle_nvmeof_error
+        @DeletePermission
+        def remove_namespace_initiator(self,
+                                       nsid: str,
+                                       subsystem_nqn: str,
+                                       host_nqn: str,
+                                       gw_group: str,
+                                       server_address: Optional[str] = None):
+            response = None
+            to_delete_nqns = host_nqn.split(',')
+
+            for del_nqn in to_delete_nqns:
+                response = NVMeoFClient(gw_group=gw_group,
+                                        server_address=server_address).stub.namespace_delete_host(
+                    NVMeoFClient.pb2.namespace_delete_host_req(
+                        subsystem_nqn=subsystem_nqn,
+                        nsid=int(nsid),
+                        host_nqn=del_nqn,
+                    )
+                )
+                if response.status != 0:
+                    return response
+                logger.info("removed host %s from namespace %s", del_nqn, nsid)
+
+            return response
index f6da04f5ec071297efa68cea61ba125ac89a47ed..7df77e1b99d96a6bd43789a176e27d158990dc0b 100644 (file)
@@ -46,14 +46,14 @@ describe('NvmeofInitiatorsFormComponent', () => {
   describe('should test form', () => {
     beforeEach(() => {
       nvmeofService = TestBed.inject(NvmeofService);
-      spyOn(nvmeofService, 'addInitiators').and.stub();
+      spyOn(nvmeofService, 'addSubsystemInitiators').and.stub();
     });
 
     it('should be creating request correctly', () => {
       const subsystemNQN = 'nqn.2001-07.com.ceph:' + mockTimestamp;
       component.subsystemNQN = subsystemNQN;
       component.onSubmit();
-      expect(nvmeofService.addInitiators).toHaveBeenCalledWith(subsystemNQN, {
+      expect(nvmeofService.addSubsystemInitiators).toHaveBeenCalledWith(subsystemNQN, {
         host_nqn: ''
       });
     });
index 2c4adf51ae5c47f6fb2c52388926f03961592ff5..4446a2de9cbdc38ed76420771479294e123f5c8d 100644 (file)
@@ -127,7 +127,7 @@ export class NvmeofInitiatorsFormComponent implements OnInit {
         task: new FinishedTask(taskUrl, {
           nqn: this.subsystemNQN
         }),
-        call: this.nvmeofService.addInitiators(this.subsystemNQN, request)
+        call: this.nvmeofService.addSubsystemInitiators(this.subsystemNQN, request)
       })
       .subscribe({
         error() {
index 9e14697ab2eb34de58f608b738646776a9c07b5a..4cccb4e154f5cb8a71be761ac64063d87bad0cd5 100644 (file)
@@ -117,7 +117,7 @@ export class NvmeofInitiatorsListComponent implements OnInit {
             nqn: this.subsystemNQN,
             plural: itemNames.length > 1
           }),
-          call: this.nvmeofService.removeInitiators(this.subsystemNQN, {
+          call: this.nvmeofService.removeSubsystemInitiators(this.subsystemNQN, {
             host_nqn,
             gw_group: this.group
           })
index 4c7e3d6c3f0f91bbaa80e772b0eec839826389fe..78aae18c0f9c33bf221deaf01c85a7cb784a2a54 100644 (file)
@@ -73,7 +73,7 @@ describe('NvmeofSubsystemsFormComponent', () => {
     beforeEach(() => {
       nvmeofService = TestBed.inject(NvmeofService);
       spyOn(nvmeofService, 'createSubsystem').and.returnValue(of({}));
-      spyOn(nvmeofService, 'addInitiators').and.returnValue(of({}));
+      spyOn(nvmeofService, 'addSubsystemInitiators').and.returnValue(of({}));
     });
 
     it('should be creating request correctly', () => {
@@ -100,7 +100,7 @@ describe('NvmeofSubsystemsFormComponent', () => {
       component.group = mockGroupName;
       component.onSubmit(payload);
 
-      expect(nvmeofService.addInitiators).toHaveBeenCalledWith('test-nqn.default', {
+      expect(nvmeofService.addSubsystemInitiators).toHaveBeenCalledWith('test-nqn.default', {
         host_nqn: '*',
         gw_group: mockGroupName
       });
index fb25ba1f815d2825517db21e07d787fd7a19ad9e..7de6ae958a5e23cf854a5fd2ed30c2a265a83d4b 100644 (file)
@@ -97,7 +97,10 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
               {
                 step: this.steps[1].label,
                 call: () =>
-                  this.nvmeofService.addInitiators(`${payload.nqn}.${this.group}`, initiatorRequest)
+                  this.nvmeofService.addSubsystemInitiators(
+                    `${payload.nqn}.${this.group}`,
+                    initiatorRequest
+                  )
               }
             ],
             stepResults
index 36c6b752a8b7ce22dc766d24395a3ac7ac11c3c0..5873fc79caf05d0275dc260f89a0273b2431e510 100755 (executable)
@@ -199,12 +199,12 @@ describe('NvmeofService', () => {
       expect(req.request.method).toBe('GET');
     });
     it('should call addInitiators', () => {
-      service.addInitiators(mockNQN, request).subscribe();
+      service.addSubsystemInitiators(mockNQN, request).subscribe();
       const req = httpTesting.expectOne(`${UI_API_PATH}/subsystem/${mockNQN}/host`);
       expect(req.request.method).toBe('POST');
     });
     it('should call removeInitiators', () => {
-      service.removeInitiators(mockNQN, request).subscribe();
+      service.removeSubsystemInitiators(mockNQN, request).subscribe();
       const req = httpTesting.expectOne(
         `${UI_API_PATH}/subsystem/${mockNQN}/host/${request.host_nqn}/${mockGroupName}`
       );
@@ -247,7 +247,9 @@ describe('NvmeofService', () => {
     const mockNsid = '1';
     it('should call listNamespaces', () => {
       service.listNamespaces(mockGroupName).subscribe();
-      const req = httpTesting.expectOne(`${API_PATH}/gateway_group/${mockGroupName}/namespace`);
+      const req = httpTesting.expectOne(
+        `${API_PATH}/subsystem/*/namespace?gw_group=${mockGroupName}`
+      );
       expect(req.request.method).toBe('GET');
     });
     it('should call getNamespace', () => {
index 8937eb5026b7da816a159b140151707efc51a224..3ede27f1ecb593f3313af7a82d39ea4aa345a20b 100644 (file)
@@ -35,6 +35,7 @@ export type NamespaceCreateRequest = NvmeofRequest & {
   rbd_image_name: string;
   rbd_pool: string;
   rbd_image_size?: number;
+  no_auto_visible?: boolean;
   create_image: boolean;
 };
 
@@ -46,6 +47,10 @@ export type InitiatorRequest = NvmeofRequest & {
   host_nqn: string;
 };
 
+export type NamespaceInitiatorRequest = InitiatorRequest & {
+  subsystem_nqn: string;
+};
+
 const API_PATH = 'api/nvmeof';
 const UI_API_PATH = 'ui-api/nvmeof';
 
@@ -174,13 +179,19 @@ export class NvmeofService {
     return this.http.get(`${API_PATH}/subsystem/${subsystemNQN}/host?gw_group=${group}`);
   }
 
-  addInitiators(subsystemNQN: string, request: InitiatorRequest) {
+  addSubsystemInitiators(subsystemNQN: string, request: InitiatorRequest) {
     return this.http.post(`${UI_API_PATH}/subsystem/${subsystemNQN}/host`, request, {
       observe: 'response'
     });
   }
 
-  removeInitiators(subsystemNQN: string, request: InitiatorRequest) {
+  addNamespaceInitiators(nsid: string, request: NamespaceInitiatorRequest) {
+    return this.http.post(`${UI_API_PATH}/namespace/${nsid}/host`, request, {
+      observe: 'response'
+    });
+  }
+
+  removeSubsystemInitiators(subsystemNQN: string, request: InitiatorRequest) {
     return this.http.delete(
       `${UI_API_PATH}/subsystem/${subsystemNQN}/host/${request.host_nqn}/${request.gw_group}`,
       {
@@ -189,6 +200,15 @@ export class NvmeofService {
     );
   }
 
+  removeNamespaceInitiators(nsid: string, request: NamespaceInitiatorRequest) {
+    return this.http.delete(
+      `${UI_API_PATH}/namespace/${nsid}/host/${request.subsystem_nqn}/${request.host_nqn}/${request.gw_group}`,
+      {
+        observe: 'response'
+      }
+    );
+  }
+
   // Listeners
   listListeners(subsystemNQN: string, group: string) {
     return this.http.get(`${API_PATH}/subsystem/${subsystemNQN}/listener?gw_group=${group}`);
@@ -221,8 +241,8 @@ export class NvmeofService {
   }
 
   // Namespaces
-  listNamespaces(group: string) {
-    return this.http.get(`${API_PATH}/gateway_group/${group}/namespace`);
+  listNamespaces(group: string, subsystemNQN: string = '*') {
+    return this.http.get(`${API_PATH}/subsystem/${subsystemNQN}/namespace?gw_group=${group}`);
   }
 
   getNamespace(subsystemNQN: string, nsid: string, group: string) {
index c20e63e4a6a57bef469c369c0b1b34e18480a052..fd80d9a14667ecb1f878208b8f0f85183ce1d0dc 100755 (executable)
@@ -12696,84 +12696,6 @@ paths:
       summary: Get the version of the NVMeoF gateway
       tags:
       - NVMe-oF Gateway
-  /api/nvmeof/gateway_group/{gw_group}/namespace:
-    get:
-      parameters:
-      - description: NVMeoF gateway group
-        in: path
-        name: gw_group
-        required: true
-        schema:
-          type: string
-      - allowEmptyValue: true
-        description: NVMeoF gateway address
-        in: query
-        name: server_address
-        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 in a gateway group
-      tags:
-      - NVMe-oF Gateway
-  /api/nvmeof/gateway_group/{gw_group}/namespace/list:
-    get:
-      parameters:
-      - description: NVMeoF gateway group
-        in: path
-        name: gw_group
-        required: true
-        schema:
-          type: string
-      - allowEmptyValue: true
-        description: NVMeoF gateway address
-        in: query
-        name: server_address
-        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 in a gateway group
-      tags:
-      - NVMe-oF Gateway
   /api/nvmeof/spdk/log_level:
     get:
       parameters: