From 28d2fa30c25cb9d089e93921ef3be022c98ac35e Mon Sep 17 00:00:00 2001 From: Dnyaneshwari Date: Wed, 7 Aug 2024 12:56:10 +0530 Subject: [PATCH] mgr/dashboard: NFS Export form fixes Fixes: https://tracker.ceph.com/issues/67400 Signed-off-by: Dnyaneshwari Talwekar --- .../ceph/nfs/nfs-form/nfs-form.component.html | 24 +++-- .../nfs/nfs-form/nfs-form.component.spec.ts | 4 +- .../ceph/nfs/nfs-form/nfs-form.component.ts | 87 ++++++++++--------- .../app/shared/constants/cephfs.constant.ts | 1 + 4 files changed, 68 insertions(+), 48 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/constants/cephfs.constant.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.html index 271625c86e8..1336baeea7c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.html @@ -73,6 +73,7 @@ label="Volume" cdRequiredField="Volume" id="fs_name" + (change)="volumeChangeHandler()" [invalid]="nfsForm.controls.fsal.controls.fs_name.invalid && (nfsForm.controls.fsal.controls.fs_name.dirty)" [invalidText]="fsNameError" [skeleton]="allFsNames === null" @@ -164,12 +165,11 @@ i18n> - - + [value]="subvol_grp.name" + [selected]="subvol_grp.name === nfsForm.get('subvolume_group').value">{{ subvol_grp.name }} @@ -183,6 +183,8 @@ id="subvolume" [skeleton]="allsubvols === null" (change)="setSubVolPath()" + [invalid]="nfsForm.controls.subvolume.invalid && (nfsForm.controls.subvolume.dirty)" + [invalidText]="subvolumeError" i18n> @@ -191,8 +193,17 @@ + [value]="subvolume.name" + [selected]="subvolume.name === nfsForm.get('subvolume').value">{{ subvolume.name }} + + + This field is required. + + @@ -220,6 +231,9 @@ This field is required. + Export on CephFS volume "/" not allowed. Path need to start with a '/' and can be followed by a word diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts index 27e76721335..1024f00adc5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts @@ -92,7 +92,7 @@ describe('NfsFormComponent', () => { clients: [], cluster_id: 'mynfs', fsal: { fs_name: '', name: 'CEPH', user_id: '' }, - path: '/', + path: '', protocolNfsv4: true, protocolNfsv3: true, pseudo: '', @@ -101,7 +101,7 @@ describe('NfsFormComponent', () => { security_label: false, squash: 'no_root_squash', subvolume: '', - subvolume_group: '', + subvolume_group: '_nogroup', transportTCP: true, transportUDP: true }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts index 64cb5d4b1b9..2317671b022 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts @@ -33,6 +33,7 @@ import { CephfsSubvolumeService } from '~/app/shared/api/cephfs-subvolume.servic import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service'; import { RgwUserService } from '~/app/shared/api/rgw-user.service'; import { RgwExportType } from '../nfs-list/nfs-list.component'; +import { DEFAULT_SUBVOLUME_GROUP } from '~/app/shared/constants/cephfs.constant'; @Component({ selector: 'cd-nfs-form', @@ -75,7 +76,7 @@ export class NfsFormComponent extends CdForm implements OnInit { selectedFsName: string = ''; selectedSubvolGroup: string = ''; selectedSubvol: string = ''; - defaultSubVolGroup = '_nogroup'; + defaultSubVolGroup = DEFAULT_SUBVOLUME_GROUP; pathDataSource = (text$: Observable) => { return text$.pipe( @@ -181,9 +182,7 @@ export class NfsFormComponent extends CdForm implements OnInit { } volumeChangeHandler() { - this.pathChangeHandler(); - const fs_name = this.nfsForm.getValue('fsal').fs_name; - this.getSubVolGrp(fs_name); + this.isDefaultSubvolumeGroup(); } async getSubVol() { @@ -197,6 +196,7 @@ export class NfsFormComponent extends CdForm implements OnInit { ).subscribe((data: any) => { this.allsubvols = data; }); + this.setUpVolumeValidation(); } getSubVolGrp(fs_name: string) { @@ -206,16 +206,15 @@ export class NfsFormComponent extends CdForm implements OnInit { } setSubVolGrpPath(): Promise { - return new Promise((resolve, reject) => { - const subvolGroup = this.nfsForm.getValue('subvolume_group'); - const fs_name = this.nfsForm.getValue('fsal').fs_name; + const fsName = this.nfsForm.getValue('fsal').fs_name; + const subvolGroup = this.nfsForm.getValue('subvolume_group'); - if (subvolGroup === this.defaultSubVolGroup) { + return new Promise((resolve, reject) => { + if (subvolGroup == this.defaultSubVolGroup) { this.updatePath('/volumes/' + this.defaultSubVolGroup); - resolve(); - } else { + } else if (subvolGroup != '') { this.subvolgrpService - .info(fs_name, subvolGroup) + .info(fsName, subvolGroup) .pipe(map((data) => data['path'])) .subscribe( (path) => { @@ -224,10 +223,23 @@ export class NfsFormComponent extends CdForm implements OnInit { }, (error) => reject(error) ); + } else { + this.updatePath(''); + this.setUpVolumeValidation(); } + resolve(); }); } + // Checking if subVolGroup is "_nogroup" and updating path to default as "/volumes/_nogroup else blank." + isDefaultSubvolumeGroup() { + const fsName = this.nfsForm.getValue('fsal').fs_name; + this.getSubVolGrp(fsName); + this.getSubVol(); + this.updatePath('/volumes/' + this.defaultSubVolGroup); + this.setUpVolumeValidation(); + } + setSubVolPath(): Promise { return new Promise((resolve, reject) => { const subvol = this.nfsForm.getValue('subvolume'); @@ -247,9 +259,21 @@ export class NfsFormComponent extends CdForm implements OnInit { }); } + setUpVolumeValidation() { + const subvolumeGroup = this.nfsForm.get('subvolume_group').value; + const subVolumeControl = this.nfsForm.get('subvolume'); + + // SubVolume is required if SubVolume Group is "_nogroup". + if (subvolumeGroup == this.defaultSubVolGroup) { + subVolumeControl?.setValidators([Validators.required]); + } else { + subVolumeControl?.clearValidators(); + } + subVolumeControl?.updateValueAndValidity(); + } + updatePath(path: string) { this.nfsForm.patchValue({ path: path }); - this.pathChangeHandler(); } createForm() { @@ -258,8 +282,11 @@ export class NfsFormComponent extends CdForm implements OnInit { cluster_id: new UntypedFormControl('', { validators: [Validators.required] }), - path: new UntypedFormControl('/', { - validators: [Validators.required] + path: new UntypedFormControl('', { + validators: [ + Validators.required, + CdValidators.custom('isIsolatedSlash', this.isolatedSlashCondition) // Path can never be single "/". + ] }), protocolNfsv3: new UntypedFormControl(true, { validators: [ @@ -324,7 +351,7 @@ export class NfsFormComponent extends CdForm implements OnInit { }), // CephFS-specific fields - subvolume_group: new UntypedFormControl(''), + subvolume_group: new UntypedFormControl(this.defaultSubVolGroup), subvolume: new UntypedFormControl(''), sec_label_xattr: new UntypedFormControl( 'security.selinux', @@ -481,6 +508,10 @@ export class NfsFormComponent extends CdForm implements OnInit { this.defaultAccessType[name] = accessType; } + isolatedSlashCondition(value: string): boolean { + return value === '/'; + } + setPathValidation() { const path = this.nfsForm.get('path'); if (this.storageBackend === SUPPORTED_FSAL.RGW) { @@ -527,14 +558,6 @@ export class NfsFormComponent extends CdForm implements OnInit { ); } - pathChangeHandler() { - if (!this.isEdit) { - this.nfsForm.patchValue({ - pseudo: this.generatePseudo() - }); - } - } - private getBucketTypeahead(path: string): Observable { if (_.isString(path) && path !== '/' && path !== '') { return this.rgwBucketService.list().pipe( @@ -550,24 +573,6 @@ export class NfsFormComponent extends CdForm implements OnInit { } } - private generatePseudo() { - const pseudoControl = this.nfsForm.get('pseudo'); - let newPseudo = pseudoControl?.dirty && this.nfsForm.getValue('pseudo'); - - if (!newPseudo) { - const path = this.nfsForm.getValue('path'); - newPseudo = `/${getPathfromFsal(this.storageBackend)}`; - - if (_.isString(path) && !_.isEmpty(path)) { - newPseudo += '/' + path; - } else if (!_.isEmpty(this.nfsForm.getValue('fsal').user_id)) { - newPseudo += '/' + this.nfsForm.getValue('fsal').user_id; - } - } - - return newPseudo; - } - submitAction() { let action: Observable; const requestModel = this.buildRequest(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/cephfs.constant.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/cephfs.constant.ts new file mode 100644 index 00000000000..56890ff7214 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/cephfs.constant.ts @@ -0,0 +1 @@ +export const DEFAULT_SUBVOLUME_GROUP = '_nogroup'; -- 2.39.5