Skip to content

Commit f8504eb

Browse files
authored
Merge pull request ceph#59861 from afreen23/nvme-gateway-fix
mgr/dashboard: List gateways in a group Reviewed-by: Afreen Misbah <[email protected]>
2 parents 20f24a5 + 696a410 commit f8504eb

File tree

4 files changed

+229
-36
lines changed

4 files changed

+229
-36
lines changed

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway/nvmeof-gateway.component.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
<cd-nvmeof-tabs></cd-nvmeof-tabs>
22

3+
<div class="pb-3"
4+
cdsCol
5+
[columnNumbers]="{md: 4}">
6+
<cds-combo-box
7+
type="single"
8+
label="Selected Gateway Group"
9+
i18n-placeholder
10+
placeholder="Enter group"
11+
[items]="gwGroups"
12+
(selected)="onGroupSelection($event)"
13+
(clear)="onGroupClear()">
14+
<cds-dropdown-list></cds-dropdown-list>
15+
</cds-combo-box>
16+
</div>
17+
318
<legend i18n>
419
Gateways
520
<cd-help-text>
@@ -11,3 +26,11 @@
1126
[columns]="gatewayColumns">
1227
</cd-table>
1328
</div>
29+
30+
<ng-template #statusTpl
31+
let-row="data.row">
32+
<span class="badge"
33+
[ngClass]="row | pipeFunction:getStatusClass">
34+
{{ row.status_desc }}
35+
</span>
36+
</ng-template>
Lines changed: 114 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,144 @@
1-
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { of } from 'rxjs';
33
import { NvmeofGatewayComponent } from './nvmeof-gateway.component';
44
import { NvmeofService } from '../../../shared/api/nvmeof.service';
55
import { HttpClientModule } from '@angular/common/http';
66
import { SharedModule } from '~/app/shared/shared.module';
7+
import { ComboBoxModule, GridModule } from 'carbon-components-angular';
8+
import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
9+
import { CephServiceService } from '~/app/shared/api/ceph-service.service';
10+
11+
const mockServiceDaemons = [
12+
{
13+
daemon_type: 'nvmeof',
14+
daemon_id: 'nvmeof.default.ceph-node-01.kdcguk',
15+
daemon_name: 'nvmeof.nvmeof.default.ceph-node-01.kdcguk',
16+
hostname: 'ceph-node-01',
17+
container_id: '6fe5a9ae9c96',
18+
container_image_id: '32a3d75b7c146d6c37b04ee3c9ba883ab88a8f7ae8f286de268d0f41ebd86a51',
19+
container_image_name: 'quay.io/ceph/nvmeof:1.2.17',
20+
container_image_digests: [
21+
'quay.io/ceph/nvmeof@sha256:4308d05d3bb2167fc695d755316fec8d12ec3f00eb7639eeeabad38a5c4df0f9'
22+
],
23+
memory_usage: 89443532,
24+
cpu_percentage: '98.87%',
25+
version: '1.2.17',
26+
status: 1,
27+
status_desc: 'running'
28+
},
29+
{
30+
daemon_type: 'nvmeof',
31+
daemon_id: 'nvmeof.default.ceph-node-02.hybprc',
32+
daemon_name: 'nvmeof.nvmeof.default.ceph-node-02.hybprc',
33+
hostname: 'ceph-node-02',
34+
container_id: '2b061130726b',
35+
container_image_id: '32a3d75b7c146d6c37b04ee3c9ba883ab88a8f7ae8f286de268d0f41ebd86a51',
36+
container_image_name: 'quay.io/ceph/nvmeof:1.2.17',
37+
container_image_digests: [
38+
'quay.io/ceph/nvmeof@sha256:4308d05d3bb2167fc695d755316fec8d12ec3f00eb7639eeeabad38a5c4df0f9'
39+
],
40+
memory_usage: 89328189,
41+
cpu_percentage: '98.89%',
42+
version: '1.2.17',
43+
status: 1,
44+
status_desc: 'running'
45+
}
46+
];
747

