diff --git a/block.c b/block.c index ba36b53a0047d0151563c74cb83d313352dfaa9e..824025f78161875724033f0ad8072355c1812a40 100644 --- a/block.c +++ b/block.c @@ -4165,6 +4165,7 @@ void bdrv_replace_node(BlockDriverState *from, BlockDriverState *to, bdrv_ref(from); assert(qemu_get_current_aio_context() == qemu_get_aio_context()); + assert(bdrv_get_aio_context(from) == bdrv_get_aio_context(to)); bdrv_drained_begin(from); /* Put all parents into @list and calculate their cumulative permissions */ diff --git a/block/backup.c b/block/backup.c index 88354dcb3273797192595bdbb9a614c61a9fccff..6023573299775b42a26a10536a7478fdad0537ce 100644 --- a/block/backup.c +++ b/block/backup.c @@ -38,9 +38,9 @@ typedef struct CowRequest { typedef struct BackupBlockJob { BlockJob common; BlockBackend *target; - /* bitmap for sync=incremental */ BdrvDirtyBitmap *sync_bitmap; MirrorSyncMode sync_mode; + BitmapSyncMode bitmap_mode; BlockdevOnError on_source_error; BlockdevOnError on_target_error; CoRwlock flush_rwlock; @@ -110,7 +110,10 @@ static int coroutine_fn backup_cow_with_bounce_buffer(BackupBlockJob *job, BlockBackend *blk = job->common.blk; int nbytes; int read_flags = is_write_notifier ? BDRV_REQ_NO_SERIALISING : 0; - int write_flags = job->serialize_target_writes ? BDRV_REQ_SERIALISING : 0; + int write_flags = + (job->serialize_target_writes ? BDRV_REQ_SERIALISING : 0) | + (job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0); + assert(QEMU_IS_ALIGNED(start, job->cluster_size)); hbitmap_reset(job->copy_bitmap, start, job->cluster_size); @@ -128,14 +131,8 @@ static int coroutine_fn backup_cow_with_bounce_buffer(BackupBlockJob *job, goto fail; } - if (buffer_is_zero(*bounce_buffer, nbytes)) { - ret = blk_co_pwrite_zeroes(job->target, start, - nbytes, write_flags | BDRV_REQ_MAY_UNMAP); - } else { - ret = blk_co_pwrite(job->target, start, - nbytes, *bounce_buffer, write_flags | - (job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0)); - } + ret = blk_co_pwrite(job->target, start, nbytes, *bounce_buffer, + write_flags); if (ret < 0) { trace_backup_do_cow_write_fail(job, start, ret); if (error_is_read) { @@ -274,8 +271,11 @@ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret) BdrvDirtyBitmap *bm; BlockDriverState *bs = blk_bs(job->common.blk); - if (ret < 0) { - /* Merge the successor back into the parent, delete nothing. */ + if (ret < 0 || job->bitmap_mode == BITMAP_SYNC_MODE_NEVER) { + /* + * Failure, or we don't want to synchronize the bitmap. + * Merge the successor back into the parent, delete nothing. + */ bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL); assert(bm); } else { @@ -461,7 +461,7 @@ static int coroutine_fn backup_run(Job *job, Error **errp) job_progress_set_remaining(job, s->len); - if (s->sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) { + if (s->sync_mode == MIRROR_SYNC_MODE_BITMAP) { backup_incremental_init_copy_bitmap(s); } else { hbitmap_set(s->copy_bitmap, 0, s->len); @@ -545,6 +545,7 @@ static int64_t backup_calculate_cluster_size(BlockDriverState *target, BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, BlockDriverState *target, int64_t speed, MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap, + BitmapSyncMode bitmap_mode, bool compress, BlockdevOnError on_source_error, BlockdevOnError on_target_error, @@ -561,6 +562,10 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, assert(bs); assert(target); + /* QMP interface protects us from these cases */ + assert(sync_mode != MIRROR_SYNC_MODE_INCREMENTAL); + assert(sync_bitmap || sync_mode != MIRROR_SYNC_MODE_BITMAP); + if (bs == target) { error_setg(errp, "Source and target cannot be the same"); return NULL; @@ -592,10 +597,10 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, return NULL; } - if (sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) { - if (!sync_bitmap) { - error_setg(errp, "must provide a valid bitmap name for " - "\"incremental\" sync mode"); + if (sync_mode == MIRROR_SYNC_MODE_BITMAP) { + /* If we need to write to this bitmap, check that we can: */ + if (bitmap_mode != BITMAP_SYNC_MODE_NEVER && + bdrv_dirty_bitmap_check(sync_bitmap, BDRV_BITMAP_DEFAULT, errp)) { return NULL; } @@ -605,8 +610,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, } } else if (sync_bitmap) { error_setg(errp, - "a sync_bitmap was provided to backup_run, " - "but received an incompatible sync_mode (%s)", + "a bitmap was given to backup_job_create, " + "but it received an incompatible sync_mode (%s)", MirrorSyncMode_str(sync_mode)); return NULL; } @@ -648,8 +653,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, job->on_source_error = on_source_error; job->on_target_error = on_target_error; job->sync_mode = sync_mode; - job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_INCREMENTAL ? - sync_bitmap : NULL; + job->sync_bitmap = sync_bitmap; + job->bitmap_mode = bitmap_mode; job->compress = compress; /* Detect image-fleecing (and similar) schemes */ diff --git a/block/mirror.c b/block/mirror.c index abcf60a961c90713fc3c9e9f2449a3646f246ac9..89a053b265fdffb03d0b7c5909f890d263e281a8 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -51,6 +51,8 @@ typedef struct MirrorBlockJob { Error *replace_blocker; bool is_none_mode; BlockMirrorBackingMode backing_mode; + /* Whether the target image requires explicit zero-initialization */ + bool zero_target; MirrorCopyMode copy_mode; BlockdevOnError on_source_error, on_target_error; bool synced; @@ -779,7 +781,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s) int ret; int64_t count; - if (base == NULL && !bdrv_has_zero_init(target_bs)) { + if (s->zero_target) { if (!bdrv_can_write_zeroes_with_unmap(target_bs)) { bdrv_set_dirty_bitmap(s->dirty_bitmap, 0, s->bdev_length); return 0; @@ -1531,6 +1533,7 @@ static BlockJob *mirror_start_job( const char *replaces, int64_t speed, uint32_t granularity, int64_t buf_size, BlockMirrorBackingMode backing_mode, + bool zero_target, BlockdevOnError on_source_error, BlockdevOnError on_target_error, bool unmap, @@ -1658,6 +1661,7 @@ static BlockJob *mirror_start_job( s->on_target_error = on_target_error; s->is_none_mode = is_none_mode; s->backing_mode = backing_mode; + s->zero_target = zero_target; s->copy_mode = copy_mode; s->base = base; s->granularity = granularity; @@ -1762,6 +1766,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs, int creation_flags, int64_t speed, uint32_t granularity, int64_t buf_size, MirrorSyncMode mode, BlockMirrorBackingMode backing_mode, + bool zero_target, BlockdevOnError on_source_error, BlockdevOnError on_target_error, bool unmap, const char *filter_node_name, @@ -1770,14 +1775,16 @@ void mirror_start(const char *job_id, BlockDriverState *bs, bool is_none_mode; BlockDriverState *base; - if (mode == MIRROR_SYNC_MODE_INCREMENTAL) { - error_setg(errp, "Sync mode 'incremental' not supported"); + if ((mode == MIRROR_SYNC_MODE_INCREMENTAL) || + (mode == MIRROR_SYNC_MODE_BITMAP)) { + error_setg(errp, "Sync mode '%s' not supported", + MirrorSyncMode_str(mode)); return; } is_none_mode = mode == MIRROR_SYNC_MODE_NONE; base = mode == MIRROR_SYNC_MODE_TOP ? backing_bs(bs) : NULL; mirror_start_job(job_id, bs, creation_flags, target, replaces, - speed, granularity, buf_size, backing_mode, + speed, granularity, buf_size, backing_mode, zero_target, on_source_error, on_target_error, unmap, NULL, NULL, &mirror_job_driver, is_none_mode, base, false, filter_node_name, true, copy_mode, errp); @@ -1804,7 +1811,7 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, ret = mirror_start_job( job_id, bs, creation_flags, base, NULL, speed, 0, 0, - MIRROR_LEAVE_BACKING_CHAIN, + MIRROR_LEAVE_BACKING_CHAIN, false, on_error, on_error, true, cb, opaque, &commit_active_job_driver, false, base, auto_complete, filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND, diff --git a/block/replication.c b/block/replication.c index 23b2993d74763a88df0498ab4c96a96c5e7860cb..936b2f8b5a45ff1828d81f405a4fdf6324838e1f 100644 --- a/block/replication.c +++ b/block/replication.c @@ -543,7 +543,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, s->backup_job = backup_job_create( NULL, s->secondary_disk->bs, s->hidden_disk->bs, - 0, MIRROR_SYNC_MODE_NONE, NULL, false, + 0, MIRROR_SYNC_MODE_NONE, NULL, 0, false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, JOB_INTERNAL, backup_job_completed, bs, NULL, &local_err); diff --git a/blockdev.c b/blockdev.c index 99c92b96d25831cebd8d90ad566bda94c8035220..d1a3b6a630558778109bf24dc66c242c24f9ecc2 100644 --- a/blockdev.c +++ b/blockdev.c @@ -1578,7 +1578,6 @@ static void external_snapshot_prepare(BlkActionState *common, DO_UPCAST(ExternalSnapshotState, common, common); TransactionAction *action = common->action; AioContext *aio_context; - int ret; /* 'blockdev-snapshot' and 'blockdev-snapshot-sync' have similar * purpose but a different set of parameters */ @@ -1718,11 +1717,6 @@ static void external_snapshot_prepare(BlkActionState *common, goto out; } - ret = bdrv_try_set_aio_context(state->new_bs, aio_context, errp); - if (ret < 0) { - goto out; - } - /* This removes our old bs and adds the new bs. This is an operation that * can fail, so we need to do it in .prepare; undoing it for abort is * always possible. */ @@ -1764,6 +1758,8 @@ static void external_snapshot_abort(BlkActionState *common) if (state->new_bs) { if (state->overlay_appended) { AioContext *aio_context; + AioContext *tmp_context; + int ret; aio_context = bdrv_get_aio_context(state->old_bs); aio_context_acquire(aio_context); @@ -1771,6 +1767,25 @@ static void external_snapshot_abort(BlkActionState *common) bdrv_ref(state->old_bs); /* we can't let bdrv_set_backind_hd() close state->old_bs; we need it */ bdrv_set_backing_hd(state->new_bs, NULL, &error_abort); + + /* + * The call to bdrv_set_backing_hd() above returns state->old_bs to + * the main AioContext. As we're still going to be using it, return + * it to the AioContext it was before. + */ + tmp_context = bdrv_get_aio_context(state->old_bs); + if (aio_context != tmp_context) { + aio_context_release(aio_context); + aio_context_acquire(tmp_context); + + ret = bdrv_try_set_aio_context(state->old_bs, + aio_context, NULL); + assert(ret == 0); + + aio_context_release(tmp_context); + aio_context_acquire(aio_context); + } + bdrv_replace_node(state->new_bs, state->old_bs, &error_abort); bdrv_unref(state->old_bs); /* bdrv_replace_node() ref'ed old_bs */ @@ -1804,39 +1819,145 @@ typedef struct DriveBackupState { BlockJob *job; } DriveBackupState; -static BlockJob *do_drive_backup(DriveBackup *backup, JobTxn *txn, - Error **errp); +static BlockJob *do_backup_common(BackupCommon *backup, + BlockDriverState *bs, + BlockDriverState *target_bs, + AioContext *aio_context, + JobTxn *txn, Error **errp); static void drive_backup_prepare(BlkActionState *common, Error **errp) { DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common); - BlockDriverState *bs; DriveBackup *backup; + BlockDriverState *bs; + BlockDriverState *target_bs; + BlockDriverState *source = NULL; AioContext *aio_context; + AioContext *old_context; + QDict *options; Error *local_err = NULL; + int flags; + int64_t size; + bool set_backing_hd = false; + int ret; assert(common->action->type == TRANSACTION_ACTION_KIND_DRIVE_BACKUP); backup = common->action->u.drive_backup.data; + if (!backup->has_mode) { + backup->mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; + } + bs = bdrv_lookup_bs(backup->device, backup->device, errp); if (!bs) { return; } + if (!bs->drv) { + error_setg(errp, "Device has no medium"); + return; + } + aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); /* Paired with .clean() */ bdrv_drained_begin(bs); - state->bs = bs; + if (!backup->has_format) { + backup->format = backup->mode == NEW_IMAGE_MODE_EXISTING ? + NULL : (char *) bs->drv->format_name; + } + + /* Early check to avoid creating target */ + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) { + goto out; + } + + flags = bs->open_flags | BDRV_O_RDWR; + + /* + * See if we have a backing HD we can use to create our new image + * on top of. + */ + if (backup->sync == MIRROR_SYNC_MODE_TOP) { + source = backing_bs(bs); + if (!source) { + backup->sync = MIRROR_SYNC_MODE_FULL; + } + } + if (backup->sync == MIRROR_SYNC_MODE_NONE) { + source = bs; + flags |= BDRV_O_NO_BACKING; + set_backing_hd = true; + } + + size = bdrv_getlength(bs); + if (size < 0) { + error_setg_errno(errp, -size, "bdrv_getlength failed"); + goto out; + } + + if (backup->mode != NEW_IMAGE_MODE_EXISTING) { + assert(backup->format); + if (source) { + bdrv_refresh_filename(source); + bdrv_img_create(backup->target, backup->format, source->filename, + source->drv->format_name, NULL, + size, flags, false, &local_err); + } else { + bdrv_img_create(backup->target, backup->format, NULL, NULL, NULL, + size, flags, false, &local_err); + } + } - state->job = do_drive_backup(backup, common->block_job_txn, &local_err); if (local_err) { error_propagate(errp, local_err); goto out; } + options = qdict_new(); + qdict_put_str(options, "discard", "unmap"); + qdict_put_str(options, "detect-zeroes", "unmap"); + if (backup->format) { + qdict_put_str(options, "driver", backup->format); + } + + target_bs = bdrv_open(backup->target, NULL, options, flags, errp); + if (!target_bs) { + goto out; + } + + /* Honor bdrv_try_set_aio_context() context acquisition requirements. */ + old_context = bdrv_get_aio_context(target_bs); + aio_context_release(aio_context); + aio_context_acquire(old_context); + + ret = bdrv_try_set_aio_context(target_bs, aio_context, errp); + if (ret < 0) { + bdrv_unref(target_bs); + aio_context_release(old_context); + return; + } + + aio_context_release(old_context); + aio_context_acquire(aio_context); + + if (set_backing_hd) { + bdrv_set_backing_hd(target_bs, source, &local_err); + if (local_err) { + goto unref; + } + } + + state->bs = bs; + + state->job = do_backup_common(qapi_DriveBackup_base(backup), + bs, target_bs, aio_context, + common->block_job_txn, errp); + +unref: + bdrv_unref(target_bs); out: aio_context_release(aio_context); } @@ -1894,16 +2015,15 @@ typedef struct BlockdevBackupState { BlockJob *job; } BlockdevBackupState; -static BlockJob *do_blockdev_backup(BlockdevBackup *backup, JobTxn *txn, - Error **errp); - static void blockdev_backup_prepare(BlkActionState *common, Error **errp) { BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common); BlockdevBackup *backup; - BlockDriverState *bs, *target; + BlockDriverState *bs; + BlockDriverState *target_bs; AioContext *aio_context; - Error *local_err = NULL; + AioContext *old_context; + int ret; assert(common->action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP); backup = common->action->u.blockdev_backup.data; @@ -1913,25 +2033,33 @@ static void blockdev_backup_prepare(BlkActionState *common, Error **errp) return; } - target = bdrv_lookup_bs(backup->target, backup->target, errp); - if (!target) { + target_bs = bdrv_lookup_bs(backup->target, backup->target, errp); + if (!target_bs) { return; } + /* Honor bdrv_try_set_aio_context() context acquisition requirements. */ aio_context = bdrv_get_aio_context(bs); + old_context = bdrv_get_aio_context(target_bs); + aio_context_acquire(old_context); + + ret = bdrv_try_set_aio_context(target_bs, aio_context, errp); + if (ret < 0) { + aio_context_release(old_context); + return; + } + + aio_context_release(old_context); aio_context_acquire(aio_context); state->bs = bs; /* Paired with .clean() */ bdrv_drained_begin(state->bs); - state->job = do_blockdev_backup(backup, common->block_job_txn, &local_err); - if (local_err) { - error_propagate(errp, local_err); - goto out; - } + state->job = do_backup_common(qapi_BlockdevBackup_base(backup), + bs, target_bs, aio_context, + common->block_job_txn, errp); -out: aio_context_release(aio_context); } @@ -3469,21 +3597,16 @@ out: aio_context_release(aio_context); } -static BlockJob *do_drive_backup(DriveBackup *backup, JobTxn *txn, - Error **errp) +/* Common QMP interface for drive-backup and blockdev-backup */ +static BlockJob *do_backup_common(BackupCommon *backup, + BlockDriverState *bs, + BlockDriverState *target_bs, + AioContext *aio_context, + JobTxn *txn, Error **errp) { - BlockDriverState *bs; - BlockDriverState *target_bs; - BlockDriverState *source = NULL; BlockJob *job = NULL; BdrvDirtyBitmap *bmap = NULL; - AioContext *aio_context; - QDict *options = NULL; - Error *local_err = NULL; - int flags, job_flags = JOB_DEFAULT; - int64_t size; - bool set_backing_hd = false; - int ret; + int job_flags = JOB_DEFAULT; if (!backup->has_speed) { backup->speed = 0; @@ -3494,9 +3617,6 @@ static BlockJob *do_drive_backup(DriveBackup *backup, JobTxn *txn, if (!backup->has_on_target_error) { backup->on_target_error = BLOCKDEV_ON_ERROR_REPORT; } - if (!backup->has_mode) { - backup->mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; - } if (!backup->has_job_id) { backup->job_id = NULL; } @@ -3510,104 +3630,46 @@ static BlockJob *do_drive_backup(DriveBackup *backup, JobTxn *txn, backup->compress = false; } - bs = bdrv_lookup_bs(backup->device, backup->device, errp); - if (!bs) { - return NULL; - } - - if (!bs->drv) { - error_setg(errp, "Device has no medium"); - return NULL; - } - - aio_context = bdrv_get_aio_context(bs); - aio_context_acquire(aio_context); - - if (!backup->has_format) { - backup->format = backup->mode == NEW_IMAGE_MODE_EXISTING ? - NULL : (char*) bs->drv->format_name; - } - - /* Early check to avoid creating target */ - if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) { - goto out; - } - - flags = bs->open_flags | BDRV_O_RDWR; - - /* See if we have a backing HD we can use to create our new image - * on top of. */ - if (backup->sync == MIRROR_SYNC_MODE_TOP) { - source = backing_bs(bs); - if (!source) { - backup->sync = MIRROR_SYNC_MODE_FULL; - } - } - if (backup->sync == MIRROR_SYNC_MODE_NONE) { - source = bs; - flags |= BDRV_O_NO_BACKING; - set_backing_hd = true; - } - - size = bdrv_getlength(bs); - if (size < 0) { - error_setg_errno(errp, -size, "bdrv_getlength failed"); - goto out; - } - - if (backup->mode != NEW_IMAGE_MODE_EXISTING) { - assert(backup->format); - if (source) { - bdrv_refresh_filename(source); - bdrv_img_create(backup->target, backup->format, source->filename, - source->drv->format_name, NULL, - size, flags, false, &local_err); - } else { - bdrv_img_create(backup->target, backup->format, NULL, NULL, NULL, - size, flags, false, &local_err); - } - } - - if (local_err) { - error_propagate(errp, local_err); - goto out; - } - - if (backup->format) { - if (!options) { - options = qdict_new(); + if ((backup->sync == MIRROR_SYNC_MODE_BITMAP) || + (backup->sync == MIRROR_SYNC_MODE_INCREMENTAL)) { + /* done before desugaring 'incremental' to print the right message */ + if (!backup->has_bitmap) { + error_setg(errp, "must provide a valid bitmap name for " + "'%s' sync mode", MirrorSyncMode_str(backup->sync)); + return NULL; } - qdict_put_str(options, "driver", backup->format); - } - - target_bs = bdrv_open(backup->target, NULL, options, flags, errp); - if (!target_bs) { - goto out; - } - - ret = bdrv_try_set_aio_context(target_bs, aio_context, errp); - if (ret < 0) { - bdrv_unref(target_bs); - goto out; } - if (set_backing_hd) { - bdrv_set_backing_hd(target_bs, source, &local_err); - if (local_err) { - goto unref; + if (backup->sync == MIRROR_SYNC_MODE_INCREMENTAL) { + if (backup->has_bitmap_mode && + backup->bitmap_mode != BITMAP_SYNC_MODE_ON_SUCCESS) { + error_setg(errp, "Bitmap sync mode must be '%s' " + "when using sync mode '%s'", + BitmapSyncMode_str(BITMAP_SYNC_MODE_ON_SUCCESS), + MirrorSyncMode_str(backup->sync)); + return NULL; } + backup->has_bitmap_mode = true; + backup->sync = MIRROR_SYNC_MODE_BITMAP; + backup->bitmap_mode = BITMAP_SYNC_MODE_ON_SUCCESS; } if (backup->has_bitmap) { bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap); if (!bmap) { error_setg(errp, "Bitmap '%s' could not be found", backup->bitmap); - goto unref; + return NULL; } - if (bdrv_dirty_bitmap_check(bmap, BDRV_BITMAP_DEFAULT, errp)) { - goto unref; + if (!backup->has_bitmap_mode) { + error_setg(errp, "Bitmap sync mode must be given " + "when providing a bitmap"); + return NULL; + } + if (bdrv_dirty_bitmap_check(bmap, BDRV_BITMAP_ALLOW_RO, errp)) { + return NULL; } } + if (!backup->auto_finalize) { job_flags |= JOB_MANUAL_FINALIZE; } @@ -3616,29 +3678,21 @@ static BlockJob *do_drive_backup(DriveBackup *backup, JobTxn *txn, } job = backup_job_create(backup->job_id, bs, target_bs, backup->speed, - backup->sync, bmap, backup->compress, - backup->on_source_error, backup->on_target_error, - job_flags, NULL, NULL, txn, &local_err); - if (local_err != NULL) { - error_propagate(errp, local_err); - goto unref; - } - -unref: - bdrv_unref(target_bs); -out: - aio_context_release(aio_context); + backup->sync, bmap, backup->bitmap_mode, + backup->compress, + backup->on_source_error, + backup->on_target_error, + job_flags, NULL, NULL, txn, errp); return job; } -void qmp_drive_backup(DriveBackup *arg, Error **errp) +void qmp_drive_backup(DriveBackup *backup, Error **errp) { - - BlockJob *job; - job = do_drive_backup(arg, NULL, errp); - if (job) { - job_start(&job->job); - } + TransactionAction action = { + .type = TRANSACTION_ACTION_KIND_DRIVE_BACKUP, + .u.drive_backup.data = backup, + }; + blockdev_do_action(&action, errp); } BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp) @@ -3651,94 +3705,13 @@ XDbgBlockGraph *qmp_x_debug_query_block_graph(Error **errp) return bdrv_get_xdbg_block_graph(errp); } -BlockJob *do_blockdev_backup(BlockdevBackup *backup, JobTxn *txn, - Error **errp) +void qmp_blockdev_backup(BlockdevBackup *backup, Error **errp) { - BlockDriverState *bs; - BlockDriverState *target_bs; - Error *local_err = NULL; - BdrvDirtyBitmap *bmap = NULL; - AioContext *aio_context; - BlockJob *job = NULL; - int job_flags = JOB_DEFAULT; - int ret; - - if (!backup->has_speed) { - backup->speed = 0; - } - if (!backup->has_on_source_error) { - backup->on_source_error = BLOCKDEV_ON_ERROR_REPORT; - } - if (!backup->has_on_target_error) { - backup->on_target_error = BLOCKDEV_ON_ERROR_REPORT; - } - if (!backup->has_job_id) { - backup->job_id = NULL; - } - if (!backup->has_auto_finalize) { - backup->auto_finalize = true; - } - if (!backup->has_auto_dismiss) { - backup->auto_dismiss = true; - } - if (!backup->has_compress) { - backup->compress = false; - } - - bs = bdrv_lookup_bs(backup->device, backup->device, errp); - if (!bs) { - return NULL; - } - - aio_context = bdrv_get_aio_context(bs); - aio_context_acquire(aio_context); - - target_bs = bdrv_lookup_bs(backup->target, backup->target, errp); - if (!target_bs) { - goto out; - } - - ret = bdrv_try_set_aio_context(target_bs, aio_context, errp); - if (ret < 0) { - goto out; - } - - if (backup->has_bitmap) { - bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap); - if (!bmap) { - error_setg(errp, "Bitmap '%s' could not be found", backup->bitmap); - goto out; - } - if (bdrv_dirty_bitmap_check(bmap, BDRV_BITMAP_DEFAULT, errp)) { - goto out; - } - } - - if (!backup->auto_finalize) { - job_flags |= JOB_MANUAL_FINALIZE; - } - if (!backup->auto_dismiss) { - job_flags |= JOB_MANUAL_DISMISS; - } - job = backup_job_create(backup->job_id, bs, target_bs, backup->speed, - backup->sync, bmap, backup->compress, - backup->on_source_error, backup->on_target_error, - job_flags, NULL, NULL, txn, &local_err); - if (local_err != NULL) { - error_propagate(errp, local_err); - } -out: - aio_context_release(aio_context); - return job; -} - -void qmp_blockdev_backup(BlockdevBackup *arg, Error **errp) -{ - BlockJob *job; - job = do_blockdev_backup(arg, NULL, errp); - if (job) { - job_start(&job->job); - } + TransactionAction action = { + .type = TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP, + .u.blockdev_backup.data = backup, + }; + blockdev_do_action(&action, errp); } /* Parameter check and block job starting for drive mirroring. @@ -3749,6 +3722,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, bool has_replaces, const char *replaces, enum MirrorSyncMode sync, BlockMirrorBackingMode backing_mode, + bool zero_target, bool has_speed, int64_t speed, bool has_granularity, uint32_t granularity, bool has_buf_size, int64_t buf_size, @@ -3857,7 +3831,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, */ mirror_start(job_id, bs, target, has_replaces ? replaces : NULL, job_flags, - speed, granularity, buf_size, sync, backing_mode, + speed, granularity, buf_size, sync, backing_mode, zero_target, on_source_error, on_target_error, unmap, filter_node_name, copy_mode, errp); } @@ -3867,12 +3841,14 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) BlockDriverState *bs; BlockDriverState *source, *target_bs; AioContext *aio_context; + AioContext *old_context; BlockMirrorBackingMode backing_mode; Error *local_err = NULL; QDict *options = NULL; int flags; int64_t size; const char *format = arg->format; + bool zero_target; int ret; bs = qmp_get_root_bs(arg->device, errp); @@ -3974,15 +3950,30 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) goto out; } + zero_target = (arg->sync == MIRROR_SYNC_MODE_FULL && + (arg->mode == NEW_IMAGE_MODE_EXISTING || + !bdrv_has_zero_init(target_bs))); + + + /* Honor bdrv_try_set_aio_context() context acquisition requirements. */ + old_context = bdrv_get_aio_context(target_bs); + aio_context_release(aio_context); + aio_context_acquire(old_context); + ret = bdrv_try_set_aio_context(target_bs, aio_context, errp); if (ret < 0) { bdrv_unref(target_bs); - goto out; + aio_context_release(old_context); + return; } + aio_context_release(old_context); + aio_context_acquire(aio_context); + blockdev_mirror_common(arg->has_job_id ? arg->job_id : NULL, bs, target_bs, arg->has_replaces, arg->replaces, arg->sync, - backing_mode, arg->has_speed, arg->speed, + backing_mode, zero_target, + arg->has_speed, arg->speed, arg->has_granularity, arg->granularity, arg->has_buf_size, arg->buf_size, arg->has_on_source_error, arg->on_source_error, @@ -4020,8 +4011,10 @@ void qmp_blockdev_mirror(bool has_job_id, const char *job_id, BlockDriverState *bs; BlockDriverState *target_bs; AioContext *aio_context; + AioContext *old_context; BlockMirrorBackingMode backing_mode = MIRROR_LEAVE_BACKING_CHAIN; Error *local_err = NULL; + bool zero_target; int ret; bs = qmp_get_root_bs(device, errp); @@ -4034,17 +4027,25 @@ void qmp_blockdev_mirror(bool has_job_id, const char *job_id, return; } + zero_target = (sync == MIRROR_SYNC_MODE_FULL); + + /* Honor bdrv_try_set_aio_context() context acquisition requirements. */ + old_context = bdrv_get_aio_context(target_bs); aio_context = bdrv_get_aio_context(bs); - aio_context_acquire(aio_context); + aio_context_acquire(old_context); ret = bdrv_try_set_aio_context(target_bs, aio_context, errp); + + aio_context_release(old_context); + aio_context_acquire(aio_context); + if (ret < 0) { goto out; } blockdev_mirror_common(has_job_id ? job_id : NULL, bs, target_bs, has_replaces, replaces, sync, backing_mode, - has_speed, speed, + zero_target, has_speed, speed, has_granularity, granularity, has_buf_size, buf_size, has_on_source_error, on_source_error, diff --git a/include/block/block_int.h b/include/block/block_int.h index 05ee6b4866f84a9ab9ba0dcda5da8028e03a45fb..154b9b5501f93edf010b3f6bd05d0866f94690e8 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -1120,6 +1120,7 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, * @buf_size: The amount of data that can be in flight at one time. * @mode: Whether to collapse all images in the chain to the target. * @backing_mode: How to establish the target's backing chain after completion. + * @zero_target: Whether the target should be explicitly zero-initialized * @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. * @unmap: Whether to unmap target where source sectors only contain zeroes. @@ -1139,6 +1140,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs, int creation_flags, int64_t speed, uint32_t granularity, int64_t buf_size, MirrorSyncMode mode, BlockMirrorBackingMode backing_mode, + bool zero_target, BlockdevOnError on_source_error, BlockdevOnError on_target_error, bool unmap, const char *filter_node_name, @@ -1152,7 +1154,8 @@ void mirror_start(const char *job_id, BlockDriverState *bs, * @target: Block device to write to. * @speed: The maximum speed, in bytes per second, or 0 for unlimited. * @sync_mode: What parts of the disk image should be copied to the destination. - * @sync_bitmap: The dirty bitmap if sync_mode is MIRROR_SYNC_MODE_INCREMENTAL. + * @sync_bitmap: The dirty bitmap if sync_mode is 'bitmap' or 'incremental' + * @bitmap_mode: The bitmap synchronization policy to use. * @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. * @creation_flags: Flags that control the behavior of the Job lifetime. @@ -1168,6 +1171,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, BlockDriverState *target, int64_t speed, MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap, + BitmapSyncMode bitmap_mode, bool compress, BlockdevOnError on_source_error, BlockdevOnError on_target_error, diff --git a/qapi/block-core.json b/qapi/block-core.json index db24f0dfe586bc21b8a914b0692f28798ba96b95..48a0bfab63b1b27654445caa5dee261ceb3a8fdb 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1127,12 +1127,32 @@ # # @none: only copy data written from now on # -# @incremental: only copy data described by the dirty bitmap. Since: 2.4 +# @incremental: only copy data described by the dirty bitmap. (since: 2.4) +# +# @bitmap: only copy data described by the dirty bitmap. (since: 4.2) +# Behavior on completion is determined by the BitmapSyncMode. # # Since: 1.3 ## { 'enum': 'MirrorSyncMode', - 'data': ['top', 'full', 'none', 'incremental'] } + 'data': ['top', 'full', 'none', 'incremental', 'bitmap'] } + +## +# @BitmapSyncMode: +# +# An enumeration of possible behaviors for the synchronization of a bitmap +# when used for data copy operations. +# +# @on-success: The bitmap is only synced when the operation is successful. +# This is the behavior always used for 'INCREMENTAL' backups. +# +# @never: The bitmap is never synchronized with the operation, and is +# treated solely as a read-only manifest of blocks to copy. +# +# Since: 4.2 +## +{ 'enum': 'BitmapSyncMode', + 'data': ['on-success', 'never'] } ## # @MirrorCopyMode: @@ -1315,32 +1335,28 @@ 'data': { 'node': 'str', 'overlay': 'str' } } ## -# @DriveBackup: +# @BackupCommon: # # @job-id: identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) # # @device: the device name or node-name of a root node which should be copied. # -# @target: the target of the new image. If the file exists, or if it -# is a device, the existing file/device will be used as the new -# destination. If it does not exist, a new file will be created. -# -# @format: the format of the new destination, default is to -# probe if @mode is 'existing', else the format of the source -# # @sync: what parts of the disk image should be copied to the destination # (all the disk, only the sectors allocated in the topmost image, from a # dirty bitmap, or only new I/O). # -# @mode: whether and how QEMU should create a new image, default is -# 'absolute-paths'. +# @speed: the maximum speed, in bytes per second. The default is 0, +# for unlimited. # -# @speed: the maximum speed, in bytes per second +# @bitmap: the name of a dirty bitmap if sync is "bitmap" or "incremental". +# Must be present if sync is "bitmap" or "incremental". +# Must not be present otherwise. +# (Since 2.4 (drive-backup), 3.1 (blockdev-backup)) # -# @bitmap: the name of dirty bitmap if sync is "incremental". -# Must be present if sync is "incremental", must NOT be present -# otherwise. (Since 2.4) +# @bitmap-mode: Specifies the type of data the bitmap should contain after +# the operation concludes. Must be present if sync is "bitmap". +# Must NOT be present otherwise. (Since 4.2) # # @compress: true to compress data, if the target format supports it. # (default: false) (since 2.8) @@ -1370,75 +1386,48 @@ # I/O. If an error occurs during a guest write request, the device's # rerror/werror actions will be used. # -# Since: 1.6 +# Since: 4.2 ## -{ 'struct': 'DriveBackup', - 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', - '*format': 'str', 'sync': 'MirrorSyncMode', - '*mode': 'NewImageMode', '*speed': 'int', - '*bitmap': 'str', '*compress': 'bool', +{ 'struct': 'BackupCommon', + 'data': { '*job-id': 'str', 'device': 'str', + 'sync': 'MirrorSyncMode', '*speed': 'int', + '*bitmap': 'str', '*bitmap-mode': 'BitmapSyncMode', + '*compress': 'bool', '*on-source-error': 'BlockdevOnError', '*on-target-error': 'BlockdevOnError', '*auto-finalize': 'bool', '*auto-dismiss': 'bool' } } ## -# @BlockdevBackup: -# -# @job-id: identifier for the newly-created block job. If -# omitted, the device name will be used. (Since 2.7) -# -# @device: the device name or node-name of a root node which should be copied. -# -# @target: the device name or node-name of the backup target node. -# -# @sync: what parts of the disk image should be copied to the destination -# (all the disk, only the sectors allocated in the topmost image, or -# only new I/O). -# -# @speed: the maximum speed, in bytes per second. The default is 0, -# for unlimited. -# -# @bitmap: the name of dirty bitmap if sync is "incremental". -# Must be present if sync is "incremental", must NOT be present -# otherwise. (Since 3.1) -# -# @compress: true to compress data, if the target format supports it. -# (default: false) (since 2.8) +# @DriveBackup: # -# @on-source-error: 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). +# @target: the target of the new image. If the file exists, or if it +# is a device, the existing file/device will be used as the new +# destination. If it does not exist, a new file will be created. # -# @on-target-error: the action to take on an error on the target, -# default 'report' (no limitations, since this applies to -# a different block device than @device). +# @format: the format of the new destination, default is to +# probe if @mode is 'existing', else the format of the source # -# @auto-finalize: When false, this job will wait in a PENDING state after it has -# finished its work, waiting for @block-job-finalize before -# making any block graph changes. -# When true, this job will automatically -# perform its abort or commit actions. -# Defaults to true. (Since 2.12) +# @mode: whether and how QEMU should create a new image, default is +# 'absolute-paths'. # -# @auto-dismiss: When false, this job will wait in a CONCLUDED state after it -# has completely ceased all work, and awaits @block-job-dismiss. -# When true, this job will automatically disappear from the query -# list without user intervention. -# Defaults to true. (Since 2.12) +# Since: 1.6 +## +{ 'struct': 'DriveBackup', + 'base': 'BackupCommon', + 'data': { 'target': 'str', + '*format': 'str', + '*mode': 'NewImageMode' } } + +## +# @BlockdevBackup: # -# Note: @on-source-error and @on-target-error only affect background -# I/O. If an error occurs during a guest write request, the device's -# rerror/werror actions will be used. +# @target: the device name or node-name of the backup target node. # # Since: 2.3 ## { 'struct': 'BlockdevBackup', - 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', - 'sync': 'MirrorSyncMode', '*speed': 'int', - '*bitmap': 'str', '*compress': 'bool', - '*on-source-error': 'BlockdevOnError', - '*on-target-error': 'BlockdevOnError', - '*auto-finalize': 'bool', '*auto-dismiss': 'bool' } } + 'base': 'BackupCommon', + 'data': { 'target': 'str' } } ## # @blockdev-snapshot-sync: diff --git a/tests/qemu-iotests/141.out b/tests/qemu-iotests/141.out index 4d71d9dcae883a95c26673bc22b19fffe5a9ba73..07e0ec66d78350cf7f10220dc1621dbb2ee5260f 100644 --- a/tests/qemu-iotests/141.out +++ b/tests/qemu-iotests/141.out @@ -10,6 +10,8 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/m. Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT backing_fmt=IMGFMT {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "paused", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} {"return": {}} {"error": {"class": "GenericError", "desc": "Node drv0 is in use"}} {"return": {}} diff --git a/tests/qemu-iotests/185.out b/tests/qemu-iotests/185.out index ddfbf3c76548b36fc220277aa3214ef5597eb1e0..a233be7f589f866cd427be2277094d5b94b3b68e 100644 --- a/tests/qemu-iotests/185.out +++ b/tests/qemu-iotests/185.out @@ -51,6 +51,8 @@ Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 l Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16 {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "paused", "id": "disk"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} diff --git a/tests/qemu-iotests/219 b/tests/qemu-iotests/219 index e0c51662c0db579568c41f9066ced48955a327d9..655f54d88199168c7e6685e9fc47d7e95e3e8881 100755 --- a/tests/qemu-iotests/219 +++ b/tests/qemu-iotests/219 @@ -63,7 +63,7 @@ def test_pause_resume(vm): # logged immediately iotests.log(vm.qmp('query-jobs')) -def test_job_lifecycle(vm, job, job_args, has_ready=False): +def test_job_lifecycle(vm, job, job_args, has_ready=False, is_mirror=False): global img_size iotests.log('') @@ -135,6 +135,9 @@ def test_job_lifecycle(vm, job, job_args, has_ready=False): iotests.log('Waiting for PENDING state...') iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + if is_mirror: + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) if not job_args.get('auto-finalize', True): # PENDING state: @@ -218,7 +221,7 @@ with iotests.FilePath('disk.img') as disk_path, \ for auto_finalize in [True, False]: for auto_dismiss in [True, False]: - test_job_lifecycle(vm, 'drive-backup', job_args={ + test_job_lifecycle(vm, 'drive-backup', is_mirror=True, job_args={ 'device': 'drive0-node', 'target': copy_path, 'sync': 'full', diff --git a/tests/qemu-iotests/219.out b/tests/qemu-iotests/219.out index 8ebd3fee60e33ab86aa510aef60b5e6b785ec682..0ea5d0b9d57cc43d68ddc36cd25aa882fe65cabc 100644 --- a/tests/qemu-iotests/219.out +++ b/tests/qemu-iotests/219.out @@ -135,6 +135,8 @@ Pause/resume in RUNNING {"return": {}} Waiting for PENDING state... +{"data": {"id": "job0", "status": "paused"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"id": "job0", "status": "running"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "waiting"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "pending"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "concluded"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} @@ -186,6 +188,8 @@ Pause/resume in RUNNING {"return": {}} Waiting for PENDING state... +{"data": {"id": "job0", "status": "paused"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"id": "job0", "status": "running"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "waiting"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "pending"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "concluded"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} @@ -245,6 +249,8 @@ Pause/resume in RUNNING {"return": {}} Waiting for PENDING state... +{"data": {"id": "job0", "status": "paused"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"id": "job0", "status": "running"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "waiting"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "pending"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"return": [{"current-progress": 4194304, "id": "job0", "status": "pending", "total-progress": 4194304, "type": "backup"}]} @@ -304,6 +310,8 @@ Pause/resume in RUNNING {"return": {}} Waiting for PENDING state... +{"data": {"id": "job0", "status": "paused"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} +{"data": {"id": "job0", "status": "running"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "waiting"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "job0", "status": "pending"}, "event": "JOB_STATUS_CHANGE", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"return": [{"current-progress": 4194304, "id": "job0", "status": "pending", "total-progress": 4194304, "type": "backup"}]} diff --git a/tests/test-block-iothread.c b/tests/test-block-iothread.c index 1949d5e61a7d53a885ac85c09bd48073545befd9..debfb69bfbb8b3a3caea298110810a70a680f57e 100644 --- a/tests/test-block-iothread.c +++ b/tests/test-block-iothread.c @@ -611,7 +611,7 @@ static void test_propagate_mirror(void) /* Start a mirror job */ mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0, - MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, + MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, false, "filter_node", MIRROR_COPY_MODE_BACKGROUND, &error_abort);