]>
Commit | Line | Data |
---|---|---|
aee94f69 | 1 | import { HttpHeaders } from '@angular/common/http'; |
11fdf7f2 | 2 | import { HttpClientTestingModule } from '@angular/common/http/testing'; |
f67539c2 | 3 | import { ComponentFixture, TestBed } from '@angular/core/testing'; |
e306af50 | 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; |
11fdf7f2 TL |
5 | import { RouterTestingModule } from '@angular/router/testing'; |
6 | ||
9f95a23c TL |
7 | import { ToastrModule } from 'ngx-toastr'; |
8 | import { of } from 'rxjs'; | |
11fdf7f2 | 9 | |
f67539c2 TL |
10 | import { CephModule } from '~/app/ceph/ceph.module'; |
11 | import { CephSharedModule } from '~/app/ceph/shared/ceph-shared.module'; | |
12 | import { CoreModule } from '~/app/core/core.module'; | |
13 | import { HostService } from '~/app/shared/api/host.service'; | |
14 | import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; | |
15 | import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component'; | |
a4b75251 | 16 | import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context'; |
f67539c2 TL |
17 | import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; |
18 | import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum'; | |
19 | import { Permissions } from '~/app/shared/models/permissions'; | |
20 | import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; | |
21 | import { SharedModule } from '~/app/shared/shared.module'; | |
22 | import { | |
23 | configureTestBed, | |
24 | OrchestratorHelper, | |
25 | TableActionHelper | |
26 | } from '~/testing/unit-test-helper'; | |
11fdf7f2 TL |
27 | import { HostsComponent } from './hosts.component'; |
28 | ||
b3b6e05e TL |
29 | class 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 |
43 | describe('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 | }); |