]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: List gateways in a group 59861/head
authorAfreen Misbah <afreen23.git@gmail.com>
Wed, 18 Sep 2024 13:54:37 +0000 (19:24 +0530)
committerAfreen Misbah <afreen23.git@gmail.com>
Thu, 19 Sep 2024 10:10:56 +0000 (15:40 +0530)
- adds a group selection component for gateways
- fetch gateway using daemons info
- added tests

fixes https://tracker.ceph.com/issues/68135

Signed-off-by: Afreen Misbah <afreen23.git@gmail.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nvmeof.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/daemon.interface.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 c466c8674cca32400acc625099ee270a2f4e1669..556033e89e547aa80b1ba6ac89580a13b0954475 100644 (file)
@@ -1,5 +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>
   Gateways
   <cd-help-text>
             [columns]="gatewayColumns">
   </cd-table>
 </div>
+
+<ng-template #statusTpl
+             let-row="data.row">
+  <span class="badge"
+        [ngClass]="row | pipeFunction:getStatusClass">
+    {{ row.status_desc }}
+  </span>
+</ng-template>
index 53187cd0f8d8c799f18b022b756b0995e123c0fb..1c8bf5485661be2f608681e302c51d61978e73b5 100644 (file)
-import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { of } from 'rxjs';
 import { NvmeofGatewayComponent } from './nvmeof-gateway.component';
 import { NvmeofService } from '../../../shared/api/nvmeof.service';
 import { HttpClientModule } from '@angular/common/http';
 import { SharedModule } from '~/app/shared/shared.module';
+import { ComboBoxModule, GridModule } from 'carbon-components-angular';
+import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
+import { CephServiceService } from '~/app/shared/api/ceph-service.service';
+
+const mockServiceDaemons = [
+  {
+    daemon_type: 'nvmeof',
+    daemon_id: 'nvmeof.default.ceph-node-01.kdcguk',
+    daemon_name: 'nvmeof.nvmeof.default.ceph-node-01.kdcguk',
+    hostname: 'ceph-node-01',
+    container_id: '6fe5a9ae9c96',
+    container_image_id: '32a3d75b7c146d6c37b04ee3c9ba883ab88a8f7ae8f286de268d0f41ebd86a51',
+    container_image_name: 'quay.io/ceph/nvmeof:1.2.17',
+    container_image_digests: [
+      'quay.io/ceph/nvmeof@sha256:4308d05d3bb2167fc695d755316fec8d12ec3f00eb7639eeeabad38a5c4df0f9'
+    ],
+    memory_usage: 89443532,
+    cpu_percentage: '98.87%',
+    version: '1.2.17',
+    status: 1,
+    status_desc: 'running'
+  },
+  {
+    daemon_type: 'nvmeof',
+    daemon_id: 'nvmeof.default.ceph-node-02.hybprc',
+    daemon_name: 'nvmeof.nvmeof.default.ceph-node-02.hybprc',
+    hostname: 'ceph-node-02',
+    container_id: '2b061130726b',
+    container_image_id: '32a3d75b7c146d6c37b04ee3c9ba883ab88a8f7ae8f286de268d0f41ebd86a51',
+    container_image_name: 'quay.io/ceph/nvmeof:1.2.17',
+    container_image_digests: [
+      'quay.io/ceph/nvmeof@sha256:4308d05d3bb2167fc695d755316fec8d12ec3f00eb7639eeeabad38a5c4df0f9'
+    ],
+    memory_usage: 89328189,
+    cpu_percentage: '98.89%',
+    version: '1.2.17',
+    status: 1,
+    status_desc: 'running'
+  }
+];
 
 const mockGateways = [
   {
-    cli_version: '',
-    version: '1.2.5',
-    name: 'client.nvmeof.rbd.ceph-node-01.jnmnwa',
-    group: '',
-    addr: '192.168.100.101',
-    port: '5500',
-    load_balancing_group: 1,
-    spdk_version: '24.01'
+    id: 'client.nvmeof.nvmeof.default.ceph-node-01.kdcguk',
+    hostname: 'ceph-node-01',
+    status_desc: 'running',
+    status: 1
+  },
+  {
+    id: 'client.nvmeof.nvmeof.default.ceph-node-02.hybprc',
+    hostname: 'ceph-node-02',
+    status_desc: 'running',
+    status: 1
+  }
+];
+
+const mockGwGroups = [
+  {
+    content: 'default',
+    serviceName: 'nvmeof.rbd.default'
+  },
+  {
+    content: 'foo',
+    serviceName: 'nvmeof.rbd.foo'
   }
 ];
 
