]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add group selector in subsystems views 59807/head
authorAfreen Misbah <afreen23.git@gmail.com>
Wed, 18 Sep 2024 10:26:25 +0000 (15:56 +0530)
committerAfreen Misbah <afreen23.git@gmail.com>
Thu, 19 Sep 2024 08:14:17 +0000 (13:44 +0530)
Allows listing the subsystems per gateway group.
Using carbon combobox component for the selector.
Adds unit tests for switcher and updates existing.

Fixes https://tracker.ceph.com/issues/68129

Signed-off-by: Afreen Misbah <afreen23.git@gmail.com>
12 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.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/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.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

index 4f3531c3d5e59f5df8393b990749ee5bc3f44f68..b6f04cadcc15c55be70898c2b44ce27c7412f4cc 100644 (file)
@@ -53,6 +53,7 @@ import { NvmeofInitiatorsFormComponent } from './nvmeof-initiators-form/nvmeof-i
 import {
   ButtonModule,
   CheckboxModule,
+  ComboBoxModule,
   DatePickerModule,
   GridModule,
   IconModule,
@@ -95,7 +96,8 @@ import Reset from '@carbon/icons/es/reset/32';
     SelectModule,
     NumberModule,
     ModalModule,
-    DatePickerModule
+    DatePickerModule,
+    ComboBoxModule
   ],
   declarations: [
     RbdListComponent,
index 412286bda2098087ea94304d6a1c8968a41ba486..cd362bf8abe19cff6343013597f8dcc82b2b2633 100644 (file)
@@ -13,9 +13,9 @@ import { FormatterService } from '~/app/shared/services/formatter.service';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
 import { HostService } from '~/app/shared/api/host.service';
-import { DaemonService } from '~/app/shared/api/daemon.service';
 import { map } from 'rxjs/operators';
 import { forkJoin } from 'rxjs';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
 
 @Component({
   selector: 'cd-nvmeof-listeners-form',
@@ -31,6 +31,7 @@ export class NvmeofListenersFormComponent implements OnInit {
   listenerForm: CdFormGroup;
   subsystemNQN: string;
   hosts: Array<object> = null;
+  group: string;
 
   constructor(
     public actionLabels: ActionLabelsI18n,
@@ -42,8 +43,7 @@ export class NvmeofListenersFormComponent implements OnInit {
     private route: ActivatedRoute,
     public activeModal: NgbActiveModal,
     public formatterService: FormatterService,
-    public dimlessBinaryPipe: DimlessBinaryPipe,
-    private daemonService: DaemonService
+    public dimlessBinaryPipe: DimlessBinaryPipe
   ) {
     this.permission = this.authStorageService.getPermissions().nvmeof;
     this.hostPermission = this.authStorageService.getPermissions().hosts;
@@ -53,13 +53,20 @@ export class NvmeofListenersFormComponent implements OnInit {
 
   setHosts() {
     forkJoin({
-      daemons: this.daemonService.list(['nvmeof']),
+      gwGroups: this.nvmeofService.listGatewayGroups(),
       hosts: this.hostService.getAllHosts()
     })
       .pipe(
-        map(({ daemons, hosts }) => {
-          const hostNamesFromDaemon = daemons.map((daemon: any) => daemon.hostname);
-          return hosts.filter((host: any) => hostNamesFromDaemon.includes(host.hostname));
+        map(({ gwGroups, hosts }) => {
+          // Find the gateway hosts in current group
+          const selectedGwGroup: CephServiceSpec = gwGroups?.[0]?.find(
+            (gwGroup: CephServiceSpec) => gwGroup?.spec?.group === this.group
+          );
+          const gatewayHosts: string[] = selectedGwGroup?.placement?.hosts;
+          // Return the gateway hosts in current group with their metadata
+          return gatewayHosts
+            ? hosts.filter((host: any) => gatewayHosts.includes(host.hostname))
+            : [];
         })
       )
       .subscribe((nvmeofHosts: any[]) => {
@@ -71,7 +78,10 @@ export class NvmeofListenersFormComponent implements OnInit {
     this.createForm();
     this.action = this.actionLabels.CREATE;
     this.route.params.subscribe((params: { subsystem_nqn: string }) => {
-      this.subsystemNQN = params.subsystem_nqn;
+      this.subsystemNQN = params?.subsystem_nqn;
+    });
+    this.route.queryParams.subscribe((params) => {
+      this.group = params?.['group'];
     });
     this.setHosts();
   }
@@ -118,7 +128,9 @@ export class NvmeofListenersFormComponent implements OnInit {
           component.listenerForm.setErrors({ cdSubmitButton: true });
         },
         complete: () => {
-          this.router.navigate([this.pageURL, { outlets: { modal: null } }]);
+          this.router.navigate([this.pageURL, { outlets: { modal: null } }], {
+            queryParams: { group: this.group }
+          });
         }
       });
   }
index 3ece51f350d48fe84a188fa869e32863d7a940c6..f88442e1bd6193d2179d60ddb1fed9a9d3c10aa1 100644 (file)
@@ -24,6 +24,8 @@ const BASE_URL = 'block/nvmeof/subsystems';
 export class NvmeofListenersListComponent implements OnInit, OnChanges {
   @Input()
   subsystemNQN: string;
+  @Input()
+  group: string;
 
   listenerColumns: any;
   tableActions: CdTableAction[];
@@ -64,10 +66,10 @@ export class NvmeofListenersListComponent implements OnInit, OnChanges {
         permission: 'create',
         icon: Icons.add,
         click: () =>
-          this.router.navigate([
-            BASE_URL,
-            { outlets: { modal: [URLVerbs.CREATE, this.subsystemNQN, 'listener'] } }
-          ]),
+          this.router.navigate(
+            [BASE_URL, { outlets: { modal: [URLVerbs.CREATE, this.subsystemNQN, 'listener'] } }],
+            { queryParams: { group: this.group } }
+          ),
         canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
       },
       {
index 3749d47bccfa018bd6d1cdd38bad9081a2fe11f4..7f15a1360adc239b893ac50f02f16f824b1b0f2c 100644 (file)
@@ -15,7 +15,8 @@
       <a ngbNavLink
          i18n>Listeners</a>
       <ng-template ngbNavContent>
-        <cd-nvmeof-listeners-list [subsystemNQN]="subsystemNQN">
+        <cd-nvmeof-listeners-list [subsystemNQN]="subsystemNQN"
+                                  [group]="group">
         </cd-nvmeof-listeners-list>
       </ng-template>
     </ng-container>
index 211905f285fbfd78a87ffd303741bfabf68003f2..cc561266677cadc93c64bb695239ec5f76223e64 100644 (file)
@@ -9,6 +9,8 @@ import { NvmeofSubsystem } from '~/app/shared/models/nvmeof';
 export class NvmeofSubsystemsDetailsComponent implements OnChanges {
   @Input()
   selection: NvmeofSubsystem;
+  @Input()
+  group: NvmeofSubsystem;
 
   selectedItem: any;
   data: any;
index 08e56debf0a741b77efce45be0809e5ad6e68a75..0f34803b7efb1919516c4fa73e95cc97a777fcef 100644 (file)
@@ -20,6 +20,7 @@ describe('NvmeofSubsystemsFormComponent', () => {
   let form: CdFormGroup;
   let formHelper: FormHelper;
   const mockTimestamp = 1720693470789;
+  const mockGroupName = 'default';
 
   beforeEach(async () => {
     spyOn(Date, 'now').and.returnValue(mockTimestamp);
@@ -42,6 +43,7 @@ describe('NvmeofSubsystemsFormComponent', () => {
     form = component.subsystemForm;
     formHelper = new FormHelper(form);
     fixture.detectChanges();
+    component.group = mockGroupName;
   });
 
   it('should create', () => {
@@ -60,7 +62,8 @@ describe('NvmeofSubsystemsFormComponent', () => {
       expect(nvmeofService.createSubsystem).toHaveBeenCalledWith({
         nqn: expectedNqn,
         max_namespaces: MAX_NAMESPACE,
-        enable_ha: true
+        enable_ha: true,
+        gw_group: mockGroupName
       });
     });
 
index 5c2e1ce5250eedbec9c7a40687d8ca30a54cea66..f7b35a2d645ec341ad99620276cdaf754df3e37d 100644 (file)
@@ -9,7 +9,7 @@ import { Permission } from '~/app/shared/models/permissions';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { FinishedTask } from '~/app/shared/models/finished-task';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
 import { MAX_NAMESPACE, NvmeofService } from '~/app/shared/api/nvmeof.service';
 
 @Component({
@@ -24,6 +24,7 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
   resource: string;
   pageURL: string;
   defaultMaxNamespace: number = MAX_NAMESPACE;
+  group: string;
 
   constructor(
     private authStorageService: AuthStorageService,
@@ -31,7 +32,8 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
     public activeModal: NgbActiveModal,
     private nvmeofService: NvmeofService,
     private taskWrapperService: TaskWrapperService,
-    private router: Router
+    private router: Router,
+    private route: ActivatedRoute
   ) {
     this.permission = this.authStorageService.getPermissions().nvmeof;
     this.resource = $localize`Subsystem`;
@@ -49,6 +51,9 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
   );
 
   ngOnInit() {
+    this.route.queryParams.subscribe((params) => {
+      this.group = params?.['group'];
+    });
     this.createForm();
     this.action = this.actionLabels.CREATE;
   }
@@ -66,7 +71,13 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
           )
         ],
         asyncValidators: [
-          CdValidators.unique(this.nvmeofService.isSubsystemPresent, this.nvmeofService)
+          CdValidators.unique(
+            this.nvmeofService.isSubsystemPresent,
+            this.nvmeofService,
+            null,
+            null,
+            this.group
+          )
         ]
       }),
       max_namespaces: new UntypedFormControl(this.defaultMaxNamespace, {
@@ -87,8 +98,9 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
 
     const request = {
       nqn,
-      max_namespaces,
-      enable_ha: true
+      enable_ha: true,
+      gw_group: this.group,
+      max_namespaces
     };
 
     if (!max_namespaces) {
@@ -106,7 +118,9 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
           component.subsystemForm.setErrors({ cdSubmitButton: true });
         },
         complete: () => {
-          this.router.navigate([this.pageURL, { outlets: { modal: null } }]);
+          this.router.navigate([this.pageURL, { outlets: { modal: null } }], {
+            queryParams: { group: this.group }
+          });
         }
       });
   }
index 6cd1f205913b972b8187ff14d2f173cc98f4ce32..1d5c1324ecbd3da4375e9f293f415092370637d6 100644 (file)
@@ -1,4 +1,20 @@
 <cd-nvmeof-tabs></cd-nvmeof-tabs>
+
+<div class="pb-3"
+     cdsCol
+     [columnNumbers]="{md: 4}">
+  <cds-combo-box
+      type="single"
+      label="Selected Gateway Group"
+      i18n-placeholder
+      placeholder="Enter group"
+      [items]="gwGroups"
+      (selected)="onGroupSelection($event)"
+      (clear)="onGroupClear()">
+    <cds-dropdown-list></cds-dropdown-list>
+  </cds-combo-box>
+</div>
+
 <legend i18n>
   Subsystems
   <cd-help-text>
@@ -23,7 +39,8 @@
   </div>
 
   <cd-nvmeof-subsystems-details *cdTableDetail
-                                [selection]="expandedRow">
+                                [selection]="expandedRow"
+                                [group]="group">
   </cd-nvmeof-subsystems-details>
 </cd-table>
 <router-outlet name="modal"></router-outlet>
index 1efd28dd11401cd1939b3cfd78df5a77fd690424..c508cf74a778445fba41a7d34734d7a27f2840c9 100644 (file)
@@ -11,6 +11,7 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { NvmeofSubsystemsComponent } from './nvmeof-subsystems.component';
 import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
 import { NvmeofSubsystemsDetailsComponent } from '../nvmeof-subsystems-details/nvmeof-subsystems-details.component';
+import { ComboBoxModule, GridModule } from 'carbon-components-angular';
 
 const mockSubsystems = [
   {
@@ -26,10 +27,36 @@ const mockSubsystems = [
   }
 ];
 
+const mockGroups = [
+  [
+    {
+      service_name: 'nvmeof.rbd.default',
+      service_type: 'nvmeof',
+      unmanaged: false,
+      spec: {
+        group: 'default'
+      }
+    },
+    {
+      service_name: 'nvmeof.rbd.foo',
+      service_type: 'nvmeof',
+      unmanaged: false,
+      spec: {
+        group: 'foo'
+      }
+    }
+  ],
+  2
+];
+
 class MockNvmeOfService {
   listSubsystems() {
     return of(mockSubsystems);
   }
+
+  listGatewayGroups() {
+    return of(mockGroups);
+  }
 }
 
 class MockAuthStorageService {
@@ -53,7 +80,7 @@ describe('NvmeofSubsystemsComponent', () => {
         NvmeofTabsComponent,
         NvmeofSubsystemsDetailsComponent
       ],
-      imports: [HttpClientModule, RouterTestingModule, SharedModule],
+      imports: [HttpClientModule, RouterTestingModule, SharedModule, ComboBoxModule, GridModule],
       providers: [
         { provide: NvmeofService, useClass: MockNvmeOfService },
         { provide: AuthStorageService, useClass: MockAuthStorageService },
@@ -77,4 +104,12 @@ describe('NvmeofSubsystemsComponent', () => {
     tick();
     expect(component.subsystems).toEqual(mockSubsystems);
   }));
+
+  it('should load gateway groups correctly', () => {
+    expect(component.gwGroups.length).toBe(2);
+  });
+
+  it('should set first group as default initially', () => {
+    expect(component.group).toBe(mockGroups[0][0].spec.group);
+  });
 });
index 1de2883b1dd05201cbdfb7c684cb2bab33b9164a..61e28274048f0d29629af87825e9a57b04a71ffb 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
 
 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
@@ -14,6 +14,12 @@ import { FinishedTask } from '~/app/shared/models/finished-task';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { NvmeofService } from '~/app/shared/api/nvmeof.service';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
+
+type ComboBoxItem = {
+  content: string;
+  selected?: boolean;
+};
 
 const BASE_URL = 'block/nvmeof/subsystems';
 
@@ -29,6 +35,8 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
   selection = new CdTableSelection();
   tableActions: CdTableAction[];
   subsystemDetails: any[];
+  gwGroups: ComboBoxItem[] = [];
+  group: string = null;
 
   constructor(
     private nvmeofService: NvmeofService,
@@ -36,13 +44,18 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
     public actionLabels: ActionLabelsI18n,
     private router: Router,
     private modalService: ModalCdsService,
-    private taskWrapper: TaskWrapperService
+    private taskWrapper: TaskWrapperService,
+    private route: ActivatedRoute
   ) {
     super();
     this.permission = this.authStorageService.getPermissions().nvmeof;
   }
 
   ngOnInit() {
+    this.route.queryParams.subscribe((params) => {
+      if (params?.['group']) this.onGroupSelection({ content: params?.['group'] });
+    });
+    this.getGatewayGroups();
     this.subsystemsColumns = [
       {
         name: $localize`NQN`,
@@ -62,7 +75,10 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
         name: this.actionLabels.CREATE,
         permission: 'create',
         icon: Icons.add,
-        click: () => this.router.navigate([BASE_URL, { outlets: { modal: [URLVerbs.CREATE] } }]),
+        click: () =>
+          this.router.navigate([BASE_URL, { outlets: { modal: [URLVerbs.CREATE] } }], {
+            queryParams: { group: this.group }
+          }),
         canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
       },
       {
@@ -92,13 +108,14 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
     ];
   }
 
+  // Subsystems
   updateSelection(selection: CdTableSelection) {
     this.selection = selection;
   }
 
   getSubsystems() {
     this.nvmeofService
-      .listSubsystems()
+      .listSubsystems(this.group)
       .subscribe((subsystems: NvmeofSubsystem[] | NvmeofSubsystem) => {
         if (Array.isArray(subsystems)) this.subsystems = subsystems;
         else this.subsystems = [subsystems];
@@ -114,8 +131,34 @@ export class NvmeofSubsystemsComponent extends ListWithDetails implements OnInit
       submitActionObservable: () =>
         this.taskWrapper.wrapTaskAroundCall({
           task: new FinishedTask('nvmeof/subsystem/delete', { nqn: subsystem.nqn }),
-          call: this.nvmeofService.deleteSubsystem(subsystem.nqn)
+          call: this.nvmeofService.deleteSubsystem(subsystem.nqn, this.group)
         })
     });
   }
+
+  // Gateway groups
+  onGroupSelection(selected: ComboBoxItem) {
+    selected.selected = true;
+    this.group = selected.content;
+    this.getSubsystems();
+  }
+
+  onGroupClear() {
+    this.group = null;
+    this.getSubsystems();
+  }
+
+  getGatewayGroups() {
+    this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
+      if (response?.[0].length) {
+        this.gwGroups = response[0].map((group: CephServiceSpec) => {
+          return {
+            content: group?.spec?.group
+          };
+        });
+      }
+      // Select first group if no group is selected
+      if (!this.group && this.gwGroups.length) this.onGroupSelection(this.gwGroups[0]);
+    });
+  }
 }
index bbc38b9ce16d5505b546e568abc57c7aabdb3745..313db3445f298563a3b475eedfe921011181d916 100644 (file)
@@ -6,6 +6,8 @@ import { NvmeofService } from '../../shared/api/nvmeof.service';
 describe('NvmeofService', () => {
   let service: NvmeofService;
   let httpTesting: HttpTestingController;
+  const mockGroupName = 'default';
+  const mockNQN = 'nqn.2001-07.com.ceph:1721041732363';
 
   configureTestBed({
     providers: [NvmeofService],
@@ -25,34 +27,51 @@ describe('NvmeofService', () => {
     expect(service).toBeTruthy();
   });
 
+  it('should call listGatewayGroups', () => {
+    service.listGatewayGroups().subscribe();
+    const req = httpTesting.expectOne('api/nvmeof/gateway/group');
+    expect(req.request.method).toBe('GET');
+  });
+
   it('should call listGateways', () => {
     service.listGateways().subscribe();
     const req = httpTesting.expectOne('api/nvmeof/gateway');
     expect(req.request.method).toBe('GET');
   });
 
+  it('should call listSubsystems', () => {
+    service.listSubsystems(mockGroupName).subscribe();
+    const req = httpTesting.expectOne(`api/nvmeof/subsystem?gw_group=${mockGroupName}`);
+    expect(req.request.method).toBe('GET');
+  });
+
   it('should call getSubsystem', () => {
-    service.getSubsystem('nqn.2001-07.com.ceph:1721041732363').subscribe();
-    const req = httpTesting.expectOne('api/nvmeof/subsystem/nqn.2001-07.com.ceph:1721041732363');
+    service.getSubsystem(mockNQN, mockGroupName).subscribe();
+    const req = httpTesting.expectOne(`api/nvmeof/subsystem/${mockNQN}?gw_group=${mockGroupName}`);
     expect(req.request.method).toBe('GET');
   });
 
   it('should call createSubsystem', () => {
     const request = {
-      nqn: 'nqn.2001-07.com.ceph:1721041732363',
+      nqn: mockNQN,
       enable_ha: true,
-      initiators: '*'
+      initiators: '*',
+      gw_group: mockGroupName
     };
     service.createSubsystem(request).subscribe();
     const req = httpTesting.expectOne('api/nvmeof/subsystem');
     expect(req.request.method).toBe('POST');
   });
 
+  it('should call deleteSubsystem', () => {
+    service.deleteSubsystem(mockNQN, mockGroupName).subscribe();
+    const req = httpTesting.expectOne(`api/nvmeof/subsystem/${mockNQN}?gw_group=${mockGroupName}`);
+    expect(req.request.method).toBe('DELETE');
+  });
+
   it('should call getInitiators', () => {
-    service.getInitiators('nqn.2001-07.com.ceph:1721041732363').subscribe();
-    const req = httpTesting.expectOne(
-      'api/nvmeof/subsystem/nqn.2001-07.com.ceph:1721041732363/host'
-    );
+    service.getInitiators(mockNQN).subscribe();
+    const req = httpTesting.expectOne(`api/nvmeof/subsystem/${mockNQN}/host`);
     expect(req.request.method).toBe('GET');
   });
 });
index 7c72530e84a288ff7c3cbabd2445d7b031512c63..40202d0d672503877c2ac01d4d2e48bbedfb1273 100644 (file)
@@ -36,32 +36,42 @@ const UI_API_PATH = 'ui-api/nvmeof';
 export class NvmeofService {
   constructor(private http: HttpClient) {}
 
+  // Gateway groups
+  listGatewayGroups() {
+    return this.http.get(`${API_PATH}/gateway/group`);
+  }
+
   // Gateways
   listGateways() {
     return this.http.get(`${API_PATH}/gateway`);
   }
 
   // Subsystems
-  listSubsystems() {
-    return this.http.get(`${API_PATH}/subsystem`);
+  listSubsystems(group: string) {
+    return this.http.get(`${API_PATH}/subsystem?gw_group=${group}`);
   }
 
-  getSubsystem(subsystemNQN: string) {
-    return this.http.get(`${API_PATH}/subsystem/${subsystemNQN}`);
+  getSubsystem(subsystemNQN: string, group: string) {
+    return this.http.get(`${API_PATH}/subsystem/${subsystemNQN}?gw_group=${group}`);
   }
 
-  createSubsystem(request: { nqn: string; max_namespaces?: number; enable_ha: boolean }) {
+  createSubsystem(request: {
+    nqn: string;
+    enable_ha: boolean;
+    gw_group: string;
+    max_namespaces?: number;
+  }) {
     return this.http.post(`${API_PATH}/subsystem`, request, { observe: 'response' });
   }
 
-  deleteSubsystem(subsystemNQN: string) {
-    return this.http.delete(`${API_PATH}/subsystem/${subsystemNQN}`, {
+  deleteSubsystem(subsystemNQN: string, group: string) {
+    return this.http.delete(`${API_PATH}/subsystem/${subsystemNQN}?gw_group=${group}`, {
       observe: 'response'
     });
   }
 
-  isSubsystemPresent(subsystemNqn: string): Observable<boolean> {
-    return this.getSubsystem(subsystemNqn).pipe(
+  isSubsystemPresent(subsystemNqn: string, group: string): Observable<boolean> {
+    return this.getSubsystem(subsystemNqn, group).pipe(
       mapTo(true),
       catchError((e) => {
         e?.preventDefault();