]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Block -> Images -> Create form improvements 58640/head
authorAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Tue, 4 Jun 2024 12:29:34 +0000 (17:59 +0530)
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Wed, 17 Jul 2024 06:37:54 +0000 (12:07 +0530)
Fixes: https://tracker.ceph.com/issues/66348
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
(cherry picked from commit e3c656440f69fed1a93ed6fa92e2b9e6adf27e45)

src/pybind/mgr/dashboard/controllers/rbd.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-feature.interface.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form-edit-request.model.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rbd.py

index f803ab1a18ae5bae153c98eea80d95c73a13cd71..767d23577b655f95f577bcabb6666c06cf0c72fb 100644 (file)
@@ -137,12 +137,12 @@ class Rbd(RESTController):
     @RbdTask('edit', ['{image_spec}', '{name}'], 4.0)
     def set(self, image_spec, name=None, size=None, features=None,
             configuration=None, metadata=None, enable_mirror=None, primary=None,
-            force=False, resync=False, mirror_mode=None, schedule_interval='',
-            remove_scheduling=False):
+            force=False, resync=False, mirror_mode=None, image_mirror_mode=None,
+            schedule_interval='', remove_scheduling=False):
         return RbdService.set(image_spec, name, size, features,
                               configuration, metadata, enable_mirror, primary,
-                              force, resync, mirror_mode, schedule_interval,
-                              remove_scheduling)
+                              force, resync, mirror_mode, image_mirror_mode,
+                              schedule_interval, remove_scheduling)
 
     @RbdTask('copy',
              {'src_image_spec': '{image_spec}',
index 825b1d2bb39b3a6183768f86a3cf9119645fc855..898bc4523196841169561cf6454952708b966f42 100644 (file)
@@ -7,4 +7,5 @@ export interface RbdImageFeature {
   key?: string;
   initDisabled?: boolean;
   helperHtml?: string;
+  helperText?: string;
 }
index 2eede58521f1e892ddb30f607aab7e947095ebcd..670203dd5f0ab5b3df1f1d107533f721bb709081 100644 (file)
@@ -12,4 +12,5 @@ export class RbdFormEditRequestModel {
   force?: boolean;
   schedule_interval: string;
   remove_scheduling? = false;
+  image_mirror_mode?: string;
 }
index cb199fe4af4aec57d9d7dcc855389e4aabef2c42..4c86ef15e27a4f2785cec128fea7a1e4fee91826 100644 (file)
           </div>
         </div>
 
+        <div class="form-group row">
+          <div class="cd-col-form-offset">
+            <!-- Mirroring -->
+            <div class="custom-control custom-checkbox">
+              <input type="checkbox"
+                     class="custom-control-input"
+                     id="mirroring"
+                     name="mirroring"
+                     (change)="setMirrorMode()"
+                     [(ngModel)]="mirroring && this.currentPoolName"
+                     formControlName="mirroring">
+              <label class="custom-control-label"
+                     for="mirroring">Mirroring</label>
+              <cd-help-text>Allow data to be asynchronously mirrored between two Ceph clusters</cd-help-text>
+              <cd-alert-panel *ngIf="showMirrorDisableMessage"
+                              [showTitle]="false"
+                              type="info">Mirroring can not be disabled on <b>Pool</b> mirror mode.
+                                          You need to change the mirror mode to enable this option.
+              </cd-alert-panel>
+              <cd-alert-panel *ngIf="currentPoolMirrorMode === 'disabled'"
+                              type="info"
+                              [showTitle]="false"
+                              i18n>You need to set <b>mirror mode</b> in the selected pool to enable mirroring.
+                <button class="btn btn-light"
+                        type="button"
+                        [routerLink]="['/block/mirroring', {outlets: {modal: ['edit', rbdForm.getValue('pool')]}}]">Set Mode</button>
+              </cd-alert-panel>
+            </div>
+            <div *ngIf="mirroring && currentPoolMirrorMode !== 'disabled'">
+              <div class="custom-control custom-radio ms-2"
+                   *ngFor="let option of mirroringOptions">
+                <input type="radio"
+                       class="form-check-input"
+                       [id]="option.value"
+                       [value]="option.value"
+                       name="mirroringMode"
+                       (change)="setExclusiveLock()"
+                       formControlName="mirroringMode"
+                       [attr.disabled]="shouldDisable(option.value)">
+                <label class="form-check-label"
+                       [for]="option.value">{{ option.value | titlecase }}</label>
+                <cd-help-text> {{ option.text}} </cd-help-text>
+                <cd-alert-panel *ngIf="shouldDisable(option.value) && mode !== 'editing'"
+                                type="info"
+                                [showTitle]="false"
+                                i18n>You need to set mode as <b>Image</b> in the selected pool to enable snapshot mirroring.
+                  <button class="btn btn-light mx-2"
+                          type="button"
+                          [routerLink]="['/block/mirroring', {outlets: {modal: ['edit', rbdForm.getValue('pool')]}}]">Set Mode</button>
+                </cd-alert-panel>
+              </div>
+            </div><br>
+            <div class="form-group row"
+                 *ngIf="rbdForm.getValue('mirroringMode') === 'snapshot' && mirroring">
+              <label class="cd-col-form-label required"
+                     [ngClass]="{'required': mode !== 'editing'}"
+                     i18n>Schedule Interval</label>
+              <div class="cd-col-form-input">
+                <input id="schedule"
+                       name="schedule"
+                       class="form-control"
+                       type="text"
+                       formControlName="schedule"
+                       i18n-placeholder
+                       placeholder="12h or 1d or 10m"
+                       [attr.disabled]="(peerConfigured === false) ? true : null">
+                <cd-help-text>
+                  <span i18n>Specify the interval to create mirror snapshots automatically. The interval can be specified in days, hours, or minutes using d, h, m suffix respectively</span>
+                </cd-help-text>
+                <span *ngIf="rbdForm.showError('schedule', formDir, 'required')"
+                      class="invalid-feedback"
+                      i18n>This field is required.</span>
+              </div>
+            </div>
+            <!-- Use a dedicated pool -->
+            <div class="custom-control custom-checkbox"
+                 *ngIf="allDataPools.length > 1 || mode === 'editing'">
+              <input type="checkbox"
+                     class="custom-control-input"
+                     id="useDataPool"
+                     name="useDataPool"
+                     formControlName="useDataPool"
+                     (change)="onUseDataPoolChange()">
+              <label class="custom-control-label"
+                     for="useDataPool"
+                     i18n>Dedicated data pool</label>
+              <cd-help-text>Use a dedicated pool to store the mirror data. If not selected, the mirror data will be stored in the same pool as the image data.</cd-help-text>
+              <cd-helper *ngIf="allDataPools.length <= 1 && mode !== 'editing'">
+                <span i18n>You need more than one pool with the rbd application label use to use a dedicated data pool.</span>
+              </cd-helper>
+            </div>
+            <!-- Data Pool -->
+            <div class="form-group row"
+                 *ngIf="rbdForm.getValue('useDataPool')">
+              <div class="cd-col-form-input pt-2 ms-4">
+                <input class="form-control"
+                       type="text"
+                       placeholder="Data pool name..."
+                       id="dataPool"
+                       name="dataPool"
+                       formControlName="dataPool"
+                       *ngIf="mode === 'editing' || !poolPermission.read">
+                <select id="dataPool"
+                        name="dataPool"
+                        class="form-select"
+                        formControlName="dataPool"
+                        (change)="onDataPoolChange($event.target.value)"
+                        *ngIf="mode !== 'editing' && poolPermission.read">
+                  <option *ngIf="dataPools === null"
+                          [ngValue]="null"
+                          i18n>Loading...</option>
+                  <option *ngIf="dataPools !== null && dataPools.length === 0"
+                          [ngValue]="null"
+                          i18n>-- No data pools available --</option>
+                  <option *ngIf="dataPools !== null && dataPools.length > 0"
+                          [ngValue]="null">-- Select a data pool --
+                  </option>
+                  <option *ngFor="let dataPool of dataPools"
+                          [value]="dataPool.pool_name">{{ dataPool.pool_name }}</option>
+                </select>
+                <cd-help-text>Dedicated pool that stores the object-data of the RBD.</cd-help-text>
+                <span class="invalid-feedback"
+                      *ngIf="rbdForm.showError('dataPool', formDir, 'required')"
+                      i18n>This field is required.</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
         <!-- Namespace -->
         <div class="form-group row"
              *ngIf="mode !== 'editing' && rbdForm.getValue('pool') && namespaces === null">
               <option *ngFor="let namespace of namespaces"
                       [value]="namespace">{{ namespace }}</option>
             </select>
-          </div>
-        </div>
-
-        <!-- Use a dedicated pool -->
-        <div class="form-group row">
-          <div class="cd-col-form-offset">
-            <div class="custom-control custom-checkbox">
-              <input type="checkbox"
-                     class="custom-control-input"
-                     id="useDataPool"
-                     name="useDataPool"
-                     formControlName="useDataPool"
-                     (change)="onUseDataPoolChange()">
-              <label class="custom-control-label"
-                     for="useDataPool"
-                     i18n>Use a dedicated data pool</label>
-              <cd-helper *ngIf="allDataPools.length <= 1">
-                <span i18n>You need more than one pool with the rbd application label use to use a dedicated data pool.</span>
-              </cd-helper>
-            </div>
-          </div>
-        </div>
-
-        <!-- Data Pool -->
-        <div class="form-group row"
-             *ngIf="rbdForm.getValue('useDataPool')">
-          <label class="cd-col-form-label"
-                 for="dataPool">
-            <span [ngClass]="{'required': mode !== 'editing'}"
-                  i18n>Data pool</span>
-            <cd-helper i18n-html
-                       html="Dedicated pool that stores the object-data of the RBD.">
-            </cd-helper>
-          </label>
-          <div class="cd-col-form-input">
-            <input class="form-control"
-                   type="text"
-                   placeholder="Data pool name..."
-                   id="dataPool"
-                   name="dataPool"
-                   formControlName="dataPool"
-                   *ngIf="mode === 'editing' || !poolPermission.read">
-            <select id="dataPool"
-                    name="dataPool"
-                    class="form-select"
-                    formControlName="dataPool"
-                    (change)="onDataPoolChange($event.target.value)"
-                    *ngIf="mode !== 'editing' && poolPermission.read">
-              <option *ngIf="dataPools === null"
-                      [ngValue]="null"
-                      i18n>Loading...</option>
-              <option *ngIf="dataPools !== null && dataPools.length === 0"
-                      [ngValue]="null"
-                      i18n>-- No data pools available --</option>
-              <option *ngIf="dataPools !== null && dataPools.length > 0"
-                      [ngValue]="null">-- Select a data pool --
-              </option>
-              <option *ngFor="let dataPool of dataPools"
-                      [value]="dataPool.pool_name">{{ dataPool.pool_name }}</option>
-            </select>
-            <span class="invalid-feedback"
-                  *ngIf="rbdForm.showError('dataPool', formDir, 'required')"
-                  i18n>This field is required.</span>
+            <cd-help-text>Namespace allows you to logically group RBD images within your Ceph Cluster.
+              Choosing a namespace makes it easier to locate and manage related RBD images efficiently</cd-help-text>
           </div>
         </div>
 
                    type="text"
                    formControlName="size"
                    i18n-placeholder
-                   placeholder="e.g., 10GiB"
+                   placeholder="10 GiB"
                    defaultUnit="GiB"
                    cdDimlessBinary>
             <span class="invalid-feedback"
             <span *ngIf="rbdForm.showError('size', formDir, 'pattern')"
                   class="invalid-feedback"
                   i18n>Size must be a number or in a valid format. eg: 5 GiB</span>
-          </div>
-        </div>
-
-        <!-- Mirroring -->
-        <div class="form-group row">
-          <div class="cd-col-form-offset">
-            <div class="custom-control custom-checkbox">
-              <input type="checkbox"
-                     class="custom-control-input"
-                     id="mirroring"
-                     name="mirroring"
-                     (change)="setMirrorMode()"
-                     formControlName="mirroring">
-              <label class="custom-control-label"
-                     for="mirroring">Mirroring</label>
-              <cd-helper *ngIf="mirroring === false && this.currentPoolName">
-                <span i18n>You need to enable a <b>mirror mode</b> in the selected pool. Please <a [routerLink]="['/block/mirroring', {outlets: {modal: ['edit', currentPoolName]}}]">click here to select a mode and enable it in this pool.</a></span>
-              </cd-helper>
-            </div>
-            <div *ngIf="mirroring">
-              <div class="custom-control custom-radio ms-2"
-                   *ngFor="let option of mirroringOptions">
-                <input type="radio"
-                       class="form-check-input"
-                       [id]="option"
-                       [value]="option"
-                       name="mirroringMode"
-                       (change)="setExclusiveLock()"
-                       formControlName="mirroringMode"
-                       [attr.disabled]="(poolMirrorMode === 'pool' && option === 'snapshot') ? true : null">
-                <label class="form-check-label"
-                       [for]="option">{{ option | titlecase }}</label>
-                <cd-helper *ngIf="poolMirrorMode === 'pool' && option === 'snapshot'">
-                  <span i18n>You need to enable <b>image mirror mode</b> in the selected pool. Please <a [routerLink]="['/block/mirroring', {outlets: {modal: ['edit', currentPoolName]}}]">click here to select a mode and enable it in this pool.</a></span>
-                </cd-helper>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <div class="form-group row"
-             *ngIf="rbdForm.getValue('mirroringMode') === 'snapshot' && mirroring">
-          <label class="cd-col-form-label"
-                 i18n>Schedule Interval
-          <cd-helper i18n-html
-                     html="Create Mirror-Snapshots automatically on a periodic basis. The interval can be specified in days, hours, or minutes using d, h, m suffix respectively. To create mirror snapshots, you must import or create and have available peers to mirror">
-          </cd-helper></label>
-          <div class="cd-col-form-input">
-            <input id="schedule"
-                   name="schedule"
-                   class="form-control"
-                   type="text"
-                   formControlName="schedule"
-                   i18n-placeholder
-                   placeholder="e.g., 12h or 1d or 10m"
-                   [attr.disabled]="(peerConfigured === false) ? true : null">
+            <cd-help-text>Supported Units: KiB, MiB, GiB, TiB, PiB etc</cd-help-text>
           </div>
         </div>
 
                        name="{{ feature.key }}"
                        formControlName="{{ feature.key }}">
                 <label class="custom-control-label"
-                       for="{{ feature.key }}">{{ feature.desc }}</label>
-                <cd-helper *ngIf="feature.helperHtml"
-                           html="{{ feature.helperHtml }}">
-                </cd-helper>
+                       for="{{ feature.key }}">{{ feature.desc }}</label><br>
+                <cd-help-text *ngIf="feature.helperText">
+                  {{ feature.helperText }}
+                </cd-help-text>
+                <cd-alert-panel type="warning"
+                                *ngIf="feature.helperHtml && rbdForm.getValue(feature.key) === false">
+                 {{ feature.helperHtml }}
+                </cd-alert-panel>
               </div>
             </div>
           </div>
index 7605348d406baf3c3a17958de49a22fc8f8538da..fbdebde67a7d79555c8163128b6de06d4e1326a9 100644 (file)
@@ -453,7 +453,7 @@ describe('RbdFormComponent', () => {
       });
 
       it('should verify only snapshot is disabled for pools that are in pool mirror mode', () => {
-        component.poolMirrorMode = 'pool';
+        component.currentPoolMirrorMode = 'pool';
         fixture.detectChanges();
         const journal = fixture.debugElement.query(By.css('#journal')).nativeElement;
         const snapshot = fixture.debugElement.query(By.css('#snapshot')).nativeElement;
index 33e67b09bbf76c2a3a7390f72caeaad5f629da80..1a8c7627b8578a5f28b313579ee92dfe6c549854 100644 (file)
@@ -33,6 +33,7 @@ import { RbdFormCreateRequestModel } from './rbd-form-create-request.model';
 import { RbdFormEditRequestModel } from './rbd-form-edit-request.model';
 import { RbdFormMode } from './rbd-form-mode.enum';
 import { RbdFormResponseModel } from './rbd-form-response.model';
+import { CdValidators } from '~/app/shared/forms/cd-validators';
 
 class ExternalData {
   rbd: RbdFormResponseModel;
@@ -79,10 +80,22 @@ export class RbdFormComponent extends CdForm implements OnInit {
 
   defaultObjectSize = '4 MiB';
 
-  mirroringOptions = ['journal', 'snapshot'];
+  mirroringOptions = [
+    {
+      value: 'journal',
+      text:
+        'Ensures reliable replication by logging changes before updating the image, but doubles write time, impacting performance. Not recommended for high-speed data processing tasks.'
+    },
+    {
+      value: 'snapshot',
+      text:
+        'This mode replicates RBD images between clusters using snapshots, efficiently copying data changes but requiring complete delta syncing during failover. Ideal for less demanding tasks due to its less granular approach compared to journaling.'
+    }
+  ];
   poolMirrorMode: string;
   mirroring = false;
   currentPoolName = '';
+  currentPoolMirrorMode = '';
 
   objectSizes: Array<string> = [
     '4 KiB',
@@ -111,6 +124,8 @@ export class RbdFormComponent extends CdForm implements OnInit {
   private routerUrl: string;
 
   icons = Icons;
+  currentImageMirrorMode = '';
+  showMirrorDisableMessage = false;
 
   constructor(
     private authStorageService: AuthStorageService,
@@ -134,27 +149,31 @@ export class RbdFormComponent extends CdForm implements OnInit {
         requires: null,
         allowEnable: false,
         allowDisable: true,
-        helperHtml: $localize`Feature can be disabled but can't be re-enabled later`
+        helperHtml: $localize`Feature can be disabled but can't be re-enabled later`,
+        helperText: $localize`Speeds up the process of deleting a clone by removing the dependency on the parent image.`
       },
       layering: {
         desc: $localize`Layering`,
         requires: null,
         allowEnable: false,
         allowDisable: false,
-        helperHtml: $localize`Feature flag can't be manipulated after the image is created. Disabling this option will also disable the Protect and Clone actions on Snapshot`
+        helperHtml: $localize`Feature flag can't be manipulated after the image is created. Disabling this option will also disable the Protect and Clone actions on Snapshot`,
+        helperText: $localize`Allows the creation of snapshots and clones of an image.`
       },
       'exclusive-lock': {
         desc: $localize`Exclusive lock`,
         requires: null,
         allowEnable: true,
-        allowDisable: true
+        allowDisable: true,
+        helperText: $localize`Ensures that only one client can write to the image at a time.`
       },
       'object-map': {
         desc: $localize`Object map (requires exclusive-lock)`,
         requires: 'exclusive-lock',
         allowEnable: true,
         allowDisable: true,
-        initDisabled: true
+        initDisabled: true,
+        helperText: $localize`Tracks which objects actually exist (have data stored on a device). Enabling object map support speeds up I/O operations for cloning, importing and exporting a sparsely populated image, and deleting.`
       },
       'fast-diff': {
         desc: $localize`Fast diff (interlocked with object-map)`,
@@ -162,7 +181,8 @@ export class RbdFormComponent extends CdForm implements OnInit {
         allowEnable: true,
         allowDisable: true,
         interlockedWith: 'object-map',
-        initDisabled: true
+        initDisabled: true,
+        helperText: $localize`Speeds up the process of comparing two images.`
       }
     };
     this.featuresList = this.objToArray(this.features);
@@ -196,9 +216,15 @@ export class RbdFormComponent extends CdForm implements OnInit {
             return acc;
           }, {})
         ),
-        mirroring: new UntypedFormControl(''),
+        mirroring: new UntypedFormControl(false),
         schedule: new UntypedFormControl('', {
-          validators: [Validators.pattern(/^([0-9]+)d|([0-9]+)h|([0-9]+)m$/)] // check schedule interval to be in format - 1d or 1h or 1m
+          validators: [
+            Validators.pattern(/^([0-9]+)d|([0-9]+)h|([0-9]+)m$/),
+            CdValidators.requiredIf({
+              mirroringMode: 'snapshot',
+              mirroring: true
+            })
+          ] // check schedule interval to be in format - 1d or 1h or 1m
         }),
         mirroringMode: new UntypedFormControl(''),
         stripingUnit: new UntypedFormControl(this.defaultStripingUnit),
@@ -256,14 +282,14 @@ export class RbdFormComponent extends CdForm implements OnInit {
       this.rbdForm.get('exclusive-lock').disable();
     } else {
       this.rbdForm.get('exclusive-lock').enable();
-      if (this.poolMirrorMode === 'pool') {
-        this.rbdForm.get('mirroringMode').setValue(this.mirroringOptions[0]);
-      }
     }
   }
 
   setMirrorMode() {
     this.mirroring = !this.mirroring;
+    if (this.mirroring) {
+      this.rbdForm.get('mirroringMode').setValue(this.mirroringOptions[0].value);
+    }
     this.setExclusiveLock();
     this.checkPeersConfigured();
   }
@@ -286,14 +312,34 @@ export class RbdFormComponent extends CdForm implements OnInit {
       this.rbdMirroringService.refresh();
       this.rbdMirroringService.subscribeSummary((data) => {
         const pool = data.content_data.pools.find((o: any) => o.name === this.currentPoolName);
-        this.poolMirrorMode = pool.mirror_mode;
-
-        if (pool.mirror_mode === 'disabled') {
-          this.mirroring = false;
-          this.rbdForm.get('mirroring').setValue(this.mirroring);
-          this.rbdForm.get('mirroring').disable();
+        this.currentPoolMirrorMode = pool.mirror_mode;
+        if (this.mode === this.rbdFormMode.editing) {
+          if (this.currentPoolMirrorMode === 'pool') {
+            this.showMirrorDisableMessage = true;
+          } else {
+            this.showMirrorDisableMessage = false;
+          }
+          if (this.currentPoolMirrorMode !== 'image') {
+            this.rbdForm.get('mirroring').disable();
+            this.rbdForm.get('mirroringMode').disable();
+          }
+        } else {
+          if (pool.mirror_mode === 'disabled') {
+            this.mirroring = false;
+            this.rbdForm.get('mirroring').setValue(this.mirroring);
+            this.rbdForm.get('mirroring').disable();
+          } else {
+            this.mirroring = true;
+            this.rbdForm.get('mirroring').enable();
+            this.rbdForm.get('mirroring').setValue(this.mirroring);
+            this.rbdForm.get('mirroringMode').setValue(this.mirroringOptions[0].value);
+          }
         }
       });
+    } else {
+      if (this.mode !== this.rbdFormMode.editing) {
+        this.rbdForm.get('mirroring').disable();
+      }
     }
     this.setExclusiveLock();
   }
@@ -390,8 +436,9 @@ export class RbdFormComponent extends CdForm implements OnInit {
     this.allPools = pools;
     this.dataPools = dataPools;
     this.allDataPools = dataPools;
-    if (this.pools.length === 1) {
-      const poolName = this.pools[0].pool_name;
+    if (this.pools.length >= 1) {
+      const allPoolNames = this.pools.map((pool) => pool.pool_name);
+      const poolName = allPoolNames.includes('rbd') ? 'rbd' : this.pools[0].pool_name;
       this.rbdForm.get('pool').setValue(poolName);
       this.onPoolChange(poolName);
     }
@@ -464,7 +511,7 @@ export class RbdFormComponent extends CdForm implements OnInit {
         sizeControlErrors = { required: true };
       } else {
         const sizeInBytes = formatter.toBytes(sizeControl.value);
-        if (stripingCount * objectSizeInBytes > sizeInBytes) {
+        if (stripingCount * objectSizeInBytes >= sizeInBytes) {
           sizeControlErrors = { invalidSizeObject: true };
         }
       }
@@ -616,6 +663,7 @@ export class RbdFormComponent extends CdForm implements OnInit {
         this.mirroring = true;
         this.rbdForm.get('mirroring').setValue(this.mirroring);
         this.rbdForm.get('mirroringMode').setValue(response?.mirror_mode);
+        this.currentImageMirrorMode = response?.mirror_mode;
         this.rbdForm.get('schedule').setValue(response?.schedule_interval);
       } else {
         this.mirroring = false;
@@ -651,12 +699,11 @@ export class RbdFormComponent extends CdForm implements OnInit {
     request.name = this.rbdForm.getValue('name');
     request.schedule_interval = this.rbdForm.getValue('schedule');
     request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
-
-    if (this.poolMirrorMode === 'image') {
-      request.mirror_mode = this.rbdForm.getValue('mirroringMode');
-    }
     this.addObjectSizeAndStripingToRequest(request);
     request.configuration = this.getDirtyConfigurationValues();
+    if (this.mirroring && this.currentPoolMirrorMode === 'image') {
+      request.mirror_mode = this.rbdForm.getValue('mirroringMode');
+    }
     return request;
   }
 
@@ -688,7 +735,8 @@ export class RbdFormComponent extends CdForm implements OnInit {
         namespace: request.namespace,
         image_name: request.name,
         schedule_interval: request.schedule_interval,
-        start_time: request.start_time
+        start_time: request.start_time,
+        mirror_mode: request.mirror_mode
       }),
       call: this.rbdService.create(request)
     });
@@ -698,19 +746,20 @@ export class RbdFormComponent extends CdForm implements OnInit {
     const request = new RbdFormEditRequestModel();
     request.name = this.rbdForm.getValue('name');
     request.schedule_interval = this.rbdForm.getValue('schedule');
-    request.name = this.rbdForm.getValue('name');
+    request.enable_mirror = this.mirroring;
     request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
     _.forIn(this.features, (feature) => {
       if (this.rbdForm.getValue(feature.key)) {
         request.features.push(feature.key);
       }
     });
-    request.enable_mirror = this.rbdForm.getValue('mirroring');
     if (request.enable_mirror) {
+      request.image_mirror_mode = this.currentImageMirrorMode;
       if (this.rbdForm.getValue('mirroringMode') === 'journal') {
+        request.mirror_mode = 'journal';
         request.features.push('journaling');
       }
-      if (this.poolMirrorMode === 'image') {
+      if (this.currentPoolMirrorMode === 'image') {
         request.mirror_mode = this.rbdForm.getValue('mirroringMode');
       }
     } else {
@@ -803,6 +852,10 @@ export class RbdFormComponent extends CdForm implements OnInit {
     });
   }
 
+  shouldDisable(option: string): boolean {
+    return this.currentPoolMirrorMode === 'pool' && option === 'snapshot' ? true : null;
+  }
+
   submit() {
     if (!this.mode) {
       this.rbdImage.next('create');
index 806829033d02a25d6983493b2028232d277a8938..095e895b526a913234a5c591be6d3aadf80253d8 100644 (file)
@@ -594,6 +594,8 @@ paths:
                 force:
                   default: false
                   type: boolean
+                image_mirror_mode:
+                  type: string
                 metadata:
                   type: string
                 mirror_mode:
index ec65b1fd5e11925bd8c42b38de903b516c0a7454..31fdb7c9818e3f684eb8c0c39b84cb22f8541276 100644 (file)
@@ -559,8 +559,8 @@ class RbdService(object):
     @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE)
     def set(cls, image_spec, name=None, size=None, features=None,
             configuration=None, metadata=None, enable_mirror=None, primary=None,
-            force=False, resync=False, mirror_mode=None, schedule_interval='',
-            remove_scheduling=False):
+            force=False, resync=False, mirror_mode=None, image_mirror_mode=None,
+            schedule_interval='', remove_scheduling=False):
         # pylint: disable=too-many-branches
         pool_name, namespace, image_name = parse_image_spec(image_spec)
 
@@ -574,15 +574,22 @@ class RbdService(object):
             if size and size != image.size():
                 image.resize(size)
 
+            if image_mirror_mode is not None and mirror_mode is not None:
+                if image_mirror_mode != mirror_mode:
+                    RbdMirroringService.disable_image(image_name, pool_name, namespace)
+
             mirror_image_info = image.mirror_image_get_info()
-            if enable_mirror and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_DISABLED:
+            if (enable_mirror is True
+                    and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_DISABLED):
                 RbdMirroringService.enable_image(
                     image_name, pool_name, namespace,
-                    MIRROR_IMAGE_MODE[mirror_mode])
+                    MIRROR_IMAGE_MODE[mirror_mode]
+                )
             elif (enable_mirror is False
-                  and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED):
+                    and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED):
                 RbdMirroringService.disable_image(
-                    image_name, pool_name, namespace)
+                    image_name, pool_name, namespace
+                )
 
             # check enable/disable features
             if features is not None: