* [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2
@ 2012-10-18 14:49 Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 01/16] block: add bdrv_query_info Paolo Bonzini
` (15 more replies)
0 siblings, 16 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Kevin,
the following changes since commit caa1a2eaf344fc8552f30d7c452df6ed292f6336:
qemu-img rebase: use empty string to rebase without backing file (2012-10-16 14:53:14 +0200)
are available in the git repository at:
git://github.com/bonzini/qemu.git blkmirror-job-1.3-part2
for you to fetch changes up to b3929fa13a81380a247904cb518166ed22ebe7b7:
qemu-iotests: add testcases for mirroring on-source-error/on-target-error (2012-10-18 16:27:15 +0200)
This message has the diff from the rebase of v2. You reviewed 15 more
patches, these are 16 because "block: rename block_job_complete to
block_job_completed" conflicted with Jeff's commit series and thus got
bumped from part 1 to part 2. I did not manage to find a better name,
and none was suggested either.
The next batch should go up to patch 41, "mirror: allow customizing the
granularity". Thanks for all your effort.
Paolo
----------------------------------------------------------------
Paolo Bonzini (16):
block: add bdrv_query_info
block: add bdrv_query_stats
block: add bdrv_open_backing_file
block: introduce new dirty bitmap functionality
block: export dirty bitmap information in query-block
block: rename block_job_complete to block_job_completed
block: add block-job-complete
block: introduce BLOCK_JOB_READY event
mirror: introduce mirror job
qmp: add drive-mirror command
mirror: implement completion
qemu-iotests: add mirroring test case
iostatus: forward block_job_iostatus_reset to block job
mirror: add support for on-source-error/on-target-error
qmp: add pull_event function
qemu-iotests: add testcases for mirroring on-source-error/on-target-error
QMP/qmp-events.txt | 20 ++
QMP/qmp.py | 20 ++
block.c | 241 +++++++++++------
block.h | 8 +-
block/Makefile.objs | 1 +
block/commit.c | 2 +-
block/mirror.c | 322 ++++++++++++++++++++++
block/stream.c | 4 +-
block_int.h | 24 ++
blockdev.c | 161 ++++++++++-
blockjob.c | 36 ++-
blockjob.h | 41 ++-
hmp-commands.hx | 38 ++-
hmp.c | 39 +++
hmp.h | 2 +
monitor.c | 1 +
monitor.h | 1 +
qapi-schema.json | 106 +++++++-
qerror.h | 3 +
qmp-commands.hx | 53 ++++
tests/qemu-iotests/041 | 617 ++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/041.out | 5 +
tests/qemu-iotests/group | 1 +
tests/qemu-iotests/iotests.py | 4 +
trace-events | 8 +
25 file modificati, 1643 inserzioni(+), 115 rimozioni(-)
create mode 100644 block/mirror.c
create mode 100755 tests/qemu-iotests/041
create mode 100644 tests/qemu-iotests/041.out
diff --git a/block.c b/block.c
index 5361b6c..3e76322 100644
--- a/block.c
+++ b/block.c
@@ -767,12 +767,6 @@ int bdrv_open_backing_file(BlockDriverState *bs)
bs->open_flags |= BDRV_O_NO_BACKING;
return ret;
}
- if (bs->is_temporary) {
- bs->backing_hd->keep_read_only = !(bs->open_flags & BDRV_O_RDWR);
- } else {
- /* base images use the same setting as leaf */
- bs->backing_hd->keep_read_only = bs->keep_read_only;
- }
return 0;
}
@@ -2838,7 +2838,8 @@ BlockInfo *bdrv_query_info(BlockDriverState *bs)
if (bs->dirty_bitmap) {
info->has_dirty = true;
info->dirty = g_malloc0(sizeof(*info->dirty));
- info->dirty->count = bdrv_get_dirty_count(bs) * BDRV_SECTORS_PER_DIRTY_CHUNK;
+ info->dirty->count = bdrv_get_dirty_count(bs) *
+ BDRV_SECTORS_PER_DIRTY_CHUNK * BDRV_SECTOR_SIZE;
}
if (bs->drv) {
@@ -2855,6 +2856,8 @@ BlockInfo *bdrv_query_info(BlockDriverState *bs)
info->inserted->backing_file = g_strdup(bs->backing_file);
}
+ info->inserted->backing_file_depth = bdrv_get_backing_file_depth(bs);
+
if (bs->io_limits_enabled) {
info->inserted->bps =
bs->io_limits.bps[BLOCK_IO_LIMIT_TOTAL];
diff --git a/block/commit.c b/block/commit.c
index 733c914..897af5f 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -160,7 +160,7 @@ exit_restore_reopen:
bdrv_reopen(overlay_bs, s->orig_overlay_flags, NULL);
}
- block_job_complete(&s->common, ret);
+ block_job_completed(&s->common, ret);
}
static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
diff --git a/block/mirror.c b/block/mirror.c
index caec272..d6618a4 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -34,7 +34,7 @@ typedef struct MirrorBlockJob {
MirrorSyncMode mode;
BlockdevOnError on_source_error, on_target_error;
bool synced;
- bool complete;
+ bool should_complete;
int64_t sector_num;
uint8_t *buf;
} MirrorBlockJob;
@@ -172,7 +172,8 @@ static void coroutine_fn mirror_run(void *opaque)
s->synced = true;
}
- should_complete = block_job_is_cancelled(&s->common) || s->complete;
+ should_complete = s->should_complete ||
+ block_job_is_cancelled(&s->common);
cnt = bdrv_get_dirty_count(bs);
}
}
@@ -227,7 +228,10 @@ immediate_exit:
g_free(s->buf);
bdrv_set_dirty_tracking(bs, false);
bdrv_iostatus_disable(s->target);
- if (s->complete && ret == 0) {
+ if (s->should_complete && ret == 0) {
+ if (bdrv_get_flags(s->target) != bdrv_get_flags(s->common.bs)) {
+ bdrv_reopen(s->target, bdrv_get_flags(s->common.bs), NULL);
+ }
bdrv_swap(s->target, s->common.bs);
}
bdrv_close(s->target);
@@ -271,7 +275,7 @@ static void mirror_complete(BlockJob *job, Error **errp)
return;
}
- s->complete = true;
+ s->should_complete = true;
block_job_resume(job);
}
diff --git a/blockdev.c b/blockdev.c
index b74673c..f37d5eb 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1218,6 +1218,11 @@ void qmp_drive_mirror(const char *device, const char *target,
return;
}
+ if (!bdrv_is_inserted(bs)) {
+ error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
+ return;
+ }
+
if (!has_format) {
format = mode == NEW_IMAGE_MODE_EXISTING ? NULL : bs->drv->format_name;
}
@@ -1229,11 +1234,6 @@ void qmp_drive_mirror(const char *device, const char *target,
}
}
- if (!bdrv_is_inserted(bs)) {
- error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
- return;
- }
-
if (bdrv_in_use(bs)) {
error_set(errp, QERR_DEVICE_IN_USE, device);
return;
diff --git a/qapi-schema.json b/qapi-schema.json
index 4b299d4..295263b 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -651,7 +651,7 @@
#
# Block dirty bitmap information.
#
-# @count: number of dirty sectors according to the dirty bitmap
+# @count: number of dirty bytes according to the dirty bitmap
#
# Since: 1.3
##
diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
old mode 100644
new mode 100755
index ec86c70..6681705
--- a/tests/qemu-iotests/041
+++ b/tests/qemu-iotests/041
@@ -2,7 +2,7 @@
#
# Tests for image mirroring.
#
-# Copyright (C) 2012 IBM Corp.
+# Copyright (C) 2012 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -162,6 +162,20 @@ class TestSingleDrive(ImageMirroringTestCase):
target=target_img)
self.assert_qmp(result, 'return', {})
+ self.cancel_and_wait(wait_ready=False)
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', test_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_cancel_after_ready(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
self.cancel_and_wait()
result = self.vm.qmp('query-block')
self.assert_qmp(result, 'return[0]/inserted/file', test_img)
@@ -211,24 +225,21 @@ class TestSingleDrive(ImageMirroringTestCase):
self.assertTrue(self.compare_images(test_img, target_img),
'target image does not match source after mirroring')
+ def test_medium_not_found(self):
+ result = self.vm.qmp('drive-mirror', device='ide1-cd0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
def test_image_not_found(self):
result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
mode='existing', target=target_img)
self.assert_qmp(result, 'error/class', 'GenericError')
- # Avoid failure on os.remove
- qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
- % (TestSingleDrive.image_len, test_img), target_img)
-
def test_device_not_found(self):
result = self.vm.qmp('drive-mirror', device='nonexistent', sync='full',
target=target_img)
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
- # Avoid failure on os.remove
- qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
- % (TestSingleDrive.image_len, test_img), target_img)
-
class TestMirrorNoBacking(ImageMirroringTestCase):
image_len = 2 * 1024 * 1024 # MB
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 01/16] block: add bdrv_query_info
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 02/16] block: add bdrv_query_stats Paolo Bonzini
` (14 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Extract it out of the implementation of "info block".
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: added back backing_file_depth
block.c | 106 ++++++++++++++++++++++++++++++++--------------------------------
block.h | 1 +
2 file modificati, 54 inserzioni(+), 53 rimozioni(-)
diff --git a/block.c b/block.c
index e95f613..ce7c2ea 100644
--- a/block.c
+++ b/block.c
@@ -2797,69 +2797,69 @@ int coroutine_fn bdrv_co_is_allocated_above(BlockDriverState *top,
return 0;
}
-BlockInfoList *qmp_query_block(Error **errp)
+BlockInfo *bdrv_query_info(BlockDriverState *bs)
{
- BlockInfoList *head = NULL, *cur_item = NULL;
- BlockDriverState *bs;
+ BlockInfo *info = g_malloc0(sizeof(*info));
+ info->device = g_strdup(bs->device_name);
+ info->type = g_strdup("unknown");
+ info->locked = bdrv_dev_is_medium_locked(bs);
+ info->removable = bdrv_dev_has_removable_media(bs);
- QTAILQ_FOREACH(bs, &bdrv_states, list) {
- BlockInfoList *info = g_malloc0(sizeof(*info));
+ if (bdrv_dev_has_removable_media(bs)) {
+ info->has_tray_open = true;
+ info->tray_open = bdrv_dev_is_tray_open(bs);
+ }
- info->value = g_malloc0(sizeof(*info->value));
- info->value->device = g_strdup(bs->device_name);
- info->value->type = g_strdup("unknown");
- info->value->locked = bdrv_dev_is_medium_locked(bs);
- info->value->removable = bdrv_dev_has_removable_media(bs);
+ if (bdrv_iostatus_is_enabled(bs)) {
+ info->has_io_status = true;
+ info->io_status = bs->iostatus;
+ }
- if (bdrv_dev_has_removable_media(bs)) {
- info->value->has_tray_open = true;
- info->value->tray_open = bdrv_dev_is_tray_open(bs);
+ if (bs->drv) {
+ info->has_inserted = true;
+ info->inserted = g_malloc0(sizeof(*info->inserted));
+ info->inserted->file = g_strdup(bs->filename);
+ info->inserted->ro = bs->read_only;
+ info->inserted->drv = g_strdup(bs->drv->format_name);
+ info->inserted->encrypted = bs->encrypted;
+ info->inserted->encryption_key_missing = bdrv_key_required(bs);
+
+ if (bs->backing_file[0]) {
+ info->inserted->has_backing_file = true;
+ info->inserted->backing_file = g_strdup(bs->backing_file);
}
- if (bdrv_iostatus_is_enabled(bs)) {
- info->value->has_io_status = true;
- info->value->io_status = bs->iostatus;
+ info->inserted->backing_file_depth = bdrv_get_backing_file_depth(bs);
+
+ if (bs->io_limits_enabled) {
+ info->inserted->bps =
+ bs->io_limits.bps[BLOCK_IO_LIMIT_TOTAL];
+ info->inserted->bps_rd =
+ bs->io_limits.bps[BLOCK_IO_LIMIT_READ];
+ info->inserted->bps_wr =
+ bs->io_limits.bps[BLOCK_IO_LIMIT_WRITE];
+ info->inserted->iops =
+ bs->io_limits.iops[BLOCK_IO_LIMIT_TOTAL];
+ info->inserted->iops_rd =
+ bs->io_limits.iops[BLOCK_IO_LIMIT_READ];
+ info->inserted->iops_wr =
+ bs->io_limits.iops[BLOCK_IO_LIMIT_WRITE];
}
+ }
+ return info;
+}
- if (bs->drv) {
- info->value->has_inserted = true;
- info->value->inserted = g_malloc0(sizeof(*info->value->inserted));
- info->value->inserted->file = g_strdup(bs->filename);
- info->value->inserted->ro = bs->read_only;
- info->value->inserted->drv = g_strdup(bs->drv->format_name);
- info->value->inserted->encrypted = bs->encrypted;
- info->value->inserted->encryption_key_missing = bdrv_key_required(bs);
- if (bs->backing_file[0]) {
- info->value->inserted->has_backing_file = true;
- info->value->inserted->backing_file = g_strdup(bs->backing_file);
- }
+BlockInfoList *qmp_query_block(Error **errp)
+{
+ BlockInfoList *head = NULL, **p_next = &head;
+ BlockDriverState *bs;
- info->value->inserted->backing_file_depth =
- bdrv_get_backing_file_depth(bs);
-
- if (bs->io_limits_enabled) {
- info->value->inserted->bps =
- bs->io_limits.bps[BLOCK_IO_LIMIT_TOTAL];
- info->value->inserted->bps_rd =
- bs->io_limits.bps[BLOCK_IO_LIMIT_READ];
- info->value->inserted->bps_wr =
- bs->io_limits.bps[BLOCK_IO_LIMIT_WRITE];
- info->value->inserted->iops =
- bs->io_limits.iops[BLOCK_IO_LIMIT_TOTAL];
- info->value->inserted->iops_rd =
- bs->io_limits.iops[BLOCK_IO_LIMIT_READ];
- info->value->inserted->iops_wr =
- bs->io_limits.iops[BLOCK_IO_LIMIT_WRITE];
- }
- }
+ QTAILQ_FOREACH(bs, &bdrv_states, list) {
+ BlockInfoList *info = g_malloc0(sizeof(*info));
+ info->value = bdrv_query_info(bs);
- /* XXX: waiting for the qapi to support GSList */
- if (!cur_item) {
- head = cur_item = info;
- } else {
- cur_item->next = info;
- cur_item = info;
- }
+ *p_next = info;
+ p_next = &info->next;
}
return head;
diff --git a/block.h b/block.h
index e2d89d7..2ede16d 100644
--- a/block.h
+++ b/block.h
@@ -313,6 +313,7 @@ void bdrv_get_backing_filename(BlockDriverState *bs,
char *filename, int filename_size);
void bdrv_get_full_backing_filename(BlockDriverState *bs,
char *dest, size_t sz);
+BlockInfo *bdrv_query_info(BlockDriverState *s);
int bdrv_can_snapshot(BlockDriverState *bs);
int bdrv_is_snapshot(BlockDriverState *bs);
BlockDriverState *bdrv_snapshots(void);
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 02/16] block: add bdrv_query_stats
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 01/16] block: add bdrv_query_info Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 03/16] block: add bdrv_open_backing_file Paolo Bonzini
` (13 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
qmp_query_blockstat cannot have errors, remove the Error argument and
create a new public function bdrv_query_stats out of it.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
block.c | 18 ++++++------------
block.h | 1 +
2 file modificati, 7 inserzioni(+), 12 rimozioni(-)
diff --git a/block.c b/block.c
index ce7c2ea..f94e001 100644
--- a/block.c
+++ b/block.c
@@ -2865,8 +2865,7 @@ BlockInfoList *qmp_query_block(Error **errp)
return head;
}
-/* Consider exposing this as a full fledged QMP command */
-static BlockStats *qmp_query_blockstat(const BlockDriverState *bs, Error **errp)
+BlockStats *bdrv_query_stats(const BlockDriverState *bs)
{
BlockStats *s;
@@ -2890,7 +2889,7 @@ static BlockStats *qmp_query_blockstat(const BlockDriverState *bs, Error **errp)
if (bs->file) {
s->has_parent = true;
- s->parent = qmp_query_blockstat(bs->file, NULL);
+ s->parent = bdrv_query_stats(bs->file);
}
return s;
@@ -2898,20 +2897,15 @@ static BlockStats *qmp_query_blockstat(const BlockDriverState *bs, Error **errp)
BlockStatsList *qmp_query_blockstats(Error **errp)
{
- BlockStatsList *head = NULL, *cur_item = NULL;
+ BlockStatsList *head = NULL, **p_next = &head;
BlockDriverState *bs;
QTAILQ_FOREACH(bs, &bdrv_states, list) {
BlockStatsList *info = g_malloc0(sizeof(*info));
- info->value = qmp_query_blockstat(bs, NULL);
+ info->value = bdrv_query_stats(bs);
- /* XXX: waiting for the qapi to support GSList */
- if (!cur_item) {
- head = cur_item = info;
- } else {
- cur_item->next = info;
- cur_item = info;
- }
+ *p_next = info;
+ p_next = &info->next;
}
return head;
diff --git a/block.h b/block.h
index 2ede16d..ed4b90d 100644
--- a/block.h
+++ b/block.h
@@ -314,6 +314,7 @@ void bdrv_get_backing_filename(BlockDriverState *bs,
void bdrv_get_full_backing_filename(BlockDriverState *bs,
char *dest, size_t sz);
BlockInfo *bdrv_query_info(BlockDriverState *s);
+BlockStats *bdrv_query_stats(const BlockDriverState *bs);
int bdrv_can_snapshot(BlockDriverState *bs);
int bdrv_is_snapshot(BlockDriverState *bs);
BlockDriverState *bdrv_snapshots(void);
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 03/16] block: add bdrv_open_backing_file
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 01/16] block: add bdrv_query_info Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 02/16] block: add bdrv_query_stats Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 04/16] block: introduce new dirty bitmap functionality Paolo Bonzini
` (12 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Mirroring runs without the backing file so that it can be copied outside
QEMU. However, we need to add it at the time the job is completed and
QEMU switches to the target. Factor out the common bits of opening an
image and completing a mirroring operation.
The new function does not assume that the file is closed immediately after
it returns failure, so it keeps the BDRV_O_NO_BACKING flag up-to-date.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: keep_read_only does not exist anymore
block.c | 56 ++++++++++++++++++++++++++++++++++++++------------------
block.h | 1 +
2 file modificati, 39 inserzioni(+), 18 rimozioni(-)
diff --git a/block.c b/block.c
index f94e001..4787312 100644
--- a/block.c
+++ b/block.c
@@ -734,6 +734,42 @@ int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags)
return 0;
}
+int bdrv_open_backing_file(BlockDriverState *bs)
+{
+ char backing_filename[PATH_MAX];
+ int back_flags, ret;
+ BlockDriver *back_drv = NULL;
+
+ if (bs->backing_hd != NULL) {
+ return 0;
+ }
+
+ bs->open_flags &= ~BDRV_O_NO_BACKING;
+ if (bs->backing_file[0] == '\0') {
+ return 0;
+ }
+
+ bs->backing_hd = bdrv_new("");
+ bdrv_get_full_backing_filename(bs, backing_filename,
+ sizeof(backing_filename));
+
+ if (bs->backing_format[0] != '\0') {
+ back_drv = bdrv_find_format(bs->backing_format);
+ }
+
+ /* backing files always opened read-only */
+ back_flags = bs->open_flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT);
+
+ ret = bdrv_open(bs->backing_hd, backing_filename, back_flags, back_drv);
+ if (ret < 0) {
+ bdrv_delete(bs->backing_hd);
+ bs->backing_hd = NULL;
+ bs->open_flags |= BDRV_O_NO_BACKING;
+ return ret;
+ }
+ return 0;
+}
+
/*
* Opens a disk image (raw, qcow2, vmdk, ...)
*/
@@ -821,24 +857,8 @@ int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
}
/* If there is a backing file, use it */
- if ((flags & BDRV_O_NO_BACKING) == 0 && bs->backing_file[0] != '\0') {
- char backing_filename[PATH_MAX];
- int back_flags;
- BlockDriver *back_drv = NULL;
-
- bs->backing_hd = bdrv_new("");
- bdrv_get_full_backing_filename(bs, backing_filename,
- sizeof(backing_filename));
-
- if (bs->backing_format[0] != '\0') {
- back_drv = bdrv_find_format(bs->backing_format);
- }
-
- /* backing files always opened read-only */
- back_flags =
- flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING);
-
- ret = bdrv_open(bs->backing_hd, backing_filename, back_flags, back_drv);
+ if ((flags & BDRV_O_NO_BACKING) == 0) {
+ ret = bdrv_open_backing_file(bs);
if (ret < 0) {
bdrv_close(bs);
return ret;
diff --git a/block.h b/block.h
index ed4b90d..096fa09 100644
--- a/block.h
+++ b/block.h
@@ -133,6 +133,7 @@ void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top);
void bdrv_delete(BlockDriverState *bs);
int bdrv_parse_cache_flags(const char *mode, int *flags);
int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags);
+int bdrv_open_backing_file(BlockDriverState *bs);
int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
BlockDriver *drv);
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 04/16] block: introduce new dirty bitmap functionality
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (2 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 03/16] block: add bdrv_open_backing_file Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 05/16] block: export dirty bitmap information in query-block Paolo Bonzini
` (11 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Assert that write_compressed is never used with the dirty bitmap.
Setting the bits early is wrong, because a coroutine might concurrently
examine them and copy incomplete data from the source.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
block.c | 51 +++++++++++++++++++++++++++++++++++++++++++++------
block.h | 5 +++--
2 file modificati, 48 inserzioni(+), 8 rimozioni(-)
diff --git a/block.c b/block.c
index 4787312..057b2b2 100644
--- a/block.c
+++ b/block.c
@@ -2389,7 +2389,7 @@ static int coroutine_fn bdrv_co_do_writev(BlockDriverState *bs,
}
if (bs->dirty_bitmap) {
- set_dirty_bitmap(bs, sector_num, nb_sectors, 1);
+ bdrv_set_dirty(bs, sector_num, nb_sectors);
}
if (bs->wr_highest_sector < sector_num + nb_sectors - 1) {
@@ -2958,9 +2958,7 @@ int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num,
if (bdrv_check_request(bs, sector_num, nb_sectors))
return -EIO;
- if (bs->dirty_bitmap) {
- set_dirty_bitmap(bs, sector_num, nb_sectors, 1);
- }
+ assert(!bs->dirty_bitmap);
return drv->bdrv_write_compressed(bs, sector_num, buf, nb_sectors);
}
@@ -4219,13 +4217,54 @@ int bdrv_get_dirty(BlockDriverState *bs, int64_t sector)
if (bs->dirty_bitmap &&
(sector << BDRV_SECTOR_BITS) < bdrv_getlength(bs)) {
- return !!(bs->dirty_bitmap[chunk / (sizeof(unsigned long) * 8)] &
- (1UL << (chunk % (sizeof(unsigned long) * 8))));
+ return !!(bs->dirty_bitmap[chunk / BITS_PER_LONG] &
+ (1UL << (chunk % BITS_PER_LONG)));
} else {
return 0;
}
}
+int64_t bdrv_get_next_dirty(BlockDriverState *bs, int64_t sector)
+{
+ int64_t chunk;
+ int bit, elem;
+
+ /* Avoid an infinite loop. */
+ assert(bs->dirty_count > 0);
+
+ sector = (sector | (BDRV_SECTORS_PER_DIRTY_CHUNK - 1)) + 1;
+ chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK;
+
+ QEMU_BUILD_BUG_ON(sizeof(bs->dirty_bitmap[0]) * 8 != BITS_PER_LONG);
+ elem = chunk / BITS_PER_LONG;
+ bit = chunk % BITS_PER_LONG;
+ for (;;) {
+ if (sector >= bs->total_sectors) {
+ sector = 0;
+ bit = elem = 0;
+ }
+ if (bit == 0 && bs->dirty_bitmap[elem] == 0) {
+ sector += BDRV_SECTORS_PER_DIRTY_CHUNK * BITS_PER_LONG;
+ elem++;
+ } else {
+ if (bs->dirty_bitmap[elem] & (1UL << bit)) {
+ return sector;
+ }
+ sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
+ if (++bit == BITS_PER_LONG) {
+ bit = 0;
+ elem++;
+ }
+ }
+ }
+}
+
+void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
+ int nr_sectors)
+{
+ set_dirty_bitmap(bs, cur_sector, nr_sectors, 1);
+}
+
void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
int nr_sectors)
{
diff --git a/block.h b/block.h
index 096fa09..27b8f80 100644
--- a/block.h
+++ b/block.h
@@ -353,8 +353,9 @@ void *qemu_blockalign(BlockDriverState *bs, size_t size);
void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable);
int bdrv_get_dirty(BlockDriverState *bs, int64_t sector);
-void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
- int nr_sectors);
+void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
+void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
+int64_t bdrv_get_next_dirty(BlockDriverState *bs, int64_t sector);
int64_t bdrv_get_dirty_count(BlockDriverState *bs);
void bdrv_enable_copy_on_read(BlockDriverState *bs);
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 05/16] block: export dirty bitmap information in query-block
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (3 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 04/16] block: introduce new dirty bitmap functionality Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 16:51 ` Eric Blake
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 06/16] block: rename block_job_complete to block_job_completed Paolo Bonzini
` (10 subsequent siblings)
15 siblings, 1 reply; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: unit of measure is now bytes
block.c | 7 +++++++
qapi-schema.json | 20 ++++++++++++++++++--
2 file modificati, 25 inserzioni(+), 2 rimozioni(-)
diff --git a/block.c b/block.c
index 057b2b2..b167f61 100644
--- a/block.c
+++ b/block.c
@@ -2835,6 +2835,13 @@ BlockInfo *bdrv_query_info(BlockDriverState *bs)
info->io_status = bs->iostatus;
}
+ if (bs->dirty_bitmap) {
+ info->has_dirty = true;
+ info->dirty = g_malloc0(sizeof(*info->dirty));
+ info->dirty->count = bdrv_get_dirty_count(bs) *
+ BDRV_SECTORS_PER_DIRTY_CHUNK * BDRV_SECTOR_SIZE;
+ }
+
if (bs->drv) {
info->has_inserted = true;
info->inserted = g_malloc0(sizeof(*info->inserted));
diff --git a/qapi-schema.json b/qapi-schema.json
index f9dbdae..2aac6b8 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -629,7 +629,7 @@
'*backing_file': 'str', 'backing_file_depth': 'int',
'encrypted': 'bool', 'encryption_key_missing': 'bool',
'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int',
- 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int'} }
+ 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int' } }
##
# @BlockDeviceIoStatus:
@@ -647,6 +647,18 @@
{ 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'failed', 'nospace' ] }
##
+# @BlockDirtyInfo:
+#
+# Block dirty bitmap information.
+#
+# @count: number of dirty bytes according to the dirty bitmap
+#
+# Since: 1.3
+##
+{ 'type': 'BlockDirtyInfo',
+ 'data': {'count': 'int'} }
+
+##
# @BlockInfo:
#
# Block device information. This structure describes a virtual device and
@@ -665,6 +677,9 @@
# @tray_open: #optional True if the device has a tray and it is open
# (only present if removable is true)
#
+# @dirty: #optional dirty bitmap information (only present if the dirty
+# bitmap is enabled)
+#
# @io-status: #optional @BlockDeviceIoStatus. Only present if the device
# supports it and the VM is configured to stop on errors
#
@@ -676,7 +691,8 @@
{ 'type': 'BlockInfo',
'data': {'device': 'str', 'type': 'str', 'removable': 'bool',
'locked': 'bool', '*inserted': 'BlockDeviceInfo',
- '*tray_open': 'bool', '*io-status': 'BlockDeviceIoStatus'} }
+ '*tray_open': 'bool', '*io-status': 'BlockDeviceIoStatus',
+ '*dirty': 'BlockDirtyInfo' } }
##
# @query-block:
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 06/16] block: rename block_job_complete to block_job_completed
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (4 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 05/16] block: export dirty bitmap information in query-block Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 07/16] block: add block-job-complete Paolo Bonzini
` (9 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
The imperative will be used for the QMP command.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: commit is also affected
block/commit.c | 2 +-
block/stream.c | 4 ++--
blockjob.c | 2 +-
blockjob.h | 4 ++--
4 file modificati, 6 inserzioni(+), 6 rimozioni(-)
diff --git a/block/commit.c b/block/commit.c
index 733c914..897af5f 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -160,7 +160,7 @@ exit_restore_reopen:
bdrv_reopen(overlay_bs, s->orig_overlay_flags, NULL);
}
- block_job_complete(&s->common, ret);
+ block_job_completed(&s->common, ret);
}
static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
diff --git a/block/stream.c b/block/stream.c
index 7926652..0c0fc7a 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -86,7 +86,7 @@ static void coroutine_fn stream_run(void *opaque)
s->common.len = bdrv_getlength(bs);
if (s->common.len < 0) {
- block_job_complete(&s->common, s->common.len);
+ block_job_completed(&s->common, s->common.len);
return;
}
@@ -184,7 +184,7 @@ wait:
}
qemu_vfree(buf);
- block_job_complete(&s->common, ret);
+ block_job_completed(&s->common, ret);
}
static void stream_set_speed(BlockJob *job, int64_t speed, Error **errp)
diff --git a/blockjob.c b/blockjob.c
index f55f55a..b5c16f3 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -71,7 +71,7 @@ void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
return job;
}
-void block_job_complete(BlockJob *job, int ret)
+void block_job_completed(BlockJob *job, int ret)
{
BlockDriverState *bs = job->bs;
diff --git a/blockjob.h b/blockjob.h
index 930cc3c..c2261a9 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -135,14 +135,14 @@ void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs,
void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns);
/**
- * block_job_complete:
+ * block_job_completed:
* @job: The job being completed.
* @ret: The status code.
*
* Call the completion function that was registered at creation time, and
* free @job.
*/
-void block_job_complete(BlockJob *job, int ret);
+void block_job_completed(BlockJob *job, int ret);
/**
* block_job_set_speed:
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 07/16] block: add block-job-complete
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (5 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 06/16] block: rename block_job_complete to block_job_completed Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 08/16] block: introduce BLOCK_JOB_READY event Paolo Bonzini
` (8 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
While streaming can be dropped as soon as it progressed through the whole
image, mirroring needs to be completed manually for two reasons: 1) so that
management knows exactly when the VM switches to the target; 2) because
for other use cases such as replication, we may leave the operation running
for the whole life of the virtual machine.
Add a new block job command that manually completes background operations.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
blockdev.c | 13 +++++++++++++
blockjob.c | 10 ++++++++++
blockjob.h | 15 +++++++++++++++
hmp-commands.hx | 17 ++++++++++++++++-
hmp.c | 10 ++++++++++
hmp.h | 1 +
qapi-schema.json | 25 +++++++++++++++++++++++++
qerror.h | 3 +++
qmp-commands.hx | 5 +++++
trace-events | 1 +
10 file modificati, 99 inserzioni(+). 1 rimozione(-)
diff --git a/blockdev.c b/blockdev.c
index 99828ad..87dabbe 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1265,6 +1265,19 @@ void qmp_block_job_resume(const char *device, Error **errp)
block_job_resume(job);
}
+void qmp_block_job_complete(const char *device, Error **errp)
+{
+ BlockJob *job = find_block_job(device);
+
+ if (!job) {
+ error_set(errp, QERR_BLOCK_JOB_NOT_ACTIVE, device);
+ return;
+ }
+
+ trace_qmp_block_job_complete(job);
+ block_job_complete(job, errp);
+}
+
static void do_qmp_query_block_jobs_one(void *opaque, BlockDriverState *bs)
{
BlockJobInfoList **prev = opaque;
diff --git a/blockjob.c b/blockjob.c
index b5c16f3..c93a0e0 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -99,6 +99,16 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
job->speed = speed;
}
+void block_job_complete(BlockJob *job, Error **errp)
+{
+ if (job->paused || job->cancelled || !job->job_type->complete) {
+ error_set(errp, QERR_BLOCK_JOB_NOT_READY, job->bs->device_name);
+ return;
+ }
+
+ job->job_type->complete(job, errp);
+}
+
void block_job_pause(BlockJob *job)
{
job->paused = true;
diff --git a/blockjob.h b/blockjob.h
index c2261a9..c44e2ea 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -41,6 +41,12 @@ typedef struct BlockJobType {
/** Optional callback for job types that support setting a speed limit */
void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
+
+ /**
+ * Optional callback for job types whose completion must be triggered
+ * manually.
+ */
+ void (*complete)(BlockJob *job, Error **errp);
} BlockJobType;
/**
@@ -164,6 +170,15 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
void block_job_cancel(BlockJob *job);
/**
+ * block_job_complete:
+ * @job: The job to be completed.
+ * @errp: Error object.
+ *
+ * Asynchronously complete the specified job.
+ */
+void block_job_complete(BlockJob *job, Error **errp);
+
+/**
* block_job_is_cancelled:
* @job: The job being queried.
*
diff --git a/hmp-commands.hx b/hmp-commands.hx
index e0b537d..fb2f237 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -109,7 +109,22 @@ ETEXI
STEXI
@item block_job_cancel
@findex block_job_cancel
-Stop an active block streaming operation.
+Stop an active background block operation (streaming, mirroring).
+ETEXI
+
+ {
+ .name = "block_job_complete",
+ .args_type = "device:B",
+ .params = "device",
+ .help = "stop an active background block operation",
+ .mhandler.cmd = hmp_block_job_complete,
+ },
+
+STEXI
+@item block_job_complete
+@findex block_job_complete
+Manually trigger completion of an active background block operation.
+For mirroring, this will switch the device to the destination path.
ETEXI
{
diff --git a/hmp.c b/hmp.c
index 70bdec2..4cfb5fe 100644
--- a/hmp.c
+++ b/hmp.c
@@ -978,6 +978,16 @@ void hmp_block_job_resume(Monitor *mon, const QDict *qdict)
hmp_handle_error(mon, &error);
}
+void hmp_block_job_complete(Monitor *mon, const QDict *qdict)
+{
+ Error *error = NULL;
+ const char *device = qdict_get_str(qdict, "device");
+
+ qmp_block_job_complete(device, &error);
+
+ hmp_handle_error(mon, &error);
+}
+
typedef struct MigrationStatus
{
QEMUTimer *timer;
diff --git a/hmp.h b/hmp.h
index 71ea384..7bdd23c 100644
--- a/hmp.h
+++ b/hmp.h
@@ -66,6 +66,7 @@ void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
void hmp_block_job_resume(Monitor *mon, const QDict *qdict);
+void hmp_block_job_complete(Monitor *mon, const QDict *qdict);
void hmp_migrate(Monitor *mon, const QDict *qdict);
void hmp_device_del(Monitor *mon, const QDict *qdict);
void hmp_dump_guest_memory(Monitor *mon, const QDict *qdict);
diff --git a/qapi-schema.json b/qapi-schema.json
index 2aac6b8..8211935 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2019,6 +2019,31 @@
{ 'command': 'block-job-resume', 'data': { 'device': 'str' } }
##
+# @block-job-complete:
+#
+# Manually trigger completion of an active background block operation. This
+# is supported for drive mirroring, where it also switches the device to
+# write to the target path only.
+#
+# 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.
+#
+# A cancelled or paused job cannot be completed.
+#
+# @device: the device name
+#
+# Returns: Nothing on success
+# If no background operation is active on this device, DeviceNotActive
+#
+# Since: 1.3
+##
+{ 'command': 'block-job-complete', 'data': { 'device': 'str' } }
+
+##
# @ObjectTypeInfo:
#
# This structure describes a search result from @qom-list-types
diff --git a/qerror.h b/qerror.h
index c91708c..dacac52 100644
--- a/qerror.h
+++ b/qerror.h
@@ -54,6 +54,9 @@ void assert_no_error(Error *err);
#define QERR_BLOCK_JOB_PAUSED \
ERROR_CLASS_GENERIC_ERROR, "The block job for device '%s' is currently paused"
+#define QERR_BLOCK_JOB_NOT_READY \
+ ERROR_CLASS_GENERIC_ERROR, "The active block job for device '%s' cannot be completed"
+
#define QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED \
ERROR_CLASS_GENERIC_ERROR, "Block format '%s' used by device '%s' does not support feature '%s'"
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 2f8477e..dbc3974 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -843,6 +843,11 @@ EQMP
.mhandler.cmd_new = qmp_marshal_input_block_job_resume,
},
{
+ .name = "block-job-complete",
+ .args_type = "device:B",
+ .mhandler.cmd_new = qmp_marshal_input_block_job_complete,
+ },
+ {
.name = "transaction",
.args_type = "actions:q",
.mhandler.cmd_new = qmp_marshal_input_transaction,
diff --git a/trace-events b/trace-events
index 42b66f1..beb1cca 100644
--- a/trace-events
+++ b/trace-events
@@ -81,6 +81,7 @@ commit_start(void *bs, void *base, void *top, void *s, void *co, void *opaque) "
qmp_block_job_cancel(void *job) "job %p"
qmp_block_job_pause(void *job) "job %p"
qmp_block_job_resume(void *job) "job %p"
+qmp_block_job_complete(void *job) "job %p"
block_job_cb(void *bs, void *job, int ret) "bs %p job %p ret %d"
qmp_block_stream(void *bs, void *job) "bs %p job %p"
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 08/16] block: introduce BLOCK_JOB_READY event
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (6 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 07/16] block: add block-job-complete Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 16:58 ` Eric Blake
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 09/16] mirror: introduce mirror job Paolo Bonzini
` (7 subsequent siblings)
15 siblings, 1 reply; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Even for jobs that need to be manually completed, management may want
to take care itself of the completion, not requiring the user to issue
a command to terminate the job. In this case we want to avoid that
they poll us continuously, waiting for completion to become available.
Thus, add a new event that signals the phase switch and the availability
of the block-job-complete command.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
QMP/qmp-events.txt | 20 ++++++++++++++++++++
blockdev.c | 14 --------------
blockjob.c | 21 +++++++++++++++++++++
blockjob.h | 16 ++++++++++++++++
monitor.c | 1 +
monitor.h | 1 +
qapi-schema.json | 3 ++-
7 file modificati, 61 inserzioni(+), 15 rimozioni(-)
diff --git a/QMP/qmp-events.txt b/QMP/qmp-events.txt
index 987c575..6d9bc12 100644
--- a/QMP/qmp-events.txt
+++ b/QMP/qmp-events.txt
@@ -118,6 +118,26 @@ Example:
"action": "stop" },
"timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+BLOCK_JOB_READY
+---------------
+
+Emitted when a block job is ready to complete.
+
+Data:
+
+- "device": device name (json-string)
+
+Example:
+
+{ "event": "BLOCK_JOB_READY",
+ "data": { "device": "ide0-hd1",
+ "operation": "write",
+ "action": "stop" },
+ "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+
+Note: The "ready to complete" status is always reset by a BLOCK_JOB_ERROR
+event.
+
DEVICE_TRAY_MOVED
-----------------
diff --git a/blockdev.c b/blockdev.c
index 87dabbe..1068960 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1056,20 +1056,6 @@ void qmp_block_resize(const char *device, int64_t size, Error **errp)
}
}
-static QObject *qobject_from_block_job(BlockJob *job)
-{
- return qobject_from_jsonf("{ 'type': %s,"
- "'device': %s,"
- "'len': %" PRId64 ","
- "'offset': %" PRId64 ","
- "'speed': %" PRId64 " }",
- job->job_type->job_type,
- bdrv_get_device_name(job->bs),
- job->len,
- job->offset,
- job->speed);
-}
-
static void block_job_cb(void *opaque, int ret)
{
BlockDriverState *bs = opaque;
diff --git a/blockjob.c b/blockjob.c
index c93a0e0..fbb7e1c 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -225,6 +225,27 @@ static void block_job_iostatus_set_err(BlockJob *job, int error)
}
+QObject *qobject_from_block_job(BlockJob *job)
+{
+ return qobject_from_jsonf("{ 'type': %s,"
+ "'device': %s,"
+ "'len': %" PRId64 ","
+ "'offset': %" PRId64 ","
+ "'speed': %" PRId64 " }",
+ job->job_type->job_type,
+ bdrv_get_device_name(job->bs),
+ job->len,
+ job->offset,
+ job->speed);
+}
+
+void block_job_ready(BlockJob *job)
+{
+ QObject *data = qobject_from_block_job(job);
+ monitor_protocol_event(QEVENT_BLOCK_JOB_READY, data);
+ qobject_decref(data);
+}
+
BlockErrorAction block_job_error_action(BlockJob *job, BlockDriverState *bs,
BlockdevOnError on_err,
int is_read, int error)
diff --git a/blockjob.h b/blockjob.h
index c44e2ea..fb2392e 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -211,6 +211,22 @@ void block_job_pause(BlockJob *job);
void block_job_resume(BlockJob *job);
/**
+ * qobject_from_block_job:
+ * @job: The job whose information is requested.
+ *
+ * Return a QDict corresponding to @job's query-block-jobs entry.
+ */
+QObject *qobject_from_block_job(BlockJob *job);
+
+/**
+ * block_job_ready:
+ * @job: The job which is now ready to complete.
+ *
+ * Send a BLOCK_JOB_READY event for the specified job.
+ */
+void block_job_ready(BlockJob *job);
+
+/**
* block_job_is_paused:
* @job: The job being queried.
*
diff --git a/monitor.c b/monitor.c
index 131b325..0a07c2d 100644
--- a/monitor.c
+++ b/monitor.c
@@ -451,6 +451,7 @@ static const char *monitor_event_names[] = {
[QEVENT_BLOCK_JOB_COMPLETED] = "BLOCK_JOB_COMPLETED",
[QEVENT_BLOCK_JOB_CANCELLED] = "BLOCK_JOB_CANCELLED",
[QEVENT_BLOCK_JOB_ERROR] = "BLOCK_JOB_ERROR",
+ [QEVENT_BLOCK_JOB_READY] = "BLOCK_JOB_READY",
[QEVENT_DEVICE_TRAY_MOVED] = "DEVICE_TRAY_MOVED",
[QEVENT_SUSPEND] = "SUSPEND",
[QEVENT_SUSPEND_DISK] = "SUSPEND_DISK",
diff --git a/monitor.h b/monitor.h
index b6e7d95..d1d2850 100644
--- a/monitor.h
+++ b/monitor.h
@@ -39,6 +39,7 @@ typedef enum MonitorEvent {
QEVENT_BLOCK_JOB_COMPLETED,
QEVENT_BLOCK_JOB_CANCELLED,
QEVENT_BLOCK_JOB_ERROR,
+ QEVENT_BLOCK_JOB_READY,
QEVENT_DEVICE_TRAY_MOVED,
QEVENT_SUSPEND,
QEVENT_SUSPEND_DISK,
diff --git a/qapi-schema.json b/qapi-schema.json
index 8211935..bc1b585 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2023,7 +2023,8 @@
#
# Manually trigger completion of an active background block operation. This
# is supported for drive mirroring, where it also switches the device to
-# write to the target path only.
+# write to the target path only. 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
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 09/16] mirror: introduce mirror job
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (7 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 08/16] block: introduce BLOCK_JOB_READY event Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command Paolo Bonzini
` (6 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
This patch adds the implementation of a new job that mirrors a disk to
a new image while letting the guest continue using the old image.
The target is treated as a "black box" and data is copied from the
source to the target in the background. This can be used for several
purposes, including storage migration, continuous replication, and
observation of the guest I/O in an external program. It is also a
first step in replacing the inefficient block migration code that is
part of QEMU.
The job is possibly never-ending, but it is logically structured into
two phases: 1) copy all data as fast as possible until the target
first gets in sync with the source; 2) keep target in sync and
ensure that reopening to the target gets a correct (full) copy
of the source data.
The second phase is indicated by the progress in "info block-jobs"
reporting the current offset to be equal to the length of the file.
When the job is cancelled in the second phase, QEMU will run the
job until the source is clean and quiescent, then it will report
successful completion of the job.
In other words, the BLOCK_JOB_CANCELLED event means that the target
may _not_ be consistent with a past state of the source; the
BLOCK_JOB_COMPLETED event means that the target is consistent with
a past state of the source. (Note that it could already happen
that management lost the race against QEMU and got a completion
event instead of cancellation).
It is not yet possible to complete the job and switch over to the target
disk. The next patches will fix this and add many refinements to the
basic idea introduced here. These include improved error management,
some tunable knobs and performance optimizations.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: test bdrv_flush return value (which was checked anyway
later in the series, hence it does not appear in the overall
diff attached to the cover letter)
block/Makefile.objs | 1 +
block/mirror.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++
block_int.h | 20 +++++
qapi-schema.json | 17 ++++
trace-events | 7 ++
5 file modificati, 280 inserzioni(+)
create mode 100644 block/mirror.c
diff --git a/block/Makefile.objs b/block/Makefile.objs
index 554f429..806e526 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -12,3 +12,4 @@ block-obj-$(CONFIG_GLUSTERFS) += gluster.o
common-obj-y += stream.o
common-obj-y += commit.o
+common-obj-y += mirror.o
diff --git a/block/mirror.c b/block/mirror.c
new file mode 100644
index 0000000..b353798
--- /dev/null
+++ b/block/mirror.c
@@ -0,0 +1,235 @@
+/*
+ * Image mirroring
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Authors:
+ * Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include "trace.h"
+#include "blockjob.h"
+#include "block_int.h"
+#include "qemu/ratelimit.h"
+
+enum {
+ /*
+ * Size of data buffer for populating the image file. This should be large
+ * enough to process multiple clusters in a single call, so that populating
+ * contiguous regions of the image is efficient.
+ */
+ BLOCK_SIZE = 512 * BDRV_SECTORS_PER_DIRTY_CHUNK, /* in bytes */
+};
+
+#define SLICE_TIME 100000000ULL /* ns */
+
+typedef struct MirrorBlockJob {
+ BlockJob common;
+ RateLimit limit;
+ BlockDriverState *target;
+ MirrorSyncMode mode;
+ int64_t sector_num;
+ uint8_t *buf;
+} MirrorBlockJob;
+
+static int coroutine_fn mirror_iteration(MirrorBlockJob *s)
+{
+ BlockDriverState *source = s->common.bs;
+ BlockDriverState *target = s->target;
+ QEMUIOVector qiov;
+ int ret, nb_sectors;
+ int64_t end;
+ struct iovec iov;
+
+ end = s->common.len >> BDRV_SECTOR_BITS;
+ s->sector_num = bdrv_get_next_dirty(source, s->sector_num);
+ nb_sectors = MIN(BDRV_SECTORS_PER_DIRTY_CHUNK, end - s->sector_num);
+ bdrv_reset_dirty(source, s->sector_num, nb_sectors);
+
+ /* Copy the dirty cluster. */
+ iov.iov_base = s->buf;
+ iov.iov_len = nb_sectors * 512;
+ qemu_iovec_init_external(&qiov, &iov, 1);
+
+ trace_mirror_one_iteration(s, s->sector_num, nb_sectors);
+ ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov);
+ if (ret < 0) {
+ return ret;
+ }
+ return bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov);
+}
+
+static void coroutine_fn mirror_run(void *opaque)
+{
+ MirrorBlockJob *s = opaque;
+ BlockDriverState *bs = s->common.bs;
+ int64_t sector_num, end;
+ int ret = 0;
+ int n;
+ bool synced = false;
+
+ if (block_job_is_cancelled(&s->common)) {
+ goto immediate_exit;
+ }
+
+ s->common.len = bdrv_getlength(bs);
+ if (s->common.len < 0) {
+ block_job_completed(&s->common, s->common.len);
+ return;
+ }
+
+ end = s->common.len >> BDRV_SECTOR_BITS;
+ s->buf = qemu_blockalign(bs, BLOCK_SIZE);
+
+ if (s->mode != MIRROR_SYNC_MODE_NONE) {
+ /* First part, loop on the sectors and initialize the dirty bitmap. */
+ BlockDriverState *base;
+ base = s->mode == MIRROR_SYNC_MODE_FULL ? NULL : bs->backing_hd;
+ for (sector_num = 0; sector_num < end; ) {
+ int64_t next = (sector_num | (BDRV_SECTORS_PER_DIRTY_CHUNK - 1)) + 1;
+ ret = bdrv_co_is_allocated_above(bs, base,
+ sector_num, next - sector_num, &n);
+
+ if (ret < 0) {
+ goto immediate_exit;
+ }
+
+ assert(n > 0);
+ if (ret == 1) {
+ bdrv_set_dirty(bs, sector_num, n);
+ sector_num = next;
+ } else {
+ sector_num += n;
+ }
+ }
+ }
+
+ s->sector_num = -1;
+ for (;;) {
+ uint64_t delay_ns;
+ int64_t cnt;
+ bool should_complete;
+
+ cnt = bdrv_get_dirty_count(bs);
+ if (cnt != 0) {
+ ret = mirror_iteration(s);
+ if (ret < 0) {
+ goto immediate_exit;
+ }
+ cnt = bdrv_get_dirty_count(bs);
+ }
+
+ should_complete = false;
+ if (cnt == 0) {
+ trace_mirror_before_flush(s);
+ ret = bdrv_flush(s->target);
+ if (ret < 0) {
+ goto immediate_exit;
+ }
+
+ /* 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.
+ */
+ synced = true;
+ s->common.offset = end * BDRV_SECTOR_SIZE;
+ should_complete = block_job_is_cancelled(&s->common);
+ cnt = bdrv_get_dirty_count(bs);
+ }
+
+ if (cnt == 0 && should_complete) {
+ /* The dirty bitmap is not updated while operations are pending.
+ * If we're about to exit, wait for pending operations before
+ * calling bdrv_get_dirty_count(bs), or we may exit while the
+ * source has dirty data to copy!
+ *
+ * Note that I/O can be submitted by the guest while
+ * mirror_populate runs.
+ */
+ trace_mirror_before_drain(s, cnt);
+ bdrv_drain_all();
+ cnt = bdrv_get_dirty_count(bs);
+ }
+
+ ret = 0;
+ trace_mirror_before_sleep(s, cnt, synced);
+ if (!synced) {
+ /* Publish progress */
+ s->common.offset = end * BDRV_SECTOR_SIZE - cnt * BLOCK_SIZE;
+
+ if (s->common.speed) {
+ delay_ns = ratelimit_calculate_delay(&s->limit, BDRV_SECTORS_PER_DIRTY_CHUNK);
+ } else {
+ delay_ns = 0;
+ }
+
+ /* Note that even when no rate limit is applied we need to yield
+ * with no pending I/O here so that qemu_aio_flush() returns.
+ */
+ block_job_sleep_ns(&s->common, rt_clock, delay_ns);
+ if (block_job_is_cancelled(&s->common)) {
+ break;
+ }
+ } else if (!should_complete) {
+ delay_ns = (cnt == 0 ? SLICE_TIME : 0);
+ block_job_sleep_ns(&s->common, rt_clock, delay_ns);
+ } else if (cnt == 0) {
+ /* The two disks are in sync. Exit and report successful
+ * completion.
+ */
+ assert(QLIST_EMPTY(&bs->tracked_requests));
+ s->common.cancelled = false;
+ break;
+ }
+ }
+
+immediate_exit:
+ g_free(s->buf);
+ bdrv_set_dirty_tracking(bs, false);
+ bdrv_close(s->target);
+ bdrv_delete(s->target);
+ block_job_completed(&s->common, ret);
+}
+
+static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
+{
+ MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+
+ if (speed < 0) {
+ error_set(errp, QERR_INVALID_PARAMETER, "speed");
+ return;
+ }
+ ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
+}
+
+static BlockJobType mirror_job_type = {
+ .instance_size = sizeof(MirrorBlockJob),
+ .job_type = "mirror",
+ .set_speed = mirror_set_speed,
+};
+
+void mirror_start(BlockDriverState *bs, BlockDriverState *target,
+ int64_t speed, MirrorSyncMode mode,
+ BlockDriverCompletionFunc *cb,
+ void *opaque, Error **errp)
+{
+ MirrorBlockJob *s;
+
+ s = block_job_create(&mirror_job_type, bs, speed, cb, opaque, errp);
+ if (!s) {
+ return;
+ }
+
+ s->target = target;
+ s->mode = mode;
+ bdrv_set_dirty_tracking(bs, true);
+ bdrv_set_enable_write_cache(s->target, true);
+ s->common.co = qemu_coroutine_create(mirror_run);
+ trace_mirror_start(bs, s, s->common.co, opaque);
+ qemu_coroutine_enter(s->common.co, s);
+}
diff --git a/block_int.h b/block_int.h
index f4bae04..aaa46a8 100644
--- a/block_int.h
+++ b/block_int.h
@@ -331,4 +331,24 @@ void commit_start(BlockDriverState *bs, BlockDriverState *base,
BlockdevOnError on_error, BlockDriverCompletionFunc *cb,
void *opaque, Error **errp);
+/*
+ * mirror_start:
+ * @bs: Block device to operate on.
+ * @target: Block device to write to.
+ * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
+ * @mode: Whether to collapse all images in the chain to the target.
+ * @cb: Completion function for the job.
+ * @opaque: Opaque pointer value passed to @cb.
+ * @errp: Error object.
+ *
+ * Start a mirroring operation on @bs. Clusters that are allocated
+ * in @bs will be written to @bs until the job is cancelled or
+ * manually completed. At the end of a successful mirroring job,
+ * @bs will be switched to read from @target.
+ */
+void mirror_start(BlockDriverState *bs, BlockDriverState *target,
+ int64_t speed, MirrorSyncMode mode,
+ BlockDriverCompletionFunc *cb,
+ void *opaque, Error **errp);
+
#endif /* BLOCK_INT_H */
diff --git a/qapi-schema.json b/qapi-schema.json
index bc1b585..2d65073 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1152,6 +1152,23 @@
'data': ['report', 'ignore', 'enospc', 'stop'] }
##
+# @MirrorSyncMode:
+#
+# An enumeration of possible behaviors for the initial synchronization
+# phase of storage mirroring.
+#
+# @top: copies data in the topmost image to the destination
+#
+# @full: copies data from all images to the destination
+#
+# @none: only copy data written from now on
+#
+# Since: 1.3
+##
+{ 'enum': 'MirrorSyncMode',
+ 'data': ['top', 'full', 'none'] }
+
+##
# @BlockJobInfo:
#
# Information about a long-running block device operation.
diff --git a/trace-events b/trace-events
index beb1cca..aa29b42 100644
--- a/trace-events
+++ b/trace-events
@@ -77,6 +77,13 @@ stream_start(void *bs, void *base, void *s, void *co, void *opaque) "bs %p base
commit_one_iteration(void *s, int64_t sector_num, int nb_sectors, int is_allocated) "s %p sector_num %"PRId64" nb_sectors %d is_allocated %d"
commit_start(void *bs, void *base, void *top, void *s, void *co, void *opaque) "bs %p base %p top %p s %p co %p opaque %p"
+# block/mirror.c
+mirror_start(void *bs, void *s, void *co, void *opaque) "bs %p s %p co %p opaque %p"
+mirror_before_flush(void *s) "s %p"
+mirror_before_drain(void *s, int64_t cnt) "s %p dirty count %"PRId64
+mirror_before_sleep(void *s, int64_t cnt, int synced) "s %p dirty count %"PRId64" synced %d"
+mirror_one_iteration(void *s, int64_t sector_num, int nb_sectors) "s %p sector_num %"PRId64" nb_sectors %d"
+
# blockdev.c
qmp_block_job_cancel(void *job) "job %p"
qmp_block_job_pause(void *job) "job %p"
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (8 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 09/16] mirror: introduce mirror job Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 17:34 ` Eric Blake
2012-10-19 12:54 ` Kevin Wolf
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 11/16] mirror: implement completion Paolo Bonzini
` (5 subsequent siblings)
15 siblings, 2 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
This adds the monitor commands that start the mirroring job.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: bdrv_is_inserted pulled before dereference of bs->drv
blockdev.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
hmp-commands.hx | 21 ++++++++++
hmp.c | 28 +++++++++++++
hmp.h | 1 +
qapi-schema.json | 34 +++++++++++++++
qmp-commands.hx | 42 +++++++++++++++++++
6 file modificati, 250 inserzioni(+)
diff --git a/blockdev.c b/blockdev.c
index 1068960..53f5c54 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1180,6 +1180,130 @@ void qmp_block_commit(const char *device,
drive_get_ref(drive_get_by_blockdev(bs));
}
+void qmp_drive_mirror(const char *device, const char *target,
+ bool has_format, const char *format,
+ enum MirrorSyncMode sync,
+ bool has_mode, enum NewImageMode mode,
+ bool has_speed, int64_t speed, Error **errp)
+{
+ BlockDriverInfo bdi;
+ BlockDriverState *bs;
+ BlockDriverState *source, *target_bs;
+ BlockDriver *proto_drv;
+ BlockDriver *drv = NULL;
+ Error *local_err = NULL;
+ int flags;
+ uint64_t size;
+ int ret;
+
+ if (!has_speed) {
+ speed = 0;
+ }
+ if (!has_mode) {
+ mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
+ }
+
+ bs = bdrv_find(device);
+ if (!bs) {
+ error_set(errp, QERR_DEVICE_NOT_FOUND, device);
+ return;
+ }
+
+ if (!bdrv_is_inserted(bs)) {
+ error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
+ return;
+ }
+
+ if (!has_format) {
+ format = mode == NEW_IMAGE_MODE_EXISTING ? NULL : bs->drv->format_name;
+ }
+ if (format) {
+ drv = bdrv_find_format(format);
+ if (!drv) {
+ error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+ return;
+ }
+ }
+
+ if (bdrv_in_use(bs)) {
+ error_set(errp, QERR_DEVICE_IN_USE, device);
+ return;
+ }
+
+ flags = bs->open_flags | BDRV_O_RDWR;
+ source = bs->backing_hd;
+ if (!source && sync == MIRROR_SYNC_MODE_TOP) {
+ sync = MIRROR_SYNC_MODE_FULL;
+ }
+
+ proto_drv = bdrv_find_protocol(target);
+ if (!proto_drv) {
+ error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+ return;
+ }
+
+ if (sync == MIRROR_SYNC_MODE_FULL && mode != NEW_IMAGE_MODE_EXISTING) {
+ /* create new image w/o backing file */
+ assert(format && drv);
+ bdrv_get_geometry(bs, &size);
+ size *= 512;
+ ret = bdrv_img_create(target, format,
+ NULL, NULL, NULL, size, flags);
+ } else {
+ switch (mode) {
+ case NEW_IMAGE_MODE_EXISTING:
+ ret = 0;
+ break;
+ case NEW_IMAGE_MODE_ABSOLUTE_PATHS:
+ /* create new image with backing file */
+ ret = bdrv_img_create(target, format,
+ source->filename,
+ source->drv->format_name,
+ NULL, -1, flags);
+ break;
+ default:
+ abort();
+ }
+ }
+
+ if (ret) {
+ error_set(errp, QERR_OPEN_FILE_FAILED, target);
+ return;
+ }
+
+ target_bs = bdrv_new("");
+ ret = bdrv_open(target_bs, target, flags | BDRV_O_NO_BACKING, drv);
+
+ if (ret < 0) {
+ bdrv_delete(target_bs);
+ error_set(errp, QERR_OPEN_FILE_FAILED, target);
+ return;
+ }
+
+ /* We need a backing file if we will copy parts of a cluster. */
+ if (bdrv_get_info(target_bs, &bdi) >= 0 && bdi.cluster_size != 0 &&
+ bdi.cluster_size >= BDRV_SECTORS_PER_DIRTY_CHUNK * 512) {
+ ret = bdrv_open_backing_file(target_bs);
+ if (ret < 0) {
+ bdrv_delete(target_bs);
+ error_set(errp, QERR_OPEN_FILE_FAILED, target);
+ return;
+ }
+ }
+
+ mirror_start(bs, target_bs, speed, sync, block_job_cb, bs, &local_err);
+ if (local_err != NULL) {
+ bdrv_delete(target_bs);
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ /* Grab a reference so hotplug does not delete the BlockDriverState from
+ * underneath us.
+ */
+ drive_get_ref(drive_get_by_blockdev(bs));
+}
+
static BlockJob *find_block_job(const char *device)
{
BlockDriverState *bs;
diff --git a/hmp-commands.hx b/hmp-commands.hx
index fb2f237..f916385 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1004,6 +1004,27 @@ Snapshot device, using snapshot file as target if provided
ETEXI
{
+ .name = "drive_mirror",
+ .args_type = "reuse:-n,full:-f,device:B,target:s,format:s?",
+ .params = "[-n] [-f] device target [format]",
+ .help = "initiates live storage\n\t\t\t"
+ "migration for a device. The device's contents are\n\t\t\t"
+ "copied to the new image file, including data that\n\t\t\t"
+ "is written after the command is started.\n\t\t\t"
+ "The -n flag requests QEMU to reuse the image found\n\t\t\t"
+ "in new-image-file, instead of recreating it from scratch.\n\t\t\t"
+ "The -f flag requests QEMU to copy the whole disk,\n\t\t\t"
+ "so that the result does not need a backing file.\n\t\t\t",
+ .mhandler.cmd = hmp_drive_mirror,
+ },
+STEXI
+@item drive_mirror
+@findex drive_mirror
+Start mirroring a block device's writes to a new destination,
+using the specified target.
+ETEXI
+
+ {
.name = "drive_add",
.args_type = "pci_addr:s,opts:s",
.params = "[[<domain>:]<bus>:]<slot>\n"
diff --git a/hmp.c b/hmp.c
index 4cfb5fe..a1057a7 100644
--- a/hmp.c
+++ b/hmp.c
@@ -759,6 +759,34 @@ void hmp_block_resize(Monitor *mon, const QDict *qdict)
hmp_handle_error(mon, &errp);
}
+void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
+{
+ const char *device = qdict_get_str(qdict, "device");
+ const char *filename = qdict_get_str(qdict, "target");
+ const char *format = qdict_get_try_str(qdict, "format");
+ int reuse = qdict_get_try_bool(qdict, "reuse", 0);
+ int full = qdict_get_try_bool(qdict, "full", 0);
+ enum NewImageMode mode;
+ Error *errp = NULL;
+
+ if (!filename) {
+ error_set(&errp, QERR_MISSING_PARAMETER, "target");
+ hmp_handle_error(mon, &errp);
+ return;
+ }
+
+ if (reuse) {
+ mode = NEW_IMAGE_MODE_EXISTING;
+ } else {
+ mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
+ }
+
+ qmp_drive_mirror(device, filename, !!format, format,
+ full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
+ true, mode, false, 0, &errp);
+ hmp_handle_error(mon, &errp);
+}
+
void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict)
{
const char *device = qdict_get_str(qdict, "device");
diff --git a/hmp.h b/hmp.h
index 7bdd23c..34eb2b3 100644
--- a/hmp.h
+++ b/hmp.h
@@ -51,6 +51,7 @@ void hmp_block_passwd(Monitor *mon, const QDict *qdict);
void hmp_balloon(Monitor *mon, const QDict *qdict);
void hmp_block_resize(Monitor *mon, const QDict *qdict);
void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
+void hmp_drive_mirror(Monitor *mon, const QDict *qdict);
void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
diff --git a/qapi-schema.json b/qapi-schema.json
index 2d65073..052f6c9 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1592,6 +1592,40 @@
'data': { 'device': 'str', '*base': 'str', 'top': 'str',
'*speed': 'int' } }
+##
+# @drive-mirror
+#
+# Start mirroring a block device's writes to a new destination.
+#
+# @device: the name of the device whose writes should be mirrored.
+#
+# @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: #optional the format of the new destination, default is to
+# probe is @mode is 'existing', else the format of the source
+#
+# @mode: #optional whether and how QEMU should create a new image, default is
+# 'absolute-paths'.
+#
+# @speed: #optional the maximum speed, in bytes per second
+#
+# @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).
+#
+# Returns: nothing on success
+# If @device is not a valid block device, DeviceNotFound
+#
+# Since 1.3
+##
+{ 'command': 'drive-mirror',
+ 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
+ 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
+ '*speed': 'int' } }
+
+##
# @migrate_cancel
#
# Cancel the current executing migration process.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index dbc3974..bfcab50 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -936,6 +936,48 @@ Example:
EQMP
{
+ .name = "drive-mirror",
+ .args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?",
+ .mhandler.cmd_new = qmp_marshal_input_drive_mirror,
+ },
+
+SQMP
+drive-mirror
+------------
+
+Start mirroring a block device's writes to a new destination. target
+specifies the target of the new image. If the file exists, or if it is
+a device, it will be used as the new destination for writes. If does not
+exist, a new file will be created. format specifies the format of the
+mirror image, default is to probe if mode='existing', else the format
+of the source.
+
+Arguments:
+
+- "device": device name to operate on (json-string)
+- "target": name of new image file (json-string)
+- "format": format of new image (json-string, optional)
+- "mode": how an image file should be created into the target
+ file/device (NewImageMode, optional, default 'absolute-paths')
+- "speed": maximum speed of the streaming job, in bytes per second
+ (json-int)
+- "sync": what parts of the disk image should be copied to the destination;
+ 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
+ (MirrorSyncMode).
+
+
+Example:
+
+-> { "execute": "drive-mirror", "arguments": { "device": "ide-hd0",
+ "target": "/some/place/my-image",
+ "sync": "full",
+ "format": "qcow2" } }
+<- { "return": {} }
+
+EQMP
+
+ {
.name = "balloon",
.args_type = "value:M",
.mhandler.cmd_new = qmp_marshal_input_balloon,
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 11/16] mirror: implement completion
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (9 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Paolo Bonzini
` (4 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Switching to the target of the migration is done mostly asynchronously,
and reported to management via the BLOCK_JOB_COMPLETED event; the only
synchronous phase is opening the backing files. bdrv_open_backing_file
can always be done, even for migration of the full image (aka sync:
'full'). In this case, qmp_drive_mirror will create the target disk
with no backing file at all, and bdrv_open_backing_file will be a no-op.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: complete member renamed to should_complete,
added bdrv_reopen
block/mirror.c | 45 ++++++++++++++++++++++++++++++++++++++++-----
1 file modificato, 40 inserzioni(+), 5 rimozioni(-)
diff --git a/block/mirror.c b/block/mirror.c
index b353798..6320f6a 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -32,6 +32,8 @@ typedef struct MirrorBlockJob {
RateLimit limit;
BlockDriverState *target;
MirrorSyncMode mode;
+ bool synced;
+ bool should_complete;
int64_t sector_num;
uint8_t *buf;
} MirrorBlockJob;
@@ -70,7 +72,6 @@ static void coroutine_fn mirror_run(void *opaque)
int64_t sector_num, end;
int ret = 0;
int n;
- bool synced = false;
if (block_job_is_cancelled(&s->common)) {
goto immediate_exit;
@@ -136,9 +137,14 @@ static void coroutine_fn mirror_run(void *opaque)
* report completion. This way, block-job-cancel will leave
* the target in a consistent state.
*/
- synced = true;
s->common.offset = end * BDRV_SECTOR_SIZE;
- should_complete = block_job_is_cancelled(&s->common);
+ 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);
}
@@ -157,8 +163,8 @@ static void coroutine_fn mirror_run(void *opaque)
}
ret = 0;
- trace_mirror_before_sleep(s, cnt, synced);
- if (!synced) {
+ trace_mirror_before_sleep(s, cnt, s->synced);
+ if (!s->synced) {
/* Publish progress */
s->common.offset = end * BDRV_SECTOR_SIZE - cnt * BLOCK_SIZE;
@@ -191,6 +197,12 @@ static void coroutine_fn mirror_run(void *opaque)
immediate_exit:
g_free(s->buf);
bdrv_set_dirty_tracking(bs, false);
+ if (s->should_complete && ret == 0) {
+ if (bdrv_get_flags(s->target) != bdrv_get_flags(s->common.bs)) {
+ bdrv_reopen(s->target, bdrv_get_flags(s->common.bs), NULL);
+ }
+ bdrv_swap(s->target, s->common.bs);
+ }
bdrv_close(s->target);
bdrv_delete(s->target);
block_job_completed(&s->common, ret);
@@ -207,10 +219,33 @@ static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp)
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
}
+static void mirror_complete(BlockJob *job, Error **errp)
+{
+ MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+ int ret;
+
+ ret = bdrv_open_backing_file(s->target);
+ if (ret < 0) {
+ char backing_filename[PATH_MAX];
+ bdrv_get_full_backing_filename(s->target, backing_filename,
+ sizeof(backing_filename));
+ error_set(errp, QERR_OPEN_FILE_FAILED, backing_filename);
+ return;
+ }
+ if (!s->synced) {
+ error_set(errp, QERR_BLOCK_JOB_NOT_READY, job->bs->device_name);
+ return;
+ }
+
+ s->should_complete = true;
+ block_job_resume(job);
+}
+
static BlockJobType mirror_job_type = {
.instance_size = sizeof(MirrorBlockJob),
.job_type = "mirror",
.set_speed = mirror_set_speed,
+ .complete = mirror_complete,
};
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (10 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 11/16] mirror: implement completion Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-19 16:19 ` Kevin Wolf
2012-10-19 17:24 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Kevin Wolf
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 13/16] iostatus: forward block_job_iostatus_reset to block job Paolo Bonzini
` (3 subsequent siblings)
15 siblings, 2 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v2->v3: new testcases test_cancel_after_ready and
test_medium_not_found, removed obsolete workaround
for os.remove failure. Fixed copyright header.
tests/qemu-iotests/041 | 364 +++++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/041.out | 5 +
tests/qemu-iotests/group | 1 +
3 file modificati, 370 inserzioni(+)
create mode 100755 tests/qemu-iotests/041
create mode 100644 tests/qemu-iotests/041.out
diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
new file mode 100755
index 0000000..ce99b00
--- /dev/null
+++ b/tests/qemu-iotests/041
@@ -0,0 +1,364 @@
+#!/usr/bin/env python
+#
+# Tests for image mirroring.
+#
+# Copyright (C) 2012 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import time
+import os
+import iotests
+from iotests import qemu_img, qemu_io
+import struct
+
+backing_img = os.path.join(iotests.test_dir, 'backing.img')
+target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
+test_img = os.path.join(iotests.test_dir, 'test.img')
+target_img = os.path.join(iotests.test_dir, 'target.img')
+
+class ImageMirroringTestCase(iotests.QMPTestCase):
+ '''Abstract base class for image mirroring test cases'''
+
+ def assert_no_active_mirrors(self):
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return', [])
+
+ def cancel_and_wait(self, drive='drive0', wait_ready=True):
+ '''Cancel a block job and wait for it to finish'''
+ if wait_ready:
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_READY':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ ready = True
+
+ result = self.vm.qmp('block-job-cancel', device=drive,
+ force=not wait_ready)
+ self.assert_qmp(result, 'return', {})
+
+ cancelled = False
+ while not cancelled:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_COMPLETED' or \
+ event['event'] == 'BLOCK_JOB_CANCELLED':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ if wait_ready:
+ self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
+ self.assert_qmp(event, 'data/offset', self.image_len)
+ self.assert_qmp(event, 'data/len', self.image_len)
+ cancelled = True
+
+ self.assert_no_active_mirrors()
+
+ def complete_and_wait(self, drive='drive0', wait_ready=True):
+ '''Complete a block job and wait for it to finish'''
+ if wait_ready:
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_READY':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ ready = True
+
+ result = self.vm.qmp('block-job-complete', device=drive)
+ self.assert_qmp(result, 'return', {})
+
+ completed = False
+ while not completed:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_COMPLETED':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ self.assert_qmp_absent(event, 'data/error')
+ self.assert_qmp(event, 'data/offset', self.image_len)
+ self.assert_qmp(event, 'data/len', self.image_len)
+ completed = True
+
+ self.assert_no_active_mirrors()
+
+ def create_image(self, name, size):
+ file = open(name, 'w')
+ i = 0
+ while i < size:
+ sector = struct.pack('>l504xl', i / 512, i / 512)
+ file.write(sector)
+ i = i + 512
+ file.close()
+
+ def compare_images(self, img1, img2):
+ try:
+ qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw')
+ qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw')
+ file1 = open(img1 + '.raw', 'r')
+ file2 = open(img2 + '.raw', 'r')
+ return file1.read() == file2.read()
+ finally:
+ if file1 is not None:
+ file1.close()
+ if file2 is not None:
+ file2.close()
+ try:
+ os.remove(img1 + '.raw')
+ except OSError:
+ pass
+ try:
+ os.remove(img2 + '.raw')
+ except OSError:
+ pass
+
+class TestSingleDrive(ImageMirroringTestCase):
+ image_len = 1 * 1024 * 1024 # MB
+
+ def setUp(self):
+ self.create_image(backing_img, TestSingleDrive.image_len)
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ try:
+ os.remove(target_img)
+ except OSError:
+ pass
+
+ def test_complete(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', target_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_cancel(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.cancel_and_wait(wait_ready=False)
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', test_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_cancel_after_ready(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.cancel_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', test_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_pause(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('block-job-pause', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ time.sleep(1)
+ result = self.vm.qmp('query-block-jobs')
+ offset = self.dictpath(result, 'return[0]/offset')
+
+ time.sleep(1)
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/offset', offset)
+
+ result = self.vm.qmp('block-job-resume', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_large_cluster(self):
+ self.assert_no_active_mirrors()
+
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
+ % (TestSingleDrive.image_len, backing_img), target_img)
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', target_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_medium_not_found(self):
+ result = self.vm.qmp('drive-mirror', device='ide1-cd0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ def test_image_not_found(self):
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ def test_device_not_found(self):
+ result = self.vm.qmp('drive-mirror', device='nonexistent', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'error/class', 'DeviceNotFound')
+
+class TestMirrorNoBacking(ImageMirroringTestCase):
+ image_len = 2 * 1024 * 1024 # MB
+
+ def complete_and_wait(self, drive='drive0', wait_ready=True):
+ self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
+ return ImageMirroringTestCase.complete_and_wait(self, drive, wait_ready)
+
+ def compare_images(self, img1, img2):
+ self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
+ return ImageMirroringTestCase.compare_images(self, img1, img2)
+
+ def setUp(self):
+ self.create_image(backing_img, TestMirrorNoBacking.image_len)
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(target_backing_img)
+ os.remove(target_img)
+
+ def test_complete(self):
+ self.assert_no_active_mirrors()
+
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', target_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_cancel(self):
+ self.assert_no_active_mirrors()
+
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.cancel_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', test_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+class TestSetSpeed(ImageMirroringTestCase):
+ image_len = 80 * 1024 * 1024 # MB
+
+ def setUp(self):
+ qemu_img('create', backing_img, str(TestSetSpeed.image_len))
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(target_img)
+
+ def test_set_speed(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ # Default speed is 0
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/device', 'drive0')
+ self.assert_qmp(result, 'return[0]/speed', 0)
+
+ result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
+ self.assert_qmp(result, 'return', {})
+
+ # Ensure the speed we set was accepted
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/device', 'drive0')
+ self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
+
+ self.cancel_and_wait()
+
+ # Check setting speed in drive-mirror works
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, speed=4*1024*1024)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/device', 'drive0')
+ self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
+
+ self.cancel_and_wait()
+
+ def test_set_speed_invalid(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, speed=-1)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ self.cancel_and_wait()
+
+if __name__ == '__main__':
+ iotests.main(supported_fmts=['qcow2', 'qed'])
diff --git a/tests/qemu-iotests/041.out b/tests/qemu-iotests/041.out
new file mode 100644
index 0000000..36376be
--- /dev/null
+++ b/tests/qemu-iotests/041.out
@@ -0,0 +1,5 @@
+..........
+----------------------------------------------------------------------
+Ran 10 tests
+
+OK
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index d5f9ab0..4f0ea65 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -47,4 +47,5 @@
038 rw auto backing
039 rw auto
040 rw auto
+041 rw auto backing
042 rw auto quick
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 13/16] iostatus: forward block_job_iostatus_reset to block job
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (11 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 14/16] mirror: add support for on-source-error/on-target-error Paolo Bonzini
` (2 subsequent siblings)
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
block.c | 3 +++
blockjob.c | 3 +++
blockjob.h | 6 +++++-
3 file modificati, 11 inserzioni(+). 1 rimozione(-)
diff --git a/block.c b/block.c
index b167f61..3e76322 100644
--- a/block.c
+++ b/block.c
@@ -4319,6 +4319,9 @@ void bdrv_iostatus_reset(BlockDriverState *bs)
{
if (bdrv_iostatus_is_enabled(bs)) {
bs->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
+ if (bs->job) {
+ block_job_iostatus_reset(bs->job);
+ }
}
}
diff --git a/blockjob.c b/blockjob.c
index fbb7e1c..cda12c6 100644
--- a/blockjob.c
+++ b/blockjob.c
@@ -142,6 +142,9 @@ bool block_job_is_cancelled(BlockJob *job)
void block_job_iostatus_reset(BlockJob *job)
{
job->iostatus = BLOCK_DEVICE_IO_STATUS_OK;
+ if (job->job_type->iostatus_reset) {
+ job->job_type->iostatus_reset(job);
+ }
}
struct BlockCancelData {
diff --git a/blockjob.h b/blockjob.h
index fb2392e..3792b73 100644
--- a/blockjob.h
+++ b/blockjob.h
@@ -42,6 +42,9 @@ typedef struct BlockJobType {
/** Optional callback for job types that support setting a speed limit */
void (*set_speed)(BlockJob *job, int64_t speed, Error **errp);
+ /** Optional callback for job types that need to forward I/O status reset */
+ void (*iostatus_reset)(BlockJob *job);
+
/**
* Optional callback for job types whose completion must be triggered
* manually.
@@ -253,7 +256,8 @@ int block_job_cancel_sync(BlockJob *job);
* block_job_iostatus_reset:
* @job: The job whose I/O status should be reset.
*
- * Reset I/O status on @job.
+ * Reset I/O status on @job and on BlockDriverState objects it uses,
+ * other than job->bs.
*/
void block_job_iostatus_reset(BlockJob *job);
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 14/16] mirror: add support for on-source-error/on-target-error
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (12 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 13/16] iostatus: forward block_job_iostatus_reset to block job Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 15/16] qmp: add pull_event function Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error Paolo Bonzini
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
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>
---
block/mirror.c | 94 +++++++++++++++++++++++++++++++++++++++++++-------------
block_int.h | 4 +++
blockdev.c | 14 +++++++--
hmp.c | 3 +-
qapi-schema.json | 11 ++++++-
qmp-commands.hx | 8 ++++-
6 file modificati, 108 inserzioni(+), 26 rimozioni(-)
diff --git a/block/mirror.c b/block/mirror.c
index 6320f6a..d6618a4 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -32,13 +32,28 @@ typedef struct MirrorBlockJob {
RateLimit limit;
BlockDriverState *target;
MirrorSyncMode mode;
+ BlockdevOnError on_source_error, on_target_error;
bool synced;
bool should_complete;
int64_t sector_num;
uint8_t *buf;
} 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 *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);
ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov);
if (ret < 0) {
- return ret;
+ *p_action = mirror_error_action(s, true, -ret);
+ goto fail;
+ }
+ 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 bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov);
+ 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)
@@ -117,8 +144,9 @@ static void coroutine_fn mirror_run(void *opaque)
cnt = bdrv_get_dirty_count(bs);
if (cnt != 0) {
- ret = mirror_iteration(s);
- if (ret < 0) {
+ BlockErrorAction action = BDRV_ACTION_REPORT;
+ ret = mirror_iteration(s, &action);
+ if (ret < 0 && action == BDRV_ACTION_REPORT) {
goto immediate_exit;
}
cnt = bdrv_get_dirty_count(bs);
@@ -129,23 +157,25 @@ static void coroutine_fn mirror_run(void *opaque)
trace_mirror_before_flush(s);
ret = bdrv_flush(s->target);
if (ret < 0) {
- goto immediate_exit;
- }
-
- /* 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;
+ 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;
+ }
+
+ should_complete = s->should_complete ||
+ block_job_is_cancelled(&s->common);
+ cnt = bdrv_get_dirty_count(bs);
}
-
- should_complete = s->should_complete ||
- block_job_is_cancelled(&s->common);
- cnt = bdrv_get_dirty_count(bs);
}
if (cnt == 0 && should_complete) {
@@ -197,6 +227,7 @@ static void coroutine_fn mirror_run(void *opaque)
immediate_exit:
g_free(s->buf);
bdrv_set_dirty_tracking(bs, false);
+ bdrv_iostatus_disable(s->target);
if (s->should_complete && ret == 0) {
if (bdrv_get_flags(s->target) != bdrv_get_flags(s->common.bs)) {
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);
}
+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)
{
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
@@ -245,25 +283,39 @@ static BlockJobType mirror_job_type = {
.instance_size = sizeof(MirrorBlockJob),
.job_type = "mirror",
.set_speed = mirror_set_speed,
+ .iostatus_reset= mirror_iostatus_reset,
.complete = mirror_complete,
};
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
int64_t speed, MirrorSyncMode mode,
+ BlockdevOnError on_source_error,
+ BlockdevOnError on_target_error,
BlockDriverCompletionFunc *cb,
void *opaque, Error **errp)
{
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);
if (!s) {
return;
}
+ s->on_source_error = on_source_error;
+ s->on_target_error = on_target_error;
s->target = target;
s->mode = mode;
bdrv_set_dirty_tracking(bs, 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);
trace_mirror_start(bs, s, s->common.co, opaque);
qemu_coroutine_enter(s->common.co, s);
diff --git a/block_int.h b/block_int.h
index aaa46a8..00204eb 100644
--- a/block_int.h
+++ b/block_int.h
@@ -337,6 +337,8 @@ void commit_start(BlockDriverState *bs, BlockDriverState *base,
* @target: Block device to write to.
* @speed: The maximum speed, in bytes per second, or 0 for unlimited.
* @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.
* @opaque: Opaque pointer value passed to @cb.
* @errp: Error object.
@@ -348,6 +350,8 @@ void commit_start(BlockDriverState *bs, BlockDriverState *base,
*/
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
int64_t speed, MirrorSyncMode mode,
+ BlockdevOnError on_source_error,
+ BlockdevOnError on_target_error,
BlockDriverCompletionFunc *cb,
void *opaque, Error **errp);
diff --git a/blockdev.c b/blockdev.c
index 53f5c54..f37d5eb 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1184,7 +1184,10 @@ void qmp_drive_mirror(const char *device, const char *target,
bool has_format, const char *format,
enum MirrorSyncMode sync,
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;
BlockDriverState *bs;
@@ -1199,6 +1202,12 @@ void qmp_drive_mirror(const char *device, const char *target,
if (!has_speed) {
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) {
mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
}
@@ -1291,7 +1300,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) {
bdrv_delete(target_bs);
error_propagate(errp, local_err);
diff --git a/hmp.c b/hmp.c
index a1057a7..b751d08 100644
--- a/hmp.c
+++ b/hmp.c
@@ -783,7 +783,8 @@ void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
qmp_drive_mirror(device, filename, !!format, format,
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);
}
diff --git a/qapi-schema.json b/qapi-schema.json
index 052f6c9..295263b 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1615,6 +1615,14 @@
# (all the disk, only the sectors allocated in the topmost image, or
# 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
# If @device is not a valid block device, DeviceNotFound
#
@@ -1623,7 +1631,8 @@
{ 'command': 'drive-mirror',
'data': { 'device': 'str', 'target': 'str', '*format': 'str',
'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
- '*speed': 'int' } }
+ '*speed': 'int', '*on-source-error': 'BlockdevOnError',
+ '*on-target-error': 'BlockdevOnError' } }
##
# @migrate_cancel
diff --git a/qmp-commands.hx b/qmp-commands.hx
index bfcab50..ddda96c 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -937,7 +937,8 @@ EQMP
{
.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,
},
@@ -965,6 +966,11 @@ Arguments:
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
(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:
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 15/16] qmp: add pull_event function
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (13 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 14/16] mirror: add support for on-source-error/on-target-error Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error Paolo Bonzini
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
This function is unlike get_events in that it makes it easy to process
one event at a time. This is useful in the mirroring test cases, where
we want to process just one event (BLOCK_JOB_ERROR) and leave the others
to a helper function.
Acked-by: Luiz Capitulino <lcapitulino@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
QMP/qmp.py | 20 ++++++++++++++++++++
1 file modificato, 20 inserzioni(+)
diff --git a/QMP/qmp.py b/QMP/qmp.py
index 32510a1..c551df1 100644
--- a/QMP/qmp.py
+++ b/QMP/qmp.py
@@ -136,6 +136,26 @@ class QEMUMonitorProtocol:
raise Exception(ret['error']['desc'])
return ret['return']
+ def pull_event(self, wait=False):
+ """
+ Get and delete the first available QMP event.
+
+ @param wait: block until an event is available (bool)
+ """
+ self.__sock.setblocking(0)
+ try:
+ self.__json_read()
+ except socket.error, err:
+ if err[0] == errno.EAGAIN:
+ # No data available
+ pass
+ self.__sock.setblocking(1)
+ if not self.__events and wait:
+ self.__json_read(only_event=True)
+ event = self.__events[0]
+ del self.__events[0]
+ return event
+
def get_events(self, wait=False):
"""
Get a list of available QMP events.
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v3 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
` (14 preceding siblings ...)
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 15/16] qmp: add pull_event function Paolo Bonzini
@ 2012-10-18 14:49 ` Paolo Bonzini
15 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-18 14:49 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
The new options are tested with blkdebug on both the source and the
target.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
tests/qemu-iotests/041 | 253 ++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/041.out | 4 +-
tests/qemu-iotests/iotests.py | 4 +
3 file modificati, 259 inserzioni(+), 2 rimozioni(-)
diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
index ce99b00..6681705 100755
--- a/tests/qemu-iotests/041
+++ b/tests/qemu-iotests/041
@@ -294,6 +294,259 @@ class TestMirrorNoBacking(ImageMirroringTestCase):
self.assertTrue(self.compare_images(test_img, target_img),
'target image does not match source after mirroring')
+class TestReadErrors(ImageMirroringTestCase):
+ image_len = 2 * 1024 * 1024 # MB
+
+ # this should be a multiple of twice the default granularity
+ # so that we hit this offset first in state 1
+ MIRROR_GRANULARITY = 1024 * 1024
+
+ def create_blkdebug_file(self, name, event, errno):
+ file = open(name, 'w')
+ file.write('''
+[inject-error]
+state = "1"
+event = "%s"
+errno = "%d"
+immediately = "off"
+once = "on"
+sector = "%d"
+
+[set-state]
+state = "1"
+event = "%s"
+new_state = "2"
+
+[set-state]
+state = "2"
+event = "%s"
+new_state = "1"
+''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
+ file.close()
+
+ def setUp(self):
+ self.blkdebug_file = backing_img + ".blkdebug"
+ self.create_image(backing_img, TestReadErrors.image_len)
+ self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
+ qemu_img('create', '-f', iotests.imgfmt,
+ '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
+ % (self.blkdebug_file, backing_img),
+ test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(self.blkdebug_file)
+
+ def test_report_read(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ completed = False
+ error = False
+ while not completed:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'read')
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(False, 'job completed unexpectedly')
+ elif event['event'] == 'BLOCK_JOB_COMPLETED':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/error', 'Input/output error')
+ self.assert_qmp(event, 'data/len', self.image_len)
+ completed = True
+
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
+ def test_ignore_read(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, on_source_error='ignore')
+ self.assert_qmp(result, 'return', {})
+
+ event = self.vm.get_qmp_event(wait=True)
+ self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'read')
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.complete_and_wait()
+ self.vm.shutdown()
+
+ def test_stop_read(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, on_source_error='stop')
+ self.assert_qmp(result, 'return', {})
+
+ error = False
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'read')
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', True)
+ self.assert_qmp(result, 'return[0]/io-status', 'failed')
+
+ result = self.vm.qmp('block-job-resume', device='drive0')
+ self.assert_qmp(result, 'return', {})
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ ready = True
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.assert_qmp(result, 'return[0]/io-status', 'ok')
+
+ self.complete_and_wait(wait_ready=False)
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
+class TestWriteErrors(ImageMirroringTestCase):
+ image_len = 2 * 1024 * 1024 # MB
+
+ # this should be a multiple of twice the default granularity
+ # so that we hit this offset first in state 1
+ MIRROR_GRANULARITY = 1024 * 1024
+
+ def create_blkdebug_file(self, name, event, errno):
+ file = open(name, 'w')
+ file.write('''
+[inject-error]
+state = "1"
+event = "%s"
+errno = "%d"
+immediately = "off"
+once = "on"
+sector = "%d"
+
+[set-state]
+state = "1"
+event = "%s"
+new_state = "2"
+
+[set-state]
+state = "2"
+event = "%s"
+new_state = "1"
+''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
+ file.close()
+
+ def setUp(self):
+ self.blkdebug_file = target_img + ".blkdebug"
+ self.create_image(backing_img, TestWriteErrors.image_len)
+ self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
+ qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
+ qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(self.blkdebug_file)
+
+ def test_report_write(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=self.target_img)
+ self.assert_qmp(result, 'return', {})
+
+ completed = False
+ error = False
+ while not completed:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'write')
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(False, 'job completed unexpectedly')
+ elif event['event'] == 'BLOCK_JOB_COMPLETED':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/error', 'Input/output error')
+ self.assert_qmp(event, 'data/len', self.image_len)
+ completed = True
+
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
+ def test_ignore_write(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=self.target_img,
+ on_target_error='ignore')
+ self.assert_qmp(result, 'return', {})
+
+ event = self.vm.get_qmp_event(wait=True)
+ self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'write')
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.complete_and_wait()
+ self.vm.shutdown()
+
+ def test_stop_write(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=self.target_img,
+ on_target_error='stop')
+ self.assert_qmp(result, 'return', {})
+
+ error = False
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'write')
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', True)
+ self.assert_qmp(result, 'return[0]/io-status', 'failed')
+
+ result = self.vm.qmp('block-job-resume', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.assert_qmp(result, 'return[0]/io-status', 'ok')
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ ready = True
+
+ self.complete_and_wait(wait_ready=False)
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
class TestSetSpeed(ImageMirroringTestCase):
image_len = 80 * 1024 * 1024 # MB
diff --git a/tests/qemu-iotests/041.out b/tests/qemu-iotests/041.out
index 36376be..b6f2576 100644
--- a/tests/qemu-iotests/041.out
+++ b/tests/qemu-iotests/041.out
@@ -1,5 +1,5 @@
-..........
+................
----------------------------------------------------------------------
-Ran 10 tests
+Ran 16 tests
OK
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 3c60b2d..735c674 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -106,6 +106,10 @@ class VM(object):
return self._qmp.cmd(cmd, args=qmp_args)
+ def get_qmp_event(self, wait=False):
+ '''Poll for one queued QMP events and return it'''
+ return self._qmp.pull_event(wait=wait)
+
def get_qmp_events(self, wait=False):
'''Poll for queued QMP events and return a list of dicts'''
events = self._qmp.get_events(wait=wait)
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 05/16] block: export dirty bitmap information in query-block
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 05/16] block: export dirty bitmap information in query-block Paolo Bonzini
@ 2012-10-18 16:51 ` Eric Blake
0 siblings, 0 replies; 29+ messages in thread
From: Eric Blake @ 2012-10-18 16:51 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kwolf, qemu-devel
[-- Attachment #1: Type: text/plain, Size: 1372 bytes --]
On 10/18/2012 08:49 AM, Paolo Bonzini wrote:
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> v2->v3: unit of measure is now bytes
>
> block.c | 7 +++++++
> qapi-schema.json | 20 ++++++++++++++++++--
> 2 file modificati, 25 inserzioni(+), 2 rimozioni(-)
>
> +++ b/qapi-schema.json
> @@ -629,7 +629,7 @@
> '*backing_file': 'str', 'backing_file_depth': 'int',
> 'encrypted': 'bool', 'encryption_key_missing': 'bool',
> 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int',
> - 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int'} }
> + 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int' } }
>
This whitespace change hunk looks out of place...
> ##
> # @BlockDeviceIoStatus:
> @@ -647,6 +647,18 @@
> { 'enum': 'BlockDeviceIoStatus', 'data': [ 'ok', 'failed', 'nospace' ] }
>
> ##
> +# @BlockDirtyInfo:
> +#
> +# Block dirty bitmap information.
> +#
> +# @count: number of dirty bytes according to the dirty bitmap
> +#
> +# Since: 1.3
> +##
> +{ 'type': 'BlockDirtyInfo',
> + 'data': {'count': 'int'} }
...especially since you aren't consistent on using space before }
However, I can live with it, rather than needing a v4.
--
Eric Blake eblake@redhat.com +1-919-301-3266
Libvirt virtualization library http://libvirt.org
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 617 bytes --]
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 08/16] block: introduce BLOCK_JOB_READY event
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 08/16] block: introduce BLOCK_JOB_READY event Paolo Bonzini
@ 2012-10-18 16:58 ` Eric Blake
0 siblings, 0 replies; 29+ messages in thread
From: Eric Blake @ 2012-10-18 16:58 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kwolf, qemu-devel
[-- Attachment #1: Type: text/plain, Size: 1386 bytes --]
On 10/18/2012 08:49 AM, Paolo Bonzini wrote:
> Even for jobs that need to be manually completed, management may want
> to take care itself of the completion, not requiring the user to issue
> a command to terminate the job. In this case we want to avoid that
> they poll us continuously, waiting for completion to become available.
> Thus, add a new event that signals the phase switch and the availability
> of the block-job-complete command.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> +BLOCK_JOB_READY
> +---------------
> +
> +Emitted when a block job is ready to complete.
> +
> +Data:
> +
> +- "device": device name (json-string)
> +
> +Example:
> +
> +{ "event": "BLOCK_JOB_READY",
> + "data": { "device": "ide0-hd1",
> + "operation": "write",
> + "action": "stop" },
This example does not match the Data above ('operation' and 'action'
were not documented.
> + "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
Just a reminder that libvirt would still like a followup patch later in
your series but prior to qemu 1.3 that adds a 'type':'str' (or even an
enum, with 'stream', 'commit', and 'mirror'). But we already agreed it
need not hold up this particular patch.
--
Eric Blake eblake@redhat.com +1-919-301-3266
Libvirt virtualization library http://libvirt.org
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 617 bytes --]
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command Paolo Bonzini
@ 2012-10-18 17:34 ` Eric Blake
2012-10-19 12:54 ` Kevin Wolf
1 sibling, 0 replies; 29+ messages in thread
From: Eric Blake @ 2012-10-18 17:34 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kwolf, qemu-devel
[-- Attachment #1: Type: text/plain, Size: 807 bytes --]
On 10/18/2012 08:49 AM, Paolo Bonzini wrote:
> This adds the monitor commands that start the mirroring job.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> +++ b/qapi-schema.json
> +#
> +# @format: #optional the format of the new destination, default is to
> +# probe is @mode is 'existing', else the format of the source
s/probe is/probe if/
> +#
>> +
> +SQMP
> +drive-mirror
> +------------
> +
> +Start mirroring a block device's writes to a new destination. target
> +specifies the target of the new image. If the file exists, or if it is
> +a device, it will be used as the new destination for writes. If does not
s/If does/If it does/
--
Eric Blake eblake@redhat.com +1-919-301-3266
Libvirt virtualization library http://libvirt.org
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 617 bytes --]
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command Paolo Bonzini
2012-10-18 17:34 ` Eric Blake
@ 2012-10-19 12:54 ` Kevin Wolf
2012-10-19 13:13 ` Paolo Bonzini
1 sibling, 1 reply; 29+ messages in thread
From: Kevin Wolf @ 2012-10-19 12:54 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel
Am 18.10.2012 16:49, schrieb Paolo Bonzini:
> This adds the monitor commands that start the mirroring job.
>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> v2->v3: bdrv_is_inserted pulled before dereference of bs->drv
>
> blockdev.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> hmp-commands.hx | 21 ++++++++++
> hmp.c | 28 +++++++++++++
> hmp.h | 1 +
> qapi-schema.json | 34 +++++++++++++++
> qmp-commands.hx | 42 +++++++++++++++++++
> 6 file modificati, 250 inserzioni(+)
>
> diff --git a/blockdev.c b/blockdev.c
> index 1068960..53f5c54 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1180,6 +1180,130 @@ void qmp_block_commit(const char *device,
> drive_get_ref(drive_get_by_blockdev(bs));
> }
>
> +void qmp_drive_mirror(const char *device, const char *target,
> + bool has_format, const char *format,
> + enum MirrorSyncMode sync,
> + bool has_mode, enum NewImageMode mode,
> + bool has_speed, int64_t speed, Error **errp)
> +{
> + BlockDriverInfo bdi;
> + BlockDriverState *bs;
> + BlockDriverState *source, *target_bs;
> + BlockDriver *proto_drv;
> + BlockDriver *drv = NULL;
> + Error *local_err = NULL;
> + int flags;
> + uint64_t size;
> + int ret;
> +
> + if (!has_speed) {
> + speed = 0;
> + }
> + if (!has_mode) {
> + mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
> + }
> +
> + bs = bdrv_find(device);
> + if (!bs) {
> + error_set(errp, QERR_DEVICE_NOT_FOUND, device);
> + return;
> + }
> +
> + if (!bdrv_is_inserted(bs)) {
> + error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
> + return;
> + }
> +
> + if (!has_format) {
> + format = mode == NEW_IMAGE_MODE_EXISTING ? NULL : bs->drv->format_name;
> + }
> + if (format) {
> + drv = bdrv_find_format(format);
> + if (!drv) {
> + error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
> + return;
> + }
> + }
> +
> + if (bdrv_in_use(bs)) {
> + error_set(errp, QERR_DEVICE_IN_USE, device);
> + return;
> + }
> +
> + flags = bs->open_flags | BDRV_O_RDWR;
> + source = bs->backing_hd;
> + if (!source && sync == MIRROR_SYNC_MODE_TOP) {
> + sync = MIRROR_SYNC_MODE_FULL;
> + }
> +
> + proto_drv = bdrv_find_protocol(target);
> + if (!proto_drv) {
> + error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
This error message is still not fixed, and totally confusing, pointing
at the wrong cause. No matter what changes to the error reporting we do
later, we should add a better error string right now.
Kevin
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command
2012-10-19 12:54 ` Kevin Wolf
@ 2012-10-19 13:13 ` Paolo Bonzini
0 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-19 13:13 UTC (permalink / raw)
To: Kevin Wolf; +Cc: qemu-devel
Il 19/10/2012 14:54, Kevin Wolf ha scritto:
>> > + proto_drv = bdrv_find_protocol(target);
>> > + if (!proto_drv) {
>> > + error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
> This error message is still not fixed, and totally confusing, pointing
> at the wrong cause. No matter what changes to the error reporting we do
> later, we should add a better error string right now.
I think there's time for this before 1.3. Either we fix everything in
this series, or since this is cut-and-paste we should not fix the pasted
occurrence. Otherwise the fix will be harder to review and it's easier
to miss something.
Paolo
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Paolo Bonzini
@ 2012-10-19 16:19 ` Kevin Wolf
2012-10-20 13:47 ` Paolo Bonzini
2012-10-19 17:24 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Kevin Wolf
1 sibling, 1 reply; 29+ messages in thread
From: Kevin Wolf @ 2012-10-19 16:19 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel
Am 18.10.2012 16:49, schrieb Paolo Bonzini:
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> v2->v3: new testcases test_cancel_after_ready and
> test_medium_not_found, removed obsolete workaround
> for os.remove failure. Fixed copyright header.
>
> tests/qemu-iotests/041 | 364 +++++++++++++++++++++++++++++++++++++++++++++
> tests/qemu-iotests/041.out | 5 +
> tests/qemu-iotests/group | 1 +
> 3 file modificati, 370 inserzioni(+)
> create mode 100755 tests/qemu-iotests/041
> create mode 100644 tests/qemu-iotests/041.out
>
> diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
> new file mode 100755
> index 0000000..ce99b00
> --- /dev/null
> +++ b/tests/qemu-iotests/041
> @@ -0,0 +1,364 @@
> +#!/usr/bin/env python
> +#
> +# Tests for image mirroring.
> +#
> +# Copyright (C) 2012 Red Hat, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 2 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program. If not, see <http://www.gnu.org/licenses/>.
> +#
> +
> +import time
> +import os
> +import iotests
> +from iotests import qemu_img, qemu_io
> +import struct
> +
> +backing_img = os.path.join(iotests.test_dir, 'backing.img')
> +target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
> +test_img = os.path.join(iotests.test_dir, 'test.img')
> +target_img = os.path.join(iotests.test_dir, 'target.img')
> +
> +class ImageMirroringTestCase(iotests.QMPTestCase):
> + '''Abstract base class for image mirroring test cases'''
> +
> + def assert_no_active_mirrors(self):
> + result = self.vm.qmp('query-block-jobs')
> + self.assert_qmp(result, 'return', [])
> +
> + def cancel_and_wait(self, drive='drive0', wait_ready=True):
> + '''Cancel a block job and wait for it to finish'''
> + if wait_ready:
> + ready = False
> + while not ready:
> + for event in self.vm.get_qmp_events(wait=True):
> + if event['event'] == 'BLOCK_JOB_READY':
> + self.assert_qmp(event, 'data/type', 'mirror')
> + self.assert_qmp(event, 'data/device', drive)
> + ready = True
> +
> + result = self.vm.qmp('block-job-cancel', device=drive,
> + force=not wait_ready)
> + self.assert_qmp(result, 'return', {})
> +
> + cancelled = False
> + while not cancelled:
> + for event in self.vm.get_qmp_events(wait=True):
> + if event['event'] == 'BLOCK_JOB_COMPLETED' or \
> + event['event'] == 'BLOCK_JOB_CANCELLED':
> + self.assert_qmp(event, 'data/type', 'mirror')
> + self.assert_qmp(event, 'data/device', drive)
> + if wait_ready:
> + self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
> + self.assert_qmp(event, 'data/offset', self.image_len)
> + self.assert_qmp(event, 'data/len', self.image_len)
> + cancelled = True
> +
> + self.assert_no_active_mirrors()
> +
> + def complete_and_wait(self, drive='drive0', wait_ready=True):
> + '''Complete a block job and wait for it to finish'''
> + if wait_ready:
> + ready = False
> + while not ready:
> + for event in self.vm.get_qmp_events(wait=True):
> + if event['event'] == 'BLOCK_JOB_READY':
> + self.assert_qmp(event, 'data/type', 'mirror')
> + self.assert_qmp(event, 'data/device', drive)
> + ready = True
> +
> + result = self.vm.qmp('block-job-complete', device=drive)
> + self.assert_qmp(result, 'return', {})
> +
> + completed = False
> + while not completed:
> + for event in self.vm.get_qmp_events(wait=True):
> + if event['event'] == 'BLOCK_JOB_COMPLETED':
> + self.assert_qmp(event, 'data/type', 'mirror')
> + self.assert_qmp(event, 'data/device', drive)
> + self.assert_qmp_absent(event, 'data/error')
> + self.assert_qmp(event, 'data/offset', self.image_len)
> + self.assert_qmp(event, 'data/len', self.image_len)
> + completed = True
> +
> + self.assert_no_active_mirrors()
> +
> + def create_image(self, name, size):
> + file = open(name, 'w')
> + i = 0
> + while i < size:
> + sector = struct.pack('>l504xl', i / 512, i / 512)
> + file.write(sector)
> + i = i + 512
> + file.close()
> +
> + def compare_images(self, img1, img2):
> + try:
> + qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw')
> + qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw')
> + file1 = open(img1 + '.raw', 'r')
> + file2 = open(img2 + '.raw', 'r')
> + return file1.read() == file2.read()
> + finally:
> + if file1 is not None:
> + file1.close()
> + if file2 is not None:
> + file2.close()
> + try:
> + os.remove(img1 + '.raw')
> + except OSError:
> + pass
> + try:
> + os.remove(img2 + '.raw')
> + except OSError:
> + pass
> +
> +class TestSingleDrive(ImageMirroringTestCase):
> + image_len = 1 * 1024 * 1024 # MB
> +
> + def setUp(self):
> + self.create_image(backing_img, TestSingleDrive.image_len)
> + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
> + self.vm = iotests.VM().add_drive(test_img)
> + self.vm.launch()
> +
> + def tearDown(self):
> + self.vm.shutdown()
> + os.remove(test_img)
> + os.remove(backing_img)
> + try:
> + os.remove(target_img)
> + except OSError:
> + pass
> +
> + def test_complete(self):
> + self.assert_no_active_mirrors()
> +
> + result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
> + target=target_img)
> + self.assert_qmp(result, 'return', {})
> +
> + self.complete_and_wait()
> + result = self.vm.qmp('query-block')
> + self.assert_qmp(result, 'return[0]/inserted/file', target_img)
> + self.vm.shutdown()
> + self.assertTrue(self.compare_images(test_img, target_img),
> + 'target image does not match source after mirroring')
> +
> + def test_cancel(self):
> + self.assert_no_active_mirrors()
> +
> + result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
> + target=target_img)
> + self.assert_qmp(result, 'return', {})
> +
> + self.cancel_and_wait(wait_ready=False)
> + result = self.vm.qmp('query-block')
> + self.assert_qmp(result, 'return[0]/inserted/file', test_img)
> + self.vm.shutdown()
> + self.assertTrue(self.compare_images(test_img, target_img),
> + 'target image does not match source after mirroring')
Hm, shouldn't the image comparison actually fail when cancelling before
the job was ready?
Kevin
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Paolo Bonzini
2012-10-19 16:19 ` Kevin Wolf
@ 2012-10-19 17:24 ` Kevin Wolf
1 sibling, 0 replies; 29+ messages in thread
From: Kevin Wolf @ 2012-10-19 17:24 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel
Am 18.10.2012 16:49, schrieb Paolo Bonzini:
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> v2->v3: new testcases test_cancel_after_ready and
> test_medium_not_found, removed obsolete workaround
> for os.remove failure. Fixed copyright header.
>
> tests/qemu-iotests/041 | 364 +++++++++++++++++++++++++++++++++++++++++++++
> tests/qemu-iotests/041.out | 5 +
> tests/qemu-iotests/group | 1 +
> 3 file modificati, 370 inserzioni(+)
> create mode 100755 tests/qemu-iotests/041
> create mode 100644 tests/qemu-iotests/041.out
You added two test cases but forgot to update the reference output:
--- 041.out 2012-10-19 10:01:15.244229847 -0400
+++ 041.out.bad 2012-10-19 10:01:24.841170380 -0400
@@ -1,5 +1,5 @@
-..........
+............
----------------------------------------------------------------------
-Ran 10 tests
+Ran 12 tests
OK
Failures: 041
Failed 1 of 1 tests
Kevin
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case
2012-10-19 16:19 ` Kevin Wolf
@ 2012-10-20 13:47 ` Paolo Bonzini
2012-10-22 8:44 ` Kevin Wolf
0 siblings, 1 reply; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-20 13:47 UTC (permalink / raw)
To: Kevin Wolf; +Cc: qemu-devel
Il 19/10/2012 18:19, Kevin Wolf ha scritto:
> Am 18.10.2012 16:49, schrieb Paolo Bonzini:
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> ---
>> v2->v3: new testcases test_cancel_after_ready and
>> test_medium_not_found, removed obsolete workaround
>> for os.remove failure. Fixed copyright header.
>>
>> tests/qemu-iotests/041 | 364 +++++++++++++++++++++++++++++++++++++++++++++
>> tests/qemu-iotests/041.out | 5 +
>> tests/qemu-iotests/group | 1 +
>> 3 file modificati, 370 inserzioni(+)
>> create mode 100755 tests/qemu-iotests/041
>> create mode 100644 tests/qemu-iotests/041.out
>>
>> diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
>> new file mode 100755
>> index 0000000..ce99b00
>> --- /dev/null
>> +++ b/tests/qemu-iotests/041
>> @@ -0,0 +1,364 @@
>> +#!/usr/bin/env python
>> +#
>> +# Tests for image mirroring.
>> +#
>> +# Copyright (C) 2012 Red Hat, Inc.
>> +#
>> +# This program is free software; you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation; either version 2 of the License, or
>> +# (at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License
>> +# along with this program. If not, see <http://www.gnu.org/licenses/>.
>> +#
>> +
>> +import time
>> +import os
>> +import iotests
>> +from iotests import qemu_img, qemu_io
>> +import struct
>> +
>> +backing_img = os.path.join(iotests.test_dir, 'backing.img')
>> +target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
>> +test_img = os.path.join(iotests.test_dir, 'test.img')
>> +target_img = os.path.join(iotests.test_dir, 'target.img')
>> +
>> +class ImageMirroringTestCase(iotests.QMPTestCase):
>> + '''Abstract base class for image mirroring test cases'''
>> +
>> + def assert_no_active_mirrors(self):
>> + result = self.vm.qmp('query-block-jobs')
>> + self.assert_qmp(result, 'return', [])
>> +
>> + def cancel_and_wait(self, drive='drive0', wait_ready=True):
>> + '''Cancel a block job and wait for it to finish'''
>> + if wait_ready:
>> + ready = False
>> + while not ready:
>> + for event in self.vm.get_qmp_events(wait=True):
>> + if event['event'] == 'BLOCK_JOB_READY':
>> + self.assert_qmp(event, 'data/type', 'mirror')
>> + self.assert_qmp(event, 'data/device', drive)
>> + ready = True
>> +
>> + result = self.vm.qmp('block-job-cancel', device=drive,
>> + force=not wait_ready)
>> + self.assert_qmp(result, 'return', {})
>> +
>> + cancelled = False
>> + while not cancelled:
>> + for event in self.vm.get_qmp_events(wait=True):
>> + if event['event'] == 'BLOCK_JOB_COMPLETED' or \
>> + event['event'] == 'BLOCK_JOB_CANCELLED':
>> + self.assert_qmp(event, 'data/type', 'mirror')
>> + self.assert_qmp(event, 'data/device', drive)
>> + if wait_ready:
>> + self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
>> + self.assert_qmp(event, 'data/offset', self.image_len)
>> + self.assert_qmp(event, 'data/len', self.image_len)
>> + cancelled = True
>> +
>> + self.assert_no_active_mirrors()
>> +
>> + def complete_and_wait(self, drive='drive0', wait_ready=True):
>> + '''Complete a block job and wait for it to finish'''
>> + if wait_ready:
>> + ready = False
>> + while not ready:
>> + for event in self.vm.get_qmp_events(wait=True):
>> + if event['event'] == 'BLOCK_JOB_READY':
>> + self.assert_qmp(event, 'data/type', 'mirror')
>> + self.assert_qmp(event, 'data/device', drive)
>> + ready = True
>> +
>> + result = self.vm.qmp('block-job-complete', device=drive)
>> + self.assert_qmp(result, 'return', {})
>> +
>> + completed = False
>> + while not completed:
>> + for event in self.vm.get_qmp_events(wait=True):
>> + if event['event'] == 'BLOCK_JOB_COMPLETED':
>> + self.assert_qmp(event, 'data/type', 'mirror')
>> + self.assert_qmp(event, 'data/device', drive)
>> + self.assert_qmp_absent(event, 'data/error')
>> + self.assert_qmp(event, 'data/offset', self.image_len)
>> + self.assert_qmp(event, 'data/len', self.image_len)
>> + completed = True
>> +
>> + self.assert_no_active_mirrors()
>> +
>> + def create_image(self, name, size):
>> + file = open(name, 'w')
>> + i = 0
>> + while i < size:
>> + sector = struct.pack('>l504xl', i / 512, i / 512)
>> + file.write(sector)
>> + i = i + 512
>> + file.close()
>> +
>> + def compare_images(self, img1, img2):
>> + try:
>> + qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw')
>> + qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw')
>> + file1 = open(img1 + '.raw', 'r')
>> + file2 = open(img2 + '.raw', 'r')
>> + return file1.read() == file2.read()
>> + finally:
>> + if file1 is not None:
>> + file1.close()
>> + if file2 is not None:
>> + file2.close()
>> + try:
>> + os.remove(img1 + '.raw')
>> + except OSError:
>> + pass
>> + try:
>> + os.remove(img2 + '.raw')
>> + except OSError:
>> + pass
>> +
>> +class TestSingleDrive(ImageMirroringTestCase):
>> + image_len = 1 * 1024 * 1024 # MB
>> +
>> + def setUp(self):
>> + self.create_image(backing_img, TestSingleDrive.image_len)
>> + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
>> + self.vm = iotests.VM().add_drive(test_img)
>> + self.vm.launch()
>> +
>> + def tearDown(self):
>> + self.vm.shutdown()
>> + os.remove(test_img)
>> + os.remove(backing_img)
>> + try:
>> + os.remove(target_img)
>> + except OSError:
>> + pass
>> +
>> + def test_complete(self):
>> + self.assert_no_active_mirrors()
>> +
>> + result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
>> + target=target_img)
>> + self.assert_qmp(result, 'return', {})
>> +
>> + self.complete_and_wait()
>> + result = self.vm.qmp('query-block')
>> + self.assert_qmp(result, 'return[0]/inserted/file', target_img)
>> + self.vm.shutdown()
>> + self.assertTrue(self.compare_images(test_img, target_img),
>> + 'target image does not match source after mirroring')
>> +
>> + def test_cancel(self):
>> + self.assert_no_active_mirrors()
>> +
>> + result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
>> + target=target_img)
>> + self.assert_qmp(result, 'return', {})
>> +
>> + self.cancel_and_wait(wait_ready=False)
>> + result = self.vm.qmp('query-block')
>> + self.assert_qmp(result, 'return[0]/inserted/file', test_img)
>> + self.vm.shutdown()
>> + self.assertTrue(self.compare_images(test_img, target_img),
>> + 'target image does not match source after mirroring')
>
> Hm, shouldn't the image comparison actually fail when cancelling before
> the job was ready?
It's racy, it is probably completing anyway. Should I send a v4 with a
regenerated .out and the assertion removed?
Paolo
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case
2012-10-20 13:47 ` Paolo Bonzini
@ 2012-10-22 8:44 ` Kevin Wolf
2012-10-22 15:38 ` Paolo Bonzini
` (2 more replies)
0 siblings, 3 replies; 29+ messages in thread
From: Kevin Wolf @ 2012-10-22 8:44 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel
Am 20.10.2012 15:47, schrieb Paolo Bonzini:
> Il 19/10/2012 18:19, Kevin Wolf ha scritto:
>> Am 18.10.2012 16:49, schrieb Paolo Bonzini:
>>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>>> ---
>>> v2->v3: new testcases test_cancel_after_ready and
>>> test_medium_not_found, removed obsolete workaround
>>> for os.remove failure. Fixed copyright header.
>>>
>>> tests/qemu-iotests/041 | 364 +++++++++++++++++++++++++++++++++++++++++++++
>>> tests/qemu-iotests/041.out | 5 +
>>> tests/qemu-iotests/group | 1 +
>>> 3 file modificati, 370 inserzioni(+)
>>> create mode 100755 tests/qemu-iotests/041
>>> create mode 100644 tests/qemu-iotests/041.out
>>>
>>> diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
>>> new file mode 100755
>>> index 0000000..ce99b00
>>> --- /dev/null
>>> +++ b/tests/qemu-iotests/041
>>> @@ -0,0 +1,364 @@
>>> +#!/usr/bin/env python
>>> +#
>>> +# Tests for image mirroring.
>>> +#
>>> +# Copyright (C) 2012 Red Hat, Inc.
>>> +#
>>> +# This program is free software; you can redistribute it and/or modify
>>> +# it under the terms of the GNU General Public License as published by
>>> +# the Free Software Foundation; either version 2 of the License, or
>>> +# (at your option) any later version.
>>> +#
>>> +# This program is distributed in the hope that it will be useful,
>>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>>> +# GNU General Public License for more details.
>>> +#
>>> +# You should have received a copy of the GNU General Public License
>>> +# along with this program. If not, see <http://www.gnu.org/licenses/>.
>>> +#
>>> +
>>> +import time
>>> +import os
>>> +import iotests
>>> +from iotests import qemu_img, qemu_io
>>> +import struct
>>> +
>>> +backing_img = os.path.join(iotests.test_dir, 'backing.img')
>>> +target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
>>> +test_img = os.path.join(iotests.test_dir, 'test.img')
>>> +target_img = os.path.join(iotests.test_dir, 'target.img')
>>> +
>>> +class ImageMirroringTestCase(iotests.QMPTestCase):
>>> + '''Abstract base class for image mirroring test cases'''
>>> +
>>> + def assert_no_active_mirrors(self):
>>> + result = self.vm.qmp('query-block-jobs')
>>> + self.assert_qmp(result, 'return', [])
>>> +
>>> + def cancel_and_wait(self, drive='drive0', wait_ready=True):
>>> + '''Cancel a block job and wait for it to finish'''
>>> + if wait_ready:
>>> + ready = False
>>> + while not ready:
>>> + for event in self.vm.get_qmp_events(wait=True):
>>> + if event['event'] == 'BLOCK_JOB_READY':
>>> + self.assert_qmp(event, 'data/type', 'mirror')
>>> + self.assert_qmp(event, 'data/device', drive)
>>> + ready = True
>>> +
>>> + result = self.vm.qmp('block-job-cancel', device=drive,
>>> + force=not wait_ready)
>>> + self.assert_qmp(result, 'return', {})
>>> +
>>> + cancelled = False
>>> + while not cancelled:
>>> + for event in self.vm.get_qmp_events(wait=True):
>>> + if event['event'] == 'BLOCK_JOB_COMPLETED' or \
>>> + event['event'] == 'BLOCK_JOB_CANCELLED':
>>> + self.assert_qmp(event, 'data/type', 'mirror')
>>> + self.assert_qmp(event, 'data/device', drive)
>>> + if wait_ready:
>>> + self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
>>> + self.assert_qmp(event, 'data/offset', self.image_len)
>>> + self.assert_qmp(event, 'data/len', self.image_len)
>>> + cancelled = True
>>> +
>>> + self.assert_no_active_mirrors()
>>> +
>>> + def complete_and_wait(self, drive='drive0', wait_ready=True):
>>> + '''Complete a block job and wait for it to finish'''
>>> + if wait_ready:
>>> + ready = False
>>> + while not ready:
>>> + for event in self.vm.get_qmp_events(wait=True):
>>> + if event['event'] == 'BLOCK_JOB_READY':
>>> + self.assert_qmp(event, 'data/type', 'mirror')
>>> + self.assert_qmp(event, 'data/device', drive)
>>> + ready = True
>>> +
>>> + result = self.vm.qmp('block-job-complete', device=drive)
>>> + self.assert_qmp(result, 'return', {})
>>> +
>>> + completed = False
>>> + while not completed:
>>> + for event in self.vm.get_qmp_events(wait=True):
>>> + if event['event'] == 'BLOCK_JOB_COMPLETED':
>>> + self.assert_qmp(event, 'data/type', 'mirror')
>>> + self.assert_qmp(event, 'data/device', drive)
>>> + self.assert_qmp_absent(event, 'data/error')
>>> + self.assert_qmp(event, 'data/offset', self.image_len)
>>> + self.assert_qmp(event, 'data/len', self.image_len)
>>> + completed = True
>>> +
>>> + self.assert_no_active_mirrors()
>>> +
>>> + def create_image(self, name, size):
>>> + file = open(name, 'w')
>>> + i = 0
>>> + while i < size:
>>> + sector = struct.pack('>l504xl', i / 512, i / 512)
>>> + file.write(sector)
>>> + i = i + 512
>>> + file.close()
>>> +
>>> + def compare_images(self, img1, img2):
>>> + try:
>>> + qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw')
>>> + qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw')
>>> + file1 = open(img1 + '.raw', 'r')
>>> + file2 = open(img2 + '.raw', 'r')
>>> + return file1.read() == file2.read()
>>> + finally:
>>> + if file1 is not None:
>>> + file1.close()
>>> + if file2 is not None:
>>> + file2.close()
>>> + try:
>>> + os.remove(img1 + '.raw')
>>> + except OSError:
>>> + pass
>>> + try:
>>> + os.remove(img2 + '.raw')
>>> + except OSError:
>>> + pass
>>> +
>>> +class TestSingleDrive(ImageMirroringTestCase):
>>> + image_len = 1 * 1024 * 1024 # MB
>>> +
>>> + def setUp(self):
>>> + self.create_image(backing_img, TestSingleDrive.image_len)
>>> + qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
>>> + self.vm = iotests.VM().add_drive(test_img)
>>> + self.vm.launch()
>>> +
>>> + def tearDown(self):
>>> + self.vm.shutdown()
>>> + os.remove(test_img)
>>> + os.remove(backing_img)
>>> + try:
>>> + os.remove(target_img)
>>> + except OSError:
>>> + pass
>>> +
>>> + def test_complete(self):
>>> + self.assert_no_active_mirrors()
>>> +
>>> + result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
>>> + target=target_img)
>>> + self.assert_qmp(result, 'return', {})
>>> +
>>> + self.complete_and_wait()
>>> + result = self.vm.qmp('query-block')
>>> + self.assert_qmp(result, 'return[0]/inserted/file', target_img)
>>> + self.vm.shutdown()
>>> + self.assertTrue(self.compare_images(test_img, target_img),
>>> + 'target image does not match source after mirroring')
>>> +
>>> + def test_cancel(self):
>>> + self.assert_no_active_mirrors()
>>> +
>>> + result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
>>> + target=target_img)
>>> + self.assert_qmp(result, 'return', {})
>>> +
>>> + self.cancel_and_wait(wait_ready=False)
>>> + result = self.vm.qmp('query-block')
>>> + self.assert_qmp(result, 'return[0]/inserted/file', test_img)
>>> + self.vm.shutdown()
>>> + self.assertTrue(self.compare_images(test_img, target_img),
>>> + 'target image does not match source after mirroring')
>>
>> Hm, shouldn't the image comparison actually fail when cancelling before
>> the job was ready?
>
> It's racy, it is probably completing anyway. Should I send a v4 with a
> regenerated .out and the assertion removed?
Yes, please.
Can we additionaly increase our chances to win the race, for example by
specifying a very low speed?
Kevin
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case
2012-10-22 8:44 ` Kevin Wolf
@ 2012-10-22 15:38 ` Paolo Bonzini
2012-10-23 14:39 ` [Qemu-devel] [PATCH v4 " Paolo Bonzini
2012-10-23 14:39 ` [Qemu-devel] [PATCH v4 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error Paolo Bonzini
2 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-22 15:38 UTC (permalink / raw)
To: Kevin Wolf; +Cc: qemu-devel
> Yes, please.
Ok, will send tomorrow.
> Can we additionaly increase our chances to win the race, for example
> by specifying a very low speed?
Added to my todo list.
Paolo
^ permalink raw reply [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v4 12/16] qemu-iotests: add mirroring test case
2012-10-22 8:44 ` Kevin Wolf
2012-10-22 15:38 ` Paolo Bonzini
@ 2012-10-23 14:39 ` Paolo Bonzini
2012-10-23 14:39 ` [Qemu-devel] [PATCH v4 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error Paolo Bonzini
2 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-23 14:39 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
tests/qemu-iotests/041 | 362 +++++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/041.out | 5 +
tests/qemu-iotests/group | 1 +
3 file modificati, 368 inserzioni(+)
create mode 100755 tests/qemu-iotests/041
create mode 100644 tests/qemu-iotests/041.out
diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
new file mode 100755
index 0000000..fd9760a
--- /dev/null
+++ b/tests/qemu-iotests/041
@@ -0,0 +1,362 @@
+#!/usr/bin/env python
+#
+# Tests for image mirroring.
+#
+# Copyright (C) 2012 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import time
+import os
+import iotests
+from iotests import qemu_img, qemu_io
+import struct
+
+backing_img = os.path.join(iotests.test_dir, 'backing.img')
+target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img')
+test_img = os.path.join(iotests.test_dir, 'test.img')
+target_img = os.path.join(iotests.test_dir, 'target.img')
+
+class ImageMirroringTestCase(iotests.QMPTestCase):
+ '''Abstract base class for image mirroring test cases'''
+
+ def assert_no_active_mirrors(self):
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return', [])
+
+ def cancel_and_wait(self, drive='drive0', wait_ready=True):
+ '''Cancel a block job and wait for it to finish'''
+ if wait_ready:
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_READY':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ ready = True
+
+ result = self.vm.qmp('block-job-cancel', device=drive,
+ force=not wait_ready)
+ self.assert_qmp(result, 'return', {})
+
+ cancelled = False
+ while not cancelled:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_COMPLETED' or \
+ event['event'] == 'BLOCK_JOB_CANCELLED':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ if wait_ready:
+ self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
+ self.assert_qmp(event, 'data/offset', self.image_len)
+ self.assert_qmp(event, 'data/len', self.image_len)
+ cancelled = True
+
+ self.assert_no_active_mirrors()
+
+ def complete_and_wait(self, drive='drive0', wait_ready=True):
+ '''Complete a block job and wait for it to finish'''
+ if wait_ready:
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_READY':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ ready = True
+
+ result = self.vm.qmp('block-job-complete', device=drive)
+ self.assert_qmp(result, 'return', {})
+
+ completed = False
+ while not completed:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_COMPLETED':
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', drive)
+ self.assert_qmp_absent(event, 'data/error')
+ self.assert_qmp(event, 'data/offset', self.image_len)
+ self.assert_qmp(event, 'data/len', self.image_len)
+ completed = True
+
+ self.assert_no_active_mirrors()
+
+ def create_image(self, name, size):
+ file = open(name, 'w')
+ i = 0
+ while i < size:
+ sector = struct.pack('>l504xl', i / 512, i / 512)
+ file.write(sector)
+ i = i + 512
+ file.close()
+
+ def compare_images(self, img1, img2):
+ try:
+ qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw')
+ qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw')
+ file1 = open(img1 + '.raw', 'r')
+ file2 = open(img2 + '.raw', 'r')
+ return file1.read() == file2.read()
+ finally:
+ if file1 is not None:
+ file1.close()
+ if file2 is not None:
+ file2.close()
+ try:
+ os.remove(img1 + '.raw')
+ except OSError:
+ pass
+ try:
+ os.remove(img2 + '.raw')
+ except OSError:
+ pass
+
+class TestSingleDrive(ImageMirroringTestCase):
+ image_len = 1 * 1024 * 1024 # MB
+
+ def setUp(self):
+ self.create_image(backing_img, TestSingleDrive.image_len)
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ try:
+ os.remove(target_img)
+ except OSError:
+ pass
+
+ def test_complete(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', target_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_cancel(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.cancel_and_wait(wait_ready=False)
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', test_img)
+ self.vm.shutdown()
+
+ def test_cancel_after_ready(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.cancel_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', test_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_pause(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('block-job-pause', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ time.sleep(1)
+ result = self.vm.qmp('query-block-jobs')
+ offset = self.dictpath(result, 'return[0]/offset')
+
+ time.sleep(1)
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/offset', offset)
+
+ result = self.vm.qmp('block-job-resume', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_large_cluster(self):
+ self.assert_no_active_mirrors()
+
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s'
+ % (TestSingleDrive.image_len, backing_img), target_img)
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', target_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_medium_not_found(self):
+ result = self.vm.qmp('drive-mirror', device='ide1-cd0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ def test_image_not_found(self):
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ def test_device_not_found(self):
+ result = self.vm.qmp('drive-mirror', device='nonexistent', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'error/class', 'DeviceNotFound')
+
+class TestMirrorNoBacking(ImageMirroringTestCase):
+ image_len = 2 * 1024 * 1024 # MB
+
+ def complete_and_wait(self, drive='drive0', wait_ready=True):
+ self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
+ return ImageMirroringTestCase.complete_and_wait(self, drive, wait_ready)
+
+ def compare_images(self, img1, img2):
+ self.create_image(target_backing_img, TestMirrorNoBacking.image_len)
+ return ImageMirroringTestCase.compare_images(self, img1, img2)
+
+ def setUp(self):
+ self.create_image(backing_img, TestMirrorNoBacking.image_len)
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(target_backing_img)
+ os.remove(target_img)
+
+ def test_complete(self):
+ self.assert_no_active_mirrors()
+
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.complete_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', target_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+ def test_cancel(self):
+ self.assert_no_active_mirrors()
+
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img)
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ self.cancel_and_wait()
+ result = self.vm.qmp('query-block')
+ self.assert_qmp(result, 'return[0]/inserted/file', test_img)
+ self.vm.shutdown()
+ self.assertTrue(self.compare_images(test_img, target_img),
+ 'target image does not match source after mirroring')
+
+class TestSetSpeed(ImageMirroringTestCase):
+ image_len = 80 * 1024 * 1024 # MB
+
+ def setUp(self):
+ qemu_img('create', backing_img, str(TestSetSpeed.image_len))
+ qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(target_img)
+
+ def test_set_speed(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ # Default speed is 0
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/device', 'drive0')
+ self.assert_qmp(result, 'return[0]/speed', 0)
+
+ result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024)
+ self.assert_qmp(result, 'return', {})
+
+ # Ensure the speed we set was accepted
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/device', 'drive0')
+ self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024)
+
+ self.cancel_and_wait()
+
+ # Check setting speed in drive-mirror works
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, speed=4*1024*1024)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/device', 'drive0')
+ self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024)
+
+ self.cancel_and_wait()
+
+ def test_set_speed_invalid(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, speed=-1)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+ self.cancel_and_wait()
+
+if __name__ == '__main__':
+ iotests.main(supported_fmts=['qcow2', 'qed'])
diff --git a/tests/qemu-iotests/041.out b/tests/qemu-iotests/041.out
new file mode 100644
index 0000000..281b69e
--- /dev/null
+++ b/tests/qemu-iotests/041.out
@@ -0,0 +1,5 @@
+............
+----------------------------------------------------------------------
+Ran 12 tests
+
+OK
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 7407492..ac86f54 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -47,5 +47,6 @@
038 rw auto backing
039 rw auto
040 rw auto
+041 rw auto backing
042 rw auto quick
043 rw auto backing
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [Qemu-devel] [PATCH v4 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error
2012-10-22 8:44 ` Kevin Wolf
2012-10-22 15:38 ` Paolo Bonzini
2012-10-23 14:39 ` [Qemu-devel] [PATCH v4 " Paolo Bonzini
@ 2012-10-23 14:39 ` Paolo Bonzini
2 siblings, 0 replies; 29+ messages in thread
From: Paolo Bonzini @ 2012-10-23 14:39 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf
The new options are tested with blkdebug on both the source and the
target.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
The only change is in 041.out.
tests/qemu-iotests/041 | 253 ++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/041.out | 4 +-
tests/qemu-iotests/iotests.py | 4 +
3 file modificati, 259 inserzioni(+), 2 rimozioni(-)
diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041
index fd9760a..c6eb851 100755
--- a/tests/qemu-iotests/041
+++ b/tests/qemu-iotests/041
@@ -292,6 +292,259 @@ class TestMirrorNoBacking(ImageMirroringTestCase):
self.assertTrue(self.compare_images(test_img, target_img),
'target image does not match source after mirroring')
+class TestReadErrors(ImageMirroringTestCase):
+ image_len = 2 * 1024 * 1024 # MB
+
+ # this should be a multiple of twice the default granularity
+ # so that we hit this offset first in state 1
+ MIRROR_GRANULARITY = 1024 * 1024
+
+ def create_blkdebug_file(self, name, event, errno):
+ file = open(name, 'w')
+ file.write('''
+[inject-error]
+state = "1"
+event = "%s"
+errno = "%d"
+immediately = "off"
+once = "on"
+sector = "%d"
+
+[set-state]
+state = "1"
+event = "%s"
+new_state = "2"
+
+[set-state]
+state = "2"
+event = "%s"
+new_state = "1"
+''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
+ file.close()
+
+ def setUp(self):
+ self.blkdebug_file = backing_img + ".blkdebug"
+ self.create_image(backing_img, TestReadErrors.image_len)
+ self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5)
+ qemu_img('create', '-f', iotests.imgfmt,
+ '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw'
+ % (self.blkdebug_file, backing_img),
+ test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(self.blkdebug_file)
+
+ def test_report_read(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img)
+ self.assert_qmp(result, 'return', {})
+
+ completed = False
+ error = False
+ while not completed:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'read')
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(False, 'job completed unexpectedly')
+ elif event['event'] == 'BLOCK_JOB_COMPLETED':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/error', 'Input/output error')
+ self.assert_qmp(event, 'data/len', self.image_len)
+ completed = True
+
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
+ def test_ignore_read(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, on_source_error='ignore')
+ self.assert_qmp(result, 'return', {})
+
+ event = self.vm.get_qmp_event(wait=True)
+ self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'read')
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.complete_and_wait()
+ self.vm.shutdown()
+
+ def test_stop_read(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ target=target_img, on_source_error='stop')
+ self.assert_qmp(result, 'return', {})
+
+ error = False
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'read')
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', True)
+ self.assert_qmp(result, 'return[0]/io-status', 'failed')
+
+ result = self.vm.qmp('block-job-resume', device='drive0')
+ self.assert_qmp(result, 'return', {})
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ ready = True
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.assert_qmp(result, 'return[0]/io-status', 'ok')
+
+ self.complete_and_wait(wait_ready=False)
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
+class TestWriteErrors(ImageMirroringTestCase):
+ image_len = 2 * 1024 * 1024 # MB
+
+ # this should be a multiple of twice the default granularity
+ # so that we hit this offset first in state 1
+ MIRROR_GRANULARITY = 1024 * 1024
+
+ def create_blkdebug_file(self, name, event, errno):
+ file = open(name, 'w')
+ file.write('''
+[inject-error]
+state = "1"
+event = "%s"
+errno = "%d"
+immediately = "off"
+once = "on"
+sector = "%d"
+
+[set-state]
+state = "1"
+event = "%s"
+new_state = "2"
+
+[set-state]
+state = "2"
+event = "%s"
+new_state = "1"
+''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event))
+ file.close()
+
+ def setUp(self):
+ self.blkdebug_file = target_img + ".blkdebug"
+ self.create_image(backing_img, TestWriteErrors.image_len)
+ self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5)
+ qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img)
+ self.vm = iotests.VM().add_drive(test_img)
+ self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img)
+ qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img)
+ self.vm.launch()
+
+ def tearDown(self):
+ self.vm.shutdown()
+ os.remove(test_img)
+ os.remove(backing_img)
+ os.remove(self.blkdebug_file)
+
+ def test_report_write(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=self.target_img)
+ self.assert_qmp(result, 'return', {})
+
+ completed = False
+ error = False
+ while not completed:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'write')
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(False, 'job completed unexpectedly')
+ elif event['event'] == 'BLOCK_JOB_COMPLETED':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/type', 'mirror')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/error', 'Input/output error')
+ self.assert_qmp(event, 'data/len', self.image_len)
+ completed = True
+
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
+ def test_ignore_write(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=self.target_img,
+ on_target_error='ignore')
+ self.assert_qmp(result, 'return', {})
+
+ event = self.vm.get_qmp_event(wait=True)
+ self.assertEquals(event['event'], 'BLOCK_JOB_ERROR')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'write')
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.complete_and_wait()
+ self.vm.shutdown()
+
+ def test_stop_write(self):
+ self.assert_no_active_mirrors()
+
+ result = self.vm.qmp('drive-mirror', device='drive0', sync='full',
+ mode='existing', target=self.target_img,
+ on_target_error='stop')
+ self.assert_qmp(result, 'return', {})
+
+ error = False
+ ready = False
+ while not ready:
+ for event in self.vm.get_qmp_events(wait=True):
+ if event['event'] == 'BLOCK_JOB_ERROR':
+ self.assert_qmp(event, 'data/device', 'drive0')
+ self.assert_qmp(event, 'data/operation', 'write')
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', True)
+ self.assert_qmp(result, 'return[0]/io-status', 'failed')
+
+ result = self.vm.qmp('block-job-resume', device='drive0')
+ self.assert_qmp(result, 'return', {})
+
+ result = self.vm.qmp('query-block-jobs')
+ self.assert_qmp(result, 'return[0]/paused', False)
+ self.assert_qmp(result, 'return[0]/io-status', 'ok')
+ error = True
+ elif event['event'] == 'BLOCK_JOB_READY':
+ self.assertTrue(error, 'job completed unexpectedly')
+ self.assert_qmp(event, 'data/device', 'drive0')
+ ready = True
+
+ self.complete_and_wait(wait_ready=False)
+ self.assert_no_active_mirrors()
+ self.vm.shutdown()
+
class TestSetSpeed(ImageMirroringTestCase):
image_len = 80 * 1024 * 1024 # MB
diff --git a/tests/qemu-iotests/041.out b/tests/qemu-iotests/041.out
index 281b69e..71009c2 100644
--- a/tests/qemu-iotests/041.out
+++ b/tests/qemu-iotests/041.out
@@ -1,5 +1,5 @@
-............
+..................
----------------------------------------------------------------------
-Ran 12 tests
+Ran 18 tests
OK
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 3c60b2d..735c674 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -106,6 +106,10 @@ class VM(object):
return self._qmp.cmd(cmd, args=qmp_args)
+ def get_qmp_event(self, wait=False):
+ '''Poll for one queued QMP events and return it'''
+ return self._qmp.pull_event(wait=wait)
+
def get_qmp_events(self, wait=False):
'''Poll for queued QMP events and return a list of dicts'''
events = self._qmp.get_events(wait=wait)
--
1.7.12.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
end of thread, other threads:[~2012-10-23 14:39 UTC | newest]
Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-10-18 14:49 [Qemu-devel] [PULL for Kevin 00/16] Block job improvements part 2 Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 01/16] block: add bdrv_query_info Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 02/16] block: add bdrv_query_stats Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 03/16] block: add bdrv_open_backing_file Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 04/16] block: introduce new dirty bitmap functionality Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 05/16] block: export dirty bitmap information in query-block Paolo Bonzini
2012-10-18 16:51 ` Eric Blake
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 06/16] block: rename block_job_complete to block_job_completed Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 07/16] block: add block-job-complete Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 08/16] block: introduce BLOCK_JOB_READY event Paolo Bonzini
2012-10-18 16:58 ` Eric Blake
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 09/16] mirror: introduce mirror job Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 10/16] qmp: add drive-mirror command Paolo Bonzini
2012-10-18 17:34 ` Eric Blake
2012-10-19 12:54 ` Kevin Wolf
2012-10-19 13:13 ` Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 11/16] mirror: implement completion Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Paolo Bonzini
2012-10-19 16:19 ` Kevin Wolf
2012-10-20 13:47 ` Paolo Bonzini
2012-10-22 8:44 ` Kevin Wolf
2012-10-22 15:38 ` Paolo Bonzini
2012-10-23 14:39 ` [Qemu-devel] [PATCH v4 " Paolo Bonzini
2012-10-23 14:39 ` [Qemu-devel] [PATCH v4 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error Paolo Bonzini
2012-10-19 17:24 ` [Qemu-devel] [PATCH v3 12/16] qemu-iotests: add mirroring test case Kevin Wolf
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 13/16] iostatus: forward block_job_iostatus_reset to block job Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 14/16] mirror: add support for on-source-error/on-target-error Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 15/16] qmp: add pull_event function Paolo Bonzini
2012-10-18 14:49 ` [Qemu-devel] [PATCH v3 16/16] qemu-iotests: add testcases for mirroring on-source-error/on-target-error Paolo Bonzini
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).