mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-27 21:40:49 +00:00
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;
|
||||
|
||||
#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_CRYPTO_HEADER 0x0537be77
|
||||
#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:
|
||||
0x00000000 - End of the header extension area
|
||||
0xE2792ACA - Backing file format name string
|
||||
0xe2792aca - Backing file format name string
|
||||
0x6803f857 - Feature name table
|
||||
0x23852875 - Bitmaps extension
|
||||
0x0537be77 - Full disk encryption header pointer
|
||||
|
@ -97,26 +97,28 @@
|
||||
|
||||
#define DIRTY_BITMAP_MIG_START_FLAG_ENABLED 0x01
|
||||
#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
|
||||
|
||||
typedef struct DirtyBitmapMigBitmapState {
|
||||
/* State of one bitmap during save process */
|
||||
typedef struct SaveBitmapState {
|
||||
/* Written during setup phase. */
|
||||
BlockDriverState *bs;
|
||||
const char *node_name;
|
||||
BdrvDirtyBitmap *bitmap;
|
||||
uint64_t total_sectors;
|
||||
uint64_t sectors_per_chunk;
|
||||
QSIMPLEQ_ENTRY(DirtyBitmapMigBitmapState) entry;
|
||||
QSIMPLEQ_ENTRY(SaveBitmapState) entry;
|
||||
uint8_t flags;
|
||||
|
||||
/* For bulk phase. */
|
||||
bool bulk_completed;
|
||||
uint64_t cur_sector;
|
||||
} DirtyBitmapMigBitmapState;
|
||||
} SaveBitmapState;
|
||||
|
||||
typedef struct DirtyBitmapMigState {
|
||||
QSIMPLEQ_HEAD(, DirtyBitmapMigBitmapState) dbms_list;
|
||||
/* State of the dirty bitmap migration (DBM) during save process */
|
||||
typedef struct DBMSaveState {
|
||||
QSIMPLEQ_HEAD(, SaveBitmapState) dbms_list;
|
||||
|
||||
bool bulk_completed;
|
||||
bool no_bitmaps;
|
||||
@ -124,30 +126,44 @@ typedef struct DirtyBitmapMigState {
|
||||
/* for send_bitmap_bits() */
|
||||
BlockDriverState *prev_bs;
|
||||
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;
|
||||
char node_name[256];
|
||||
char bitmap_name[256];
|
||||
BlockDriverState *bs;
|
||||
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;
|
||||
BdrvDirtyBitmap *bitmap;
|
||||
bool migrated;
|
||||
} DirtyBitmapLoadBitmapState;
|
||||
static GSList *enabled_bitmaps;
|
||||
QemuMutex finish_lock;
|
||||
/*
|
||||
* cancelled
|
||||
* Incoming migration is cancelled for some reason. That means that we
|
||||
* still should read our chunks from migration stream, to not affect other
|
||||
* migration objects (like RAM), but just ignore them and do not touch any
|
||||
* bitmaps or nodes.
|
||||
*/
|
||||
bool cancelled;
|
||||
|
||||
void init_dirty_bitmap_incoming_migration(void)
|
||||
{
|
||||
qemu_mutex_init(&finish_lock);
|
||||
}
|
||||
GSList *bitmaps;
|
||||
QemuMutex lock; /* protect bitmaps */
|
||||
} DBMLoadState;
|
||||
|
||||
typedef struct DBMState {
|
||||
DBMSaveState save;
|
||||
DBMLoadState load;
|
||||
} DBMState;
|
||||
|
||||
static DBMState dbm_state;
|
||||
|
||||
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)
|
||||
{
|
||||
/* 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)));
|
||||
|
||||
qemu_put_byte(f, flags);
|
||||
}
|
||||
|
||||
static void send_bitmap_header(QEMUFile *f, DirtyBitmapMigBitmapState *dbms,
|
||||
uint32_t additional_flags)
|
||||
static void send_bitmap_header(QEMUFile *f, DBMSaveState *s,
|
||||
SaveBitmapState *dbms, uint32_t additional_flags)
|
||||
{
|
||||
BlockDriverState *bs = dbms->bs;
|
||||
BdrvDirtyBitmap *bitmap = dbms->bitmap;
|
||||
uint32_t flags = additional_flags;
|
||||
trace_send_bitmap_header_enter();
|
||||
|
||||
if (bs != dirty_bitmap_mig_state.prev_bs) {
|
||||
dirty_bitmap_mig_state.prev_bs = bs;
|
||||
if (bs != s->prev_bs) {
|
||||
s->prev_bs = bs;
|
||||
flags |= DIRTY_BITMAP_MIG_FLAG_DEVICE_NAME;
|
||||
}
|
||||
|
||||
if (bitmap != dirty_bitmap_mig_state.prev_bitmap) {
|
||||
dirty_bitmap_mig_state.prev_bitmap = bitmap;
|
||||
if (bitmap != s->prev_bitmap) {
|
||||
s->prev_bitmap = bitmap;
|
||||
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_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)
|
||||
{
|
||||
/* 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);
|
||||
|
||||
send_bitmap_header(f, dbms, flags);
|
||||
send_bitmap_header(f, s, dbms, flags);
|
||||
|
||||
qemu_put_be64(f, start_sector);
|
||||
qemu_put_be32(f, nr_sectors);
|
||||
@ -255,12 +274,12 @@ static void send_bitmap_bits(QEMUFile *f, DirtyBitmapMigBitmapState *dbms,
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
QSIMPLEQ_REMOVE_HEAD(&dirty_bitmap_mig_state.dbms_list, entry);
|
||||
while ((dbms = QSIMPLEQ_FIRST(&s->dbms_list)) != NULL) {
|
||||
QSIMPLEQ_REMOVE_HEAD(&s->dbms_list, entry);
|
||||
bdrv_dirty_bitmap_set_busy(dbms->bitmap, false);
|
||||
bdrv_unref(dbms->bs);
|
||||
g_free(dbms);
|
||||
@ -268,10 +287,11 @@ static void dirty_bitmap_mig_cleanup(void)
|
||||
}
|
||||
|
||||
/* 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;
|
||||
DirtyBitmapMigBitmapState *dbms;
|
||||
SaveBitmapState *dbms;
|
||||
Error *local_err = NULL;
|
||||
|
||||
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_dirty_bitmap_set_busy(bitmap, true);
|
||||
|
||||
dbms = g_new0(DirtyBitmapMigBitmapState, 1);
|
||||
dbms = g_new0(SaveBitmapState, 1);
|
||||
dbms->bs = bs;
|
||||
dbms->node_name = bs_name;
|
||||
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;
|
||||
}
|
||||
|
||||
QSIMPLEQ_INSERT_TAIL(&dirty_bitmap_mig_state.dbms_list,
|
||||
dbms, entry);
|
||||
QSIMPLEQ_INSERT_TAIL(&s->dbms_list, dbms, entry);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called with iothread lock taken. */
|
||||
static int init_dirty_bitmap_migration(void)
|
||||
static int init_dirty_bitmap_migration(DBMSaveState *s)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
DirtyBitmapMigBitmapState *dbms;
|
||||
SaveBitmapState *dbms;
|
||||
GHashTable *handled_by_blk = g_hash_table_new(NULL, NULL);
|
||||
BlockBackend *blk;
|
||||
|
||||
dirty_bitmap_mig_state.bulk_completed = false;
|
||||
dirty_bitmap_mig_state.prev_bs = NULL;
|
||||
dirty_bitmap_mig_state.prev_bitmap = NULL;
|
||||
dirty_bitmap_mig_state.no_bitmaps = false;
|
||||
s->bulk_completed = false;
|
||||
s->prev_bs = NULL;
|
||||
s->prev_bitmap = NULL;
|
||||
s->no_bitmaps = false;
|
||||
|
||||
/*
|
||||
* 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 (add_bitmaps_to_list(bs, name)) {
|
||||
if (add_bitmaps_to_list(s, bs, name)) {
|
||||
goto fail;
|
||||
}
|
||||
g_hash_table_add(handled_by_blk, bs);
|
||||
@ -382,18 +401,18 @@ static int init_dirty_bitmap_migration(void)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
if (QSIMPLEQ_EMPTY(&dirty_bitmap_mig_state.dbms_list)) {
|
||||
dirty_bitmap_mig_state.no_bitmaps = true;
|
||||
if (QSIMPLEQ_EMPTY(&s->dbms_list)) {
|
||||
s->no_bitmaps = true;
|
||||
}
|
||||
|
||||
g_hash_table_destroy(handled_by_blk);
|
||||
@ -402,18 +421,19 @@ static int init_dirty_bitmap_migration(void)
|
||||
|
||||
fail:
|
||||
g_hash_table_destroy(handled_by_blk);
|
||||
dirty_bitmap_mig_cleanup();
|
||||
dirty_bitmap_do_save_cleanup(s);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
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;
|
||||
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. */
|
||||
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) {
|
||||
bulk_phase_send_chunk(f, dbms);
|
||||
bulk_phase_send_chunk(f, s, dbms);
|
||||
if (limit && qemu_file_rate_limit(f)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dirty_bitmap_mig_state.bulk_completed = true;
|
||||
s->bulk_completed = true;
|
||||
}
|
||||
|
||||
/* for SaveVMHandlers */
|
||||
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)
|
||||
{
|
||||
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||
|
||||
trace_dirty_bitmap_save_iterate(migration_in_postcopy());
|
||||
|
||||
if (migration_in_postcopy() && !dirty_bitmap_mig_state.bulk_completed) {
|
||||
bulk_phase(f, true);
|
||||
if (migration_in_postcopy() && !s->bulk_completed) {
|
||||
bulk_phase(f, s, true);
|
||||
}
|
||||
|
||||
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. */
|
||||
|
||||
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();
|
||||
|
||||
if (!dirty_bitmap_mig_state.bulk_completed) {
|
||||
bulk_phase(f, false);
|
||||
if (!s->bulk_completed) {
|
||||
bulk_phase(f, s, false);
|
||||
}
|
||||
|
||||
QSIMPLEQ_FOREACH(dbms, &dirty_bitmap_mig_state.dbms_list, entry) {
|
||||
send_bitmap_complete(f, dbms);
|
||||
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
|
||||
send_bitmap_complete(f, s, dbms);
|
||||
}
|
||||
|
||||
qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS);
|
||||
|
||||
trace_dirty_bitmap_save_complete_finish();
|
||||
|
||||
dirty_bitmap_mig_cleanup();
|
||||
dirty_bitmap_save_cleanup(opaque);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -486,12 +511,13 @@ static void dirty_bitmap_save_pending(QEMUFile *f, void *opaque,
|
||||
uint64_t *res_compatible,
|
||||
uint64_t *res_postcopy_only)
|
||||
{
|
||||
DirtyBitmapMigBitmapState *dbms;
|
||||
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||
SaveBitmapState *dbms;
|
||||
uint64_t pending = 0;
|
||||
|
||||
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 sectors = dbms->bulk_completed ? 0 :
|
||||
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 */
|
||||
static int dirty_bitmap_load_start(QEMUFile *f, DirtyBitmapLoadState *s)
|
||||
static int dirty_bitmap_load_start(QEMUFile *f, DBMLoadState *s)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
uint32_t granularity = qemu_get_be32(f);
|
||||
uint8_t flags = qemu_get_byte(f);
|
||||
LoadBitmapState *b;
|
||||
|
||||
if (s->cancelled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (s->bitmap) {
|
||||
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);
|
||||
if (flags & DIRTY_BITMAP_MIG_START_FLAG_ENABLED) {
|
||||
DirtyBitmapLoadBitmapState *b;
|
||||
|
||||
bdrv_dirty_bitmap_create_successor(s->bitmap, &local_err);
|
||||
if (local_err) {
|
||||
error_report_err(local_err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
b = g_new(DirtyBitmapLoadBitmapState, 1);
|
||||
b->bs = s->bs;
|
||||
b->bitmap = s->bitmap;
|
||||
b->migrated = false;
|
||||
enabled_bitmaps = g_slist_prepend(enabled_bitmaps, b);
|
||||
}
|
||||
|
||||
b = g_new(LoadBitmapState, 1);
|
||||
b->bs = s->bs;
|
||||
b->bitmap = s->bitmap;
|
||||
b->migrated = false;
|
||||
b->enabled = flags & DIRTY_BITMAP_MIG_START_FLAG_ENABLED;
|
||||
|
||||
s->bitmaps = g_slist_prepend(s->bitmaps, b);
|
||||
|
||||
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;
|
||||
|
||||
qemu_mutex_lock(&finish_lock);
|
||||
|
||||
for (item = enabled_bitmaps; item; item = g_slist_next(item)) {
|
||||
DirtyBitmapLoadBitmapState *b = item->data;
|
||||
DBMLoadState *s = opaque;
|
||||
LoadBitmapState *b = item;
|
||||
|
||||
if (b->enabled) {
|
||||
if (b->migrated) {
|
||||
bdrv_enable_dirty_bitmap_locked(b->bitmap);
|
||||
bdrv_enable_dirty_bitmap(b->bitmap);
|
||||
} else {
|
||||
bdrv_dirty_bitmap_enable_successor(b->bitmap);
|
||||
}
|
||||
|
||||
g_free(b);
|
||||
}
|
||||
|
||||
g_slist_free(enabled_bitmaps);
|
||||
enabled_bitmaps = NULL;
|
||||
|
||||
qemu_mutex_unlock(&finish_lock);
|
||||
if (b->migrated) {
|
||||
s->bitmaps = g_slist_remove(s->bitmaps, b);
|
||||
g_free(b);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
trace_dirty_bitmap_load_complete();
|
||||
|
||||
if (s->cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
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)) {
|
||||
DirtyBitmapLoadBitmapState *b = item->data;
|
||||
for (item = s->bitmaps; item; item = g_slist_next(item)) {
|
||||
LoadBitmapState *b = item->data;
|
||||
|
||||
if (b->bitmap == s->bitmap) {
|
||||
b->migrated = true;
|
||||
if (s->before_vm_start_handled) {
|
||||
s->bitmaps = g_slist_remove(s->bitmaps, b);
|
||||
g_free(b);
|
||||
}
|
||||
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 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) {
|
||||
trace_dirty_bitmap_load_bits_zeroes();
|
||||
bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte, nr_bytes,
|
||||
false);
|
||||
if (!s->cancelled) {
|
||||
bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte,
|
||||
nr_bytes, false);
|
||||
}
|
||||
} else {
|
||||
size_t ret;
|
||||
uint8_t *buf;
|
||||
g_autofree uint8_t *buf = NULL;
|
||||
uint64_t buf_size = qemu_get_be64(f);
|
||||
uint64_t needed_size =
|
||||
bdrv_dirty_bitmap_serialization_size(s->bitmap,
|
||||
first_byte, nr_bytes);
|
||||
uint64_t needed_size;
|
||||
|
||||
/*
|
||||
* 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 ||
|
||||
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 "
|
||||
"match the destination bitmap '%s' granularity",
|
||||
bdrv_dirty_bitmap_name(s->bitmap));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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;
|
||||
cancel_incoming_locked(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
|
||||
false);
|
||||
g_free(buf);
|
||||
}
|
||||
|
||||
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;
|
||||
bool nothing;
|
||||
@ -680,14 +784,16 @@ static int dirty_bitmap_load_header(QEMUFile *f, DirtyBitmapLoadState *s)
|
||||
error_report("Unable to read node name string");
|
||||
return -EINVAL;
|
||||
}
|
||||
s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
|
||||
if (!s->bs) {
|
||||
error_report_err(local_err);
|
||||
return -EINVAL;
|
||||
if (!s->cancelled) {
|
||||
s->bs = bdrv_lookup_bs(s->node_name, s->node_name, &local_err);
|
||||
if (!s->bs) {
|
||||
error_report_err(local_err);
|
||||
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");
|
||||
return -EINVAL;
|
||||
cancel_incoming_locked(s);
|
||||
}
|
||||
|
||||
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");
|
||||
return -EINVAL;
|
||||
}
|
||||
s->bitmap = bdrv_find_dirty_bitmap(s->bs, s->bitmap_name);
|
||||
if (!s->cancelled) {
|
||||
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 */
|
||||
if (!s->bitmap && !(s->flags & DIRTY_BITMAP_MIG_FLAG_START)) {
|
||||
error_report("Error: unknown dirty bitmap "
|
||||
"'%s' for block device '%s'",
|
||||
s->bitmap_name, s->node_name);
|
||||
return -EINVAL;
|
||||
/*
|
||||
* 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)) {
|
||||
error_report("Error: unknown dirty bitmap "
|
||||
"'%s' for block device '%s'",
|
||||
s->bitmap_name, s->node_name);
|
||||
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");
|
||||
return -EINVAL;
|
||||
cancel_incoming_locked(s);
|
||||
}
|
||||
|
||||
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 DirtyBitmapLoadState s;
|
||||
DBMLoadState *s = &((DBMState *)opaque)->load;
|
||||
int ret = 0;
|
||||
|
||||
trace_dirty_bitmap_load_enter();
|
||||
|
||||
if (version_id != 1) {
|
||||
QEMU_LOCK_GUARD(&s->lock);
|
||||
cancel_incoming_locked(s);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
do {
|
||||
ret = dirty_bitmap_load_header(f, &s);
|
||||
QEMU_LOCK_GUARD(&s->lock);
|
||||
|
||||
ret = dirty_bitmap_load_header(f, s);
|
||||
if (ret < 0) {
|
||||
cancel_incoming_locked(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (s.flags & DIRTY_BITMAP_MIG_FLAG_START) {
|
||||
ret = dirty_bitmap_load_start(f, &s);
|
||||
} else if (s.flags & DIRTY_BITMAP_MIG_FLAG_COMPLETE) {
|
||||
dirty_bitmap_load_complete(f, &s);
|
||||
} else if (s.flags & DIRTY_BITMAP_MIG_FLAG_BITS) {
|
||||
ret = dirty_bitmap_load_bits(f, &s);
|
||||
if (s->flags & DIRTY_BITMAP_MIG_FLAG_START) {
|
||||
ret = dirty_bitmap_load_start(f, s);
|
||||
} else if (s->flags & DIRTY_BITMAP_MIG_FLAG_COMPLETE) {
|
||||
dirty_bitmap_load_complete(f, s);
|
||||
} else if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITS) {
|
||||
ret = dirty_bitmap_load_bits(f, s);
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
@ -743,9 +868,10 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
cancel_incoming_locked(s);
|
||||
return ret;
|
||||
}
|
||||
} while (!(s.flags & DIRTY_BITMAP_MIG_FLAG_EOS));
|
||||
} while (!(s->flags & DIRTY_BITMAP_MIG_FLAG_EOS));
|
||||
|
||||
trace_dirty_bitmap_load_success();
|
||||
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)
|
||||
{
|
||||
DirtyBitmapMigBitmapState *dbms = NULL;
|
||||
if (init_dirty_bitmap_migration() < 0) {
|
||||
DBMSaveState *s = &((DBMState *)opaque)->save;
|
||||
SaveBitmapState *dbms = NULL;
|
||||
if (init_dirty_bitmap_migration(s) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QSIMPLEQ_FOREACH(dbms, &dirty_bitmap_mig_state.dbms_list, entry) {
|
||||
send_bitmap_start(f, dbms);
|
||||
QSIMPLEQ_FOREACH(dbms, &s->dbms_list, entry) {
|
||||
send_bitmap_start(f, s, dbms);
|
||||
}
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@ -796,9 +925,10 @@ static SaveVMHandlers savevm_dirty_bitmap_handlers = {
|
||||
|
||||
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,
|
||||
&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_fault, 0);
|
||||
|
||||
init_dirty_bitmap_incoming_migration();
|
||||
|
||||
if (!migration_object_check(current_migration, &err)) {
|
||||
error_report_err(err);
|
||||
exit(1);
|
||||
@ -190,6 +188,19 @@ void migration_shutdown(void)
|
||||
*/
|
||||
migrate_fd_cancel(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 */
|
||||
|
@ -335,7 +335,8 @@ void migrate_send_rp_recv_bitmap(MigrationIncomingState *mis,
|
||||
void migrate_send_rp_resume_ack(MigrationIncomingState *mis, uint32_t value);
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
QEMUFile *f = mis->from_src_file;
|
||||
int load_res;
|
||||
MigrationState *migr = migrate_get_current();
|
||||
|
||||
object_ref(OBJECT(migr));
|
||||
|
||||
migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
|
||||
MIGRATION_STATUS_POSTCOPY_ACTIVE);
|
||||
@ -1839,11 +1842,24 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
||||
|
||||
trace_postcopy_ram_listen_thread_exit();
|
||||
if (load_res < 0) {
|
||||
error_report("%s: loadvm failed: %d", __func__, load_res);
|
||||
qemu_file_set_error(f, load_res);
|
||||
migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
|
||||
MIGRATION_STATUS_FAILED);
|
||||
} else {
|
||||
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,
|
||||
MIGRATION_STATUS_FAILED);
|
||||
}
|
||||
}
|
||||
if (load_res >= 0) {
|
||||
/*
|
||||
* 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'
|
||||
@ -1879,6 +1895,8 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
||||
mis->have_listen_thread = false;
|
||||
postcopy_state_set(POSTCOPY_INCOMING_END);
|
||||
|
||||
object_unref(OBJECT(migr));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -2437,6 +2455,8 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
|
||||
{
|
||||
trace_postcopy_pause_incoming();
|
||||
|
||||
assert(migrate_postcopy_ram());
|
||||
|
||||
/* Clear the triggered bit to allow one recovery */
|
||||
mis->postcopy_recover_triggered = false;
|
||||
|
||||
@ -2521,15 +2541,22 @@ out:
|
||||
if (ret < 0) {
|
||||
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
|
||||
* of bail out to at least keep the VM's dirty data. Note
|
||||
* that POSTCOPY_INCOMING_LISTENING stage is still not enough,
|
||||
* during which we're still receiving device states and we
|
||||
* 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 &&
|
||||
postcopy_pause_incoming(mis)) {
|
||||
migrate_postcopy_ram() && postcopy_pause_incoming(mis)) {
|
||||
/* Reset f to point to the newly created channel */
|
||||
f = mis->from_src_file;
|
||||
goto retry;
|
||||
|
@ -20,17 +20,76 @@
|
||||
|
||||
import os
|
||||
import iotests
|
||||
import time
|
||||
from iotests import qemu_img
|
||||
|
||||
debug = False
|
||||
|
||||
disk_a = os.path.join(iotests.test_dir, 'disk_a')
|
||||
disk_b = os.path.join(iotests.test_dir, 'disk_b')
|
||||
size = '256G'
|
||||
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):
|
||||
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_b.shutdown()
|
||||
os.remove(disk_a)
|
||||
@ -41,51 +100,66 @@ class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
||||
os.mkfifo(fifo)
|
||||
qemu_img('create', '-f', iotests.imgfmt, disk_a, size)
|
||||
qemu_img('create', '-f', iotests.imgfmt, disk_b, size)
|
||||
self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a)
|
||||
self.vm_b = iotests.VM(path_suffix='b').add_drive(disk_b)
|
||||
self.vm_a = iotests.VM(path_suffix='a').add_drive(disk_a,
|
||||
'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_a.launch()
|
||||
self.vm_b.launch()
|
||||
|
||||
def test_postcopy(self):
|
||||
write_size = 0x40000000
|
||||
granularity = 512
|
||||
chunk = 4096
|
||||
# collect received events for debug
|
||||
self.vm_a_events = []
|
||||
self.vm_b_events = []
|
||||
|
||||
result = self.vm_a.qmp('block-dirty-bitmap-add', node='drive0',
|
||||
name='bitmap', granularity=granularity)
|
||||
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
|
||||
s = 0x8000
|
||||
while s < write_size:
|
||||
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % (s, chunk))
|
||||
s += 0x10000
|
||||
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',
|
||||
name='bitmap{}'.format(i),
|
||||
granularity=granularity,
|
||||
persistent=True)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||
node='drive0', name='bitmap')
|
||||
sha256 = result['return']['sha256']
|
||||
node='drive0', name='bitmap0')
|
||||
empty_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
|
||||
apply_discards(self.vm_a, discards1)
|
||||
|
||||
bitmaps_cap = {'capability': 'dirty-bitmaps', 'state': True}
|
||||
events_cap = {'capability': 'events', 'state': True}
|
||||
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||
node='drive0', name='bitmap0')
|
||||
self.discards1_sha256 = result['return']['sha256']
|
||||
|
||||
result = self.vm_a.qmp('migrate-set-capabilities',
|
||||
capabilities=[bitmaps_cap, events_cap])
|
||||
# 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=[bitmaps_cap])
|
||||
result = self.vm_b.qmp('migrate-set-capabilities', capabilities=caps)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
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')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
while True:
|
||||
event = self.vm_a.event_wait('MIGRATION')
|
||||
if event['data']['status'] == 'completed':
|
||||
break
|
||||
event_resume = self.vm_b.event_wait('RESUME')
|
||||
self.vm_b_events.append(event_resume)
|
||||
return event_resume
|
||||
|
||||
s = 0x8000
|
||||
while s < write_size:
|
||||
self.vm_b.hmp_qemu_io('drive0', 'write %d %d' % (s, chunk))
|
||||
s += 0x10000
|
||||
def test_postcopy_success(self):
|
||||
event_resume = self.start_postcopy()
|
||||
|
||||
result = self.vm_b.qmp('query-block');
|
||||
while len(result['return'][0]['dirty-bitmaps']) > 1:
|
||||
time.sleep(2)
|
||||
result = self.vm_b.qmp('query-block');
|
||||
# enabled bitmaps should be updated
|
||||
apply_discards(self.vm_b, discards2)
|
||||
|
||||
result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||
node='drive0', name='bitmap')
|
||||
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',
|
||||
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__':
|
||||
iotests.main(supported_fmts=['qcow2'], supported_cache_modes=['none'],
|
||||
supported_protocols=['file'])
|
||||
iotests.main(supported_fmts=['qcow2'])
|
||||
|
@ -1,5 +1,5 @@
|
||||
.
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 tests
|
||||
Ran 3 tests
|
||||
|
||||
OK
|
||||
|
@ -112,7 +112,7 @@
|
||||
088 rw quick
|
||||
089 rw auto quick
|
||||
090 rw auto quick
|
||||
091 rw migration
|
||||
091 rw migration quick
|
||||
092 rw quick
|
||||
093 throttle
|
||||
094 rw quick
|
||||
@ -186,7 +186,7 @@
|
||||
162 quick
|
||||
163 rw
|
||||
165 rw quick
|
||||
169 rw quick migration
|
||||
169 rw migration
|
||||
170 rw auto quick
|
||||
171 rw quick
|
||||
172 auto
|
||||
@ -197,9 +197,9 @@
|
||||
177 rw auto quick
|
||||
178 img
|
||||
179 rw auto quick
|
||||
181 rw auto migration
|
||||
181 rw auto migration quick
|
||||
182 rw quick
|
||||
183 rw migration
|
||||
183 rw migration quick
|
||||
184 rw auto quick
|
||||
185 rw
|
||||
186 rw auto
|
||||
@ -216,9 +216,9 @@
|
||||
198 rw
|
||||
199 rw migration
|
||||
200 rw
|
||||
201 rw migration
|
||||
201 rw migration quick
|
||||
202 rw quick
|
||||
203 rw auto migration
|
||||
203 rw auto migration quick
|
||||
204 rw quick
|
||||
205 rw quick
|
||||
206 rw
|
||||
|
Loading…
Reference in New Issue
Block a user