]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts
bump version to 18.2.4-pve3
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / pool / pool-list / pool-list.component.spec.ts
CommitLineData
11fdf7f2
TL
1import { HttpClientTestingModule } from '@angular/common/http/testing';
2import { ComponentFixture, TestBed } from '@angular/core/testing';
e306af50 3import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
11fdf7f2
TL
4import { RouterTestingModule } from '@angular/router/testing';
5
f67539c2
TL
6import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
7import _ from 'lodash';
494da23a 8import { ToastrModule } from 'ngx-toastr';
11fdf7f2
TL
9import { of } from 'rxjs';
10
f67539c2
TL
11import { RbdConfigurationListComponent } from '~/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component';
12import { PgCategoryService } from '~/app/ceph/shared/pg-category.service';
13import { ConfigurationService } from '~/app/shared/api/configuration.service';
14import { ErasureCodeProfileService } from '~/app/shared/api/erasure-code-profile.service';
15import { PoolService } from '~/app/shared/api/pool.service';
16import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
17import { ErasureCodeProfile } from '~/app/shared/models/erasure-code-profile';
18import { ExecutingTask } from '~/app/shared/models/executing-task';
19import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
20import { ModalService } from '~/app/shared/services/modal.service';
21import { SummaryService } from '~/app/shared/services/summary.service';
22import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
23import { SharedModule } from '~/app/shared/shared.module';
24import { configureTestBed, expectItemTasks, Mocks } from '~/testing/unit-test-helper';
11fdf7f2
TL
25import { Pool } from '../pool';
26import { PoolDetailsComponent } from '../pool-details/pool-details.component';
27import { PoolListComponent } from './pool-list.component';
28
29describe('PoolListComponent', () => {
30 let component: PoolListComponent;
31 let fixture: ComponentFixture<PoolListComponent>;
32 let poolService: PoolService;
f67539c2 33 let getECPList: jasmine.Spy;
11fdf7f2 34
f67539c2
TL
35 const getPoolList = (): Pool[] => {
36 return [Mocks.getPool('a', 0), Mocks.getPool('b', 1), Mocks.getPool('c', 2)];
11fdf7f2 37 };
f67539c2
TL
38 const getECPProfiles = (): ErasureCodeProfile[] => {
39 const ecpProfile = new ErasureCodeProfile();
40 ecpProfile.name = 'default';
41 ecpProfile.k = 2;
42 ecpProfile.m = 1;
11fdf7f2 43
f67539c2 44 return [ecpProfile];
11fdf7f2
TL
45 };
46
47 configureTestBed({
48 declarations: [PoolListComponent, PoolDetailsComponent, RbdConfigurationListComponent],
49 imports: [
e306af50 50 BrowserAnimationsModule,
11fdf7f2 51 SharedModule,
494da23a 52 ToastrModule.forRoot(),
11fdf7f2 53 RouterTestingModule,
f67539c2 54 NgbNavModule,
11fdf7f2
TL
55 HttpClientTestingModule
56 ],
f67539c2 57 providers: [PgCategoryService]
11fdf7f2
TL
58 });
59
60 beforeEach(() => {
61 fixture = TestBed.createComponent(PoolListComponent);
62 component = fixture.componentInstance;
63 component.permissions.pool.read = true;
f67539c2 64 poolService = TestBed.inject(PoolService);
eafe8130 65 spyOn(poolService, 'getList').and.callFake(() => of(getPoolList()));
f67539c2
TL
66 getECPList = spyOn(TestBed.inject(ErasureCodeProfileService), 'list');
67 getECPList.and.returnValue(of(getECPProfiles()));
11fdf7f2
TL
68 fixture.detectChanges();
69 });
70
71 it('should create', () => {
72 expect(component).toBeTruthy();
73 });
74
81eedcae 75 it('should have columns that are sortable', () => {
e306af50
TL
76 expect(
77 component.columns
78 .filter((column) => !(column.prop === undefined))
79 .every((column) => Boolean(column.prop))
80 ).toBeTruthy();
81eedcae
TL
81 });
82
494da23a 83 describe('monAllowPoolDelete', () => {
92f5a8d4 84 let configOptRead: boolean;
494da23a
TL
85 let configurationService: ConfigurationService;
86
87 beforeEach(() => {
92f5a8d4 88 configOptRead = true;
f67539c2 89 spyOn(TestBed.inject(AuthStorageService), 'getPermissions').and.callFake(() => ({
92f5a8d4
TL
90 configOpt: { read: configOptRead }
91 }));
f67539c2 92 configurationService = TestBed.inject(ConfigurationService);
494da23a
TL
93 });
94
95 it('should set value correctly if mon_allow_pool_delete flag is set to true', () => {
96 const configOption = {
97 name: 'mon_allow_pool_delete',
98 value: [
99 {
100 section: 'mon',
101 value: 'true'
102 }
103 ]
104 };
105 spyOn(configurationService, 'get').and.returnValue(of(configOption));
106 fixture = TestBed.createComponent(PoolListComponent);
107 component = fixture.componentInstance;
108 expect(component.monAllowPoolDelete).toBe(true);
109 });
110
111 it('should set value correctly if mon_allow_pool_delete flag is set to false', () => {
112 const configOption = {
113 name: 'mon_allow_pool_delete',
114 value: [
115 {
116 section: 'mon',
117 value: 'false'
118 }
119 ]
120 };
121 spyOn(configurationService, 'get').and.returnValue(of(configOption));
122 fixture = TestBed.createComponent(PoolListComponent);
123 component = fixture.componentInstance;
124 expect(component.monAllowPoolDelete).toBe(false);
125 });
126
127 it('should set value correctly if mon_allow_pool_delete flag is not set', () => {
128 const configOption = {
129 name: 'mon_allow_pool_delete'
130 };
131 spyOn(configurationService, 'get').and.returnValue(of(configOption));
132 fixture = TestBed.createComponent(PoolListComponent);
133 component = fixture.componentInstance;
134 expect(component.monAllowPoolDelete).toBe(false);
135 });
92f5a8d4
TL
136
137 it('should set value correctly w/o config-opt read privileges', () => {
138 configOptRead = false;
139 fixture = TestBed.createComponent(PoolListComponent);
140 component = fixture.componentInstance;
141 expect(component.monAllowPoolDelete).toBe(false);
142 });
494da23a
TL
143 });
144
11fdf7f2
TL
145 describe('pool deletion', () => {
146 let taskWrapper: TaskWrapperService;
f67539c2 147 let modalRef: any;
11fdf7f2 148
9f95a23c
TL
149 const setSelectedPool = (poolName: string) =>
150 (component.selection.selected = [{ pool_name: poolName }]);
11fdf7f2
TL
151
152 const callDeletion = () => {
153 component.deletePoolModal();
f67539c2
TL
154 expect(modalRef).toBeTruthy();
155 const deletion: CriticalConfirmationModalComponent = modalRef && modalRef.componentInstance;
11fdf7f2
TL
156 deletion.submitActionObservable();
157 };
158
9f95a23c 159 const testPoolDeletion = (poolName: string) => {
11fdf7f2
TL
160 setSelectedPool(poolName);
161 callDeletion();
162 expect(poolService.delete).toHaveBeenCalledWith(poolName);
163 expect(taskWrapper.wrapTaskAroundCall).toHaveBeenCalledWith({
164 task: {
165 name: 'pool/delete',
166 metadata: {
167 pool_name: poolName
168 }
169 },
170 call: undefined // because of stub
171 });
172 };
173
174 beforeEach(() => {
f67539c2
TL
175 spyOn(TestBed.inject(ModalService), 'show').and.callFake((deletionClass, initialState) => {
176 modalRef = {
177 componentInstance: Object.assign(new deletionClass(), initialState)
11fdf7f2 178 };
f67539c2 179 return modalRef;
11fdf7f2
TL
180 });
181 spyOn(poolService, 'delete').and.stub();
f67539c2 182 taskWrapper = TestBed.inject(TaskWrapperService);
11fdf7f2
TL
183 spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
184 });
185
186 it('should pool deletion with two different pools', () => {
187 testPoolDeletion('somePoolName');
188 testPoolDeletion('aDifferentPoolName');
189 });
190 });
191
192 describe('handling of executing tasks', () => {
11fdf7f2
TL
193 let summaryService: SummaryService;
194
195 const addTask = (name: string, pool: string) => {
196 const task = new ExecutingTask();
197 task.name = name;
198 task.metadata = { pool_name: pool };
199 summaryService.addRunningTask(task);
200 };
201
202 beforeEach(() => {
f67539c2 203 summaryService = TestBed.inject(SummaryService);
f6b5b4d7
TL
204 summaryService['summaryDataSource'].next({
205 executing_tasks: [],
206 finished_tasks: []
207 });
11fdf7f2
TL
208 });
209
210 it('gets all pools without executing pools', () => {
211 expect(component.pools.length).toBe(3);
212 expect(component.pools.every((pool) => !pool.executingTasks)).toBeTruthy();
213 });
214
215 it('gets a pool from a task during creation', () => {
216 addTask('pool/create', 'd');
217 expect(component.pools.length).toBe(4);
eafe8130 218 expectItemTasks(component.pools[3], 'Creating');
11fdf7f2
TL
219 });
220
221 it('gets all pools with one executing pools', () => {
222 addTask('pool/create', 'a');
223 expect(component.pools.length).toBe(3);
eafe8130 224 expectItemTasks(component.pools[0], 'Creating');
11fdf7f2
TL
225 expect(component.pools[1].cdExecuting).toBeFalsy();
226 expect(component.pools[2].cdExecuting).toBeFalsy();
227 });
228
229 it('gets all pools with multiple executing pools', () => {
230 addTask('pool/create', 'a');
231 addTask('pool/edit', 'a');
232 addTask('pool/delete', 'a');
233 addTask('pool/edit', 'b');
234 addTask('pool/delete', 'b');
235 addTask('pool/delete', 'c');
236 expect(component.pools.length).toBe(3);
eafe8130
TL
237 expectItemTasks(component.pools[0], 'Creating..., Updating..., Deleting');
238 expectItemTasks(component.pools[1], 'Updating..., Deleting');
239 expectItemTasks(component.pools[2], 'Deleting');
11fdf7f2
TL
240 });
241
242 it('gets all pools with multiple executing tasks (not only pool tasks)', () => {
243 addTask('rbd/create', 'a');
244 addTask('rbd/edit', 'a');
245 addTask('pool/delete', 'a');
246 addTask('pool/edit', 'b');
247 addTask('rbd/delete', 'b');
248 addTask('rbd/delete', 'c');
249 expect(component.pools.length).toBe(3);
eafe8130
TL
250 expectItemTasks(component.pools[0], 'Deleting');
251 expectItemTasks(component.pools[1], 'Updating');
11fdf7f2
TL
252 expect(component.pools[2].cdExecuting).toBeFalsy();
253 });
254 });
255
256 describe('getPgStatusCellClass', () => {
9f95a23c 257 const testMethod = (value: string, expected: string) =>
11fdf7f2
TL
258 expect(component.getPgStatusCellClass('', '', value)).toEqual({
259 'text-right': true,
260 [expected]: true
261 });
262
263 it('pg-clean', () => {
264 testMethod('8 active+clean', 'pg-clean');
265 });
266
267 it('pg-working', () => {
268 testMethod(' 8 active+clean+scrubbing+deep, 255 active+clean ', 'pg-working');
269 });
270
271 it('pg-warning', () => {
272 testMethod('8 active+clean+scrubbing+down', 'pg-warning');
273 testMethod('8 active+clean+scrubbing+down+nonMappedState', 'pg-warning');
274 });
275
276 it('pg-unknown', () => {
277 testMethod('8 active+clean+scrubbing+nonMappedState', 'pg-unknown');
278 testMethod('8 ', 'pg-unknown');
279 testMethod('', 'pg-unknown');
280 });
281 });
282
494da23a
TL
283 describe('custom row comparators', () => {
284 const expectCorrectComparator = (statsAttribute: string) => {
9f95a23c 285 const mockPool = (v: number) => ({ stats: { [statsAttribute]: { latest: v } } });
494da23a
TL
286 const columnDefinition = _.find(
287 component.columns,
288 (column) => column.prop === `stats.${statsAttribute}.rates`
289 );
290 expect(columnDefinition.comparator(undefined, undefined, mockPool(2), mockPool(1))).toBe(1);
291 expect(columnDefinition.comparator(undefined, undefined, mockPool(1), mockPool(2))).toBe(-1);
292 };
293
294 it('compares read bytes correctly', () => {
295 expectCorrectComparator('rd_bytes');
296 });
297
298 it('compares write bytes correctly', () => {
299 expectCorrectComparator('wr_bytes');
300 });
301 });
302
11fdf7f2 303 describe('transformPoolsData', () => {
eafe8130 304 let pool: Pool;
11fdf7f2 305
9f95a23c 306 const getPoolData = (o: object) => [
eafe8130 307 _.merge(
f67539c2 308 _.merge(Mocks.getPool('a', 0), {
81eedcae
TL
309 cdIsBinary: true,
310 pg_status: '',
311 stats: {
494da23a
TL
312 bytes_used: { latest: 0, rate: 0, rates: [] },
313 max_avail: { latest: 0, rate: 0, rates: [] },
f6b5b4d7
TL
314 avail_raw: { latest: 0, rate: 0, rates: [] },
315 percent_used: { latest: 0, rate: 0, rates: [] },
494da23a
TL
316 rd: { latest: 0, rate: 0, rates: [] },
317 rd_bytes: { latest: 0, rate: 0, rates: [] },
318 wr: { latest: 0, rate: 0, rates: [] },
319 wr_bytes: { latest: 0, rate: 0, rates: [] }
81eedcae 320 },
f67539c2
TL
321 usage: 0,
322 data_protection: 'replica: ×3'
eafe8130
TL
323 }),
324 o
325 )
326 ];
327
328 beforeEach(() => {
f67539c2 329 pool = Mocks.getPool('a', 0);
eafe8130
TL
330 });
331
f67539c2 332 it('transforms replicated pools data correctly', () => {
eafe8130
TL
333 pool = _.merge(pool, {
334 stats: {
335 bytes_used: { latest: 5, rate: 0, rates: [] },
f6b5b4d7
TL
336 avail_raw: { latest: 15, rate: 0, rates: [] },
337 percent_used: { latest: 0.25, rate: 0, rates: [] },
801d1391
TL
338 rd_bytes: {
339 latest: 6,
340 rate: 4,
341 rates: [
342 [0, 2],
343 [1, 6]
344 ]
345 }
eafe8130
TL
346 },
347 pg_status: { 'active+clean': 8, down: 2 }
348 });
349 expect(component.transformPoolsData([pool])).toEqual(
350 getPoolData({
351 pg_status: '8 active+clean, 2 down',
352 stats: {
353 bytes_used: { latest: 5, rate: 0, rates: [] },
f6b5b4d7
TL
354 avail_raw: { latest: 15, rate: 0, rates: [] },
355 percent_used: { latest: 0.25, rate: 0, rates: [] },
eafe8130
TL
356 rd_bytes: { latest: 6, rate: 4, rates: [2, 6] }
357 },
f67539c2
TL
358 usage: 0.25,
359 data_protection: 'replica: ×3'
360 })
361 );
362 });
363
364 it('transforms erasure pools data correctly', () => {
365 pool.type = 'erasure';
366 pool.erasure_code_profile = 'default';
367 component.ecProfileList = getECPProfiles();
368
369 expect(component.transformPoolsData([pool])).toEqual(
370 getPoolData({
371 type: 'erasure',
372 erasure_code_profile: 'default',
373 data_protection: 'EC: 2+1'
eafe8130
TL
374 })
375 );
376 });
377
378 it('transforms pools data correctly if stats are missing', () => {
379 expect(component.transformPoolsData([pool])).toEqual(getPoolData({}));
11fdf7f2
TL
380 });
381
382 it('transforms empty pools data correctly', () => {
eafe8130
TL
383 expect(component.transformPoolsData(undefined)).toEqual(undefined);
384 expect(component.transformPoolsData([])).toEqual([]);
385 });
386
387 it('shows not marked pools in progress if pg_num does not match pg_num_target', () => {
388 const pools = [
389 _.merge(pool, {
390 pg_num: 32,
391 pg_num_target: 16,
392 pg_placement_num: 32,
393 pg_placement_num_target: 16
394 })
395 ];
396 expect(component.transformPoolsData(pools)).toEqual(
397 getPoolData({
398 cdExecuting: 'Updating',
399 pg_num: 32,
400 pg_num_target: 16,
401 pg_placement_num: 32,
f67539c2
TL
402 pg_placement_num_target: 16,
403 data_protection: 'replica: ×3'
eafe8130
TL
404 })
405 );
406 });
407
408 it('shows marked pools in progress as defined by task', () => {
409 const pools = [
410 _.merge(pool, {
411 pg_num: 32,
412 pg_num_target: 16,
413 pg_placement_num: 32,
414 pg_placement_num_target: 16,
415 cdExecuting: 'Updating... 50%'
416 })
417 ];
418 expect(component.transformPoolsData(pools)).toEqual(
419 getPoolData({
420 cdExecuting: 'Updating... 50%',
421 pg_num: 32,
422 pg_num_target: 16,
423 pg_placement_num: 32,
f67539c2
TL
424 pg_placement_num_target: 16,
425 data_protection: 'replica: ×3'
eafe8130
TL
426 })
427 );
11fdf7f2
TL
428 });
429 });
430
431 describe('transformPgStatus', () => {
432 it('returns status groups correctly', () => {
433 const pgStatus = { 'active+clean': 8 };
434 const expected = '8 active+clean';
435
436 expect(component.transformPgStatus(pgStatus)).toEqual(expected);
437 });
438
439 it('returns separated status groups', () => {
440 const pgStatus = { 'active+clean': 8, down: 2 };
441 const expected = '8 active+clean, 2 down';
442
443 expect(component.transformPgStatus(pgStatus)).toEqual(expected);
444 });
445
446 it('returns separated statuses correctly', () => {
447 const pgStatus = { active: 8, down: 2 };
448 const expected = '8 active, 2 down';
449
450 expect(component.transformPgStatus(pgStatus)).toEqual(expected);
451 });
452
453 it('returns empty string', () => {
9f95a23c 454 const pgStatus: any = undefined;
11fdf7f2
TL
455 const expected = '';
456
457 expect(component.transformPgStatus(pgStatus)).toEqual(expected);
458 });
459 });
460
11fdf7f2 461 describe('getSelectionTiers', () => {
11fdf7f2 462 const setSelectionTiers = (tiers: number[]) => {
e306af50 463 component.expandedRow = { tiers };
11fdf7f2
TL
464 component.getSelectionTiers();
465 };
466
467 beforeEach(() => {
eafe8130 468 component.pools = getPoolList();
11fdf7f2
TL
469 });
470
471 it('should select multiple existing cache tiers', () => {
472 setSelectionTiers([0, 1, 2]);
e306af50 473 expect(component.cacheTiers).toEqual(getPoolList());
11fdf7f2
TL
474 });
475
476 it('should select correct existing cache tier', () => {
477 setSelectionTiers([0]);
f67539c2 478 expect(component.cacheTiers).toEqual([Mocks.getPool('a', 0)]);
11fdf7f2
TL
479 });
480
481 it('should not select cache tier if id is invalid', () => {
482 setSelectionTiers([-1]);
e306af50 483 expect(component.cacheTiers).toEqual([]);
11fdf7f2
TL
484 });
485
486 it('should not select cache tier if empty', () => {
487 setSelectionTiers([]);
e306af50 488 expect(component.cacheTiers).toEqual([]);
11fdf7f2
TL
489 });
490
491 it('should be able to selected one pool with multiple tiers, than with a single tier, than with no tiers', () => {
492 setSelectionTiers([0, 1, 2]);
e306af50 493 expect(component.cacheTiers).toEqual(getPoolList());
11fdf7f2 494 setSelectionTiers([0]);
f67539c2 495 expect(component.cacheTiers).toEqual([Mocks.getPool('a', 0)]);
11fdf7f2 496 setSelectionTiers([]);
e306af50 497 expect(component.cacheTiers).toEqual([]);
11fdf7f2
TL
498 });
499 });
494da23a
TL
500
501 describe('getDisableDesc', () => {
f91f0fd5
TL
502 beforeEach(() => {
503 component.selection.selected = [{ pool_name: 'foo' }];
504 });
505
494da23a
TL
506 it('should return message if mon_allow_pool_delete flag is set to false', () => {
507 component.monAllowPoolDelete = false;
508 expect(component.getDisableDesc()).toBe(
509 'Pool deletion is disabled by the mon_allow_pool_delete configuration setting.'
510 );
511 });
512
f91f0fd5 513 it('should return false if mon_allow_pool_delete flag is set to true', () => {
494da23a 514 component.monAllowPoolDelete = true;
f91f0fd5 515 expect(component.getDisableDesc()).toBeFalsy();
494da23a
TL
516 });
517 });
11fdf7f2 518});