mirror of
https://github.com/ceph/ceph
synced 2025-03-11 02:39:05 +00:00
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:
parent
5168ccce49
commit
5ea4171ae3
@ -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')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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');
|
||||
});
|
||||
|
@ -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'
|
||||
|
@ -765,10 +765,13 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
mirrorImageSnapshot:
|
||||
type: string
|
||||
snapshot_name:
|
||||
type: string
|
||||
required:
|
||||
- snapshot_name
|
||||
- mirrorImageSnapshot
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
Loading…
Reference in New Issue
Block a user