From: pujaoshahu Date: Fri, 10 Apr 2026 07:13:29 +0000 (+0530) Subject: mgr/dashboard : Unable to remove gateway node from gateway group X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=36c6b9261c2fa6ea32e80a9a10ecbed576aaf77a;p=ceph.git mgr/dashboard : Unable to remove gateway node from gateway group Fixes: https://tracker.ceph.com/issues/75864 Signed-off-by: pujaoshahu --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.ts index c6f1937f315b..6c447d2e244f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node-add-modal/nvmeof-gateway-node-add-modal.component.ts @@ -169,8 +169,8 @@ export class NvmeofGatewayNodeAddModalComponent extends CdForm implements OnInit const { status, ...modifiedSpec } = this.serviceSpec; - if ('events' in modifiedSpec) { - delete (modifiedSpec as any).events; + if (modifiedSpec.events) { + delete modifiedSpec.events; } if (modifiedSpec.placement) { @@ -180,7 +180,7 @@ export class NvmeofGatewayNodeAddModalComponent extends CdForm implements OnInit } if ('locations' in modifiedSpec.placement) { - delete (modifiedSpec.placement as any).locations; + delete modifiedSpec.placement.locations; } modifiedSpec.placement.hosts = newHosts; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts index 833d31aaf624..5e7aea62685a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts @@ -26,7 +26,7 @@ import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface'; import { Permission } from '~/app/shared/models/permissions'; import { Host } from '~/app/shared/models/host.interface'; -import { CephServiceSpec } from '~/app/shared/models/service.interface'; +import { CephServiceSpec, CephServiceSpecUpdate } from '~/app/shared/models/service.interface'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { CephServiceService } from '~/app/shared/api/ceph-service.service'; import { NvmeofService } from '~/app/shared/api/nvmeof.service'; @@ -186,11 +186,9 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { deletionMessage: $localize`Removing ${hostname} will detach it from the gateway group and stop handling new I/O requests. Active connections may be disrupted.

You can re-add this node later if required.` }, submitActionObservable: () => { - const updatedSpec = _.cloneDeep(this.serviceSpec); - updatedSpec.placement.hosts = updatedSpec.placement.hosts.filter((h) => h !== hostname); - delete updatedSpec.status; - if (updatedSpec['events']) { - delete updatedSpec['events']; + const updatedSpec = this.buildRemoveGatewaySpecPayload(hostname); + if (!updatedSpec) { + return of(null); } return this.taskWrapper .wrapTaskAroundCall({ @@ -216,6 +214,34 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { }); } + private buildRemoveGatewaySpecPayload(hostname: string): CephServiceSpecUpdate | null { + if (!this.serviceSpec) { + this.notificationService.show( + NotificationType.error, + $localize`Service specification is missing.` + ); + return null; + } + + const { status, ...updatedSpec } = _.cloneDeep(this.serviceSpec); + + if (updatedSpec['events']) { + delete updatedSpec['events']; + } + + if (!updatedSpec.placement) { + updatedSpec.placement = {}; + } + + if ('locations' in updatedSpec.placement) { + delete updatedSpec.placement.locations; + } + + const currentHosts = updatedSpec.placement.hosts || []; + updatedSpec.placement.hosts = currentHosts.filter((h: string) => h !== hostname); + return updatedSpec; + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); @@ -275,7 +301,7 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { const allUsedHostnames = new Set(); groupList.forEach((group: CephServiceSpec) => { - const hosts = group.placement?.hosts || (group.spec as any)?.placement?.hosts || []; + const hosts = group.placement?.hosts || group.spec?.placement?.hosts || []; hosts.forEach((hostname: string) => allUsedHostnames.add(hostname)); }); @@ -301,7 +327,7 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { this.hosts = []; } else { const placementHosts = - this.serviceSpec.placement?.hosts || (this.serviceSpec.spec as any)?.placement?.hosts || []; + this.serviceSpec.placement?.hosts || this.serviceSpec.spec?.placement?.hosts || []; const currentGroupHosts = new Set(placementHosts); this.hosts = (hostList || []).filter((host: Host) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts index 583c7b7edcf5..c85b6b795722 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts @@ -49,12 +49,14 @@ export interface CephServiceSpec { certificate?: CephServiceCertificate; spec: CephServiceAdditionalSpec; placement: CephServicePlacement; + events?: string[]; } // Type for service spec update payload (excludes read-only status field) export type CephServiceSpecUpdate = Omit; export interface CephServiceAdditionalSpec { + placement?: CephServicePlacement; backend_service: string; api_user: string; api_password: string; @@ -107,6 +109,7 @@ export interface CephServicePlacement { placement?: string; hosts?: string[]; label?: string | string[]; + locations?: Record; } export interface QatSepcs {