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