Skip to contentSkip to conversation

Live block commit

PATCH Superseded Jeff Cody 3 participants 25 messages Sep 25, 2012 – Sep 26, 2012 Block layer

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Jeff Cody

author
#0 lore raw

v2 cover Live block commit

This series adds the basic case, of a live commit between two images below the active layer, e.g.:

[base] <--- [snp-1] <--- [snp-2] <--- [snp-3] <--- [active]

can be collapsed down via commit, into:

[base] <--- [active]

or,

[base] <--- [snp-1] <--- [active],

[base] <--- [snp-3] <--- [active],

etc..

TODO: * 'stage-2' of live commit functionality, to be able to push down the
active layer.

These changes are all reflected in my github repo:

git://github.com/codyprime/qemu-kvm-jtc.git branch: jtc-live-commit-1.3-v8

Changes from v1 -> v2:
===================================

Patch 1/7: [Eric] - Commit message cleanup
- Make comments clearer
- fixed whitespace removal

Dropped old Patch 4/8 (new qerror message) - Eric

Patch 2/7: - Changed error_set() to error_setg()

Patch 5/7: [Eric] - Changed error_set() to error_setg()
- Updated comments/docs to be clearer
- fixed whitespace removal

Changes from RFC to v1:
===================================

* Feedback incorporated (see below)
* qemu-iotests added
* live snapshots have active->backing_hd reopened read-only.

Patch 1/8: [Kevin] - Rename bdrv_delete_intermediate() to
bdrv_drop_intermediate()
- Change terminology from 'child' to 'overlay'
- Do not allow top == active, and return NULL for error, simplified some logic.

Patch 2/8: [Kevin] - Preserve errors in commit_populate()
[Kevin, Eric] - Check that there is enough room in base, and resize
with bdrv_truncate() if not.
[Jeff] - Switch from 'child' to 'overlay' terminology
- Fixed up more error checking
- Minor cleanup
[Kevin] - Perform the bdrv_reopen() inside commit_start(),
instead of in blockdev.c

Patch 3/8: [Paolo] - Rearranged patch, so the forward declaration is gone

Patch 4/8: None
Patch 5/8: None

Patch 6/8: [Eric] - Updated QMP/qmp-events.txt with the new 'type' field
[Kevin] - If base cannot be found, return attempted base name
- Moved reopen() logic to commit_start
- Fixed comments in JSON command header
[Jeff] - If 'top' is not specified, default to active->backing_hd

Patch 7/8: New - simple qemu-iotest for commit.

Patch 8/8: New - after live snapshot, use bdrv_reopen() to reopen

                 active->backing_hd read-only.

Changes from the RFC v1 -> RFC v2:
===================================

* This patch series is not on top of Paolo's blk mirror series yet, to make it easier to apply independently if desired. This means some of what was in the previous RFC series is not in this one (BlockdevOnError, for instance), but that can be easily added in once Paolo's series are in.

* This patches series is dependent on the reopen() series with transactional reopen.

* The target release for this series is 1.3

* Found some mistakes in the reopen calls

* Dropped the BlockdevOnError argument (for now), will add in if rebasing on top of Paolo's series.

* Used the new qerror system

Jeff Cody (7):
block: add support functions for live commit, to find and delete
images.
block: add live block commit functionality
blockdev: rename block_stream_cb to a generic block_job_cb
block: helper function, to find the base image of a chain
QAPI: add command for live block commit, 'block-commit'
qemu-iotests: add initial tests for live block commit
block: after creating a live snapshot, make old image read-only

 QMP/qmp-events.txt         |   6 +-
 block.c                    | 166 ++++++++++++++++++++++++++++++
 block.h                    |   5 +
 block/Makefile.objs        |   1 +
 block/commit.c             | 246 +++++++++++++++++++++++++++++++++++++++++++++
 block_int.h                |  16 +++
 blockdev.c                 |  75 +++++++++++++-
 qapi-schema.json           |  35 +++++++
 qmp-commands.hx            |   6 ++
 tests/qemu-iotests/040     | 142 ++++++++++++++++++++++++++
 tests/qemu-iotests/040.out |   5 +
 tests/qemu-iotests/group   |   1 +
 trace-events               |   4 +-
 13 files changed, 702 insertions(+), 6 deletions(-)
 create mode 100644 block/commit.c
 create mode 100755 tests/qemu-iotests/040
 create mode 100644 tests/qemu-iotests/040.out
Signature · 1 line
1.7.11.4
author
#1 lore raw

PATCH v2 1/7 block: add support functions for live commit, to find and delete images.

Add bdrv_find_overlay(), and bdrv_drop_intermediate().

bdrv_find_overlay(): given 'bs' and the active (topmost) BDS of an image chain,
find the image that is the immediate top of 'bs'

bdrv_drop_intermediate():
Given 3 BDS (active, top, base), drop images above base up to and including top, and set base to be the backing file of top's overlay node.

E.g., this converts:

bottom <- base <- intermediate <- top <- active

to

bottom <- base <- active

---
 block.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 block.h |   4 ++
 2 files changed, 154 insertions(+)
block.c +150 -0
index 751ebdc..d044529 100644
@@ -1724,6 +1724,156 @@ int bdrv_change_backing_file(BlockDriverState *bs,
     return ret;
 }
 
