]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: NFS Export form fixes 59900/head
authorDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Wed, 7 Aug 2024 07:26:10 +0000 (12:56 +0530)
committerDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Wed, 9 Oct 2024 06:47:15 +0000 (12:17 +0530)
Fixes: https://tracker.ceph.com/issues/67400
Signed-off-by: Dnyaneshwari Talwekar <dtalweka@redhat.com>
(cherry picked from commit 28d2fa30c25cb9d089e93921ef3be022c98ac35e)

src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/constants/cephfs.constant.ts [new file with mode: 0644]

index ef78407b7e5eef1db9552c83121ca99d38f0893f..1e5654b6578bfd5aab04f02242fbd330af8f188e 100644 (file)
               <option *ngIf="allsubvolgrps === null"
                       value=""
                       i18n>Loading...</option>
-              <option *ngIf="allsubvolgrps !== null && allsubvolgrps.length === 0"
-                      value=""
-                      i18n>-- No CephFS subvolume group available --</option>
-              <option *ngIf="allsubvolgrps !== null && allsubvolgrps.length > 0"
+              <option *ngIf="allsubvolgrps !== null && allsubvolgrps.length >= 0"
                       value=""
                       i18n>-- Select the CephFS subvolume group --</option>
               <option *ngFor="let subvol_grp of allsubvolgrps"
-                      [value]="subvol_grp.name">{{ subvol_grp.name }}</option>
+                      [value]="subvol_grp.name"
+                      [selected]="subvol_grp.name === nfsForm.get('subvolume_group').value">{{ subvol_grp.name }}</option>
               <option [value]="defaultSubVolGroup">{{ defaultSubVolGroup }}</option>
             </select>
           </div>
                   formControlName="subvolume"
                   name="subvolume"
                   id="subvolume"
-                  (change)="setSubVolPath()">
+                  (change)="setSubVolPath()"
+                  [invalid]="nfsForm.controls.subvolume.invalid && (nfsForm.controls.subvolume.dirty)"
+                  [invalidText]="subvolumeError">
             <option *ngIf="allsubvols === null"
                     value=""
                     i18n>Loading...</option>
                     value=""
                     i18n>-- Select the CephFS subvolume --</option>
             <option *ngFor="let subvolume of allsubvols"
-                    [value]="subvolume.name">{{ subvolume.name }}</option>
+                    [value]="subvolume.name"
+                    [selected]="subvolume.name === nfsForm.get('subvolume').value">{{ subvolume.name }}</option>
           </select>
+          <span class="invalid-feedback"
+                *ngIf="nfsForm.getValue('subvolume_group') === defaultSubVolGroup && !nfsForm.getValue('subvolume')"
+                i18n>This field is required.</span>
         </div>
       </div>
 
             <span class="invalid-feedback"
                   *ngIf="nfsForm.showError('path', formDir, 'required')"
                   i18n>This field is required.</span>
-
+            <span class="invalid-feedback"
+                  *ngIf="nfsForm.get('path').hasError('isIsolatedSlash') && nfsForm.get('path').touched"
+                  i18n>Export on CephFS volume "<code>/</code>" not allowed.</span>
             <span class="invalid-feedback"
                   *ngIf="nfsForm.showError('path', formDir, 'pattern')"
                   i18n>Path need to start with a '/' and can be followed by a word</span>
index 7f88c6486843f586d5339826aa5ff2d01ff3e92d..5d27c18a4dff0feb14c2f4a0a365040741a4ffa3 100644 (file)
@@ -92,7 +92,7 @@ describe('NfsFormComponent', () => {
       clients: [],
       cluster_id: 'mynfs',
       fsal: { fs_name: '', name: 'CEPH' },
-      path: '/',
+      path: '',
       protocolNfsv4: true,
       protocolNfsv3: true,
       pseudo: '',
@@ -100,7 +100,7 @@ describe('NfsFormComponent', () => {
       security_label: false,
       squash: 'no_root_squash',
       subvolume: '',
-      subvolume_group: '',
+      subvolume_group: '_nogroup',
       transportTCP: true,
       transportUDP: true
     });
index 18d91e2cddfdf6a02dc2041511c904b188e3a68d..e6f413f326b696ada861b9e31237c56d95d6a91b 100644 (file)
@@ -31,6 +31,7 @@ import { NfsFormClientComponent } from '../nfs-form-client/nfs-form-client.compo
 import { getFsalFromRoute, getPathfromFsal } from '../utils';
 import { CephfsSubvolumeService } from '~/app/shared/api/cephfs-subvolume.service';
 import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service';
+import { DEFAULT_SUBVOLUME_GROUP } from '~/app/shared/constants/cephfs.constant';
 
 @Component({
   selector: 'cd-nfs-form',
@@ -71,7 +72,7 @@ export class NfsFormComponent extends CdForm implements OnInit {
   selectedFsName: string = '';
   selectedSubvolGroup: string = '';
   selectedSubvol: string = '';
-  defaultSubVolGroup = '_nogroup';
+  defaultSubVolGroup = DEFAULT_SUBVOLUME_GROUP;
 
   pathDataSource = (text$: Observable<string>) => {
     return text$.pipe(
@@ -160,9 +161,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() {
@@ -176,6 +175,7 @@ export class NfsFormComponent extends CdForm implements OnInit {
     ).subscribe((data: any) => {
       this.allsubvols = data;
     });
+    this.setUpVolumeValidation();
   }
 
   getSubVolGrp(fs_name: string) {
@@ -185,16 +185,15 @@ export class NfsFormComponent extends CdForm implements OnInit {
   }
 
   setSubVolGrpPath(): Promise<void> {
-    return new Promise<void>((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<void>((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) => {
@@ -203,10 +202,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<void> {
     return new Promise<void>((resolve, reject) => {
       const subvol = this.nfsForm.getValue('subvolume');
@@ -226,9 +238,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() {
@@ -248,10 +272,13 @@ export class NfsFormComponent extends CdForm implements OnInit {
           ]
         })
       }),
-      subvolume_group: new UntypedFormControl(''),
+      subvolume_group: new UntypedFormControl(this.defaultSubVolGroup),
       subvolume: new UntypedFormControl(''),
-      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: [
@@ -416,6 +443,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) {
@@ -462,14 +493,6 @@ export class NfsFormComponent extends CdForm implements OnInit {
     );
   }
 
-  pathChangeHandler() {
-    if (!this.isEdit) {
-      this.nfsForm.patchValue({
-        pseudo: this.generatePseudo()
-      });
-    }
-  }
-
   private getBucketTypeahead(path: string): Observable<any> {
     if (_.isString(path) && path !== '/' && path !== '') {
       return this.rgwBucketService.list().pipe(
@@ -485,20 +508,6 @@ export class NfsFormComponent extends CdForm implements OnInit {
     }
   }
 
-  private generatePseudo() {
-    let newPseudo = this.nfsForm.getValue('pseudo');
-    if (this.nfsForm.get('pseudo') && !this.nfsForm.get('pseudo').dirty) {
-      newPseudo = undefined;
-      if (this.storageBackend === 'CEPH') {
-        newPseudo = '/cephfs';
-        if (_.isString(this.nfsForm.getValue('path'))) {
-          newPseudo += this.nfsForm.getValue('path');
-        }
-      }
-    }
-    return newPseudo;
-  }
-
   submitAction() {
     let action: Observable<any>;
     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 (file)
index 0000000..56890ff
--- /dev/null
@@ -0,0 +1 @@
+export const DEFAULT_SUBVOLUME_GROUP = '_nogroup';