diff options
-rw-r--r-- | block/mirror.c | 62 | ||||
-rw-r--r-- | blockdev.c | 15 | ||||
-rwxr-xr-x | tests/qemu-iotests/041 | 50 | ||||
-rw-r--r-- | tests/qemu-iotests/041.out | 4 | ||||
-rw-r--r-- | trace-events | 1 |
5 files changed, 109 insertions, 23 deletions
diff --git a/block/mirror.c b/block/mirror.c index 20cb1e7..307bcf1 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -15,6 +15,7 @@ #include "block/blockjob.h" #include "block/block_int.h" #include "qemu/ratelimit.h" +#include "qemu/bitmap.h" enum { /* @@ -36,6 +37,8 @@ typedef struct MirrorBlockJob { bool synced; bool should_complete; int64_t sector_num; + size_t buf_size; + unsigned long *cow_bitmap; HBitmapIter hbi; uint8_t *buf; } MirrorBlockJob; @@ -60,7 +63,7 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s, BlockDriverState *target = s->target; QEMUIOVector qiov; int ret, nb_sectors; - int64_t end; + int64_t end, sector_num, chunk_num; struct iovec iov; s->sector_num = hbitmap_iter_next(&s->hbi); @@ -71,32 +74,53 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s, assert(s->sector_num >= 0); } + /* If we have no backing file yet in the destination, and the cluster size + * is very large, we need to do COW ourselves. The first time a cluster is + * copied, copy it entirely. + * + * Because both BDRV_SECTORS_PER_DIRTY_CHUNK and the cluster size are + * powers of two, the number of sectors to copy cannot exceed one cluster. + */ + sector_num = s->sector_num; + nb_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK; + chunk_num = sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK; + if (s->cow_bitmap && !test_bit(chunk_num, s->cow_bitmap)) { + trace_mirror_cow(s, sector_num); + bdrv_round_to_clusters(s->target, + sector_num, BDRV_SECTORS_PER_DIRTY_CHUNK, + §or_num, &nb_sectors); + } + end = s->common.len >> BDRV_SECTOR_BITS; - nb_sectors = MIN(BDRV_SECTORS_PER_DIRTY_CHUNK, end - s->sector_num); - bdrv_reset_dirty(source, s->sector_num, nb_sectors); + nb_sectors = MIN(nb_sectors, end - sector_num); + bdrv_reset_dirty(source, 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); + trace_mirror_one_iteration(s, sector_num, nb_sectors); + ret = bdrv_co_readv(source, sector_num, nb_sectors, &qiov); if (ret < 0) { *p_action = mirror_error_action(s, true, -ret); goto fail; } - ret = bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov); + ret = bdrv_co_writev(target, sector_num, nb_sectors, &qiov); if (ret < 0) { *p_action = mirror_error_action(s, false, -ret); s->synced = false; goto fail; } + if (s->cow_bitmap) { + bitmap_set(s->cow_bitmap, sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK, + nb_sectors / BDRV_SECTORS_PER_DIRTY_CHUNK); + } return 0; fail: /* Try again later. */ - bdrv_set_dirty(source, s->sector_num, nb_sectors); + bdrv_set_dirty(source, sector_num, nb_sectors); return ret; } @@ -104,7 +128,9 @@ static void coroutine_fn mirror_run(void *opaque) { MirrorBlockJob *s = opaque; BlockDriverState *bs = s->common.bs; - int64_t sector_num, end; + int64_t sector_num, end, length; + BlockDriverInfo bdi; + char backing_filename[1024]; int ret = 0; int n; @@ -118,8 +144,23 @@ static void coroutine_fn mirror_run(void *opaque) return; } + /* If we have no backing file yet in the destination, we cannot let + * the destination do COW. Instead, we copy sectors around the + * dirty data if needed. We need a bitmap to do that. + */ + bdrv_get_backing_filename(s->target, backing_filename, + sizeof(backing_filename)); + if (backing_filename[0] && !s->target->backing_hd) { + bdrv_get_info(s->target, &bdi); + if (s->buf_size < bdi.cluster_size) { + s->buf_size = bdi.cluster_size; + length = (bdrv_getlength(bs) + BLOCK_SIZE - 1) / BLOCK_SIZE; + s->cow_bitmap = bitmap_new(length); + } + } + end = s->common.len >> BDRV_SECTOR_BITS; - s->buf = qemu_blockalign(bs, BLOCK_SIZE); + s->buf = qemu_blockalign(bs, s->buf_size); if (s->mode != MIRROR_SYNC_MODE_NONE) { /* First part, loop on the sectors and initialize the dirty bitmap. */ @@ -234,6 +275,7 @@ static void coroutine_fn mirror_run(void *opaque) immediate_exit: qemu_vfree(s->buf); + g_free(s->cow_bitmap); bdrv_set_dirty_tracking(bs, false); bdrv_iostatus_disable(s->target); if (s->should_complete && ret == 0) { @@ -320,6 +362,8 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target, s->on_target_error = on_target_error; s->target = target; s->mode = mode; + s->buf_size = BLOCK_SIZE; + 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); @@ -1197,7 +1197,6 @@ void qmp_drive_mirror(const char *device, const char *target, bool has_on_target_error, BlockdevOnError on_target_error, Error **errp) { - BlockDriverInfo bdi; BlockDriverState *bs; BlockDriverState *source, *target_bs; BlockDriver *proto_drv; @@ -1288,6 +1287,9 @@ void qmp_drive_mirror(const char *device, const char *target, return; } + /* Mirroring takes care of copy-on-write using the source's backing + * file. + */ target_bs = bdrv_new(""); ret = bdrv_open(target_bs, target, flags | BDRV_O_NO_BACKING, drv); @@ -1297,17 +1299,6 @@ void qmp_drive_mirror(const char *device, const char *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, on_source_error, on_target_error, block_job_cb, bs, &local_err); if (local_err != NULL) { diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 index c6eb851..a1299b3 100755 --- a/tests/qemu-iotests/041 +++ b/tests/qemu-iotests/041 @@ -292,6 +292,27 @@ class TestMirrorNoBacking(ImageMirroringTestCase): 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 fails if the image is not there + qemu_img('create', '-f', iotests.imgfmt, '-o', 'size=%d' + %(TestMirrorNoBacking.image_len), target_backing_img) + qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s' + % (TestMirrorNoBacking.image_len, target_backing_img), target_img) + os.remove(target_backing_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') + class TestReadErrors(ImageMirroringTestCase): image_len = 2 * 1024 * 1024 # MB @@ -330,6 +351,9 @@ new_state = "1" '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' % (self.blkdebug_file, backing_img), test_img) + # Write something for tests that use sync='top' + qemu_io('-c', 'write %d 512' % (self.MIRROR_GRANULARITY + 65536), + test_img) self.vm = iotests.VM().add_drive(test_img) self.vm.launch() @@ -383,6 +407,32 @@ new_state = "1" self.complete_and_wait() self.vm.shutdown() + def test_large_cluster(self): + self.assert_no_active_mirrors() + + # Test COW into the target image. The first half of the + # cluster at MIRROR_GRANULARITY has to be copied from + # backing_img, even though sync='top'. + qemu_img('create', '-f', iotests.imgfmt, '-ocluster_size=131072,backing_file=%s' %(backing_img), target_img) + result = self.vm.qmp('drive-mirror', device='drive0', sync='top', + on_source_error='ignore', + mode='existing', target=target_img) + 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() + + # Detach blkdebug to compare images successfully + qemu_img('rebase', '-f', iotests.imgfmt, '-u', '-b', backing_img, test_img) + self.assertTrue(self.compare_images(test_img, target_img), + 'target image does not match source after mirroring') + def test_stop_read(self): self.assert_no_active_mirrors() diff --git a/tests/qemu-iotests/041.out b/tests/qemu-iotests/041.out index 71009c2..3a89159 100644 --- a/tests/qemu-iotests/041.out +++ b/tests/qemu-iotests/041.out @@ -1,5 +1,5 @@ -.................. +.................... ---------------------------------------------------------------------- -Ran 18 tests +Ran 20 tests OK diff --git a/trace-events b/trace-events index 61ed349..ffa2756 100644 --- a/trace-events +++ b/trace-events @@ -84,6 +84,7 @@ 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" +mirror_cow(void *s, int64_t sector_num) "s %p sector_num %"PRId64 # blockdev.c qmp_block_job_cancel(void *job) "job %p" |