[PATCH 0/5] qemu: migration: Always try to migrate block dirty bitmaps
Try to migrate them always when they are not present in the destination image. While this has no change if they are present and on shared filesystem it does allow migrating them when the filesystem is not shared but we're not migrating storage. This is a special case for qcow2 overlay with data_file feature. The code uses the existing logic for offering and accepting bitmaps, just fixed a bug and enables the code for all kinds of migration. See 5/5 for more in-depth description. Peter Krempa (5): qemublocktest: Iterate all nodenames in 'testQemuDetectBitmaps' qemu: monitor: Detect list of bitmaps from 'qcow2' format specific data qemuMigrationDstPrepareAnyBlockDirtyBitmaps: Fix check for existing bitmaps qemu: migration: Always offer block dirty bitmaps during migration qemuMigrationDstPrepareAnyBlockDirtyBitmaps: Always consider offered bitmaps src/qemu/qemu_migration.c | 54 ++++++------------- src/qemu/qemu_monitor.h | 4 ++ src/qemu/qemu_monitor_json.c | 17 ++++++ tests/qemublocktest.c | 35 ++++++------ tests/qemublocktestdata/bitmap/basic.out | 1 + tests/qemublocktestdata/bitmap/empty.out | 1 + .../bitmap/snapshots-internal.out | 7 +++ tests/qemublocktestdata/bitmap/snapshots.out | 10 ++++ tests/qemublocktestdata/bitmap/synthetic.out | 9 ++++ 9 files changed, 86 insertions(+), 52 deletions(-) -- 2.52.0
From: Peter Krempa <pkrempa@redhat.com> Rather than looking for 30 specific nodenames (via a loop) iterate everything in the hash table (in a sorted order). This simplifies the code and provides more test outputs on previously-ignored nodenames. The listing of internal snapshots in the output was also missing a newline, which would now cause problems with multiple images reproted. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- tests/qemublocktest.c | 24 +++++++------------ tests/qemublocktestdata/bitmap/basic.out | 1 + tests/qemublocktestdata/bitmap/empty.out | 1 + .../bitmap/snapshots-internal.out | 7 ++++++ tests/qemublocktestdata/bitmap/snapshots.out | 5 ++++ tests/qemublocktestdata/bitmap/synthetic.out | 5 ++++ 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index 47746207cc..51d9268cdd 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -575,17 +575,15 @@ testQemuImageCreate(const void *opaque) static const char *bitmapDetectPrefix = "qemublocktestdata/bitmap/"; -static void -testQemuDetectBitmapsWorker(GHashTable *nodedata, +static int +testQemuDetectBitmapsWorker(void *payload, const char *nodename, - virBuffer *buf) + void *opaque) { - qemuBlockNamedNodeData *data; + qemuBlockNamedNodeData *data = payload; + virBuffer *buf = opaque; size_t i; - if (!(data = virHashLookup(nodedata, nodename))) - return; - virBufferAsprintf(buf, "%s:\n", nodename); if (data->qcow2v2) virBufferAddLit(buf, " qcow2 v2\n"); @@ -617,9 +615,12 @@ testQemuDetectBitmapsWorker(GHashTable *nodedata, virBufferAsprintf(buf, " '%s'%s", (const char *) n->key, vms); } + + virBufferAddLit(buf, "\n"); } virBufferAdjustIndent(buf, -1); + return 0; } @@ -632,7 +633,6 @@ testQemuDetectBitmaps(const void *opaque) g_autofree char *actual = NULL; g_autofree char *expectpath = NULL; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; - size_t i; expectpath = g_strdup_printf("%s/%s%s.out", abs_srcdir, bitmapDetectPrefix, name); @@ -646,13 +646,7 @@ testQemuDetectBitmaps(const void *opaque) return -1; } - /* we detect for the first 30 nodenames for simplicity */ - for (i = 0; i < 30; i++) { - g_autofree char *nodename = g_strdup_printf("libvirt-%zu-format", i); - - testQemuDetectBitmapsWorker(nodedata, nodename, &buf); - } - + virHashForEachSorted(nodedata, testQemuDetectBitmapsWorker, &buf); actual = virBufferContentAndReset(&buf); return virTestCompareToFile(actual, expectpath); diff --git a/tests/qemublocktestdata/bitmap/basic.out b/tests/qemublocktestdata/bitmap/basic.out index 5c4c35b3f0..b96ffe3d39 100644 --- a/tests/qemublocktestdata/bitmap/basic.out +++ b/tests/qemublocktestdata/bitmap/basic.out @@ -4,3 +4,4 @@ libvirt-1-format: c: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 b: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-1-storage: diff --git a/tests/qemublocktestdata/bitmap/empty.out b/tests/qemublocktestdata/bitmap/empty.out index 3787cbd354..c9a5be4f07 100644 --- a/tests/qemublocktestdata/bitmap/empty.out +++ b/tests/qemublocktestdata/bitmap/empty.out @@ -1 +1,2 @@ libvirt-1-format: +libvirt-1-storage: diff --git a/tests/qemublocktestdata/bitmap/snapshots-internal.out b/tests/qemublocktestdata/bitmap/snapshots-internal.out index dbb3cfded4..cf7bde96a5 100644 --- a/tests/qemublocktestdata/bitmap/snapshots-internal.out +++ b/tests/qemublocktestdata/bitmap/snapshots-internal.out @@ -1,2 +1,9 @@ libvirt-1-format: internal snapshots: '1727868651'(*) '1727872064'(*) +libvirt-1-storage: +libvirt-2-storage: +libvirt-pflash0-format: +libvirt-pflash0-storage: +libvirt-pflash1-format: + internal snapshots: '1727868651' '1727872064' +libvirt-pflash1-storage: diff --git a/tests/qemublocktestdata/bitmap/snapshots.out b/tests/qemublocktestdata/bitmap/snapshots.out index 24ca27e4d8..29c586be7e 100644 --- a/tests/qemublocktestdata/bitmap/snapshots.out +++ b/tests/qemublocktestdata/bitmap/snapshots.out @@ -4,16 +4,21 @@ libvirt-1-format: b: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 c: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 current: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-1-storage: libvirt-2-format: c: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 b: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 d: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-2-storage: libvirt-3-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 b: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 c: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-3-storage: libvirt-4-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-4-storage: libvirt-5-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-5-storage: diff --git a/tests/qemublocktestdata/bitmap/synthetic.out b/tests/qemublocktestdata/bitmap/synthetic.out index 45423903a0..2f4ae2b217 100644 --- a/tests/qemublocktestdata/bitmap/synthetic.out +++ b/tests/qemublocktestdata/bitmap/synthetic.out @@ -6,12 +6,17 @@ libvirt-1-format: top-inactive: record:0 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 top-transient: record:1 busy:0 persist:0 inconsist:0 gran:65536 dirty:0 top-transient-inactive: record:0 busy:0 persist:0 inconsist:0 gran:65536 dirty:0 +libvirt-1-storage: libvirt-2-format: d: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-2-storage: libvirt-3-format: b: record:1 busy:0 persist:0 inconsist:0 gran:65536 dirty:0 c: record:0 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 d: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-3-storage: libvirt-4-format: +libvirt-4-storage: libvirt-5-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 +libvirt-5-storage: -- 2.52.0
From: Peter Krempa <pkrempa@redhat.com> We currently probe dirty block tracking bitmaps by looking at the loaded ones ('dirty-bitmaps'). Unfortunately those may not yet be populated on incoming migration when the image was not yet activated, but we need to know which ones are stored in the image so that we don't migrate those explicitly, which would fail. Load the list of bitmaps in a qcow2 image from the format specific data, which is already loaded at that point. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.h | 4 ++++ src/qemu/qemu_monitor_json.c | 17 +++++++++++++++++ tests/qemublocktest.c | 11 +++++++++++ tests/qemublocktestdata/bitmap/snapshots.out | 5 +++++ tests/qemublocktestdata/bitmap/synthetic.out | 4 ++++ 5 files changed, 41 insertions(+) diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index d096f474c1..041aa7bc12 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -893,8 +893,12 @@ struct _qemuBlockNamedNodeData { unsigned long long capacity; unsigned long long physical; + /* Information about change block tracking bitmaps which are active and loaded */ qemuBlockNamedNodeDataBitmap **bitmaps; size_t nbitmaps; + /* With qcow2 we have also a separate list of bitmaps present in the image + * but not yet activated, which happens when starting qemu during migration */ + char **qcow2bitmaps; /* hash table indexed by snapshot name containing data about snapshots * (qemuBlockNamedNodeDataSnapshot) */ diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index a602b1e65b..f345741207 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2730,6 +2730,7 @@ qemuMonitorJSONBlockNamedNodeDataFree(qemuBlockNamedNodeData *data) qemuMonitorJSONBlockNamedNodeDataBitmapFree(data->bitmaps[i]); g_clear_pointer(&data->snapshots, g_hash_table_unref); g_free(data->bitmaps); + g_strfreev(data->qcow2bitmaps); g_free(data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockNamedNodeData, qemuMonitorJSONBlockNamedNodeDataFree); @@ -2854,6 +2855,9 @@ qemuMonitorJSONBlockGetNamedNodeDataWorker(size_t pos G_GNUC_UNUSED, virJSONValue *qcow2props = virJSONValueObjectGetObject(format_specific, "data"); if (qcow2props) { + virJSONValue *bmp; + size_t nbmp; + if (STREQ_NULLABLE(virJSONValueObjectGetString(qcow2props, "compat"), "0.10")) ent->qcow2v2 = true; @@ -2862,6 +2866,19 @@ qemuMonitorJSONBlockGetNamedNodeDataWorker(size_t pos G_GNUC_UNUSED, ignore_value(virJSONValueObjectGetBoolean(qcow2props, "data-file-raw", &ent->qcow2dataFileRaw)); + + if ((bmp = virJSONValueObjectGetArray(qcow2props, "bitmaps")) && + ((nbmp = virJSONValueArraySize(bmp)) > 0)) { + size_t i; + + ent->qcow2bitmaps = g_new0(char *, nbmp + 1); + + for (i = 0; i < nbmp; i++) { + virJSONValue *b = virJSONValueArrayGet(bmp, i); + + ent->qcow2bitmaps[i] = g_strdup(virJSONValueObjectGetString(b, "name")); + } + } } } diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index 51d9268cdd..18ec90edf5 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -600,6 +600,17 @@ testQemuDetectBitmapsWorker(void *payload, bitmap->granularity, bitmap->dirtybytes); } + if (data->qcow2bitmaps) { + char **b; + + virBufferAddLit(buf, "qcow2 bitmaps:"); + + for (b = data->qcow2bitmaps; *b; b++) + virBufferAsprintf(buf, " %s", *b); + + virBufferAddLit(buf, "\n"); + } + if (data->snapshots) { g_autofree virHashKeyValuePair *snaps = virHashGetItems(data->snapshots, NULL, true); virHashKeyValuePair *n; diff --git a/tests/qemublocktestdata/bitmap/snapshots.out b/tests/qemublocktestdata/bitmap/snapshots.out index 29c586be7e..dedd77465c 100644 --- a/tests/qemublocktestdata/bitmap/snapshots.out +++ b/tests/qemublocktestdata/bitmap/snapshots.out @@ -4,21 +4,26 @@ libvirt-1-format: b: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 c: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 current: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: current c b a d libvirt-1-storage: libvirt-2-format: c: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 b: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 d: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: d a b c libvirt-2-storage: libvirt-3-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 b: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 c: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: c b a libvirt-3-storage: libvirt-4-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: a libvirt-4-storage: libvirt-5-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: a libvirt-5-storage: diff --git a/tests/qemublocktestdata/bitmap/synthetic.out b/tests/qemublocktestdata/bitmap/synthetic.out index 2f4ae2b217..0a47a90107 100644 --- a/tests/qemublocktestdata/bitmap/synthetic.out +++ b/tests/qemublocktestdata/bitmap/synthetic.out @@ -6,17 +6,21 @@ libvirt-1-format: top-inactive: record:0 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 top-transient: record:1 busy:0 persist:0 inconsist:0 gran:65536 dirty:0 top-transient-inactive: record:0 busy:0 persist:0 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: current libvirt-1-storage: libvirt-2-format: d: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: d libvirt-2-storage: libvirt-3-format: b: record:1 busy:0 persist:0 inconsist:0 gran:65536 dirty:0 c: record:0 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 d: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: c b libvirt-3-storage: libvirt-4-format: libvirt-4-storage: libvirt-5-format: a: record:1 busy:0 persist:1 inconsist:0 gran:65536 dirty:0 + qcow2 bitmaps: a libvirt-5-storage: -- 2.52.0
From: Peter Krempa <pkrempa@redhat.com> On incoming migration qemu doesn't load bitmaps into memory (which makes them available under the 'dirty-bitmaps' field which we parse as the 'bitmaps' array in 'qemuBlockNamedNodeData') until ater actually resuming CPUs, thus the check for existing bitmaps never actually worked. We need to check the 'qcow2bitmaps' field instead which is populated from the qcow2 headers prior to activating the image. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_migration.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 6dd022163b..a502515d93 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -3230,6 +3230,8 @@ qemuMigrationDstPrepareAnyBlockDirtyBitmaps(virDomainObj *vm, qemuBlockNamedNodeData *nodedata; GSList *nextbitmap; + VIR_DEBUG("offer migrate bitmaps for '%s'", disk->target); + if (!(nodedata = virHashLookup(blockNamedNodeData, disk->nodename))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to find data for block node '%1$s'"), @@ -3246,18 +3248,14 @@ qemuMigrationDstPrepareAnyBlockDirtyBitmaps(virDomainObj *vm, for (nextbitmap = disk->bitmaps; nextbitmap; nextbitmap = nextbitmap->next) { qemuMigrationBlockDirtyBitmapsDiskBitmap *bitmap = nextbitmap->data; - size_t k; /* don't migrate into existing bitmaps */ - for (k = 0; k < nodedata->nbitmaps; k++) { - if (STREQ(bitmap->bitmapname, nodedata->bitmaps[k]->name)) { - bitmap->skip = true; - break; - } - } + if (nodedata->qcow2bitmaps) + bitmap->skip = g_strv_contains((const char **) nodedata->qcow2bitmaps, bitmap->bitmapname); + + VIR_DEBUG("offer migrate bitmap '%s' disk '%s' -> skip: '%d'", + bitmap->bitmapname, disk->target, bitmap->skip); - if (bitmap->skip) - continue; } } -- 2.52.0
On a Wednesday in 2026, Peter Krempa via Devel wrote:
From: Peter Krempa <pkrempa@redhat.com>
On incoming migration qemu doesn't load bitmaps into memory (which makes them available under the 'dirty-bitmaps' field which we parse as the 'bitmaps' array in 'qemuBlockNamedNodeData') until ater actually
after Jano
resuming CPUs, thus the check for existing bitmaps never actually worked.
We need to check the 'qcow2bitmaps' field instead which is populated from the qcow2 headers prior to activating the image.
Signed-off-by: Peter Krempa <pkrempa@redhat.com>
From: Peter Krempa <pkrempa@redhat.com> Until now block dirty bitmaps were offered to destination only if non-shared storage migration was enabled. Upcoming patches will want to support it also in cases when storage is shared but the destination has a qcow2 overlay using the 'data_file' feature where the qcow2 overlay is not actually shared. To support that we'll now always offer bitmaps for migration. The destination can then decide (using existing logic) to pick only the ones that are not present in the image on destination, which is how it was supposed to work even now. The patch removes all the flag checks and simply offers bitmaps in any case. The overhead incurred by this is one 'query-named-block-nodes' call to qemu. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_migration.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index a502515d93..14617a59f4 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -2583,16 +2583,13 @@ qemuMigrationAnyConnectionClosed(virDomainObj *vm, * qemuMigrationSrcBeginPhaseBlockDirtyBitmaps: * @mig: migration cookie struct * @vm: domain object - * @migrate_disks: disks which are being migrated - * @nmigrage_disks: number of @migrate_disks * * Enumerates block dirty bitmaps on disks which will undergo storage migration * and fills them into @mig to be offered to the destination. */ static int qemuMigrationSrcBeginPhaseBlockDirtyBitmaps(qemuMigrationCookie *mig, - virDomainObj *vm, - const char **migrate_disks) + virDomainObj *vm) { GSList *disks = NULL; @@ -2614,9 +2611,6 @@ qemuMigrationSrcBeginPhaseBlockDirtyBitmaps(qemuMigrationCookie *mig, if (!nodedata) continue; - if (!qemuMigrationAnyCopyDisk(diskdef, migrate_disks)) - continue; - for (j = 0; j < nodedata->nbitmaps; j++) { qemuMigrationBlockDirtyBitmapsDiskBitmap *bitmap; @@ -2683,7 +2677,6 @@ qemuMigrationSrcBeginXML(virDomainObj *vm, char **cookieout, int *cookieoutlen, unsigned int cookieFlags, - const char **migrate_disks, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; @@ -2699,8 +2692,7 @@ qemuMigrationSrcBeginXML(virDomainObj *vm, if (!(mig = qemuMigrationCookieNew(vm->def, priv->origname))) return NULL; - if (cookieFlags & QEMU_MIGRATION_COOKIE_NBD && - qemuMigrationSrcBeginPhaseBlockDirtyBitmaps(mig, vm, migrate_disks) < 0) + if (qemuMigrationSrcBeginPhaseBlockDirtyBitmaps(mig, vm) < 0) return NULL; if (qemuMigrationCookieFormat(mig, driver, vm, @@ -2882,8 +2874,7 @@ qemuMigrationSrcBeginPhase(virQEMUDriver *driver, return NULL; return qemuMigrationSrcBeginXML(vm, xmlin, - cookieout, cookieoutlen, cookieFlags, - migrate_disks, flags); + cookieout, cookieoutlen, cookieFlags, flags); } @@ -2972,8 +2963,7 @@ qemuMigrationSrcBeginResume(virDomainObj *vm, return NULL; } - return qemuMigrationSrcBeginXML(vm, xmlin, - cookieout, cookieoutlen, 0, NULL, flags); + return qemuMigrationSrcBeginXML(vm, xmlin, cookieout, cookieoutlen, 0, flags); } @@ -4754,7 +4744,6 @@ qemuMigrationSrcRunPrepareBlockDirtyBitmaps(virDomainObj *vm, /* For VIR_MIGRATE_NON_SHARED_INC we can migrate the bitmaps directly, * otherwise we must create merged bitmaps from the whole chain */ - if (!(flags & VIR_MIGRATE_NON_SHARED_INC) && qemuMigrationSrcRunPrepareBlockDirtyBitmapsMerge(vm, mig) < 0) return -1; @@ -4945,7 +4934,7 @@ qemuMigrationSrcRun(virQEMUDriver *driver, VIR_AUTOCLOSE fd = -1; unsigned long restore_max_bandwidth = priv->migMaxBandwidth; virErrorPtr orig_err = NULL; - unsigned int cookieFlags = 0; + unsigned int cookieFlags = QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS; bool abort_on_error = !!(flags & VIR_MIGRATE_ABORT_ON_ERROR); bool storageMigration = flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC); bool cancel = false; @@ -4969,10 +4958,8 @@ qemuMigrationSrcRun(virQEMUDriver *driver, storageMigration = qemuMigrationHasAnyStorageMigrationDisks(vm->def, migrate_disks); - if (storageMigration) { + if (storageMigration) cookieFlags |= QEMU_MIGRATION_COOKIE_NBD; - cookieFlags |= QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS; - } if (virLockManagerPluginUsesState(driver->lockManager) && !cookieout) { @@ -5006,8 +4993,7 @@ qemuMigrationSrcRun(virQEMUDriver *driver, cookiein, cookieinlen, cookieFlags | QEMU_MIGRATION_COOKIE_GRAPHICS | - QEMU_MIGRATION_COOKIE_CAPS | - QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS); + QEMU_MIGRATION_COOKIE_CAPS); if (!mig) goto error; -- 2.52.0
From: Peter Krempa <pkrempa@redhat.com> Consider bitmaps for incoming migration regardless of non-shared storage flag. When bitmaps are offered from the source, consult the local image if the bitmap is present and if not accept migration. Migration of bitmaps which exist in the qcow2 metadata is skipped because qemu rejects such setup (although handles it correctly in case of shared storage setup; see below). This allows bitmap propagation for cases when the qcow2 image is not actually shared between destinations but the data is (using the data_file feature). At the same time this preserves existing bitmap handling semantics for other cases. Specifically qemu, in case of shared storage properly propagates the bitmap which was already recorded in the qcow2 metadata on disk even if libvirt doesn't instruct migration, yet tolerates migration instruction if the file is not yet recorded in the on-disk metadata. In both cases the contents are preserved correctly. When storage is not shared (which includes even cases when we migrate it via NBD) it's expected that the bitmaps don't exist on the destination and thus all will be picked for migration. We can also infer that this wasn't ever a problem by the fact that the code skipping migration of existing bitmaps was broken until recently, and qemu would refuse such config. I've tested all the above scenarios including verifying that the resulting bitmaps capture dirtied regions before and after migration. For testing this the following command is useful: virsh qemu-monitor-command --domain DOMNAME --hmp 'qemu-io -d /machine/peripheral/virtio-disk0/virtio-backend "write -P 0xcc 4M 1M"' Which simulates a write from the guest side without the need to interact with the guest OS. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_migration.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 14617a59f4..b4d2e27370 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -3186,7 +3186,6 @@ qemuMigrationDstPrepare(virQEMUDriver *driver, * @vm: domain object * @mig: migration cookie * @migParams: migration parameters - * @flags: migration flags * * Checks whether block dirty bitmaps offered by the migration source are * to be migrated (e.g. they don't exist, the destination is compatible etc) @@ -3197,16 +3196,13 @@ qemuMigrationDstPrepare(virQEMUDriver *driver, static int qemuMigrationDstPrepareAnyBlockDirtyBitmaps(virDomainObj *vm, qemuMigrationCookie *mig, - qemuMigrationParams *migParams, - unsigned int flags) + qemuMigrationParams *migParams) { g_autoptr(virJSONValue) mapping = NULL; g_autoptr(GHashTable) blockNamedNodeData = NULL; GSList *nextdisk; - if (!mig->nbd || - !mig->blockDirtyBitmaps || - !(flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC))) + if (!mig->blockDirtyBitmaps) return 0; if (qemuMigrationCookieBlockDirtyBitmapsMatchDisks(vm->def, mig->blockDirtyBitmaps) < 0) @@ -3353,7 +3349,7 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver, goto error; } - if (qemuMigrationDstPrepareAnyBlockDirtyBitmaps(vm, mig, migParams, flags) < 0) + if (qemuMigrationDstPrepareAnyBlockDirtyBitmaps(vm, mig, migParams) < 0) goto error; if (qemuMigrationParamsCheck(vm, VIR_ASYNC_JOB_MIGRATION_IN, migParams, -- 2.52.0
On a Wednesday in 2026, Peter Krempa via Devel wrote:
Try to migrate them always when they are not present in the destination image. While this has no change if they are present and on shared filesystem it does allow migrating them when the filesystem is not shared but we're not migrating storage. This is a special case for qcow2 overlay with data_file feature.
The code uses the existing logic for offering and accepting bitmaps, just fixed a bug and enables the code for all kinds of migration.
See 5/5 for more in-depth description.
Peter Krempa (5): qemublocktest: Iterate all nodenames in 'testQemuDetectBitmaps' qemu: monitor: Detect list of bitmaps from 'qcow2' format specific data qemuMigrationDstPrepareAnyBlockDirtyBitmaps: Fix check for existing bitmaps qemu: migration: Always offer block dirty bitmaps during migration qemuMigrationDstPrepareAnyBlockDirtyBitmaps: Always consider offered bitmaps
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano
participants (2)
-
Ján Tomko -
Peter Krempa