]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/blob
2b28492bc1a47f204d8df03d5c876ebcbf5b3269
[ceph-ci.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 { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component';
22 import { PgSummaryPipe } from '../pg-summary.pipe';
23 import { DashboardV3Component } from './dashboard-v3.component';
24 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
25 import { AlertClass } from '~/app/shared/enum/health-icon.enum';
26 import { HealthSnapshotMap } from '~/app/shared/models/health.interface';
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 orchestratorService: OrchestratorService;
45   let getHealthStatusSpy: jasmine.Spy;
46   let getAlertsSpy: jasmine.Spy;
47   let fakeFeatureTogglesService: jasmine.Spy;
48
49   const healthStatusPayload: HealthSnapshotMap = {
50     fsid: '7d0cc9da-ca8d-4539-a953-ab062139c26a',
51     health: {
52       status: 'HEALTH_WARN',
53       checks: {
54         DASHBOARD_DEBUG: {
55           severity: 'HEALTH_WARN',
56           summary: {
57             message: 'Dashboard debug mode is enabled',
58             count: 0
59           },
60           muted: false
61         }
62       },
63       mutes: []
64     },
65     monmap: {
66       num_mons: 3
67     },
68     osdmap: {
69       in: 3,
70       up: 3,
71       num_osds: 3
72     },
73     pgmap: {
74       pgs_by_state: [
75         {
76           state_name: 'active+clean',
77           count: 497
78         }
79       ],
80       num_pools: 14,
81       bytes_used: 3236978688,
82       bytes_total: 325343772672,
83       num_pgs: 497
84     },
85     mgrmap: {
86       num_active: 1,
87       num_standbys: 0
88     },
89     fsmap: {
90       num_standbys: 2,
91       num_active: 1
92     },
93     num_rgw_gateways: 3,
94     num_iscsi_gateways: {
95       up: 0,
96       down: 0
97     },
98     num_hosts: 1
99   };
100
101   const alertsPayload: AlertmanagerAlert[] = [
102     {
103       labels: {
104         alertname: 'CephMgrPrometheusModuleInactive',
105         instance: 'ceph2:9283',
106         job: 'ceph',
107         severity: 'critical'
108       },
109       annotations: {
110         description: 'The mgr/prometheus module at ceph2:9283 is unreachable.',
111         summary: 'The mgr/prometheus module is not available'
112       },
113       startsAt: '2022-09-28T08:23:41.152Z',
114       endsAt: '2022-09-28T15:28:01.152Z',
115       generatorURL: 'http://prometheus:9090/testUrl',
116       status: {
117         state: 'active',
118         silencedBy: null,
119         inhibitedBy: null
120       },
121       receivers: ['ceph2'],
122       fingerprint: 'fingerprint'
123     },
124     {
125       labels: {
126         alertname: 'CephOSDDownHigh',
127         instance: 'ceph:9283',
128         job: 'ceph',
129         severity: 'critical'
130       },
131       annotations: {
132         description: '66.67% or 2 of 3 OSDs are down (>= 10%).',
133         summary: 'More than 10% of OSDs are down'
134       },
135       startsAt: '2022-09-28T14:17:22.665Z',
136       endsAt: '2022-09-28T15:28:32.665Z',
137       generatorURL: 'http://prometheus:9090/testUrl',
138       status: {
139         state: 'active',
140         silencedBy: null,
141         inhibitedBy: null
142       },
143       receivers: ['default'],
144       fingerprint: 'fingerprint'
145     },
146     {
147       labels: {
148         alertname: 'CephHealthWarning',
149         instance: 'ceph:9283',
150         job: 'ceph',
151         severity: 'warning'
152       },
153       annotations: {
154         description: 'The cluster state has been HEALTH_WARN for more than 15 minutes.',
155         summary: 'Ceph is in the WARNING state'
156       },
157       startsAt: '2022-09-28T08:41:38.454Z',
158       endsAt: '2022-09-28T15:28:38.454Z',
159       generatorURL: 'http://prometheus:9090/testUrl',
160       status: {
161         state: 'active',
162         silencedBy: null,
163         inhibitedBy: null
164       },
165       receivers: ['ceph'],
166       fingerprint: 'fingerprint'
167     }
168   ];
169
170   const orchName: any = 'Cephadm';
171
172   configureTestBed({
173     imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule],
174     declarations: [DashboardV3Component, DashboardPieComponent, PgSummaryPipe],
175     schemas: [NO_ERRORS_SCHEMA],
176     providers: [
177       { provide: SummaryService, useClass: SummaryServiceMock },
178       PrometheusAlertService,
179       CssHelper,
180       PgCategoryService
181     ]
182   });
183
184   beforeEach(() => {
185     fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue(
186       of({
187         rbd: true,
188         mirroring: true,
189         iscsi: true,
190         cephfs: true,
191         rgw: true
192       })
193     );
194     fixture = TestBed.createComponent(DashboardV3Component);
195     component = fixture.componentInstance;
196     orchestratorService = TestBed.inject(OrchestratorService);
197     getHealthStatusSpy = spyOn(TestBed.inject(HealthService), 'getHealthSnapshot');
198     getHealthStatusSpy.and.returnValue(of(healthStatusPayload));
199     getAlertsSpy = spyOn(TestBed.inject(PrometheusService), 'getAlerts');
200     getAlertsSpy.and.returnValue(of(alertsPayload));
201     component.prometheusAlertService.alerts = alertsPayload;
202     component.isAlertmanagerConfigured = true;
203     let prometheusAlertService = TestBed.inject(PrometheusAlertService);
204     spyOn(prometheusAlertService, 'getAlerts').and.callFake(() => of([]));
205     prometheusAlertService.activeCriticalAlerts = 2;
206     prometheusAlertService.activeWarningAlerts = 1;
207   });
208
209   it('should create', () => {
210     expect(component).toBeTruthy();
211   });
212
213   it('should render all cards', () => {
214     fixture.detectChanges();
215     const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card');
216     expect(dashboardCards.length).toBe(5);
217   });
218
219   it('should get corresponding data into detailsCardData', () => {
220     spyOn(orchestratorService, 'getName').and.returnValue(of(orchName));
221     component.ngOnInit();
222     expect(component.detailsCardData.fsid).toBe(healthStatusPayload['fsid']);
223     expect(component.detailsCardData.orchestrator).toBe('Cephadm');
224     expect(component.detailsCardData.cephVersion).toBe('17.0.0-12222-gcd0cd7cb quincy (dev)');
225   });
226
227   it('should check if the respective icon is shown for each status', () => {
228     const payload = _.cloneDeep(healthStatusPayload);
229
230     // HEALTH_WARN
231     payload.health['status'] = 'HEALTH_WARN';
232     payload.health['checks'] = {
233       FAKE_CHECK: {
234         severity: 'HEALTH_WARN',
235         summary: { message: 'fake warning', count: 1 },
236         muted: false
237       }
238     };
239
240     getHealthStatusSpy.and.returnValue(of(payload));
241     fixture.detectChanges();
242     const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"] i'));
243     expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
244
245     // HEALTH_ERR
246     payload.health['status'] = 'HEALTH_ERR';
247     payload.health['checks'] = {
248       FAKE_CHECK: {
249         severity: 'HEALTH_ERR',
250         summary: { message: 'fake error', count: 1 },
251         muted: false
252       }
253     };
254
255     getHealthStatusSpy.and.returnValue(of(payload));
256     fixture.detectChanges();
257     expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
258
259     // HEALTH_OK
260     payload.health['status'] = 'HEALTH_OK';
261     payload.health['checks'] = {};
262
263     getHealthStatusSpy.and.returnValue(of(payload));
264     fixture.detectChanges();
265     expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`);
266   });
267
268   it('should show the actual alert count on each alerts pill', () => {
269     fixture.detectChanges();
270
271     const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts] span'));
272
273     const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts] span'));
274
275     expect(warningAlerts.nativeElement.textContent).toBe('1');
276     expect(dangerAlerts.nativeElement.textContent).toBe('2');
277   });
278
279   it('should show the critical alerts window and its content', () => {
280     const payload = _.cloneDeep(alertsPayload[0]);
281     component.toggleAlertsWindow(AlertClass[0]);
282     fixture.detectChanges();
283
284     const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
285
286     expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
287     expect(component.alertType).not.toBe('warning');
288   });
289
290   it('should show the warning alerts window and its content', () => {
291     const payload = _.cloneDeep(alertsPayload[2]);
292     component.toggleAlertsWindow(AlertClass.warning);
293     fixture.detectChanges();
294
295     const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title'));
296
297     expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname);
298     expect(component.alertType).not.toBe('critical');
299   });
300
301   it('should only show the pills when the alerts are not empty', () => {
302     spyOn(TestBed.inject(PrometheusAlertService), 'alerts').and.returnValue(0);
303     fixture.detectChanges();
304
305     const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts]'));
306
307     const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts]'));
308
309     expect(warningAlerts).toBe(null);
310     expect(dangerAlerts).toBe(null);
311   });
312
313   it('should render "Status" card text that is not clickable', () => {
314     const payload = _.cloneDeep(healthStatusPayload);
315     payload.health['status'] = 'HEALTH_OK';
316     payload.health['checks'] = null;
317
318     getHealthStatusSpy.and.returnValue(of(payload));
319     fixture.detectChanges();
320     const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
321     const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
322     expect(clickableContent).toBeNull();
323   });
324
325   it('should render "Status" card text that is clickable (popover)', () => {
326     const payload = _.cloneDeep(healthStatusPayload);
327     payload.health['status'] = 'HEALTH_WARN';
328     payload.health['checks'] = {
329       FAKE_CHECK: {
330         severity: 'HEALTH_WARN',
331         summary: { message: 'fake warning', count: 1 },
332         muted: false
333       }
334     };
335
336     getHealthStatusSpy.and.returnValue(of(payload));
337     fixture.detectChanges();
338
339     const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]'));
340     const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary'));
341     expect(clickableContent).not.toBeNull();
342   });
343
344   describe('features disabled', () => {
345     beforeEach(() => {
346       fakeFeatureTogglesService.and.returnValue(
347         of({
348           rbd: false,
349           mirroring: false,
350           iscsi: false,
351           cephfs: false,
352           rgw: false
353         })
354       );
355       fixture = TestBed.createComponent(DashboardV3Component);
356       component = fixture.componentInstance;
357     });
358
359     it('should not render items related to disabled features', () => {
360       fixture.detectChanges();
361
362       const iscsiCard = fixture.debugElement.query(By.css('li[id=iscsi-item]'));
363       const rgwCard = fixture.debugElement.query(By.css('li[id=rgw-item]'));
364       const mds = fixture.debugElement.query(By.css('li[id=mds-item]'));
365
366       expect(iscsiCard).toBeFalsy();
367       expect(rgwCard).toBeFalsy();
368       expect(mds).toBeFalsy();
369     });
370   });
371 });