nbd patches for 2019-01-14

Promote bitmap/NBD interfaces to stable for use in incremental
 backups. Add 'qemu-nbd --bitmap'.
 
 - John Snow: 0/11 bitmaps: remove x- prefix from QMP api
 - Philippe Mathieu-Daudé: qemu-nbd: Rename 'exp' variable clashing with math::exp() symbol
 - Eric Blake: 0/8 Promote x-nbd-server-add-bitmap to stable
 -----BEGIN PGP SIGNATURE-----
 
 iQEcBAABCAAGBQJcPLU5AAoJEKeha0olJ0Nqt4AH/2JXm2Ky9lth8QdVzPodh0cl
 xeBteQ51LVjneAlQuT5u5LR6+0ujljVCDUPaUfAcxhWJagofzPVIdSe+kOnQEtgN
 e7zf1pFxvLkITTl2iu0/HFxBz1pJb+ZayZqZ5zOHfj7LVSafwNC7MbW/t5yueiXl
 fPbxZqF4+C7lN9woS4p6zgB5berQuWHdHxsLLbkxFigr9JRiH3t8qm1tsa0nd/AZ
 uCyVAzqxFBkchh86uZ8TisUU7XqR+ab99/feOEkMWj2ctra7HfkJeCkPOZialzOS
 5A4UBZSDzCwr10NtHBMQhvSmvBFghm4L3u9faejg81TTTpbIQtcUoXu+ClGIHVM=
 =cYBw
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2019-01-14' into staging

nbd patches for 2019-01-14

Promote bitmap/NBD interfaces to stable for use in incremental
backups. Add 'qemu-nbd --bitmap'.

- John Snow: 0/11 bitmaps: remove x- prefix from QMP api
- Philippe Mathieu-Daudé: qemu-nbd: Rename 'exp' variable clashing with math::exp() symbol
- Eric Blake: 0/8 Promote x-nbd-server-add-bitmap to stable

# gpg: Signature made Mon 14 Jan 2019 16:13:45 GMT
# gpg:                using RSA key A7A16B4A2527436A
# gpg: Good signature from "Eric Blake <eblake@redhat.com>"
# gpg:                 aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>"
# gpg:                 aka "[jpeg image of size 6874]"
# Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2  F3AA A7A1 6B4A 2527 436A

* remotes/ericb/tags/pull-nbd-2019-01-14:
  qemu-nbd: Add --bitmap=NAME option
  nbd: Merge nbd_export_bitmap into nbd_export_new
  nbd: Remove x-nbd-server-add-bitmap
  nbd: Allow bitmap export during QMP nbd-server-add
  nbd: Merge nbd_export_set_name into nbd_export_new
  nbd: Only require disabled bitmap for read-only exports
  nbd: Forbid nbd-server-stop when server is not running
  nbd: Add some error case testing to iotests 223
  qemu-nbd: Rename 'exp' variable clashing with math::exp() symbol
  iotests: add iotest 236 for testing bitmap merge
  iotests: implement pretty-print for log and qmp_log
  iotests: change qmp_log filters to expect QMP objects only
  iotests: remove default filters from qmp_log
  iotests: add qmp recursive sorting function
  iotests: add filter_generated_node_ids
  iotests.py: don't abort if IMGKEYSECRET is undefined
  block: remove 'x' prefix from experimental bitmap APIs
  blockdev: n-ary bitmap merge
  block/dirty-bitmap: remove assertion from restore
  blockdev: abort transactions in reverse order

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-01-15 14:19:18 +00:00
commit 44ba601063
18 changed files with 839 additions and 242 deletions

View File

