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';
139 labels: ['foo', 'bar']
143 OrchestratorHelper.mockStatus(false);
144 hostListSpy.and.callFake(() => of(payload));
145 fixture.detectChanges();
147 component.getHosts(new CdTableFetchDataContext(() => undefined));
148 fixture.detectChanges();
150 const spans = fixture.debugElement.nativeElement.querySelectorAll(
151 '.datatable-body-cell-label span span.badge.badge-background-primary'
153 expect(spans[0].textContent).toContain('mgr: 2');
154 expect(spans[1].textContent).toContain('osd: 3');
155 expect(spans[2].textContent).toContain('rgw: 1');
158 it('should test if host facts are tranformed correctly if orch available', () => {
159 const features = [OrchestratorFeature.HOST_FACTS];
162 hostname: 'host_test',
171 memory_total_kb: 1024,
173 hdd_capacity_bytes: 1024,
175 flash_capacity_bytes: 1024,
179 OrchestratorHelper.mockStatus(true, features);
180 hostListSpy.and.callFake(() => of(payload));
181 fixture.detectChanges();
183 component.getHosts(new CdTableFetchDataContext(() => undefined));
184 expect(hostListSpy).toHaveBeenCalled();
185 expect(component.hosts[0]['cpu_count']).toEqual(2);
186 expect(component.hosts[0]['memory_total_bytes']).toEqual(1048576);
187 expect(component.hosts[0]['raw_capacity']).toEqual(2048);
188 expect(component.hosts[0]['hdd_count']).toEqual(4);
189 expect(component.hosts[0]['flash_count']).toEqual(4);
190 expect(component.hosts[0]['cpu_cores']).toEqual(1);
191 expect(component.hosts[0]['nic_count']).toEqual(1);
194 it('should test if host facts are unavailable if no orch available', () => {
197 hostname: 'host_test',
206 OrchestratorHelper.mockStatus(false);
207 hostListSpy.and.callFake(() => of(payload));
208 fixture.detectChanges();
210 component.getHosts(new CdTableFetchDataContext(() => undefined));
211 fixture.detectChanges();
213 const spans = fixture.debugElement.nativeElement.querySelectorAll(
214 '.datatable-body-cell-label span'
216 expect(spans[7].textContent).toBe('N/A');
219 it('should test if host facts are unavailable if get_fatcs orch feature is not available', () => {
222 hostname: 'host_test',
231 OrchestratorHelper.mockStatus(true);
232 hostListSpy.and.callFake(() => of(payload));
233 fixture.detectChanges();
235 component.getHosts(new CdTableFetchDataContext(() => undefined));
236 fixture.detectChanges();
238 const spans = fixture.debugElement.nativeElement.querySelectorAll(
239 '.datatable-body-cell-label span'
241 expect(spans[7].textContent).toBe('N/A');
244 it('should test if memory/raw capacity columns shows N/A if facts are available but in fetching state', () => {
245 const features = [OrchestratorFeature.HOST_FACTS];
246 let hostPayload: any[];
249 hostname: 'host_test',
258 memory_total_kb: undefined,
260 hdd_capacity_bytes: undefined,
262 flash_capacity_bytes: undefined,
266 OrchestratorHelper.mockStatus(true, features);
267 hostListSpy.and.callFake(() => of(hostPayload));
268 fixture.detectChanges();
270 component.getHosts(new CdTableFetchDataContext(() => undefined));
271 expect(component.hosts[0]['memory_total_bytes']).toEqual('N/A');
272 expect(component.hosts[0]['raw_capacity']).toEqual('N/A');
275 it('should show force maintenance modal when it is safe to stop host', () => {
276 const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service.
277 Service will not be operational with no daemons left. At
278 least 1 daemon must be running to guarantee service.`;
279 showForceMaintenanceModal.showModalDialog(errorMsg);
280 expect(showForceMaintenanceModal.showModal).toBeTruthy();
283 it('should not show force maintenance modal when error is an ALERT', () => {
284 const errorMsg = `ALERT: Cannot stop active Mgr daemon, Please switch active Mgrs
285 with 'ceph mgr fail ceph-node-00'`;
286 showForceMaintenanceModal.showModalDialog(errorMsg);
287 expect(showForceMaintenanceModal.showModal).toBeFalsy();
290 it('should not show force maintenance modal when it is not 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 It is NOT safe to stop ['mon.ceph-node-00']: not enough
295 monitors would be available (ceph-node-02) after stopping mons`;
296 showForceMaintenanceModal.showModalDialog(errorMsg);
297 expect(showForceMaintenanceModal.showModal).toBeFalsy();
300 it('should not show force maintenance modal when it is unsafe to stop host', () => {
301 const errorMsg = 'unsafe to stop osd.0 because of some unknown reason';
302 showForceMaintenanceModal.showModalDialog(errorMsg);
303 expect(showForceMaintenanceModal.showModal).toBeFalsy();
306 describe('table actions', () => {
307 const fakeHosts = require('./fixtures/host_list_response.json');
310 hostListSpy.and.callFake(() => of(fakeHosts));
313 const testTableActions = async (
315 features: OrchestratorFeature[],
316 tests: { selectRow?: number; expectResults: any }[]
318 OrchestratorHelper.mockStatus(orch, features);
319 fixture.detectChanges();
320 await fixture.whenStable();
322 for (const test of tests) {
323 if (test.selectRow) {
324 component.selection = new CdTableSelection();
325 component.selection.selected = [test.selectRow];
327 await TableActionHelper.verifyTableActions(
329 component.tableActions,
335 it('should have correct states when Orchestrator is enabled', async () => {
339 Add: { disabled: false, disableDesc: '' },
340 Edit: { disabled: true, disableDesc: '' },
341 Remove: { disabled: true, disableDesc: '' }
345 selectRow: fakeHosts[0], // non-orchestrator host
347 Add: { disabled: false, disableDesc: '' },
348 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
349 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
353 selectRow: fakeHosts[1], // orchestrator host
355 Add: { disabled: false, disableDesc: '' },
356 Edit: { disabled: false, disableDesc: '' },
357 Remove: { disabled: false, disableDesc: '' }
363 OrchestratorFeature.HOST_ADD,
364 OrchestratorFeature.HOST_LABEL_ADD,
365 OrchestratorFeature.HOST_REMOVE,
366 OrchestratorFeature.HOST_LABEL_REMOVE,
367 OrchestratorFeature.HOST_DRAIN
369 await testTableActions(true, features, tests);
372 it('should have correct states when Orchestrator is disabled', async () => {
373 const resultNoOrchestrator = {
375 disableDesc: orchService.disableMessages.noOrchestrator
380 Add: resultNoOrchestrator,
381 Edit: { disabled: true, disableDesc: '' },
382 Remove: { disabled: true, disableDesc: '' }
386 selectRow: fakeHosts[0], // non-orchestrator host
388 Add: resultNoOrchestrator,
389 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
390 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
394 selectRow: fakeHosts[1], // orchestrator host
396 Add: resultNoOrchestrator,
397 Edit: resultNoOrchestrator,
398 Remove: resultNoOrchestrator
402 await testTableActions(false, [], tests);
405 it('should have correct states when Orchestrator features are missing', async () => {
406 const resultMissingFeatures = {
408 disableDesc: orchService.disableMessages.missingFeature
413 Add: resultMissingFeatures,
414 Edit: { disabled: true, disableDesc: '' },
415 Remove: { disabled: true, disableDesc: '' }
419 selectRow: fakeHosts[0], // non-orchestrator host
421 Add: resultMissingFeatures,
422 Edit: { disabled: true, disableDesc: component.messages.nonOrchHost },
423 Remove: { disabled: true, disableDesc: component.messages.nonOrchHost }
427 selectRow: fakeHosts[1], // orchestrator host
429 Add: resultMissingFeatures,
430 Edit: resultMissingFeatures,
431 Remove: resultMissingFeatures
435 await testTableActions(true, [], tests);