mirror: add support for on-source-error/on-target-error

Error management is important for mirroring; otherwise, an error on the
target (even something as "innocent" as ENOSPC) requires to start again
with a full copy.  Similar to on_read_error/on_write_error, two separate
knobs are provided for on_source_error (reads) and on_target_error (writes).
The default is 'report' for both.

The 'ignore' policy will leave the sector dirty, so that it will be
retried later.  Thus, it will not cause corruption.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Paolo Bonzini 2012-10-18 16:49:28 +02:00 committed by Kevin Wolf
parent 3bd293c3fd
commit b952b5589a
6 changed files with 107 additions and 25 deletions

View File

@ -32,13 +32,28 @@ typedef struct MirrorBlockJob {
RateLimit limit; RateLimit limit;
BlockDriverState *target; BlockDriverState *target;
MirrorSyncMode mode; MirrorSyncMode mode;
BlockdevOnError on_source_error, on_target_error;
bool synced; bool synced;
bool should_complete; bool should_complete;
int64_t sector_num; int64_t sector_num;
uint8_t *buf; uint8_t *buf;
} MirrorBlockJob; } MirrorBlockJob;
static int coroutine_fn mirror_iteration(MirrorBlockJob *s) static BlockErrorAction mirror_error_action(MirrorBlockJob *s, bool read,
int error)
{
s->synced = false;
if (read) {
return block_job_error_action(&s->common, s->common.bs,
s->on_source_error, true, error);
} else {
return block_job_error_action(&s->common, s->target,
s->on_target_error, false, error);
}
}
static int coroutine_fn mirror_iteration(MirrorBlockJob *s,
BlockErrorAction *p_action)
{ {
BlockDriverState *source = s->common.bs; BlockDriverState *source = s->common.bs;
BlockDriverState *target = s->target; BlockDriverState *target = s->target;
@ -60,9 +75,21 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s)
trace_mirror_one_iteration(s, s->sector_num, nb_sectors); trace_mirror_one_iteration(s, s->sector_num, nb_sectors);
ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov); ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov);
if (ret < 0) { if (ret < 0) {
return ret; *p_action = mirror_error_action(s, true, -ret);
goto fail;
} }
return bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov); ret = bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov);
if (ret < 0) {
*p_action = mirror_error_action(s, false, -ret);
s->synced = false;
goto fail;
}
return 0;
fail:
/* Try again later. */
bdrv_set_dirty(source, s->sector_num, nb_sectors);
return ret;
} }
static void coroutine_fn mirror_run(void *opaque) static void coroutine_fn mirror_run(void *opaque)
@ -117,8 +144,9 @@ static void coroutine_fn mirror_run(void *opaque)
cnt = bdrv_get_dirty_count(bs); cnt = bdrv_get_dirty_count(bs);
if (cnt != 0) { if (cnt != 0) {
ret = mirror_iteration(s); BlockErrorAction action = BDRV_ACTION_REPORT;
if (ret < 0) { ret = mirror_iteration(s, &action);
if (ret < 0 && action == BDRV_ACTION_REPORT) {
goto immediate_exit; goto immediate_exit;
} }
cnt = bdrv_get_dirty_count(bs); cnt = bdrv_get_dirty_count(bs);
@ -129,23 +157,25 @@ static void coroutine_fn mirror_run(void *opaque)
trace_mirror_before_flush(s); trace_mirror_before_flush(s);
ret = bdrv_flush(s->target); ret = bdrv_flush(s->target);
if (ret < 0) { if (ret < 0) {
goto immediate_exit; if (mirror_error_action(s, false, -ret) == BDRV_ACTION_REPORT) {
} goto immediate_exit;
}
} else {
/* We're out of the streaming phase. From now on, if the job
* is cancelled we will actually complete all pending I/O and
* report completion. This way, block-job-cancel will leave
* the target in a consistent state.
*/
s->common.offset = end * BDRV_SECTOR_SIZE;
if (!s->synced) {
block_job_ready(&s->common);
s->synced = true;
}
/* We're out of the streaming phase. From now on, if the job should_complete = s->should_complete ||
* is cancelled we will actually complete all pending I/O and block_job_is_cancelled(&s->common);
* report completion. This way, block-job-cancel will leave cnt = bdrv_get_dirty_count(bs);
* the target in a consistent state.
*/
s->common.offset = end * BDRV_SECTOR_SIZE;
if (!s->synced) {
block_job_ready(&s->common);
s->synced = true;
} }
should_complete = s->should_complete ||
block_job_is_cancelled(&s->common);
cnt = bdrv_get_dirty_count(bs);
} }
if (cnt == 0 && should_complete) { if (cnt == 0 && should_complete) {
@ -197,6 +227,7 @@ static void coroutine_fn mirror_run(void *opaque)
immediate_exit: immediate_exit:
g_free(s->buf); g_free(s->buf);
bdrv_set_dirty_tracking(bs, false); bdrv_set_dirty_tracking(bs, false);
bdrv_iostatus_disable(s->target);
if (s->should_complete && ret == 0) { if (s->should_complete && ret == 0) {
if (bdrv_get_flags(s->target) != bdrv_get_flags(s->common.bs)) { if (bdrv_get_flags(s->target) != bdrv_get_flags(s->common.bs)) {
bdrv_reopen(s->target, bdrv_get_flags(s->common.bs), NULL); bdrv_reopen(s->target, bdrv_get_flags(s->common.bs), NULL);
@ -219,6 +250,13 @@ static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME); ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
} }
static void mirror_iostatus_reset(BlockJob *job)
{
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
bdrv_iostatus_reset(s->target);
}
static void mirror_complete(BlockJob *job, Error **errp) static void mirror_complete(BlockJob *job, Error **errp)
{ {
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common); MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
@ -245,25 +283,39 @@ static BlockJobType mirror_job_type = {
.instance_size = sizeof(MirrorBlockJob), .instance_size = sizeof(MirrorBlockJob),
.job_type = "mirror", .job_type = "mirror",
.set_speed = mirror_set_speed, .set_speed = mirror_set_speed,
.iostatus_reset= mirror_iostatus_reset,
.complete = mirror_complete, .complete = mirror_complete,
}; };
void mirror_start(BlockDriverState *bs, BlockDriverState *target, void mirror_start(BlockDriverState *bs, BlockDriverState *target,
int64_t speed, MirrorSyncMode mode, int64_t speed, MirrorSyncMode mode,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
BlockDriverCompletionFunc *cb, BlockDriverCompletionFunc *cb,
void *opaque, Error **errp) void *opaque, Error **errp)
{ {
MirrorBlockJob *s; MirrorBlockJob *s;
if ((on_source_error == BLOCKDEV_ON_ERROR_STOP ||
on_source_error == BLOCKDEV_ON_ERROR_ENOSPC) &&
!bdrv_iostatus_is_enabled(bs)) {
error_set(errp, QERR_INVALID_PARAMETER, "on-source-error");
return;
}
s = block_job_create(&mirror_job_type, bs, speed, cb, opaque, errp); s = block_job_create(&mirror_job_type, bs, speed, cb, opaque, errp);
if (!s) { if (!s) {
return; return;
} }
s->on_source_error = on_source_error;
s->on_target_error = on_target_error;
s->target = target; s->target = target;
s->mode = mode; s->mode = mode;
bdrv_set_dirty_tracking(bs, true); bdrv_set_dirty_tracking(bs, true);
bdrv_set_enable_write_cache(s->target, true); bdrv_set_enable_write_cache(s->target, true);
bdrv_set_on_error(s->target, on_target_error, on_target_error);
bdrv_iostatus_enable(s->target);
s->common.co = qemu_coroutine_create(mirror_run); s->common.co = qemu_coroutine_create(mirror_run);
trace_mirror_start(bs, s, s->common.co, opaque); trace_mirror_start(bs, s, s->common.co, opaque);
qemu_coroutine_enter(s->common.co, s); qemu_coroutine_enter(s->common.co, s);

View File

@ -337,6 +337,8 @@ void commit_start(BlockDriverState *bs, BlockDriverState *base,
* @target: Block device to write to. * @target: Block device to write to.
* @speed: The maximum speed, in bytes per second, or 0 for unlimited. * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
* @mode: Whether to collapse all images in the chain to the target. * @mode: Whether to collapse all images in the chain to the target.
* @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.
* @cb: Completion function for the job. * @cb: Completion function for the job.
* @opaque: Opaque pointer value passed to @cb. * @opaque: Opaque pointer value passed to @cb.
* @errp: Error object. * @errp: Error object.
@ -348,6 +350,8 @@ void commit_start(BlockDriverState *bs, BlockDriverState *base,
*/ */
void mirror_start(BlockDriverState *bs, BlockDriverState *target, void mirror_start(BlockDriverState *bs, BlockDriverState *target,
int64_t speed, MirrorSyncMode mode, int64_t speed, MirrorSyncMode mode,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
BlockDriverCompletionFunc *cb, BlockDriverCompletionFunc *cb,
void *opaque, Error **errp); void *opaque, Error **errp);

View File

@ -1185,7 +1185,10 @@ void qmp_drive_mirror(const char *device, const char *target,
bool has_format, const char *format, bool has_format, const char *format,
enum MirrorSyncMode sync, enum MirrorSyncMode sync,
bool has_mode, enum NewImageMode mode, bool has_mode, enum NewImageMode mode,
bool has_speed, int64_t speed, Error **errp) bool has_speed, int64_t speed,
bool has_on_source_error, BlockdevOnError on_source_error,
bool has_on_target_error, BlockdevOnError on_target_error,
Error **errp)
{ {
BlockDriverInfo bdi; BlockDriverInfo bdi;
BlockDriverState *bs; BlockDriverState *bs;
@ -1200,6 +1203,12 @@ void qmp_drive_mirror(const char *device, const char *target,
if (!has_speed) { if (!has_speed) {
speed = 0; speed = 0;
} }
if (!has_on_source_error) {
on_source_error = BLOCKDEV_ON_ERROR_REPORT;
}
if (!has_on_target_error) {
on_target_error = BLOCKDEV_ON_ERROR_REPORT;
}
if (!has_mode) { if (!has_mode) {
mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
} }
@ -1292,7 +1301,8 @@ void qmp_drive_mirror(const char *device, const char *target,
} }
} }
mirror_start(bs, target_bs, speed, sync, block_job_cb, bs, &local_err); mirror_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
block_job_cb, bs, &local_err);
if (local_err != NULL) { if (local_err != NULL) {
bdrv_delete(target_bs); bdrv_delete(target_bs);
error_propagate(errp, local_err); error_propagate(errp, local_err);

3
hmp.c
View File

@ -795,7 +795,8 @@ void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
qmp_drive_mirror(device, filename, !!format, format, qmp_drive_mirror(device, filename, !!format, format,
full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP, full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
true, mode, false, 0, &errp); true, mode, false, 0,
false, 0, false, 0, &errp);
hmp_handle_error(mon, &errp); hmp_handle_error(mon, &errp);
} }

View File

@ -1629,6 +1629,14 @@
# (all the disk, only the sectors allocated in the topmost image, or # (all the disk, only the sectors allocated in the topmost image, or
# only new I/O). # only new I/O).
# #
# @on-source-error: #optional the action to take on an error on the source,
# default 'report'. 'stop' and 'enospc' can only be used
# if the block device supports io-status (see BlockInfo).
#
# @on-target-error: #optional the action to take on an error on the target,
# default 'report' (no limitations, since this applies to
# a different block device than @device).
#
# Returns: nothing on success # Returns: nothing on success
# If @device is not a valid block device, DeviceNotFound # If @device is not a valid block device, DeviceNotFound
# #
@ -1637,7 +1645,8 @@
{ 'command': 'drive-mirror', { 'command': 'drive-mirror',
'data': { 'device': 'str', 'target': 'str', '*format': 'str', 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
'sync': 'MirrorSyncMode', '*mode': 'NewImageMode', 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
'*speed': 'int' } } '*speed': 'int', '*on-source-error': 'BlockdevOnError',
'*on-target-error': 'BlockdevOnError' } }
## ##
# @migrate_cancel # @migrate_cancel

View File

@ -937,7 +937,8 @@ EQMP
{ {
.name = "drive-mirror", .name = "drive-mirror",
.args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?", .args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
"on-source-error:s?,on-target-error:s?",
.mhandler.cmd_new = qmp_marshal_input_drive_mirror, .mhandler.cmd_new = qmp_marshal_input_drive_mirror,
}, },
@ -965,6 +966,11 @@ Arguments:
possibilities include "full" for all the disk, "top" for only the sectors possibilities include "full" for all the disk, "top" for only the sectors
allocated in the topmost image, or "none" to only replicate new I/O allocated in the topmost image, or "none" to only replicate new I/O
(MirrorSyncMode). (MirrorSyncMode).
- "on-source-error": the action to take on an error on the source
(BlockdevOnError, default 'report')
- "on-target-error": the action to take on an error on the target
(BlockdevOnError, default 'report')
Example: Example: