]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard : add stretch cluster validation for pools form 67952/head
authorAbhishek Desai <abhishek.desai1@ibm.com>
Wed, 15 Apr 2026 08:45:25 +0000 (14:15 +0530)
committerAbhishek Desai <abhishek.desai1@ibm.com>
Sun, 19 Apr 2026 18:39:21 +0000 (00:09 +0530)
fixes : https://tracker.ceph.com/issues/75667
Signed-off-by: Abhishek Desai <abhishek.desai1@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts

index 9652cf145a08382a6f568043043288c2fae2d580..9b44da73c00b04502200b0085f659eea86aefe46 100644 (file)
         class="form-header"
       >
         {{ action | titlecase }} {{ resource | upperFirst }}
+      @if (isStretchMode) {
+      <cd-help-text>
+        Stretch cluster is enabled. Only replicated pool type is supported in Stretch mode.
+      </cd-help-text>
+      }
       </div>
       <!-- Name -->
       <div
           >
             Pool type
           </legend>
+          @if (isStretchMode) {
+          <cds-tooltip
+            description="In stretch mode, only replicated pool type is supported."
+            placement="right"
+            [highContrast]="true"
+            [caret]="true"
+          >
+            <cds-radio-group
+              formControlName="poolType"
+              cdRequiredField="Pool type"
+              data-testid="pool-type-select"
+            >
+              @for (poolType of data.poolTypes; track poolType) {
+              <cds-radio
+                [value]="poolType"
+                [id]="poolType"
+                [disabled]="isStretchMode && !editing && poolType === 'erasure'"
+                (change)="data.erasureInfo = false; data.crushInfo = false;"
+                i18n
+              >
+                {{ poolType | upperFirst }}
+              </cds-radio>
+              }
+            </cds-radio-group>
+          </cds-tooltip>
+          } @else {
           <cds-radio-group
             formControlName="poolType"
             cdRequiredField="Pool type"
             <cds-radio
               [value]="poolType"
               [id]="poolType"
+              [disabled]="isStretchMode && !editing && poolType === 'erasure'"
               (change)="data.erasureInfo = false; data.crushInfo = false;"
               i18n
             >
             </cds-radio>
             }
           </cds-radio-group>
+          }
           @if (form.showError('poolType', formDir, 'required')) {
           <span
             class="cds--form-requirement invalid-feedback"
               }
             </ng-template>
             <ng-template #sizeWarning>
+              @if (isStretchMode) {
+              <span i18n>
+                The replicated size value is predefined for stretch cluster
+              </span>
+              }
               @if (form.getValue('size') === 1) {
               <span
                 class="text-warning-dark"
             [columnNumbers]="{ lg: 13 }"
           >
             <cds-select
+              cdValidate
+              #crushRuleRef="cdValidate"
               formControlName="crushRule"
               label="Crush ruleset"
               i18n-label
               id="crushRule"
+              [invalid]="crushRuleRef.isInvalid"
+              [invalidText]="crushRuleError"
             >
               <option [value]="null"
                       i18n>-- Select a crush rule --
               </option>
               }
             </cds-select>
