bitmaps patches for 2020-07-27
- Improve handling of various post-copy bitmap migration scenarios. A lost bitmap should merely mean that the next backup must be full rather than incremental, rather than abruptly breaking the entire guest migration. - Associated iotest improvements -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAl8fPRkACgkQp6FrSiUn Q2qanQf/dRTrqZ7/hs8aENySf44o0dBzOLZr+FBcrqEj2sd0c6jPzV2X5CVtnA1v gBgKJJGLpti3mSeNQDbaXZIQrsesBAuxvJsc6vZ9npDCdMYnK/qPE3Zfw1bx12qR cb39ba28P4izgs216h92ZACtUewnvjkxyJgN7zfmCJdNcwZINMUItAS183tSbQjn n39Wb7a+umsRgV9HQv/6cXlQIPqFMyAOl5kkzV3evuw7EBoHFnNq4cjPrUnjkqiD xf2pcSomaedYd37SpvoH57JxfL3z/90OBcuXhFvbqFk4FgQ63rJ32nRve2ZbIDI0 XPbohnYjYoFv6Xs/jtTzctZCbZ+jTg== =1dmz -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/ericb/tags/pull-bitmaps-2020-07-27' into staging bitmaps patches for 2020-07-27 - Improve handling of various post-copy bitmap migration scenarios. A lost bitmap should merely mean that the next backup must be full rather than incremental, rather than abruptly breaking the entire guest migration. - Associated iotest improvements # gpg: Signature made Mon 27 Jul 2020 21:46:17 BST # gpg: using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A # gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full] # gpg: aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full] # gpg: aka "[jpeg image of size 6874]" [full] # Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2 F3AA A7A1 6B4A 2527 436A * remotes/ericb/tags/pull-bitmaps-2020-07-27: (24 commits) migration: Fix typos in bitmap migration comments iotests: Adjust which migration tests are quick qemu-iotests/199: add source-killed case to bitmaps postcopy qemu-iotests/199: add early shutdown case to bitmaps postcopy qemu-iotests/199: check persistent bitmaps qemu-iotests/199: prepare for new test-cases addition migration/savevm: don't worry if bitmap migration postcopy failed migration/block-dirty-bitmap: cancel migration on shutdown migration/block-dirty-bitmap: relax error handling in incoming part migration/block-dirty-bitmap: keep bitmap state for all bitmaps migration/block-dirty-bitmap: simplify dirty_bitmap_load_complete migration/block-dirty-bitmap: rename finish_lock to just lock migration/block-dirty-bitmap: refactor state global variables migration/block-dirty-bitmap: move mutex init to dirty_bitmap_mig_init migration/block-dirty-bitmap: rename dirty_bitmap_mig_cleanup migration/block-dirty-bitmap: rename state structure types migration/block-dirty-bitmap: fix dirty_bitmap_mig_before_vm_start qemu-iotests/199: increase postcopy period qemu-iotests/199: change discard patterns qemu-iotests/199: improve performance: set bitmap by discard ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
2649915121
@ -66,7 +66,7 @@ typedef struct {
|
|||||||
} QEMU_PACKED QCowExtension;
|
} QEMU_PACKED QCowExtension;
|
||||||
|
|
||||||
#define QCOW2_EXT_MAGIC_END 0
|
#define QCOW2_EXT_MAGIC_END 0
|
||||||
#define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA
|
#define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xe2792aca
|
||||||
#define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
|
#define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
|
||||||
#define QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x0537be77
|
#define QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x0537be77
|
||||||
#define QCOW2_EXT_MAGIC_BITMAPS 0x23852875
|
#define QCOW2_EXT_MAGIC_BITMAPS 0x23852875
|
||||||
|
@ -231,7 +231,7 @@ be stored. Each extension has a structure like the following:
|
|||||||
|
|
||||||
Byte 0 - 3: Header extension type:
|
Byte 0 - 3: Header extension type:
|
||||||
0x00000000 - End of the header extension area
|
0x00000000 - End of the header extension area
|
||||||
0xE2792ACA - Backing file format name string
|
0xe2792aca - Backing file format name string
|
||||||
0x6803f857 - Feature name table
|
0x6803f857 - Feature name table
|
||||||
0x23852875 - Bitmaps extension
|
0x23852875 - Bitmaps extension
|
||||||
0x0537be77 - Full disk encryption header pointer
|
0x0537be77 - Full disk encryption header pointer
|
||||||
|
@ -97,26 +97,28 @@
|
|||||||
|
|
||||||
#define DIRTY_BITMAP_MIG_START_FLAG_ENABLED 0x01
|
#define DIRTY_BITMAP_MIG_START_FLAG_ENABLED 0x01
|
||||||
#define DIRTY_BITMAP_MIG_START_FLAG_PERSISTENT 0x02
|
#define DIRTY_BITMAP_MIG_START_FLAG_PERSISTENT 0x02
|
||||||
/* 0x04 was "AUTOLOAD" flags on elder versions, no it is ignored */
|
/* 0x04 was "AUTOLOAD" flags on older versions, now it is ignored */
|
||||||
#define DIRTY_BITMAP_MIG_START_FLAG_RESERVED_MASK 0xf8
|
#define DIRTY_BITMAP_MIG_START_FLAG_RESERVED_MASK 0xf8
|
||||||
|
|
||||||
typedef struct DirtyBitmapMigBitmapState {
|
/* State of one bitmap during save process */
|
||||||
|
typedef struct SaveBitmapState {
|
||||||
/* Written during setup phase. */
|
/* Written during setup phase. */
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
const char *node_name;
|
const char *node_name;
|
||||||
BdrvDirtyBitmap *bitmap;
|
BdrvDirtyBitmap *bitmap;
|
||||||
uint64_t total_sectors;
|
uint64_t total_sectors;
|
||||||
uint64_t sectors_per_chunk;
|
uint64_t sectors_per_chunk;
|
||||||
QSIMPLEQ_ENTRY(DirtyBitmapMigBitmapState) entry;
|
QSIMPLEQ_ENTRY(SaveBitmapState) entry;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
|
|
||||||
/* For bulk phase. */
|
/* For bulk phase. */
|
||||||
bool bulk_completed;
|
bool bulk_completed;
|
||||||
uint64_t cur_sector;
|
uint64_t cur_sector;
|
||||||
} DirtyBitmapMigBitmapState;
|
} SaveBitmapState;
|
||||||
|
|
||||||
typedef struct DirtyBitmapMigState {
|
/* State of the dirty bitmap migration (DBM) during save process */
|
||||||
QSIMPLEQ_HEAD(, DirtyBitmapMigBitmapState) dbms_list;
|
typedef struct DBMSaveState {
|
||||||
|
QSIMPLEQ_HEAD(, SaveBitmapState) dbms_list;
|
||||||
|
|
||||||
bool bulk_completed;
|
bool bulk_completed;
|
||||||
bool no_bitmaps;
|
bool no_bitmaps;
|
||||||
@ -124,30 +126,44 @@ typedef struct DirtyBitmapMigState {
|
|||||||
/* for send_bitmap_bits() */
|
/* for send_bitmap_bits() */
|
||||||
BlockDriverState *prev_bs;
|
BlockDriverState *prev_bs;
|
||||||
BdrvDirtyBitmap *prev_bitmap;
|
BdrvDirtyBitmap *prev_bitmap;
|
||||||
} DirtyBitmapMigState;
|
} DBMSaveState;
|
||||||
|
|
||||||
typedef struct DirtyBitmapLoadState {
|
typedef struct LoadBitmapState {
|
||||||
|
BlockDriverState *bs;
|
||||||
|
BdrvDirtyBitmap *bitmap;
|
||||||
|
bool migrated;
|
||||||
|
bool enabled;
|
||||||
|
} LoadBitmapState;
|
||||||
|
|
||||||
|
/* State of the dirty bitmap migration (DBM) during load process */
|
||||||
|
typedef struct DBMLoadState {
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
char node_name[256];
|
char node_name[256];
|
||||||
char bitmap_name[256];
|
char bitmap_name[256];
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
BdrvDirtyBitmap *bitmap;
|
BdrvDirtyBitmap *bitmap;
|
||||||
} DirtyBitmapLoadState;
|
|
||||||
|
|
||||||
static DirtyBitmapMigState dirty_bitmap_mig_state;
|
bool before_vm_start_handled; /* set in dirty_bitmap_mig_before_vm_start */
|
||||||
|
|
||||||
typedef struct DirtyBitmapLoadBitmapState {
|
/*
|
||||||
BlockDriverState *bs;
|
* cancelled
|
||||||
BdrvDirtyBitmap *bitmap;
|
* Incoming migration is cancelled for some reason. That means that we
|
||||||
bool migrated;
|
* still should read our chunks from migration stream, to not affect other
|
||||||
} DirtyBitmapLoadBitmapState;
|
* migration objects (like RAM), but just ignore them and do not touch any
|
||||||
static GSList *enabled_bitmaps;
|
* bitmaps or nodes.
|
||||||
QemuMutex finish_lock;
|
*/
|
||||||
|
bool cancelled;
|
||||||
|
|
||||||
void init_dirty_bitmap_incoming_migration(void)
|
GSList *bitmaps;
|
||||||
{
|
QemuMutex lock; /* protect bitmaps */
|
||||||
qemu_mutex_init(&finish_lock);
|
} DBMLoadState;
|
||||||
}
|
|
||||||
|
typedef struct DBMState {
|
||||||
|
DBMSaveState save;
|
||||||
|
DBMLoadState load;
|
||||||
|
} DBMState;
|
||||||
|
|
||||||
|
static DBMState dbm_state;
|
||||||
|
|
||||||
static uint32_t qemu_get_bitmap_flags(QEMUFile *f)
|
static uint32_t qemu_get_bitmap_flags(QEMUFile *f)
|
||||||
{
|
{
|
||||||
@ -164,27 +180,27 @@ static uint32_t qemu_get_bitmap_flags(QEMUFile *f)
|
|||||||
|
|
||||||
static void qemu_put_bitmap_flags(QEMUFile *f, uint32_t flags)
|
static void qemu_put_bitmap_flags(QEMUFile *f, uint32_t flags)
|
||||||
{
|
{
|
||||||
/* The code currently do not send flags more than one byte */
|
/* The code currently does not send flags as more than one byte */
|
||||||
assert(!(flags & (0xffffff00 | DIRTY_BITMAP_MIG_EXTRA_FLAGS)));
|
assert(!(flags & (0xffffff00 | DIRTY_BITMAP_MIG_EXTRA_FLAGS)));
|
||||||
|
|
||||||
qemu_put_byte(f, flags);
|
qemu_put_byte(f, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_bitmap_header(QEMUFile *f, DirtyBitmapMigBitmapState *dbms,
|
static void send_bitmap_header(QEMUFile *f, DBMSaveState *s,
|
||||||
uint32_t additional_flags)
|
SaveBitmapState *dbms, uint32_t additional_flags)
|
||||||
{
|
{
|
||||||
BlockDriverState *bs = dbms->bs;
|
BlockDriverState *bs = dbms->bs;
|
||||||
BdrvDirtyBitmap *bitmap = dbms->bitmap;
|
BdrvDirtyBitmap *bitmap = dbms->bitmap;
|
||||||
uint32_t flags = additional_flags;
|
uint32_t flags = additional_flags;
|
||||||
trace_send_bitmap_header_enter();
|
trace_send_bitmap_header_enter();
|
||||||
|
|
||||||
if (bs != dirty_bitmap_mig_state.prev_bs) {
|
if (bs != s->prev_bs) {
|
||||||
dirty_bitmap_mig_state.prev_bs = bs;
|
s->prev_bs = bs;
|
||||||
flags |= DIRTY_BITMAP_MIG_FLAG_DEVICE_NAME;
|
flags |= DIRTY_BITMAP_MIG_FLAG_DEVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bitmap != dirty_bitmap_mig_state.prev_bitmap) {
|
if (bitmap != s->prev_bitmap) {
|
||||||
dirty_bitmap_mig_state.prev_bitmap = bitmap;
|
s->prev_bitmap = bitmap;
|
||||||
flags |= DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME;
|
flags |= DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,19 +215,22 @@ static void send_bitmap_header(QEMUFile *f, DirtyBitmapMigBitmapState *dbms,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_bitmap_start(QEMUFile *f, DirtyBitmapMigBitmapState *dbms)
|
static void send_bitmap_start(QEMUFile *f, DBMSaveState *s,
|
||||||
|
SaveBitmapState *dbms)
|
||||||
{
|
{
|
||||||
send_bitmap_header(f, dbms, DIRTY_BITMAP_MIG_FLAG_START);
|
send_bitmap_header(f, s, dbms, DIRTY_BITMAP_MIG_FLAG_START);
|
||||||
qemu_put_be32(f, bdrv_dirty_bitmap_granularity(dbms->bitmap));
|
qemu_put_be32(f, bdrv_dirty_bitmap_granularity(dbms->bitmap));
|
||||||
qemu_put_byte(f, dbms->flags);
|
qemu_put_byte(f, dbms->flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_bitmap_complete(QEMUFile *f, DirtyBitmapMigBitmapState *dbms)
|
static void send_bitmap_complete(QEMUFile *f, DBMSaveState *s,
|
||||||
|
SaveBitmapState *dbms)
|
||||||
{
|
{
|
||||||
send_bitmap_header(f, dbms, DIRTY_BITMAP_MIG_FLAG_COMPLETE);
|
send_bitmap_header(f, s, dbms, DIRTY_BITMAP_MIG_FLAG_COMPLETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_bitmap_bits(QEMUFile *f, DirtyBitmapMigBitmapState *dbms,
|
static void send_bitmap_bits(QEMUFile *f, DBMSaveState *s,
|
||||||
|
SaveBitmapState *dbms,
|
||||||
uint64_t start_sector, uint32_t nr_sectors)
|
uint64_t start_sector, uint32_t nr_sectors)
|
||||||
{
|
{
|
||||||
/* align for buffer_is_zero() */
|
/* align for buffer_is_zero() */
|
||||||
@ -236,7 +255,7 @@ static void send_bitmap_bits(QEMUFile *f, DirtyBitmapMigBitmapState *dbms,
|
|||||||
|
|
||||||
trace_send_bitmap_bits(flags, start_sector, nr_sectors, buf_size);
|
trace_send_bitmap_bits(flags, start_sector, nr_sectors, buf_size);
|
||||||
|
|
||||||
send_bitmap_header(f, dbms, flags);
|
send_bitmap_header(f, s, dbms, flags);
|
||||||
|
|
||||||
qemu_put_be64(f, start_sector);
|
qemu_put_be64(f, start_sector);
|
||||||
qemu_put_be32(f, nr_sectors);
|
qemu_put_be32(f, nr_sectors);
|
||||||
@ -255,12 +274,12 @@ static void send_bitmap_bits(QEMUFile *f, DirtyBitmapMigBitmapState *dbms,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
/* Called with iothread lock taken. */
|
||||||
static void dirty_bitmap_mig_cleanup(void)
|
static void dirty_bitmap_do_save_cleanup(DBMSaveState *s)
|
||||||
{
|
{
|
||||||
DirtyBitmapMigBitmapState *dbms;
|
SaveBitmapState *dbms;
|
||||||
|
|
||||||
while ((dbms = QSIMPLEQ_FIRST(&dirty_bitmap_mig_state.dbms_list)) != NULL) {
|
while ((dbms = QSIMPLEQ_FIRST(&s->dbms_list)) != NULL) {
|
||||||
QSIMPLEQ_REMOVE_HEAD(&dirty_bitmap_mig_state.dbms_list, entry);
|
QSIMPLEQ_REMOVE_HEAD(&s->dbms_list, entry);
|
||||||
bdrv_dirty_bitmap_set_busy(dbms->bitmap, false);
|
bdrv_dirty_bitmap_set_busy(dbms->bitmap, false);
|
||||||
bdrv_unref(dbms->bs);
|
bdrv_unref(dbms->bs);
|
||||||
g_free(dbms);
|
g_free(dbms);
|
||||||
@ -268,10 +287,11 @@ static void dirty_bitmap_mig_cleanup(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
/* Called with iothread lock taken. */
|
||||||
static int add_bitmaps_to_list(BlockDriverState *bs, const char *bs_name)
|
static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs,
|
||||||
|
const char *bs_name)
|
||||||
{
|
{
|
||||||
BdrvDirtyBitmap *bitmap;
|
BdrvDirtyBitmap *bitmap;
|
||||||
DirtyBitmapMigBitmapState *dbms;
|
SaveBitmapState *dbms;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
|
||||||
FOR_EACH_DIRTY_BITMAP(bs, bitmap) {
|
FOR_EACH_DIRTY_BITMAP(bs, bitmap) {
|
||||||
@ -309,7 +329,7 @@ static int add_bitmaps_to_list(BlockDriverState *bs, const char *bs_name)
|
|||||||
bdrv_ref(bs);
|
bdrv_ref(bs);
|
||||||
bdrv_dirty_bitmap_set_busy(bitmap, true);
|
bdrv_dirty_bitmap_set_busy(bitmap, true);
|
||||||
|
|
||||||
dbms = g_new0(DirtyBitmapMigBitmapState, 1);
|
dbms = g_new0(SaveBitmapState, 1);
|
||||||
dbms->bs = bs;
|
dbms->bs = bs;
|
||||||
dbms->node_name = bs_name;
|
dbms->node_name = bs_name;
|
||||||
dbms->bitmap = bitmap;
|
dbms->bitmap = bitmap;
|
||||||
@ -323,25 +343,24 @@ static int add_bitmaps_to_list(BlockDriverState *bs, const char *bs_name)
|
|||||||
dbms->flags |= DIRTY_BITMAP_MIG_START_FLAG_PERSISTENT;
|
dbms->flags |= DIRTY_BITMAP_MIG_START_FLAG_PERSISTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSIMPLEQ_INSERT_TAIL(&dirty_bitmap_mig_state.dbms_list,
|
QSIMPLEQ_INSERT_TAIL(&s->dbms_list, dbms, entry);
|
||||||
dbms, entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
/* Called with iothread lock taken. */
|
||||||
static int init_dirty_bitmap_migration(void)
|
static int init_dirty_bitmap_migration(DBMSaveState *s)
|
||||||
{
|
{
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
DirtyBitmapMigBitmapState *dbms;
|
SaveBitmapState *dbms;
|
||||||
GHashTable *handled_by_blk = g_hash_table_new(NULL, NULL);
|
GHashTable *handled_by_blk = g_hash_table_new(NULL, NULL);
|
||||||
BlockBackend *blk;
|
BlockBackend *blk;
|
||||||
|
|
||||||
dirty_bitmap_mig_state.bulk_completed = false;
|
s->bulk_completed = false;
|
||||||
dirty_bitmap_mig_state.prev_bs = NULL;
|
s->prev_bs = NULL;
|
||||||
dirty_bitmap_mig_state.prev_bitmap = NULL;
|
s->prev_bitmap = NULL;
|
||||||
dirty_bitmap_mig_state.no_bitmaps = false;
|
s->no_bitmaps = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use blockdevice name for direct (or filtered) children of named block
|
* Use blockdevice name for direct (or filtered) children of named block
|
||||||
@ -370,7 +389,7 @@ static int init_dirty_bitmap_migration(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bs && bs->drv && !bs->drv->is_filter) {
|
if (bs && bs->drv && !bs->drv->is_filter) {
|
||||||
if (add_bitmaps_to_list(bs, name)) {
|
if (add_bitmaps_to_list(s, bs, name)) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
g_hash_table_add(handled_by_blk, bs);
|
g_hash_table_add(handled_by_blk, bs);
|
||||||
@ -382,18 +401,18 @@ static int init_dirty_bitmap_migration(void)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (add_bitmaps_to_list(bs, bdrv_get_node_name(bs))) {
|
if (add_bitmaps_to_list(s, bs, bdrv_get_node_name(bs))) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* unset migration flags here, to not roll back it */
|
/* unset migration flags here, to not roll back it */
|
||||||
QSIMPLEQ_FOREACH(dbms, &dirty_bitmap_mig_state.dbms_list, entry) {
|
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
|
||||||
bdrv_dirty_bitmap_skip_store(dbms->bitmap, true);
|
bdrv_dirty_bitmap_skip_store(dbms->bitmap, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (QSIMPLEQ_EMPTY(&dirty_bitmap_mig_state.dbms_list)) {
|
if (QSIMPLEQ_EMPTY(&s->dbms_list)) {
|
||||||
dirty_bitmap_mig_state.no_bitmaps = true;
|
s->no_bitmaps = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_hash_table_destroy(handled_by_blk);
|
g_hash_table_destroy(handled_by_blk);
|
||||||
@ -402,18 +421,19 @@ static int init_dirty_bitmap_migration(void)
|
|||||||
|
|
||||||
fail:
|
fail:
|
||||||
g_hash_table_destroy(handled_by_blk);
|
g_hash_table_destroy(handled_by_blk);
|
||||||
dirty_bitmap_mig_cleanup();
|
dirty_bitmap_do_save_cleanup(s);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with no lock taken. */
|
/* Called with no lock taken. */
|
||||||
static void bulk_phase_send_chunk(QEMUFile *f, DirtyBitmapMigBitmapState *dbms)
|
static void bulk_phase_send_chunk(QEMUFile *f, DBMSaveState *s,
|
||||||
|
SaveBitmapState *dbms)
|
||||||
{
|
{
|
||||||
uint32_t nr_sectors = MIN(dbms->total_sectors - dbms->cur_sector,
|
uint32_t nr_sectors = MIN(dbms->total_sectors - dbms->cur_sector,
|
||||||
dbms->sectors_per_chunk);
|
dbms->sectors_per_chunk);
|
||||||
|
|
||||||
send_bitmap_bits(f, dbms, dbms->cur_sector, nr_sectors);
|
send_bitmap_bits(f, s, dbms, dbms->cur_sector, nr_sectors);
|
||||||
|
|
||||||
dbms->cur_sector += nr_sectors;
|
dbms->cur_sector += nr_sectors;
|
||||||
if (dbms->cur_sector >= dbms->total_sectors) {
|
if (dbms->cur_sector >= dbms->total_sectors) {
|
||||||
@ -422,61 +442,66 @@ static void bulk_phase_send_chunk(QEMUFile *f, DirtyBitmapMigBitmapState *dbms)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Called with no lock taken. */
|
/* Called with no lock taken. */
|
||||||
static void bulk_phase(QEMUFile *f, bool limit)
|
static void bulk_phase(QEMUFile *f, DBMSaveState *s, bool limit)
|
||||||
{
|
{
|
||||||
DirtyBitmapMigBitmapState *dbms;
|
SaveBitmapState *dbms;
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(dbms, &dirty_bitmap_mig_state.dbms_list, entry) {
|
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
|
||||||
while (!dbms->bulk_completed) {
|
while (!dbms->bulk_completed) {
|
||||||
bulk_phase_send_chunk(f, dbms);
|
bulk_phase_send_chunk(f, s, dbms);
|
||||||
if (limit && qemu_file_rate_limit(f)) {
|
if (limit && qemu_file_rate_limit(f)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dirty_bitmap_mig_state.bulk_completed = true;
|
s->bulk_completed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for SaveVMHandlers */
|
/* for SaveVMHandlers */
|
||||||
static void dirty_bitmap_save_cleanup(void *opaque)
|
static void dirty_bitmap_save_cleanup(void *opaque)
|
||||||
{
|
{
|
||||||
dirty_bitmap_mig_cleanup();
|
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||||
|
|
||||||
|
dirty_bitmap_do_save_cleanup(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dirty_bitmap_save_iterate(QEMUFile *f, void *opaque)
|
static int dirty_bitmap_save_iterate(QEMUFile *f, void *opaque)
|
||||||
{
|
{
|
||||||
|
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||||
|
|
||||||
trace_dirty_bitmap_save_iterate(migration_in_postcopy());
|
trace_dirty_bitmap_save_iterate(migration_in_postcopy());
|
||||||
|
|
||||||
if (migration_in_postcopy() && !dirty_bitmap_mig_state.bulk_completed) {
|
if (migration_in_postcopy() && !s->bulk_completed) {
|
||||||
bulk_phase(f, true);
|
bulk_phase(f, s, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
|
qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
|
||||||
|
|
||||||
return dirty_bitmap_mig_state.bulk_completed;
|
return s->bulk_completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with iothread lock taken. */
|
/* Called with iothread lock taken. */
|
||||||
|
|
||||||
static int dirty_bitmap_save_complete(QEMUFile *f, void *opaque)
|
static int dirty_bitmap_save_complete(QEMUFile *f, void *opaque)
|
||||||
{
|
{
|
||||||
DirtyBitmapMigBitmapState *dbms;
|
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||||
|
SaveBitmapState *dbms;
|
||||||
trace_dirty_bitmap_save_complete_enter();
|
trace_dirty_bitmap_save_complete_enter();
|
||||||
|
|
||||||
if (!dirty_bitmap_mig_state.bulk_completed) {
|
if (!s->bulk_completed) {
|
||||||
bulk_phase(f, false);
|
bulk_phase(f, s, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(dbms, &dirty_bitmap_mig_state.dbms_list, entry) {
|
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
|
||||||
send_bitmap_complete(f, dbms);
|
send_bitmap_complete(f, s, dbms);
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
|
qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
|
||||||
|
|
||||||
trace_dirty_bitmap_save_complete_finish();
|
trace_dirty_bitmap_save_complete_finish();
|
||||||
|
|
||||||
dirty_bitmap_mig_cleanup();
|
dirty_bitmap_save_cleanup(opaque);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,12 +511,13 @@ static void dirty_bitmap_save_pending(QEMUFile *f, void *opaque,
|
|||||||
uint64_t *res_compatible,
|
uint64_t *res_compatible,
|
||||||
uint64_t *res_postcopy_only)
|
uint64_t *res_postcopy_only)
|
||||||
{
|
{
|
||||||
DirtyBitmapMigBitmapState *dbms;
|
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||||
|
SaveBitmapState *dbms;
|
||||||
uint64_t pending = 0;
|
uint64_t pending = 0;
|
||||||
|
|
||||||
qemu_mutex_lock_iothread();
|
qemu_mutex_lock_iothread();
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(dbms, &dirty_bitmap_mig_state.dbms_list, entry) {
|
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
|
||||||
uint64_t gran = bdrv_dirty_bitmap_granularity(dbms->bitmap);
|
uint64_t gran = bdrv_dirty_bitmap_granularity(dbms->bitmap);
|
||||||
uint64_t sectors = dbms->bulk_completed ? 0 :
|
uint64_t sectors = dbms->bulk_completed ? 0 :
|
||||||
dbms->total_sectors - dbms->cur_sector;
|
dbms->total_sectors - dbms->cur_sector;
|
||||||
@ -507,11 +533,16 @@ static void dirty_bitmap_save_pending(QEMUFile *f, void *opaque,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* First occurrence of this bitmap. It should be created if doesn't exist */
|
/* First occurrence of this bitmap. It should be created if doesn't exist */
|
||||||
static int dirty_bitmap_load_start(QEMUFile *f, DirtyBitmapLoadState *s)
|
static int dirty_bitmap_load_start(QEMUFile *f, DBMLoadState *s)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
uint32_t granularity = qemu_get_be32(f);
|
uint32_t granularity = qemu_get_be32(f);
|
||||||
uint8_t flags = qemu_get_byte(f);
|
uint8_t flags = qemu_get_byte(f);
|
||||||
|
LoadBitmapState *b;
|
||||||
|
|
||||||
|
if (s->cancelled) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (s->bitmap) {
|
if (s->bitmap) {
|
||||||
error_report("Bitmap with the same name ('%s') already exists on "
|
error_report("Bitmap with the same name ('%s') already exists on "
|
||||||
@ -538,90 +569,140 @@ static int dirty_bitmap_load_start(QEMUFile *f, DirtyBitmapLoadState *s)
|
|||||||
|
|
||||||
bdrv_disable_dirty_bitmap(s->bitmap);
|
bdrv_disable_dirty_bitmap(s->bitmap);
|
||||||
if (flags & DIRTY_BITMAP_MIG_START_FLAG_ENABLED) {
|
if (flags & DIRTY_BITMAP_MIG_START_FLAG_ENABLED) {
|
||||||
DirtyBitmapLoadBitmapState *b;
|
|
||||||
|
|
||||||
bdrv_dirty_bitmap_create_successor(s->bitmap, &local_err);
|
bdrv_dirty_bitmap_create_successor(s->bitmap, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_report_err(local_err);
|
error_report_err(local_err);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
b = g_new(DirtyBitmapLoadBitmapState, 1);
|
b = g_new(LoadBitmapState, 1);
|
||||||
b->bs = s->bs;
|
b->bs = s->bs;
|
||||||
b->bitmap = s->bitmap;
|
b->bitmap = s->bitmap;
|
||||||
b->migrated = false;
|
b->migrated = false;
|
||||||
enabled_bitmaps = g_slist_prepend(enabled_bitmaps, b);
|
b->enabled = flags & DIRTY_BITMAP_MIG_START_FLAG_ENABLED;
|
||||||
}
|
|
||||||
|
s->bitmaps = g_slist_prepend(s->bitmaps, b);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dirty_bitmap_mig_before_vm_start(void)
|
/*
|
||||||
|
* before_vm_start_handle_item
|
||||||
|
*
|
||||||
|
* g_slist_foreach helper
|
||||||
|
*
|
||||||
|
* item is LoadBitmapState*
|
||||||
|
* opaque is DBMLoadState*
|
||||||
|
*/
|
||||||
|
static void before_vm_start_handle_item(void *item, void *opaque)
|
||||||
{
|
{
|
||||||
GSList *item;
|
DBMLoadState *s = opaque;
|
||||||
|
LoadBitmapState *b = item;
|
||||||
qemu_mutex_lock(&finish_lock);
|
|
||||||
|
|
||||||
for (item = enabled_bitmaps; item; item = g_slist_next(item)) {
|
|
||||||
DirtyBitmapLoadBitmapState *b = item->data;
|
|
||||||
|
|
||||||
|
if (b->enabled) {
|
||||||
if (b->migrated) {
|
if (b->migrated) {
|
||||||
bdrv_enable_dirty_bitmap_locked(b->bitmap);
|
bdrv_enable_dirty_bitmap(b->bitmap);
|
||||||
} else {
|
} else {
|
||||||
bdrv_dirty_bitmap_enable_successor(b->bitmap);
|
bdrv_dirty_bitmap_enable_successor(b->bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_slist_free(enabled_bitmaps);
|
if (b->migrated) {
|
||||||
enabled_bitmaps = NULL;
|
s->bitmaps = g_slist_remove(s->bitmaps, b);
|
||||||
|
g_free(b);
|
||||||
qemu_mutex_unlock(&finish_lock);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dirty_bitmap_load_complete(QEMUFile *f, DirtyBitmapLoadState *s)
|
void dirty_bitmap_mig_before_vm_start(void)
|
||||||
|
{
|
||||||
|
DBMLoadState *s = &dbm_state.load;
|
||||||
|
qemu_mutex_lock(&s->lock);
|
||||||
|
|
||||||
|
assert(!s->before_vm_start_handled);
|
||||||
|
g_slist_foreach(s->bitmaps, before_vm_start_handle_item, s);
|
||||||
|
s->before_vm_start_handled = true;
|
||||||
|
|
||||||
|
qemu_mutex_unlock(&s->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cancel_incoming_locked(DBMLoadState *s)
|
||||||
|
{
|
||||||
|
GSList *item;
|
||||||
|
|
||||||
|
if (s->cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->cancelled = true;
|
||||||
|
s->bs = NULL;
|
||||||
|
s->bitmap = NULL;
|
||||||
|
|
||||||
|
/* Drop all unfinished bitmaps */
|
||||||
|
for (item = s->bitmaps; item; item = g_slist_next(item)) {
|
||||||
|
LoadBitmapState *b = item->data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bitmap must be unfinished, as finished bitmaps should already be
|
||||||
|
* removed from the list.
|
||||||
|
*/
|
||||||
|
assert(!s->before_vm_start_handled || !b->migrated);
|
||||||
|
if (bdrv_dirty_bitmap_has_successor(b->bitmap)) {
|
||||||
|
bdrv_reclaim_dirty_bitmap(b->bitmap, &error_abort);
|
||||||
|
}
|
||||||
|
bdrv_release_dirty_bitmap(b->bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_slist_free_full(s->bitmaps, g_free);
|
||||||
|
s->bitmaps = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dirty_bitmap_mig_cancel_outgoing(void)
|
||||||
|
{
|
||||||
|
dirty_bitmap_do_save_cleanup(&dbm_state.save);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dirty_bitmap_mig_cancel_incoming(void)
|
||||||
|
{
|
||||||
|
DBMLoadState *s = &dbm_state.load;
|
||||||
|
|
||||||
|
qemu_mutex_lock(&s->lock);
|
||||||
|
|
||||||
|
cancel_incoming_locked(s);
|
||||||
|
|
||||||
|
qemu_mutex_unlock(&s->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
|
||||||
{
|
{
|
||||||
GSList *item;
|
GSList *item;
|
||||||
trace_dirty_bitmap_load_complete();
|
trace_dirty_bitmap_load_complete();
|
||||||
|
|
||||||
|
if (s->cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
|
bdrv_dirty_bitmap_deserialize_finish(s->bitmap);
|
||||||
|
|
||||||
qemu_mutex_lock(&finish_lock);
|
if (bdrv_dirty_bitmap_has_successor(s->bitmap)) {
|
||||||
|
bdrv_reclaim_dirty_bitmap(s->bitmap, &error_abort);
|
||||||
|
}
|
||||||
|
|
||||||
for (item = enabled_bitmaps; item; item = g_slist_next(item)) {
|
for (item = s->bitmaps; item; item = g_slist_next(item)) {
|
||||||
DirtyBitmapLoadBitmapState *b = item->data;
|
LoadBitmapState *b = item->data;
|
||||||
|
|
||||||
if (b->bitmap == s->bitmap) {
|
if (b->bitmap == s->bitmap) {
|
||||||
b->migrated = true;
|
b->migrated = true;
|
||||||
|
if (s->before_vm_start_handled) {
|
||||||
|
s->bitmaps = g_slist_remove(s->bitmaps, b);
|
||||||
|
g_free(b);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bdrv_dirty_bitmap_has_successor(s->bitmap)) {
|
|
||||||
bdrv_dirty_bitmap_lock(s->bitmap);
|
|
||||||
if (enabled_bitmaps == NULL) {
|
|
||||||
/* in postcopy */
|
|
||||||
bdrv_reclaim_dirty_bitmap_locked(s->bitmap, &error_abort);
|
|
||||||
bdrv_enable_dirty_bitmap_locked(s->bitmap);
|
|
||||||
} else {
|
|
||||||
/* target not started, successor must be empty */
|
|
||||||
int64_t count = bdrv_get_dirty_count(s->bitmap);
|
|
||||||
BdrvDirtyBitmap *ret = bdrv_reclaim_dirty_bitmap_locked(s->bitmap,
|
|
||||||
NULL);
|
|
||||||
/* bdrv_reclaim_dirty_bitmap can fail only on no successor (it
|
|
||||||
* must be) or on merge fail, but merge can't fail when second
|
|
||||||
* bitmap is empty
|
|
||||||
*/
|
|
||||||
assert(ret == s->bitmap &&
|
|
||||||
count == bdrv_get_dirty_count(s->bitmap));
|
|
||||||
}
|
|
||||||
bdrv_dirty_bitmap_unlock(s->bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_mutex_unlock(&finish_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dirty_bitmap_load_bits(QEMUFile *f, DirtyBitmapLoadState *s)
|
static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
|
||||||
{
|
{
|
||||||
uint64_t first_byte = qemu_get_be64(f) << BDRV_SECTOR_BITS;
|
uint64_t first_byte = qemu_get_be64(f) << BDRV_SECTOR_BITS;
|
||||||
uint64_t nr_bytes = (uint64_t)qemu_get_be32(f) << BDRV_SECTOR_BITS;
|
uint64_t nr_bytes = (uint64_t)qemu_get_be32(f) << BDRV_SECTOR_BITS;
|
||||||
@ -630,15 +711,46 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DirtyBitmapLoadState *s)
|
|||||||
|
|
||||||
if (s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES) {
|
if (s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES) {
|
||||||
trace_dirty_bitmap_load_bits_zeroes();
|
trace_dirty_bitmap_load_bits_zeroes();
|
||||||
bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte, nr_bytes,
|
if (!s->cancelled) {
|
||||||
false);
|
bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte,
|
||||||
|
nr_bytes, false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
size_t ret;
|
size_t ret;
|
||||||
uint8_t *buf;
|
g_autofree uint8_t *buf = NULL;
|
||||||
uint64_t buf_size = qemu_get_be64(f);
|
uint64_t buf_size = qemu_get_be64(f);
|
||||||
uint64_t needed_size =
|
uint64_t needed_size;
|
||||||
bdrv_dirty_bitmap_serialization_size(s->bitmap,
|
|
||||||
first_byte, nr_bytes);
|
/*
|
||||||
|
* The actual check for buf_size is done a bit later. We can't do it in
|
||||||
|
* cancelled mode as we don't have the bitmap to check the constraints
|
||||||
|
* (so, we allocate a buffer and read prior to the check). On the other
|
||||||
|
* hand, we shouldn't blindly g_malloc the number from the stream.
|
||||||
|
* Actually one chunk should not be larger than CHUNK_SIZE. Let's allow
|
||||||
|
* a bit larger (which means that bitmap migration will fail anyway and
|
||||||
|
* the whole migration will most probably fail soon due to broken
|
||||||
|
* stream).
|
||||||
|
*/
|
||||||
|
if (buf_size > 10 * CHUNK_SIZE) {
|
||||||
|
error_report("Bitmap migration stream buffer allocation request "
|
||||||
|
"is too large");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = g_malloc(buf_size);
|
||||||
|
ret = qemu_get_buffer(f, buf, buf_size);
|
||||||
|
if (ret != buf_size) {
|
||||||
|
error_report("Failed to read bitmap bits");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->cancelled) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
needed_size = bdrv_dirty_bitmap_serialization_size(s->bitmap,
|
||||||
|
first_byte,
|
||||||
|
nr_bytes);
|
||||||
|
|
||||||
if (needed_size > buf_size ||
|
if (needed_size > buf_size ||
|
||||||
buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
|
buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
|
||||||
@ -647,26 +759,18 @@ static int dirty_bitmap_load_bits(QEMUFile *f, DirtyBitmapLoadState *s)
|
|||||||
error_report("Migrated bitmap granularity doesn't "
|
error_report("Migrated bitmap granularity doesn't "
|
||||||
"match the destination bitmap '%s' granularity",
|
"match the destination bitmap '%s' granularity",
|
||||||
bdrv_dirty_bitmap_name(s->bitmap));
|
bdrv_dirty_bitmap_name(s->bitmap));
|
||||||
return -EINVAL;
|
cancel_incoming_locked(s);
|
||||||
}
|
return 0;
|
||||||
|
|
||||||
buf = g_malloc(buf_size);
|
|
||||||
ret = qemu_get_buffer(f, buf, buf_size);
|
|
||||||
if (ret != buf_size) {
|
|
||||||
error_report("Failed to read bitmap bits");
|
|
||||||
g_free(buf);
|
|
||||||
return -EIO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
|
bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
|
||||||
false);
|
false);
|
||||||
g_free(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dirty_bitmap_load_header(QEMUFile *f, DirtyBitmapLoadState *s)
|
static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
bool nothing;
|
bool nothing;
|
||||||
@ -680,14 +784,16 @@ static int dirty_bitmap_load_header(QEMUFile *f, DirtyBitmapLoadState *s)
|
|||||||
error_report("Unable to read node name string");
|
error_report("Unable to read node name string");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
if (!s->cancelled) {
|
||||||
s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
|
s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
|
||||||
if (!s->bs) {
|
if (!s->bs) {
|
||||||
error_report_err(local_err);
|
error_report_err(local_err);
|
||||||
return -EINVAL;
|
cancel_incoming_locked(s);
|
||||||
}
|
}
|
||||||
} else if (!s->bs && !nothing) {
|
}
|
||||||
|
} else if (!s->bs && !nothing && !s->cancelled) {
|
||||||
error_report("Error: block device name is not set");
|
error_report("Error: block device name is not set");
|
||||||
return -EINVAL;
|
cancel_incoming_locked(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) {
|
if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) {
|
||||||
@ -695,47 +801,66 @@ static int dirty_bitmap_load_header(QEMUFile *f, DirtyBitmapLoadState *s)
|
|||||||
error_report("Unable to read bitmap name string");
|
error_report("Unable to read bitmap name string");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
if (!s->cancelled) {
|
||||||
s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
|
s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
|
||||||
|
|
||||||
/* bitmap may be NULL here, it wouldn't be an error if it is the
|
/*
|
||||||
* first occurrence of the bitmap */
|
* bitmap may be NULL here, it wouldn't be an error if it is the
|
||||||
|
* first occurrence of the bitmap
|
||||||
|
*/
|
||||||
if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
|
if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
|
||||||
error_report("Error: unknown dirty bitmap "
|
error_report("Error: unknown dirty bitmap "
|
||||||
"'%s' for block device '%s'",
|
"'%s' for block device '%s'",
|
||||||
s->bitmap_name, s->node_name);
|
s->bitmap_name, s->node_name);
|
||||||
return -EINVAL;
|
cancel_incoming_locked(s);
|
||||||
}
|
}
|
||||||
} else if (!s->bitmap && !nothing) {
|
}
|
||||||
|
} else if (!s->bitmap && !nothing && !s->cancelled) {
|
||||||
error_report("Error: block device name is not set");
|
error_report("Error: block device name is not set");
|
||||||
return -EINVAL;
|
cancel_incoming_locked(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dirty_bitmap_load
|
||||||
|
*
|
||||||
|
* Load sequence of dirty bitmap chunks. Return error only on fatal io stream
|
||||||
|
* violations. On other errors just cancel bitmaps incoming migration and return
|
||||||
|
* 0.
|
||||||
|
*
|
||||||
|
* Note, than when incoming bitmap migration is canceled, we still must read all
|
||||||
|
* our chunks (and just ignore them), to not affect other migration objects.
|
||||||
|
*/
|
||||||
static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
|
static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
|
||||||
{
|
{
|
||||||
static DirtyBitmapLoadState s;
|
DBMLoadState *s = &((DBMState *)opaque)->load;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
trace_dirty_bitmap_load_enter();
|
trace_dirty_bitmap_load_enter();
|
||||||
|
|
||||||
if (version_id != 1) {
|
if (version_id != 1) {
|
||||||
|
QEMU_LOCK_GUARD(&s->lock);
|
||||||
|
cancel_incoming_locked(s);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
ret = dirty_bitmap_load_header(f, &s);
|
QEMU_LOCK_GUARD(&s->lock);
|
||||||
|
|
||||||
|
ret = dirty_bitmap_load_header(f, s);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
cancel_incoming_locked(s);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s.flags & DIRTY_BITMAP_MIG_FLAG_START) {
|
if (s->flags & DIRTY_BITMAP_MIG_FLAG_START) {
|
||||||
ret = dirty_bitmap_load_start(f, &s);
|
ret = dirty_bitmap_load_start(f, s);
|
||||||
} else if (s.flags & DIRTY_BITMAP_MIG_FLAG_COMPLETE) {
|
} else if (s->flags & DIRTY_BITMAP_MIG_FLAG_COMPLETE) {
|
||||||
dirty_bitmap_load_complete(f, &s);
|
dirty_bitmap_load_complete(f, s);
|
||||||
} else if (s.flags & DIRTY_BITMAP_MIG_FLAG_BITS) {
|
} else if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITS) {
|
||||||
ret = dirty_bitmap_load_bits(f, &s);
|
ret = dirty_bitmap_load_bits(f, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
@ -743,9 +868,10 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
cancel_incoming_locked(s);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
} while (!(s.flags & DIRTY_BITMAP_MIG_FLAG_EOS));
|
} while (!(s->flags & DIRTY_BITMAP_MIG_FLAG_EOS));
|
||||||
|
|
||||||
trace_dirty_bitmap_load_success();
|
trace_dirty_bitmap_load_success();
|
||||||
return 0;
|
return 0;
|
||||||
@ -753,13 +879,14 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
|
|||||||
|
|
||||||
static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque)
|
static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque)
|
||||||
{
|
{
|
||||||
DirtyBitmapMigBitmapState *dbms = NULL;
|
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||||
if (init_dirty_bitmap_migration() < 0) {
|
SaveBitmapState *dbms = NULL;
|
||||||
|
if (init_dirty_bitmap_migration(s) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(dbms, &dirty_bitmap_mig_state.dbms_list, entry) {
|
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
|
||||||
send_bitmap_start(f, dbms);
|
send_bitmap_start(f, s, dbms);
|
||||||
}
|
}
|
||||||
qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
|
qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
|
||||||
|
|
||||||
@ -768,7 +895,9 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque)
|
|||||||
|
|
||||||
static bool dirty_bitmap_is_active(void *opaque)
|
static bool dirty_bitmap_is_active(void *opaque)
|
||||||
{
|
{
|
||||||
return migrate_dirty_bitmaps() && !dirty_bitmap_mig_state.no_bitmaps;
|
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||||
|
|
||||||
|
return migrate_dirty_bitmaps() && !s->no_bitmaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dirty_bitmap_is_active_iterate(void *opaque)
|
static bool dirty_bitmap_is_active_iterate(void *opaque)
|
||||||
@ -796,9 +925,10 @@ static SaveVMHandlers savevm_dirty_bitmap_handlers = {
|
|||||||
|
|
||||||
void dirty_bitmap_mig_init(void)
|
void dirty_bitmap_mig_init(void)
|
||||||
{
|
{
|
||||||
QSIMPLEQ_INIT(&dirty_bitmap_mig_state.dbms_list);
|
QSIMPLEQ_INIT(&dbm_state.save.dbms_list);
|
||||||
|
qemu_mutex_init(&dbm_state.load.lock);
|
||||||
|
|
||||||
register_savevm_live("dirty-bitmap", 0, 1,
|
register_savevm_live("dirty-bitmap", 0, 1,
|
||||||
&savevm_dirty_bitmap_handlers,
|
&savevm_dirty_bitmap_handlers,
|
||||||
&dirty_bitmap_mig_state);
|
&dbm_state);
|
||||||
}
|
}
|
||||||
|
@ -165,8 +165,6 @@ void migration_object_init(void)
|
|||||||
qemu_sem_init(¤t_incoming->postcopy_pause_sem_dst, 0);
|
qemu_sem_init(¤t_incoming->postcopy_pause_sem_dst, 0);
|
||||||
qemu_sem_init(¤t_incoming->postcopy_pause_sem_fault, 0);
|
qemu_sem_init(¤t_incoming->postcopy_pause_sem_fault, 0);
|
||||||
|
|
||||||
init_dirty_bitmap_incoming_migration();
|
|
||||||
|
|
||||||
if (!migration_object_check(current_migration, &err)) {
|
if (!migration_object_check(current_migration, &err)) {
|
||||||
error_report_err(err);
|
error_report_err(err);
|
||||||
exit(1);
|
exit(1);
|
||||||
@ -190,6 +188,19 @@ void migration_shutdown(void)
|
|||||||
*/
|
*/
|
||||||
migrate_fd_cancel(current_migration);
|
migrate_fd_cancel(current_migration);
|
||||||
object_unref(OBJECT(current_migration));
|
object_unref(OBJECT(current_migration));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cancel outgoing migration of dirty bitmaps. It should
|
||||||
|
* at least unref used block nodes.
|
||||||
|
*/
|
||||||
|
dirty_bitmap_mig_cancel_outgoing();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cancel incoming migration of dirty bitmaps. Dirty bitmaps
|
||||||
|
* are non-critical data, and their loss never considered as
|
||||||
|
* something serious.
|
||||||
|
*/
|
||||||
|
dirty_bitmap_mig_cancel_incoming();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For outgoing */
|
/* For outgoing */
|
||||||
|
@ -335,7 +335,8 @@ void migrate_send_rp_recv_bitmap(MigrationIncomingState *mis,
|
|||||||
void migrate_send_rp_resume_ack(MigrationIncomingState *mis, uint32_t value);
|
void migrate_send_rp_resume_ack(MigrationIncomingState *mis, uint32_t value);
|
||||||
|
|
||||||
void dirty_bitmap_mig_before_vm_start(void);
|
void dirty_bitmap_mig_before_vm_start(void);
|
||||||
void init_dirty_bitmap_incoming_migration(void);
|
void dirty_bitmap_mig_cancel_outgoing(void);
|
||||||
|
void dirty_bitmap_mig_cancel_incoming(void);
|
||||||
void migrate_add_address(SocketAddress *address);
|
void migrate_add_address(SocketAddress *address);
|
||||||
|
|
||||||
int foreach_not_ignored_block(RAMBlockIterFunc func, void *opaque);
|
int foreach_not_ignored_block(RAMBlockIterFunc func, void *opaque);
|
||||||
|
@ -1813,6 +1813,9 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
|||||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||||
QEMUFile *f = mis->from_src_file;
|
QEMUFile *f = mis->from_src_file;
|
||||||
int load_res;
|
int load_res;
|
||||||
|
MigrationState *migr = migrate_get_current();
|
||||||
|
|
||||||
|
object_ref(OBJECT(migr));
|
||||||
|
|
||||||
migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
|
migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
|
||||||
MIGRATION_STATUS_POSTCOPY_ACTIVE);
|
MIGRATION_STATUS_POSTCOPY_ACTIVE);
|
||||||
@ -1839,11 +1842,24 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
|||||||
|
|
||||||
trace_postcopy_ram_listen_thread_exit();
|
trace_postcopy_ram_listen_thread_exit();
|
||||||
if (load_res < 0) {
|
if (load_res < 0) {
|
||||||
error_report("%s: loadvm failed: %d", __func__, load_res);
|
|
||||||
qemu_file_set_error(f, load_res);
|
qemu_file_set_error(f, load_res);
|
||||||
|
dirty_bitmap_mig_cancel_incoming();
|
||||||
|
if (postcopy_state_get() == POSTCOPY_INCOMING_RUNNING &&
|
||||||
|
!migrate_postcopy_ram() && migrate_dirty_bitmaps())
|
||||||
|
{
|
||||||
|
error_report("%s: loadvm failed during postcopy: %d. All states "
|
||||||
|
"are migrated except dirty bitmaps. Some dirty "
|
||||||
|
"bitmaps may be lost, and present migrated dirty "
|
||||||
|
"bitmaps are correctly migrated and valid.",
|
||||||
|
__func__, load_res);
|
||||||
|
load_res = 0; /* prevent further exit() */
|
||||||
|
} else {
|
||||||
|
error_report("%s: loadvm failed: %d", __func__, load_res);
|
||||||
migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
|
migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
|
||||||
MIGRATION_STATUS_FAILED);
|
MIGRATION_STATUS_FAILED);
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
if (load_res >= 0) {
|
||||||
/*
|
/*
|
||||||
* This looks good, but it's possible that the device loading in the
|
* This looks good, but it's possible that the device loading in the
|
||||||
* main thread hasn't finished yet, and so we might not be in 'RUN'
|
* main thread hasn't finished yet, and so we might not be in 'RUN'
|
||||||
@ -1879,6 +1895,8 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
|||||||
mis->have_listen_thread = false;
|
mis->have_listen_thread = false;
|
||||||
postcopy_state_set(POSTCOPY_INCOMING_END);
|
postcopy_state_set(POSTCOPY_INCOMING_END);
|
||||||
|
|
||||||
|
object_unref(OBJECT(migr));
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2437,6 +2455,8 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
|
|||||||
{
|
{
|
||||||
trace_postcopy_pause_incoming();
|
trace_postcopy_pause_incoming();
|
||||||
|
|
||||||
|
assert(migrate_postcopy_ram());
|
||||||
|
|
||||||
/* Clear the triggered bit to allow one recovery */
|
/* Clear the triggered bit to allow one recovery */
|
||||||
mis->postcopy_recover_triggered = false;
|
mis->postcopy_recover_triggered = false;
|
||||||
|
|
||||||
@ -2521,15 +2541,22 @@ out:
|
|||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
qemu_file_set_error(f, ret);
|
qemu_file_set_error(f, ret);
|
||||||
|
|
||||||
|
/* Cancel bitmaps incoming regardless of recovery */
|
||||||
|
dirty_bitmap_mig_cancel_incoming();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we are during an active postcopy, then we pause instead
|
* If we are during an active postcopy, then we pause instead
|
||||||
* of bail out to at least keep the VM's dirty data. Note
|
* of bail out to at least keep the VM's dirty data. Note
|
||||||
* that POSTCOPY_INCOMING_LISTENING stage is still not enough,
|
* that POSTCOPY_INCOMING_LISTENING stage is still not enough,
|
||||||
* during which we're still receiving device states and we
|
* during which we're still receiving device states and we
|
||||||
* still haven't yet started the VM on destination.
|
* still haven't yet started the VM on destination.
|
||||||
|
*
|
||||||
|
* Only RAM postcopy supports recovery. Still, if RAM postcopy is
|
||||||
|
* enabled, canceled bitmaps postcopy will not affect RAM postcopy
|
||||||
|
* recovering.
|
||||||
*/
|
*/
|
||||||
if (postcopy_state_get() == POSTCOPY_INCOMING_RUNNING &&
|
if (postcopy_state_get() == POSTCOPY_INCOMING_RUNNING &&
|
||||||
postcopy_pause_incoming(mis)) {
|
migrate_postcopy_ram() && postcopy_pause_incoming(mis)) {
|
||||||
/* Reset f to point to the newly created channel */
|
/* Reset f to point to the newly created channel */
|
||||||
f = mis->from_src_file;
|
f = mis->from_src_file;
|
||||||
goto retry;
|
goto retry;
|
||||||
|
@ -20,17 +20,76 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import iotests
|
import iotests
|
||||||
import time
|
|
||||||
from iotests import qemu_img
|
from iotests import qemu_img
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
|
||||||
disk_a = os.path.join(iotests.test_dir, 'disk_a')
|
disk_a = os.path.join(iotests.test_dir, 'disk_a')
|
||||||
disk_b = os.path.join(iotests.test_dir, 'disk_b')
|
disk_b = os.path.join(iotests.test_dir, 'disk_b')
|
||||||
size = '256G'
|
size = '256G'
|
||||||
fifo = os.path.join(iotests.test_dir, 'mig_fifo')
|
fifo = os.path.join(iotests.test_dir, 'mig_fifo')
|
||||||
|
|
||||||
class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
granularity = 512
|
||||||
|
nb_bitmaps = 15
|
||||||
|
|
||||||
|
GiB = 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
discards1 = (
|
||||||
|
(0, GiB),
|
||||||
|
(2 * GiB + 512 * 5, 512),
|
||||||
|
(3 * GiB + 512 * 5, 512),
|
||||||
|
(100 * GiB, GiB)
|
||||||
|
)
|
||||||
|
|
||||||
|
discards2 = (
|
||||||
|
(3 * GiB + 512 * 8, 512),
|
||||||
|
(4 * GiB + 512 * 8, 512),
|
||||||
|
(50 * GiB, GiB),
|
||||||
|
(100 * GiB + GiB // 2, GiB)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_discards(vm, discards):
|
||||||
|
for d in discards:
|
||||||
|
vm.hmp_qemu_io('drive0', 'discard {} {}'.format(*d))
|
||||||
|
|
||||||
|
|
||||||
|
def event_seconds(event):
|
||||||
|
return event['timestamp']['seconds'] + \
|
||||||
|
event['timestamp']['microseconds'] / 1000000.0
|
||||||
|
|
||||||
|
|
||||||
|
def event_dist(e1, e2):
|
||||||
|
return event_seconds(e2) - event_seconds(e1)
|
||||||
|
|
||||||
|
|
||||||
|
def check_bitmaps(vm, count):
|
||||||
|
result = vm.qmp('query-block')
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
assert 'dirty-bitmaps' not in result['return'][0]
|
||||||
|
else:
|
||||||
|
assert len(result['return'][0]['dirty-bitmaps']) == count
|
||||||
|
|
||||||
|
|
||||||
|
class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
if debug:
|
||||||
|
self.vm_a_events += self.vm_a.get_qmp_events()
|
||||||
|
self.vm_b_events += self.vm_b.get_qmp_events()
|
||||||
|
for e in self.vm_a_events:
|
||||||
|
e['vm'] = 'SRC'
|
||||||
|
for e in self.vm_b_events:
|
||||||
|
e['vm'] = 'DST'
|
||||||
|
events = (self.vm_a_events + self.vm_b_events)
|
||||||
|
events = [(e['timestamp']['seconds'],
|
||||||
|
e['timestamp']['microseconds'],
|
||||||
|
e['vm'],
|
||||||
|
e['event'],
|
||||||
|
e.get('data', '')) for e in events]
|
||||||
|
for e in sorted(events):
|
||||||
|
print('{}.{:06} {} {} {}'.format(*e))
|
||||||
|
|
||||||
self.vm_a.shutdown()
|
self.vm_a.shutdown()
|
||||||
self.vm_b.shutdown()
|
self.vm_b.shutdown()
|
||||||
os.remove(disk_a)
|
os.remove(disk_a)
|
||||||
@ -41,51 +100,66 @@ class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
|||||||
os.mkfifo(fifo)
|
os.mkfifo(fifo)
|
||||||
qemu_img('create', '-f', iotests.imgfmt, disk_a, size)
|
qemu_img('create', '-f', iotests.imgfmt, disk_a, size)
|
||||||
qemu_img('create', '-f', iotests.imgfmt, disk_b, size)
|
qemu_img('create', '-f', iotests.imgfmt, disk_b, size)
|
||||||
self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a)
|
self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a,
|
||||||
self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b)
|
'discard=unmap')
|
||||||
|
self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b,
|
||||||
|
'discard=unmap')
|
||||||
self.vm_b.add_incoming("exec: cat '" + fifo + "'")
|
self.vm_b.add_incoming("exec: cat '" + fifo + "'")
|
||||||
self.vm_a.launch()
|
self.vm_a.launch()
|
||||||
self.vm_b.launch()
|
self.vm_b.launch()
|
||||||
|
|
||||||
def test_postcopy(self):
|
# collect received events for debug
|
||||||
write_size = 0x40000000
|
self.vm_a_events = []
|
||||||
granularity = 512
|
self.vm_b_events = []
|
||||||
chunk = 4096
|
|
||||||
|
|
||||||
|
def start_postcopy(self):
|
||||||
|
""" Run migration until RESUME event on target. Return this event. """
|
||||||
|
for i in range(nb_bitmaps):
|
||||||
result = self.vm_a.qmp('block-dirty-bitmap-add', node='drive0',
|
result = self.vm_a.qmp('block-dirty-bitmap-add', node='drive0',
|
||||||
name='bitmap', granularity=granularity)
|
name='bitmap{}'.format(i),
|
||||||
self.assert_qmp(result, 'return', {});
|
granularity=granularity,
|
||||||
|
persistent=True)
|
||||||
s = 0
|
|
||||||
while s < write_size:
|
|
||||||
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % (s, chunk))
|
|
||||||
s += 0x10000
|
|
||||||
s = 0x8000
|
|
||||||
while s < write_size:
|
|
||||||
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % (s, chunk))
|
|
||||||
s += 0x10000
|
|
||||||
|
|
||||||
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
|
||||||
node='drive0', name='bitmap')
|
|
||||||
sha256 = result['return']['sha256']
|
|
||||||
|
|
||||||
result = self.vm_a.qmp('block-dirty-bitmap-clear', node='drive0',
|
|
||||||
name='bitmap')
|
|
||||||
self.assert_qmp(result, 'return', {});
|
|
||||||
s = 0
|
|
||||||
while s < write_size:
|
|
||||||
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % (s, chunk))
|
|
||||||
s += 0x10000
|
|
||||||
|
|
||||||
bitmaps_cap = {'capability': 'dirty-bitmaps', 'state': True}
|
|
||||||
events_cap = {'capability': 'events', 'state': True}
|
|
||||||
|
|
||||||
result = self.vm_a.qmp('migrate-set-capabilities',
|
|
||||||
capabilities=[bitmaps_cap, events_cap])
|
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
result = self.vm_b.qmp('migrate-set-capabilities',
|
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
capabilities=[bitmaps_cap])
|
node='drive0', name='bitmap0')
|
||||||
|
empty_sha256 = result['return']['sha256']
|
||||||
|
|
||||||
|
apply_discards(self.vm_a, discards1)
|
||||||
|
|
||||||
|
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
|
node='drive0', name='bitmap0')
|
||||||
|
self.discards1_sha256 = result['return']['sha256']
|
||||||
|
|
||||||
|
# Check, that updating the bitmap by discards works
|
||||||
|
assert self.discards1_sha256 != empty_sha256
|
||||||
|
|
||||||
|
# We want to calculate resulting sha256. Do it in bitmap0, so, disable
|
||||||
|
# other bitmaps
|
||||||
|
for i in range(1, nb_bitmaps):
|
||||||
|
result = self.vm_a.qmp('block-dirty-bitmap-disable', node='drive0',
|
||||||
|
name='bitmap{}'.format(i))
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
apply_discards(self.vm_a, discards2)
|
||||||
|
|
||||||
|
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
|
node='drive0', name='bitmap0')
|
||||||
|
self.all_discards_sha256 = result['return']['sha256']
|
||||||
|
|
||||||
|
# Now, enable some bitmaps, to be updated during migration
|
||||||
|
for i in range(2, nb_bitmaps, 2):
|
||||||
|
result = self.vm_a.qmp('block-dirty-bitmap-enable', node='drive0',
|
||||||
|
name='bitmap{}'.format(i))
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
caps = [{'capability': 'dirty-bitmaps', 'state': True},
|
||||||
|
{'capability': 'events', 'state': True}]
|
||||||
|
|
||||||
|
result = self.vm_a.qmp('migrate-set-capabilities', capabilities=caps)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
result = self.vm_b.qmp('migrate-set-capabilities', capabilities=caps)
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
result = self.vm_a.qmp('migrate', uri='exec:cat>' + fifo)
|
result = self.vm_a.qmp('migrate', uri='exec:cat>' + fifo)
|
||||||
@ -94,26 +168,94 @@ class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
|||||||
result = self.vm_a.qmp('migrate-start-postcopy')
|
result = self.vm_a.qmp('migrate-start-postcopy')
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
while True:
|
event_resume = self.vm_b.event_wait('RESUME')
|
||||||
event = self.vm_a.event_wait('MIGRATION')
|
self.vm_b_events.append(event_resume)
|
||||||
if event['data']['status'] == 'completed':
|
return event_resume
|
||||||
break
|
|
||||||
|
|
||||||
s = 0x8000
|
def test_postcopy_success(self):
|
||||||
while s < write_size:
|
event_resume = self.start_postcopy()
|
||||||
self.vm_b.hmp_qemu_io('drive0', 'write %d %d' % (s, chunk))
|
|
||||||
s += 0x10000
|
|
||||||
|
|
||||||
result = self.vm_b.qmp('query-block');
|
# enabled bitmaps should be updated
|
||||||
while len(result['return'][0]['dirty-bitmaps']) > 1:
|
apply_discards(self.vm_b, discards2)
|
||||||
time.sleep(2)
|
|
||||||
result = self.vm_b.qmp('query-block');
|
|
||||||
|
|
||||||
|
match = {'data': {'status': 'completed'}}
|
||||||
|
event_complete = self.vm_b.event_wait('MIGRATION', match=match)
|
||||||
|
self.vm_b_events.append(event_complete)
|
||||||
|
|
||||||
|
# take queued event, should already been happened
|
||||||
|
event_stop = self.vm_a.event_wait('STOP')
|
||||||
|
self.vm_a_events.append(event_stop)
|
||||||
|
|
||||||
|
downtime = event_dist(event_stop, event_resume)
|
||||||
|
postcopy_time = event_dist(event_resume, event_complete)
|
||||||
|
|
||||||
|
assert downtime * 10 < postcopy_time
|
||||||
|
if debug:
|
||||||
|
print('downtime:', downtime)
|
||||||
|
print('postcopy_time:', postcopy_time)
|
||||||
|
|
||||||
|
# check that there are no bitmaps stored on source
|
||||||
|
self.vm_a_events += self.vm_a.get_qmp_events()
|
||||||
|
self.vm_a.shutdown()
|
||||||
|
self.vm_a.launch()
|
||||||
|
check_bitmaps(self.vm_a, 0)
|
||||||
|
|
||||||
|
# check that bitmaps are migrated and persistence works
|
||||||
|
check_bitmaps(self.vm_b, nb_bitmaps)
|
||||||
|
self.vm_b.shutdown()
|
||||||
|
# recreate vm_b, so there is no incoming option, which prevents
|
||||||
|
# loading bitmaps from disk
|
||||||
|
self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b)
|
||||||
|
self.vm_b.launch()
|
||||||
|
check_bitmaps(self.vm_b, nb_bitmaps)
|
||||||
|
|
||||||
|
# Check content of migrated bitmaps. Still, don't waste time checking
|
||||||
|
# every bitmap
|
||||||
|
for i in range(0, nb_bitmaps, 5):
|
||||||
result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256',
|
result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
node='drive0', name='bitmap')
|
node='drive0', name='bitmap{}'.format(i))
|
||||||
|
sha = self.discards1_sha256 if i % 2 else self.all_discards_sha256
|
||||||
|
self.assert_qmp(result, 'return/sha256', sha)
|
||||||
|
|
||||||
|
def test_early_shutdown_destination(self):
|
||||||
|
self.start_postcopy()
|
||||||
|
|
||||||
|
self.vm_b_events += self.vm_b.get_qmp_events()
|
||||||
|
self.vm_b.shutdown()
|
||||||
|
# recreate vm_b, so there is no incoming option, which prevents
|
||||||
|
# loading bitmaps from disk
|
||||||
|
self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b)
|
||||||
|
self.vm_b.launch()
|
||||||
|
check_bitmaps(self.vm_b, 0)
|
||||||
|
|
||||||
|
# Bitmaps will be lost if we just shutdown the vm, as they are marked
|
||||||
|
# to skip storing to disk when prepared for migration. And that's
|
||||||
|
# correct, as actual data may be modified in target vm, so we play
|
||||||
|
# safe.
|
||||||
|
# Still, this mark would be taken away if we do 'cont', and bitmaps
|
||||||
|
# become persistent again. (see iotest 169 for such behavior case)
|
||||||
|
result = self.vm_a.qmp('query-status')
|
||||||
|
assert not result['return']['running']
|
||||||
|
self.vm_a_events += self.vm_a.get_qmp_events()
|
||||||
|
self.vm_a.shutdown()
|
||||||
|
self.vm_a.launch()
|
||||||
|
check_bitmaps(self.vm_a, 0)
|
||||||
|
|
||||||
|
def test_early_kill_source(self):
|
||||||
|
self.start_postcopy()
|
||||||
|
|
||||||
|
self.vm_a_events = self.vm_a.get_qmp_events()
|
||||||
|
self.vm_a.kill()
|
||||||
|
|
||||||
|
self.vm_a.launch()
|
||||||
|
|
||||||
|
match = {'data': {'status': 'completed'}}
|
||||||
|
e_complete = self.vm_b.event_wait('MIGRATION', match=match)
|
||||||
|
self.vm_b_events.append(e_complete)
|
||||||
|
|
||||||
|
check_bitmaps(self.vm_a, 0)
|
||||||
|
check_bitmaps(self.vm_b, 0)
|
||||||
|
|
||||||
self.assert_qmp(result, 'return/sha256', sha256);
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
iotests.main(supported_fmts=['qcow2'], supported_cache_modes=['none'],
|
iotests.main(supported_fmts=['qcow2'])
|
||||||
supported_protocols=['file'])
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.
|
...
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
Ran 1 tests
|
Ran 3 tests
|
||||||
|
|
||||||
OK
|
OK
|
||||||
|
@ -112,7 +112,7 @@
|
|||||||
088 rw quick
|
088 rw quick
|
||||||
089 rw auto quick
|
089 rw auto quick
|
||||||
090 rw auto quick
|
090 rw auto quick
|
||||||
091 rw migration
|
091 rw migration quick
|
||||||
092 rw quick
|
092 rw quick
|
||||||
093 throttle
|
093 throttle
|
||||||
094 rw quick
|
094 rw quick
|
||||||
@ -186,7 +186,7 @@
|
|||||||
162 quick
|
162 quick
|
||||||
163 rw
|
163 rw
|
||||||
165 rw quick
|
165 rw quick
|
||||||
169 rw quick migration
|
169 rw migration
|
||||||
170 rw auto quick
|
170 rw auto quick
|
||||||
171 rw quick
|
171 rw quick
|
||||||
172 auto
|
172 auto
|
||||||
@ -197,9 +197,9 @@
|
|||||||
177 rw auto quick
|
177 rw auto quick
|
||||||
178 img
|
178 img
|
||||||
179 rw auto quick
|
179 rw auto quick
|
||||||
181 rw auto migration
|
181 rw auto migration quick
|
||||||
182 rw quick
|
182 rw quick
|
||||||
183 rw migration
|
183 rw migration quick
|
||||||
184 rw auto quick
|
184 rw auto quick
|
||||||
185 rw
|
185 rw
|
||||||
186 rw auto
|
186 rw auto
|
||||||
@ -216,9 +216,9 @@
|
|||||||
198 rw
|
198 rw
|
||||||
199 rw migration
|
199 rw migration
|
||||||
200 rw
|
200 rw
|
||||||
201 rw migration
|
201 rw migration quick
|
||||||
202 rw quick
|
202 rw quick
|
||||||
203 rw auto migration
|
203 rw auto migration quick
|
||||||
204 rw quick
|
204 rw quick
|
||||||
205 rw quick
|
205 rw quick
|
||||||
206 rw
|
206 rw
|
||||||
|
Loading…
x
Reference in New Issue
Block a user