1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { AbstractControl } from '@angular/forms';
4 import { By } from '@angular/platform-browser';
5 import { ActivatedRoute, Router, Routes } from '@angular/router';
6 import { RouterTestingModule } from '@angular/router/testing';
8 import { BsModalService } from 'ngx-bootstrap/modal';
9 import { TabsModule } from 'ngx-bootstrap/tabs';
10 import { ToastrModule } from 'ngx-toastr';
11 import { of } from 'rxjs';
18 } from '../../../../testing/unit-test-helper';
19 import { NotFoundComponent } from '../../../core/not-found/not-found.component';
20 import { ErasureCodeProfileService } from '../../../shared/api/erasure-code-profile.service';
21 import { PoolService } from '../../../shared/api/pool.service';
22 import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
23 import { SelectBadgesComponent } from '../../../shared/components/select-badges/select-badges.component';
24 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
25 import { CrushRule } from '../../../shared/models/crush-rule';
26 import { ErasureCodeProfile } from '../../../shared/models/erasure-code-profile';
27 import { Permission } from '../../../shared/models/permissions';
28 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
29 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
30 import { Pool } from '../pool';
31 import { PoolModule } from '../pool.module';
32 import { PoolFormComponent } from './pool-form.component';
34 describe('PoolFormComponent', () => {
36 let formHelper: FormHelper;
37 let fixtureHelper: FixtureHelper;
38 let component: PoolFormComponent;
39 let fixture: ComponentFixture<PoolFormComponent>;
40 let poolService: PoolService;
41 let form: CdFormGroup;
43 let ecpService: ErasureCodeProfileService;
45 const setPgNum = (pgs): AbstractControl => {
46 const control = formHelper.setValue('pgNum', pgs);
47 fixture.debugElement.query(By.css('#pgNum')).nativeElement.dispatchEvent(new Event('blur'));
51 const createCrushRule = ({
53 name = 'somePoolName',
64 const typeNumber = type === 'erasure' ? 3 : 1;
65 const rule = new CrushRule();
69 rule.ruleset = typeNumber;
70 rule.rule_name = name;
86 component.info['crush_rules_' + type].push(rule);
90 const expectValidSubmit = (
93 poolServiceMethod: 'create' | 'update'
95 spyOn(poolService, poolServiceMethod).and.stub();
96 const taskWrapper = TestBed.get(TaskWrapperService);
97 spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
99 expect(poolService[poolServiceMethod]).toHaveBeenCalledWith(pool);
100 expect(taskWrapper.wrapTaskAroundCall).toHaveBeenCalledWith({
107 call: undefined // because of stub
111 const setUpPoolComponent = () => {
112 fixture = TestBed.createComponent(PoolFormComponent);
113 fixtureHelper = new FixtureHelper(fixture);
114 component = fixture.componentInstance;
118 is_all_bluestore: true,
119 bluestore_compression_algorithm: 'snappy',
120 compression_algorithms: ['snappy'],
121 compression_modes: ['none', 'passive'],
122 crush_rules_replicated: [],
123 crush_rules_erasure: []
125 const ecp1 = new ErasureCodeProfile();
127 component.ecProfiles = [ecp1];
128 form = component.form;
129 formHelper = new FormHelper(form);
132 const routes: Routes = [{ path: '404', component: NotFoundComponent }];
135 declarations: [NotFoundComponent],
137 HttpClientTestingModule,
138 RouterTestingModule.withRoutes(routes),
139 ToastrModule.forRoot(),
140 TabsModule.forRoot(),
144 ErasureCodeProfileService,
145 SelectBadgesComponent,
146 { provide: ActivatedRoute, useValue: { params: of({ name: 'somePoolName' }) } },
152 setUpPoolComponent();
153 poolService = TestBed.get(PoolService);
154 spyOn(poolService, 'getInfo').and.callFake(() => [component.info]);
155 ecpService = TestBed.get(ErasureCodeProfileService);
156 spyOn(ecpService, 'list').and.callFake(() => [component.ecProfiles]);
157 router = TestBed.get(Router);
158 spyOn(router, 'navigate').and.stub();
161 it('should create', () => {
162 expect(component).toBeTruthy();
165 describe('redirect not allowed users', () => {
166 let poolPermissions: Permission;
167 let authStorageService: AuthStorageService;
169 const testForRedirect = (times: number) => {
170 component.authenticate();
171 expect(router.navigate).toHaveBeenCalledTimes(times);
181 authStorageService = TestBed.get(AuthStorageService);
182 spyOn(authStorageService, 'getPermissions').and.callFake(() => ({
183 pool: poolPermissions
187 it('navigates to 404 if not allowed', () => {
188 component.authenticate();
189 expect(router.navigate).toHaveBeenCalledWith(['/404']);
192 it('navigates if user is not allowed', () => {
194 poolPermissions.read = true;
196 poolPermissions.delete = true;
198 poolPermissions.update = true;
200 component.editing = true;
201 poolPermissions.update = false;
202 poolPermissions.create = true;
206 it('does not navigate users with right permissions', () => {
207 poolPermissions.read = true;
208 poolPermissions.create = true;
210 component.editing = true;
211 poolPermissions.update = true;
213 poolPermissions.create = false;
218 describe('pool form validation', () => {
220 fixture.detectChanges();
223 it('is invalid at the beginning all sub forms are valid', () => {
224 expect(form.valid).toBeFalsy();
225 ['name', 'poolType', 'pgNum'].forEach((name) => formHelper.expectError(name, 'required'));
226 ['crushRule', 'size', 'erasureProfile', 'ecOverwrites'].forEach((name) =>
227 formHelper.expectValid(name)
229 expect(component.form.get('compression').valid).toBeTruthy();
232 it('validates name', () => {
233 expect(component.editing).toBeFalsy();
234 formHelper.expectError('name', 'required');
235 formHelper.expectValidChange('name', 'some-name');
236 formHelper.expectValidChange('name', 'name/with/slash');
237 component.info.pool_names.push('someExistingPoolName');
238 formHelper.expectErrorChange('name', 'someExistingPoolName', 'uniqueName');
239 formHelper.expectErrorChange('name', 'wrong format with spaces', 'pattern');
242 it('should validate with dots in pool name', () => {
243 formHelper.expectValidChange('name', 'pool.default.bar', true);
246 it('validates poolType', () => {
247 formHelper.expectError('poolType', 'required');
248 formHelper.expectValidChange('poolType', 'erasure');
249 formHelper.expectValidChange('poolType', 'replicated');
252 it('validates that pgNum is required creation mode', () => {
253 formHelper.expectError(form.get('pgNum'), 'required');
256 it('validates pgNum in edit mode', () => {
257 component.data.pool = new Pool('test');
258 component.data.pool.pg_num = 16;
259 component.editing = true;
260 component.ngOnInit(); // Switches form into edit mode
261 formHelper.setValue('poolType', 'erasure');
262 fixture.detectChanges();
263 formHelper.expectError(setPgNum('8'), 'noDecrease');
266 it('is valid if pgNum, poolType and name are valid', () => {
267 formHelper.setValue('name', 'some-name');
268 formHelper.setValue('poolType', 'erasure');
269 fixture.detectChanges();
271 expect(form.valid).toBeTruthy();
274 it('validates crushRule', () => {
275 formHelper.expectValid('crushRule');
276 formHelper.expectErrorChange('crushRule', { min_size: 20 }, 'tooFewOsds');
279 it('validates size', () => {
280 formHelper.setValue('poolType', 'replicated');
281 formHelper.expectValid('size');
282 formHelper.setValue('crushRule', {
286 formHelper.expectErrorChange('size', 1, 'min');
287 formHelper.expectErrorChange('size', 8, 'max');
288 formHelper.expectValidChange('size', 6);
291 it('validates compression mode default value', () => {
292 expect(form.getValue('mode')).toBe('none');
295 describe('compression form', () => {
297 formHelper.setValue('poolType', 'replicated');
298 formHelper.setValue('mode', 'passive');
301 it('is valid', () => {
302 expect(component.form.get('compression').valid).toBeTruthy();
305 it('validates minBlobSize to be only valid between 0 and maxBlobSize', () => {
306 formHelper.expectErrorChange('minBlobSize', -1, 'min');
307 formHelper.expectValidChange('minBlobSize', 0);
308 formHelper.setValue('maxBlobSize', '2 KiB');
309 formHelper.expectErrorChange('minBlobSize', '3 KiB', 'maximum');
310 formHelper.expectValidChange('minBlobSize', '1.9 KiB');
313 it('validates minBlobSize converts numbers', () => {
314 const control = formHelper.setValue('minBlobSize', '1');
315 fixture.detectChanges();
316 formHelper.expectValid(control);
317 expect(control.value).toBe('1 KiB');
320 it('validates maxBlobSize to be only valid bigger than minBlobSize', () => {
321 formHelper.expectErrorChange('maxBlobSize', -1, 'min');
322 formHelper.setValue('minBlobSize', '1 KiB');
323 formHelper.expectErrorChange('maxBlobSize', '0.5 KiB', 'minimum');
324 formHelper.expectValidChange('maxBlobSize', '1.5 KiB');
327 it('s valid to only use one blob size', () => {
328 formHelper.expectValid(formHelper.setValue('minBlobSize', '1 KiB'));
329 formHelper.expectValid(formHelper.setValue('maxBlobSize', ''));
330 formHelper.expectValid(formHelper.setValue('minBlobSize', ''));
331 formHelper.expectValid(formHelper.setValue('maxBlobSize', '1 KiB'));
334 it('dismisses any size error if one of the blob sizes is changed into a valid state', () => {
335 const min = formHelper.setValue('minBlobSize', '10 KiB');
336 const max = formHelper.setValue('maxBlobSize', '1 KiB');
337 fixture.detectChanges();
339 formHelper.expectValid(min);
340 formHelper.expectValid(max);
341 max.setValue('1 KiB');
342 fixture.detectChanges();
343 min.setValue('0.5 KiB');
344 formHelper.expectValid(min);
345 formHelper.expectValid(max);
348 it('validates maxBlobSize converts numbers', () => {
349 const control = formHelper.setValue('maxBlobSize', '2');
350 fixture.detectChanges();
351 expect(control.value).toBe('2 KiB');
354 it('validates that odd size validator works as expected', () => {
355 const odd = (min, max) => component['oddBlobSize'](min, max);
356 expect(odd('10', '8')).toBe(true);
357 expect(odd('8', '-')).toBe(false);
358 expect(odd('8', '10')).toBe(false);
359 expect(odd(null, '8')).toBe(false);
360 expect(odd('10', '')).toBe(false);
361 expect(odd('10', null)).toBe(false);
362 expect(odd(null, null)).toBe(false);
365 it('validates ratio to be only valid between 0 and 1', () => {
366 formHelper.expectValid('ratio');
367 formHelper.expectErrorChange('ratio', -0.1, 'min');
368 formHelper.expectValidChange('ratio', 0);
369 formHelper.expectValidChange('ratio', 1);
370 formHelper.expectErrorChange('ratio', 1.1, 'max');
374 it('validates application metadata name', () => {
375 formHelper.setValue('poolType', 'replicated');
376 fixture.detectChanges();
377 const selectBadges = fixture.debugElement.query(By.directive(SelectBadgesComponent))
379 const control = selectBadges.cdSelect.filter;
380 formHelper.expectValid(control);
381 control.setValue('?');
382 formHelper.expectError(control, 'pattern');
383 control.setValue('Ab3_');
384 formHelper.expectValid(control);
385 control.setValue('a'.repeat(129));
386 formHelper.expectError(control, 'maxlength');
390 describe('pool type changes', () => {
392 component.ngOnInit();
393 createCrushRule({ id: 3, min: 1, max: 1, name: 'ep1', type: 'erasure' });
394 createCrushRule({ id: 0, min: 2, max: 4, name: 'rep1', type: 'replicated' });
395 createCrushRule({ id: 1, min: 3, max: 18, name: 'rep2', type: 'replicated' });
398 it('should have a default replicated size of 3', () => {
399 formHelper.setValue('poolType', 'replicated');
400 expect(form.getValue('size')).toBe(3);
403 describe('replicatedRuleChange', () => {
405 formHelper.setValue('poolType', 'replicated');
406 formHelper.setValue('size', 99);
409 it('should not set size if a replicated pool is not set', () => {
410 formHelper.setValue('poolType', 'erasure');
411 expect(form.getValue('size')).toBe(99);
412 formHelper.setValue('crushRule', component.info.crush_rules_replicated[1]);
413 expect(form.getValue('size')).toBe(99);
416 it('should set size to maximum if size exceeds maximum', () => {
417 formHelper.setValue('crushRule', component.info.crush_rules_replicated[0]);
418 expect(form.getValue('size')).toBe(4);
421 it('should set size to minimum if size is lower than minimum', () => {
422 formHelper.setValue('size', -1);
423 formHelper.setValue('crushRule', component.info.crush_rules_replicated[0]);
424 expect(form.getValue('size')).toBe(2);
428 describe('rulesChange', () => {
429 it('has no effect if info is not there', () => {
430 delete component.info;
431 formHelper.setValue('poolType', 'replicated');
432 expect(component.current.rules).toEqual([]);
435 it('has no effect if pool type is not set', () => {
436 component['rulesChange']();
437 expect(component.current.rules).toEqual([]);
440 it('shows all replicated rules when pool type is "replicated"', () => {
441 formHelper.setValue('poolType', 'replicated');
442 expect(component.current.rules).toEqual(component.info.crush_rules_replicated);
443 expect(component.current.rules.length).toBe(2);
446 it('shows all erasure code rules when pool type is "erasure"', () => {
447 formHelper.setValue('poolType', 'erasure');
448 expect(component.current.rules).toEqual(component.info.crush_rules_erasure);
449 expect(component.current.rules.length).toBe(1);
452 it('disables rule field if only one rule exists which is used in the disabled field', () => {
453 formHelper.setValue('poolType', 'erasure');
454 const control = form.get('crushRule');
455 expect(control.value).toEqual(component.info.crush_rules_erasure[0]);
456 expect(control.disabled).toBe(true);
459 it('does not select the first rule if more than one exist', () => {
460 formHelper.setValue('poolType', 'replicated');
461 const control = form.get('crushRule');
462 expect(control.value).toEqual(null);
463 expect(control.disabled).toBe(false);
466 it('changing between both types will not leave crushRule in a bad state', () => {
467 formHelper.setValue('poolType', 'erasure');
468 formHelper.setValue('poolType', 'replicated');
469 const control = form.get('crushRule');
470 expect(control.value).toEqual(null);
471 expect(control.disabled).toBe(false);
472 formHelper.setValue('poolType', 'erasure');
473 expect(control.value).toEqual(component.info.crush_rules_erasure[0]);
474 expect(control.disabled).toBe(true);
479 describe('getMaxSize and getMinSize', () => {
480 const setCrushRule = ({ min, max }: { min?: number; max?: number }) => {
481 formHelper.setValue('crushRule', {
487 it('returns nothing if osd count is 0', () => {
488 component.info.osd_count = 0;
489 expect(component.getMinSize()).toBe(undefined);
490 expect(component.getMaxSize()).toBe(undefined);
493 it('returns nothing if info is not there', () => {
494 delete component.info;
495 expect(component.getMinSize()).toBe(undefined);
496 expect(component.getMaxSize()).toBe(undefined);
499 it('returns minimum and maximum of rule', () => {
500 setCrushRule({ min: 2, max: 6 });
501 expect(component.getMinSize()).toBe(2);
502 expect(component.getMaxSize()).toBe(6);
505 it('returns 1 as minimum and the osd count as maximum if no crush rule is available', () => {
506 expect(component.getMinSize()).toBe(1);
507 expect(component.getMaxSize()).toBe(OSDS);
510 it('returns the osd count as maximum if the rule maximum exceeds it', () => {
511 setCrushRule({ max: 100 });
512 expect(component.getMaxSize()).toBe(OSDS);
515 it('should return the osd count as minimum if its lower the the rule minimum', () => {
516 setCrushRule({ min: 10 });
517 expect(component.getMinSize()).toBe(10);
518 const control = form.get('crushRule');
519 expect(control.invalid).toBe(true);
520 formHelper.expectError(control, 'tooFewOsds');
524 describe('application metadata', () => {
525 let selectBadges: SelectBadgesComponent;
527 const testAddApp = (app?: string, result?: string[]) => {
528 selectBadges.cdSelect.filter.setValue(app);
529 selectBadges.cdSelect.updateFilter();
530 selectBadges.cdSelect.selectOption();
531 expect(component.data.applications.selected).toEqual(result);
534 const testRemoveApp = (app: string, result: string[]) => {
535 selectBadges.cdSelect.removeItem(app);
536 expect(component.data.applications.selected).toEqual(result);
539 const setCurrentApps = (apps: string[]) => {
540 component.data.applications.selected = apps;
541 fixture.detectChanges();
542 selectBadges.cdSelect.ngOnInit();
547 formHelper.setValue('poolType', 'replicated');
548 fixture.detectChanges();
549 selectBadges = fixture.debugElement.query(By.directive(SelectBadgesComponent))
553 it('adds all predefined and a custom applications to the application metadata array', () => {
554 testAddApp('g', ['rgw']);
555 testAddApp('b', ['rbd', 'rgw']);
556 testAddApp('c', ['cephfs', 'rbd', 'rgw']);
557 testAddApp('something', ['cephfs', 'rbd', 'rgw', 'something']);
560 it('only allows 4 apps to be added to the array', () => {
561 const apps = setCurrentApps(['d', 'c', 'b', 'a']);
562 testAddApp('e', apps);
565 it('can remove apps', () => {
566 setCurrentApps(['a', 'b', 'c', 'd']);
567 testRemoveApp('c', ['a', 'b', 'd']);
568 testRemoveApp('a', ['b', 'd']);
569 testRemoveApp('d', ['b']);
570 testRemoveApp('b', []);
573 it('does not remove any app that is not in the array', () => {
574 const apps = ['a', 'b', 'c', 'd'];
575 setCurrentApps(apps);
576 testRemoveApp('e', apps);
577 testRemoveApp('0', apps);
581 describe('pg number changes', () => {
582 const setPgs = (pgs) => {
583 formHelper.setValue('pgNum', pgs);
584 fixture.debugElement.query(By.css('#pgNum')).nativeElement.dispatchEvent(new Event('blur'));
587 const testPgUpdate = (pgs, jump, returnValue) => {
592 setPgs(form.getValue('pgNum') + jump);
594 expect(form.getValue('pgNum')).toBe(returnValue);
598 formHelper.setValue('crushRule', {
602 formHelper.setValue('poolType', 'erasure');
603 fixture.detectChanges();
607 it('updates by value', () => {
608 testPgUpdate(10, undefined, 8);
609 testPgUpdate(22, undefined, 16);
610 testPgUpdate(26, undefined, 32);
611 testPgUpdate(200, undefined, 256);
612 testPgUpdate(300, undefined, 256);
613 testPgUpdate(350, undefined, 256);
616 it('updates by jump -> a magnitude of the power of 2', () => {
617 testPgUpdate(undefined, 1, 512);
618 testPgUpdate(undefined, -1, 256);
621 it('returns 1 as minimum for false numbers', () => {
622 testPgUpdate(-26, undefined, 1);
623 testPgUpdate(0, undefined, 1);
624 testPgUpdate(0, -1, 1);
625 testPgUpdate(undefined, -20, 1);
628 it('changes the value and than jumps', () => {
629 testPgUpdate(230, 1, 512);
630 testPgUpdate(3500, -1, 2048);
633 describe('pg power jump', () => {
634 it('should jump correctly at the beginning', () => {
635 testPgUpdate(1, -1, 1);
636 testPgUpdate(1, 1, 2);
637 testPgUpdate(2, -1, 1);
638 testPgUpdate(2, 1, 4);
639 testPgUpdate(4, -1, 2);
640 testPgUpdate(4, 1, 8);
641 testPgUpdate(4, 1, 8);
644 it('increments pg power if difference to the current number is 1', () => {
645 testPgUpdate(undefined, 1, 512);
646 testPgUpdate(undefined, 1, 1024);
647 testPgUpdate(undefined, 1, 2048);
648 testPgUpdate(undefined, 1, 4096);
651 it('decrements pg power if difference to the current number is -1', () => {
652 testPgUpdate(undefined, -1, 128);
653 testPgUpdate(undefined, -1, 64);
654 testPgUpdate(undefined, -1, 32);
655 testPgUpdate(undefined, -1, 16);
656 testPgUpdate(undefined, -1, 8);
660 describe('pgCalc', () => {
663 const getValidCase = () => ({
674 const testPgCalc = ({ type, osds, size, ecp, expected }) => {
675 component.info.osd_count = osds;
676 formHelper.setValue('poolType', type);
677 if (type === 'replicated') {
678 formHelper.setValue('size', size);
680 formHelper.setValue('erasureProfile', ecp);
682 expect(form.getValue('pgNum')).toBe(expected);
683 expect(component.externalPgChange).toBe(PGS !== expected);
690 it('does not change anything if type is not valid', () => {
691 const test = getValidCase();
697 it('does not change anything if ecp is not valid', () => {
698 const test = getValidCase();
700 test.type = 'erasure';
705 it('calculates some replicated values', () => {
706 const test = getValidCase();
717 it('calculates erasure code values even if selection is disabled', () => {
718 component['initEcp']([{ k: 2, m: 2, name: 'bla', plugin: '', technique: '' }]);
719 const test = getValidCase();
720 test.type = 'erasure';
722 expect(form.get('erasureProfile').disabled).toBeTruthy();
725 it('calculates some erasure code values', () => {
726 const test = getValidCase();
727 test.type = 'erasure';
738 it('should not change a manual set pg number', () => {
739 form.get('pgNum').markAsDirty();
740 const test = getValidCase();
747 describe('crushRule', () => {
749 createCrushRule({ name: 'replicatedRule' });
750 fixture.detectChanges();
751 formHelper.setValue('poolType', 'replicated');
752 fixture.detectChanges();
755 it('should not show info per default', () => {
756 fixtureHelper.expectElementVisible('#crushRule', true);
757 fixtureHelper.expectElementVisible('#crush-info-block', false);
760 it('should show info if the info button is clicked', () => {
761 fixture.detectChanges();
762 const infoButton = fixture.debugElement.query(By.css('#crush-info-button'));
763 infoButton.triggerEventHandler('click', null);
764 expect(component.data.crushInfo).toBeTruthy();
765 fixture.detectChanges();
766 expect(infoButton.classes['active']).toBeTruthy();
767 fixtureHelper.expectIdElementsVisible(['crushRule', 'crush-info-block'], true);
771 describe('erasure code profile', () => {
772 const setSelectedEcp = (name: string) => {
773 formHelper.setValue('erasureProfile', { name: name });
777 formHelper.setValue('poolType', 'erasure');
778 fixture.detectChanges();
781 it('should not show info per default', () => {
782 fixtureHelper.expectElementVisible('#erasureProfile', true);
783 fixtureHelper.expectElementVisible('#ecp-info-block', false);
786 it('should show info if the info button is clicked', () => {
787 const infoButton = fixture.debugElement.query(By.css('#ecp-info-button'));
788 infoButton.triggerEventHandler('click', null);
789 expect(component.data.erasureInfo).toBeTruthy();
790 fixture.detectChanges();
791 expect(infoButton.classes['active']).toBeTruthy();
792 fixtureHelper.expectIdElementsVisible(['erasureProfile', 'ecp-info-block'], true);
795 describe('ecp deletion', () => {
796 let taskWrapper: TaskWrapperService;
797 let deletion: CriticalConfirmationModalComponent;
799 const callDeletion = () => {
800 component.deleteErasureCodeProfile();
801 deletion.submitActionObservable();
804 const testPoolDeletion = (name) => {
805 setSelectedEcp(name);
807 expect(ecpService.delete).toHaveBeenCalledWith(name);
808 expect(taskWrapper.wrapTaskAroundCall).toHaveBeenCalledWith({
815 call: undefined // because of stub
820 spyOn(TestBed.get(BsModalService), 'show').and.callFake((deletionClass, config) => {
821 deletion = Object.assign(new deletionClass(), config.initialState);
826 spyOn(ecpService, 'delete').and.stub();
827 taskWrapper = TestBed.get(TaskWrapperService);
828 spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
831 it('should delete two different erasure code profiles', () => {
832 testPoolDeletion('someEcpName');
833 testPoolDeletion('aDifferentEcpName');
838 describe('submit - create', () => {
839 const setMultipleValues = (settings: {}) => {
840 Object.keys(settings).forEach((name) => {
841 formHelper.setValue(name, settings[name]);
844 const testCreate = (pool) => {
845 expectValidSubmit(pool, 'pool/create', 'create');
849 createCrushRule({ name: 'replicatedRule' });
850 createCrushRule({ name: 'erasureRule', type: 'erasure', id: 1 });
853 describe('erasure coded pool', () => {
854 it('minimum requirements', () => {
862 pool_type: 'erasure',
867 it('with erasure coded profile', () => {
868 const ecp = { name: 'ecpMinimalMock' };
873 size: 2, // Will be ignored
878 pool_type: 'erasure',
880 erasure_code_profile: ecp.name
884 it('with ec_overwrite flag', () => {
886 name: 'ecOverwrites',
892 pool: 'ecOverwrites',
893 pool_type: 'erasure',
895 flags: ['ec_overwrites']
899 it('with rbd qos settings', () => {
901 name: 'replicatedRbdQos',
902 poolType: 'replicated',
906 component.currentConfigurationValues = {
907 rbd_qos_bps_limit: 55
910 pool: 'replicatedRbdQos',
911 pool_type: 'replicated',
915 rbd_qos_bps_limit: 55
921 describe('replicated coded pool', () => {
922 it('minimum requirements', () => {
923 const ecp = { name: 'ecpMinimalMock' };
926 poolType: 'replicated',
928 erasureProfile: ecp, // Will be ignored
933 pool_type: 'replicated',
940 it('pool with compression', () => {
953 pool_type: 'erasure',
955 compression_mode: 'passive',
956 compression_algorithm: 'lz4',
957 compression_min_blob_size: 4096,
958 compression_max_blob_size: 4194304,
959 compression_required_ratio: 0.7
963 it('pool with application metadata', () => {
969 component.data.applications.selected = ['cephfs', 'rgw'];
972 pool_type: 'erasure',
974 application_metadata: ['cephfs', 'rgw']
979 describe('edit mode', () => {
980 const setUrl = (url) => {
981 Object.defineProperty(router, 'url', { value: url });
982 setUpPoolComponent(); // Renew of component needed because the constructor has to be called
987 pool = new Pool('somePoolName');
988 pool.type = 'replicated';
990 pool.crush_rule = 'someRule';
993 pool.options.compression_mode = 'passive';
994 pool.options.compression_algorithm = 'lz4';
995 pool.options.compression_min_blob_size = 1024 * 512;
996 pool.options.compression_max_blob_size = 1024 * 1024;
997 pool.options.compression_required_ratio = 0.8;
998 pool.flags_names = 'someFlag1,someFlag2';
999 pool.application_metadata = ['rbd', 'rgw'];
1001 createCrushRule({ name: 'someRule' });
1002 spyOn(poolService, 'get').and.callFake(() => of(pool));
1005 it('is not in edit mode if edit is not included in url', () => {
1006 setUrl('/pool/add');
1007 expect(component.editing).toBeFalsy();
1010 it('is in edit mode if edit is included in url', () => {
1011 setUrl('/pool/edit/somePoolName');
1012 expect(component.editing).toBeTruthy();
1015 describe('after ngOnInit', () => {
1017 component.editing = true;
1018 fixture.detectChanges();
1021 it('disabled inputs', () => {
1022 const disabled = ['poolType', 'crushRule', 'size', 'erasureProfile', 'ecOverwrites'];
1023 disabled.forEach((controlName) => {
1024 return expect(form.get(controlName).disabled).toBeTruthy();
1035 enabled.forEach((controlName) => {
1036 return expect(form.get(controlName).enabled).toBeTruthy();
1040 it('set all control values to the given pool', () => {
1041 expect(form.getValue('name')).toBe(pool.pool_name);
1042 expect(form.getValue('poolType')).toBe(pool.type);
1043 expect(form.getValue('crushRule')).toEqual(component.info.crush_rules_replicated[0]);
1044 expect(form.getValue('size')).toBe(pool.size);
1045 expect(form.getValue('pgNum')).toBe(pool.pg_num);
1046 expect(form.getValue('mode')).toBe(pool.options.compression_mode);
1047 expect(form.getValue('algorithm')).toBe(pool.options.compression_algorithm);
1048 expect(form.getValue('minBlobSize')).toBe('512 KiB');
1049 expect(form.getValue('maxBlobSize')).toBe('1 MiB');
1050 expect(form.getValue('ratio')).toBe(pool.options.compression_required_ratio);
1053 it('is only be possible to use the same or more pgs like before', () => {
1054 formHelper.expectValid(setPgNum(64));
1055 formHelper.expectError(setPgNum(4), 'noDecrease');
1058 describe('submit', () => {
1059 const markControlAsPreviouslySet = (controlName) => form.get(controlName).markAsPristine();
1062 ['algorithm', 'maxBlobSize', 'minBlobSize', 'mode', 'pgNum', 'ratio', 'name'].forEach(
1063 (name) => markControlAsPreviouslySet(name)
1065 fixture.detectChanges();
1068 it(`always provides the application metadata array with submit even if it's empty`, () => {
1069 expect(form.get('mode').dirty).toBe(false);
1070 component.data.applications.selected = [];
1073 application_metadata: [],
1074 pool: 'somePoolName'
1081 it(`will always provide reset value for compression options`, () => {
1082 formHelper.setValue('minBlobSize', '').markAsDirty();
1083 formHelper.setValue('maxBlobSize', '').markAsDirty();
1084 formHelper.setValue('ratio', '').markAsDirty();
1087 application_metadata: ['rbd', 'rgw'],
1088 compression_max_blob_size: 0,
1089 compression_min_blob_size: 0,
1090 compression_required_ratio: 0,
1091 pool: 'somePoolName'
1098 it(`will unset mode not used anymore`, () => {
1099 formHelper.setValue('mode', 'none').markAsDirty();
1102 application_metadata: ['rbd', 'rgw'],
1103 compression_mode: 'unset',
1104 pool: 'somePoolName'
1114 describe('test pool configuration component', () => {
1115 it('is visible for replicated pools with rbd application', () => {
1116 const poolType = component.form.get('poolType');
1117 poolType.markAsDirty();
1118 poolType.setValue('replicated');
1119 component.data.applications.selected = ['rbd'];
1120 fixture.detectChanges();
1122 fixture.debugElement.query(By.css('cd-rbd-configuration-form')).nativeElement.parentElement
1127 it('is invisible for erasure coded pools', () => {
1128 const poolType = component.form.get('poolType');
1129 poolType.markAsDirty();
1130 poolType.setValue('erasure');
1131 fixture.detectChanges();
1133 fixture.debugElement.query(By.css('cd-rbd-configuration-form')).nativeElement.parentElement