]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix rbd mirror snapshot creation 51076/head
authorAashish Sharma <aasharma@redhat.com>
Wed, 21 Dec 2022 11:53:37 +0000 (17:23 +0530)
committerAashish Sharma <aasharma@redhat.com>
Fri, 14 Apr 2023 07:12:57 +0000 (12:42 +0530)
There are two types of snapshots that can be created on a snapshot based mirroring image - Normal Snapshot(same as journal based snapshot) and Nirror Image Snapshot. Till now Dashboard allowed only Mirror image snapshot, this PR intends to enable both the types

Signed-off-by: Aashish Sharma <aasharma@redhat.com>
(cherry picked from commit 5ea4171ae3d2a28efbe20b825602eff1429ef92d)

qa/tasks/mgr/dashboard/test_rbd.py
src/pybind/mgr/dashboard/controllers/rbd.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-form/rbd-snapshot-form-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-form/rbd-snapshot-form-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts
src/pybind/mgr/dashboard/openapi.yaml

index 997d10f2a801e0883ad14c8d76d50ced1b144e0e..633a5151cfddc90b7e202d7e39377b29628c4567 100644 (file)
@@ -24,7 +24,7 @@ class RbdTest(DashboardTestCase):
     def test_create_access_permissions(self):
         self.create_image('pool', None, 'name', 0)
         self.assertStatus(403)
-        self.create_snapshot('pool', None, 'image', 'snapshot')
+        self.create_snapshot('pool', None, 'image', 'snapshot', False)
         self.assertStatus(403)
         self.copy_image('src_pool', None, 'src_image', 'dest_pool', None, 'dest_image')
         self.assertStatus(403)
@@ -110,10 +110,10 @@ class RbdTest(DashboardTestCase):
         return cls._task_post('/api/block/image/{}%2F{}{}/flatten'.format(pool, namespace, image))
 
     @classmethod
-    def create_snapshot(cls, pool, namespace, image, snapshot):
+    def create_snapshot(cls, pool, namespace, image, snapshot, mirrorImageSnapshot):
         namespace = '{}%2F'.format(namespace) if namespace else ''
         return cls._task_post('/api/block/image/{}%2F{}{}/snap'.format(pool, namespace, image),
-                              {'snapshot_name': snapshot})
+                              {'snapshot_name': snapshot, 'mirrorImageSnapshot': mirrorImageSnapshot})  # noqa E501 #pylint: disable=line-too-long
 
     @classmethod
     def remove_snapshot(cls, pool, namespace, image, snapshot):
@@ -404,8 +404,8 @@ class RbdTest(DashboardTestCase):
         self.assertStatus(204)
 
     def test_snapshots_and_clone_info(self):
-        self.create_snapshot('rbd', None, 'img1', 'snap1')
-        self.create_snapshot('rbd', None, 'img1', 'snap2')
+        self.create_snapshot('rbd', None, 'img1', 'snap1', False)
+        self.create_snapshot('rbd', None, 'img1', 'snap2', False)
         self._rbd_cmd(['snap', 'protect', 'rbd/img1@snap1'])
         self._rbd_cmd(['clone', 'rbd/img1@snap1', 'rbd_iscsi/img1_clone'])
 
@@ -440,11 +440,11 @@ class RbdTest(DashboardTestCase):
 
     def test_disk_usage(self):
         self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '50M', 'rbd/img2'])
-        self.create_snapshot('rbd', None, 'img2', 'snap1')
+        self.create_snapshot('rbd', None, 'img2', 'snap1', False)
         self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '20M', 'rbd/img2'])
-        self.create_snapshot('rbd', None, 'img2', 'snap2')
+        self.create_snapshot('rbd', None, 'img2', 'snap2', False)
         self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '10M', 'rbd/img2'])
-        self.create_snapshot('rbd', None, 'img2', 'snap3')
+        self.create_snapshot('rbd', None, 'img2', 'snap3', False)
         self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M', 'rbd/img2'])
         img = self.get_image('rbd', None, 'img2')
         self.assertStatus(200)
@@ -462,9 +462,9 @@ class RbdTest(DashboardTestCase):
     def test_image_delete(self):
         self.create_image('rbd', None, 'delete_me', 2**30)
         self.assertStatus(201)
-        self.create_snapshot('rbd', None, 'delete_me', 'snap1')
+        self.create_snapshot('rbd', None, 'delete_me', 'snap1', False)
         self.assertStatus(201)
-        self.create_snapshot('rbd', None, 'delete_me', 'snap2')
+        self.create_snapshot('rbd', None, 'delete_me', 'snap2', False)
         self.assertStatus(201)
 
         img = self.get_image('rbd', None, 'delete_me')