@ -625,7 +625,6 @@ void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap, HBitmap **out)
void bdrv_restore_dirty_bitmap(BdrvDirtyBitmap *bitmap, HBitmap *backup)
{
HBitmap *tmp = bitmap->bitmap;
assert(bdrv_dirty_bitmap_enabled(bitmap));
assert(!bdrv_dirty_bitmap_readonly(bitmap));
bitmap->bitmap = backup;
hbitmap_free(tmp);

View File

@ -140,7 +140,8 @@ void qmp_nbd_server_start(SocketAddressLegacy *addr,
}
void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
bool has_writable, bool writable, Error **errp)
bool has_writable, bool writable,
bool has_bitmap, const char *bitmap, Error **errp)
{
BlockDriverState *bs = NULL;
BlockBackend *on_eject_blk;
@ -174,14 +175,13 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
writable = false;
}
exp = nbd_export_new(bs, 0, -1, writable ? 0 : NBD_FLAG_READ_ONLY,
exp = nbd_export_new(bs, 0, -1, name, NULL, bitmap,
writable ? 0 : NBD_FLAG_READ_ONLY,
NULL, false, on_eject_blk, errp);
if (!exp) {
return;
}
nbd_export_set_name(exp, name);
/* The list of named exports has a strong reference to this export now and
* our only way of accessing it is through nbd_export_find(), so we can drop
* the strong reference that is @exp. */
@ -214,31 +214,13 @@ void qmp_nbd_server_remove(const char *name,
void qmp_nbd_server_stop(Error **errp)
{
nbd_export_close_all();
nbd_server_free(nbd_server);
nbd_server = NULL;
}
void qmp_x_nbd_server_add_bitmap(const char *name, const char *bitmap,
bool has_bitmap_export_name,
const char *bitmap_export_name,
Error **errp)
{
NBDExport *exp;
if (!nbd_server) {
error_setg(errp, "NBD server not running");
return;
}
exp = nbd_export_find(name);
if (exp == NULL) {
error_setg(errp, "Export '%s' is not found", name);
return;
}
nbd_export_close_all();
nbd_export_bitmap(exp, bitmap,
has_bitmap_export_name ? bitmap_export_name : bitmap,
errp);
nbd_server_free(nbd_server);
nbd_server = NULL;
}

View File

@ -1339,7 +1339,7 @@ struct BlkActionState {
const BlkActionOps *ops;
JobTxn *block_job_txn;
TransactionProperties *txn_props;
QSIMPLEQ_ENTRY(BlkActionState) entry;
QTAILQ_ENTRY(BlkActionState) entry;
};
/* internal snapshot private data */
@ -1963,7 +1963,7 @@ static void block_dirty_bitmap_add_prepare(BlkActionState *common,
action->has_granularity, action->granularity,
action->has_persistent, action->persistent,
action->has_autoload, action->autoload,
action->has_x_disabled, action->x_disabled,
action->has_disabled, action->disabled,
&local_err);
if (!local_err) {
@ -2048,7 +2048,7 @@ static void block_dirty_bitmap_enable_prepare(BlkActionState *common,
return;
}
action = common->action->u.x_block_dirty_bitmap_enable.data;
action = common->action->u.block_dirty_bitmap_enable.data;
state->bitmap = block_dirty_bitmap_lookup(action->node,
action->name,
NULL,
@ -2089,7 +2089,7 @@ static void block_dirty_bitmap_disable_prepare(BlkActionState *common,
return;
}
action = common->action->u.x_block_dirty_bitmap_disable.data;
action = common->action->u.block_dirty_bitmap_disable.data;
state->bitmap = block_dirty_bitmap_lookup(action->node,
action->name,
NULL,
@ -2119,33 +2119,28 @@ static void block_dirty_bitmap_disable_abort(BlkActionState *common)
}
}
static BdrvDirtyBitmap *do_block_dirty_bitmap_merge(const char *node,
const char *target,
strList *bitmaps,
HBitmap **backup,
Error **errp);
static void block_dirty_bitmap_merge_prepare(BlkActionState *common,
Error **errp)
{
BlockDirtyBitmapMerge *action;
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
BdrvDirtyBitmap *merge_source;
if (action_check_completion_mode(common, errp) < 0) {
return;
}
action = common->action->u.x_block_dirty_bitmap_merge.data;
state->bitmap = block_dirty_bitmap_lookup(action->node,
action->dst_name,
&state->bs,
errp);
if (!state->bitmap) {
return;
}
action = common->action->u.block_dirty_bitmap_merge.data;
merge_source = bdrv_find_dirty_bitmap(state->bs, action->src_name);
if (!merge_source) {
return;
}
bdrv_merge_dirty_bitmap(state->bitmap, merge_source, &state->backup, errp);
state->bitmap = do_block_dirty_bitmap_merge(action->node, action->target,
action->bitmaps, &state->backup,
errp);
}
static void abort_prepare(BlkActionState *common, Error **errp)
@ -2209,17 +2204,17 @@ static const BlkActionOps actions[] = {
.commit = block_dirty_bitmap_free_backup,
.abort = block_dirty_bitmap_restore,
},
[TRANSACTION_ACTION_KIND_X_BLOCK_DIRTY_BITMAP_ENABLE] = {
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ENABLE] = {
.instance_size = sizeof(BlockDirtyBitmapState),
.prepare = block_dirty_bitmap_enable_prepare,
.abort = block_dirty_bitmap_enable_abort,
},
[TRANSACTION_ACTION_KIND_X_BLOCK_DIRTY_BITMAP_DISABLE] = {
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_DISABLE] = {
.instance_size = sizeof(BlockDirtyBitmapState),
.prepare = block_dirty_bitmap_disable_prepare,
.abort = block_dirty_bitmap_disable_abort,
},
[TRANSACTION_ACTION_KIND_X_BLOCK_DIRTY_BITMAP_MERGE] = {
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_MERGE] = {
.instance_size = sizeof(BlockDirtyBitmapState),
.prepare = block_dirty_bitmap_merge_prepare,
.commit = block_dirty_bitmap_free_backup,
@ -2266,8 +2261,8 @@ void qmp_transaction(TransactionActionList *dev_list,
BlkActionState *state, *next;
Error *local_err = NULL;
QSIMPLEQ_HEAD(, BlkActionState) snap_bdrv_states;
QSIMPLEQ_INIT(&snap_bdrv_states);
QTAILQ_HEAD(, BlkActionState) snap_bdrv_states;
QTAILQ_INIT(&snap_bdrv_states);
/* Does this transaction get canceled as a group on failure?
* If not, we don't really need to make a JobTxn.
@ -2298,7 +2293,7 @@ void qmp_transaction(TransactionActionList *dev_list,
state->action = dev_info;
state->block_job_txn = block_job_txn;
state->txn_props = props;
QSIMPLEQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
QTAILQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
state->ops->prepare(state, &local_err);
if (local_err) {
@ -2307,7 +2302,7 @@ void qmp_transaction(TransactionActionList *dev_list,
}
}
QSIMPLEQ_FOREACH(state, &snap_bdrv_states, entry) {
QTAILQ_FOREACH(state, &snap_bdrv_states, entry) {
if (state->ops->commit) {
state->ops->commit(state);
}
@ -2318,13 +2313,13 @@ void qmp_transaction(TransactionActionList *dev_list,
delete_and_fail:
/* failure, and it is all-or-none; roll back all operations */
QSIMPLEQ_FOREACH(state, &snap_bdrv_states, entry) {
QTAILQ_FOREACH_REVERSE(state, &snap_bdrv_states, entry) {
if (state->ops->abort) {
state->ops->abort(state);
}
}
exit:
QSIMPLEQ_FOREACH_SAFE(state, &snap_bdrv_states, entry, next) {
QTAILQ_FOREACH_SAFE(state, &snap_bdrv_states, entry, next) {
if (state->ops->clean) {
state->ops->clean(state);
}
@ -2935,7 +2930,7 @@ void qmp_block_dirty_bitmap_clear(const char *node, const char *name,
bdrv_clear_dirty_bitmap(bitmap, NULL);
}
void qmp_x_block_dirty_bitmap_enable(const char *node, const char *name,
void qmp_block_dirty_bitmap_enable(const char *node, const char *name,
Error **errp)
{
BlockDriverState *bs;
@ -2956,7 +2951,7 @@ void qmp_x_block_dirty_bitmap_enable(const char *node, const char *name,
bdrv_enable_dirty_bitmap(bitmap);
}
void qmp_x_block_dirty_bitmap_disable(const char *node, const char *name,
void qmp_block_dirty_bitmap_disable(const char *node, const char *name,
Error **errp)
{
BlockDriverState *bs;
@ -2977,24 +2972,56 @@ void qmp_x_block_dirty_bitmap_disable(const char *node, const char *name,
bdrv_disable_dirty_bitmap(bitmap);
}
void qmp_x_block_dirty_bitmap_merge(const char *node, const char *dst_name,
const char *src_name, Error **errp)
static BdrvDirtyBitmap *do_block_dirty_bitmap_merge(const char *node,
const char *target,
strList *bitmaps,
HBitmap **backup,
Error **errp)
{
BlockDriverState *bs;
BdrvDirtyBitmap *dst, *src;
BdrvDirtyBitmap *dst, *src, *anon;
strList *lst;
Error *local_err = NULL;
dst = block_dirty_bitmap_lookup(node, dst_name, &bs, errp);
dst = block_dirty_bitmap_lookup(node, target, &bs, errp);
if (!dst) {
return;
return NULL;
}
src = bdrv_find_dirty_bitmap(bs, src_name);
if (!src) {
error_setg(errp, "Dirty bitmap '%s' not found", src_name);
return;
anon = bdrv_create_dirty_bitmap(bs, bdrv_dirty_bitmap_granularity(dst),
NULL, errp);
if (!anon) {
return NULL;
}
bdrv_merge_dirty_bitmap(dst, src, NULL, errp);
for (lst = bitmaps; lst; lst = lst->next) {
src = bdrv_find_dirty_bitmap(bs, lst->value);
if (!src) {
error_setg(errp, "Dirty bitmap '%s' not found", lst->value);
dst = NULL;
goto out;
}
bdrv_merge_dirty_bitmap(anon, src, NULL, &local_err);
if (local_err) {
error_propagate(errp, local_err);
dst = NULL;
goto out;
}
}
/* Merge into dst; dst is unchanged on failure. */
bdrv_merge_dirty_bitmap(dst, anon, backup, errp);
out:
bdrv_release_dirty_bitmap(bs, anon);
return dst;
}
void qmp_block_dirty_bitmap_merge(const char *node, const char *target,
strList *bitmaps, Error **errp)
{
do_block_dirty_bitmap_merge(node, target, bitmaps, NULL, errp);
}
BlockDirtyBitmapSha256 *qmp_x_debug_block_dirty_bitmap_sha256(const char *node,

5
hmp.c
View File

@ -2326,7 +2326,7 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
}
qmp_nbd_server_add(info->value->device, false, NULL,
true, writable, &local_err);
true, writable, false, NULL, &local_err);
if (local_err != NULL) {
qmp_nbd_server_stop(NULL);
@ -2347,7 +2347,8 @@ void hmp_nbd_server_add(Monitor *mon, const QDict *qdict)
bool writable = qdict_get_try_bool(qdict, "writable", false);
Error *local_err = NULL;
qmp_nbd_server_add(device, !!name, name, true, writable, &local_err);
qmp_nbd_server_add(device, !!name, name, true, writable,
false, NULL, &local_err);
hmp_handle_error(mon, &local_err);
}

View File

@ -295,9 +295,10 @@ typedef struct NBDExport NBDExport;
typedef struct NBDClient NBDClient;
NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
uint16_t nbdflags, void (*close)(NBDExport *),
bool writethrough, BlockBackend *on_eject_blk,
Error **errp);
const char *name, const char *description,
const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp);
void nbd_export_close(NBDExport *exp);
void nbd_export_remove(NBDExport *exp, NbdServerRemoveMode mode, Error **errp);
void nbd_export_get(NBDExport *exp);
@ -306,8 +307,6 @@ void nbd_export_put(NBDExport *exp);
BlockBackend *nbd_export_get_blockdev(NBDExport *exp);
NBDExport *nbd_export_find(const char *name);
void nbd_export_set_name(NBDExport *exp, const char *name);
void nbd_export_set_description(NBDExport *exp, const char *description);
void nbd_export_close_all(void);
void nbd_client_new(QIOChannelSocket *sioc,
@ -320,9 +319,6 @@ void nbd_client_put(NBDClient *client);
void nbd_server_start(SocketAddress *addr, const char *tls_creds,
Error **errp);
void nbd_export_bitmap(NBDExport *exp, const char *bitmap,
const char *bitmap_export_name, Error **errp);
/* nbd_read
* Reads @size bytes from @ioc. Returns 0 on success.
*/

View File

@ -1456,9 +1456,10 @@ static void nbd_eject_notifier(Notifier *n, void *data)
}
NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
uint16_t nbdflags, void (*close)(NBDExport *),
bool writethrough, BlockBackend *on_eject_blk,
Error **errp)
const char *name, const char *description,
const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp)
{
AioContext *ctx;
BlockBackend *blk;
@ -1471,6 +1472,7 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
* that BDRV_O_INACTIVE is cleared and the image is ready for write
* access since the export could be available before migration handover.
*/
assert(name);
ctx = bdrv_get_aio_context(bs);
aio_context_acquire(ctx);
bdrv_invalidate_cache(bs, NULL);
@ -1494,6 +1496,8 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
QTAILQ_INIT(&exp->clients);
exp->blk = blk;
exp->dev_offset = dev_offset;
exp->name = g_strdup(name);
exp->description = g_strdup(description);
exp->nbdflags = nbdflags;
exp->size = size < 0 ? blk_getlength(blk) : size;
if (exp->size < 0) {
@ -1503,6 +1507,43 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
}
exp->size -= exp->size % BDRV_SECTOR_SIZE;
if (bitmap) {
BdrvDirtyBitmap *bm = NULL;
BlockDriverState *bs = blk_bs(blk);
while (true) {
bm = bdrv_find_dirty_bitmap(bs, bitmap);
if (bm != NULL || bs->backing == NULL) {
break;
}
bs = bs->backing->bs;
}
if (bm == NULL) {
error_setg(errp, "Bitmap '%s' is not found", bitmap);
goto fail;
}
if ((nbdflags & NBD_FLAG_READ_ONLY) && bdrv_is_writable(bs) &&
bdrv_dirty_bitmap_enabled(bm)) {
error_setg(errp,
"Enabled bitmap '%s' incompatible with readonly export",
bitmap);
goto fail;
}
if (bdrv_dirty_bitmap_user_locked(bm)) {
error_setg(errp, "Bitmap '%s' is in use", bitmap);
goto fail;
}
bdrv_dirty_bitmap_set_qmp_locked(bm, true);
exp->export_bitmap = bm;
exp->export_bitmap_context = g_strdup_printf("qemu:dirty-bitmap:%s",
bitmap);
}
exp->close = close;
exp->ctx = blk_get_aio_context(blk);
blk_add_aio_context_notifier(blk, blk_aio_attached, blk_aio_detach, exp);
@ -1513,10 +1554,14 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
exp->eject_notifier.notify = nbd_eject_notifier;
blk_add_remove_bs_notifier(on_eject_blk, &exp->eject_notifier);
}
QTAILQ_INSERT_TAIL(&exports, exp, next);
nbd_export_get(exp);
return exp;
fail:
blk_unref(blk);
g_free(exp->name);
g_free(exp->description);
g_free(exp);
return NULL;
}
@ -1533,43 +1578,29 @@ NBDExport *nbd_export_find(const char *name)
return NULL;
}
void nbd_export_set_name(NBDExport *exp, const char *name)
{
if (exp->name == name) {
return;
}
nbd_export_get(exp);
if (exp->name != NULL) {
g_free(exp->name);
exp->name = NULL;
QTAILQ_REMOVE(&exports, exp, next);
nbd_export_put(exp);
}
if (name != NULL) {
nbd_export_get(exp);
exp->name = g_strdup(name);
QTAILQ_INSERT_TAIL(&exports, exp, next);
}
nbd_export_put(exp);
}
void nbd_export_set_description(NBDExport *exp, const char *description)
{
g_free(exp->description);
exp->description = g_strdup(description);
}
void nbd_export_close(NBDExport *exp)
{
NBDClient *client, *next;
nbd_export_get(exp);
/*
* TODO: Should we expand QMP NbdServerRemoveNode enum to allow a
* close mode that stops advertising the export to new clients but
* still permits existing clients to run to completion? Because of
* that possibility, nbd_export_close() can be called more than
* once on an export.
*/
QTAILQ_FOREACH_SAFE(client, &exp->clients, next, next) {
client_close(client, true);
}
nbd_export_set_name(exp, NULL);
nbd_export_set_description(exp, NULL);
if (exp->name) {
nbd_export_put(exp);
g_free(exp->name);
exp->name = NULL;
QTAILQ_REMOVE(&exports, exp, next);
}
g_free(exp->description);
exp->description = NULL;
nbd_export_put(exp);
}
@ -2430,44 +2461,3 @@ void nbd_client_new(QIOChannelSocket *sioc,
co = qemu_coroutine_create(nbd_co_client_start, client);
qemu_coroutine_enter(co);
}
void nbd_export_bitmap(NBDExport *exp, const char *bitmap,
const char *bitmap_export_name, Error **errp)
{
BdrvDirtyBitmap *bm = NULL;
BlockDriverState *bs = blk_bs(exp->blk);
if (exp->export_bitmap) {
error_setg(errp, "Export bitmap is already set");
return;
}
while (true) {
bm = bdrv_find_dirty_bitmap(bs, bitmap);
if (bm != NULL || bs->backing == NULL) {
break;
}
bs = bs->backing->bs;
}
if (bm == NULL) {
error_setg(errp, "Bitmap '%s' is not found", bitmap);
return;
}
if (bdrv_dirty_bitmap_enabled(bm)) {
error_setg(errp, "Bitmap '%s' is enabled", bitmap);
return;
}
if (bdrv_dirty_bitmap_user_locked(bm)) {
error_setg(errp, "Bitmap '%s' is in use", bitmap);
return;
}
bdrv_dirty_bitmap_set_qmp_locked(bm, true);
exp->export_bitmap = bm;
exp->export_bitmap_context =
g_strdup_printf("qemu:dirty-bitmap:%s", bitmap_export_name);
}

View File

@ -1806,29 +1806,29 @@
# Currently, all dirty tracking bitmaps are loaded from Qcow2 on
# open.
#
# @x-disabled: the bitmap is created in the disabled state, which means that
# it will not track drive changes. The bitmap may be enabled with
# x-block-dirty-bitmap-enable. Default is false. (Since: 3.0)
# @disabled: the bitmap is created in the disabled state, which means that
# it will not track drive changes. The bitmap may be enabled with
# block-dirty-bitmap-enable. Default is false. (Since: 4.0)
#
# Since: 2.4
##
{ 'struct': 'BlockDirtyBitmapAdd',
'data': { 'node': 'str', 'name': 'str', '*granularity': 'uint32',
'*persistent': 'bool', '*autoload': 'bool', '*x-disabled': 'bool' } }
'*persistent': 'bool', '*autoload': 'bool', '*disabled': 'bool' } }
##
# @BlockDirtyBitmapMerge:
#
# @node: name of device/node which the bitmap is tracking
#
# @dst_name: name of the destination dirty bitmap
# @target: name of the destination dirty bitmap
#
# @src_name: name of the source dirty bitmap
# @bitmaps: name(s) of the source dirty bitmap(s)
#
# Since: 3.0
# Since: 4.0
##
{ 'struct': 'BlockDirtyBitmapMerge',
'data': { 'node': 'str', 'dst_name': 'str', 'src_name': 'str' } }
'data': { 'node': 'str', 'target': 'str', 'bitmaps': ['str'] } }
##
# @block-dirty-bitmap-add:
@ -1899,7 +1899,7 @@
'data': 'BlockDirtyBitmap' }
##
# @x-block-dirty-bitmap-enable:
# @block-dirty-bitmap-enable:
#
# Enables a dirty bitmap so that it will begin tracking disk changes.
#
@ -1907,20 +1907,20 @@
# If @node is not a valid block device, DeviceNotFound
# If @name is not found, GenericError with an explanation
#
# Since: 3.0
# Since: 4.0
#
# Example:
#
# -> { "execute": "x-block-dirty-bitmap-enable",
# -> { "execute": "block-dirty-bitmap-enable",
# "arguments": { "node": "drive0", "name": "bitmap0" } }
# <- { "return": {} }
#
##
{ 'command': 'x-block-dirty-bitmap-enable',
{ 'command': 'block-dirty-bitmap-enable',
'data': 'BlockDirtyBitmap' }
##
# @x-block-dirty-bitmap-disable:
# @block-dirty-bitmap-disable:
#
# Disables a dirty bitmap so that it will stop tracking disk changes.
#
@ -1928,42 +1928,42 @@
# If @node is not a valid block device, DeviceNotFound
# If @name is not found, GenericError with an explanation
#
# Since: 3.0
# Since: 4.0
#
# Example:
#
# -> { "execute": "x-block-dirty-bitmap-disable",
# -> { "execute": "block-dirty-bitmap-disable",
# "arguments": { "node": "drive0", "name": "bitmap0" } }
# <- { "return": {} }
#
##
{ 'command': 'x-block-dirty-bitmap-disable',
{ 'command': 'block-dirty-bitmap-disable',
'data': 'BlockDirtyBitmap' }
##
# @x-block-dirty-bitmap-merge:
# @block-dirty-bitmap-merge:
#
# FIXME: Rename @src_name and @dst_name to src-name and dst-name.
#
# Merge @src_name dirty bitmap to @dst_name dirty bitmap. @src_name dirty
# bitmap is unchanged. On error, @dst_name is unchanged.
# Merge dirty bitmaps listed in @bitmaps to the @target dirty bitmap.
# The @bitmaps dirty bitmaps are unchanged.
# On error, @target is unchanged.
#
# Returns: nothing on success
# If @node is not a valid block device, DeviceNotFound
# If @dst_name or @src_name is not found, GenericError
# If bitmaps has different sizes or granularities, GenericError
# If any bitmap in @bitmaps or @target is not found, GenericError
# If any of the bitmaps have different sizes or granularities,
# GenericError
#
# Since: 3.0
# Since: 4.0
#
# Example:
#
# -> { "execute": "x-block-dirty-bitmap-merge",
# "arguments": { "node": "drive0", "dst_name": "bitmap0",
# "src_name": "bitmap1" } }
# -> { "execute": "block-dirty-bitmap-merge",
# "arguments": { "node": "drive0", "target": "bitmap0",
# "bitmaps": ["bitmap1"] } }
# <- { "return": {} }
#
##
{ 'command': 'x-block-dirty-bitmap-merge',
{ 'command': 'block-dirty-bitmap-merge',
'data': 'BlockDirtyBitmapMerge' }
##

View File

@ -246,6 +246,10 @@
#
# @writable: Whether clients should be able to write to the device via the
# NBD connection (default false).
# @bitmap: Also export the dirty bitmap reachable from @device, so the
# NBD client can use NBD_OPT_SET_META_CONTEXT with
# "qemu:dirty-bitmap:NAME" to inspect the bitmap. (since 4.0)
#
# Returns: error if the server is not running, or export with the same name
# already exists.
@ -253,7 +257,8 @@
# Since: 1.3.0
##
{ 'command': 'nbd-server-add',
'data': {'device': 'str', '*name': 'str', '*writable': 'bool'} }
'data': {'device': 'str', '*name': 'str', '*writable': 'bool',
'*bitmap': 'str' } }
##
# @NbdServerRemoveMode:
@ -296,29 +301,6 @@
{ 'command': 'nbd-server-remove',
'data': {'name': 'str', '*mode': 'NbdServerRemoveMode'} }
##
# @x-nbd-server-add-bitmap:
#
# Expose a dirty bitmap associated with the selected export. The bitmap search
# starts at the device attached to the export, and includes all backing files.
# The exported bitmap is then locked until the NBD export is removed.
#
# @name: Export name.
#
# @bitmap: Bitmap name to search for.
#
# @bitmap-export-name: How the bitmap will be seen by nbd clients
# (default @bitmap)
#
# Note: the client must use NBD_OPT_SET_META_CONTEXT with a query of
# "qemu:dirty-bitmap:NAME" (where NAME matches @bitmap-export-name) to access
# the exposed bitmap.
#
# Since: 3.0
##
{ 'command': 'x-nbd-server-add-bitmap',
'data': {'name': 'str', 'bitmap': 'str', '*bitmap-export-name': 'str'} }
##
# @nbd-server-stop:
#

View File

@ -46,9 +46,9 @@
# - @abort: since 1.6
# - @block-dirty-bitmap-add: since 2.5
# - @block-dirty-bitmap-clear: since 2.5
# - @x-block-dirty-bitmap-enable: since 3.0
# - @x-block-dirty-bitmap-disable: since 3.0
# - @x-block-dirty-bitmap-merge: since 3.1
# - @block-dirty-bitmap-enable: since 4.0
# - @block-dirty-bitmap-disable: since 4.0
# - @block-dirty-bitmap-merge: since 4.0
# - @blockdev-backup: since 2.3
# - @blockdev-snapshot: since 2.5
# - @blockdev-snapshot-internal-sync: since 1.7
@ -62,9 +62,9 @@
'abort': 'Abort',
'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd',
'block-dirty-bitmap-clear': 'BlockDirtyBitmap',
'x-block-dirty-bitmap-enable': 'BlockDirtyBitmap',
'x-block-dirty-bitmap-disable': 'BlockDirtyBitmap',
'x-block-dirty-bitmap-merge': 'BlockDirtyBitmapMerge',
'block-dirty-bitmap-enable': 'BlockDirtyBitmap',
'block-dirty-bitmap-disable': 'BlockDirtyBitmap',
'block-dirty-bitmap-merge': 'BlockDirtyBitmapMerge',
'blockdev-backup': 'BlockdevBackup',
'blockdev-snapshot': 'BlockdevSnapshot',
'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',

View File

@ -61,7 +61,7 @@
#define MBR_SIZE 512
static NBDExport *exp;
static NBDExport *export;
static int verbose;
static char *srcpath;
static SocketAddress *saddr;
@ -95,6 +95,7 @@ static void usage(const char *name)
"Exposing part of the image:\n"
" -o, --offset=OFFSET offset into the image\n"
" -P, --partition=NUM only expose partition NUM\n"
" -B, --bitmap=NAME expose a persistent dirty bitmap\n"
"\n"
"General purpose options:\n"
" --object type,id=ID,... define an object such as 'secret' for providing\n"
@ -335,7 +336,7 @@ static int nbd_can_accept(void)
return state == RUNNING && nb_fds < shared;
}
static void nbd_export_closed(NBDExport *exp)
static void nbd_export_closed(NBDExport *export)
{
assert(state == TERMINATING);
state = TERMINATED;
@ -509,7 +510,7 @@ int main(int argc, char **argv)
off_t fd_size;
QemuOpts *sn_opts = NULL;
const char *sn_id_or_name = NULL;
const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:";
const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:B:";
struct option lopt[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
@ -519,6 +520,7 @@ int main(int argc, char **argv)
{ "offset", required_argument, NULL, 'o' },
{ "read-only", no_argument, NULL, 'r' },
{ "partition", required_argument, NULL, 'P' },
{ "bitmap", required_argument, NULL, 'B' },
{ "connect", required_argument, NULL, 'c' },
{ "disconnect", no_argument, NULL, 'd' },
{ "snapshot", no_argument, NULL, 's' },
@ -558,6 +560,7 @@ int main(int argc, char **argv)
QDict *options = NULL;
const char *export_name = ""; /* Default export name */
const char *export_description = NULL;
const char *bitmap = NULL;
const char *tlscredsid = NULL;
bool imageOpts = false;
bool writethrough = true;
@ -695,6 +698,9 @@ int main(int argc, char **argv)
exit(EXIT_FAILURE);
}
break;
case 'B':
bitmap = optarg;
break;
case 'k':
sockpath = optarg;
if (sockpath[0] != '/') {
@ -1015,10 +1021,10 @@ int main(int argc, char **argv)
}
}
exp = nbd_export_new(bs, dev_offset, fd_size, nbdflags, nbd_export_closed,
writethrough, NULL, &error_fatal);
nbd_export_set_name(exp, export_name);
nbd_export_set_description(exp, export_description);
export = nbd_export_new(bs, dev_offset, fd_size, export_name,
export_description, bitmap, nbdflags,
nbd_export_closed, writethrough, NULL,
&error_fatal);
if (device) {
#if HAVE_NBD_DEVICE
@ -1055,9 +1061,9 @@ int main(int argc, char **argv)
main_loop_wait(false);
if (state == TERMINATE) {
state = TERMINATING;
nbd_export_close(exp);
nbd_export_put(exp);
exp = NULL;
nbd_export_close(export);
nbd_export_put(export);
export = NULL;
}
} while (state != TERMINATED);

View File

@ -45,6 +45,10 @@ auto-detecting
Export the disk as read-only
@item -P, --partition=@var{num}
Only expose partition @var{num}
@item -B, --bitmap=@var{name}
If @var{filename} has a qcow2 persistent bitmap @var{name}, expose
that bitmap via the ``qemu:dirty-bitmap:@var{name}'' context
accessible through NBD_OPT_SET_META_CONTEXT.
@item -s, --snapshot
Use @var{filename} as an external snapshot, create a temporary
file with backing_file=@var{filename}, redirect the write to

View File

@ -26,7 +26,9 @@ from iotests import imgfmt
iotests.verify_image_format(supported_fmts=['qcow2'])
def blockdev_create(vm, options):
result = vm.qmp_log('blockdev-create', job_id='job0', options=options)
result = vm.qmp_log('blockdev-create',
filters=[iotests.filter_qmp_testfiles],
job_id='job0', options=options)
if 'return' in result:
assert result['return'] == {}
@ -52,7 +54,9 @@ with iotests.FilePath('t.qcow2') as disk_path, \
'filename': disk_path,
'size': 0 })
vm.qmp_log('blockdev-add', driver='file', filename=disk_path,
vm.qmp_log('blockdev-add',
filters=[iotests.filter_qmp_testfiles],
driver='file', filename=disk_path,
node_name='imgfile')
blockdev_create(vm, { 'driver': imgfmt,

View File

@ -25,6 +25,7 @@ status=1 # failure is the default!
_cleanup()
{
nbd_server_stop
_cleanup_test_img
_cleanup_qemu
rm -f "$TEST_DIR/nbd"
@ -35,6 +36,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
. ./common.rc
. ./common.filter
. ./common.qemu
. ./common.nbd
_supported_fmt qcow2
_supported_proto file # uses NBD as well
@ -61,6 +63,8 @@ echo "=== Create partially sparse image, then add dirty bitmaps ==="
echo
# Two bitmaps, to contrast granularity issues
# Also note that b will be disabled, while b2 is left enabled, to
# check for read-only interactions
_make_test_img -o cluster_size=4k 4M
$QEMU_IO -c 'w -P 0x11 1M 2M' "$TEST_IMG" | _filter_qemu_io
run_qemu <<EOF
@ -107,26 +111,37 @@ echo
_launch_qemu 2> >(_filter_nbd)
# Intentionally provoke some errors as well, to check error handling
silent=
_send_qemu_cmd $QEMU_HANDLE '{"execute":"qmp_capabilities"}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"blockdev-add",
"arguments":{"driver":"qcow2", "node-name":"n",
"file":{"driver":"file", "filename":"'"$TEST_IMG"'"}}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-block-dirty-bitmap-disable",
_send_qemu_cmd $QEMU_HANDLE '{"execute":"block-dirty-bitmap-disable",
"arguments":{"node":"n", "name":"b"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-block-dirty-bitmap-disable",
"arguments":{"node":"n", "name":"b2"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n"}}' "error" # Attempt add without server
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix",
"data":{"path":"'"$TEST_DIR/nbd"'"}}}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix",
"data":{"path":"'"$TEST_DIR/nbd"1'"}}}}' "error" # Attempt second server
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-nbd-server-add-bitmap",
"arguments":{"name":"n", "bitmap":"b"}}' "return"
"arguments":{"device":"n", "bitmap":"b"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"x-nbd-server-add-bitmap",
"arguments":{"name":"n2", "bitmap":"b2"}}' "return"
"arguments":{"device":"nosuch"}}' "error" # Attempt to export missing node
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n"}}' "error" # Attempt to export same name twice
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2",
"bitmap":"b2"}}' "error" # enabled vs. read-only
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2",
"bitmap":"b3"}}' "error" # Missing bitmap
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2", "writable":true,
"bitmap":"b2"}}' "return"
echo
echo "=== Contrast normal status to large granularity dirty-bitmap ==="
@ -150,16 +165,33 @@ $QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map
echo
echo "=== End NBD server ==="
echo "=== End qemu NBD server ==="
echo
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-remove",
"arguments":{"name":"n"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-remove",
"arguments":{"name":"n2"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-remove",
"arguments":{"name":"n2"}}' "error" # Attempt duplicate clean
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-stop"}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-stop"}' "error" # Again
_send_qemu_cmd $QEMU_HANDLE '{"execute":"quit"}' "return"
echo
echo "=== Use qemu-nbd as server ==="
echo
nbd_server_start_unix_socket -r -f $IMGFMT -B b "$TEST_IMG"
IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b" | _filter_qemu_img_map
nbd_server_start_unix_socket -f $IMGFMT -B b2 "$TEST_IMG"
IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map
# success, all done
echo '*** done'
rm -f $seq.full

