From 7c6c16c593652281d519c176f63c1e60ae2649b1 Mon Sep 17 00:00:00 2001 From: Afreen Misbah Date: Wed, 18 Sep 2024 15:56:25 +0530 Subject: [PATCH] mgr/dashboard: Add group selector in subsystems views 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 --- .../src/app/ceph/block/block.module.ts | 4 +- .../nvmeof-listeners-form.component.ts | 30 +++++++---- .../nvmeof-listeners-list.component.ts | 10 ++-- .../nvmeof-subsystems-details.component.html | 3 +- .../nvmeof-subsystems-details.component.ts | 2 + .../nvmeof-subsystems-form.component.spec.ts | 5 +- .../nvmeof-subsystems-form.component.ts | 26 ++++++--- .../nvmeof-subsystems.component.html | 19 ++++++- .../nvmeof-subsystems.component.spec.ts | 37 ++++++++++++- .../nvmeof-subsystems.component.ts | 53 +++++++++++++++++-- .../src/app/shared/api/nvmeof.service.spec.ts | 35 +++++++++--- .../src/app/shared/api/nvmeof.service.ts | 28 ++++++---- 12 files changed, 206 insertions(+), 46 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts index 4f3531c3d5e59..b6f04cadcc15c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts @@ -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, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts index 412286bda2098..cd362bf8abe19 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts @@ -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 = 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 } + }); } }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.ts index 3ece51f350d48..f88442e1bd619 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.ts @@ -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 }, { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.html index 3749d47bccfa0..7f15a1360adc2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.html @@ -15,7 +15,8 @@ Listeners - + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.ts index 211905f285fbf..cc561266677ca 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.ts @@ -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; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts index 08e56debf0a74..0f34803b7efb1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts @@ -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 }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts index 5c2e1ce5250ee..f7b35a2d645ec 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts @@ -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 } + }); } }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html index 6cd1f205913b9..1d5c1324ecbd3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html @@ -1,4 +1,20 @@ + +
+ + + +
+ Subsystems @@ -23,7 +39,8 @@ + [selection]="expandedRow" + [group]="group"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts index 1efd28dd11401..c508cf74a7784 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts @@ -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); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.ts index 1de2883b1dd05..61e28274048f0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.ts @@ -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]); + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts index bbc38b9ce16d5..313db3445f298 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.spec.ts @@ -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'); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts index 7c72530e84a28..40202d0d67250 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts @@ -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 { - return this.getSubsystem(subsystemNqn).pipe( + isSubsystemPresent(subsystemNqn: string, group: string): Observable { + return this.getSubsystem(subsystemNqn, group).pipe( mapTo(true), catchError((e) => { e?.preventDefault(); -- 2.39.5