diff mbox

[2/3] qapi: Introduce blockdev-group-snapshot-sync command

Message ID 82683103d1b30237a58f760ee068f98e0a9b4dd1.1329758006.git.jcody@redhat.com
State New
Headers show

Commit Message

Jeff Cody Feb. 20, 2012, 5:31 p.m. UTC
This is a QAPI/QMP only command to take a snapshot of a group of
devices. This is simlar to the blockdev-snapshot-sync command, except
blockdev-group-snapshot-sync accepts a list devices, filenames, and
formats.

It is attempted to keep the snapshot of the group atomic; if
any snapshot of a device in a given group fails, then the whole group
is reverted back to its original image, and error is reported.

This allows the group of disks to remain consistent with each other,
even across snapshot failures.

Signed-off-by: Jeff Cody <jcody@redhat.com>
---
 blockdev.c       |  130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 qapi-schema.json |   45 +++++++++++++++++++
 qmp-commands.hx  |   39 ++++++++++++++++
 3 files changed, 214 insertions(+), 0 deletions(-)

Comments

Eric Blake Feb. 20, 2012, 5:41 p.m. UTC | #1
On 02/20/2012 10:31 AM, Jeff Cody wrote:
> This is a QAPI/QMP only command to take a snapshot of a group of
> devices. This is simlar to the blockdev-snapshot-sync command, except

s/simlar/similar/

> blockdev-group-snapshot-sync accepts a list devices, filenames, and
> formats.
> 
> It is attempted to keep the snapshot of the group atomic; if
> any snapshot of a device in a given group fails, then the whole group
> is reverted back to its original image, and error is reported.
> 
> This allows the group of disks to remain consistent with each other,
> even across snapshot failures.
> 
> Signed-off-by: Jeff Cody <jcody@redhat.com>
> ---
>  blockdev.c       |  130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  qapi-schema.json |   45 +++++++++++++++++++
>  qmp-commands.hx  |   39 ++++++++++++++++
>  3 files changed, 214 insertions(+), 0 deletions(-)

