]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts
update ceph source to reef 18.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / hosts / hosts.component.spec.ts
CommitLineData
aee94f69 1import { HttpHeaders } from '@angular/common/http';
11fdf7f2 2import { HttpClientTestingModule } from '@angular/common/http/testing';
f67539c2 3import { ComponentFixture, TestBed } from '@angular/core/testing';
e306af50 4import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
11fdf7f2
TL
5import { RouterTestingModule } from '@angular/router/testing';
6
9f95a23c
TL
7import { ToastrModule } from 'ngx-toastr';
8import { of } from 'rxjs';
11fdf7f2 9
f67539c2
TL
10import { CephModule } from '~/app/ceph/ceph.module';
11import { CephSharedModule } from '~/app/ceph/shared/ceph-shared.module';
12import { CoreModule } from '~/app/core/core.module';
13import { HostService } from '~/app/shared/api/host.service';
14import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
15import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
a4b75251 16import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
f67539c2
TL
17import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
18import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
19import { Permissions } from '~/app/shared/models/permissions';
20import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
21import { SharedModule } from '~/app/shared/shared.module';
22import {
23 configureTestBed,
24 OrchestratorHelper,
25 TableActionHelper
26} from '~/testing/unit-test-helper';
11fdf7f2
TL
27import { HostsComponent } from './hosts.component';
28
b3b6e05e
TL
29class MockShowForceMaintenanceModal {
30 showModal = false;
31 showModalDialog(msg: string) {
32 if (
33 msg.includes('WARNING') &&
34 !msg.includes('It is NOT safe to stop') &&
35 !msg.includes('ALERT') &&
522d829b 36 !msg.includes('unsafe to stop')
b3b6e05e
TL
37 ) {
38 this.showModal = true;
39 }
40 }
41}
42
11fdf7f2
TL
43describe('HostsComponent', () => {
44 let component: HostsComponent;
45 let fixture: ComponentFixture<HostsComponent>;
9f95a23c 46 let hostListSpy: jasmine.Spy;
f67539c2 47 let orchService: OrchestratorService;
b3b6e05e 48 let showForceMaintenanceModal: MockShowForceMaintenanceModal;
aee94f69 49 let headers: HttpHeaders;
11fdf7f2
TL
50
51 const fakeAuthStorageService = {
52 getPermissions: () => {
53 return new Permissions({ hosts: ['read', 'update', 'create', 'delete'] });
54 }
55 };
56
57 configureTestBed({
58 imports: [
e306af50 59 BrowserAnimationsModule,
9f95a23c 60 CephSharedModule,
11fdf7f2
TL
61 SharedModule,
62 HttpClientTestingModule,
9f95a23c
TL
63 RouterTestingModule,
64 ToastrModule.forRoot(),
65 CephModule,
66 CoreModule
11fdf7f2 67 ],
f67539c2
TL
68 providers: [
69 { provide: AuthStorageService, useValue: fakeAuthStorageService },
70 TableActionsComponent
71 ]
11fdf7f2
TL
72 });
73
74 beforeEach(() => {
b3b6e05e 75 showForceMaintenanceModal = new MockShowForceMaintenanceModal();
11fdf7f2
TL
76 fixture = TestBed.createComponent(HostsComponent);
77 component = fixture.componentInstance;
f67539c2
TL
78 hostListSpy = spyOn(TestBed.inject(HostService), 'list');
79 orchService = TestBed.inject(OrchestratorService);
aee94f69 80 headers = new HttpHeaders().set('x-total-count', '10');
11fdf7f2
TL
81 });
82
83 it('should create', () => {
84 expect(component).toBeTruthy();
85 });
86
f67539c2 87 it('should render hosts list even with not permission mapped services', () => {
11fdf7f2
TL
88 const hostname = 'ceph.dev';
89 const payload = [
90 {
91 services: [
92 {
93 type: 'osd',
94 id: '0'
95 },
96 {
97 type: 'rgw',
98 id: 'rgw'
99 },
100 {
101 type: 'notPermissionMappedService',
102 id: '1'
103 }
104 ],
105 hostname: hostname,
aee94f69
TL
106 labels: ['foo', 'bar'],
107 headers: headers
11fdf7f2
TL
108 }
109 ];
110
a4b75251 111 OrchestratorHelper.mockStatus(false);
aee94f69 112 fixture.detectChanges();
a4b75251
TL
113 hostListSpy.and.callFake(() => of(payload));
114 fixture.detectChanges();
115
116 component.getHosts(new CdTableFetchDataContext(() => undefined));
117 fixture.detectChanges();
118
119 const spans = fixture.debugElement.nativeElement.querySelectorAll(
120 '.datatable-body-cell-label span'
121 );
1e59de90 122 expect(spans[0].textContent.trim()).toBe(hostname);
a4b75251
TL
123 });
124
33c7a0ef
TL
125 it('should show the exact count of the repeating daemons', () => {
126 const hostname = 'ceph.dev';
127 const payload = [
128 {
39ae355f 129 service_instances: [
33c7a0ef
TL
130 {
131 type: 'mgr',
39ae355f 132 count: 2
33c7a0ef
TL
133 },
134 {
135 type: 'osd',
39ae355f 136 count: 3
33c7a0ef
TL
137 },
138 {
139 type: 'rgw',
39ae355f 140 count: 1
33c7a0ef
TL
141 }
142 ],
143 hostname: hostname,
aee94f69
TL
144 labels: ['foo', 'bar'],
145 headers: headers
33c7a0ef
TL
146 }
147 ];
148
149 OrchestratorHelper.mockStatus(false);
aee94f69 150 fixture.detectChanges();
33c7a0ef
TL
151 hostListSpy.and.callFake(() => of(payload));
152 fixture.detectChanges();
153
154 component.getHosts(new CdTableFetchDataContext(() => undefined));
155 fixture.detectChanges();
156
157 const spans = fixture.debugElement.nativeElement.querySelectorAll(
158 '.datatable-body-cell-label span span.badge.badge-background-primary'
159 );
160 expect(spans[0].textContent).toContain('mgr: 2');
161 expect(spans[1].textContent).toContain('osd: 3');
162 expect(spans[2].textContent).toContain('rgw: 1');
163 });
164
a4b75251
TL
165 it('should test if host facts are tranformed correctly if orch available', () => {
166 const features = [OrchestratorFeature.HOST_FACTS];
167 const payload = [
168 {
169 hostname: 'host_test',
170 services: [
171 {
172 type: 'osd',
173 id: '0'
174 }
175 ],
176 cpu_count: 2,
177 cpu_cores: 1,
178 memory_total_kb: 1024,
179 hdd_count: 4,
180 hdd_capacity_bytes: 1024,
181 flash_count: 4,
182 flash_capacity_bytes: 1024,
aee94f69
TL
183 nic_count: 1,
184 headers: headers
a4b75251
TL
185 }
186 ];
187 OrchestratorHelper.mockStatus(true, features);
aee94f69 188 fixture.detectChanges();
a4b75251
TL
189 hostListSpy.and.callFake(() => of(payload));
190 fixture.detectChanges();
191
192 component.getHosts(new CdTableFetchDataContext(() => undefined));
193 expect(hostListSpy).toHaveBeenCalled();
194 expect(component.hosts[0]['cpu_count']).toEqual(2);
195 expect(component.hosts[0]['memory_total_bytes']).toEqual(1048576);
196 expect(component.hosts[0]['raw_capacity']).toEqual(2048);
197 expect(component.hosts[0]['hdd_count']).toEqual(4);
198 expect(component.hosts[0]['flash_count']).toEqual(4);
199 expect(component.hosts[0]['cpu_cores']).toEqual(1);
200 expect(component.hosts[0]['nic_count']).toEqual(1);
201 });
202
203 it('should test if host facts are unavailable if no orch available', () => {
204 const payload = [
205 {
206 hostname: 'host_test',
207 services: [
208 {
209 type: 'osd',
210 id: '0'
211 }
aee94f69
TL
212 ],
213 headers: headers
a4b75251
TL
214 }
215 ];
216 OrchestratorHelper.mockStatus(false);
aee94f69 217 fixture.detectChanges();
a4b75251
TL
218 hostListSpy.and.callFake(() => of(payload));
219 fixture.detectChanges();
220
221 component.getHosts(new CdTableFetchDataContext(() => undefined));
222 fixture.detectChanges();
223
224 const spans = fixture.debugElement.nativeElement.querySelectorAll(
225 '.datatable-body-cell-label span'
226 );
20effc67 227 expect(spans[7].textContent).toBe('N/A');
a4b75251
TL
228 });
229
230 it('should test if host facts are unavailable if get_fatcs orch feature is not available', () => {
231 const payload = [
232 {
233 hostname: 'host_test',
234 services: [
235 {
236 type: 'osd',
237 id: '0'
238 }
aee94f69
TL
239 ],
240 headers: headers
a4b75251
TL
241 }
242 ];
f67539c2 243 OrchestratorHelper.mockStatus(true);
aee94f69 244 fixture.detectChanges();
9f95a23c 245 hostListSpy.and.callFake(() => of(payload));
f67539c2 246 fixture.detectChanges();
11fdf7f2 247
a4b75251
TL
248 component.getHosts(new CdTableFetchDataContext(() => undefined));
249 fixture.detectChanges();
11fdf7f2 250
a4b75251
TL
251 const spans = fixture.debugElement.nativeElement.querySelectorAll(
252 '.datatable-body-cell-label span'
253 );
20effc67 254 expect(spans[7].textContent).toBe('N/A');
f67539c2 255 });
f6b5b4d7 256
33c7a0ef
TL
257 it('should test if memory/raw capacity columns shows N/A if facts are available but in fetching state', () => {
258 const features = [OrchestratorFeature.HOST_FACTS];
259 let hostPayload: any[];
260 hostPayload = [
261 {
262 hostname: 'host_test',
263 services: [
264 {
265 type: 'osd',
266 id: '0'
267 }
268 ],
269 cpu_count: 2,
270 cpu_cores: 1,
271 memory_total_kb: undefined,
272 hdd_count: 4,
273 hdd_capacity_bytes: undefined,
274 flash_count: 4,
275 flash_capacity_bytes: undefined,
aee94f69
TL
276 nic_count: 1,
277 headers: headers
33c7a0ef
TL
278 }
279 ];
280 OrchestratorHelper.mockStatus(true, features);
aee94f69 281 fixture.detectChanges();
33c7a0ef
TL
282 hostListSpy.and.callFake(() => of(hostPayload));
283 fixture.detectChanges();
284
285 component.getHosts(new CdTableFetchDataContext(() => undefined));
286 expect(component.hosts[0]['memory_total_bytes']).toEqual('N/A');
287 expect(component.hosts[0]['raw_capacity']).toEqual('N/A');
288 });
289
b3b6e05e
TL
290 it('should show force maintenance modal when it is safe to stop host', () => {
291 const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service.
292 Service will not be operational with no daemons left. At
293 least 1 daemon must be running to guarantee service.`;
294 showForceMaintenanceModal.showModalDialog(errorMsg);
295 expect(showForceMaintenanceModal.showModal).toBeTruthy();
296 });
297
298 it('should not show force maintenance modal when error is an ALERT', () => {
299 const errorMsg = `ALERT: Cannot stop active Mgr daemon, Please switch active Mgrs
300 with 'ceph mgr fail ceph-node-00'`;
301 showForceMaintenanceModal.showModalDialog(errorMsg);
302 expect(showForceMaintenanceModal.showModal).toBeFalsy();
303 });
304
305 it('should not show force maintenance modal when it is not safe to stop host', () => {
306 const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service.
307 Service will not be operational with no daemons left. At
308 least 1 daemon must be running to guarantee service.
309 It is NOT safe to stop ['mon.ceph-node-00']: not enough
310 monitors would be available (ceph-node-02) after stopping mons`;
311 showForceMaintenanceModal.showModalDialog(errorMsg);
312 expect(showForceMaintenanceModal.showModal).toBeFalsy();
313 });
314
522d829b
TL
315 it('should not show force maintenance modal when it is unsafe to stop host', () => {
316 const errorMsg = 'unsafe to stop osd.0 because of some unknown reason';
b3b6e05e
TL
317 showForceMaintenanceModal.showModalDialog(errorMsg);
318 expect(showForceMaintenanceModal.showModal).toBeFalsy();
319 });
320
f67539c2
TL
321 describe('table actions', () => {
322 const fakeHosts = require('./fixtures/host_list_response.json');
f6b5b4d7
TL
323
324 beforeEach(() => {
aee94f69
TL
325 let headers = new HttpHeaders().set('x-total-count', '10');
326 headers = headers.set('x-total-count', '10');
327 fakeHosts[0].headers = headers;
328 fakeHosts[1].headers = headers;
f6b5b4d7
TL
329 });
330
f67539c2
TL
331 const testTableActions = async (
332 orch: boolean,
333 features: OrchestratorFeature[],
334 tests: { selectRow?: number; expectResults: any }[]
335 ) => {
336 OrchestratorHelper.mockStatus(orch, features);
337 fixture.detectChanges();
338 await fixture.whenStable();
f91f0fd5 339
aee94f69
TL
340 component.getHosts(new CdTableFetchDataContext(() => undefined));
341 hostListSpy.and.callFake(() => of(fakeHosts));
342 fixture.detectChanges();
f67539c2
TL
343 for (const test of tests) {
344 if (test.selectRow) {
345 component.selection = new CdTableSelection();
346 component.selection.selected = [test.selectRow];
f91f0fd5 347 }
f67539c2
TL
348 await TableActionHelper.verifyTableActions(
349 fixture,
350 component.tableActions,
351 test.expectResults
352 );
353 }
354 };
355
356 it('should have correct states when Orchestrator is enabled', async () => {
357 const tests = [
358 {
359 expectResults: {
a4b75251 360 Add: { disabled: false, disableDesc: '' },
f67539c2 361 Edit: { disabled: true, disableDesc: '' },
a4b75251 362 Remove: { disabled: true, disableDesc: '' }
f67539c2
TL
363 }
364 },
365 {
366 selectRow: fakeHosts[0], // non-orchestrator host
367 expectResults: {
a4b75251 368 Add: { disabled: false, disableDesc: '' },
f67539c2 369 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
a4b75251 370 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
f67539c2
TL
371 }
372 },
373 {
374 selectRow: fakeHosts[1], // orchestrator host
375 expectResults: {
a4b75251 376 Add: { disabled: false, disableDesc: '' },
f67539c2 377 Edit: { disabled: false, disableDesc: '' },
a4b75251 378 Remove: { disabled: false, disableDesc: '' }
f67539c2 379 }
f91f0fd5 380 }
f67539c2
TL
381 ];
382
383 const features = [
a4b75251 384 OrchestratorFeature.HOST_ADD,
f67539c2 385 OrchestratorFeature.HOST_LABEL_ADD,
a4b75251 386 OrchestratorFeature.HOST_REMOVE,
20effc67
TL
387 OrchestratorFeature.HOST_LABEL_REMOVE,
388 OrchestratorFeature.HOST_DRAIN
f67539c2
TL
389 ];
390 await testTableActions(true, features, tests);
f91f0fd5
TL
391 });
392
f67539c2
TL
393 it('should have correct states when Orchestrator is disabled', async () => {
394 const resultNoOrchestrator = {
395 disabled: true,
396 disableDesc: orchService.disableMessages.noOrchestrator
397 };
398 const tests = [
399 {
400 expectResults: {
a4b75251 401 Add: resultNoOrchestrator,
f67539c2 402 Edit: { disabled: true, disableDesc: '' },
a4b75251 403 Remove: { disabled: true, disableDesc: '' }
f67539c2
TL
404 }
405 },
406 {
407 selectRow: fakeHosts[0], // non-orchestrator host
408 expectResults: {
a4b75251 409 Add: resultNoOrchestrator,
f67539c2 410 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
a4b75251 411 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
f67539c2
TL
412 }
413 },
414 {
415 selectRow: fakeHosts[1], // orchestrator host
416 expectResults: {
a4b75251 417 Add: resultNoOrchestrator,
f67539c2 418 Edit: resultNoOrchestrator,
a4b75251 419 Remove: resultNoOrchestrator
f67539c2
TL
420 }
421 }
422 ];
423 await testTableActions(false, [], tests);
f91f0fd5
TL
424 });
425
f67539c2
TL
426 it('should have correct states when Orchestrator features are missing', async () => {
427 const resultMissingFeatures = {
428 disabled: true,
429 disableDesc: orchService.disableMessages.missingFeature
430 };
431 const tests = [
432 {
433 expectResults: {
a4b75251 434 Add: resultMissingFeatures,
f67539c2 435 Edit: { disabled: true, disableDesc: '' },
a4b75251 436 Remove: { disabled: true, disableDesc: '' }
f67539c2
TL
437 }
438 },
439 {
440 selectRow: fakeHosts[0], // non-orchestrator host
441 expectResults: {
a4b75251 442 Add: resultMissingFeatures,
f67539c2 443 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
a4b75251 444 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
f67539c2
TL
445 }
446 },
447 {
448 selectRow: fakeHosts[1], // orchestrator host
449 expectResults: {
a4b75251 450 Add: resultMissingFeatures,
f67539c2 451 Edit: resultMissingFeatures,
a4b75251 452 Remove: resultMissingFeatures
f67539c2 453 }
f91f0fd5 454 }
f67539c2
TL
455 ];
456 await testTableActions(true, [], tests);
f6b5b4d7
TL
457 });
458 });
11fdf7f2 459});