]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
f2f5d0bb50832cc94c7ab14e70e1c679d6e94642
[ceph.git] /
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { NO_ERRORS_SCHEMA } from '@angular/core';
3 import { ComponentFixture, TestBed } from '@angular/core/testing';
4 import { By } from '@angular/platform-browser';
5 import { RouterTestingModule } from '@angular/router/testing';
6
7 import _ from 'lodash';
8 import { ToastrModule } from 'ngx-toastr';
9 import { BehaviorSubject, of } from 'rxjs';
10
11 import { HealthService } from '~/app/shared/api/health.service';
12 import { PrometheusService } from '~/app/shared/api/prometheus.service';
13 import { CssHelper } from '~/app/shared/classes/css-helper';
14 import { AlertmanagerAlert } from '~/app/shared/models/prometheus-alerts';
15 import { FeatureTogglesService } from '~/app/shared/services/feature-toggles.service';
16 import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
17 import { SummaryService } from '~/app/shared/services/summary.service';
18 import { SharedModule } from '~/app/shared/shared.module';
19 import { configureTestBed } from '~/testing/unit-test-helper';
20 import { PgCategoryService } from '../../shared/pg-category.service';
21 import { CardRowComponent } from '../card-row/card-row.component';
22 import { CardComponent } from '../card/card.component';
23 import { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component';
24 import { PgSummaryPipe } from '../pg-summary.pipe';
25 import { DashboardV3Component } from './dashboard-v3.component';
26 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
27
28 export class SummaryServiceMock {
29   summaryDataSource = new BehaviorSubject({
30     version:
31       'ceph version 17.0.0-12222-gcd0cd7cb ' +
32       '(b8193bb4cda16ccc5b028c3e1df62bc72350a15d) quincy (dev)'
33   });
34   summaryData$ = this.summaryDataSource.asObservable();
35
36   subscribe(call: any) {
37     return this.summaryData$.subscribe(call);
38   }
39 }
40
41 describe('Dashbord Component', () => {
42   let component: DashboardV3Component;
43   let fixture: ComponentFixture<DashboardV3Component>;
44   let healthService: HealthService;
45   let orchestratorService: OrchestratorService;
46   let getHealthSpy: jasmine.Spy;
47   let getAlertsSpy: jasmine.Spy;
48   let fakeFeatureTogglesService: jasmine.Spy;
49
50   const healthPayload: Record<string, any> = {
51     health: { status: 'HEALTH_OK' },
52     mon_status: { monmap: { mons: [] }, quorum: [] },
53     osd_map: { osds: [] },
54     mgr_map: { standbys: [] },
55     hosts: 0,
56     rgw: 0,
57     fs_map: { filesystems: [], standbys: [] },
58     iscsi_daemons: 1,
59     client_perf: {},
60     scrub_status: 'Inactive',
61     pools: [],
62     df: { stats: {} },
63     pg_info: { object_stats: { num_objects: 1 } }
64   };
65
66   const alertsPayload: AlertmanagerAlert[] = [
67     {
68       labels: {
69         alertname: 'CephMgrPrometheusModuleInactive',
70         instance: 'ceph2:9283',
71         job: 'ceph',
72         severity: 'critical'
73       },
74       annotations: {
75         description: 'The mgr/prometheus module at ceph2:9283 is unreachable.',
76         summary: 'The mgr/prometheus module is not available'
77       },
78       startsAt: '2022-09-28T08:23:41.152Z',
79       endsAt: '2022-09-28T15:28:01.152Z',
80       generatorURL: 'http://prometheus:9090/testUrl',
81       status: {
82         state: 'active',
83         silencedBy: null,
84         inhibitedBy: null
85       },
86       receivers: ['ceph2'],
87       fingerprint: 'fingerprint'
88     },
89     {
90       labels: {
91         alertname: 'CephOSDDownHigh',
92         instance: 'ceph:9283',
93         job: 'ceph',
94         severity: 'critical'
95       },
96       annotations: {
97         description: '66.67% or 2 of 3 OSDs are down (>= 10%).',
98         summary: 'More than 10% of OSDs are down'
99       },
100       startsAt: '2022-09-28T14:17:22.665Z',
101       endsAt: '2022-09-28T15:28:32.665Z',
102       generatorURL: 'http://prometheus:9090/testUrl',
103       status: {
104         state: 'active',
105         silencedBy: null,
106         inhibitedBy: null
107       },
108       receivers: ['default'],
109       fingerprint: 'fingerprint'
110     },
111     {
112       labels: {
113         alertname: 'CephHealthWarning',
114         instance: 'ceph:9283',
115         job: 'ceph',
116         severity: 'warning'
117       },
118       annotations: {
119         description: 'The cluster state has been HEALTH_WARN for more than 15 minutes.',
120         summary: 'Ceph is in the WARNING state'
121       },
122       startsAt: '2022-09-28T08:41:38.454Z',
123       endsAt: '2022-09-28T15:28:38.454Z',
124       generatorURL: 'http://prometheus:9090/testUrl',
125       status: {
126         state: 'active',
127         silencedBy: null,
128         inhibitedBy: null
129       },
130       receivers: ['ceph'],
131       fingerprint: 'fingerprint'
132     }
133   ];
134
135   const configValueData: any = 'e90a0d58-658e-4148-8f61-e896c86f0696';
136
137   const orchName: any = 'Cephadm';
138
139   configureTestBed({
140     imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule],
141     declarations: [
142       DashboardV3Component,
143       CardComponent,
144       DashboardPieComponent,
145       CardRowComponent,
146       PgSummaryPipe
147     ],
148     schemas: [NO_ERRORS_SCHEMA],
149     providers: [
150       { provide: SummaryService, useClass: SummaryServiceMock },
151       {
152         provide: PrometheusAlertService,
153         useValue: {
154           activeCriticalAlerts: 2,
155           activeWarningAlerts: 1
156         }
157       },
158       CssHelper,
159       PgCategoryService
160     ]
161   });
162
163   beforeEach(() => {
164     fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue(
165       of({
166         rbd: true,
167         mirroring: true,
168         iscsi: true,
169         cephfs: true,
170         rgw: true
171       })
172     );
173     fixture = TestBed.createComponent(DashboardV3Component);
174     component = fixture.componentInstance;
175     healthService = TestBed.inject(HealthService);
176     orchestratorService = TestBed.inject(OrchestratorService);
177     getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth');
178     getHealthSpy.and.returnValue(of(healthPayload));
179     spyOn(TestBed.inject(PrometheusService), 'ifAlertmanagerConfigured').and.callFake((fn) => fn());
180     getAlertsSpy = spyOn(TestBed.inject(PrometheusService), 'getAlerts');
181     getAlertsSpy.and.returnValue(of(alertsPayload));
182   });
183
184   it('should create', () => {
185     expect(component).toBeTruthy();
186   });
187
188   it('should render all cards', () => {
189     fixture.detectChanges();
190     const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card');
191     expect(dashboardCards.length).toBe(5);
192   });
193
194   it('should get corresponding data into detailsCardData', () => {
195     spyOn(healthService, 'getClusterFsid').and.returnValue(of(configValueData));
196     spyOn(orchestratorService, 'getName').and.returnValue(of(orchName));
197     component.ngOnInit();
198     expect(component.detailsCardData.fsid).toBe('e90a0d58-658e-4148-8f61-e896c86f0696');
199     expect(component.detailsCardData.orchestrator).toBe('Cephadm');
200     expect(component.detailsCardData.cephVersion).toBe('17.0.0-12222-gcd0cd7cb quincy (dev)');
201   });
202
203   it('should check if the respective icon is shown for each status', () => {
204     const payload = _.cloneDeep(healthPayload);
205
206     // HEALTH_WARN
207     payload.health['status'] = 'HEALTH_WARN';
208     payload.health['checks'] = [
209       { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } }
210     ];
211
212     getHealthSpy.and.returnValue(of(payload));
213     fixture.detectChanges();
214     const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"] i'));
215     expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
216
217     // HEALTH_ERR
218     payload.health['status'] = 'HEALTH_ERR';
219     payload.health['checks'] = [
220       { severity: 'HEALTH_ERR', type: 'ERR', summary: { message: 'fake error' } }
221     ];
222
223     getHealthSpy.and.returnValue(of(payload));
224     fixture.detectChanges();
225     expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
226
227     // HEALTH_OK
228     payload.health['status'] = 'HEALTH_OK';
229     payload.health['checks'] = [
230       { severity: 'HEALTH_OK', type: 'OK', summary: { message: 'fake success' } }
231     ];
232
233     getHealthSpy.and.returnValue(of(payload));
234     fixture.detectChanges();
235     expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
236   });
237
238   it('should show the actual alert count on each alerts pill', () => {
239     fixture.detectChanges();
240
241     const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts] span'));
242
243     const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts] span'));
244
245     expect(warningAlerts.nativeElement.textContent).toBe('1');
246     expect(dangerAlerts.nativeElement.textContent).toBe('2');
247   });
248
249   it('should show the critical alerts window and its content', () => {
250     const payload = _.cloneDeep(alertsPayload[0]);
251     component.toggleAlertsWindow('danger');
252     fixture.detectChanges();
253
254     const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
255
256     expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
257     expect(component.alertType).not.toBe('warning');
258   });
259
260   it('should show the warning alerts window and its content', () => {
261     const payload = _.cloneDeep(alertsPayload[2]);
262     component.toggleAlertsWindow('warning');
263     fixture.detectChanges();
264
265     const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
266
267     expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
268     expect(component.alertType).not.toBe('critical');
269   });
270
271   it('should only show the pills when the alerts are not empty', () => {
272     spyOn(TestBed.inject(PrometheusAlertService), 'activeCriticalAlerts').and.returnValue(0);
273     spyOn(TestBed.inject(PrometheusAlertService), 'activeWarningAlerts').and.returnValue(0);
274     fixture.detectChanges();
275
276     const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts]'));
277
278     const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts]'));
279
280     expect(warningAlerts).toBe(null);
281     expect(dangerAlerts).toBe(null);
282   });
283
284   it('should render "Status" card text that is not clickable', () => {
285     fixture.detectChanges();
286
287     const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
288     const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
289     expect(clickableContent).toBeNull();
290   });
291
292   it('should render "Status" card text that is clickable (popover)', () => {
293     const payload = _.cloneDeep(healthPayload);
294     payload.health['status'] = 'HEALTH_WARN';
295     payload.health['checks'] = [
296       { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } }
297     ];
298
299     getHealthSpy.and.returnValue(of(payload));
300     fixture.detectChanges();
301
302     const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
303     const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
304     expect(clickableContent).not.toBeNull();
305   });
306
307   describe('features disabled', () => {
308     beforeEach(() => {
309       fakeFeatureTogglesService.and.returnValue(
310         of({
311           rbd: false,
312           mirroring: false,
313           iscsi: false,
314           cephfs: false,
315           rgw: false
316         })
317       );
318       fixture = TestBed.createComponent(DashboardV3Component);
319       component = fixture.componentInstance;
320     });
321
322     it('should not render items related to disabled features', () => {
323       fixture.detectChanges();
324
325       const iscsiCard = fixture.debugElement.query(By.css('li[id=iscsi-item]'));
326       const rgwCard = fixture.debugElement.query(By.css('li[id=rgw-item]'));
327       const mds = fixture.debugElement.query(By.css('li[id=mds-item]'));
328
329       expect(iscsiCard).toBeFalsy();
330       expect(rgwCard).toBeFalsy();
331       expect(mds).toBeFalsy();
332     });
333   });
334 });