Browse Source
- New block filter: preallocate (which, on writes beyond an image file's end, allocates big chunks of data so that such post-EOF writes will occur less frequently) - write-zeroes and block-status support for Quorum - Implementation of truncate for the nvme block driver similarly to the existing implementations for host block devices and iscsi devices - Block layer refactoring: Drop the tighten_restrictions concept in the block permission functions - iotest fixes -----BEGIN PGP SIGNATURE----- iQFGBAABCAAwFiEEkb62CjDbPohX0Rgp9AfbAGHVz0AFAl/cwIoSHG1yZWl0ekBy ZWRoYXQuY29tAAoJEPQH2wBh1c9AnvMH+gOnZCwEUKWuBxGX3Wjb/kqV1OuhAhcP IVrKLRnqdarCYMQ9M4SZL6pedfsujHA7vClTV7NTrenXBsEIradBQ59ztQ0oDirS 4ipIjVtNqj7m86l+IRZDq5HlwOYwwFnWogmLo2bcmNJGLpPQQfrhL2vRJ1wLgFYk WjeAVlkkYcHnTIDvs4ne9WRSlxGVBWJ4X5nSlRdZqeyUcMY9v4wL4P9Wc4ZuORmq /5HRcT5JKGaT2bAueaqAGEdtPFGbazEP5uU7MTTK/fueDKIRAXO2d0gqhANtOOJQ 7hMmKhwOPOOhrrpCVi9nxsVwdCOHfurV0km6cOs+Iprm/Wm2UtuS/A8= =z+7k -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/maxreitz/tags/pull-block-2020-12-18' into staging Block patches: - New block filter: preallocate (which, on writes beyond an image file's end, allocates big chunks of data so that such post-EOF writes will occur less frequently) - write-zeroes and block-status support for Quorum - Implementation of truncate for the nvme block driver similarly to the existing implementations for host block devices and iscsi devices - Block layer refactoring: Drop the tighten_restrictions concept in the block permission functions - iotest fixes # gpg: Signature made Fri 18 Dec 2020 14:45:30 GMT # gpg: using RSA key 91BEB60A30DB3E8857D11829F407DB0061D5CF40 # gpg: issuer "mreitz@redhat.com" # gpg: Good signature from "Max Reitz <mreitz@redhat.com>" [full] # Primary key fingerprint: 91BE B60A 30DB 3E88 57D1 1829 F407 DB00 61D5 CF40 * remotes/maxreitz/tags/pull-block-2020-12-18: (30 commits) iotests: Fix _send_qemu_cmd with bash 5.1 iotests/102: Pass $QEMU_HANDLE to _send_qemu_cmd block/nvme: Implement fake truncate() coroutine quorum: Implement bdrv_co_pwrite_zeroes() quorum: Implement bdrv_co_block_status() scripts/simplebench: add bench_prealloc.py simplebench/results_to_text: make executable simplebench/results_to_text: add difference line to the table simplebench/results_to_text: improve view of the table simplebench: move results_to_text() into separate file simplebench: rename ascii() to results_to_text() scripts/simplebench: use standard deviation for +- error scripts/simplebench: support iops scripts/simplebench: fix grammar: s/successed/succeeded/ iotests: add 298 to test new preallocate filter driver iotests.py: execute_setup_common(): add required_fmts argument iotests: qemu_io_silent: support --image-opts qemu-io: add preallocate mode parameter for truncate command block: introduce preallocate filter block: bdrv_check_perm(): process children anyway ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>pull/105/head
48 changed files with 2357 additions and 447 deletions
@ -0,0 +1,559 @@ |
|||
/*
|
|||
* preallocate filter driver |
|||
* |
|||
* The driver performs preallocate operation: it is injected above |
|||
* some node, and before each write over EOF it does additional preallocating |
|||
* write-zeroes request. |
|||
* |
|||
* Copyright (c) 2020 Virtuozzo International GmbH. |
|||
* |
|||
* Author: |
|||
* Sementsov-Ogievskiy Vladimir <vsementsov@virtuozzo.com> |
|||
* |
|||
* 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/>.
|
|||
*/ |
|||
|
|||
#include "qemu/osdep.h" |
|||
|
|||
#include "qapi/error.h" |
|||
#include "qemu/module.h" |
|||
#include "qemu/option.h" |
|||
#include "qemu/units.h" |
|||
#include "block/block_int.h" |
|||
|
|||
|
|||
typedef struct PreallocateOpts { |
|||
int64_t prealloc_size; |
|||
int64_t prealloc_align; |
|||
} PreallocateOpts; |
|||
|
|||
typedef struct BDRVPreallocateState { |
|||
PreallocateOpts opts; |
|||
|
|||
/*
|
|||
* Track real data end, to crop preallocation on close. If < 0 the status is |
|||
* unknown. |
|||
* |
|||
* @data_end is a maximum of file size on open (or when we get write/resize |
|||
* permissions) and all write request ends after it. So it's safe to |
|||
* truncate to data_end if it is valid. |
|||
*/ |
|||
int64_t data_end; |
|||
|
|||
/*
|
|||
* Start of trailing preallocated area which reads as zero. May be smaller |
|||
* than data_end, if user does over-EOF write zero operation. If < 0 the |
|||
* status is unknown. |
|||
* |
|||
* If both @zero_start and @file_end are valid, the region |
|||
* [@zero_start, @file_end) is known to be preallocated zeroes. If @file_end |
|||
* is not valid, @zero_start doesn't make much sense. |
|||
*/ |
|||
int64_t zero_start; |
|||
|
|||
/*
|
|||
* Real end of file. Actually the cache for bdrv_getlength(bs->file->bs), |
|||
* to avoid extra lseek() calls on each write operation. If < 0 the status |
|||
* is unknown. |
|||
*/ |
|||
int64_t file_end; |
|||
|
|||
/*
|
|||
* All three states @data_end, @zero_start and @file_end are guaranteed to |
|||
* be invalid (< 0) when we don't have both exclusive BLK_PERM_RESIZE and |
|||
* BLK_PERM_WRITE permissions on file child. |
|||
*/ |
|||
} BDRVPreallocateState; |
|||
|
|||
#define PREALLOCATE_OPT_PREALLOC_ALIGN "prealloc-align" |
|||
#define PREALLOCATE_OPT_PREALLOC_SIZE "prealloc-size" |
|||
static QemuOptsList runtime_opts = { |
|||
.name = "preallocate", |
|||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), |
|||
.desc = { |
|||
{ |
|||
.name = PREALLOCATE_OPT_PREALLOC_ALIGN, |
|||
.type = QEMU_OPT_SIZE, |
|||
.help = "on preallocation, align file length to this number, " |
|||
"default 1M", |
|||
}, |
|||
{ |
|||
.name = PREALLOCATE_OPT_PREALLOC_SIZE, |
|||
.type = QEMU_OPT_SIZE, |
|||
.help = "how much to preallocate, default 128M", |
|||
}, |
|||
{ /* end of list */ } |
|||
}, |
|||
}; |
|||
|
|||
static bool preallocate_absorb_opts(PreallocateOpts *dest, QDict *options, |
|||
BlockDriverState *child_bs, Error **errp) |
|||
{ |
|||
QemuOpts *opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); |
|||
|
|||
if (!qemu_opts_absorb_qdict(opts, options, errp)) { |
|||
return false; |
|||
} |
|||
|
|||
dest->prealloc_align = |
|||
qemu_opt_get_size(opts, PREALLOCATE_OPT_PREALLOC_ALIGN, 1 * MiB); |
|||
dest->prealloc_size = |
|||
qemu_opt_get_size(opts, PREALLOCATE_OPT_PREALLOC_SIZE, 128 * MiB); |
|||
|
|||
qemu_opts_del(opts); |
|||
|
|||
if (!QEMU_IS_ALIGNED(dest->prealloc_align, BDRV_SECTOR_SIZE)) { |
|||
error_setg(errp, "prealloc-align parameter of preallocate filter " |
|||
"is not aligned to %llu", BDRV_SECTOR_SIZE); |
|||
return false; |
|||
} |
|||
|
|||
if (!QEMU_IS_ALIGNED(dest->prealloc_align, |
|||
child_bs->bl.request_alignment)) { |
|||
error_setg(errp, "prealloc-align parameter of preallocate filter " |
|||
"is not aligned to underlying node request alignment " |
|||
"(%" PRIi32 ")", child_bs->bl.request_alignment); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
static int preallocate_open(BlockDriverState *bs, QDict *options, int flags, |
|||
Error **errp) |
|||
{ |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
|
|||
/*
|
|||
* s->data_end and friends should be initialized on permission update. |
|||
* For this to work, mark them invalid. |
|||
*/ |
|||
s->file_end = s->zero_start = s->data_end = -EINVAL; |
|||
|
|||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds, |
|||
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, |
|||
false, errp); |
|||
if (!bs->file) { |
|||
return -EINVAL; |
|||
} |
|||
|
|||
if (!preallocate_absorb_opts(&s->opts, options, bs->file->bs, errp)) { |
|||
return -EINVAL; |
|||
} |
|||
|
|||
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED | |
|||
(BDRV_REQ_FUA & bs->file->bs->supported_write_flags); |
|||
|
|||
bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED | |
|||
((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK) & |
|||
bs->file->bs->supported_zero_flags); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static void preallocate_close(BlockDriverState *bs) |
|||
{ |
|||
int ret; |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
|
|||
if (s->data_end < 0) { |
|||
return; |
|||
} |
|||
|
|||
if (s->file_end < 0) { |
|||
s->file_end = bdrv_getlength(bs->file->bs); |
|||
if (s->file_end < 0) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (s->data_end < s->file_end) { |
|||
ret = bdrv_truncate(bs->file, s->data_end, true, PREALLOC_MODE_OFF, 0, |
|||
NULL); |
|||
s->file_end = ret < 0 ? ret : s->data_end; |
|||
} |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* Handle reopen. |
|||
* |
|||
* We must implement reopen handlers, otherwise reopen just don't work. Handle |
|||
* new options and don't care about preallocation state, as it is handled in |
|||
* set/check permission handlers. |
|||
*/ |
|||
|
|||
static int preallocate_reopen_prepare(BDRVReopenState *reopen_state, |
|||
BlockReopenQueue *queue, Error **errp) |
|||
{ |
|||
PreallocateOpts *opts = g_new0(PreallocateOpts, 1); |
|||
|
|||
if (!preallocate_absorb_opts(opts, reopen_state->options, |
|||
reopen_state->bs->file->bs, errp)) { |
|||
g_free(opts); |
|||
return -EINVAL; |
|||
} |
|||
|
|||
reopen_state->opaque = opts; |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static void preallocate_reopen_commit(BDRVReopenState *state) |
|||
{ |
|||
BDRVPreallocateState *s = state->bs->opaque; |
|||
|
|||
s->opts = *(PreallocateOpts *)state->opaque; |
|||
|
|||
g_free(state->opaque); |
|||
state->opaque = NULL; |
|||
} |
|||
|
|||
static void preallocate_reopen_abort(BDRVReopenState *state) |
|||
{ |
|||
g_free(state->opaque); |
|||
state->opaque = NULL; |
|||
} |
|||
|
|||
static coroutine_fn int preallocate_co_preadv_part( |
|||
BlockDriverState *bs, uint64_t offset, uint64_t bytes, |
|||
QEMUIOVector *qiov, size_t qiov_offset, int flags) |
|||
{ |
|||
return bdrv_co_preadv_part(bs->file, offset, bytes, qiov, qiov_offset, |
|||
flags); |
|||
} |
|||
|
|||
static int coroutine_fn preallocate_co_pdiscard(BlockDriverState *bs, |
|||
int64_t offset, int bytes) |
|||
{ |
|||
return bdrv_co_pdiscard(bs->file, offset, bytes); |
|||
} |
|||
|
|||
static bool can_write_resize(uint64_t perm) |
|||
{ |
|||
return (perm & BLK_PERM_WRITE) && (perm & BLK_PERM_RESIZE); |
|||
} |
|||
|
|||
static bool has_prealloc_perms(BlockDriverState *bs) |
|||
{ |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
|
|||
if (can_write_resize(bs->file->perm)) { |
|||
assert(!(bs->file->shared_perm & BLK_PERM_WRITE)); |
|||
assert(!(bs->file->shared_perm & BLK_PERM_RESIZE)); |
|||
return true; |
|||
} |
|||
|
|||
assert(s->data_end < 0); |
|||
assert(s->zero_start < 0); |
|||
assert(s->file_end < 0); |
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* Call on each write. Returns true if @want_merge_zero is true and the region |
|||
* [offset, offset + bytes) is zeroed (as a result of this call or earlier |
|||
* preallocation). |
|||
* |
|||
* want_merge_zero is used to merge write-zero request with preallocation in |
|||
* one bdrv_co_pwrite_zeroes() call. |
|||
*/ |
|||
static bool coroutine_fn handle_write(BlockDriverState *bs, int64_t offset, |
|||
int64_t bytes, bool want_merge_zero) |
|||
{ |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
int64_t end = offset + bytes; |
|||
int64_t prealloc_start, prealloc_end; |
|||
int ret; |
|||
|
|||
if (!has_prealloc_perms(bs)) { |
|||
/* We don't have state neither should try to recover it */ |
|||
return false; |
|||
} |
|||
|
|||
if (s->data_end < 0) { |
|||
s->data_end = bdrv_getlength(bs->file->bs); |
|||
if (s->data_end < 0) { |
|||
return false; |
|||
} |
|||
|
|||
if (s->file_end < 0) { |
|||
s->file_end = s->data_end; |
|||
} |
|||
} |
|||
|
|||
if (end <= s->data_end) { |
|||
return false; |
|||
} |
|||
|
|||
/* We have valid s->data_end, and request writes beyond it. */ |
|||
|
|||
s->data_end = end; |
|||
if (s->zero_start < 0 || !want_merge_zero) { |
|||
s->zero_start = end; |
|||
} |
|||
|
|||
if (s->file_end < 0) { |
|||
s->file_end = bdrv_getlength(bs->file->bs); |
|||
if (s->file_end < 0) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/* Now s->data_end, s->zero_start and s->file_end are valid. */ |
|||
|
|||
if (end <= s->file_end) { |
|||
/* No preallocation needed. */ |
|||
return want_merge_zero && offset >= s->zero_start; |
|||
} |
|||
|
|||
/* Now we want new preallocation, as request writes beyond s->file_end. */ |
|||
|
|||
prealloc_start = want_merge_zero ? MIN(offset, s->file_end) : s->file_end; |
|||
prealloc_end = QEMU_ALIGN_UP(end + s->opts.prealloc_size, |
|||
s->opts.prealloc_align); |
|||
|
|||
ret = bdrv_co_pwrite_zeroes( |
|||
bs->file, prealloc_start, prealloc_end - prealloc_start, |
|||
BDRV_REQ_NO_FALLBACK | BDRV_REQ_SERIALISING | BDRV_REQ_NO_WAIT); |
|||
if (ret < 0) { |
|||
s->file_end = ret; |
|||
return false; |
|||
} |
|||
|
|||
s->file_end = prealloc_end; |
|||
return want_merge_zero; |
|||
} |
|||
|
|||
static int coroutine_fn preallocate_co_pwrite_zeroes(BlockDriverState *bs, |
|||
int64_t offset, int bytes, BdrvRequestFlags flags) |
|||
{ |
|||
bool want_merge_zero = |
|||
!(flags & ~(BDRV_REQ_ZERO_WRITE | BDRV_REQ_NO_FALLBACK)); |
|||
if (handle_write(bs, offset, bytes, want_merge_zero)) { |
|||
return 0; |
|||
} |
|||
|
|||
return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags); |
|||
} |
|||
|
|||
static coroutine_fn int preallocate_co_pwritev_part(BlockDriverState *bs, |
|||
uint64_t offset, |
|||
uint64_t bytes, |
|||
QEMUIOVector *qiov, |
|||
size_t qiov_offset, |
|||
int flags) |
|||
{ |
|||
handle_write(bs, offset, bytes, false); |
|||
|
|||
return bdrv_co_pwritev_part(bs->file, offset, bytes, qiov, qiov_offset, |
|||
flags); |
|||
} |
|||
|
|||
static int coroutine_fn |
|||
preallocate_co_truncate(BlockDriverState *bs, int64_t offset, |
|||
bool exact, PreallocMode prealloc, |
|||
BdrvRequestFlags flags, Error **errp) |
|||
{ |
|||
ERRP_GUARD(); |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
int ret; |
|||
|
|||
if (s->data_end >= 0 && offset > s->data_end) { |
|||
if (s->file_end < 0) { |
|||
s->file_end = bdrv_getlength(bs->file->bs); |
|||
if (s->file_end < 0) { |
|||
error_setg(errp, "failed to get file length"); |
|||
return s->file_end; |
|||
} |
|||
} |
|||
|
|||
if (prealloc == PREALLOC_MODE_FALLOC) { |
|||
/*
|
|||
* If offset <= s->file_end, the task is already done, just |
|||
* update s->data_end, to move part of "filter preallocation" |
|||
* to "preallocation requested by user". |
|||
* Otherwise just proceed to preallocate missing part. |
|||
*/ |
|||
if (offset <= s->file_end) { |
|||
s->data_end = offset; |
|||
return 0; |
|||
} |
|||
} else { |
|||
/*
|
|||
* We have to drop our preallocation, to |
|||
* - avoid "Cannot use preallocation for shrinking files" in |
|||
* case of offset < file_end |
|||
* - give PREALLOC_MODE_OFF a chance to keep small disk |
|||
* usage |
|||
* - give PREALLOC_MODE_FULL a chance to actually write the |
|||
* whole region as user expects |
|||
*/ |
|||
if (s->file_end > s->data_end) { |
|||
ret = bdrv_co_truncate(bs->file, s->data_end, true, |
|||
PREALLOC_MODE_OFF, 0, errp); |
|||
if (ret < 0) { |
|||
s->file_end = ret; |
|||
error_prepend(errp, "preallocate-filter: failed to drop " |
|||
"write-zero preallocation: "); |
|||
return ret; |
|||
} |
|||
s->file_end = s->data_end; |
|||
} |
|||
} |
|||
|
|||
s->data_end = offset; |
|||
} |
|||
|
|||
ret = bdrv_co_truncate(bs->file, offset, exact, prealloc, flags, errp); |
|||
if (ret < 0) { |
|||
s->file_end = s->zero_start = s->data_end = ret; |
|||
return ret; |
|||
} |
|||
|
|||
if (has_prealloc_perms(bs)) { |
|||
s->file_end = s->zero_start = s->data_end = offset; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static int coroutine_fn preallocate_co_flush(BlockDriverState *bs) |
|||
{ |
|||
return bdrv_co_flush(bs->file->bs); |
|||
} |
|||
|
|||
static int64_t preallocate_getlength(BlockDriverState *bs) |
|||
{ |
|||
int64_t ret; |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
|
|||
if (s->data_end >= 0) { |
|||
return s->data_end; |
|||
} |
|||
|
|||
ret = bdrv_getlength(bs->file->bs); |
|||
|
|||
if (has_prealloc_perms(bs)) { |
|||
s->file_end = s->zero_start = s->data_end = ret; |
|||
} |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
static int preallocate_check_perm(BlockDriverState *bs, |
|||
uint64_t perm, uint64_t shared, Error **errp) |
|||
{ |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
|
|||
if (s->data_end >= 0 && !can_write_resize(perm)) { |
|||
/*
|
|||
* Lose permissions. |
|||
* We should truncate in check_perm, as in set_perm bs->file->perm will |
|||
* be already changed, and we should not violate it. |
|||
*/ |
|||
if (s->file_end < 0) { |
|||
s->file_end = bdrv_getlength(bs->file->bs); |
|||
if (s->file_end < 0) { |
|||
error_setg(errp, "Failed to get file length"); |
|||
return s->file_end; |
|||
} |
|||
} |
|||
|
|||
if (s->data_end < s->file_end) { |
|||
int ret = bdrv_truncate(bs->file, s->data_end, true, |
|||
PREALLOC_MODE_OFF, 0, NULL); |
|||
if (ret < 0) { |
|||
error_setg(errp, "Failed to drop preallocation"); |
|||
s->file_end = ret; |
|||
return ret; |
|||
} |
|||
s->file_end = s->data_end; |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static void preallocate_set_perm(BlockDriverState *bs, |
|||
uint64_t perm, uint64_t shared) |
|||
{ |
|||
BDRVPreallocateState *s = bs->opaque; |
|||
|
|||
if (can_write_resize(perm)) { |
|||
if (s->data_end < 0) { |
|||
s->data_end = s->file_end = s->zero_start = |
|||
bdrv_getlength(bs->file->bs); |
|||
} |
|||
} else { |
|||
/*
|
|||
* We drop our permissions, as well as allow shared |
|||
* permissions (see preallocate_child_perm), anyone will be able to |
|||
* change the child, so mark all states invalid. We'll regain control if |
|||
* get good permissions back. |
|||
*/ |
|||
s->data_end = s->file_end = s->zero_start = -EINVAL; |
|||
} |
|||
} |
|||
|
|||
static void preallocate_child_perm(BlockDriverState *bs, BdrvChild *c, |
|||
BdrvChildRole role, BlockReopenQueue *reopen_queue, |
|||
uint64_t perm, uint64_t shared, uint64_t *nperm, uint64_t *nshared) |
|||
{ |
|||
bdrv_default_perms(bs, c, role, reopen_queue, perm, shared, nperm, nshared); |
|||
|
|||
if (can_write_resize(perm)) { |
|||
/* This should come by default, but let's enforce: */ |
|||
*nperm |= BLK_PERM_WRITE | BLK_PERM_RESIZE; |
|||
|
|||
/*
|
|||
* Don't share, to keep our states s->file_end, s->data_end and |
|||
* s->zero_start valid. |
|||
*/ |
|||
*nshared &= ~(BLK_PERM_WRITE | BLK_PERM_RESIZE); |
|||
} |
|||
} |
|||
|
|||
BlockDriver bdrv_preallocate_filter = { |
|||
.format_name = "preallocate", |
|||
.instance_size = sizeof(BDRVPreallocateState), |
|||
|
|||
.bdrv_getlength = preallocate_getlength, |
|||
.bdrv_open = preallocate_open, |
|||
.bdrv_close = preallocate_close, |
|||
|
|||
.bdrv_reopen_prepare = preallocate_reopen_prepare, |
|||
.bdrv_reopen_commit = preallocate_reopen_commit, |
|||
.bdrv_reopen_abort = preallocate_reopen_abort, |
|||
|
|||
.bdrv_co_preadv_part = preallocate_co_preadv_part, |
|||
.bdrv_co_pwritev_part = preallocate_co_pwritev_part, |
|||
.bdrv_co_pwrite_zeroes = preallocate_co_pwrite_zeroes, |
|||
.bdrv_co_pdiscard = preallocate_co_pdiscard, |
|||
.bdrv_co_flush = preallocate_co_flush, |
|||
.bdrv_co_truncate = preallocate_co_truncate, |
|||
|
|||
.bdrv_check_perm = preallocate_check_perm, |
|||
.bdrv_set_perm = preallocate_set_perm, |
|||
.bdrv_child_perm = preallocate_child_perm, |
|||
|
|||
.has_variable_length = true, |
|||
.is_filter = true, |
|||
}; |
|||
|
|||
static void bdrv_preallocate_init(void) |
|||
{ |
|||
bdrv_register(&bdrv_preallocate_filter); |
|||
} |
|||
|
|||
block_init(bdrv_preallocate_init); |
|||
@ -0,0 +1,132 @@ |
|||
#!/usr/bin/env python3 |
|||
# |
|||
# Benchmark preallocate filter |
|||
# |
|||
# Copyright (c) 2020 Virtuozzo International GmbH. |
|||
# |
|||
# 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 sys |
|||
import os |
|||
import subprocess |
|||
import re |
|||
import json |
|||
|
|||
import simplebench |
|||
from results_to_text import results_to_text |
|||
|
|||
|
|||
def qemu_img_bench(args): |
|||
p = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
|||
universal_newlines=True) |
|||
|
|||
if p.returncode == 0: |
|||
try: |
|||
m = re.search(r'Run completed in (\d+.\d+) seconds.', p.stdout) |
|||
return {'seconds': float(m.group(1))} |
|||
except Exception: |
|||
return {'error': f'failed to parse qemu-img output: {p.stdout}'} |
|||
else: |
|||
return {'error': f'qemu-img failed: {p.returncode}: {p.stdout}'} |
|||
|
|||
|
|||
def bench_func(env, case): |
|||
fname = f"{case['dir']}/prealloc-test.qcow2" |
|||
try: |
|||
os.remove(fname) |
|||
except OSError: |
|||
pass |
|||
|
|||
subprocess.run([env['qemu-img-binary'], 'create', '-f', 'qcow2', fname, |
|||
'16G'], stdout=subprocess.DEVNULL, |
|||
stderr=subprocess.DEVNULL, check=True) |
|||
|
|||
args = [env['qemu-img-binary'], 'bench', '-c', str(case['count']), |
|||
'-d', '64', '-s', case['block-size'], '-t', 'none', '-n', '-w'] |
|||
if env['prealloc']: |
|||
args += ['--image-opts', |
|||
'driver=qcow2,file.driver=preallocate,file.file.driver=file,' |
|||
f'file.file.filename={fname}'] |
|||
else: |
|||
args += ['-f', 'qcow2', fname] |
|||
|
|||
return qemu_img_bench(args) |
|||
|
|||
|
|||
def auto_count_bench_func(env, case): |
|||
case['count'] = 100 |
|||
while True: |
|||
res = bench_func(env, case) |
|||
if 'error' in res: |
|||
return res |
|||
|
|||
if res['seconds'] >= 1: |
|||
break |
|||
|
|||
case['count'] *= 10 |
|||
|
|||
if res['seconds'] < 5: |
|||
case['count'] = round(case['count'] * 5 / res['seconds']) |
|||
res = bench_func(env, case) |
|||
if 'error' in res: |
|||
return res |
|||
|
|||
res['iops'] = case['count'] / res['seconds'] |
|||
return res |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
if len(sys.argv) < 2: |
|||
print(f'USAGE: {sys.argv[0]} <qemu-img binary> ' |
|||
'DISK_NAME:DIR_PATH ...') |
|||
exit(1) |
|||
|
|||
qemu_img = sys.argv[1] |
|||
|
|||
envs = [ |
|||
{ |
|||
'id': 'no-prealloc', |
|||
'qemu-img-binary': qemu_img, |
|||
'prealloc': False |
|||
}, |
|||
{ |
|||
'id': 'prealloc', |
|||
'qemu-img-binary': qemu_img, |
|||
'prealloc': True |
|||
} |
|||
] |
|||
|
|||
aligned_cases = [] |
|||
unaligned_cases = [] |
|||
|
|||
for disk in sys.argv[2:]: |
|||
name, path = disk.split(':') |
|||
aligned_cases.append({ |
|||
'id': f'{name}, aligned sequential 16k', |
|||
'block-size': '16k', |
|||
'dir': path |
|||
}) |
|||
unaligned_cases.append({ |
|||
'id': f'{name}, unaligned sequential 64k', |
|||
'block-size': '16k', |
|||
'dir': path |
|||
}) |
|||
|
|||
result = simplebench.bench(auto_count_bench_func, envs, |
|||
aligned_cases + unaligned_cases, count=5) |
|||
print(results_to_text(result)) |
|||
with open('results.json', 'w') as f: |
|||
json.dump(result, f, indent=4) |
|||
@ -0,0 +1,126 @@ |
|||
#!/usr/bin/env python3 |
|||
# |
|||
# Simple benchmarking framework |
|||
# |
|||
# Copyright (c) 2019 Virtuozzo International GmbH. |
|||
# |
|||
# 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 math |
|||
import tabulate |
|||
|
|||
# We want leading whitespace for difference row cells (see below) |
|||
tabulate.PRESERVE_WHITESPACE = True |
|||
|
|||
|
|||
def format_value(x, stdev): |
|||
stdev_pr = stdev / x * 100 |
|||
if stdev_pr < 1.5: |
|||
# don't care too much |
|||
return f'{x:.2g}' |
|||
else: |
|||
return f'{x:.2g} ± {math.ceil(stdev_pr)}%' |
|||
|
|||
|
|||
def result_to_text(result): |
|||
"""Return text representation of bench_one() returned dict.""" |
|||
if 'average' in result: |
|||
s = format_value(result['average'], result['stdev']) |
|||
if 'n-failed' in result: |
|||
s += '\n({} failed)'.format(result['n-failed']) |
|||
return s |
|||
else: |
|||
return 'FAILED' |
|||
|
|||
|
|||
def results_dimension(results): |
|||
dim = None |
|||
for case in results['cases']: |
|||
for env in results['envs']: |
|||
res = results['tab'][case['id']][env['id']] |
|||
if dim is None: |
|||
dim = res['dimension'] |
|||
else: |
|||
assert dim == res['dimension'] |
|||
|
|||
assert dim in ('iops', 'seconds') |
|||
|
|||
return dim |
|||
|
|||
|
|||
def results_to_text(results): |
|||
"""Return text representation of bench() returned dict.""" |
|||
n_columns = len(results['envs']) |
|||
named_columns = n_columns > 2 |
|||
dim = results_dimension(results) |
|||
tab = [] |
|||
|
|||
if named_columns: |
|||
# Environment columns are named A, B, ... |
|||
tab.append([''] + [chr(ord('A') + i) for i in range(n_columns)]) |
|||
|
|||
tab.append([''] + [c['id'] for c in results['envs']]) |
|||
|
|||
for case in results['cases']: |
|||
row = [case['id']] |
|||
case_results = results['tab'][case['id']] |
|||
for env in results['envs']: |
|||
res = case_results[env['id']] |
|||
row.append(result_to_text(res)) |
|||
tab.append(row) |
|||
|
|||
# Add row of difference between columns. For each column starting from |
|||
# B we calculate difference with all previous columns. |
|||
row = ['', ''] # case name and first column |
|||
for i in range(1, n_columns): |
|||
cell = '' |
|||
env = results['envs'][i] |
|||
res = case_results[env['id']] |
|||
|
|||
if 'average' not in res: |
|||
# Failed result |
|||
row.append(cell) |
|||
continue |
|||
|
|||
for j in range(0, i): |
|||
env_j = results['envs'][j] |
|||
res_j = case_results[env_j['id']] |
|||
cell += ' ' |
|||
|
|||
if 'average' not in res_j: |
|||
# Failed result |
|||
cell += '--' |
|||
continue |
|||
|
|||
col_j = tab[0][j + 1] if named_columns else '' |
|||
diff_pr = round((res['average'] - res_j['average']) / |
|||
res_j['average'] * 100) |
|||
cell += f' {col_j}{diff_pr:+}%' |
|||
row.append(cell) |
|||
tab.append(row) |
|||
|
|||
return f'All results are in {dim}\n\n' + tabulate.tabulate(tab) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
import sys |
|||
import json |
|||
|
|||
if len(sys.argv) < 2: |
|||
print(f'USAGE: {sys.argv[0]} results.json') |
|||
exit(1) |
|||
|
|||
with open(sys.argv[1]) as f: |
|||
print(results_to_text(json.load(f))) |
|||
@ -0,0 +1,186 @@ |
|||
#!/usr/bin/env python3 |
|||
# |
|||
# Test for preallocate filter |
|||
# |
|||
# Copyright (c) 2020 Virtuozzo International GmbH. |
|||
# |
|||
# 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 os |
|||
import iotests |
|||
|
|||
MiB = 1024 * 1024 |
|||
disk = os.path.join(iotests.test_dir, 'disk') |
|||
overlay = os.path.join(iotests.test_dir, 'overlay') |
|||
refdisk = os.path.join(iotests.test_dir, 'refdisk') |
|||
drive_opts = f'node-name=disk,driver={iotests.imgfmt},' \ |
|||
f'file.node-name=filter,file.driver=preallocate,' \ |
|||
f'file.file.node-name=file,file.file.filename={disk}' |
|||
|
|||
|
|||
class TestPreallocateBase(iotests.QMPTestCase): |
|||
def setUp(self): |
|||
iotests.qemu_img_create('-f', iotests.imgfmt, disk, str(10 * MiB)) |
|||
|
|||
def tearDown(self): |
|||
try: |
|||
self.check_small() |
|||
check = iotests.qemu_img_check(disk) |
|||
self.assertFalse('leaks' in check) |
|||
self.assertFalse('corruptions' in check) |
|||
self.assertEqual(check['check-errors'], 0) |
|||
finally: |
|||
os.remove(disk) |
|||
|
|||
def check_big(self): |
|||
self.assertTrue(os.path.getsize(disk) > 100 * MiB) |
|||
|
|||
def check_small(self): |
|||
self.assertTrue(os.path.getsize(disk) < 10 * MiB) |
|||
|
|||
|
|||
class TestQemuImg(TestPreallocateBase): |
|||
def test_qemu_img(self): |
|||
p = iotests.QemuIoInteractive('--image-opts', drive_opts) |
|||
|
|||
p.cmd('write 0 1M') |
|||
p.cmd('flush') |
|||
|
|||
self.check_big() |
|||
|
|||
p.close() |
|||
|
|||
|
|||
class TestPreallocateFilter(TestPreallocateBase): |
|||
def setUp(self): |
|||
super().setUp() |
|||
self.vm = iotests.VM().add_drive(path=None, opts=drive_opts) |
|||
self.vm.launch() |
|||
|
|||
def tearDown(self): |
|||
self.vm.shutdown() |
|||
super().tearDown() |
|||
|
|||
def test_prealloc(self): |
|||
self.vm.hmp_qemu_io('drive0', 'write 0 1M') |
|||
self.check_big() |
|||
|
|||
def test_external_snapshot(self): |
|||
self.test_prealloc() |
|||
|
|||
result = self.vm.qmp('blockdev-snapshot-sync', node_name='disk', |
|||
snapshot_file=overlay, |
|||
snapshot_node_name='overlay') |
|||
self.assert_qmp(result, 'return', {}) |
|||
|
|||
# on reopen to r-o base preallocation should be dropped |
|||
self.check_small() |
|||
|
|||
self.vm.hmp_qemu_io('drive0', 'write 1M 1M') |
|||
|
|||
result = self.vm.qmp('block-commit', device='overlay') |
|||
self.assert_qmp(result, 'return', {}) |
|||
self.complete_and_wait() |
|||
|
|||
# commit of new megabyte should trigger preallocation |
|||
self.check_big() |
|||
|
|||
def test_reopen_opts(self): |
|||
result = self.vm.qmp('x-blockdev-reopen', **{ |
|||
'node-name': 'disk', |
|||
'driver': iotests.imgfmt, |
|||
'file': { |
|||
'node-name': 'filter', |
|||
'driver': 'preallocate', |
|||
'prealloc-size': 20 * MiB, |
|||
'prealloc-align': 5 * MiB, |
|||
'file': { |
|||
'node-name': 'file', |
|||
'driver': 'file', |
|||
'filename': disk |
|||
} |
|||
} |
|||
}) |
|||
self.assert_qmp(result, 'return', {}) |
|||
|
|||
self.vm.hmp_qemu_io('drive0', 'write 0 1M') |
|||
self.assertTrue(os.path.getsize(disk) == 25 * MiB) |
|||
|
|||
|
|||
class TestTruncate(iotests.QMPTestCase): |
|||
def setUp(self): |
|||
iotests.qemu_img_create('-f', iotests.imgfmt, disk, str(10 * MiB)) |
|||
iotests.qemu_img_create('-f', iotests.imgfmt, refdisk, str(10 * MiB)) |
|||
|
|||
def tearDown(self): |
|||
os.remove(disk) |
|||
os.remove(refdisk) |
|||
|
|||
def do_test(self, prealloc_mode, new_size): |
|||
ret = iotests.qemu_io_silent('--image-opts', '-c', 'write 0 10M', '-c', |
|||
f'truncate -m {prealloc_mode} {new_size}', |
|||
drive_opts) |
|||
self.assertEqual(ret, 0) |
|||
|
|||
ret = iotests.qemu_io_silent('-f', iotests.imgfmt, '-c', 'write 0 10M', |
|||
'-c', |
|||
f'truncate -m {prealloc_mode} {new_size}', |
|||
refdisk) |
|||
self.assertEqual(ret, 0) |
|||
|
|||
stat = os.stat(disk) |
|||
refstat = os.stat(refdisk) |
|||
|
|||
# Probably we'll want preallocate filter to keep align to cluster when |
|||
# shrink preallocation, so, ignore small differece |
|||
self.assertLess(abs(stat.st_size - refstat.st_size), 64 * 1024) |
|||
|
|||
# Preallocate filter may leak some internal clusters (for example, if |
|||
# guest write far over EOF, skipping some clusters - they will remain |
|||
# fallocated, preallocate filter don't care about such leaks, it drops |
|||
# only trailing preallocation. |
|||
self.assertLess(abs(stat.st_blocks - refstat.st_blocks) * 512, |
|||
1024 * 1024) |
|||
|
|||
def test_real_shrink(self): |
|||
self.do_test('off', '5M') |
|||
|
|||
def test_truncate_inside_preallocated_area__falloc(self): |
|||
self.do_test('falloc', '50M') |
|||
|
|||
def test_truncate_inside_preallocated_area__metadata(self): |
|||
self.do_test('metadata', '50M') |
|||
|
|||
def test_truncate_inside_preallocated_area__full(self): |
|||
self.do_test('full', '50M') |
|||
|
|||
def test_truncate_inside_preallocated_area__off(self): |
|||
self.do_test('off', '50M') |
|||
|
|||
def test_truncate_over_preallocated_area__falloc(self): |
|||
self.do_test('falloc', '150M') |
|||
|
|||
def test_truncate_over_preallocated_area__metadata(self): |
|||
self.do_test('metadata', '150M') |
|||
|
|||
def test_truncate_over_preallocated_area__full(self): |
|||
self.do_test('full', '150M') |
|||
|
|||
def test_truncate_over_preallocated_area__off(self): |
|||
self.do_test('off', '150M') |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
iotests.main(supported_fmts=['qcow2'], required_fmts=['preallocate']) |
|||
@ -0,0 +1,5 @@ |
|||
............. |
|||
---------------------------------------------------------------------- |
|||
Ran 13 tests |
|||
|
|||
OK |
|||
@ -0,0 +1,159 @@ |
|||
#!/usr/bin/env bash |
|||
# |
|||
# Test drive-mirror with quorum |
|||
# |
|||
# The goal of this test is to check how the quorum driver reports |
|||
# regions that are known to read as zeroes (BDRV_BLOCK_ZERO). The idea |
|||
# is that drive-mirror will try the efficient representation of zeroes |
|||
# in the destination image instead of writing actual zeroes. |
|||
# |
|||
# Copyright (C) 2020 Igalia, S.L. |
|||
# Author: Alberto Garcia <berto@igalia.com> |
|||
# |
|||
# This program is free software; you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation; either version 2 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
|
|||
# creator |
|||
owner=berto@igalia.com |
|||
|
|||
seq=`basename $0` |
|||
echo "QA output created by $seq" |
|||
|
|||
status=1 # failure is the default! |
|||
|
|||
_cleanup() |
|||
{ |
|||
_rm_test_img "$TEST_IMG.0" |
|||
_rm_test_img "$TEST_IMG.1" |
|||
_rm_test_img "$TEST_IMG.2" |
|||
_rm_test_img "$TEST_IMG.3" |
|||
_cleanup_qemu |
|||
} |
|||
trap "_cleanup; exit \$status" 0 1 2 3 15 |
|||
|
|||
# get standard environment, filters and checks |
|||
. ./common.rc |
|||
. ./common.filter |
|||
. ./common.qemu |
|||
|
|||
_supported_fmt qcow2 |
|||
_supported_proto file |
|||
_supported_os Linux |
|||
_unsupported_imgopts cluster_size data_file |
|||
|
|||
echo |
|||
echo '### Create all images' # three source (quorum), one destination |
|||
echo |
|||
TEST_IMG="$TEST_IMG.0" _make_test_img -o cluster_size=64k 10M |
|||
TEST_IMG="$TEST_IMG.1" _make_test_img -o cluster_size=64k 10M |
|||
TEST_IMG="$TEST_IMG.2" _make_test_img -o cluster_size=64k 10M |
|||
TEST_IMG="$TEST_IMG.3" _make_test_img -o cluster_size=64k 10M |
|||
|
|||
quorum="driver=raw,file.driver=quorum,file.vote-threshold=2" |
|||
quorum="$quorum,file.children.0.file.filename=$TEST_IMG.0" |
|||
quorum="$quorum,file.children.1.file.filename=$TEST_IMG.1" |
|||
quorum="$quorum,file.children.2.file.filename=$TEST_IMG.2" |
|||
quorum="$quorum,file.children.0.driver=$IMGFMT" |
|||
quorum="$quorum,file.children.1.driver=$IMGFMT" |
|||
quorum="$quorum,file.children.2.driver=$IMGFMT" |
|||
|
|||
echo |
|||
echo '### Output of qemu-img map (empty quorum)' |
|||
echo |
|||
$QEMU_IMG map --image-opts $quorum | _filter_qemu_img_map |
|||
|
|||
# Now we write data to the quorum. All three images will read as |
|||
# zeroes in all cases, but with different ways to represent them |
|||
# (unallocated clusters, zero clusters, data clusters with zeroes) |
|||
# that will have an effect on how the data will be mirrored and the |
|||
# output of qemu-img map on the resulting image. |
|||
echo |
|||
echo '### Write data to the quorum' |
|||
echo |
|||
# Test 1: data regions surrounded by unallocated clusters. |
|||
# Three data regions, the largest one (0x30000) will be picked, end result: |
|||
# offset 0x10000, length 0x30000 -> data |
|||
$QEMU_IO -c "write -P 0 $((0x10000)) $((0x10000))" "$TEST_IMG.0" | _filter_qemu_io |
|||
$QEMU_IO -c "write -P 0 $((0x10000)) $((0x30000))" "$TEST_IMG.1" | _filter_qemu_io |
|||
$QEMU_IO -c "write -P 0 $((0x10000)) $((0x20000))" "$TEST_IMG.2" | _filter_qemu_io |
|||
|
|||
# Test 2: zero regions surrounded by data clusters. |
|||
# First we allocate the data clusters. |
|||
$QEMU_IO -c "open -o $quorum" -c "write -P 0 $((0x100000)) $((0x40000))" | _filter_qemu_io |
|||
|
|||
# Three zero regions, the smallest one (0x10000) will be picked, end result: |
|||
# offset 0x100000, length 0x10000 -> data |
|||
# offset 0x110000, length 0x10000 -> zeroes |
|||
# offset 0x120000, length 0x20000 -> data |
|||
$QEMU_IO -c "write -z $((0x110000)) $((0x10000))" "$TEST_IMG.0" | _filter_qemu_io |
|||
$QEMU_IO -c "write -z $((0x110000)) $((0x30000))" "$TEST_IMG.1" | _filter_qemu_io |
|||
$QEMU_IO -c "write -z $((0x110000)) $((0x20000))" "$TEST_IMG.2" | _filter_qemu_io |
|||
|
|||
# Test 3: zero clusters surrounded by unallocated clusters. |
|||
# Everything reads as zeroes, no effect on the end result. |
|||
$QEMU_IO -c "write -z $((0x150000)) $((0x10000))" "$TEST_IMG.0" | _filter_qemu_io |
|||
$QEMU_IO -c "write -z $((0x150000)) $((0x30000))" "$TEST_IMG.1" | _filter_qemu_io |
|||
$QEMU_IO -c "write -z $((0x150000)) $((0x20000))" "$TEST_IMG.2" | _filter_qemu_io |
|||
|
|||
# Test 4: mix of data and zero clusters. |
|||
# The zero region will be ignored in favor of the largest data region |
|||
# (0x20000), end result: |
|||
# offset 0x200000, length 0x20000 -> data |
|||
$QEMU_IO -c "write -P 0 $((0x200000)) $((0x10000))" "$TEST_IMG.0" | _filter_qemu_io |
|||
$QEMU_IO -c "write -z $((0x200000)) $((0x30000))" "$TEST_IMG.1" | _filter_qemu_io |
|||
$QEMU_IO -c "write -P 0 $((0x200000)) $((0x20000))" "$TEST_IMG.2" | _filter_qemu_io |
|||
|
|||
# Test 5: write data to a region and then zeroize it, doing it |
|||
# directly on the quorum device instead of the individual images. |
|||
# This has no effect on the end result but proves that the quorum driver |
|||
# supports 'write -z'. |
|||
$QEMU_IO -c "open -o $quorum" -c "write -P 1 $((0x250000)) $((0x10000))" | _filter_qemu_io |
|||
# Verify the data that we just wrote |
|||
$QEMU_IO -c "open -o $quorum" -c "read -P 1 $((0x250000)) $((0x10000))" | _filter_qemu_io |
|||
$QEMU_IO -c "open -o $quorum" -c "write -z $((0x250000)) $((0x10000))" | _filter_qemu_io |
|||
# Now it should read back as zeroes |
|||
$QEMU_IO -c "open -o $quorum" -c "read -P 0 $((0x250000)) $((0x10000))" | _filter_qemu_io |
|||
|
|||
echo |
|||
echo '### Launch the drive-mirror job' |
|||
echo |
|||
qemu_comm_method="qmp" _launch_qemu -drive if=virtio,"$quorum" |
|||
h=$QEMU_HANDLE |
|||
_send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" 'return' |
|||
|
|||
_send_qemu_cmd $h \ |
|||
"{'execute': 'drive-mirror', |
|||
'arguments': {'device': 'virtio0', |
|||
'format': '$IMGFMT', |
|||
'target': '$TEST_IMG.3', |
|||
'sync': 'full', |
|||
'mode': 'existing' }}" \ |
|||
"BLOCK_JOB_READY.*virtio0" |
|||
|
|||
_send_qemu_cmd $h \ |
|||
"{ 'execute': 'block-job-complete', |
|||
'arguments': { 'device': 'virtio0' } }" \ |
|||
'BLOCK_JOB_COMPLETED' |
|||
|
|||
_send_qemu_cmd $h "{ 'execute': 'quit' }" '' |
|||
|
|||
echo |
|||
echo '### Output of qemu-img map (destination image)' |
|||
echo |
|||
$QEMU_IMG map "$TEST_IMG.3" | _filter_qemu_img_map |
|||
|
|||
# success, all done |
|||
echo "*** done" |
|||
rm -f $seq.full |
|||
status=0 |
|||
@ -0,0 +1,81 @@ |
|||
QA output created by 312 |
|||
|
|||
### Create all images |
|||
|
|||
Formatting 'TEST_DIR/t.IMGFMT.0', fmt=IMGFMT size=10485760 |
|||
Formatting 'TEST_DIR/t.IMGFMT.1', fmt=IMGFMT size=10485760 |
|||
Formatting 'TEST_DIR/t.IMGFMT.2', fmt=IMGFMT size=10485760 |
|||
Formatting 'TEST_DIR/t.IMGFMT.3', fmt=IMGFMT size=10485760 |
|||
|
|||
### Output of qemu-img map (empty quorum) |
|||
|
|||
Offset Length File |
|||
|
|||
### Write data to the quorum |
|||
|
|||
wrote 65536/65536 bytes at offset 65536 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 196608/196608 bytes at offset 65536 |
|||
192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 131072/131072 bytes at offset 65536 |
|||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 262144/262144 bytes at offset 1048576 |
|||
256 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 65536/65536 bytes at offset 1114112 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 196608/196608 bytes at offset 1114112 |
|||
192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 131072/131072 bytes at offset 1114112 |
|||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 65536/65536 bytes at offset 1376256 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 196608/196608 bytes at offset 1376256 |
|||
192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 131072/131072 bytes at offset 1376256 |
|||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 65536/65536 bytes at offset 2097152 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 196608/196608 bytes at offset 2097152 |
|||
192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 131072/131072 bytes at offset 2097152 |
|||
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 65536/65536 bytes at offset 2424832 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
read 65536/65536 bytes at offset 2424832 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
wrote 65536/65536 bytes at offset 2424832 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
read 65536/65536 bytes at offset 2424832 |
|||
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
|||
|
|||
### Launch the drive-mirror job |
|||
|
|||
{ 'execute': 'qmp_capabilities' } |
|||
{"return": {}} |
|||
{'execute': 'drive-mirror', |
|||
'arguments': {'device': 'virtio0', |
|||
'format': 'IMGFMT', |
|||
'target': 'TEST_DIR/t.IMGFMT.3', |
|||
'sync': 'full', |
|||
'mode': 'existing' }} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "virtio0"}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "virtio0"}} |
|||
{"return": {}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "virtio0"}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "virtio0", "len": 10485760, "offset": 10485760, "speed": 0, "type": "mirror"}} |
|||
{ 'execute': 'block-job-complete', |
|||
'arguments': { 'device': 'virtio0' } } |
|||
{"return": {}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "virtio0"}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "virtio0"}} |
|||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "virtio0", "len": 10485760, "offset": 10485760, "speed": 0, "type": "mirror"}} |
|||
{ 'execute': 'quit' } |
|||
|
|||
### Output of qemu-img map (destination image) |
|||
|
|||
Offset Length File |
|||
0x10000 0x30000 TEST_DIR/t.IMGFMT.3 |
|||
0x100000 0x10000 TEST_DIR/t.IMGFMT.3 |
|||
0x120000 0x20000 TEST_DIR/t.IMGFMT.3 |
|||
0x200000 0x20000 TEST_DIR/t.IMGFMT.3 |
|||
*** done |
|||
Loading…
Reference in new issue