@@ -488,9 +488,9 @@ class RbdTest(DashboardTestCase):
     def test_image_delete_with_snapshot(self):
         self.create_image('rbd', None, 'delete_me', 2**30)
         self.assertStatus(201)
-        self.create_snapshot('rbd', None, 'delete_me', 'snap1')
+        self.create_snapshot('rbd', None, 'delete_me', 'snap1', False)
         self.assertStatus(201)
-        self.create_snapshot('rbd', None, 'delete_me', 'snap2')
+        self.create_snapshot('rbd', None, 'delete_me', 'snap2', False)
         self.assertStatus(201)
 
         img = self.get_image('rbd', None, 'delete_me')
@@ -605,7 +605,7 @@ class RbdTest(DashboardTestCase):
         self.assertStatus(204)
 
     def test_update_snapshot(self):
-        self.create_snapshot('rbd', None, 'img1', 'snap5')
+        self.create_snapshot('rbd', None, 'img1', 'snap5', False)
         self.assertStatus(201)
         img = self.get_image('rbd', None, 'img1')
         self._validate_snapshot_list(img['snapshots'], 'snap5', is_protected=False)
@@ -633,7 +633,7 @@ class RbdTest(DashboardTestCase):
                           features=["layering", "exclusive-lock", "fast-diff",
                                     "object-map"])
         self.assertStatus(201)
-        self.create_snapshot('rbd', None, 'rollback_img', 'snap1')
+        self.create_snapshot('rbd', None, 'rollback_img', 'snap1', False)
         self.assertStatus(201)
 
         img = self.get_image('rbd', None, 'rollback_img')
@@ -662,7 +662,7 @@ class RbdTest(DashboardTestCase):
     def test_clone(self):
         self.create_image('rbd', None, 'cimg', 2**30, features=["layering"])
         self.assertStatus(201)
-        self.create_snapshot('rbd', None, 'cimg', 'snap1')
+        self.create_snapshot('rbd', None, 'cimg', 'snap1', False)
         self.assertStatus(201)
         self.update_snapshot('rbd', None, 'cimg', 'snap1', None, True)
         self.assertStatus(200)
@@ -720,7 +720,7 @@ class RbdTest(DashboardTestCase):
         self.assertStatus(204)
 
     def test_flatten(self):
-        self.create_snapshot('rbd', None, 'img1', 'snapf')
+        self.create_snapshot('rbd', None, 'img1', 'snapf', False)
         self.update_snapshot('rbd', None, 'img1', 'snapf', None, True)
         self.clone_image('rbd', None, 'img1', 'snapf', 'rbd_iscsi', None, 'img1_snapf_clone')
 
index 01e5b58c11bd372879344432387e88085a7a2692..eef3be4975a1e8f063c8996621f067c6743d751f 100644 (file)
@@ -336,15 +336,14 @@ class RbdSnapshot(RESTController):
     RESOURCE_ID = "snapshot_name"
 
     @RbdTask('snap/create',
-             ['{image_spec}', '{snapshot_name}'], 2.0)
-    def create(self, image_spec, snapshot_name):
+             ['{image_spec}', '{snapshot_name}', '{mirrorImageSnapshot}'], 2.0)
+    def create(self, image_spec, snapshot_name, mirrorImageSnapshot):
         pool_name, namespace, image_name = parse_image_spec(image_spec)
 
         def _create_snapshot(ioctx, img, snapshot_name):
             mirror_info = img.mirror_image_get_info()
             mirror_mode = img.mirror_image_get_mode()
-            if (mirror_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED
-                    and mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_SNAPSHOT):
+            if (mirror_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED and mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_SNAPSHOT) and mirrorImageSnapshot:  # noqa E501 #pylint: disable=line-too-long
                 img.mirror_image_create_snapshot()
             else:
                 img.create_snap(snapshot_name)
index 598e3fd3843ecf27d6e1b9edc44e27c61cbc99f8..fb246a44451b10849378586cb82c5f17114db0fe 100644 (file)
                    placeholder="Snapshot name..."
                    id="snapshotName"
                    name="snapshotName"
-                   [attr.disabled]="(mirroring === 'snapshot') ? true : null"
+                   [attr.disabled]="((mirroring === 'snapshot') ? true : null) && (snapshotForm.getValue('mirrorImageSnapshot') === true) ? true: null"
                    formControlName="snapshotName"
                    autofocus>
             <span class="invalid-feedback"
                   *ngIf="snapshotForm.showError('snapshotName', formDir, 'required')"
                   i18n>This field is required.</span><br><br>
-            <span *ngIf="mirroring === 'snapshot'"
+            <span *ngIf="((mirroring === 'snapshot') ? true : null) && (snapshotForm.getValue('mirrorImageSnapshot') === true) ? true: null"
                   i18n>Snapshot mode is enabled on image <b>{{ imageName }}</b>: snapshot names are auto generated</span>
           </div>
         </div>