> +
> +error_rollback:
> +    /* failure, undo everything as much as we can */
> +    QSIMPLEQ_FOREACH(snap_entry, &gsnp_list, entry) {
> +        if (snap_entry->has_pivoted) {
> +            ret = bdrv_open(snap_entry->bs, snap_entry->old_filename,
> +                            snap_entry->flags, snap_entry->old_drv);
> +            if (ret != 0) {
> +                /* This is very very bad */
> +                error_set(errp, QERR_OPEN_FILE_FAILED,
> +                          snap_entry->old_filename);

Is there any way to reduce the likelihood of a rollback failure?


> +SQMP
> +blockdev-group-snapshot-sync
> +----------------------
> +
> +Synchronous snapshot of one or more block devices.  A list array input
> +is accepted, that contains the device, snapshot-file to be create as the
> +target of the new image. If the file exists, or if it is a device, the
> +snapshot will be created in the existing file/device. If does not
> +exist, a new file will be created. format specifies the format of the
> +snapshot image, default is qcow2.  On failure of any device, it is
> +attempted to reopen the original image for all the devices that were
> +specified.
> +
> +Arguments:
> +
> +- "device": device name to snapshot (json-string)
> +- "snapshot-file": name of new image file (json-string)
> +- "format": format of new image (json-string, optional)

Shouldn't this mention that the arguments is a JSON list, rather than a
single argument?

> +
> +Example:
> +
> +-> { "execute": "blockdev-group-snapshot-sync", "arguments": [{ "device": "ide-hd0",
> +                                                                "snapshot-file":
> +                                                                "/some/place/my-image",
> +                                                                "format": "qcow2" },
> +                                                              { "device": "ide-hd1",
> +                                                                "snapshot-file":
> +                                                                "/some/place/my-image2",
> +                                                                "format": "qcow2" } }

Are you missing a ']' before the final '}' here?
Jeff Cody Feb. 21, 2012, 12:52 p.m. UTC | #2
On 02/20/2012 12:41 PM, Eric Blake wrote:
> On 02/20/2012 10:31 AM, Jeff Cody wrote:
>> This is a QAPI/QMP only command to take a snapshot of a group of
>> devices. This is simlar to the blockdev-snapshot-sync command, except
>
> s/simlar/similar/
>

Oops - fixed for v2.


>> blockdev-group-snapshot-sync accepts a list devices, filenames, and
>> formats.
>>
>> It is attempted to keep the snapshot of the group atomic; if
>> any snapshot of a device in a given group fails, then the whole group
>> is reverted back to its original image, and error is reported.
>>
>> This allows the group of disks to remain consistent with each other,
>> even across snapshot failures.
>>
>> Signed-off-by: Jeff Cody<jcody@redhat.com>
>> ---
>>   blockdev.c       |  130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   qapi-schema.json |   45 +++++++++++++++++++
>>   qmp-commands.hx  |   39 ++++++++++++++++
>>   3 files changed, 214 insertions(+), 0 deletions(-)
>
>> +
>> +error_rollback:
>> +    /* failure, undo everything as much as we can */
>> +    QSIMPLEQ_FOREACH(snap_entry,&gsnp_list, entry) {
>> +        if (snap_entry->has_pivoted) {
>> +            ret = bdrv_open(snap_entry->bs, snap_entry->old_filename,
>> +                            snap_entry->flags, snap_entry->old_drv);
>> +            if (ret != 0) {
>> +                /* This is very very bad */
>> +                error_set(errp, QERR_OPEN_FILE_FAILED,
>> +                          snap_entry->old_filename);
>
> Is there any way to reduce the likelihood of a rollback failure?

Good question.  Obviously, it is ideal for this to have the lowest 
possible likelihood of a rollback failure.  I guess the real question 
is: What are the events that would cause the original image re-open to 
fail, and how can we avoid them - or, as Kevin mentioned to me, do we 
never close the original file to avoid the re-open (and, will that avoid 
those failure events)?  Hopefully these are rare events, in any case.

>
>
>> +SQMP
>> +blockdev-group-snapshot-sync
>> +----------------------
>> +
>> +Synchronous snapshot of one or more block devices.  A list array input
>> +is accepted, that contains the device, snapshot-file to be create as the
>> +target of the new image. If the file exists, or if it is a device, the
>> +snapshot will be created in the existing file/device. If does not
>> +exist, a new file will be created. format specifies the format of the
>> +snapshot image, default is qcow2.  On failure of any device, it is
>> +attempted to reopen the original image for all the devices that were
>> +specified.
>> +
>> +Arguments:
>> +
>> +- "device": device name to snapshot (json-string)
>> +- "snapshot-file": name of new image file (json-string)
>> +- "format": format of new image (json-string, optional)
>
> Shouldn't this mention that the arguments is a JSON list, rather than a
> single argument?

Yes, you are right - adding that in.

>
>> +
>> +Example:
>> +
>> +->  { "execute": "blockdev-group-snapshot-sync", "arguments": [{ "device": "ide-hd0",
>> +                                                                "snapshot-file":
>> +                                                                "/some/place/my-image",
>> +                                                                "format": "qcow2" },
>> +                                                              { "device": "ide-hd1",
>> +                                                                "snapshot-file":
>> +                                                                "/some/place/my-image2",
>> +                                                                "format": "qcow2" } }
>
> Are you missing a ']' before the final '}' here?
>

Yes, thanks.  Forgot that in the example.  Added for v2.

Jeff
diff mbox

Patch

diff --git a/blockdev.c b/blockdev.c
index 05e7c5e..0149720 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -714,6 +714,136 @@  void qmp_blockdev_snapshot_sync(const char *device, const char *snapshot_file,
     }
 }
 
+
+typedef struct BlkGroupSnapshotData {
+    BlockDriverState *bs;
+    BlockDriver *drv;
+    BlockDriver *old_drv;
+    int flags;
+    const char *format;
+    char old_filename[1024];
+    const char *snapshot_file;
+    bool has_pivoted;
+    QSIMPLEQ_ENTRY(BlkGroupSnapshotData) entry;
+} BlkGroupSnapshotData;
+
+/*
+ * 'Atomic' group snapshots.  The snapshots are taken as a set, and if any fail
+ *  then we attempt to undo all of the pivots performed.
+ */
+void qmp_blockdev_group_snapshot_sync(SnapshotDevList *dev_list,
+                                                   Error **errp)
+{
+    int ret = 0;
+    SnapshotDevList *dev_entry = dev_list;
+    SnapshotDev *dev_info = NULL;
+    BlkGroupSnapshotData *snap_entry;
+    BlockDriver *proto_drv;
+
+    QSIMPLEQ_HEAD(gsnp_list, BlkGroupSnapshotData) gsnp_list;
+    QSIMPLEQ_INIT(&gsnp_list);
+
+    /* We don't do anything in this loop that commits us to the snapshot */
+    while (NULL != dev_entry) {
+        dev_info = dev_entry->value;
+        dev_entry = dev_entry->next;
+
+        snap_entry = g_malloc0(sizeof(BlkGroupSnapshotData));
+
+        snap_entry->bs = bdrv_find(dev_info->device);
+
+        if (!snap_entry->bs) {
+            error_set(errp, QERR_DEVICE_NOT_FOUND, dev_info->device);
+            goto exit;
+        }
+        if (bdrv_in_use(snap_entry->bs)) {
+            error_set(errp, QERR_DEVICE_IN_USE, dev_info->device);
+            goto exit;
+        }
+
+        pstrcpy(snap_entry->old_filename, sizeof(snap_entry->old_filename),
+                snap_entry->bs->filename);
+
+        snap_entry->snapshot_file = dev_info->snapshot_file;
+        snap_entry->old_drv = snap_entry->bs->drv;
+        snap_entry->flags = snap_entry->bs->open_flags;
+
+        if (!dev_info->has_format) {
+            snap_entry->format = "qcow2";
+        } else {
+            snap_entry->format = dev_info->format;
+        }
+
+        snap_entry->drv = bdrv_find_format(snap_entry->format);
+        if (!snap_entry->drv) {
+            error_set(errp, QERR_INVALID_BLOCK_FORMAT, snap_entry->format);
+            goto exit;
+        }
+
+        proto_drv = bdrv_find_protocol(snap_entry->snapshot_file);
+        if (!proto_drv) {
+            error_set(errp, QERR_INVALID_BLOCK_FORMAT, snap_entry->format);
+            goto exit;
+        }
+        ret = bdrv_img_create(snap_entry->snapshot_file, snap_entry->format,
+                              snap_entry->bs->filename,
+                              snap_entry->bs->drv->format_name, NULL, -1,
+                              snap_entry->flags);
+        if (ret) {
+            error_set(errp, QERR_UNDEFINED_ERROR);
+            goto exit;
+        }
+        snap_entry->has_pivoted = false;
+        QSIMPLEQ_INSERT_TAIL(&gsnp_list, snap_entry, entry);
+    }
+
+    /*
+     * Now we will drain, flush, & pivot everything - if any of the pivots fail,
+     * we will flag an error, and attempt to rollback.
+     */
+    bdrv_drain_all();
+    QSIMPLEQ_FOREACH(snap_entry, &gsnp_list, entry) {
+        bdrv_flush(snap_entry->bs);
+        bdrv_close(snap_entry->bs);
+
+        ret = bdrv_open(snap_entry->bs, snap_entry->snapshot_file,
+                        snap_entry->flags, snap_entry->drv);
+        snap_entry->has_pivoted = true;
+        /*
+         * If we fail any of these, then we need to rollback all that we have
+         * performed up to this point
+         */
+        if (ret != 0) {
+            error_set(errp, QERR_OPEN_FILE_FAILED, snap_entry->snapshot_file);
+            goto error_rollback;
+        }
+    }
+
+    /* success */
+    goto exit;
+
+error_rollback:
+    /* failure, undo everything as much as we can */
+    QSIMPLEQ_FOREACH(snap_entry, &gsnp_list, entry) {
+        if (snap_entry->has_pivoted) {
+            ret = bdrv_open(snap_entry->bs, snap_entry->old_filename,
+                            snap_entry->flags, snap_entry->old_drv);
+            if (ret != 0) {
+                /* This is very very bad */
+                error_set(errp, QERR_OPEN_FILE_FAILED,
+                          snap_entry->old_filename);
+            }
+        }
+    }
+exit:
+    QSIMPLEQ_FOREACH(snap_entry, &gsnp_list, entry) {
+        g_free(snap_entry);
+    }
+
+    return;
+}
+
+
 static void eject_device(BlockDriverState *bs, int force, Error **errp)
 {
     if (bdrv_in_use(bs)) {
diff --git a/qapi-schema.json b/qapi-schema.json
index 80debe6..b8d66d0 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1107,6 +1107,51 @@ 
 { 'command': 'block_resize', 'data': { 'device': 'str', 'size': 'int' }}
 
 ##
+# @SnapshotDev
+#
+# @device:  the name of the device to generate the snapshot from.
+#
+# @snapshot-file: the target of the new image. If the file exists, or if it
+#                 is a device, the snapshot will be created in the existing
+#                 file/device. If does not exist, a new file will be created.
+#
+# @format: #optional the format of the snapshot image, default is 'qcow2'.
+##
+{ 'type': 'SnapshotDev',
+  'data': {'device': 'str', 'snapshot-file': 'str', '*format': 'str' } }
+
+##
+# @blockdev-group-snapshot-sync
+#
+# Generates a synchronous snapshot of a group of one or more block devices,
+# as atomically as possible.
+#
+#  List of:
+#  @SnapshotDev: information needed for the device snapshot
+#
+# Returns: nothing on success
+#          If @device is not a valid block device, DeviceNotFound
+#          If @snapshot-file can't be opened, OpenFileFailed
+#          If @format is invalid, InvalidBlockFormat
+#
+# Notes: One of the last steps taken by this command is to close the current
+#        images being used by each specified @device and open the corresponding
+#        @snapshot-file one. If that fails, the command will try to reopen
+#        all of the original image files, for each device specified. If
+#        that also fails OpenFileFailed will be returned and the guest may get
+#        unexpected errors.
+#
+#        Since there could potentially be more than one file open or drive
+#        failures, the additional command 'blockdev-query-group-snapshot-failure'
+#        will return a list of all device files that have failed.  This could
+#        include the original filename if the reopen of an original image file
+#        failed.
+#
+##
+{ 'command': 'blockdev-group-snapshot-sync',
+  'data': { 'dev': [ 'SnapshotDev' ] } }
+
+##
 # @blockdev-snapshot-sync
 #
 # Generates a synchronous snapshot of a block device.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index bd6b641..2fe1e6e 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -665,6 +665,45 @@  EQMP
         .args_type  = "device:B",
         .mhandler.cmd_new = qmp_marshal_input_block_job_cancel,
     },
+    {
+        .name       = "blockdev-group-snapshot-sync",
+        .args_type  = "device:B,snapshot-file:s,format:s?",
+        .mhandler.cmd_new = qmp_marshal_input_blockdev_group_snapshot_sync,
+        .flags      = MONITOR_CMD_ARRAY_INPUT,
+    },
+
+SQMP
+blockdev-group-snapshot-sync
+----------------------
+
+Synchronous snapshot of one or more block devices.  A list array input
+is accepted, that contains the device, snapshot-file to be create as the
+target of the new image. If the file exists, or if it is a device, the
+snapshot will be created in the existing file/device. If does not
+exist, a new file will be created. format specifies the format of the
+snapshot image, default is qcow2.  On failure of any device, it is
+attempted to reopen the original image for all the devices that were
+specified.
+
+Arguments:
+
+- "device": device name to snapshot (json-string)
+- "snapshot-file": name of new image file (json-string)
+- "format": format of new image (json-string, optional)
+
+Example:
+
+-> { "execute": "blockdev-group-snapshot-sync", "arguments": [{ "device": "ide-hd0",
+                                                                "snapshot-file":
+                                                                "/some/place/my-image",
+                                                                "format": "qcow2" },
+                                                              { "device": "ide-hd1",
+                                                                "snapshot-file":
+                                                                "/some/place/my-image2",
+                                                                "format": "qcow2" } }
+<- { "return": {} }
+
+EQMP
 
     {
         .name       = "blockdev-snapshot-sync",