+            <ng-template #crushRuleError>
+              @if (form.showError('crushRule', formDir, 'required')) {
+              <span
+                class="invalid-feedback"
+                i18n
+              >
+                This field is required!
+              </span>
+              }
+            </ng-template>
           </div>
           } @else {
           <div cdsCol>
index 99466c9fe56af17ca10b77c72411c5aa660786c9..f2e9df5a5efb7cc8f05513cd8eeb7329b94422a6 100644 (file)
@@ -38,6 +38,7 @@ import { Pool } from '../pool';
 import { PoolFormData } from './pool-form-data';
 import { PoolEditModeResponseModel } from '../../block/mirroring/pool-edit-mode-modal/pool-edit-mode-response.model';
 import { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service';
+import { MonitorService } from '~/app/shared/api/monitor.service';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 
 interface FormFieldDescription {
@@ -49,6 +50,12 @@ interface FormFieldDescription {
   resetValue?: any;
 }
 
+interface MonitorResponse {
+  mon_status?: {
+    stretch_mode?: boolean;
+  };
+}
+
 @Component({
   selector: 'cd-pool-form',
   templateUrl: './pool-form.component.html',
@@ -95,6 +102,12 @@ export class PoolFormComponent extends CdForm implements OnInit {
   DEFAULT_RATIO = 0.875;
   isApplicationsSelected = true;
   msrCrush: boolean = false;
+  isStretchMode: boolean = false;
+
+  readonly DEFAULT_REPLICATED_MIN_SIZE = 1;
+  readonly DEFAULT_REPLICATED_MAX_SIZE = 3;
+  readonly STRETCH_REPLICATED_MIN_SIZE = 2;
+  readonly STRETCH_REPLICATED_MAX_SIZE = 4;
 
   private modalSubscription: Subscription;
 
@@ -111,6 +124,7 @@ export class PoolFormComponent extends CdForm implements OnInit {
     private crushRuleService: CrushRuleService,
     public actionLabels: ActionLabelsI18n,
     private rbdMirroringService: RbdMirroringService,
+    private monitorService: MonitorService,
     private cdr: ChangeDetectorRef
   ) {
     super();
@@ -205,6 +219,13 @@ export class PoolFormComponent extends CdForm implements OnInit {
   }
 
   ngOnInit() {
+    this.monitorService.getMonitor().subscribe((data: MonitorResponse) => {
+      this.isStretchMode = data?.mon_status?.stretch_mode || false;
+      if (this.isStretchMode) {
+        this.applyStretchModeRestrictions();
+      }
+      this.replicatedRuleChange();
+    });
     this.poolService.getInfo().subscribe((info: PoolFormInfo) => {
       this.initInfo(info);
       if (this.editing) {
@@ -459,9 +480,31 @@ export class PoolFormComponent extends CdForm implements OnInit {
       this.setListControlStatus('crushRule', rules);
     }
     this.replicatedRuleChange();
+    if (this.isStretchMode) {
+      this.applyStretchModeRestrictions();
+    }
     this.pgCalc();
   }
 
+  private applyStretchModeRestrictions() {
+    if (!this.editing && this.form.getValue('poolType') === 'erasure') {
+      this.form.silentSet('poolType', 'replicated');
+      this.poolTypeChange('replicated');
+      return;
+    }
+    if (this.editing) {
+      return;
+    }
+    const sizeControl = this.form.get('size');
+    if (this.isStretchMode) {
+      if (sizeControl.enabled) {
+        sizeControl.disable({ emitEvent: false });
+      }
+    } else if (sizeControl.disabled) {
+      sizeControl.enable({ emitEvent: false });
+    }
+  }
+
   private setTypeBooleans(replicated: boolean, erasure: boolean) {
     this.isReplicated = replicated;
     this.isErasure = erasure;
@@ -472,7 +515,9 @@ export class PoolFormComponent extends CdForm implements OnInit {
       return;
     }
     const control = this.form.get('size');
-    let size = this.form.getValue('size') || 3;
+    let size =
+      this.form.getValue('size') ||
+      (this.isStretchMode ? this.STRETCH_REPLICATED_MAX_SIZE : this.DEFAULT_REPLICATED_MAX_SIZE);
     const min = this.getMinSize();
     const max = this.getMaxSize();
     if (size < min) {
@@ -489,7 +534,7 @@ export class PoolFormComponent extends CdForm implements OnInit {
     if (!this.info || this.info.osd_count < 1) {
       return 0;
     }
-    return 1;
+    return this.isStretchMode ? this.STRETCH_REPLICATED_MIN_SIZE : this.DEFAULT_REPLICATED_MIN_SIZE;
   }
 
   getMaxSize(): number {
@@ -497,10 +542,9 @@ export class PoolFormComponent extends CdForm implements OnInit {
     if (!this.info) {
       return 0;
     }
+    if (this.isStretchMode) return this.STRETCH_REPLICATED_MAX_SIZE;
     if (!rule) {
-      const osds = this.info.osd_count;
-      const defaultSize = 3;
-      return Math.min(osds, defaultSize);
+      return Math.min(this.info.osd_count, this.DEFAULT_REPLICATED_MAX_SIZE);
     }
     return rule.usable_size;
   }