From: Stephan Müller Date: Mon, 31 Aug 2020 16:10:43 +0000 (+0200) Subject: mgr/dashboard: Disable empty data pool selection in RBD form X-Git-Tag: v16.1.0~801^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=75b9fae708cbb8281b03be0e0c94a808bede0c5e;p=ceph.git mgr/dashboard: Disable empty data pool selection in RBD form Disables empty data pool selections in the RBD form if no or no other than the selected pool is available as data pool. If that's the case a hint can be displayed why that happened. Fixes: https://tracker.ceph.com/issues/37408 Signed-off-by: Stephan Müller --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html index 4db306f9ebf..91a4439b904 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html @@ -141,6 +141,9 @@ + + You need more than one pool with the rbd application label use to use a dedicated data pool. + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts index 8f86c19a5e0..f9bbd4c112b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts @@ -123,6 +123,36 @@ describe('RbdFormComponent', () => { tick(DELAY); })); + describe('disable data pools', () => { + beforeEach(() => { + component.ngOnInit(); + }); + + it('should be enabled with more than 1 pool', () => { + component['handleExternalData'](mock); + expect(component.allDataPools.length).toBe(3); + expect(component.rbdForm.get('useDataPool').disabled).toBe(false); + + mock.pools.pop(); + component['handleExternalData'](mock); + expect(component.allDataPools.length).toBe(2); + expect(component.rbdForm.get('useDataPool').disabled).toBe(false); + }); + + it('should be disabled with 1 pool', () => { + mock.pools = [mock.pools[0]]; + component['handleExternalData'](mock); + expect(component.rbdForm.get('useDataPool').disabled).toBe(true); + }); + + // Reason for 2 tests - useDataPool is not re-enabled anywhere else + it('should be disabled without any pool', () => { + mock.pools = []; + component['handleExternalData'](mock); + expect(component.rbdForm.get('useDataPool').disabled).toBe(true); + }); + }); + it('should edit image after image data is received', () => { component.mode = RbdFormMode.editing; component.ngOnInit(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts index c4146a088e2..676922c43d7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts @@ -33,6 +33,12 @@ import { RbdFormEditRequestModel } from './rbd-form-edit-request.model'; import { RbdFormMode } from './rbd-form-mode.enum'; import { RbdFormResponseModel } from './rbd-form-response.model'; +class ExternalData { + rbd: RbdFormResponseModel; + defaultFeatures: string[]; + pools: Pool[]; +} + @Component({ selector: 'cd-rbd-form', templateUrl: './rbd-form.component.html', @@ -50,8 +56,8 @@ export class RbdFormComponent extends CdForm implements OnInit { namespacesByPoolCache = {}; pools: Array = null; allPools: Array = null; - dataPools: Array = null; - allDataPools: Array = null; + dataPools: Array = null; + allDataPools: Array = []; features: { [key: string]: RbdImageFeature }; featuresList: RbdImageFeature[] = []; initializeConfigData = new EventEmitter<{ @@ -220,98 +226,108 @@ export class RbdFormComponent extends CdForm implements OnInit { } ngOnInit() { - if (this.router.url.startsWith('/block/rbd/edit')) { + this.prepareFormForAction(); + this.gatherNeededData().subscribe(this.handleExternalData.bind(this)); + } + + private prepareFormForAction() { + const url = this.router.url; + if (url.startsWith('/block/rbd/edit')) { this.mode = this.rbdFormMode.editing; this.action = this.actionLabels.EDIT; this.disableForEdit(); - } else if (this.router.url.startsWith('/block/rbd/clone')) { + } else if (url.startsWith('/block/rbd/clone')) { this.mode = this.rbdFormMode.cloning; this.disableForClone(); this.action = this.actionLabels.CLONE; - } else if (this.router.url.startsWith('/block/rbd/copy')) { + } else if (url.startsWith('/block/rbd/copy')) { this.mode = this.rbdFormMode.copying; this.action = this.actionLabels.COPY; this.disableForCopy(); } else { this.action = this.actionLabels.CREATE; } - enum Promisse { - RbdServiceGet = 'rbdService.get', - PoolServiceList = 'poolService.list' - } - const promisses = {}; - if ( - this.mode === this.rbdFormMode.editing || - this.mode === this.rbdFormMode.cloning || - this.mode === this.rbdFormMode.copying - ) { + _.each(this.features, (feature) => { + this.rbdForm + .get('features') + .get(feature.key) + .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value)); + }); + } + + private gatherNeededData(): Observable { + const promises = {}; + if (this.mode) { + // Mode is not set for creation this.route.params.subscribe((params: { image_spec: string; snap: string }) => { const imageSpec = ImageSpec.fromString(decodeURIComponent(params.image_spec)); if (params.snap) { this.snapName = decodeURIComponent(params.snap); } - promisses[Promisse.RbdServiceGet] = this.rbdService.get(imageSpec); + promises['rbd'] = this.rbdService.get(imageSpec); }); } else { // New image - this.rbdService.defaultFeatures().subscribe((defaultFeatures: Array) => { - this.setFeatures(defaultFeatures); - }); + promises['defaultFeatures'] = this.rbdService.defaultFeatures(); } if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) { - promisses[Promisse.PoolServiceList] = this.poolService.list([ + promises['pools'] = this.poolService.list([ 'pool_name', 'type', 'flags_names', 'application_metadata' ]); } + return forkJoin(promises); + } - forkJoin(promisses).subscribe((data: object) => { - // poolService.list - if (data[Promisse.PoolServiceList]) { - const pools: Pool[] = []; - const dataPools = []; - for (const pool of data[Promisse.PoolServiceList]) { - if (this.rbdService.isRBDPool(pool)) { - if (pool.type === 'replicated') { - pools.push(pool); - dataPools.push(pool); - } else if ( - pool.type === 'erasure' && - pool.flags_names.indexOf('ec_overwrites') !== -1 - ) { - dataPools.push(pool); - } - } - } - this.pools = pools; - this.allPools = pools; - this.dataPools = dataPools; - this.allDataPools = dataPools; - if (this.pools.length === 1) { - const poolName = this.pools[0].pool_name; - this.rbdForm.get('pool').setValue(poolName); - this.onPoolChange(poolName); - } - } + private handleExternalData(data: ExternalData) { + this.handlePoolData(data.pools); - // rbdService.get - if (data[Promisse.RbdServiceGet]) { - const resp: RbdFormResponseModel = data[Promisse.RbdServiceGet]; - this.setResponse(resp, this.snapName); - this.rbdImage.next(resp); - } + if (data.defaultFeatures) { + // Fetched only during creation + this.setFeatures(data.defaultFeatures); + } - this.loadingReady(); - }); + if (data.rbd) { + // Not fetched for creation + const resp = data.rbd; + this.setResponse(resp, this.snapName); + this.rbdImage.next(resp); + } - _.each(this.features, (feature) => { - this.rbdForm - .get('features') - .get(feature.key) - .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value)); - }); + this.loadingReady(); + } + + private handlePoolData(data: Pool[]) { + if (!data) { + // Not fetched while editing + return; + } + const pools: Pool[] = []; + const dataPools = []; + for (const pool of data) { + if (this.rbdService.isRBDPool(pool)) { + if (pool.type === 'replicated') { + pools.push(pool); + dataPools.push(pool); + } else if (pool.type === 'erasure' && pool.flags_names.indexOf('ec_overwrites') !== -1) { + dataPools.push(pool); + } + } + } + this.pools = pools; + this.allPools = pools; + this.dataPools = dataPools; + this.allDataPools = dataPools; + if (this.pools.length === 1) { + const poolName = this.pools[0].pool_name; + this.rbdForm.get('pool').setValue(poolName); + this.onPoolChange(poolName); + } + if (this.allDataPools.length <= 1) { + this.rbdForm.get('useDataPool').disable(); + } } onPoolChange(selectedPoolName: string) {