+/*
+ * Finds the image layer in the chain that has 'bs' as its backing file.
+ *
+ * active is the current topmost image.
+ *
+ * Returns NULL if bs is not found in active's image chain,
+ * or if active == bs.
+ */
+BlockDriverState *bdrv_find_overlay(BlockDriverState *active,
+                                    BlockDriverState *bs)
+{
+    BlockDriverState *overlay = NULL;
+    BlockDriverState *intermediate;
+
+    assert(active != NULL);
+    assert(bs != NULL);
+
+    /* if bs is the same as active, then by definition it has no overlay
+     */
+    if (active == bs) {
+        return NULL;
+    }
+
+    intermediate = active;
+    while (intermediate->backing_hd) {
+        if (intermediate->backing_hd == bs) {
+            overlay = intermediate;
+            break;
+        }
+        intermediate = intermediate->backing_hd;
+    }
+
+    return overlay;
+}
+
+typedef struct BlkIntermediateStates {
+    BlockDriverState *bs;
+    QSIMPLEQ_ENTRY(BlkIntermediateStates) entry;
+} BlkIntermediateStates;
+
+
+/*
+ * Drops images above 'base' up to and including 'top', and sets the image
+ * above 'top' to have base as its backing file.
+ *
+ * Requires that the overlay to 'top' is opened r/w, so that the backing file
+ * information in 'bs' can be properly updated.
+ *
+ * E.g., this will convert the following chain:
+ * bottom <- base <- intermediate <- top <- active
+ *
+ * to
+ *
+ * bottom <- base <- active
+ *
+ * It is allowed for bottom==base, in which case it converts:
+ *
+ * base <- intermediate <- top <- active
+ *
+ * to
+ *
+ * base <- active
+ *
+ * base <- intermediate <- top
+ *
+ * becomes
+ *
+ * base <- top
+ *
+ * Error conditions:
+ *  if active == top, that is considered an error
+ *
+ */
+int bdrv_drop_intermediate(BlockDriverState *active, BlockDriverState *top,
+                           BlockDriverState *base)
+{
+    BlockDriverState *intermediate;
+    BlockDriverState *base_bs = NULL;
+    BlockDriverState *new_top_bs = NULL;
+    BlkIntermediateStates *intermediate_state, *next;
+    int ret = -EIO;
+
+    QSIMPLEQ_HEAD(states_to_delete, BlkIntermediateStates) states_to_delete;
+    QSIMPLEQ_INIT(&states_to_delete);
+
+    if (!top->drv || !base->drv) {
+        goto exit;
+    }
+
+    new_top_bs = bdrv_find_overlay(active, top);
+
+    if (new_top_bs == NULL) {
+        /* we could not find the image above 'top', this is an error */
+        goto exit;
+    }
+
+    /* special case of new_top_bs->backing_hd already pointing to base - nothing
+     * to do, no intermediate images */
+    if (new_top_bs->backing_hd == base) {
+        ret = 0;
+        goto exit;
+    }
+
+    intermediate = new_top_bs->backing_hd;  /* this should be the same
+                                               as 'top' */
+
+    /* now we will go down through the list, and add each BDS we find
+     * into our deletion queue, until we hit the 'base'
+     */
+    while (intermediate) {
+        intermediate_state = g_malloc0(sizeof(BlkIntermediateStates));
+        intermediate_state->bs = intermediate;
+        QSIMPLEQ_INSERT_TAIL(&states_to_delete, intermediate_state, entry);
+
+        if (intermediate->backing_hd == base) {
+            base_bs = intermediate->backing_hd;
+            break;
+        }
+        intermediate = intermediate->backing_hd;
+    }
+    if (base_bs == NULL) {
+        /* something went wrong, we did not end at the base. safely
+         * unravel everything, and exit with error */
+        goto exit;
+    }
+
+    /* success - we can delete the intermediate states, and link top->base */
+    ret = bdrv_change_backing_file(new_top_bs, base_bs->filename,
+                                   base_bs->drv ? base_bs->drv->format_name : "");
+    if (ret) {
+        goto exit;
+    }
+    new_top_bs->backing_hd = base_bs;
+
+
+    QSIMPLEQ_FOREACH_SAFE(intermediate_state, &states_to_delete, entry, next) {
+        /* so that bdrv_close() does not recursively close the chain */
+        intermediate_state->bs->backing_hd = NULL;
+        bdrv_delete(intermediate_state->bs);
+    }
+    ret = 0;
+
+exit:
+    QSIMPLEQ_FOREACH_SAFE(intermediate_state, &states_to_delete, entry, next) {
+        g_free(intermediate_state);
+    }
+    return ret;
+}
+
+
 static int bdrv_check_byte_request(BlockDriverState *bs, int64_t offset,
                                    size_t size)
 {
block.h +4 -0
index b1095d8..8c9b424 100644
@@ -203,6 +203,10 @@ int bdrv_commit_all(void);
 int bdrv_change_backing_file(BlockDriverState *bs,
     const char *backing_file, const char *backing_fmt);
 void bdrv_register(BlockDriver *bdrv);
+int bdrv_drop_intermediate(BlockDriverState *active, BlockDriverState *top,
+                           BlockDriverState *base);
+BlockDriverState *bdrv_find_overlay(BlockDriverState *active,
+                                    BlockDriverState *bs);
 
 
 typedef struct BdrvCheckResult {
Signature · 1 line
1.7.11.4
#2 lore raw

Re: [Qemu-devel] [PATCH v2 1/7] block: add support functions for live commit, to find and delete images.

Am 25.09.2012 18:29, schrieb Jeff Cody:

Quoted 33 lines

Add bdrv_find_overlay(), and bdrv_drop_intermediate().

bdrv_find_overlay(): given 'bs' and the active (topmost) BDS of an image chain,
find the image that is the immediate top of 'bs'

bdrv_drop_intermediate():
Given 3 BDS (active, top, base), drop images above base up to and including top, and set base to be the backing file of top's overlay node.

E.g., this converts:

bottom <- base <- intermediate <- top <- active

to

bottom <- base <- active

+/*
+ * Drops images above 'base' up to and including 'top', and sets the image
+ * above 'top' to have base as its backing file.
+ *
+ * Requires that the overlay to 'top' is opened r/w, so that the backing file
+ * information in 'bs' can be properly updated.
+ *
+ * E.g., this will convert the following chain:
+ * bottom <- base <- intermediate <- top <- active
+ *
+ * to
+ *
+ * bottom <- base <- active

Quoted 8 lines

+ *
+ * It is allowed for bottom==base, in which case it converts:
+ *
+ * base <- intermediate <- top <- active
+ *
+ * to
+ *
+ * base <- active

Compared to RFC v2 you deleted this part of the comment:

+ *
+ * It is also allowed for top==active, except in that case active is not
+ * deleted:

You describe the condition now as an error (which I think is what our conclusion on IRC was), but now I think the following example must be removed as well:

Quoted 33 lines

+ *
+ * base <- intermediate <- top
+ *
+ * becomes
+ *
+ * base <- top
+ *
+ * Error conditions:
+ * if active == top, that is considered an error
+ *
+ */

block.c +22 -0
+int bdrv_drop_intermediate(BlockDriverState *active, BlockDriverState *top,
+                           BlockDriverState *base)
+{
+    BlockDriverState *intermediate;
+    BlockDriverState *base_bs = NULL;
+    BlockDriverState *new_top_bs = NULL;
+    BlkIntermediateStates *intermediate_state, *next;
+    int ret = -EIO;
+
+    QSIMPLEQ_HEAD(states_to_delete, BlkIntermediateStates) states_to_delete;
+    QSIMPLEQ_INIT(&states_to_delete);
+
+    if (!top->drv || !base->drv) {
+        goto exit;
+    }
+
+    new_top_bs = bdrv_find_overlay(active, top);
+
+    if (new_top_bs == NULL) {
+        /* we could not find the image above 'top', this is an error */
+        goto exit;
+    }

block.c int bdrv_change_backing_file(BlockDriverState *bs, · line 1831

Quoted 10 lines
block.c +10 -0
+
+    /* special case of new_top_bs->backing_hd already pointing to base - nothing
+     * to do, no intermediate images */
+    if (new_top_bs->backing_hd == base) {
+        ret = 0;
+        goto exit;
+    }
+
+    intermediate = new_top_bs->backing_hd;  /* this should be the same
+                                               as 'top' */

Should? Is it the same or not? If yes, you can write it as an assert(); it no, the comment is only confusing.

(In fact it's obvious enough that I'd just go with intermediate = top; without any comment)

Kevin

author
#3 lore raw

PATCH v2 2/7 block: add live block commit functionality

In reply to Jeff Cody

This adds the live commit coroutine. This iteration focuses on the commit only below the active layer, and not the active layer itself.

The behaviour is similar to block streaming; the sectors are walked through, and anything that exists above 'base' is committed back down into base. At the end, intermediate images are deleted, and the chain stitched together. Images are restored to their original open flags upon completion.

---
 block/Makefile.objs |   1 +
 block/commit.c      | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 block_int.h         |  16 ++++
 trace-events        |   2 +
 4 files changed, 265 insertions(+)
 create mode 100644 block/commit.c
block/Makefile.objs +1 -0
index b5754d3..4a136b8 100644
@@ -4,6 +4,7 @@ block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
 block-obj-y += qed-check.o
 block-obj-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o
 block-obj-y += stream.o
+block-obj-y += commit.o
 block-obj-$(CONFIG_WIN32) += raw-win32.o
 block-obj-$(CONFIG_POSIX) += raw-posix.o
 block-obj-$(CONFIG_LIBISCSI) += iscsi.o
block/commit.c new file +246 -0

246 lines not on this page · raw message

block_int.h +16 -0
index ac4245c..56164a7 100644
@@ -463,4 +463,20 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base,
                   BlockDriverCompletionFunc *cb,
                   void *opaque, Error **errp);
 
+/**
+ * commit_start:
+ * @bs: Top Block device
+ * @base: Block device that will be written into, and become the new top
+ * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
+ * @on_error: The action to take upon error.
+ * @cb: Completion function for the job.
+ * @opaque: Opaque pointer value passed to @cb.
+ * @errp: Error object.
+ *
+ */
+void commit_start(BlockDriverState *bs, BlockDriverState *base,
+                 BlockDriverState *top, int64_t speed,
+                 BlockErrorAction on_error, BlockDriverCompletionFunc *cb,
+                 void *opaque, Error **errp);
+
 #endif /* BLOCK_INT_H */
trace-events +2 -0
index f5b5097..dbc3007 100644
@@ -74,6 +74,8 @@ bdrv_co_do_copy_on_readv(void *bs, int64_t sector_num, int nb_sectors, int64_t c
 # block/stream.c
 stream_one_iteration(void *s, int64_t sector_num, int nb_sectors, int is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d"
 stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p base %p s %p co %p opaque %p"
+commit_one_iteration(void *s, int64_t sector_num, int nb_sectors, int is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d"
+commit_start(void *bs, void *base, void *top, void *s, void *co, void *opaque) "bs %p base %p top %p s %p co %p opaque %p"
 
 # blockdev.c
 qmp_block_job_cancel(void *job) "job %p"
Signature · 1 line
1.7.11.4
#4 lore raw

Re: [Qemu-devel] [PATCH v2 2/7] block: add live block commit functionality

Quoted 29 lines · Jeff Cody

This adds the live commit coroutine. This iteration focuses on the commit only below the active layer, and not the active layer itself.

The behaviour is similar to block streaming; the sectors are walked through, and anything that exists above 'base' is committed back down into base. At the end, intermediate images are deleted, and the chain stitched together. Images are restored to their original open flags upon completion.

block/commit.c new file +18 -0
+void commit_start(BlockDriverState *bs, BlockDriverState *base,
+                 BlockDriverState *top, int64_t speed,
+                 BlockErrorAction on_error, BlockDriverCompletionFunc *cb,
+                 void *opaque, Error **errp)
+{
+    CommitBlockJob *s;
+    BlockReopenQueue *reopen_queue = NULL;
+    int orig_overlay_flags;
+    int orig_base_flags;
+    BlockDriverState *overlay_bs;
+    Error *local_err = NULL;
+
+    if ((on_error == BLOCK_ERR_STOP_ANY ||
+         on_error == BLOCK_ERR_STOP_ENOSPC) &&
+        !bdrv_iostatus_is_enabled(bs)) {
+        error_set(errp, QERR_INVALID_PARAMETER_COMBINATION);
+        return;
+    }

block/commit.c line 209

Quoted 10 lines
block/commit.c new file +10 -0
+
+    overlay_bs = bdrv_find_overlay(bs, top);
+
+    if (overlay_bs == NULL) {
+        error_setg(errp, "Could not find overlay image for %s:", top->filename);
+        return;
+    }
+
+    orig_base_flags    = bdrv_get_flags(base);
+    orig_overlay_flags = bdrv_get_flags(overlay_bs);

I think you are missing a check here that base is on the backing chain of top. See also my comments to 5/7.

block/commit.c line 221

Quoted 12 lines
block/commit.c new file +12 -0
+
+    /* convert base_bs & overlay_bs to r/w, if necessary */
+    if (!(orig_base_flags & BDRV_O_RDWR)) {
+        reopen_queue = bdrv_reopen_queue(reopen_queue, base,
+                                         orig_base_flags | BDRV_O_RDWR);
+    }
+    if (!(orig_overlay_flags & BDRV_O_RDWR)) {
+        reopen_queue = bdrv_reopen_queue(reopen_queue, overlay_bs,
+                                         orig_overlay_flags | BDRV_O_RDWR);
+    }
+    if (reopen_queue) {
+        bdrv_reopen_multiple(reopen_queue, &local_err);

Is it valid to make a no-op call, such as:

{ "execute":"block-commit", "arguments":{
"device":"drive0", "top":"base", "base":"base" }}

If so, should we do an early exit here, rather than temporarily changing base to R/W just to change it back to R/O?

If not, should we be rejecting it up front (again, back to the question of failing if 'base' is not a backing file of 'top', even if both 'top' and 'base' are backing files of 'device').

Signature · 2 lines
Eric Blake   [email protected]    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#5 lore raw

Re: [Qemu-devel] [PATCH v2 2/7] block: add live block commit functionality

Quoted 35 lines · Eric Blake
Quoted 34 lines · Jeff Cody

This adds the live commit coroutine. This iteration focuses on the commit only below the active layer, and not the active layer itself.

The behaviour is similar to block streaming; the sectors are walked through, and anything that exists above 'base' is committed back down into base. At the end, intermediate images are deleted, and the chain stitched together. Images are restored to their original open flags upon completion.

block/commit.c new file +23 -0
+void commit_start(BlockDriverState *bs, BlockDriverState *base,
+                 BlockDriverState *top, int64_t speed,
+                 BlockErrorAction on_error, BlockDriverCompletionFunc *cb,
+                 void *opaque, Error **errp)
+{
+    CommitBlockJob *s;
+    BlockReopenQueue *reopen_queue = NULL;
+    int orig_overlay_flags;
+    int orig_base_flags;
+    BlockDriverState *overlay_bs;
+    Error *local_err = NULL;
+
+    if ((on_error == BLOCK_ERR_STOP_ANY ||
+         on_error == BLOCK_ERR_STOP_ENOSPC) &&
+        !bdrv_iostatus_is_enabled(bs)) {
+        error_set(errp, QERR_INVALID_PARAMETER_COMBINATION);
+        return;
+    }
+
+    overlay_bs = bdrv_find_overlay(bs, top);
+
+    if (overlay_bs == NULL) {
+        error_setg(errp, "Could not find overlay image for %s:", top->filename);
Quoted 9 lines

block/commit.c line 209

Quoted 5 lines
block/commit.c new file +5 -0
+        return;
+    }
+
+    orig_base_flags    = bdrv_get_flags(base);
+    orig_overlay_flags = bdrv_get_flags(overlay_bs);

I think you are missing a check here that base is on the backing chain of top. See also my comments to 5/7.

Did you mean your comments on 6/7 (or am I missing an email)?

This does get partially validated in patch 5/7, in the qmp_block_commit() handler - both base and top are verified to be in the chain 'bs'. What is not validated, however, is that you did not swap your 'top' and 'base' arguments. I'll add a check here for that, to make sure that base is reachable from overlay_bs.

Quoted 18 lines

block/commit.c line 221

Quoted 12 lines
block/commit.c new file +12 -0
+
+    /* convert base_bs & overlay_bs to r/w, if necessary */
+    if (!(orig_base_flags & BDRV_O_RDWR)) {
+        reopen_queue = bdrv_reopen_queue(reopen_queue, base,
+                                         orig_base_flags | BDRV_O_RDWR);
+    }
+    if (!(orig_overlay_flags & BDRV_O_RDWR)) {
+        reopen_queue = bdrv_reopen_queue(reopen_queue, overlay_bs,
+                                         orig_overlay_flags | BDRV_O_RDWR);
+    }
+    if (reopen_queue) {
+        bdrv_reopen_multiple(reopen_queue, &local_err);

Is it valid to make a no-op call, such as:

{ "execute":"block-commit", "arguments":{
"device":"drive0", "top":"base", "base":"base" }}

Quoted 7 lines

If so, should we do an early exit here, rather than temporarily changing base to R/W just to change it back to R/O?

If not, should we be rejecting it up front (again, back to the question of failing if 'base' is not a backing file of 'top', even if both 'top' and 'base' are backing files of 'device').

I'll add a quick check for that as well. Patch 5 checks for it in one scenario (default 'top'), and returns a generic error of:
"Invalid files for merge: top and base are the same"

I'll make sure to check for that in all scenarios (or just make the test mentioned earlier incorporate top == base as well)

Thanks,
Jeff

#6 lore raw

Re: [Qemu-devel] [PATCH v2 2/7] block: add live block commit functionality

Quoted 11 lines · Jeff Cody
Quoted 8 lines · Eric Blake
Quoted 3 lines · Jeff Cody

This adds the live commit coroutine. This iteration focuses on the commit only below the active layer, and not the active layer itself.

I think you are missing a check here that base is on the backing chain of top. See also my comments to 5/7.

Did you mean your comments on 6/7 (or am I missing an email)?

Shoot. You're right, and I messed myself up by reviewing out of order :)

Quoted 6 lines

This does get partially validated in patch 5/7, in the qmp_block_commit() handler - both base and top are verified to be in the chain 'bs'. What is not validated, however, is that you did not swap your 'top' and 'base' arguments. I'll add a check here for that, to make sure that base is reachable from overlay_bs.

At any rate, it sounds like I got my point across, in spite of myself.

Signature · 2 lines
Eric Blake   [email protected]    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
Reviewed-by on v2 2/7
#7 lore raw

Re: [Qemu-devel] [PATCH v2 2/7] block: add live block commit functionality

In reply to Jeff Cody

Am 25.09.2012 18:29, schrieb Jeff Cody:

Quoted 10 lines

This adds the live commit coroutine. This iteration focuses on the commit only below the active layer, and not the active layer itself.

The behaviour is similar to block streaming; the sectors are walked through, and anything that exists above 'base' is committed back down into base. At the end, intermediate images are deleted, and the chain stitched together. Images are restored to their original open flags upon completion.

author
#8 lore raw

PATCH v2 3/7 blockdev: rename block_stream_cb to a generic block_job_cb

In reply to Jeff Cody
---
 blockdev.c   | 6 +++---
 trace-events | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)
blockdev.c +3 -3
index 7c83baa..9caa16f 100644
@@ -1063,12 +1063,12 @@ static QObject *qobject_from_block_job(BlockJob *job)
                               job->speed);
 }
 
-static void block_stream_cb(void *opaque, int ret)
+static void block_job_cb(void *opaque, int ret)
 {
     BlockDriverState *bs = opaque;
     QObject *obj;
 
-    trace_block_stream_cb(bs, bs->job, ret);
+    trace_block_job_cb(bs, bs->job, ret);
 
     assert(bs->job);
     obj = qobject_from_block_job(bs->job);
@@ -1110,7 +1110,7 @@ void qmp_block_stream(const char *device, bool has_base,
     }
 
     stream_start(bs, base_bs, base, has_speed ? speed : 0,
-                 block_stream_cb, bs, &local_err);
+                 block_job_cb, bs, &local_err);
     if (error_is_set(&local_err)) {
         error_propagate(errp, local_err);
         return;
trace-events +1 -1
index dbc3007..29771a7 100644
@@ -79,7 +79,7 @@ commit_start(void *bs, void *base, void *top, void *s, void *co, void *opaque) "
 
 # blockdev.c
 qmp_block_job_cancel(void *job) "job %p"
-block_stream_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
+block_job_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
 qmp_block_stream(void *bs, void *job) "bs %p job %p"
 
 # hw/virtio-blk.c
Signature · 1 line
1.7.11.4
author
#9 lore raw

PATCH v2 4/7 block: helper function, to find the base image of a chain

In reply to Jeff Cody

This is a simple helper function, that will return the base image of a given image chain.

---
 block.c | 16 ++++++++++++++++
 block.h |  1 +
 2 files changed, 17 insertions(+)
block.c +16 -0
index d044529..4a9bdc1 100644
@@ -3124,6 +3124,22 @@ int bdrv_get_backing_file_depth(BlockDriverState *bs)
     return 1 + bdrv_get_backing_file_depth(bs->backing_hd);
 }
 
+BlockDriverState *bdrv_find_base(BlockDriverState *bs)
+{
+    BlockDriverState *curr_bs = NULL;
+
+    if (!bs) {
+        return NULL;
+    }
+
+    curr_bs = bs;
+
+    while (curr_bs->backing_hd) {
+        curr_bs = curr_bs->backing_hd;
+    }
+    return curr_bs;
+}
+
 #define NB_SUFFIXES 4
 
 char *get_human_readable_size(char *buf, int buf_size, int64_t size)
block.h +1 -0
index 8c9b424..e9249c4 100644
@@ -207,6 +207,7 @@ int bdrv_drop_intermediate(BlockDriverState *active, BlockDriverState *top,
                            BlockDriverState *base);
 BlockDriverState *bdrv_find_overlay(BlockDriverState *active,
                                     BlockDriverState *bs);
+BlockDriverState *bdrv_find_base(BlockDriverState *bs);
 
 
 typedef struct BdrvCheckResult {
Signature · 1 line
1.7.11.4
Reviewed-by on v2 4/7
#10 lore raw

Re: [Qemu-devel] [PATCH v2 4/7] block: helper function, to find the base image of a chain

Quoted 8 lines · Jeff Cody

This is a simple helper function, that will return the base image of a given image chain.

---
 block.c | 16 ++++++++++++++++
 block.h |  1 +
 2 files changed, 17 insertions(+)

Bikeshed question: Any reason patch 1/7 adds two helper functions, and patch 4/7 adds a third? Perhaps it would be worth squashing all three into one commit, or else having three commits, one per helper? But there's no point in repainting things now, so:

Signature · 2 lines
Eric Blake   [email protected]    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#11 lore raw

Re: [Qemu-devel] [PATCH v2 4/7] block: helper function, to find the base image of a chain

Quoted 10 lines · Eric Blake
Quoted 8 lines · Jeff Cody

This is a simple helper function, that will return the base image of a given image chain.

---
 block.c | 16 ++++++++++++++++
 block.h |  1 +
 2 files changed, 17 insertions(+)
Quoted 7 lines

Bikeshed question: Any reason patch 1/7 adds two helper functions, and patch 4/7 adds a third? Perhaps it would be worth squashing all three into one commit, or else having three commits, one per helper? But there's no point in repainting things now, so:

The reasoning was the helper functions in 1/7 were to support block commit (patch 2/7), and the helper function in 4/7 was to support the block commit QMP command (patch 5/7), so it seemed logical to split them.

author
#12 lore raw

PATCH v2 5/7 QAPI: add command for live block commit, 'block-commit'

In reply to Jeff Cody

The command for live block commit is added, which has the following arguments:

device: the block device to perform the commit on (mandatory)
base:   the base image to commit into; optional (if not specified,

it is the underlying original image)
top: the top image of the commit - all data from inside top down
to base will be committed into base. optional (if not specified, it is one below the active image) - see note below
speed: maximum speed, in bytes/sec

note: eventually this will support merging down the active layer,
but that code is not yet complete. If the active layer is passed in currently as top, or top is left to the default, then an error will be returned.

The is done as a block job, so upon completion a BLOCK_JOB_COMPLETED will be emitted.

---
 QMP/qmp-events.txt |  6 +++--
 blockdev.c         | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 qapi-schema.json   | 35 +++++++++++++++++++++++++++++
 qmp-commands.hx    |  6 +++++
 4 files changed, 109 insertions(+), 2 deletions(-)
QMP/qmp-events.txt +4 -2
index 2878058..4491020 100644
@@ -50,7 +50,8 @@ Emitted when a block job has been cancelled.
 
 Data:
 
-- "type":     Job type ("stream" for image streaming, json-string)
+- "type":     Job type (json-string; "stream" for image streaming
+                                     "commit" for block commit)
 - "device":   Device name (json-string)
 - "len":      Maximum progress value (json-int)
 - "offset":   Current progress value (json-int)
@@ -73,7 +74,8 @@ Emitted when a block job has completed.
 
 Data:
 
-- "type":     Job type ("stream" for image streaming, json-string)
+- "type":     Job type (json-string; "stream" for image streaming
+                                     "commit" for block commit)
 - "device":   Device name (json-string)
 - "len":      Maximum progress value (json-int)
 - "offset":   Current progress value (json-int)
blockdev.c +64 -0

70 lines not on this page · raw message

qapi-schema.json +35 -0
index 14e4419..e614453 100644
@@ -1468,6 +1468,41 @@
   'returns': 'str' }
 
 ##
+# @block-commit
+#
+# Live commit of data from overlay image nodes into backing nodes - i.e.,
+# writes data between 'top' and 'base' into 'base'.
+#
+# @device:  the name of the device
+#
+# @base:   #optional The file name of the backing image to write data into.
+#                    If not specified, this is the deepest backing image
+#
+# @top:    #optional The file name of the backing image within the image chain,
+#                    which contains the topmost data to be committed down.
+#                    If not specified, this is one layer below the active
+#                    layer (i.e. active->backing_hd).
+#
+#                    If top == base, that is an error.
+#
+#
+# @speed:  #optional the maximum speed, in bytes per second
+#
+# Returns: Nothing on success
+#          If commit or stream is already active on this device, DeviceInUse
+#          If @device does not exist, DeviceNotFound
+#          If image commit is not supported by this device, NotSupported
+#          If @base does not exist, a generic error is returned
+#          If @top does not exist, a generic error is returned
+#          If @speed is invalid, InvalidParameter
+#
+# Since: 1.3
+#
+##
+{ 'command': 'block-commit',
+  'data': { 'device': 'str', '*base': 'str', '*top': 'str',
+            '*speed': 'int' } }
+
 # @migrate_cancel
 #
 # Cancel the current executing migration process.
qmp-commands.hx +6 -0
index 6e21ddb..e244763 100644
@@ -792,6 +792,12 @@ EQMP
     },
 
     {
+        .name       = "block-commit",
+        .args_type  = "device:B,base:s?,top:s?,speed:o?",
+        .mhandler.cmd_new = qmp_marshal_input_block_commit,
+    },
+
+    {
         .name       = "block-job-set-speed",
         .args_type  = "device:B,speed:o",
         .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed,
Signature · 1 line
1.7.11.4
#13 lore raw

Re: [Qemu-devel] [PATCH v2 5/7] QAPI: add command for live block commit, 'block-commit'

Quoted 9 lines · Jeff Cody

The command for live block commit is added, which has the following arguments:

device: the block device to perform the commit on (mandatory)
base:   the base image to commit into; optional (if not specified,

it is the underlying original image)
top: the top image of the commit - all data from inside top down
to base will be committed into base. optional (if not specified, it is one below the active image) - see note below

This says that for 'base' <- 'mid' <- 'active', omitting 'top' is the same as specifying 'top':'mid'.

Quoted 6 lines

speed: maximum speed, in bytes/sec

note: eventually this will support merging down the active layer,
but that code is not yet complete. If the active layer is passed in currently as top, or top is left to the default, then an error will be returned.

This says that for 'base' <- 'mid' <- 'active', omitting 'top' is an error (because it would be the same as an explicit 'top:'active').

Let's check the code...

blockdev.c void qmp_block_stream(const char *device, bool has_base, · line 1167

Quoted 8 lines
blockdev.c +8 -0
+    if (top && has_top) {
+        /* if we want to allow the active layer,
+         * use 'bdrv_find_image()' here */
+        top_bs = bdrv_find_backing_image(bs, top);
+    } else {
+        /* default is one below the active layer, unless that is
+         * the base */
+        top_bs = bs->backing_hd;

Aha - the former is correct as coded (you default to 'top':'mid' in my example), so the 'note' in your commit message needs editing.

On the other hand, when we ever DO get around to adding live commit, which is the saner default? That is, am I more likely to want to do live commit from 'active', or more likely to do commit of the layer below 'active'? If live commit is the more desirable case, then that argues that THIS commit should always error out if !has_top, and that the future patch will default top_bs = bs, and the rest of your commit message and documentation would need tweaking accordingly.

I don't have a preference either way (libvirt can arrange to always pass the top argument, and thus avoid the confusion when top is omitted), but if anyone else cares, now is the time to get the API right before we are locked in to it.

Quoted 12 lines

+++ b/qapi-schema.json

qapi-schema.json +7 -0
@@ -1468,6 +1468,41 @@
   'returns': 'str' }
 
 ##
+# @block-commit
+#
+# Live commit of data from overlay image nodes into backing nodes - i.e.,
+# writes data between 'top' and 'base' into 'base'.
+#
+# @device:  the name of the device
+#

qapi-schema.json line 1487

Quoted 10 lines
qapi-schema.json +10 -0
+# @base:   #optional The file name of the backing image to write data into.
+#                    If not specified, this is the deepest backing image
+#
+# @top:    #optional The file name of the backing image within the image chain,
+#                    which contains the topmost data to be committed down.
+#                    If not specified, this is one layer below the active
+#                    layer (i.e. active->backing_hd).
+#
+#                    If top == base, that is an error.
+#

This documentation of @top matches the first paragraph of your commit message.

qapi-schema.json line 1496

Quoted 9 lines
qapi-schema.json +9 -0
+#
+# @speed:  #optional the maximum speed, in bytes per second
+#
+# Returns: Nothing on success
+#          If commit or stream is already active on this device, DeviceInUse
+#          If @device does not exist, DeviceNotFound
+#          If image commit is not supported by this device, NotSupported
+#          If @base does not exist, a generic error is returned
+#          If @top does not exist, a generic error is returned

These two lines could be merged, or even made more generic (for example, based on the rest of this thread, you will also be erroring out if base and top exist, but appear as swapped arguments):

If @base or @top is invalid, a generic error is returned

Signature · 2 lines
Eric Blake   [email protected]    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#14 lore raw

Re: [Qemu-devel] [PATCH v2 5/7] QAPI: add command for live block commit, 'block-commit'

Quoted 11 lines · Eric Blake
Quoted 9 lines · Jeff Cody

The command for live block commit is added, which has the following arguments:

device: the block device to perform the commit on (mandatory)
base:   the base image to commit into; optional (if not specified,

it is the underlying original image)
top: the top image of the commit - all data from inside top down
to base will be committed into base. optional (if not specified, it is one below the active image) - see note below

Quoted 3 lines

This says that for 'base' <- 'mid' <- 'active', omitting 'top' is the same as specifying 'top':'mid'.

That is the intended default.

Quoted 9 lines
Quoted 6 lines

speed: maximum speed, in bytes/sec

note: eventually this will support merging down the active layer,
but that code is not yet complete. If the active layer is passed in currently as top, or top is left to the default, then an error will be returned.

This says that for 'base' <- 'mid' <- 'active', omitting 'top' is an error (because it would be the same as an explicit 'top:'active').

This is how I originally had it, and then I changed it to default to top->backing_hd. I apparently did not remember to catch the note, however.

Quoted 23 lines

Let's check the code...

blockdev.c void qmp_block_stream(const char *device, bool has_base, · line 1167

Quoted 8 lines
blockdev.c +8 -0
+    if (top && has_top) {
+        /* if we want to allow the active layer,
+         * use 'bdrv_find_image()' here */
+        top_bs = bdrv_find_backing_image(bs, top);
+    } else {
+        /* default is one below the active layer, unless that is
+         * the base */
+        top_bs = bs->backing_hd;

Aha - the former is correct as coded (you default to 'top':'mid' in my example), so the 'note' in your commit message needs editing.

On the other hand, when we ever DO get around to adding live commit, which is the saner default? That is, am I more likely to want to do live commit from 'active', or more likely to do commit of the layer below 'active'? If live commit is the more desirable case, then that argues that THIS commit should always error out if !has_top, and that the future patch will default top_bs = bs, and the rest of your commit message and documentation would need tweaking accordingly.

Quoted 5 lines

I don't have a preference either way (libvirt can arrange to always pass the top argument, and thus avoid the confusion when top is omitted), but if anyone else cares, now is the time to get the API right before we are locked in to it.

I guess I don't have a strong preference either - I originally had it the other way, but then that meant the default in the current implementation was actually an error.

Also, I assumed (danger!) that the most common use of commit would be a snapshot, followed by a commit of active->backing_hd. With that assumption, it seemed like a sane default.

Quoted 40 lines
Quoted 12 lines

+++ b/qapi-schema.json

qapi-schema.json +7 -0
@@ -1468,6 +1468,41 @@
   'returns': 'str' }
 
 ##
+# @block-commit
+#
+# Live commit of data from overlay image nodes into backing nodes - i.e.,
+# writes data between 'top' and 'base' into 'base'.
+#
+# @device:  the name of the device
+#

qapi-schema.json line 1487

Quoted 10 lines
qapi-schema.json +10 -0
+# @base:   #optional The file name of the backing image to write data into.
+#                    If not specified, this is the deepest backing image
+#
+# @top:    #optional The file name of the backing image within the image chain,
+#                    which contains the topmost data to be committed down.
+#                    If not specified, this is one layer below the active
+#                    layer (i.e. active->backing_hd).
+#
+#                    If top == base, that is an error.
+#

This documentation of @top matches the first paragraph of your commit message.

qapi-schema.json line 1496

Quoted 9 lines
qapi-schema.json +9 -0
+#
+# @speed:  #optional the maximum speed, in bytes per second
+#
+# Returns: Nothing on success
+#          If commit or stream is already active on this device, DeviceInUse
+#          If @device does not exist, DeviceNotFound
+#          If image commit is not supported by this device, NotSupported
+#          If @base does not exist, a generic error is returned
+#          If @top does not exist, a generic error is returned

These two lines could be merged, or even made more generic (for example, based on the rest of this thread, you will also be erroring out if base and top exist, but appear as swapped arguments):

Quoted 2 lines

If @base or @top is invalid, a generic error is returned

OK

#15 lore raw

Re: [Qemu-devel] [PATCH v2 5/7] QAPI: add command for live block commit, 'block-commit'

In reply to Jeff Cody

Am 25.09.2012 18:29, schrieb Jeff Cody:

Quoted 33 lines

The command for live block commit is added, which has the following arguments:

device: the block device to perform the commit on (mandatory)
base:   the base image to commit into; optional (if not specified,

it is the underlying original image)
top: the top image of the commit - all data from inside top down
to base will be committed into base. optional (if not specified, it is one below the active image) - see note below
speed: maximum speed, in bytes/sec

note: eventually this will support merging down the active layer,
but that code is not yet complete. If the active layer is passed in currently as top, or top is left to the default, then an error will be returned.

The is done as a block job, so upon completion a BLOCK_JOB_COMPLETED will be emitted.

qapi-schema.json +4 -0
index 14e4419..e614453 100644
@@ -1468,6 +1468,41 @@
   'returns': 'str' }
 
 ##
+# @block-commit
+#
+# Live commit of data from overlay image nodes into backing nodes - i.e.,
+# writes data between 'top' and 'base' into 'base'.

qapi-schema.json line 1484

Quoted 10 lines
qapi-schema.json +10 -0
+#
+# @device:  the name of the device
+#
+# @base:   #optional The file name of the backing image to write data into.
+#                    If not specified, this is the deepest backing image
+#
+# @top:    #optional The file name of the backing image within the image chain,
+#                    which contains the topmost data to be committed down.
+#                    If not specified, this is one layer below the active
+#                    layer (i.e. active->backing_hd).

Why isn't active the default any more? I know, we don't support it yet, but long term this is what makes most sense as a default.

Quoted 10 lines
qapi-schema.json +10 -0
+#
+#                    If top == base, that is an error.
+#
+#
+# @speed:  #optional the maximum speed, in bytes per second
+#
+# Returns: Nothing on success
+#          If commit or stream is already active on this device, DeviceInUse
+#          If @device does not exist, DeviceNotFound
+#          If image commit is not supported by this device, NotSupported

qapi-schema.json line 1504

Quoted 10 lines
qapi-schema.json +10 -0
+#          If @base does not exist, a generic error is returned
+#          If @top does not exist, a generic error is returned
+#          If @speed is invalid, InvalidParameter
+#
+# Since: 1.3
+#
+##
+{ 'command': 'block-commit',
+  'data': { 'device': 'str', '*base': 'str', '*top': 'str',
+            '*speed': 'int' } }

Kevin

author
#16 lore raw

Re: [Qemu-devel] [PATCH v2 5/7] QAPI: add command for live block commit, 'block-commit'

Quoted 39 lines · Kevin Wolf

Am 25.09.2012 18:29, schrieb Jeff Cody:

Quoted 38 lines

The command for live block commit is added, which has the following arguments:

device: the block device to perform the commit on (mandatory)
base:   the base image to commit into; optional (if not specified,

it is the underlying original image)
top: the top image of the commit - all data from inside top down
to base will be committed into base. optional (if not specified, it is one below the active image) - see note below
speed: maximum speed, in bytes/sec

note: eventually this will support merging down the active layer,
but that code is not yet complete. If the active layer is passed in currently as top, or top is left to the default, then an error will be returned.

The is done as a block job, so upon completion a BLOCK_JOB_COMPLETED will be emitted.

qapi-schema.json +9 -0
index 14e4419..e614453 100644
@@ -1468,6 +1468,41 @@
   'returns': 'str' }
 
 ##
+# @block-commit
+#
+# Live commit of data from overlay image nodes into backing nodes - i.e.,
+# writes data between 'top' and 'base' into 'base'.
+#
+# @device:  the name of the device
+#
+# @base:   #optional The file name of the backing image to write data into.
+#                    If not specified, this is the deepest backing image
Quoted 9 lines

qapi-schema.json line 1484

Quoted 5 lines
qapi-schema.json +5 -0
+#
+# @top:    #optional The file name of the backing image within the image chain,
+#                    which contains the topmost data to be committed down.
+#                    If not specified, this is one layer below the active
+#                    layer (i.e. active->backing_hd).

Why isn't active the default any more? I know, we don't support it yet, but long term this is what makes most sense as a default.

Eric had a similar question, and asked if anyone had any preference - this was my response:

---

I guess I don't have a strong preference either - I originally had it the other way, but then that meant the default in the current implementation was actually an error.

Also, I assumed (danger!) that the most common use of commit would be a snapshot, followed by a commit of active->backing_hd. With that assumption, it seemed like a sane default.

---

I can certainly revert back to having the active layer be the top, if that is the preference.

Quoted 23 lines
Quoted 10 lines
qapi-schema.json +10 -0
+#
+#                    If top == base, that is an error.
+#
+#
+# @speed:  #optional the maximum speed, in bytes per second
+#
+# Returns: Nothing on success
+#          If commit or stream is already active on this device, DeviceInUse
+#          If @device does not exist, DeviceNotFound
+#          If image commit is not supported by this device, NotSupported

qapi-schema.json line 1504

Quoted 10 lines
qapi-schema.json +10 -0
+#          If @base does not exist, a generic error is returned
+#          If @top does not exist, a generic error is returned
+#          If @speed is invalid, InvalidParameter
+#
+# Since: 1.3
+#
+##
+{ 'command': 'block-commit',
+  'data': { 'device': 'str', '*base': 'str', '*top': 'str',
+            '*speed': 'int' } }

Kevin

#17 lore raw

Re: [Qemu-devel] [PATCH v2 5/7] QAPI: add command for live block commit, 'block-commit'

Am 26.09.2012 16:25, schrieb Jeff Cody:

Quoted 50 lines · Kevin Wolf
Quoted 39 lines

Am 25.09.2012 18:29, schrieb Jeff Cody:

Quoted 38 lines

The command for live block commit is added, which has the following arguments:

device: the block device to perform the commit on (mandatory)
base:   the base image to commit into; optional (if not specified,

it is the underlying original image)
top: the top image of the commit - all data from inside top down
to base will be committed into base. optional (if not specified, it is one below the active image) - see note below
speed: maximum speed, in bytes/sec

note: eventually this will support merging down the active layer,
but that code is not yet complete. If the active layer is passed in currently as top, or top is left to the default, then an error will be returned.

The is done as a block job, so upon completion a BLOCK_JOB_COMPLETED will be emitted.

qapi-schema.json +9 -0
index 14e4419..e614453 100644
@@ -1468,6 +1468,41 @@
   'returns': 'str' }
 
 ##
+# @block-commit
+#
+# Live commit of data from overlay image nodes into backing nodes - i.e.,
+# writes data between 'top' and 'base' into 'base'.
+#
+# @device:  the name of the device
+#
+# @base:   #optional The file name of the backing image to write data into.
+#                    If not specified, this is the deepest backing image
Quoted 9 lines

qapi-schema.json line 1484

Quoted 5 lines
qapi-schema.json +5 -0
+#
+# @top:    #optional The file name of the backing image within the image chain,
+#                    which contains the topmost data to be committed down.
+#                    If not specified, this is one layer below the active
+#                    layer (i.e. active->backing_hd).

Why isn't active the default any more? I know, we don't support it yet, but long term this is what makes most sense as a default.

Quoted 8 lines

Eric had a similar question, and asked if anyone had any preference - this was my response:

---

I guess I don't have a strong preference either - I originally had it the other way, but then that meant the default in the current implementation was actually an error.

We can make it non-optional for now and use active as the default once we introduce support for committing the active layer.

Quoted 8 lines

Also, I assumed (danger!) that the most common use of commit would be a snapshot, followed by a commit of active->backing_hd. With that assumption, it seemed like a sane default.

---

I can certainly revert back to having the active layer be the top, if that is the preference.

I think it is, if nothing else for consistency with the existing synchronous 'commit' command.

Kevin

#18 lore raw

Re: [Qemu-devel] [PATCH v2 5/7] QAPI: add command for live block commit, 'block-commit'

In reply to Jeff Cody
Quoted 14 lines · Jeff Cody
Quoted 8 lines

qapi-schema.json line 1484

Quoted 4 lines
qapi-schema.json +4 -0
+# @top:    #optional The file name of the backing image within the image chain,
+#                    which contains the topmost data to be committed down.
+#                    If not specified, this is one layer below the active
+#                    layer (i.e. active->backing_hd).

Why isn't active the default any more? I know, we don't support it yet, but long term this is what makes most sense as a default.

Eric had a similar question, and asked if anyone had any preference - this was my response:

---

Quoted 7 lines

I guess I don't have a strong preference either - I originally had it the other way, but then that meant the default in the current implementation was actually an error.

Also, I assumed (danger!) that the most common use of commit would be a snapshot, followed by a commit of active->backing_hd. With that assumption, it seemed like a sane default.

Actually, I envision my personal use case to be:

Take a snapshot, do an experiment in the guest (such as install a questionable package), and then either roll back to the snapshot (experiment failed) or commit the active (experiment worked, no need to have a snapshot any more); either way, taking the snapshot created a new temporary file name, and after I make my decision on whether to commit or discard the snapshot, I want to get back to the original file name. Since the snapshot effectively created a temporary file name, I'd rather not have to know the name of that temporary file just to pass it to an explicit 'top' argument when committing the active layer.

Having to specify 'top' to avoid an error when not committing the active layer is not as bad as getting the defaults wrong for when we do add support for committing the active layer.

Quoted 4 lines

---

I can certainly revert back to having the active layer be the top, if that is the preference.

Given that it has been asked again, I'd say yes, go ahead and revert to the behavior of defaulting 'top' to the active layer.

Signature · 2 lines
Eric Blake   [email protected]    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#19 lore raw

PATCH v2 6/7 qemu-iotests: add initial tests for live block commit

In reply to Jeff Cody

Derived from the streaming test cases (030), this adds the following tests:

1. For the following image chain, commit [mid] into [backing], and use qemu-io to verify [backing] has its original data, as well as the data from [mid]

           [backing] <-- [mid] <-- [test]

2. Verifies that 'block-commit' with the 'speed' parameter sets the speed parameter, as reported by 'query-block-jobs'

3. Verifies that a bogus 'device' parameter to 'block-commit' results in error

---
 tests/qemu-iotests/040     | 142 +++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/040.out |   5 ++
 tests/qemu-iotests/group   |   1 +
 3 files changed, 148 insertions(+)
 create mode 100755 tests/qemu-iotests/040
 create mode 100644 tests/qemu-iotests/040.out
tests/qemu-iotests/040 new file +142 -0
new file mode 100755
index 0000000..6b89a41
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+#
+# Tests for image block commit.
+#
+# Copyright (C) 2012 IBM, Corp.
+# Copyright (C) 2012 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Test for live block commit
+# Derived from Image Streaming Test 030
+
+import time
+import os
+import iotests
+from iotests import qemu_img, qemu_io
+import struct
+
+backing_img = os.path.join(iotests.test_dir, 'backing.img')
+mid_img = os.path.join(iotests.test_dir, 'mid.img')
+test_img = os.path.join(iotests.test_dir, 'test.img')
+
+class ImageCommitTestCase(iotests.QMPTestCase):
+    '''Abstract base class for image commit test cases'''
+
+    def assert_no_active_commit(self):
+        result = self.vm.qmp('query-block-jobs')
+        self.assert_qmp(result, 'return', [])
+
+    def cancel_and_wait(self, drive='drive0'):
+        '''Cancel a block job and wait for it to finish'''
+        result = self.vm.qmp('block-job-cancel', device=drive)
+        self.assert_qmp(result, 'return', {})
+
+        cancelled = False
+        while not cancelled:
+            for event in self.vm.get_qmp_events(wait=True):
+                if event['event'] == 'BLOCK_JOB_CANCELLED':
+                    self.assert_qmp(event, 'data/type', 'commit')
+                    self.assert_qmp(event, 'data/device', drive)
+                    cancelled = True
+
+        self.assert_no_active_commit()
+
+    def create_image(self, name, size):
+        file = open(name, 'w')
+        i = 0
+        while i < size:
+            sector = struct.pack('>l504xl', i / 512, i / 512)
+            file.write(sector)
+            i = i + 512
+        file.close()
+
+
+class TestSingleDrive(ImageCommitTestCase):
+    image_len = 1 * 1024 * 1024
+    test_len = 1 * 1024 * 256
+
+    def setUp(self):
+        self.create_image(backing_img, TestSingleDrive.image_len)
+        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img)
+        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img)
+        qemu_io('-c', 'write -P 0xab 0 524288', backing_img)
+        qemu_io('-c', 'write -P 0xef 524288 524288', mid_img)
+        self.vm = iotests.VM().add_drive(test_img)
+        self.vm.launch()
+
+    def tearDown(self):
+        self.vm.shutdown()
+        os.remove(test_img)
+        os.remove(mid_img)
+        os.remove(backing_img)
+
+    def test_commit(self):
+        self.assert_no_active_commit()
+        result = self.vm.qmp('block-commit', device='drive0')
+        self.assert_qmp(result, 'return', {})
+
+        completed = False
+        while not completed:
+            for event in self.vm.get_qmp_events(wait=True):
+                if event['event'] == 'BLOCK_JOB_COMPLETED':
+                    self.assert_qmp(event, 'data/type', 'commit')
+                    self.assert_qmp(event, 'data/device', 'drive0')
+                    self.assert_qmp(event, 'data/offset', self.image_len)
+                    self.assert_qmp(event, 'data/len', self.image_len)
+                    completed = True
+
+        self.assert_no_active_commit()
+        self.vm.shutdown()
+
+        self.assertEqual(-1, qemu_io('-c', 'read -P 0xab 0 524288', backing_img).find("verification failed"))
+        self.assertEqual(-1, qemu_io('-c', 'read -P 0xef 524288 524288', backing_img).find("verification failed"))
+
+    def test_device_not_found(self):
+        result = self.vm.qmp('block-commit', device='nonexistent')
+        self.assert_qmp(result, 'error/class', 'DeviceNotFound')
+
+
+class TestSetSpeed(ImageCommitTestCase):
+    image_len = 80 * 1024 * 1024 # MB
+
+    def setUp(self):
+        qemu_img('create', backing_img, str(TestSetSpeed.image_len))
+        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, mid_img)
+        qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % mid_img, test_img)
+        self.vm = iotests.VM().add_drive(test_img)
+        self.vm.launch()
+
+    def tearDown(self):
+        self.vm.shutdown()
+        os.remove(test_img)
+        os.remove(mid_img)
+        os.remove(backing_img)
+
+    def test_set_speed(self):
+        self.assert_no_active_commit()
+
+        result = self.vm.qmp('block-commit', device='drive0', top=mid_img, speed=1024 * 1024)
+        self.assert_qmp(result, 'return', {})
+
+        # Ensure the speed we set was accepted
+        result = self.vm.qmp('query-block-jobs')
+        self.assert_qmp(result, 'return[0]/device', 'drive0')
+        self.assert_qmp(result, 'return[0]/speed', 1024 * 1024)
+
+        self.cancel_and_wait()
+
+
+if __name__ == '__main__':
+    iotests.main(supported_fmts=['qcow2', 'qed'])
tests/qemu-iotests/040.out new file +5 -0
new file mode 100644
index 0000000..8d7e996
@@ -0,0 +1,5 @@
+...
+----------------------------------------------------------------------
+Ran 3 tests
+
+OK
tests/qemu-iotests/group +1 -0
index ebb5ca4..4b54fa6 100644
@@ -46,3 +46,4 @@
 037 rw auto backing
 038 rw auto backing
 039 rw auto
+040 rw auto
Signature · 1 line
1.7.11.4
#20 lore raw

Re: [Qemu-devel] [PATCH v2 6/7] qemu-iotests: add initial tests for live block commit

Quoted 7 lines · Jeff Cody

Derived from the streaming test cases (030), this adds the following tests:

1. For the following image chain, commit [mid] into [backing], and use qemu-io to verify [backing] has its original data, as well as the data from [mid]

Quoted 7 lines
           [backing] <-- [mid] <-- [test]

2. Verifies that 'block-commit' with the 'speed' parameter sets the speed parameter, as reported by 'query-block-jobs'

3. Verifies that a bogus 'device' parameter to 'block-commit' results in error

I think you are missing a test; you should also verify that:

{ "command":"block-commit", "arguments":{
"device":"drive0", "base":"mid", "top":"backing" } }

properly fails, since 'mid' is not a backing file of 'backing'. I saw code in patch 1/7 that bdrv_drop_intermediate() should detect the situation, but I'm not confident enough in my reading of patch 2/7 to know if that detection point was early enough, or whether the coroutine stuff in 2/7 ends up corrupting 'mid' prior to failure.

Signature · 2 lines
Eric Blake   [email protected]    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
author
#21 lore raw

Re: [Qemu-devel] [PATCH v2 6/7] qemu-iotests: add initial tests for live block commit

Quoted 18 lines · Eric Blake
Quoted 7 lines · Jeff Cody

Derived from the streaming test cases (030), this adds the following tests:

1. For the following image chain, commit [mid] into [backing], and use qemu-io to verify [backing] has its original data, as well as the data from [mid]

Quoted 7 lines
           [backing] <-- [mid] <-- [test]

2. Verifies that 'block-commit' with the 'speed' parameter sets the speed parameter, as reported by 'query-block-jobs'

3. Verifies that a bogus 'device' parameter to 'block-commit' results in error

I think you are missing a test; you should also verify that:

Quoted 9 lines

{ "command":"block-commit", "arguments":{
"device":"drive0", "base":"mid", "top":"backing" } }

properly fails, since 'mid' is not a backing file of 'backing'. I saw code in patch 1/7 that bdrv_drop_intermediate() should detect the situation, but I'm not confident enough in my reading of patch 2/7 to know if that detection point was early enough, or whether the coroutine stuff in 2/7 ends up corrupting 'mid' prior to failure.

Good idea. This seems like a good test to have in place.

author
#22 lore raw

PATCH v2 7/7 block: after creating a live snapshot, make old image read-only

In reply to Jeff Cody

Currently, after a live snapshot of a drive, the image that has been 'demoted' to be below the new active layer remains r/w. This patch reopens it read-only.

Note that we do not check for error on the reopen(), because we will not abort the snapshots if the reopen fails.

This patch depends on the bdrv_reopen() series.

---
 blockdev.c | 5 +++++
 1 file changed, 5 insertions(+)
blockdev.c +5 -0
index 6f1080c..fa7efae 100644
@@ -803,6 +803,11 @@ void qmp_transaction(BlockdevActionList *dev_list, Error **errp)
     QSIMPLEQ_FOREACH(states, &snap_bdrv_states, entry) {
         /* This removes our old bs from the bdrv_states, and adds the new bs */
         bdrv_append(states->new_bs, states->old_bs);
+        /* We don't need (or want) to use the transactional
+         * bdrv_reopen_multiple() across all the entries at once, because we
+         * don't want to abort all of them if one of them fails the reopen */
+        bdrv_reopen(states->new_bs, states->new_bs->open_flags & ~BDRV_O_RDWR,
+                    NULL);
     }
 
     /* success */
Signature · 1 line
1.7.11.4
#23 lore raw

Re: [Qemu-devel] [PATCH v2 7/7] block: after creating a live snapshot, make old image read-only

Am 25.09.2012 18:29, schrieb Jeff Cody:

Quoted 10 lines

Currently, after a live snapshot of a drive, the image that has been 'demoted' to be below the new active layer remains r/w. This patch reopens it read-only.

Note that we do not check for error on the reopen(), because we will not abort the snapshots if the reopen fails.

This patch depends on the bdrv_reopen() series.

This should be independent from the live commit patches, so I already applied this one to the block branch.

Kevin

author
#24 lore raw

Re: [Qemu-devel] [PATCH v2 7/7] block: after creating a live snapshot, make old image read-only

Quoted 8 lines · Kevin Wolf

Am 25.09.2012 18:29, schrieb Jeff Cody:

Quoted 7 lines

Currently, after a live snapshot of a drive, the image that has been 'demoted' to be below the new active layer remains r/w. This patch reopens it read-only.

Note that we do not check for error on the reopen(), because we will not abort the snapshots if the reopen fails.

Quoted 9 lines
Quoted 3 lines

This patch depends on the bdrv_reopen() series.

This should be independent from the live commit patches, so I already applied this one to the block branch.

Kevin

Thanks