diff options
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts')
-rw-r--r-- | src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts new file mode 100644 index 000000000..5fce54fbb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts @@ -0,0 +1,419 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { ToastrModule } from 'ngx-toastr'; +import { of } from 'rxjs'; + +import { CephModule } from '~/app/ceph/ceph.module'; +import { CephSharedModule } from '~/app/ceph/shared/ceph-shared.module'; +import { CoreModule } from '~/app/core/core.module'; +import { HostService } from '~/app/shared/api/host.service'; +import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; +import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component'; +import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context'; +import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; +import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum'; +import { Permissions } from '~/app/shared/models/permissions'; +import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; +import { SharedModule } from '~/app/shared/shared.module'; +import { + configureTestBed, + OrchestratorHelper, + TableActionHelper +} from '~/testing/unit-test-helper'; +import { HostsComponent } from './hosts.component'; + +class MockShowForceMaintenanceModal { + showModal = false; + showModalDialog(msg: string) { + if ( + msg.includes('WARNING') && + !msg.includes('It is NOT safe to stop') && + !msg.includes('ALERT') && + !msg.includes('unsafe to stop') + ) { + this.showModal = true; + } + } +} + +describe('HostsComponent', () => { + let component: HostsComponent; + let fixture: ComponentFixture<HostsComponent>; + let hostListSpy: jasmine.Spy; + let orchService: OrchestratorService; + let showForceMaintenanceModal: MockShowForceMaintenanceModal; + + const fakeAuthStorageService = { + getPermissions: () => { + return new Permissions({ hosts: ['read', 'update', 'create', 'delete'] }); + } + }; + + configureTestBed({ + imports: [ + BrowserAnimationsModule, + CephSharedModule, + SharedModule, + HttpClientTestingModule, + RouterTestingModule, + ToastrModule.forRoot(), + CephModule, + CoreModule + ], + providers: [ + { provide: AuthStorageService, useValue: fakeAuthStorageService }, + TableActionsComponent + ] + }); + + beforeEach(() => { + showForceMaintenanceModal = new MockShowForceMaintenanceModal(); + fixture = TestBed.createComponent(HostsComponent); + component = fixture.componentInstance; + hostListSpy = spyOn(TestBed.inject(HostService), 'list'); + orchService = TestBed.inject(OrchestratorService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render hosts list even with not permission mapped services', () => { + const hostname = 'ceph.dev'; + const payload = [ + { + services: [ + { + type: 'osd', + id: '0' + }, + { + type: 'rgw', + id: 'rgw' + }, + { + type: 'notPermissionMappedService', + id: '1' + } + ], + hostname: hostname, + labels: ['foo', 'bar'] + } + ]; + + OrchestratorHelper.mockStatus(false); + hostListSpy.and.callFake(() => of(payload)); + fixture.detectChanges(); + + component.getHosts(new CdTableFetchDataContext(() => undefined)); + fixture.detectChanges(); + + const spans = fixture.debugElement.nativeElement.querySelectorAll( + '.datatable-body-cell-label span' + ); + expect(spans[0].textContent).toBe(hostname); + }); + + it('should show the exact count of the repeating daemons', () => { + const hostname = 'ceph.dev'; + const payload = [ + { + services: [ + { + type: 'mgr', + id: 'x' + }, + { + type: 'mgr', + id: 'y' + }, + { + type: 'osd', + id: '0' + }, + { + type: 'osd', + id: '1' + }, + { + type: 'osd', + id: '2' + }, + { + type: 'rgw', + id: 'rgw' + } + ], + hostname: hostname, + labels: ['foo', 'bar'] + } + ]; + + OrchestratorHelper.mockStatus(false); + hostListSpy.and.callFake(() => of(payload)); + fixture.detectChanges(); + + component.getHosts(new CdTableFetchDataContext(() => undefined)); + fixture.detectChanges(); + + const spans = fixture.debugElement.nativeElement.querySelectorAll( + '.datatable-body-cell-label span span.badge.badge-background-primary' + ); + expect(spans[0].textContent).toContain('mgr: 2'); + expect(spans[1].textContent).toContain('osd: 3'); + expect(spans[2].textContent).toContain('rgw: 1'); + }); + + it('should test if host facts are tranformed correctly if orch available', () => { + const features = [OrchestratorFeature.HOST_FACTS]; + const payload = [ + { + hostname: 'host_test', + services: [ + { + type: 'osd', + id: '0' + } + ], + cpu_count: 2, + cpu_cores: 1, + memory_total_kb: 1024, + hdd_count: 4, + hdd_capacity_bytes: 1024, + flash_count: 4, + flash_capacity_bytes: 1024, + nic_count: 1 + } + ]; + OrchestratorHelper.mockStatus(true, features); + hostListSpy.and.callFake(() => of(payload)); + fixture.detectChanges(); + + component.getHosts(new CdTableFetchDataContext(() => undefined)); + expect(hostListSpy).toHaveBeenCalled(); + expect(component.hosts[0]['cpu_count']).toEqual(2); + expect(component.hosts[0]['memory_total_bytes']).toEqual(1048576); + expect(component.hosts[0]['raw_capacity']).toEqual(2048); + expect(component.hosts[0]['hdd_count']).toEqual(4); + expect(component.hosts[0]['flash_count']).toEqual(4); + expect(component.hosts[0]['cpu_cores']).toEqual(1); + expect(component.hosts[0]['nic_count']).toEqual(1); + }); + + it('should test if host facts are unavailable if no orch available', () => { + const payload = [ + { + hostname: 'host_test', + services: [ + { + type: 'osd', + id: '0' + } + ] + } + ]; + OrchestratorHelper.mockStatus(false); + hostListSpy.and.callFake(() => of(payload)); + fixture.detectChanges(); + + component.getHosts(new CdTableFetchDataContext(() => undefined)); + fixture.detectChanges(); + + const spans = fixture.debugElement.nativeElement.querySelectorAll( + '.datatable-body-cell-label span' + ); + expect(spans[7].textContent).toBe('N/A'); + }); + + it('should test if host facts are unavailable if get_fatcs orch feature is not available', () => { + const payload = [ + { + hostname: 'host_test', + services: [ + { + type: 'osd', + id: '0' + } + ] + } + ]; + OrchestratorHelper.mockStatus(true); + hostListSpy.and.callFake(() => of(payload)); + fixture.detectChanges(); + + component.getHosts(new CdTableFetchDataContext(() => undefined)); + fixture.detectChanges(); + + const spans = fixture.debugElement.nativeElement.querySelectorAll( + '.datatable-body-cell-label span' + ); + expect(spans[7].textContent).toBe('N/A'); + }); + + it('should show force maintenance modal when it is safe to stop host', () => { + const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service. + Service will not be operational with no daemons left. At + least 1 daemon must be running to guarantee service.`; + showForceMaintenanceModal.showModalDialog(errorMsg); + expect(showForceMaintenanceModal.showModal).toBeTruthy(); + }); + + it('should not show force maintenance modal when error is an ALERT', () => { + const errorMsg = `ALERT: Cannot stop active Mgr daemon, Please switch active Mgrs + with 'ceph mgr fail ceph-node-00'`; + showForceMaintenanceModal.showModalDialog(errorMsg); + expect(showForceMaintenanceModal.showModal).toBeFalsy(); + }); + + it('should not show force maintenance modal when it is not safe to stop host', () => { + const errorMsg = `WARNING: Stopping 1 out of 1 daemons in Grafana service. + Service will not be operational with no daemons left. At + least 1 daemon must be running to guarantee service. + It is NOT safe to stop ['mon.ceph-node-00']: not enough + monitors would be available (ceph-node-02) after stopping mons`; + showForceMaintenanceModal.showModalDialog(errorMsg); + expect(showForceMaintenanceModal.showModal).toBeFalsy(); + }); + + it('should not show force maintenance modal when it is unsafe to stop host', () => { + const errorMsg = 'unsafe to stop osd.0 because of some unknown reason'; + showForceMaintenanceModal.showModalDialog(errorMsg); + expect(showForceMaintenanceModal.showModal).toBeFalsy(); + }); + + describe('table actions', () => { + const fakeHosts = require('./fixtures/host_list_response.json'); + + beforeEach(() => { + hostListSpy.and.callFake(() => of(fakeHosts)); + }); + + const testTableActions = async ( + orch: boolean, + features: OrchestratorFeature[], + tests: { selectRow?: number; expectResults: any }[] + ) => { + OrchestratorHelper.mockStatus(orch, features); + fixture.detectChanges(); + await fixture.whenStable(); + + for (const test of tests) { + if (test.selectRow) { + component.selection = new CdTableSelection(); + component.selection.selected = [test.selectRow]; + } + await TableActionHelper.verifyTableActions( + fixture, + component.tableActions, + test.expectResults + ); + } + }; + + it('should have correct states when Orchestrator is enabled', async () => { + const tests = [ + { + expectResults: { + Add: { disabled: false, disableDesc: '' }, + Edit: { disabled: true, disableDesc: '' }, + Remove: { disabled: true, disableDesc: '' } + } + }, + { + selectRow: fakeHosts[0], // non-orchestrator host + expectResults: { + Add: { disabled: false, disableDesc: '' }, + Edit: { disabled: true, disableDesc: component.messages.nonOrchHost }, + Remove: { disabled: true, disableDesc: component.messages.nonOrchHost } + } + }, + { + selectRow: fakeHosts[1], // orchestrator host + expectResults: { + Add: { disabled: false, disableDesc: '' }, + Edit: { disabled: false, disableDesc: '' }, + Remove: { disabled: false, disableDesc: '' } + } + } + ]; + + const features = [ + OrchestratorFeature.HOST_ADD, + OrchestratorFeature.HOST_LABEL_ADD, + OrchestratorFeature.HOST_REMOVE, + OrchestratorFeature.HOST_LABEL_REMOVE, + OrchestratorFeature.HOST_DRAIN + ]; + await testTableActions(true, features, tests); + }); + + it('should have correct states when Orchestrator is disabled', async () => { + const resultNoOrchestrator = { + disabled: true, + disableDesc: orchService.disableMessages.noOrchestrator + }; + const tests = [ + { + expectResults: { + Add: resultNoOrchestrator, + Edit: { disabled: true, disableDesc: '' }, + Remove: { disabled: true, disableDesc: '' } + } + }, + { + selectRow: fakeHosts[0], // non-orchestrator host + expectResults: { + Add: resultNoOrchestrator, + Edit: { disabled: true, disableDesc: component.messages.nonOrchHost }, + Remove: { disabled: true, disableDesc: component.messages.nonOrchHost } + } + }, + { + selectRow: fakeHosts[1], // orchestrator host + expectResults: { + Add: resultNoOrchestrator, + Edit: resultNoOrchestrator, + Remove: resultNoOrchestrator + } + } + ]; + await testTableActions(false, [], tests); + }); + + it('should have correct states when Orchestrator features are missing', async () => { + const resultMissingFeatures = { + disabled: true, + disableDesc: orchService.disableMessages.missingFeature + }; + const tests = [ + { + expectResults: { + Add: resultMissingFeatures, + Edit: { disabled: true, disableDesc: '' }, + Remove: { disabled: true, disableDesc: '' } + } + }, + { + selectRow: fakeHosts[0], // non-orchestrator host + expectResults: { + Add: resultMissingFeatures, + Edit: { disabled: true, disableDesc: component.messages.nonOrchHost }, + Remove: { disabled: true, disableDesc: component.messages.nonOrchHost } + } + }, + { + selectRow: fakeHosts[1], // orchestrator host + expectResults: { + Add: resultMissingFeatures, + Edit: resultMissingFeatures, + Remove: resultMissingFeatures + } + } + ]; + await testTableActions(true, [], tests); + }); + }); +}); |