]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: NFS Export form fixes
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>
Thu, 19 Sep 2024 16:07:16 +0000 (21:37 +0530)
Fixes: https://tracker.ceph.com/issues/67400
Signed-off-by: Dnyaneshwari Talwekar <dtalweka@redhat.com>
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 271625c86e8b32acc6dd1101a2d59687e0ce9179..1336baeea7cd8677e8e6a904029aad0d554924e4 100644 (file)
@@ -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"
                     i18n>
           <option *ngIf="allsubvolgrps === null"
                   value="">Loading...</option>
-          <option *ngIf="allsubvolgrps !== null && allsubvolgrps.length === 0"
-                  value="">-- No CephFS subvolume group available --</option>
-          <option *ngIf="allsubvolgrps !== null && allsubvolgrps.length > 0"
+          <option *ngIf="allsubvolgrps !== null && allsubvolgrps.length >= 0"
                   value="">-- 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>
         </cds-select>
       </div>
                   id="subvolume"
                   [skeleton]="allsubvols === null"
                   (change)="setSubVolPath()"
+                  [invalid]="nfsForm.controls.subvolume.invalid && (nfsForm.controls.subvolume.dirty)"
+                  [invalidText]="subvolumeError"
                   i18n>
         <option *ngIf="allsubvols === null"
                 value="">Loading...</option>
         <option *ngIf="allsubvols !== null && allsubvols.length > 0"
                 value="">-- 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>
       </cds-select>
+      <ng-template #subvolumeError>
+        <span
+                  *ngIf="nfsForm.getValue('subvolume_group') === defaultSubVolGroup && !nfsForm.getValue('subvolume')"
+                  class="invalid-feedback"
+                  i18n>
+                  This field is required.
+        </span>
+      </ng-template>
     </div>
 
       <!-- Path -->
           <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 27e76721335e9d864f0bee0252f807c633e7b27f..1024f00adc5d85157c3887b29cbe74c26102e291 100644 (file)
@@ -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
     });
index 64cb5d4b1b939bc14a6aa87f77fa5fd35d27c283..2317671b02238d42965fb5e979306b610c9e2588 100644 (file)
@@ -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<string>) => {
     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<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) => {
@@ -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<void> {
     return new Promise<void>((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<any> {
     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<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';