View File

@ -27,11 +27,14 @@ wrote 2097152/2097152 bytes at offset 2097152
{"return": {}}
{"return": {}}
{"return": {}}
{"error": {"class": "GenericError", "desc": "NBD server not running"}}
{"return": {}}
{"error": {"class": "GenericError", "desc": "NBD server already running"}}
{"return": {}}
{"return": {}}
{"return": {}}
{"return": {}}
{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
{"error": {"class": "GenericError", "desc": "NBD server already has export named 'n'"}}
{"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}}
{"error": {"class": "GenericError", "desc": "Bitmap 'b3' is not found"}}
{"return": {}}
=== Contrast normal status to large granularity dirty-bitmap ===
@ -58,10 +61,22 @@ read 2097152/2097152 bytes at offset 2097152
{ "start": 1024, "length": 2096128, "depth": 0, "zero": false, "data": true},
{ "start": 2097152, "length": 2097152, "depth": 0, "zero": false, "data": false}]
=== End NBD server ===
=== End qemu NBD server ===
{"return": {}}
{"return": {}}
{"error": {"class": "GenericError", "desc": "Export 'n2' is not found"}}
{"return": {}}
{"error": {"class": "GenericError", "desc": "NBD server not running"}}
{"return": {}}
=== Use qemu-nbd as server ===
[{ "start": 0, "length": 65536, "depth": 0, "zero": false, "data": false},
{ "start": 65536, "length": 2031616, "depth": 0, "zero": false, "data": true},
{ "start": 2097152, "length": 2097152, "depth": 0, "zero": false, "data": false}]
[{ "start": 0, "length": 512, "depth": 0, "zero": false, "data": true},
{ "start": 512, "length": 512, "depth": 0, "zero": false, "data": false},
{ "start": 1024, "length": 2096128, "depth": 0, "zero": false, "data": true},
{ "start": 2097152, "length": 2097152, "depth": 0, "zero": false, "data": false}]
*** done

161
tests/qemu-iotests/236 Executable file
View File

@ -0,0 +1,161 @@
#!/usr/bin/env python
#
# Test bitmap merges.
#
# Copyright (c) 2018 John Snow for 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/>.
#
# owner=jsnow@redhat.com
import iotests
from iotests import log
iotests.verify_image_format(supported_fmts=['generic'])
size = 64 * 1024 * 1024
granularity = 64 * 1024
patterns = [("0x5d", "0", "64k"),
("0xd5", "1M", "64k"),
("0xdc", "32M", "64k"),
("0xcd", "0x3ff0000", "64k")] # 64M - 64K
overwrite = [("0xab", "0", "64k"), # Full overwrite
("0xad", "0x00f8000", "64k"), # Partial-left (1M-32K)
("0x1d", "0x2008000", "64k"), # Partial-right (32M+32K)
("0xea", "0x3fe0000", "64k")] # Adjacent-left (64M - 128K)
def query_bitmaps(vm):
res = vm.qmp("query-block")
return { "bitmaps": { device['device']: device.get('dirty-bitmaps', []) for
device in res['return'] } }
with iotests.FilePath('img') as img_path, \
iotests.VM() as vm:
log('--- Preparing image & VM ---\n')
iotests.qemu_img_create('-f', iotests.imgfmt, img_path, str(size))
vm.add_drive(img_path)
vm.launch()
log('\n--- Adding preliminary bitmaps A & B ---\n')
vm.qmp_log("block-dirty-bitmap-add", node="drive0",
name="bitmapA", granularity=granularity)
vm.qmp_log("block-dirty-bitmap-add", node="drive0",
name="bitmapB", granularity=granularity)
# Dirties 4 clusters. count=262144
log('\n--- Emulating writes ---\n')
for p in patterns:
cmd = "write -P%s %s %s" % p
log(cmd)
log(vm.hmp_qemu_io("drive0", cmd))
log(query_bitmaps(vm), indent=2)
log('\n--- Submitting & Aborting Transaction ---\n')
vm.qmp_log("transaction", indent=2, actions=[
{ "type": "block-dirty-bitmap-disable",
"data": { "node": "drive0", "name": "bitmapB" }},
{ "type": "block-dirty-bitmap-add",
"data": { "node": "drive0", "name": "bitmapC",
"granularity": granularity }},
{ "type": "block-dirty-bitmap-clear",
"data": { "node": "drive0", "name": "bitmapA" }},
{ "type": "abort", "data": {}}
])
log(query_bitmaps(vm), indent=2)
log('\n--- Disabling B & Adding C ---\n')
vm.qmp_log("transaction", indent=2, actions=[
{ "type": "block-dirty-bitmap-disable",
"data": { "node": "drive0", "name": "bitmapB" }},
{ "type": "block-dirty-bitmap-add",
"data": { "node": "drive0", "name": "bitmapC",
"granularity": granularity }},
# Purely extraneous, but test that it works:
{ "type": "block-dirty-bitmap-disable",
"data": { "node": "drive0", "name": "bitmapC" }},
{ "type": "block-dirty-bitmap-enable",
"data": { "node": "drive0", "name": "bitmapC" }},
])
log('\n--- Emulating further writes ---\n')
# Dirties 6 clusters, 3 of which are new in contrast to "A".
# A = 64 * 1024 * (4 + 3) = 458752
# C = 64 * 1024 * 6 = 393216
for p in overwrite:
cmd = "write -P%s %s %s" % p
log(cmd)
log(vm.hmp_qemu_io("drive0", cmd))
log('\n--- Disabling A & C ---\n')
vm.qmp_log("transaction", indent=2, actions=[
{ "type": "block-dirty-bitmap-disable",
"data": { "node": "drive0", "name": "bitmapA" }},
{ "type": "block-dirty-bitmap-disable",
"data": { "node": "drive0", "name": "bitmapC" }}
])
# A: 7 clusters
# B: 4 clusters
# C: 6 clusters
log(query_bitmaps(vm), indent=2)
log('\n--- Submitting & Aborting Merge Transaction ---\n')
vm.qmp_log("transaction", indent=2, actions=[
{ "type": "block-dirty-bitmap-add",
"data": { "node": "drive0", "name": "bitmapD",
"disabled": True, "granularity": granularity }},
{ "type": "block-dirty-bitmap-merge",
"data": { "node": "drive0", "target": "bitmapD",
"bitmaps": ["bitmapB", "bitmapC"] }},
{ "type": "abort", "data": {}}
])
log(query_bitmaps(vm), indent=2)
log('\n--- Creating D as a merge of B & C ---\n')
# Good hygiene: create a disabled bitmap as a merge target.
vm.qmp_log("transaction", indent=2, actions=[
{ "type": "block-dirty-bitmap-add",
"data": { "node": "drive0", "name": "bitmapD",
"disabled": True, "granularity": granularity }},
{ "type": "block-dirty-bitmap-merge",
"data": { "node": "drive0", "target": "bitmapD",
"bitmaps": ["bitmapB", "bitmapC"] }}
])
# A and D should now both have 7 clusters apiece.
# B and C remain unchanged with 4 and 6 respectively.
log(query_bitmaps(vm), indent=2)
# A and D should be equivalent.
# Some formats round the size of the disk, so don't print the checksums.
check_a = vm.qmp('x-debug-block-dirty-bitmap-sha256',
node="drive0", name="bitmapA")['return']['sha256']
check_d = vm.qmp('x-debug-block-dirty-bitmap-sha256',
node="drive0", name="bitmapD")['return']['sha256']
assert(check_a == check_d)
log('\n--- Removing bitmaps A, B, C, and D ---\n')
vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapA")
vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapB")
vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapC")
vm.qmp_log("block-dirty-bitmap-remove", node="drive0", name="bitmapD")
log('\n--- Final Query ---\n')
log(query_bitmaps(vm), indent=2)
log('\n--- Done ---\n')
vm.shutdown()

351
tests/qemu-iotests/236.out Normal file
View File

@ -0,0 +1,351 @@
--- Preparing image & VM ---
--- Adding preliminary bitmaps A & B ---
{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmapA", "node": "drive0"}}
{"return": {}}
{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmapB", "node": "drive0"}}
{"return": {}}
--- Emulating writes ---
write -P0x5d 0 64k
{"return": ""}
write -P0xd5 1M 64k
{"return": ""}
write -P0xdc 32M 64k
{"return": ""}
write -P0xcd 0x3ff0000 64k
{"return": ""}
{
"bitmaps": {
"drive0": [
{
"count": 262144,
"granularity": 65536,
"name": "bitmapB",
"status": "active"
},
{
"count": 262144,
"granularity": 65536,
"name": "bitmapA",
"status": "active"
}
]
}
}
--- Submitting & Aborting Transaction ---
{
"execute": "transaction",
"arguments": {
"actions": [
{
"data": {
"node": "drive0",
"name": "bitmapB"
},
"type": "block-dirty-bitmap-disable"
},
{
"data": {
"node": "drive0",
"name": "bitmapC",
"granularity": 65536
},
"type": "block-dirty-bitmap-add"
},
{
"data": {
"node": "drive0",
"name": "bitmapA"
},
"type": "block-dirty-bitmap-clear"
},
{
"data": {},
"type": "abort"
}
]
}
}
{
"error": {
"class": "GenericError",
"desc": "Transaction aborted using Abort action"
}
}
{
"bitmaps": {
"drive0": [
{
"count": 262144,
"granularity": 65536,
"name": "bitmapB",
"status": "active"
},
{
"count": 262144,
"granularity": 65536,
"name": "bitmapA",
"status": "active"
}
]
}
}
--- Disabling B & Adding C ---
{
"execute": "transaction",
"arguments": {
"actions": [
{
"data": {
"node": "drive0",
"name": "bitmapB"
},
"type": "block-dirty-bitmap-disable"
},
{
"data": {
"node": "drive0",
"name": "bitmapC",
"granularity": 65536
},
"type": "block-dirty-bitmap-add"
},
{
"data": {
"node": "drive0",
"name": "bitmapC"
},
"type": "block-dirty-bitmap-disable"
},
{
"data": {
"node": "drive0",
"name": "bitmapC"
},
"type": "block-dirty-bitmap-enable"
}
]
}
}
{
"return": {}
}
--- Emulating further writes ---
write -P0xab 0 64k
{"return": ""}
write -P0xad 0x00f8000 64k
{"return": ""}
write -P0x1d 0x2008000 64k
{"return": ""}
write -P0xea 0x3fe0000 64k
{"return": ""}
--- Disabling A & C ---
{
"execute": "transaction",
"arguments": {
"actions": [
{
"data": {
"node": "drive0",
"name": "bitmapA"
},
"type": "block-dirty-bitmap-disable"
},
{
"data": {
"node": "drive0",
"name": "bitmapC"
},
"type": "block-dirty-bitmap-disable"
}
]
}
}
{
"return": {}
}
{
"bitmaps": {
"drive0": [
{
"count": 393216,
"granularity": 65536,
"name": "bitmapC",
"status": "disabled"
},
{
"count": 262144,
"granularity": 65536,
"name": "bitmapB",
"status": "disabled"
},
{
"count": 458752,
"granularity": 65536,
"name": "bitmapA",
"status": "disabled"
}
]
}
}
--- Submitting & Aborting Merge Transaction ---
{
"execute": "transaction",
"arguments": {
"actions": [
{
"data": {
"node": "drive0",
"disabled": true,
"name": "bitmapD",
"granularity": 65536
},
"type": "block-dirty-bitmap-add"
},
{
"data": {
"node": "drive0",
"target": "bitmapD",
"bitmaps": [
"bitmapB",
"bitmapC"
]
},
"type": "block-dirty-bitmap-merge"
},
{
"data": {},
"type": "abort"
}
]
}
}
{
"error": {
"class": "GenericError",
"desc": "Transaction aborted using Abort action"
}
}
{
"bitmaps": {
"drive0": [
{
"count": 393216,
"granularity": 65536,
"name": "bitmapC",
"status": "disabled"
},
{
"count": 262144,
"granularity": 65536,
"name": "bitmapB",
"status": "disabled"
},
{
"count": 458752,
"granularity": 65536,
"name": "bitmapA",
"status": "disabled"
}
]
}
}
--- Creating D as a merge of B & C ---
{
"execute": "transaction",
"arguments": {
"actions": [
{
"data": {
"node": "drive0",
"disabled": true,
"name": "bitmapD",
"granularity": 65536
},
"type": "block-dirty-bitmap-add"
},
{
"data": {
"node": "drive0",
"target": "bitmapD",
"bitmaps": [
"bitmapB",
"bitmapC"
]
},
"type": "block-dirty-bitmap-merge"
}
]
}
}
{
"return": {}
}
{
"bitmaps": {
"drive0": [
{
"count": 458752,
"granularity": 65536,
"name": "bitmapD",
"status": "disabled"
},
{
"count": 393216,
"granularity": 65536,
"name": "bitmapC",
"status": "disabled"
},
{
"count": 262144,
"granularity": 65536,
"name": "bitmapB",
"status": "disabled"
},
{
"count": 458752,
"granularity": 65536,
"name": "bitmapA",
"status": "disabled"
}
]
}
}
--- Removing bitmaps A, B, C, and D ---
{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapA", "node": "drive0"}}
{"return": {}}
{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapB", "node": "drive0"}}
{"return": {}}
{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapC", "node": "drive0"}}
{"return": {}}
{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmapD", "node": "drive0"}}
{"return": {}}
--- Final Query ---
{
"bitmaps": {
"drive0": []
}
}
--- Done ---

View File

@ -233,3 +233,4 @@
233 auto quick
234 auto quick migration
235 auto quick
236 auto quick

View File

@ -30,6 +30,7 @@
import logging
import atexit
import io
from collections import OrderedDict
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
import qtest
@ -63,7 +64,7 @@
debug = False
luks_default_secret_object = 'secret,id=keysec0,data=' + \
os.environ['IMGKEYSECRET']
os.environ.get('IMGKEYSECRET', '')
luks_default_key_secret_opt = 'key-secret=keysec0'
@ -75,6 +76,16 @@ def qemu_img(*args):
sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
return exitcode
def ordered_kwargs(kwargs):
# kwargs prior to 3.6 are not ordered, so:
od = OrderedDict()
for k, v in sorted(kwargs.items()):
if isinstance(v, dict):
od[k] = ordered_kwargs(v)
else:
od[k] = v
return od
def qemu_img_create(*args):
args = list(args)
@ -235,10 +246,36 @@ def filter_qmp_event(event):
event['timestamp']['microseconds'] = 'USECS'
return event
def filter_qmp(qmsg, filter_fn):
'''Given a string filter, filter a QMP object's values.
filter_fn takes a (key, value) pair.'''
# Iterate through either lists or dicts;
if isinstance(qmsg, list):
items = enumerate(qmsg)
else:
items = qmsg.items()
for k, v in items:
if isinstance(v, list) or isinstance(v, dict):
qmsg[k] = filter_qmp(v, filter_fn)
else:
qmsg[k] = filter_fn(k, v)
return qmsg
def filter_testfiles(msg):
prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
return msg.replace(prefix, 'TEST_DIR/PID-')
def filter_qmp_testfiles(qmsg):
def _filter(key, value):
if key == 'filename' or key == 'backing-file':
return filter_testfiles(value)
return value
return filter_qmp(qmsg, _filter)
def filter_generated_node_ids(msg):
return re.sub("#block[0-9]+", "NODE_NAME", msg)
def filter_img_info(output, filename):
lines = []
for line in output.split('\n'):
@ -251,11 +288,18 @@ def filter_img_info(output, filename):
lines.append(line)
return '\n'.join(lines)
def log(msg, filters=[]):
def log(msg, filters=[], indent=None):
'''Logs either a string message or a JSON serializable message (like QMP).
If indent is provided, JSON serializable messages are pretty-printed.'''
for flt in filters:
msg = flt(msg)
if type(msg) is dict or type(msg) is list:
print(json.dumps(msg, sort_keys=True))
if isinstance(msg, dict) or isinstance(msg, list):
# Python < 3.4 needs to know not to add whitespace when pretty-printing:
separators = (', ', ': ') if indent is None else (',', ': ')
# Don't sort if it's already sorted
do_sort = not isinstance(msg, OrderedDict)
print(json.dumps(msg, sort_keys=do_sort,
indent=indent, separators=separators))
else:
print(msg)
@ -444,12 +488,14 @@ def get_qmp_events_filtered(self, wait=True):
result.append(filter_qmp_event(ev))
return result
def qmp_log(self, cmd, filters=[filter_testfiles], **kwargs):
logmsg = '{"execute": "%s", "arguments": %s}' % \
(cmd, json.dumps(kwargs, sort_keys=True))
log(logmsg, filters)
def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
full_cmd = OrderedDict((
("execute", cmd),
("arguments", ordered_kwargs(kwargs))
))
log(full_cmd, filters, indent=indent)
result = self.qmp(cmd, **kwargs)
log(json.dumps(result, sort_keys=True), filters)
log(result, filters, indent=indent)
return result
def run_job(self, job, auto_finalize=True, auto_dismiss=False):