mirror of
https://github.com/qemu/qemu.git
synced 2025-08-16 14:54:29 +00:00
block-job patches
- deprecate some old block-job- APIs - on-cbw-error option for backup - more efficient zero handling in block commit -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEi5wmzbL9FHyIDoahVh8kwfGfefsFAmgiEpwACgkQVh8kwfGf eftuPw//UWU7MN7Kd8Tc7x/5xJuVOiuOUp8iu78EBtvJy7+yy6lZxDmrVSpob3pI fiIjZRd0LTO0/hu5nLeTqyGs8cthKNO+hHO1i8xIQuOVC3WqdbCYkiXYUjcHJCeT ZD2xR2l3F/cjBHXnp7w8K2wuqd4OGjvUpw/JG3mvkDp6uAMJBp+qccAtiCXKLAGv a4qvFt02TIi7IZYoEyRN+NGuwYvmwrD0TPSbWDzroYsmdZyz93dZniiWkV8elheW iCDzv4AG1yquAbw6INW3BRWblBYWCLSvrtMVN9XAYf8R+b75bDghUzdHPyFiitsL aenMMPaNeH1z0jB7oSLrRWx12eCfuRy5UTeil+RQsH9HsGCu7C5yBWkuAyZwlVk1 Qdu3SQ6HGk6BYET0TSRgk/fivmVq14vYxCFWbwclBEuN1HyNxwDJHZE3YxsqGZnM KM1rByFViOCA+bjw00dFrn18wO8XRWHmRjed8KMAOZvc3jvJUdlr5OR3zfw3RR8l bpBETylF7d7IpPs6LnxX08SAMBGLYzQe4rvguxjQ/2YB8C9KBkTodygKUYXR3Afw Wp+vOVmG03XzOdaffuB9VAfyZrE7QmhbdWZTQVBcoqu/oHUbukHboB5p68L3oHXy 0AxHjMyaW5d01JELU0Mlj1+R8e+nK2kTq17v+ghmdX/LyySUyzc= =tjus -----END PGP SIGNATURE----- Merge tag 'pull-block-jobs-2025-04-29-v3' of https://gitlab.com/vsementsov/qemu into staging block-job patches - deprecate some old block-job- APIs - on-cbw-error option for backup - more efficient zero handling in block commit # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCgAdFiEEi5wmzbL9FHyIDoahVh8kwfGfefsFAmgiEpwACgkQVh8kwfGf # eftuPw//UWU7MN7Kd8Tc7x/5xJuVOiuOUp8iu78EBtvJy7+yy6lZxDmrVSpob3pI # fiIjZRd0LTO0/hu5nLeTqyGs8cthKNO+hHO1i8xIQuOVC3WqdbCYkiXYUjcHJCeT # ZD2xR2l3F/cjBHXnp7w8K2wuqd4OGjvUpw/JG3mvkDp6uAMJBp+qccAtiCXKLAGv # a4qvFt02TIi7IZYoEyRN+NGuwYvmwrD0TPSbWDzroYsmdZyz93dZniiWkV8elheW # iCDzv4AG1yquAbw6INW3BRWblBYWCLSvrtMVN9XAYf8R+b75bDghUzdHPyFiitsL # aenMMPaNeH1z0jB7oSLrRWx12eCfuRy5UTeil+RQsH9HsGCu7C5yBWkuAyZwlVk1 # Qdu3SQ6HGk6BYET0TSRgk/fivmVq14vYxCFWbwclBEuN1HyNxwDJHZE3YxsqGZnM # KM1rByFViOCA+bjw00dFrn18wO8XRWHmRjed8KMAOZvc3jvJUdlr5OR3zfw3RR8l # bpBETylF7d7IpPs6LnxX08SAMBGLYzQe4rvguxjQ/2YB8C9KBkTodygKUYXR3Afw # Wp+vOVmG03XzOdaffuB9VAfyZrE7QmhbdWZTQVBcoqu/oHUbukHboB5p68L3oHXy # 0AxHjMyaW5d01JELU0Mlj1+R8e+nK2kTq17v+ghmdX/LyySUyzc= # =tjus # -----END PGP SIGNATURE----- # gpg: Signature made Mon 12 May 2025 11:24:12 EDT # gpg: using RSA key 8B9C26CDB2FD147C880E86A1561F24C1F19F79FB # gpg: Good signature from "Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>" [unknown] # gpg: aka "Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: 8B9C 26CD B2FD 147C 880E 86A1 561F 24C1 F19F 79FB * tag 'pull-block-jobs-2025-04-29-v3' of https://gitlab.com/vsementsov/qemu: blockdev-backup: Add error handling option for copy-before-write jobs qapi/block-core: deprecate some block-job- APIs qapi: synchronize jobs and block-jobs documentation block: add test non-active commit with zeroed data block: allow commit to unmap zero blocks block: refactor error handling of commit_iteration block: move commit_run loop to separate function block: get type of block allocation in commit_run Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
commit
a114a6a539
@ -361,6 +361,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
|||||||
BackupPerf *perf,
|
BackupPerf *perf,
|
||||||
BlockdevOnError on_source_error,
|
BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
|
OnCbwError on_cbw_error,
|
||||||
int creation_flags,
|
int creation_flags,
|
||||||
BlockCompletionFunc *cb, void *opaque,
|
BlockCompletionFunc *cb, void *opaque,
|
||||||
JobTxn *txn, Error **errp)
|
JobTxn *txn, Error **errp)
|
||||||
@ -458,7 +459,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
cbw = bdrv_cbw_append(bs, target, filter_node_name, discard_source,
|
cbw = bdrv_cbw_append(bs, target, filter_node_name, discard_source,
|
||||||
perf->min_cluster_size, &bcs, errp);
|
perf->min_cluster_size, &bcs, on_cbw_error, errp);
|
||||||
if (!cbw) {
|
if (!cbw) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
116
block/commit.c
116
block/commit.c
@ -15,6 +15,8 @@
|
|||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
#include "qemu/cutils.h"
|
#include "qemu/cutils.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
#include "block/block-common.h"
|
||||||
|
#include "block/coroutines.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "block/blockjob_int.h"
|
#include "block/blockjob_int.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
@ -126,6 +128,84 @@ static void commit_clean(Job *job)
|
|||||||
blk_unref(s->top);
|
blk_unref(s->top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int commit_iteration(CommitBlockJob *s, int64_t offset,
|
||||||
|
int64_t *requested_bytes, void *buf)
|
||||||
|
{
|
||||||
|
BlockErrorAction action;
|
||||||
|
int64_t bytes = *requested_bytes;
|
||||||
|
int ret = 0;
|
||||||
|
bool error_in_source = true;
|
||||||
|
|
||||||
|
/* Copy if allocated above the base */
|
||||||
|
WITH_GRAPH_RDLOCK_GUARD() {
|
||||||
|
ret = bdrv_co_common_block_status_above(blk_bs(s->top),
|
||||||
|
s->base_overlay, true, true, offset, COMMIT_BUFFER_SIZE,
|
||||||
|
&bytes, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_commit_one_iteration(s, offset, bytes, ret);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret & BDRV_BLOCK_ALLOCATED) {
|
||||||
|
if (ret & BDRV_BLOCK_ZERO) {
|
||||||
|
/*
|
||||||
|
* If the top (sub)clusters are smaller than the base
|
||||||
|
* (sub)clusters, this will not unmap unless the underlying device
|
||||||
|
* does some tracking of these requests. Ideally, we would find
|
||||||
|
* the maximal extent of the zero clusters.
|
||||||
|
*/
|
||||||
|
ret = blk_co_pwrite_zeroes(s->base, offset, bytes,
|
||||||
|
BDRV_REQ_MAY_UNMAP);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_in_source = false;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(bytes < SIZE_MAX);
|
||||||
|
|
||||||
|
ret = blk_co_pread(s->top, offset, bytes, buf, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = blk_co_pwrite(s->base, offset, bytes, buf, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_in_source = false;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether zeroes actually end up on disk depends on the details of
|
||||||
|
* the underlying driver. Therefore, this might rate limit more than
|
||||||
|
* is necessary.
|
||||||
|
*/
|
||||||
|
block_job_ratelimit_processed_bytes(&s->common, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Publish progress */
|
||||||
|
|
||||||
|
job_progress_update(&s->common.job, bytes);
|
||||||
|
|
||||||
|
*requested_bytes = bytes;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
action = block_job_error_action(&s->common, s->on_error,
|
||||||
|
error_in_source, -ret);
|
||||||
|
if (action == BLOCK_ERROR_ACTION_REPORT) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
*requested_bytes = 0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int coroutine_fn commit_run(Job *job, Error **errp)
|
static int coroutine_fn commit_run(Job *job, Error **errp)
|
||||||
{
|
{
|
||||||
CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
|
CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
|
||||||
@ -156,9 +236,6 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
|
|||||||
buf = blk_blockalign(s->top, COMMIT_BUFFER_SIZE);
|
buf = blk_blockalign(s->top, COMMIT_BUFFER_SIZE);
|
||||||
|
|
||||||
for (offset = 0; offset < len; offset += n) {
|
for (offset = 0; offset < len; offset += n) {
|
||||||
bool copy;
|
|
||||||
bool error_in_source = true;
|
|
||||||
|
|
||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* with no pending I/O here so that bdrv_drain_all() returns.
|
* with no pending I/O here so that bdrv_drain_all() returns.
|
||||||
*/
|
*/
|
||||||
@ -166,38 +243,11 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
|
|||||||
if (job_is_cancelled(&s->common.job)) {
|
if (job_is_cancelled(&s->common.job)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Copy if allocated above the base */
|
|
||||||
ret = blk_co_is_allocated_above(s->top, s->base_overlay, true,
|
|
||||||
offset, COMMIT_BUFFER_SIZE, &n);
|
|
||||||
copy = (ret > 0);
|
|
||||||
trace_commit_one_iteration(s, offset, n, ret);
|
|
||||||
if (copy) {
|
|
||||||
assert(n < SIZE_MAX);
|
|
||||||
|
|
||||||
ret = blk_co_pread(s->top, offset, n, buf, 0);
|
ret = commit_iteration(s, offset, &n, buf);
|
||||||
if (ret >= 0) {
|
|
||||||
ret = blk_co_pwrite(s->base, offset, n, buf, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_in_source = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
BlockErrorAction action =
|
return ret;
|
||||||
block_job_error_action(&s->common, s->on_error,
|
|
||||||
error_in_source, -ret);
|
|
||||||
if (action == BLOCK_ERROR_ACTION_REPORT) {
|
|
||||||
return ret;
|
|
||||||
} else {
|
|
||||||
n = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Publish progress */
|
|
||||||
job_progress_update(&s->common.job, n);
|
|
||||||
|
|
||||||
if (copy) {
|
|
||||||
block_job_ratelimit_processed_bytes(&s->common, n);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,6 +551,7 @@ BlockDriverState *bdrv_cbw_append(BlockDriverState *source,
|
|||||||
bool discard_source,
|
bool discard_source,
|
||||||
uint64_t min_cluster_size,
|
uint64_t min_cluster_size,
|
||||||
BlockCopyState **bcs,
|
BlockCopyState **bcs,
|
||||||
|
OnCbwError on_cbw_error,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
BDRVCopyBeforeWriteState *state;
|
BDRVCopyBeforeWriteState *state;
|
||||||
@ -568,6 +569,7 @@ BlockDriverState *bdrv_cbw_append(BlockDriverState *source,
|
|||||||
}
|
}
|
||||||
qdict_put_str(opts, "file", bdrv_get_node_name(source));
|
qdict_put_str(opts, "file", bdrv_get_node_name(source));
|
||||||
qdict_put_str(opts, "target", bdrv_get_node_name(target));
|
qdict_put_str(opts, "target", bdrv_get_node_name(target));
|
||||||
|
qdict_put_str(opts, "on-cbw-error", OnCbwError_str(on_cbw_error));
|
||||||
|
|
||||||
if (min_cluster_size > INT64_MAX) {
|
if (min_cluster_size > INT64_MAX) {
|
||||||
error_setg(errp, "min-cluster-size too large: %" PRIu64 " > %" PRIi64,
|
error_setg(errp, "min-cluster-size too large: %" PRIu64 " > %" PRIi64,
|
||||||
|
@ -42,6 +42,7 @@ BlockDriverState *bdrv_cbw_append(BlockDriverState *source,
|
|||||||
bool discard_source,
|
bool discard_source,
|
||||||
uint64_t min_cluster_size,
|
uint64_t min_cluster_size,
|
||||||
BlockCopyState **bcs,
|
BlockCopyState **bcs,
|
||||||
|
OnCbwError on_cbw_error,
|
||||||
Error **errp);
|
Error **errp);
|
||||||
void bdrv_cbw_drop(BlockDriverState *bs);
|
void bdrv_cbw_drop(BlockDriverState *bs);
|
||||||
|
|
||||||
|
@ -583,7 +583,9 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
|
|||||||
0, MIRROR_SYNC_MODE_NONE, NULL, 0, false, false,
|
0, MIRROR_SYNC_MODE_NONE, NULL, 0, false, false,
|
||||||
NULL, &perf,
|
NULL, &perf,
|
||||||
BLOCKDEV_ON_ERROR_REPORT,
|
BLOCKDEV_ON_ERROR_REPORT,
|
||||||
BLOCKDEV_ON_ERROR_REPORT, JOB_INTERNAL,
|
BLOCKDEV_ON_ERROR_REPORT,
|
||||||
|
ON_CBW_ERROR_BREAK_GUEST_WRITE,
|
||||||
|
JOB_INTERNAL,
|
||||||
backup_job_completed, bs, NULL, &local_err);
|
backup_job_completed, bs, NULL, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
|
@ -2641,6 +2641,7 @@ static BlockJob *do_backup_common(BackupCommon *backup,
|
|||||||
BdrvDirtyBitmap *bmap = NULL;
|
BdrvDirtyBitmap *bmap = NULL;
|
||||||
BackupPerf perf = { .max_workers = 64 };
|
BackupPerf perf = { .max_workers = 64 };
|
||||||
int job_flags = JOB_DEFAULT;
|
int job_flags = JOB_DEFAULT;
|
||||||
|
OnCbwError on_cbw_error = ON_CBW_ERROR_BREAK_GUEST_WRITE;
|
||||||
|
|
||||||
if (!backup->has_speed) {
|
if (!backup->has_speed) {
|
||||||
backup->speed = 0;
|
backup->speed = 0;
|
||||||
@ -2745,6 +2746,10 @@ static BlockJob *do_backup_common(BackupCommon *backup,
|
|||||||
job_flags |= JOB_MANUAL_DISMISS;
|
job_flags |= JOB_MANUAL_DISMISS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (backup->has_on_cbw_error) {
|
||||||
|
on_cbw_error = backup->on_cbw_error;
|
||||||
|
}
|
||||||
|
|
||||||
job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
|
job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
|
||||||
backup->sync, bmap, backup->bitmap_mode,
|
backup->sync, bmap, backup->bitmap_mode,
|
||||||
backup->compress, backup->discard_source,
|
backup->compress, backup->discard_source,
|
||||||
@ -2752,6 +2757,7 @@ static BlockJob *do_backup_common(BackupCommon *backup,
|
|||||||
&perf,
|
&perf,
|
||||||
backup->on_source_error,
|
backup->on_source_error,
|
||||||
backup->on_target_error,
|
backup->on_target_error,
|
||||||
|
on_cbw_error,
|
||||||
job_flags, NULL, NULL, txn, errp);
|
job_flags, NULL, NULL, txn, errp);
|
||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,37 @@ options are removed in favor of using explicit ``blockdev-create`` and
|
|||||||
``blockdev-add`` calls. See :doc:`/interop/live-block-operations` for
|
``blockdev-add`` calls. See :doc:`/interop/live-block-operations` for
|
||||||
details.
|
details.
|
||||||
|
|
||||||
|
``block-job-pause`` (since 10.1)
|
||||||
|
''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
Use ``job-pause`` instead. The only difference is that ``job-pause``
|
||||||
|
always reports GenericError on failure when ``block-job-pause`` reports
|
||||||
|
DeviceNotActive when block-job is not found.
|
||||||
|
|
||||||
|
``block-job-resume`` (since 10.1)
|
||||||
|
'''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
Use ``job-resume`` instead. The only difference is that ``job-resume``
|
||||||
|
always reports GenericError on failure when ``block-job-resume`` reports
|
||||||
|
DeviceNotActive when block-job is not found.
|
||||||
|
|
||||||
|
``block-job-complete`` (since 10.1)
|
||||||
|
'''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
Use ``job-complete`` instead. The only difference is that ``job-complete``
|
||||||
|
always reports GenericError on failure when ``block-job-complete`` reports
|
||||||
|
DeviceNotActive when block-job is not found.
|
||||||
|
|
||||||
|
``block-job-dismiss`` (since 10.1)
|
||||||
|
''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
Use ``job-dismiss`` instead.
|
||||||
|
|
||||||
|
``block-job-finalize`` (since 10.1)
|
||||||
|
'''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
Use ``job-finalize`` instead.
|
||||||
|
|
||||||
``query-migrationthreads`` (since 9.2)
|
``query-migrationthreads`` (since 9.2)
|
||||||
''''''''''''''''''''''''''''''''''''''
|
''''''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
@ -179,6 +179,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
|
|||||||
* all ".has_*" fields are ignored.
|
* all ".has_*" fields are ignored.
|
||||||
* @on_source_error: The action to take upon error reading from the source.
|
* @on_source_error: The action to take upon error reading from the source.
|
||||||
* @on_target_error: The action to take upon error writing to the target.
|
* @on_target_error: The action to take upon error writing to the target.
|
||||||
|
* @on_cbw_error: The action to take upon error in copy-before-write operations.
|
||||||
* @creation_flags: Flags that control the behavior of the Job lifetime.
|
* @creation_flags: Flags that control the behavior of the Job lifetime.
|
||||||
* See @BlockJobCreateFlags
|
* See @BlockJobCreateFlags
|
||||||
* @cb: Completion function for the job.
|
* @cb: Completion function for the job.
|
||||||
@ -198,6 +199,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
|||||||
BackupPerf *perf,
|
BackupPerf *perf,
|
||||||
BlockdevOnError on_source_error,
|
BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
|
OnCbwError on_cbw_error,
|
||||||
int creation_flags,
|
int creation_flags,
|
||||||
BlockCompletionFunc *cb, void *opaque,
|
BlockCompletionFunc *cb, void *opaque,
|
||||||
JobTxn *txn, Error **errp);
|
JobTxn *txn, Error **errp);
|
||||||
|
@ -1602,6 +1602,9 @@
|
|||||||
# default 'report' (no limitations, since this applies to a
|
# default 'report' (no limitations, since this applies to a
|
||||||
# different block device than @device).
|
# different block device than @device).
|
||||||
#
|
#
|
||||||
|
# @on-cbw-error: policy defining behavior on I/O errors in
|
||||||
|
# copy-before-write jobs; defaults to break-guest-write. (Since 10.1)
|
||||||
|
#
|
||||||
# @auto-finalize: When false, this job will wait in a PENDING state
|
# @auto-finalize: When false, this job will wait in a PENDING state
|
||||||
# after it has finished its work, waiting for @block-job-finalize
|
# after it has finished its work, waiting for @block-job-finalize
|
||||||
# before making any block graph changes. When true, this job will
|
# before making any block graph changes. When true, this job will
|
||||||
@ -1641,6 +1644,7 @@
|
|||||||
'*compress': 'bool',
|
'*compress': 'bool',
|
||||||
'*on-source-error': 'BlockdevOnError',
|
'*on-source-error': 'BlockdevOnError',
|
||||||
'*on-target-error': 'BlockdevOnError',
|
'*on-target-error': 'BlockdevOnError',
|
||||||
|
'*on-cbw-error': 'OnCbwError',
|
||||||
'*auto-finalize': 'bool', '*auto-dismiss': 'bool',
|
'*auto-finalize': 'bool', '*auto-dismiss': 'bool',
|
||||||
'*filter-node-name': 'str',
|
'*filter-node-name': 'str',
|
||||||
'*discard-source': 'bool',
|
'*discard-source': 'bool',
|
||||||
@ -2956,18 +2960,24 @@
|
|||||||
#
|
#
|
||||||
# Pause an active background block operation.
|
# Pause an active background block operation.
|
||||||
#
|
#
|
||||||
# This command returns immediately after marking the active background
|
# This command returns immediately after marking the active job for
|
||||||
# block operation for pausing. It is an error to call this command if
|
# pausing. Pausing an already paused job is an error.
|
||||||
# no operation is in progress or if the job is already paused.
|
|
||||||
#
|
#
|
||||||
# The operation will pause as soon as possible. No event is emitted
|
# The job will pause as soon as possible, which means transitioning
|
||||||
# when the operation is actually paused. Cancelling a paused job
|
# into the PAUSED state if it was RUNNING, or into STANDBY if it was
|
||||||
# automatically resumes it.
|
# READY. The corresponding JOB_STATUS_CHANGE event will be emitted.
|
||||||
|
#
|
||||||
|
# Cancelling a paused job automatically resumes it.
|
||||||
#
|
#
|
||||||
# @device: The job identifier. This used to be a device name (hence
|
# @device: The job identifier. This used to be a device name (hence
|
||||||
# the name of the parameter), but since QEMU 2.7 it can have other
|
# the name of the parameter), but since QEMU 2.7 it can have other
|
||||||
# values.
|
# values.
|
||||||
#
|
#
|
||||||
|
# Features:
|
||||||
|
#
|
||||||
|
# @deprecated: This command is deprecated. Use @job-pause
|
||||||
|
# instead.
|
||||||
|
#
|
||||||
# Errors:
|
# Errors:
|
||||||
# - If no background operation is active on this device,
|
# - If no background operation is active on this device,
|
||||||
# DeviceNotActive
|
# DeviceNotActive
|
||||||
@ -2975,6 +2985,7 @@
|
|||||||
# Since: 1.3
|
# Since: 1.3
|
||||||
##
|
##
|
||||||
{ 'command': 'block-job-pause', 'data': { 'device': 'str' },
|
{ 'command': 'block-job-pause', 'data': { 'device': 'str' },
|
||||||
|
'features': ['deprecated'],
|
||||||
'allow-preconfig': true }
|
'allow-preconfig': true }
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -2982,9 +2993,8 @@
|
|||||||
#
|
#
|
||||||
# Resume an active background block operation.
|
# Resume an active background block operation.
|
||||||
#
|
#
|
||||||
# This command returns immediately after resuming a paused background
|
# This command returns immediately after resuming a paused job.
|
||||||
# block operation. It is an error to call this command if no
|
# Resuming an already running job is an error.
|
||||||
# operation is in progress or if the job is not paused.
|
|
||||||
#
|
#
|
||||||
# This command also clears the error status of the job.
|
# This command also clears the error status of the job.
|
||||||
#
|
#
|
||||||
@ -2992,6 +3002,11 @@
|
|||||||
# the name of the parameter), but since QEMU 2.7 it can have other
|
# the name of the parameter), but since QEMU 2.7 it can have other
|
||||||
# values.
|
# values.
|
||||||
#
|
#
|
||||||
|
# Features:
|
||||||
|
#
|
||||||
|
# @deprecated: This command is deprecated. Use @job-resume
|
||||||
|
# instead.
|
||||||
|
#
|
||||||
# Errors:
|
# Errors:
|
||||||
# - If no background operation is active on this device,
|
# - If no background operation is active on this device,
|
||||||
# DeviceNotActive
|
# DeviceNotActive
|
||||||
@ -2999,15 +3014,21 @@
|
|||||||
# Since: 1.3
|
# Since: 1.3
|
||||||
##
|
##
|
||||||
{ 'command': 'block-job-resume', 'data': { 'device': 'str' },
|
{ 'command': 'block-job-resume', 'data': { 'device': 'str' },
|
||||||
|
'features': ['deprecated'],
|
||||||
'allow-preconfig': true }
|
'allow-preconfig': true }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @block-job-complete:
|
# @block-job-complete:
|
||||||
#
|
#
|
||||||
# Manually trigger completion of an active background block operation.
|
# Manually trigger completion of an active job in the READY or STANDBY
|
||||||
# This is supported for drive mirroring, where it also switches the
|
# state. Completing the job in any other state is an error.
|
||||||
# device to write to the target path only. The ability to complete is
|
#
|
||||||
# signaled with a BLOCK_JOB_READY event.
|
# This is supported only for drive mirroring, where it also switches
|
||||||
|
# the device to write to the target path only. Note that drive
|
||||||
|
# mirroring includes drive-mirror, blockdev-mirror and block-commit
|
||||||
|
# job (only in case of "active commit", when the node being commited
|
||||||
|
# is used by the guest). The ability to complete is signaled with a
|
||||||
|
# BLOCK_JOB_READY event.
|
||||||
#
|
#
|
||||||
# This command completes an active background block operation
|
# This command completes an active background block operation
|
||||||
# synchronously. The ordering of this command's return with the
|
# synchronously. The ordering of this command's return with the
|
||||||
@ -3017,12 +3038,15 @@
|
|||||||
# rerror/werror arguments that were specified when starting the
|
# rerror/werror arguments that were specified when starting the
|
||||||
# operation.
|
# operation.
|
||||||
#
|
#
|
||||||
# A cancelled or paused job cannot be completed.
|
|
||||||
#
|
|
||||||
# @device: The job identifier. This used to be a device name (hence
|
# @device: The job identifier. This used to be a device name (hence
|
||||||
# the name of the parameter), but since QEMU 2.7 it can have other
|
# the name of the parameter), but since QEMU 2.7 it can have other
|
||||||
# values.
|
# values.
|
||||||
#
|
#
|
||||||
|
# Features:
|
||||||
|
#
|
||||||
|
# @deprecated: This command is deprecated. Use @job-complete
|
||||||
|
# instead.
|
||||||
|
#
|
||||||
# Errors:
|
# Errors:
|
||||||
# - If no background operation is active on this device,
|
# - If no background operation is active on this device,
|
||||||
# DeviceNotActive
|
# DeviceNotActive
|
||||||
@ -3030,15 +3054,19 @@
|
|||||||
# Since: 1.3
|
# Since: 1.3
|
||||||
##
|
##
|
||||||
{ 'command': 'block-job-complete', 'data': { 'device': 'str' },
|
{ 'command': 'block-job-complete', 'data': { 'device': 'str' },
|
||||||
|
'features': ['deprecated'],
|
||||||
'allow-preconfig': true }
|
'allow-preconfig': true }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @block-job-dismiss:
|
# @block-job-dismiss:
|
||||||
#
|
#
|
||||||
# For jobs that have already concluded, remove them from the
|
# Deletes a job that is in the CONCLUDED state. This command only
|
||||||
# block-job-query list. This command only needs to be run for jobs
|
# needs to be run explicitly for jobs that don't have automatic
|
||||||
# which were started with QEMU 2.12+ job lifetime management
|
# dismiss enabled. In turn, automatic dismiss may be enabled only
|
||||||
# semantics.
|
# for jobs that have @auto-dismiss option, which are drive-backup,
|
||||||
|
# blockdev-backup, drive-mirror, blockdev-mirror, block-commit and
|
||||||
|
# block-stream. @auto-dismiss is enabled by default for these
|
||||||
|
# jobs.
|
||||||
#
|
#
|
||||||
# This command will refuse to operate on any job that has not yet
|
# This command will refuse to operate on any job that has not yet
|
||||||
# reached its terminal state, JOB_STATUS_CONCLUDED. For jobs that
|
# reached its terminal state, JOB_STATUS_CONCLUDED. For jobs that
|
||||||
@ -3047,26 +3075,43 @@
|
|||||||
#
|
#
|
||||||
# @id: The job identifier.
|
# @id: The job identifier.
|
||||||
#
|
#
|
||||||
|
# Features:
|
||||||
|
#
|
||||||
|
# @deprecated: This command is deprecated. Use @job-dismiss
|
||||||
|
# instead.
|
||||||
|
#
|
||||||
# Since: 2.12
|
# Since: 2.12
|
||||||
##
|
##
|
||||||
{ 'command': 'block-job-dismiss', 'data': { 'id': 'str' },
|
{ 'command': 'block-job-dismiss', 'data': { 'id': 'str' },
|
||||||
|
'features': ['deprecated'],
|
||||||
'allow-preconfig': true }
|
'allow-preconfig': true }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @block-job-finalize:
|
# @block-job-finalize:
|
||||||
#
|
#
|
||||||
# Once a job that has manual=true reaches the pending state, it can be
|
# Instructs all jobs in a transaction (or a single job if it is not
|
||||||
# instructed to finalize any graph changes and do any necessary
|
# part of any transaction) to finalize any graph changes and do any
|
||||||
# cleanup via this command. For jobs in a transaction, instructing
|
# necessary cleanup. This command requires that all involved jobs are
|
||||||
# one job to finalize will force ALL jobs in the transaction to
|
# in the PENDING state.
|
||||||
# finalize, so it is only necessary to instruct a single member job to
|
#
|
||||||
# finalize.
|
# For jobs in a transaction, instructing one job to finalize will
|
||||||
|
# force ALL jobs in the transaction to finalize, so it is only
|
||||||
|
# necessary to instruct a single member job to finalize.
|
||||||
|
#
|
||||||
|
# The command is applicable only to jobs which have @auto-finalize option
|
||||||
|
# and only when this option is set to false.
|
||||||
#
|
#
|
||||||
# @id: The job identifier.
|
# @id: The job identifier.
|
||||||
#
|
#
|
||||||
|
# Features:
|
||||||
|
#
|
||||||
|
# @deprecated: This command is deprecated. Use @job-finalize
|
||||||
|
# instead.
|
||||||
|
#
|
||||||
# Since: 2.12
|
# Since: 2.12
|
||||||
##
|
##
|
||||||
{ 'command': 'block-job-finalize', 'data': { 'id': 'str' },
|
{ 'command': 'block-job-finalize', 'data': { 'id': 'str' },
|
||||||
|
'features': ['deprecated'],
|
||||||
'allow-preconfig': true }
|
'allow-preconfig': true }
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -156,6 +156,9 @@
|
|||||||
# This command returns immediately after resuming a paused job.
|
# This command returns immediately after resuming a paused job.
|
||||||
# Resuming an already running job is an error.
|
# Resuming an already running job is an error.
|
||||||
#
|
#
|
||||||
|
# This command also clears the error status for block-jobs (stream,
|
||||||
|
# commit, mirror, backup).
|
||||||
|
#
|
||||||
# @id: The job identifier.
|
# @id: The job identifier.
|
||||||
#
|
#
|
||||||
# Since: 3.0
|
# Since: 3.0
|
||||||
@ -184,7 +187,23 @@
|
|||||||
##
|
##
|
||||||
# @job-complete:
|
# @job-complete:
|
||||||
#
|
#
|
||||||
# Manually trigger completion of an active job in the READY state.
|
# Manually trigger completion of an active job in the READY or STANDBY
|
||||||
|
# state. Completing the job in any other state is an error.
|
||||||
|
#
|
||||||
|
# This is supported only for drive mirroring, where it also switches
|
||||||
|
# the device to write to the target path only. Note that drive
|
||||||
|
# mirroring includes drive-mirror, blockdev-mirror and block-commit
|
||||||
|
# job (only in case of "active commit", when the node being commited
|
||||||
|
# is used by the guest). The ability to complete is signaled with a
|
||||||
|
# BLOCK_JOB_READY event.
|
||||||
|
#
|
||||||
|
# This command completes an active background block operation
|
||||||
|
# synchronously. The ordering of this command's return with the
|
||||||
|
# BLOCK_JOB_COMPLETED event is not defined. Note that if an I/O error
|
||||||
|
# occurs during the processing of this command: 1) the command itself
|
||||||
|
# will fail; 2) the error will be processed according to the
|
||||||
|
# rerror/werror arguments that were specified when starting the
|
||||||
|
# operation.
|
||||||
#
|
#
|
||||||
# @id: The job identifier.
|
# @id: The job identifier.
|
||||||
#
|
#
|
||||||
@ -197,7 +216,11 @@
|
|||||||
#
|
#
|
||||||
# Deletes a job that is in the CONCLUDED state. This command only
|
# Deletes a job that is in the CONCLUDED state. This command only
|
||||||
# needs to be run explicitly for jobs that don't have automatic
|
# needs to be run explicitly for jobs that don't have automatic
|
||||||
# dismiss enabled.
|
# dismiss enabled. In turn, automatic dismiss may be enabled only
|
||||||
|
# for jobs that have @auto-dismiss option, which are drive-backup,
|
||||||
|
# blockdev-backup, drive-mirror, blockdev-mirror, block-commit and
|
||||||
|
# block-stream. @auto-dismiss is enabled by default for these
|
||||||
|
# jobs.
|
||||||
#
|
#
|
||||||
# This command will refuse to operate on any job that has not yet
|
# This command will refuse to operate on any job that has not yet
|
||||||
# reached its terminal state, JOB_STATUS_CONCLUDED. For jobs that
|
# reached its terminal state, JOB_STATUS_CONCLUDED. For jobs that
|
||||||
@ -222,6 +245,9 @@
|
|||||||
# force ALL jobs in the transaction to finalize, so it is only
|
# force ALL jobs in the transaction to finalize, so it is only
|
||||||
# necessary to instruct a single member job to finalize.
|
# necessary to instruct a single member job to finalize.
|
||||||
#
|
#
|
||||||
|
# The command is applicable only to jobs which have @auto-finalize option
|
||||||
|
# and only when this option is set to false.
|
||||||
|
#
|
||||||
# @id: The identifier of any job in the transaction, or of a job that
|
# @id: The identifier of any job in the transaction, or of a job that
|
||||||
# is not part of any transaction.
|
# is not part of any transaction.
|
||||||
#
|
#
|
||||||
|
96
tests/qemu-iotests/tests/commit-zero-blocks
Executable file
96
tests/qemu-iotests/tests/commit-zero-blocks
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# group: rw quick
|
||||||
|
#
|
||||||
|
# Test for commit of discarded blocks
|
||||||
|
#
|
||||||
|
# This tests committing a live snapshot where some of the blocks that
|
||||||
|
# are present in the base image are discarded in the intermediate image.
|
||||||
|
# This intends to check that these blocks are also discarded in the base
|
||||||
|
# image after the commit.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Vincent Vanlaer.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
# creator
|
||||||
|
owner=libvirt-e6954efa@volkihar.be
|
||||||
|
|
||||||
|
seq=`basename $0`
|
||||||
|
echo "QA output created by $seq"
|
||||||
|
|
||||||
|
status=1 # failure is the default!
|
||||||
|
|
||||||
|
_cleanup()
|
||||||
|
{
|
||||||
|
_cleanup_qemu
|
||||||
|
_rm_test_img "${TEST_IMG}.base"
|
||||||
|
_rm_test_img "${TEST_IMG}.mid"
|
||||||
|
_cleanup_test_img
|
||||||
|
}
|
||||||
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
|
||||||
|
# get standard environment, filters and checks
|
||||||
|
cd ..
|
||||||
|
. ./common.rc
|
||||||
|
. ./common.filter
|
||||||
|
. ./common.qemu
|
||||||
|
|
||||||
|
_supported_fmt qcow2
|
||||||
|
_supported_proto file
|
||||||
|
|
||||||
|
size="1M"
|
||||||
|
|
||||||
|
TEST_IMG="$TEST_IMG.base" _make_test_img $size
|
||||||
|
TEST_IMG="$TEST_IMG.mid" _make_test_img -b "$TEST_IMG.base" -F $IMGFMT $size
|
||||||
|
_make_test_img -b "${TEST_IMG}.mid" -F $IMGFMT $size
|
||||||
|
|
||||||
|
$QEMU_IO -c "write -P 0x01 64k 128k" "$TEST_IMG.base" | _filter_qemu_io
|
||||||
|
$QEMU_IO -c "discard 64k 64k" "$TEST_IMG.mid" | _filter_qemu_io
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Base image info before commit ==="
|
||||||
|
TEST_IMG="${TEST_IMG}.base" _img_info | _filter_img_info
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG.base" | _filter_qemu_img_map
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Middle image info before commit ==="
|
||||||
|
TEST_IMG="${TEST_IMG}.mid" _img_info | _filter_img_info
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG.mid" | _filter_qemu_img_map
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo === Running QEMU Live Commit Test ===
|
||||||
|
echo
|
||||||
|
|
||||||
|
qemu_comm_method="qmp"
|
||||||
|
_launch_qemu -drive file="${TEST_IMG}",if=virtio,id=test
|
||||||
|
h=$QEMU_HANDLE
|
||||||
|
|
||||||
|
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" "return"
|
||||||
|
|
||||||
|
_send_qemu_cmd $h "{ 'execute': 'block-commit',
|
||||||
|
'arguments': { 'device': 'test',
|
||||||
|
'top': '"${TEST_IMG}.mid"',
|
||||||
|
'base': '"${TEST_IMG}.base"'} }" '"status": "null"'
|
||||||
|
|
||||||
|
_cleanup_qemu
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Base image info after commit ==="
|
||||||
|
TEST_IMG="${TEST_IMG}.base" _img_info | _filter_img_info
|
||||||
|
$QEMU_IMG map --output=json "$TEST_IMG.base" | _filter_qemu_img_map
|
||||||
|
|
||||||
|
# success, all done
|
||||||
|
echo "*** done"
|
||||||
|
rm -f $seq.full
|
||||||
|
status=0
|
54
tests/qemu-iotests/tests/commit-zero-blocks.out
Normal file
54
tests/qemu-iotests/tests/commit-zero-blocks.out
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
QA output created by commit-zero-blocks
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=1048576
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.mid', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT.mid backing_fmt=IMGFMT
|
||||||
|
wrote 131072/131072 bytes at offset 65536
|
||||||
|
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
discard 65536/65536 bytes at offset 65536
|
||||||
|
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
|
||||||
|
=== Base image info before commit ===
|
||||||
|
image: TEST_DIR/t.IMGFMT.base
|
||||||
|
file format: IMGFMT
|
||||||
|
virtual size: 1 MiB (1048576 bytes)
|
||||||
|
[{ "start": 0, "length": 65536, "depth": 0, "present": false, "zero": true, "data": false, "compressed": false},
|
||||||
|
{ "start": 65536, "length": 131072, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET},
|
||||||
|
{ "start": 196608, "length": 851968, "depth": 0, "present": false, "zero": true, "data": false, "compressed": false}]
|
||||||
|
|
||||||
|
=== Middle image info before commit ===
|
||||||
|
image: TEST_DIR/t.IMGFMT.mid
|
||||||
|
file format: IMGFMT
|
||||||
|
virtual size: 1 MiB (1048576 bytes)
|
||||||
|
backing file: TEST_DIR/t.IMGFMT.base
|
||||||
|
backing file format: IMGFMT
|
||||||
|
[{ "start": 0, "length": 65536, "depth": 1, "present": false, "zero": true, "data": false, "compressed": false},
|
||||||
|
{ "start": 65536, "length": 65536, "depth": 0, "present": true, "zero": true, "data": false, "compressed": false},
|
||||||
|
{ "start": 131072, "length": 65536, "depth": 1, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET},
|
||||||
|
{ "start": 196608, "length": 851968, "depth": 1, "present": false, "zero": true, "data": false, "compressed": false}]
|
||||||
|
|
||||||
|
=== Running QEMU Live Commit Test ===
|
||||||
|
|
||||||
|
{ 'execute': 'qmp_capabilities' }
|
||||||
|
{"return": {}}
|
||||||
|
{ 'execute': 'block-commit',
|
||||||
|
'arguments': { 'device': 'test',
|
||||||
|
'top': 'TEST_DIR/t.IMGFMT.mid',
|
||||||
|
'base': 'TEST_DIR/t.IMGFMT.base'} }
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "test"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "test"}}
|
||||||
|
{"return": {}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "test"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "test"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "test", "len": 1048576, "offset": 1048576, "speed": 0, "type": "commit"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "test"}}
|
||||||
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "test"}}
|
||||||
|
|
||||||
|
=== Base image info after commit ===
|
||||||
|
image: TEST_DIR/t.IMGFMT.base
|
||||||
|
file format: IMGFMT
|
||||||
|
virtual size: 1 MiB (1048576 bytes)
|
||||||
|
[{ "start": 0, "length": 65536, "depth": 0, "present": false, "zero": true, "data": false, "compressed": false},
|
||||||
|
{ "start": 65536, "length": 65536, "depth": 0, "present": true, "zero": true, "data": false, "compressed": false},
|
||||||
|
{ "start": 131072, "length": 65536, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET},
|
||||||
|
{ "start": 196608, "length": 851968, "depth": 0, "present": false, "zero": true, "data": false, "compressed": false}]
|
||||||
|
*** done
|
@ -99,6 +99,68 @@ class TestCbwError(iotests.QMPTestCase):
|
|||||||
log = iotests.filter_qemu_io(log)
|
log = iotests.filter_qemu_io(log)
|
||||||
return log
|
return log
|
||||||
|
|
||||||
|
def do_cbw_error_via_blockdev_backup(self, on_cbw_error=None):
|
||||||
|
self.vm.cmd('blockdev-add', {
|
||||||
|
'node-name': 'source',
|
||||||
|
'driver': iotests.imgfmt,
|
||||||
|
'file': {
|
||||||
|
'driver': 'file',
|
||||||
|
'filename': source_img
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.vm.cmd('blockdev-add', {
|
||||||
|
'node-name': 'target',
|
||||||
|
'driver': iotests.imgfmt,
|
||||||
|
'file': {
|
||||||
|
'driver': 'blkdebug',
|
||||||
|
'image': {
|
||||||
|
'driver': 'file',
|
||||||
|
'filename': temp_img
|
||||||
|
},
|
||||||
|
'inject-error': [
|
||||||
|
{
|
||||||
|
'event': 'write_aio',
|
||||||
|
'errno': 5,
|
||||||
|
'immediately': False,
|
||||||
|
'once': True
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
blockdev_backup_options = {
|
||||||
|
'device': 'source',
|
||||||
|
'target': 'target',
|
||||||
|
'sync': 'none',
|
||||||
|
'job-id': 'job-id',
|
||||||
|
'filter-node-name': 'cbw'
|
||||||
|
}
|
||||||
|
|
||||||
|
if on_cbw_error:
|
||||||
|
blockdev_backup_options['on-cbw-error'] = on_cbw_error
|
||||||
|
|
||||||
|
self.vm.cmd('blockdev-backup', blockdev_backup_options)
|
||||||
|
|
||||||
|
self.vm.cmd('blockdev-add', {
|
||||||
|
'node-name': 'access',
|
||||||
|
'driver': 'snapshot-access',
|
||||||
|
'file': 'cbw'
|
||||||
|
})
|
||||||
|
|
||||||
|
result = self.vm.qmp('human-monitor-command',
|
||||||
|
command_line='qemu-io cbw "write 0 1M"')
|
||||||
|
self.assert_qmp(result, 'return', '')
|
||||||
|
|
||||||
|
result = self.vm.qmp('human-monitor-command',
|
||||||
|
command_line='qemu-io access "read 0 1M"')
|
||||||
|
self.assert_qmp(result, 'return', '')
|
||||||
|
|
||||||
|
self.vm.shutdown()
|
||||||
|
log = self.vm.get_log()
|
||||||
|
log = iotests.filter_qemu_io(log)
|
||||||
|
return log
|
||||||
|
|
||||||
def test_break_snapshot_on_cbw_error(self):
|
def test_break_snapshot_on_cbw_error(self):
|
||||||
"""break-snapshot behavior:
|
"""break-snapshot behavior:
|
||||||
Guest write succeed, but further snapshot-read fails, as snapshot is
|
Guest write succeed, but further snapshot-read fails, as snapshot is
|
||||||
@ -123,6 +185,39 @@ read failed: Permission denied
|
|||||||
write failed: Input/output error
|
write failed: Input/output error
|
||||||
read 1048576/1048576 bytes at offset 0
|
read 1048576/1048576 bytes at offset 0
|
||||||
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_break_snapshot_policy_forwarding(self):
|
||||||
|
"""Ensure CBW filter accepts break-snapshot policy
|
||||||
|
specified in blockdev-backup QMP command.
|
||||||
|
"""
|
||||||
|
log = self.do_cbw_error_via_blockdev_backup('break-snapshot')
|
||||||
|
self.assertEqual(log, """\
|
||||||
|
wrote 1048576/1048576 bytes at offset 0
|
||||||
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
read failed: Permission denied
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_break_guest_write_policy_forwarding(self):
|
||||||
|
"""Ensure CBW filter accepts break-guest-write policy
|
||||||
|
specified in blockdev-backup QMP command.
|
||||||
|
"""
|
||||||
|
log = self.do_cbw_error_via_blockdev_backup('break-guest-write')
|
||||||
|
self.assertEqual(log, """\
|
||||||
|
write failed: Input/output error
|
||||||
|
read 1048576/1048576 bytes at offset 0
|
||||||
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_default_on_cbw_error_policy_forwarding(self):
|
||||||
|
"""Ensure break-guest-write policy is used by default when
|
||||||
|
on-cbw-error is not explicitly specified.
|
||||||
|
"""
|
||||||
|
log = self.do_cbw_error_via_blockdev_backup()
|
||||||
|
self.assertEqual(log, """\
|
||||||
|
write failed: Input/output error
|
||||||
|
read 1048576/1048576 bytes at offset 0
|
||||||
|
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def do_cbw_timeout(self, on_cbw_error):
|
def do_cbw_timeout(self, on_cbw_error):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
....
|
.......
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
Ran 4 tests
|
Ran 7 tests
|
||||||
|
|
||||||
OK
|
OK
|
||||||
|
Loading…
Reference in New Issue
Block a user