]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Allow host with labels in listener form 64131/head
authorAfreen Misbah <afreen@ibm.com>
Mon, 16 Jun 2025 17:09:46 +0000 (22:39 +0530)
committerAfreen Misbah <afreen@ibm.com>
Wed, 25 Jun 2025 06:56:12 +0000 (12:26 +0530)
- Currently, listeners cannot be added with the Ceph Dashboard if the gateway nodes are selected by label instead of hosts.

- Refactored the code to incorporate nodes with labels

- Also added missing typings and removed 'any'

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

Signed-off-by: Afreen Misbah <afreen@ibm.com>
(cherry picked from commit 0bd2704a88f517b48196a8b1a3c07b0f8032b0f6)

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.html
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/cephfs/cephfs-form/cephfs-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-form/smb-cluster-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/host.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/host.interface.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts

index 279d108d3fe2021488fbbaaeedd0f8ab83df6f6e..910e5579ee4b4d558a53f16a23d3170609667bd1 100644 (file)
@@ -12,7 +12,7 @@
           <label class="cd-col-form-label"
                  for="host">
             <span class="required"
-                  i18n>Host Name</span>
+                  i18n>Hostname</span>
           </label>
           <div class="cd-col-form-input">
             <select id="host"
index 8310e65d203e5409c55d04039718b18ba1d29109..81f639a571304426ee1ef7abbb56ec510b4bb325 100644 (file)
@@ -1,8 +1,9 @@
+import _ from 'lodash';
 import { Component, OnInit } from '@angular/core';
 import { UntypedFormControl, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
-import { ListenerRequest, NvmeofService } from '~/app/shared/api/nvmeof.service';
+import { GatewayGroup, ListenerRequest, NvmeofService } from '~/app/shared/api/nvmeof.service';
 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { FinishedTask } from '~/app/shared/models/finished-task';
@@ -15,7 +16,7 @@ import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
 import { HostService } from '~/app/shared/api/host.service';
 import { map } from 'rxjs/operators';
 import { forkJoin } from 'rxjs';
-import { CephServiceSpec } from '~/app/shared/models/service.interface';
+import { Host } from '~/app/shared/models/host.interface';
 
 @Component({
   selector: 'cd-nvmeof-listeners-form',
@@ -51,26 +52,42 @@ export class NvmeofListenersFormComponent implements OnInit {
     this.pageURL = 'block/nvmeof/subsystems';
   }
 
+  filterHostsByLabel(allHosts: Host[], gwNodesLabel: string | string[]) {
+    return allHosts.filter((host: Host) => {
+      const hostLabels: string[] = host?.labels;
+      if (typeof gwNodesLabel === 'string') {
+        return hostLabels.includes(gwNodesLabel);
+      }
+      return hostLabels?.length === gwNodesLabel?.length && _.isEqual(hostLabels, gwNodesLabel);
+    });
+  }
+
+  filterHostsByHostname(allHosts: Host[], gwNodes: string[]) {
+    return allHosts.filter((host: Host) => gwNodes.includes(host.hostname));
+  }
+
+  getGwGroupPlacement(gwGroups: GatewayGroup[][]) {
+    return (
+      gwGroups?.[0]?.find((gwGroup: GatewayGroup) => gwGroup?.spec?.group === this.group)
+        ?.placement || { hosts: [], label: [] }
+    );
+  }
+
   setHosts() {
     forkJoin({
       gwGroups: this.nvmeofService.listGatewayGroups(),
-      hosts: this.hostService.getAllHosts()
+      allHosts: this.hostService.getAllHosts()
     })
       .pipe(
-        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))
-            : [];
+        map(({ gwGroups, allHosts }) => {
+          const { hosts, label } = this.getGwGroupPlacement(gwGroups);
+          if (hosts?.length) return this.filterHostsByHostname(allHosts, hosts);
+          else if (label?.length) return this.filterHostsByLabel(allHosts, label);
+          return [];
         })
       )
-      .subscribe((nvmeofHosts: any[]) => {
-        this.hosts = nvmeofHosts.map((h) => ({ hostname: h.hostname, addr: h.addr }));
+      .subscribe((nvmeofGwNodes: Host[]) => {
+        this.hosts = nvmeofGwNodes.map((h) => ({ hostname: h.hostname, addr: h.addr }));
       });
   }
 
index 3681bfeff7ee87340efcab2fd4cf732ef8d06856..f3a4302831379290395342f5e89a4215fbe2a604 100644 (file)
@@ -21,6 +21,7 @@ import { Permission } from '~/app/shared/models/permissions';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { PoolService } from '~/app/shared/api/pool.service';
 import { Pool } from '../../pool/pool';
+import { Host } from '~/app/shared/models/host.interface';
 
 @Component({
   selector: 'cd-cephfs-form',
@@ -176,7 +177,7 @@ export class CephfsVolumeFormComponent extends CdForm implements OnInit {
         labels: this.hostService.getLabels()
       }).pipe(
         map(({ hosts, labels }) => ({
-          hosts: hosts.map((host: any) => ({ content: host['hostname'] })),
+          hosts: hosts.map((host: Host) => ({ content: host['hostname'] })),
           labels: labels.map((label: string) => ({ content: label }))
         }))
       );
index 5d979137a3a0c94a190d578927c5ddbc34a9f485..b010ba39905da925fde78bcca42099cf84b385e8 100644 (file)
@@ -34,6 +34,7 @@ import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { FinishedTask } from '~/app/shared/models/finished-task';
+import { Host } from '~/app/shared/models/host.interface';
 import { CephServiceSpec } from '~/app/shared/models/service.interface';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
@@ -636,9 +637,9 @@ export class ServiceFormComponent extends CdForm implements OnInit {
 
       this.serviceTypes = _.difference(resp, this.hiddenServices).sort();
     });
-    this.hostService.getAllHosts().subscribe((resp: object[]) => {
+    this.hostService.getAllHosts().subscribe((resp: Host[]) => {
       const options: SelectOption[] = [];
-      _.forEach(resp, (host: object) => {
+      _.forEach(resp, (host: Host) => {
         if (_.get(host, 'sources.orchestrator', false)) {
           const option = new SelectOption(false, _.get(host, 'hostname'), '');
           options.push(option);
index 78bb616246f529e8234e44637c9f85750a96ea11..a104c59c24b2c58da4c2e39d0b76a054cf212471 100644 (file)
@@ -37,6 +37,7 @@ import { CephServicePlacement } from '~/app/shared/models/service.interface';
 import { UpperFirstPipe } from '~/app/shared/pipes/upper-first.pipe';
 import { CLUSTER_PATH } from '../smb-cluster-list/smb-cluster-list.component';
 import { USERSGROUPS_PATH } from '../smb-usersgroups-list/smb-usersgroups-list.component';
+import { Host } from '~/app/shared/models/host.interface';
 
 @Component({
   selector: 'cd-smb-cluster-form',
@@ -95,7 +96,7 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
       labels: this.hostService.getLabels()
     }).pipe(
       map(({ hosts, labels }) => ({
-        hosts: hosts.map((host: any) => ({ content: host['hostname'] })),
+        hosts: hosts.map((host: Host) => ({ content: host['hostname'] })),
         labels: labels.map((label: string) => ({ content: label }))
       }))
     );
@@ -169,7 +170,7 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
         labels: this.hostService.getLabels()
       }).pipe(
         map(({ hosts, labels }) => ({
-          hosts: hosts.map((host: any) => ({ content: host['hostname'] })),
+          hosts: hosts.map((host: Host) => ({ content: host['hostname'] })),
           labels: labels.map((label: string) => ({ content: label }))
         }))
       );
index ce23302ba26de19a21b02c7a7d65c485a4234980..0fcd63c940c48c71ee7042590d9bb1497c6b2b24 100644 (file)
@@ -13,6 +13,7 @@ import { Daemon } from '../models/daemon.interface';
 import { CdDevice } from '../models/devices';
 import { SmartDataResponseV1 } from '../models/smart';
 import { DeviceService } from '../services/device.service';
+import { Host } from '../models/host.interface';
 
 @Injectable({
   providedIn: 'root'
@@ -163,7 +164,7 @@ export class HostService extends ApiClient {
     );
   }
 
-  getAllHosts(): Observable<object[]> {
-    return this.http.get<object[]>(`${this.baseUIURL}/list`);
+  getAllHosts(): Observable<Host[]> {
+    return this.http.get<Host[]>(`${this.baseUIURL}/list`);
   }
 }
index b68562bfed3114c98ebc5a86c2450d885b4ead96..8ea39a8667d58fe31b185c62e045ac41c6b259b7 100644 (file)
@@ -8,6 +8,8 @@ import { CephServiceSpec } from '../models/service.interface';
 
 export const MAX_NAMESPACE = 1024;
 
+export type GatewayGroup = CephServiceSpec;
+
 export type GroupsComboboxItem = {
   content: string;
   serviceName?: string;
@@ -71,7 +73,7 @@ export class NvmeofService {
 
   // Gateway groups
   listGatewayGroups() {
-    return this.http.get(`${API_PATH}/gateway/group`);
+    return this.http.get<GatewayGroup[][]>(`${API_PATH}/gateway/group`);
   }
 
   // Gateways
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/host.interface.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/host.interface.ts
new file mode 100644 (file)
index 0000000..bff2aea
--- /dev/null
@@ -0,0 +1,13 @@
+export interface Host {
+  ceph_version: string;
+  services: Array<{ type: string; id: string }>;
+  sources: {
+    ceph: boolean;
+    orchestrator: boolean;
+  };
+  hostname: string;
+  addr: string;
+  labels: string[];
+  status: any;
+  service_instances: Array<{ type: string; count: number }>;
+}
index f906a25ad2a9921ca73b075e281525143cc0a419..966774a05a2ceb483462a7cc5d7428b573a18811 100644 (file)
@@ -68,5 +68,5 @@ export interface CephServicePlacement {
   count?: number;
   placement?: string;
   hosts?: string[];
-  label?: string;
+  label?: string | string[];
 }