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:
commit
44ba601063
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
107
blockdev.c
107
blockdev.c
@ -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
5
hmp.c
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
136
nbd/server.c
136
nbd/server.c
@ -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);
|
||||
}
|
||||
|
@ -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' }
|
||||
|
||||
##
|
||||
|
@ -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:
|
||||
#
|
||||
|
@ -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',
|
||||
|
26
qemu-nbd.c
26
qemu-nbd.c
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
161
tests/qemu-iotests/236
Executable 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
351
tests/qemu-iotests/236.out
Normal 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 ---
|
||||
|
@ -233,3 +233,4 @@
|
||||
233 auto quick
|
||||
234 auto quick migration
|
||||
235 auto quick
|
||||
236 auto quick
|
||||
|
@ -30,6 +30,7 @@ import signal
|
||||
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 @@ socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
|
||||
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 @@ class VM(qtest.QEMUQtestMachine):
|
||||
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):
|
||||
|
Loading…
Reference in New Issue
Block a user