+        <div *ngIf="(mirroring === 'snapshot') ? true : null">
+          <div class="form-group row">
+            <div class="cd-col-form-offset">
+              <div class="custom-control custom-checkbox">
+                <input type="checkbox"
+                       class="custom-control-input"
+                       formControlName="mirrorImageSnapshot"
+                       name="mirrorImageSnapshot"
+                       id="mirrorImageSnapshot"
+                       (change)="onMirrorCheckBoxChange()">
+                <label for="mirrorImageSnapshot"
+                       class="custom-control-label"
+                       i18n>Mirror Image Snapshot</label>
+              </div>
+            </div>
+          </div>
+        </div>
       </div>
-
       <div class="modal-footer">
         <cd-form-button-panel (submitActionEvent)="submit()"
                               [form]="snapshotForm"
index d5861163f198b836cbb4221bb91075248c929a0e..9fc5179e23b54932aba32aa85a1baac23c545b1b 100644 (file)
@@ -48,7 +48,8 @@ export class RbdSnapshotFormModalComponent {
     this.snapshotForm = new CdFormGroup({
       snapshotName: new FormControl('', {
         validators: [Validators.required]
-      })
+      }),
+      mirrorImageSnapshot: new FormControl(false, {})
     });
   }
 
@@ -61,6 +62,12 @@ export class RbdSnapshotFormModalComponent {
     }
   }
 
+  onMirrorCheckBoxChange() {
+    if (this.snapshotForm.getValue('mirrorImageSnapshot') === true) {
+      this.snapshotForm.get('snapshotName').setValue('');
+    }
+  }
+
   /**
    * Set the 'editing' flag. If set to TRUE, the modal dialog is in
    * 'Edit' mode, otherwise in 'Create' mode.
@@ -101,6 +108,7 @@ export class RbdSnapshotFormModalComponent {
 
   createAction() {
     const snapshotName = this.snapshotForm.getValue('snapshotName');
+    const mirrorImageSnapshot = this.snapshotForm.getValue('mirrorImageSnapshot');
     const imageSpec = new ImageSpec(this.poolName, this.namespace, this.imageName);
     const finishedTask = new FinishedTask();
     finishedTask.name = 'rbd/snap/create';
@@ -109,7 +117,7 @@ export class RbdSnapshotFormModalComponent {
       snapshot_name: snapshotName
     };
     this.rbdService
-      .createSnapshot(imageSpec, snapshotName)
+      .createSnapshot(imageSpec, snapshotName, mirrorImageSnapshot)
       .toPromise()
       .then(() => {
         this.taskManagerService.subscribe(
index 84abf6d346188559a24a563c340a020889018d56..5bafc9e63d37119da657b6a40fc67b05b182d8c6 100644 (file)
@@ -90,10 +90,13 @@ describe('RbdService', () => {
   });
 
   it('should call createSnapshot', () => {
-    service.createSnapshot(new ImageSpec('poolName', null, 'rbdName'), 'snapshotName').subscribe();
+    service
+      .createSnapshot(new ImageSpec('poolName', null, 'rbdName'), 'snapshotName', false)
+      .subscribe();
     const req = httpTesting.expectOne('api/block/image/poolName%2FrbdName/snap');
     expect(req.request.body).toEqual({
-      snapshot_name: 'snapshotName'
+      snapshot_name: 'snapshotName',
+      mirrorImageSnapshot: false
     });
     expect(req.request.method).toBe('POST');
   });
index 555f0db0f4f8009335f3392fd5d2f271ccb0e4d0..0ffa8fcff1c8eb31a8e37f8d3524f3430a599fc3 100644 (file)
@@ -89,9 +89,14 @@ export class RbdService extends ApiClient {
     return this.http.get<number>('api/block/image/clone_format_version');
   }
 
-  createSnapshot(imageSpec: ImageSpec, @cdEncodeNot snapshotName: string) {
+  createSnapshot(
+    imageSpec: ImageSpec,
+    @cdEncodeNot snapshotName: string,
+    mirrorImageSnapshot: boolean
+  ) {
     const request = {
-      snapshot_name: snapshotName
+      snapshot_name: snapshotName,
+      mirrorImageSnapshot: mirrorImageSnapshot
     };
     return this.http.post(`api/block/image/${imageSpec.toStringEncoded()}/snap`, request, {
       observe: 'response'
index 917963128955c894d16a00ddaa8c8036873b9ed5..13f2fb4d32b1df1a7025dc67c14f132583c37918 100644 (file)
@@ -762,10 +762,13 @@ paths:
           application/json:
             schema:
               properties:
+                mirrorImageSnapshot:
+                  type: string
                 snapshot_name:
                   type: string
               required:
               - snapshot_name
+              - mirrorImageSnapshot
               type: object
       responses:
         '201':