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