From: Kiefer Chang Date: Fri, 10 May 2019 03:54:12 +0000 (+0800) Subject: mgr/dashboard: allow managing pool quotas in frontend X-Git-Tag: v15.1.0~2312^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=809e0a29a316732f800442dc4003e7dc77d67760;p=ceph.git mgr/dashboard: allow managing pool quotas in frontend Fixes: https://tracker.ceph.com/issues/36559 Signed-off-by: Kiefer Chang --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html index ad9dca78e350..5b8ce5080093 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html @@ -423,6 +423,61 @@ + +
+ Quotas + + +
+ +
+ +
+
+ + +
+ +
+ + The value should be greater or equal to 0 +
+
+
+
{ expect(form.getValue('mode')).toBe('none'); }); + it('validate quotas', () => { + formHelper.expectValid('max_bytes'); + formHelper.expectValid('max_objects'); + formHelper.expectValidChange('max_bytes', '10 Gib'); + formHelper.expectValidChange('max_bytes', ''); + formHelper.expectValidChange('max_objects', ''); + formHelper.expectErrorChange('max_objects', -1, 'min'); + }); + describe('compression form', () => { beforeEach(() => { formHelper.setValue('poolType', 'replicated'); @@ -930,6 +939,23 @@ describe('PoolFormComponent', () => { size: 2 }); }); + + it('with quotas', () => { + setMultipleValues({ + name: 'RepPoolWithQuotas', + poolType: 'replicated', + max_bytes: 1024 * 1024, + max_objects: 3000, + pgNum: 8 + }); + testCreate({ + pool: 'RepPoolWithQuotas', + pool_type: 'replicated', + quota_max_bytes: 1024 * 1024, + quota_max_objects: 3000, + pg_num: 8 + }); + }); }); it('pool with compression', () => { @@ -992,6 +1018,8 @@ describe('PoolFormComponent', () => { pool.options.compression_required_ratio = 0.8; pool.flags_names = 'someFlag1,someFlag2'; pool.application_metadata = ['rbd', 'rgw']; + pool.quota_max_bytes = 1024 * 1024 * 1024; + pool.quota_max_objects = 3000; createCrushRule({ name: 'someRule' }); spyOn(poolService, 'get').and.callFake(() => of(pool)); @@ -1025,7 +1053,9 @@ describe('PoolFormComponent', () => { 'algorithm', 'minBlobSize', 'maxBlobSize', - 'ratio' + 'ratio', + 'max_bytes', + 'max_objects' ]; enabled.forEach((controlName) => { return expect(form.get(controlName).enabled).toBeTruthy(); @@ -1043,6 +1073,8 @@ describe('PoolFormComponent', () => { expect(form.getValue('minBlobSize')).toBe('512 KiB'); expect(form.getValue('maxBlobSize')).toBe('1 MiB'); expect(form.getValue('ratio')).toBe(pool.options.compression_required_ratio); + expect(form.getValue('max_bytes')).toBe('1 GiB'); + expect(form.getValue('max_objects')).toBe(pool.quota_max_objects); }); it('updates pgs on every change', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts index b6f57ee9a826..52ac90176565 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts @@ -136,7 +136,11 @@ export class PoolFormComponent implements OnInit { validators: [Validators.required, Validators.min(1)] }), ecOverwrites: new FormControl(false), - compression: compressionForm + compression: compressionForm, + max_bytes: new FormControl(''), + max_objects: new FormControl(0, { + validators: [Validators.min(0)] + }) }, [ CdValidators.custom('form', () => null), @@ -221,7 +225,9 @@ export class PoolFormComponent implements OnInit { algorithm: pool.options.compression_algorithm, minBlobSize: this.dimlessBinaryPipe.transform(pool.options.compression_min_blob_size), maxBlobSize: this.dimlessBinaryPipe.transform(pool.options.compression_max_blob_size), - ratio: pool.options.compression_required_ratio + ratio: pool.options.compression_required_ratio, + max_bytes: this.dimlessBinaryPipe.transform(pool.quota_max_bytes), + max_objects: pool.quota_max_objects }; Object.keys(dataMap).forEach((controlName: string) => { @@ -529,7 +535,20 @@ export class PoolFormComponent implements OnInit { formControlName: 'erasureProfile', attr: 'name' }, - { externalFieldName: 'rule_name', formControlName: 'crushRule', attr: 'rule_name' } + { externalFieldName: 'rule_name', formControlName: 'crushRule', attr: 'rule_name' }, + { + externalFieldName: 'quota_max_bytes', + formControlName: 'max_bytes', + replaceFn: this.formatter.toBytes, + editable: true, + resetValue: this.editing ? 0 : undefined + }, + { + externalFieldName: 'quota_max_objects', + formControlName: 'max_objects', + editable: true, + resetValue: this.editing ? 0 : undefined + } ]); if (this.info.is_all_bluestore) {