848
const mockGateways = [
949
{
10-
cli_version: '',
11-
version: '1.2.5',
12-
name: 'client.nvmeof.rbd.ceph-node-01.jnmnwa',
13-
group: '',
14-
addr: '192.168.100.101',
15-
port: '5500',
16-
load_balancing_group: 1,
17-
spdk_version: '24.01'
50+
id: 'client.nvmeof.nvmeof.default.ceph-node-01.kdcguk',
51+
hostname: 'ceph-node-01',
52+
status_desc: 'running',
53+
status: 1
54+
},
55+
{
56+
id: 'client.nvmeof.nvmeof.default.ceph-node-02.hybprc',
57+
hostname: 'ceph-node-02',
58+
status_desc: 'running',
59+
status: 1
60+
}
61+
];
62+
63+
const mockGwGroups = [
64+
{
65+
content: 'default',
66+
serviceName: 'nvmeof.rbd.default'
67+
},
68+
{
69+
content: 'foo',
70+
serviceName: 'nvmeof.rbd.foo'
1871
}
1972
];
2073

74+
const mockServices = [
75+
[
76+
{
77+
service_name: 'nvmeof.rbd.default',
78+
service_type: 'nvmeof',
79+
unmanaged: false,
80+
spec: {
81+
group: 'default'
82+
}
83+
},
84+
{
85+
service_name: 'nvmeof.rbd.foo',
86+
service_type: 'nvmeof',
87+
unmanaged: false,
88+
spec: {
89+
group: 'foo'
90+
}
91+
}
92+
],
93+
2
94+
];
2195
class MockNvmeOfService {
22-
listGateways() {
23-
return of(mockGateways);
96+
listGatewayGroups() {
97+
return of(mockServices);
98+
}
99+
}
100+
101+
class MockCephServiceService {
102+
getDaemons(_service: string) {
103+
return of(mockServiceDaemons);
24104
}
25105
}
26106

27107
describe('NvmeofGatewayComponent', () => {
28108
let component: NvmeofGatewayComponent;
29109
let fixture: ComponentFixture<NvmeofGatewayComponent>;
30110

31-
beforeEach(fakeAsync(() => {
32-
TestBed.configureTestingModule({
33-
declarations: [NvmeofGatewayComponent],
34-
imports: [HttpClientModule, SharedModule],
35-
providers: [{ provide: NvmeofService, useClass: MockNvmeOfService }]
111+
beforeEach(async () => {
112+
await TestBed.configureTestingModule({
113+
declarations: [NvmeofGatewayComponent, NvmeofTabsComponent],
114+
imports: [HttpClientModule, SharedModule, ComboBoxModule, GridModule],
115+
providers: [
116+
{ provide: NvmeofService, useClass: MockNvmeOfService },
117+
{ provide: CephServiceService, useClass: MockCephServiceService }
118+
]
36119
}).compileComponents();
37-
}));
38120

39-
beforeEach(() => {
40121
fixture = TestBed.createComponent(NvmeofGatewayComponent);
41122
component = fixture.componentInstance;
123+
component.ngOnInit();
124+
fixture.detectChanges();
42125
});
43126

44127
it('should create', () => {
45128
expect(component).toBeTruthy();
46129
});
47130

48-
it('should retrieve gateways', fakeAsync(() => {
49-
component.getGateways();
50-
tick();
51-
expect(component.gateways).toEqual(mockGateways);
52-
}));
131+
it('should load gateway groups correctly', () => {
132+
expect(component.gwGroups.length).toBe(2);
133+
expect(component.gwGroups).toStrictEqual(mockGwGroups);
134+
});
135+
136+
it('should set service name of gateway groups correctly', () => {
137+
expect(component.groupService).toBe(mockServices[0][0].service_name);
138+
});
139+
140+
it('should set gateways correctly', () => {
141+
expect(component.gateways.length).toBe(2);
142+
expect(component.gateways).toStrictEqual(mockGateways);
143+
});
53144
});
Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,121 @@
1-
import { Component } from '@angular/core';
1+
import { Component, TemplateRef, ViewChild } from '@angular/core';
2+
3+
import _ from 'lodash';
24

35
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
46
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
5-
import { NvmeofGateway } from '~/app/shared/models/nvmeof';
67

78
import { NvmeofService } from '../../../shared/api/nvmeof.service';
9+
import { CephServiceSpec } from '~/app/shared/models/service.interface';
10+
import { CephServiceService } from '~/app/shared/api/ceph-service.service';
11+
import { Daemon } from '~/app/shared/models/daemon.interface';
12+
13+
type ComboBoxItem = {
14+
content: string;
15+
serviceName: string;
16+
selected?: boolean;
17+
};
18+
19+
type Gateway = {
20+
id: string;
21+
hostname: string;
22+
status: number;
23+
status_desc: string;
24+
};
825

926
@Component({
1027
selector: 'cd-nvmeof-gateway',
1128
templateUrl: './nvmeof-gateway.component.html',
1229
styleUrls: ['./nvmeof-gateway.component.scss']
1330
})
1431
export class NvmeofGatewayComponent {
15-
gateways: NvmeofGateway[] = [];
32+
@ViewChild('statusTpl', { static: true })
33+
statusTpl: TemplateRef<any>;
34+
35+
gateways: Gateway[] = [];
1636
gatewayColumns: any;
1737
selection = new CdTableSelection();
38+
gwGroups: ComboBoxItem[] = [];
39+
groupService: string = null;
1840

19-
constructor(private nvmeofService: NvmeofService, public actionLabels: ActionLabelsI18n) {}
41+
constructor(
42+
private nvmeofService: NvmeofService,
43+
private cephServiceService: CephServiceService,
44+
public actionLabels: ActionLabelsI18n
45+
) {}
2046

2147
ngOnInit() {
48+
this.getGatewayGroups();
2249
this.gatewayColumns = [
2350
{
24-
name: $localize`Name`,
25-
prop: 'name'
51+
name: $localize`Gateway ID`,
52+
prop: 'id'
2653
},
2754
{
28-
name: $localize`Address`,
29-
prop: 'addr'
55+
name: $localize`Host name`,
56+
prop: 'hostname'
3057
},
3158
{
32-
name: $localize`Port`,
33-
prop: 'port'
59+
name: $localize`Status`,
60+
prop: 'status_desc',
61+
cellTemplate: this.statusTpl
3462
}
3563
];
3664
}
3765

66+
// for Status column
67+
getStatusClass(row: Gateway): string {
68+
return _.get(
69+
{
70+
'-1': 'badge-danger',
71+
'0': 'badge-warning',
72+
'1': 'badge-success'
73+
},
74+
row.status,
75+
'badge-dark'
76+
);
77+
}
78+
79+
// Gateways
3880
getGateways() {
39-
this.nvmeofService.listGateways().subscribe((gateways: NvmeofGateway[] | NvmeofGateway) => {
40-
if (Array.isArray(gateways)) this.gateways = gateways;
41-
else this.gateways = [gateways];
81+
this.cephServiceService.getDaemons(this.groupService).subscribe((daemons: Daemon[]) => {
82+
this.gateways = daemons.length
83+
? daemons.map((daemon: Daemon) => {
84+
return {
85+
id: `client.${daemon.daemon_name}`,
86+
hostname: daemon.hostname,
87+
status_desc: daemon.status_desc,
88+
status: daemon.status
89+
};
90+
})
91+
: [];
92+
});
93+
}
94+
95+
// Gateway groups
96+
onGroupSelection(selected: ComboBoxItem) {
97+
selected.selected = true;
98+
this.groupService = selected.serviceName;
99+
this.getGateways();
100+
}
101+
102+
onGroupClear() {
103+
this.groupService = null;
104+
this.getGateways();
105+
}
106+
107+
getGatewayGroups() {
108+
this.nvmeofService.listGatewayGroups().subscribe((response: CephServiceSpec[][]) => {
109+
this.gwGroups = response?.[0]?.length
110+
? response[0].map((group: CephServiceSpec) => {
111+
return {
112+
content: group?.spec?.group,
113+
serviceName: group?.service_name
114+
};
115+
})
116+
: [];
117+
// Select first group if no group is selected
118+
if (!this.groupService && this.gwGroups.length) this.onGroupSelection(this.gwGroups[0]);
42119
});
43120
}
44121
}

src/pybind/mgr/dashboard/frontend/src/app/shared/models/daemon.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export interface Daemon {
55
container_image_name: string;
66
daemon_id: string;
77
daemon_type: string;
8+
daemon_name: string;
9+
hostname: string;
810
version: string;
911
status: number;
1012
status_desc: string;

0 commit comments

Comments
 (0)