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