+const mockServices = [
+  [
+    {
+      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 {
-  listGateways() {
-    return of(mockGateways);
+  listGatewayGroups() {
+    return of(mockServices);
+  }
+}
+
+class MockCephServiceService {
+  getDaemons(_service: string) {
+    return of(mockServiceDaemons);
   }
 }
 
@@ -28,26 +108,37 @@ describe('NvmeofGatewayComponent', () => {
   let component: NvmeofGatewayComponent;
   let fixture: ComponentFixture<NvmeofGatewayComponent>;
 
-  beforeEach(fakeAsync(() => {
-    TestBed.configureTestingModule({
-      declarations: [NvmeofGatewayComponent],
-      imports: [HttpClientModule, SharedModule],
-      providers: [{ provide: NvmeofService, useClass: MockNvmeOfService }]
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [NvmeofGatewayComponent, NvmeofTabsComponent],
+      imports: [HttpClientModule, SharedModule, ComboBoxModule, GridModule],
+      providers: [
+        { provide: NvmeofService, useClass: MockNvmeOfService },
+        { provide: CephServiceService, useClass: MockCephServiceService }
+      ]
     }).compileComponents();
-  }));
 
-  beforeEach(() => {
     fixture = TestBed.createComponent(NvmeofGatewayComponent);
     component = fixture.componentInstance;
+    component.ngOnInit();
+    fixture.detectChanges();
   });
 
   it('should create', () => {
     expect(component).toBeTruthy();
   });
 
-  it('should retrieve gateways', fakeAsync(() => {
-    component.getGateways();
-    tick();
-    expect(component.gateways).toEqual(mockGateways);
-  }));
+  it('should load gateway groups correctly', () => {
+    expect(component.gwGroups.length).toBe(2);
+    expect(component.gwGroups).toStrictEqual(mockGwGroups);
+  });
+
+  it('should set service name of gateway groups correctly', () => {
+    expect(component.groupService).toBe(mockServices[0][0].service_name);
+  });
+
+  it('should set gateways correctly', () => {
+    expect(component.gateways.length).toBe(2);
+    expect(component.gateways).toStrictEqual(mockGateways);
+  });
 });
index 46600388bd96500c6d6e93cdd594582f54f30853..c966da9b9c27f98d231595f8a2c06b2391582dea 100644 (file)
@@ -1,10 +1,27 @@
-import { Component } from '@angular/core';
+import { Component, TemplateRef, ViewChild } from '@angular/core';
+
+import _ from 'lodash';
 
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
-import { NvmeofGateway } from '~/app/shared/models/nvmeof';
 
 import { NvmeofService } from '../../../shared/api/nvmeof.service';
+import { CephServiceSpec } from '~/app/shared/models/service.interface';
+import { CephServiceService } from '~/app/shared/api/ceph-service.service';
+import { Daemon } from '~/app/shared/models/daemon.interface';
+
+type ComboBoxItem = {
+  content: string;
+  serviceName: string;
+  selected?: boolean;
+};
+
+type Gateway = {
+  id: string;
+  hostname: string;
+  status: number;
+  status_desc: string;
+};
 
 @Component({
   selector: 'cd-nvmeof-gateway',
@@ -12,33 +29,93 @@ import { NvmeofService } from '../../../shared/api/nvmeof.service';
   styleUrls: ['./nvmeof-gateway.component.scss']
 })
 export class NvmeofGatewayComponent {
-  gateways: NvmeofGateway[] = [];
+  @ViewChild('statusTpl', { static: true })
+  statusTpl: TemplateRef<any>;
+
+  gateways: Gateway[] = [];
   gatewayColumns: any;
   selection = new CdTableSelection();
+  gwGroups: ComboBoxItem[] = [];
+  groupService: string = null;
 
-  constructor(private nvmeofService: NvmeofService, public actionLabels: ActionLabelsI18n) {}
+  constructor(
+    private nvmeofService: NvmeofService,
+    private cephServiceService: CephServiceService,
+    public actionLabels: ActionLabelsI18n
+  ) {}
 
   ngOnInit() {
+    this.getGatewayGroups();
     this.gatewayColumns = [
       {
-        name: $localize`Name`,
-        prop: 'name'
+        name: $localize`Gateway ID`,
+        prop: 'id'
       },
       {
-        name: $localize`Address`,
-        prop: 'addr'
+        name: $localize`Host name`,
+        prop: 'hostname'
       },
       {
-        name: $localize`Port`,
-        prop: 'port'
+        name: $localize`Status`,
+        prop: 'status_desc',
+        cellTemplate: this.statusTpl
       }
     ];
   }
 
+  // for Status column
+  getStatusClass(row: Gateway): string {
+    return _.get(
+      {
+        '-1': 'badge-danger',
+        '0': 'badge-warning',
+        '1': 'badge-success'
+      },
+      row.status,
+      'badge-dark'
+    );
+  }
+
+  // Gateways
   getGateways() {
-    this.nvmeofService.listGateways().subscribe((gateways: NvmeofGateway[] | NvmeofGateway) => {
-      if (Array.isArray(gateways)) this.gateways = gateways;
-      else this.gateways = [gateways];
+    this.cephServiceService.getDaemons(this.groupService).subscribe((daemons: Daemon[]) => {
+      this.gateways = daemons.length
+        ? daemons.map((daemon: Daemon) => {
+            return {
+              id: `client.${daemon.daemon_name}`,
+              hostname: daemon.hostname,
+              status_desc: daemon.status_desc,
+              status: daemon.status
+            };
+          })
+        : [];
+    });
+  }
+
+  // Gateway groups
+  onGroupSelection(selected: ComboBoxItem) {
+    selected.selected = true;
+    this.groupService = selected.serviceName;
+    this.getGateways();
+  }
+
+  onGroupClear() {
+    this.groupService = null;
+    this.getGateways();
+  }
+
+  getGatewayGroups() {
+    this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
+      this.gwGroups = response?.[0]?.length
+        ? response[0].map((group: CephServiceSpec) => {
+            return {
+              content: group?.spec?.group,
+              serviceName: group?.service_name
+            };
+          })
+        : [];
+      // Select first group if no group is selected
+      if (!this.groupService && this.gwGroups.length) this.onGroupSelection(this.gwGroups[0]);
     });
   }
 }
index 7c72530e84a288ff7c3cbabd2445d7b031512c63..a8baac4863f9fe2ecbfd6894e2d733c3d01e8f19 100644 (file)
@@ -36,6 +36,11 @@ 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`);
index c69a27851c68f36345237a2d65ebda675e06a1d9..440b276fafe89ede1634768c81f0acfc8bb5dee7 100644 (file)
@@ -5,6 +5,8 @@ export interface Daemon {
   container_image_name: string;
   daemon_id: string;
   daemon_type: string;
+  daemon_name: string;
+  hostname: string;
   version: string;
   status: number;
   status_desc: string;