mgr/dashboard: fix rbd mirror snapshot creation

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>
This commit is contained in:
Aashish Sharma 2022-12-21 17:23:37 +05:30
parent 5168ccce49
commit 5ea4171ae3
7 changed files with 63 additions and 29 deletions

View 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):
@ -426,8 +426,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'])
@ -462,11 +462,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)
@ -484,9 +484,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')
@ -510,9 +510,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')
@ -668,7 +668,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)
@ -696,7 +696,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')
@ -726,7 +726,7 @@ class RbdTest(DashboardTestCase):
self.create_image('rbd', None, 'cimg', 2**30, features=["layering"],
metadata={'key1': 'val1'})
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)
@ -790,7 +790,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')

View File

@ -351,15 +351,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)

View File

@ -19,18 +19,34 @@
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"

View 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(

View 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');
});

View 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'

View File

@ -765,10 +765,13 @@ paths:
application/json:
schema:
properties:
mirrorImageSnapshot:
type: string
snapshot_name:
type: string
required:
- snapshot_name
- mirrorImageSnapshot
type: object
responses:
'201':