1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 import { RouterTestingModule } from '@angular/router/testing';
6 import { ToastrModule } from 'ngx-toastr';
7 import { of } from 'rxjs';
9 import { CephModule } from '~/app/ceph/ceph.module';
10 import { CephSharedModule } from '~/app/ceph/shared/ceph-shared.module';
11 import { CoreModule } from '~/app/core/core.module';
12 import { HostService } from '~/app/shared/api/host.service';
13 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
14 import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
15 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
16 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
17 import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum';
18 import { Permissions } from '~/app/shared/models/permissions';
19 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
20 import { SharedModule } from '~/app/shared/shared.module';
25 } from '~/testing/unit-test-helper';
26 import { HostsComponent } from './hosts.component';
28 class MockShowForceMaintenanceModal {
30 showModalDialog(msg: string) {
32 msg.includes('WARNING') &&
33 !msg.includes('It is NOT safe to stop') &&
34 !msg.includes('ALERT') &&
35 !msg.includes('unsafe to stop')
37 this.showModal = true;
42 describe('HostsComponent', () => {
43 let component: HostsComponent;
44 let fixture: ComponentFixture<HostsComponent>;
45 let hostListSpy: jasmine.Spy;
46 let orchService: OrchestratorService;
47 let showForceMaintenanceModal: MockShowForceMaintenanceModal;
49 const fakeAuthStorageService = {
50 getPermissions: () => {
51 return new Permissions({ hosts: ['read', 'update', 'create', 'delete'] });
57 BrowserAnimationsModule,
60 HttpClientTestingModule,
62 ToastrModule.forRoot(),
67 { provide: AuthStorageService, useValue: fakeAuthStorageService },
73 showForceMaintenanceModal = new MockShowForceMaintenanceModal();
74 fixture = TestBed.createComponent(HostsComponent);
75 component = fixture.componentInstance;
76 hostListSpy = spyOn(TestBed.inject(HostService), 'list');
77 orchService = TestBed.inject(OrchestratorService);
80 it('should create', () => {
81 expect(component).toBeTruthy();
84 it('should render hosts list even with not permission mapped services', () => {
85 const hostname = 'ceph.dev';
98 type: 'notPermissionMappedService',
103 labels: ['foo', 'bar']
107 OrchestratorHelper.mockStatus(false);
108 hostListSpy.and.callFake(() => of(payload));
109 fixture.detectChanges();
111 component.getHosts(new CdTableFetchDataContext(() => undefined));
112 fixture.detectChanges();
114 const spans = fixture.debugElement.nativeElement.querySelectorAll(
115 '.datatable-body-cell-label span'
117 expect(spans[0].textContent).toBe(hostname);
120 it('should show the exact count of the repeating daemons', () => {
121 const hostname = 'ceph.dev';
151 labels: ['foo', 'bar']
155 OrchestratorHelper.mockStatus(false);
156 hostListSpy.and.callFake(() => of(payload));
157 fixture.detectChanges();
159 component.getHosts(new CdTableFetchDataContext(() => undefined));
160 fixture.detectChanges();
162 const spans = fixture.debugElement.nativeElement.querySelectorAll(
163 '.datatable-body-cell-label span span.badge.badge-background-primary'
165 expect(spans[0].textContent).toContain('mgr: 2');
166 expect(spans[1].textContent).toContain('osd: 3');
167 expect(spans[2].textContent).toContain('rgw: 1');
170 it('should test if host facts are tranformed correctly if orch available', () => {
171 const features = [OrchestratorFeature.HOST_FACTS];
174 hostname: 'host_test',
183 memory_total_kb: 1024,
185 hdd_capacity_bytes: 1024,
187 flash_capacity_bytes: 1024,
191 OrchestratorHelper.mockStatus(true, features);
192 hostListSpy.and.callFake(() => of(payload));
193 fixture.detectChanges();
195 component.getHosts(new CdTableFetchDataContext(() => undefined));
196 expect(hostListSpy).toHaveBeenCalled();
197 expect(component.hosts[0]['cpu_count']).toEqual(2);
198 expect(component.hosts[0]['memory_total_bytes']).toEqual(1048576);
199 expect(component.hosts[0]['raw_capacity']).toEqual(2048);
200 expect(component.hosts[0]['hdd_count']).toEqual(4);
201 expect(component.hosts[0]['flash_count']).toEqual(4);
202 expect(component.hosts[0]['cpu_cores']).toEqual(1);
203 expect(component.hosts[0]['nic_count']).toEqual(1);
206 it('should test if host facts are unavailable if no orch available', () => {
209 hostname: 'host_test',
218 OrchestratorHelper.mockStatus(false);
219 hostListSpy.and.callFake(() => of(payload));
220 fixture.detectChanges();
222 component.getHosts(new CdTableFetchDataContext(() => undefined));
223 fixture.detectChanges();
225 const spans = fixture.debugElement.nativeElement.querySelectorAll(
226 '.datatable-body-cell-label span'
228 expect(spans[7].textContent).toBe('N/A');
231 it('should test if host facts are unavailable if get_fatcs orch feature is not available', () => {
234 hostname: 'host_test',
243 OrchestratorHelper.mockStatus(true);
244 hostListSpy.and.callFake(() => of(payload));
245 fixture.detectChanges();
247 component.getHosts(new CdTableFetchDataContext(() => undefined));
248 fixture.detectChanges();
250 const spans = fixture.debugElement.nativeElement.querySelectorAll(
251 '.datatable-body-cell-label span'
253 expect(spans[7].textContent).toBe('N/A');
256 it('should test if memory/raw capacity columns shows N/A if facts are available but in fetching state', () => {
257 const features = [OrchestratorFeature.HOST_FACTS];
258 let hostPayload: any[];
261 hostname: 'host_test',
270 memory_total_kb: undefined,
272 hdd_capacity_bytes: undefined,
274 flash_capacity_bytes: undefined,
278 OrchestratorHelper.mockStatus(true, features);
279 hostListSpy.and.callFake(() => of(hostPayload));
280 fixture.detectChanges();
282 component.getHosts(new CdTableFetchDataContext(() => undefined));
283 expect(component.hosts[0]['memory_total_bytes']).toEqual('N/A');
284 expect(component.hosts[0]['raw_capacity']).toEqual('N/A');
287 it('should show force maintenance modal when it is safe to stop host', () => {
288 const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service.
289 Service will not be operational with no daemons left. At
290 least 1 daemon must be running to guarantee service.`;
291 showForceMaintenanceModal.showModalDialog(errorMsg);
292 expect(showForceMaintenanceModal.showModal).toBeTruthy();
295 it('should not show force maintenance modal when error is an ALERT', () => {
296 const errorMsg = `ALERT: Cannot stop active Mgr daemon, Please switch active Mgrs
297 with 'ceph mgr fail ceph-node-00'`;
298 showForceMaintenanceModal.showModalDialog(errorMsg);
299 expect(showForceMaintenanceModal.showModal).toBeFalsy();
302 it('should not show force maintenance modal when it is not safe to stop host', () => {
303 const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service.
304 Service will not be operational with no daemons left. At
305 least 1 daemon must be running to guarantee service.
306 It is NOT safe to stop ['mon.ceph-node-00']: not enough
307 monitors would be available (ceph-node-02) after stopping mons`;
308 showForceMaintenanceModal.showModalDialog(errorMsg);
309 expect(showForceMaintenanceModal.showModal).toBeFalsy();
312 it('should not show force maintenance modal when it is unsafe to stop host', () => {
313 const errorMsg = 'unsafe to stop osd.0 because of some unknown reason';
314 showForceMaintenanceModal.showModalDialog(errorMsg);
315 expect(showForceMaintenanceModal.showModal).toBeFalsy();
318 describe('table actions', () => {
319 const fakeHosts = require('./fixtures/host_list_response.json');
322 hostListSpy.and.callFake(() => of(fakeHosts));
325 const testTableActions = async (
327 features: OrchestratorFeature[],
328 tests: { selectRow?: number; expectResults: any }[]
330 OrchestratorHelper.mockStatus(orch, features);
331 fixture.detectChanges();
332 await fixture.whenStable();
334 for (const test of tests) {
335 if (test.selectRow) {
336 component.selection = new CdTableSelection();
337 component.selection.selected = [test.selectRow];
339 await TableActionHelper.verifyTableActions(
341 component.tableActions,
347 it('should have correct states when Orchestrator is enabled', async () => {
351 Add: { disabled: false, disableDesc: '' },
352 Edit: { disabled: true, disableDesc: '' },
353 Remove: { disabled: true, disableDesc: '' }
357 selectRow: fakeHosts[0], // non-orchestrator host
359 Add: { disabled: false, disableDesc: '' },
360 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
361 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
365 selectRow: fakeHosts[1], // orchestrator host
367 Add: { disabled: false, disableDesc: '' },
368 Edit: { disabled: false, disableDesc: '' },
369 Remove: { disabled: false, disableDesc: '' }
375 OrchestratorFeature.HOST_ADD,
376 OrchestratorFeature.HOST_LABEL_ADD,
377 OrchestratorFeature.HOST_REMOVE,
378 OrchestratorFeature.HOST_LABEL_REMOVE,
379 OrchestratorFeature.HOST_DRAIN
381 await testTableActions(true, features, tests);
384 it('should have correct states when Orchestrator is disabled', async () => {
385 const resultNoOrchestrator = {
387 disableDesc: orchService.disableMessages.noOrchestrator
392 Add: resultNoOrchestrator,
393 Edit: { disabled: true, disableDesc: '' },
394 Remove: { disabled: true, disableDesc: '' }
398 selectRow: fakeHosts[0], // non-orchestrator host
400 Add: resultNoOrchestrator,
401 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
402 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
406 selectRow: fakeHosts[1], // orchestrator host
408 Add: resultNoOrchestrator,
409 Edit: resultNoOrchestrator,
410 Remove: resultNoOrchestrator
414 await testTableActions(false, [], tests);
417 it('should have correct states when Orchestrator features are missing', async () => {
418 const resultMissingFeatures = {
420 disableDesc: orchService.disableMessages.missingFeature
425 Add: resultMissingFeatures,
426 Edit: { disabled: true, disableDesc: '' },
427 Remove: { disabled: true, disableDesc: '' }
431 selectRow: fakeHosts[0], // non-orchestrator host
433 Add: resultMissingFeatures,
434 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
435 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
439 selectRow: fakeHosts[1], // orchestrator host
441 Add: resultMissingFeatures,
442 Edit: resultMissingFeatures,
443 Remove: resultMissingFeatures
447 await testTableActions(true, [], tests);