mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Merge tag 'migration-20250502-pull-request' of https://gitlab.com/peterx/qemu into staging
Migration pull request - Prasad's few pre-requisite patches from multifd+postcopy enablement series - Markus's fix on a latent bug for tls_authz setup - Zhijian's latest RDMA series (includes the rdma soft-RoCE unit test) - Jack's RDMA migration patch to re-enable ipv6 - Thomas's vmstate static checker update on rename field in acpi/ghes - Peter's postcopy preempt optimization for locality hint # -----BEGIN PGP SIGNATURE----- # # iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCaBT1DBIccGV0ZXJ4QHJl # ZGhhdC5jb20ACgkQO1/MzfOr1wbp5QD8DIxndg/ssr2s+jb4T3tLHj5887FqH9P3 # vU8aoppi4dkA/iYifF8eK+jmhh4yEAP+/NzbmDy+kLO7uEAJDIK+Z/UM # =Ae6/ # -----END PGP SIGNATURE----- # gpg: Signature made Fri 02 May 2025 12:38:36 EDT # gpg: using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706 # gpg: issuer "peterx@redhat.com" # gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [full] # gpg: aka "Peter Xu <peterx@redhat.com>" [full] # Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D D1A9 3B5F CCCD F3AB D706 * tag 'migration-20250502-pull-request' of https://gitlab.com/peterx/qemu: scripts/vmstate-static-checker.py: Allow new name for ghes_addr_le field migration/rdma: Remove qemu_rdma_broken_ipv6_kernel migration/postcopy: Spatial locality page hint for preempt mode tests/qtest/migration: consolidate set capabilities migration/ram: Implement save_postcopy_prepare() migration: Add save_postcopy_prepare() savevm handler migration: refactor channel discovery mechanism migration/multifd: move macros to multifd header migration: Fix latent bug in migrate_params_test_apply() migration: Add qtest for migration over RDMA migration: Unfold control_save_page() migration/rdma: Remove redundant migration_in_postcopy checks migration: disable RDMA + postcopy-ram migration: check RDMA and capabilities are compatible on both sides Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
@@ -3538,6 +3538,7 @@ R: Li Zhijian <lizhijian@fujitsu.com>
|
||||
R: Peter Xu <peterx@redhat.com>
|
||||
S: Odd Fixes
|
||||
F: migration/rdma*
|
||||
F: scripts/rdma-migration-helper.sh
|
||||
|
||||
Migration dirty limit and dirty page rate
|
||||
M: Hyman Huang <yong.huang@smartx.com>
|
||||
|
||||
@@ -189,6 +189,21 @@ typedef struct SaveVMHandlers {
|
||||
|
||||
/* This runs outside the BQL! */
|
||||
|
||||
/**
|
||||
* @save_postcopy_prepare
|
||||
*
|
||||
* This hook will be invoked on the source side right before switching
|
||||
* to postcopy (before VM stopped).
|
||||
*
|
||||
* @f: QEMUFile where to send the data
|
||||
* @opaque: Data pointer passed to register_savevm_live()
|
||||
* @errp: Error** used to report error message
|
||||
*
|
||||
* Returns: true if succeeded, false if error occured. When false is
|
||||
* returned, @errp must be set.
|
||||
*/
|
||||
bool (*save_postcopy_prepare)(QEMUFile *f, void *opaque, Error **errp);
|
||||
|
||||
/**
|
||||
* @state_pending_estimate
|
||||
*
|
||||
|
||||
+94
-70
@@ -95,6 +95,9 @@ enum mig_rp_message_type {
|
||||
MIG_RP_MSG_MAX
|
||||
};
|
||||
|
||||
/* Migration channel types */
|
||||
enum { CH_MAIN, CH_MULTIFD, CH_POSTCOPY };
|
||||
|
||||
/* When we add fault tolerance, we could have several
|
||||
migrations at once. For now we don't need to add
|
||||
dynamic creation of migration */
|
||||
@@ -259,6 +262,24 @@ migration_channels_and_transport_compatible(MigrationAddress *addr,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
migration_capabilities_and_transport_compatible(MigrationAddress *addr,
|
||||
Error **errp)
|
||||
{
|
||||
if (addr->transport == MIGRATION_ADDRESS_TYPE_RDMA) {
|
||||
return migrate_rdma_caps_check(migrate_get_current()->capabilities,
|
||||
errp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool migration_transport_compatible(MigrationAddress *addr, Error **errp)
|
||||
{
|
||||
return migration_channels_and_transport_compatible(addr, errp) &&
|
||||
migration_capabilities_and_transport_compatible(addr, errp);
|
||||
}
|
||||
|
||||
static gint page_request_addr_cmp(gconstpointer ap, gconstpointer bp)
|
||||
{
|
||||
uintptr_t a = (uintptr_t) ap, b = (uintptr_t) bp;
|
||||
@@ -750,7 +771,7 @@ static void qemu_start_incoming_migration(const char *uri, bool has_channels,
|
||||
}
|
||||
|
||||
/* transport mechanism not suitable for migration? */
|
||||
if (!migration_channels_and_transport_compatible(addr, errp)) {
|
||||
if (!migration_transport_compatible(addr, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -769,14 +790,6 @@ static void qemu_start_incoming_migration(const char *uri, bool has_channels,
|
||||
}
|
||||
#ifdef CONFIG_RDMA
|
||||
} else if (addr->transport == MIGRATION_ADDRESS_TYPE_RDMA) {
|
||||
if (migrate_xbzrle()) {
|
||||
error_setg(errp, "RDMA and XBZRLE can't be used together");
|
||||
return;
|
||||
}
|
||||
if (migrate_multifd()) {
|
||||
error_setg(errp, "RDMA and multifd can't be used together");
|
||||
return;
|
||||
}
|
||||
rdma_start_incoming_migration(&addr->u.rdma, errp);
|
||||
#endif
|
||||
} else if (addr->transport == MIGRATION_ADDRESS_TYPE_EXEC) {
|
||||
@@ -931,9 +944,8 @@ static void migration_incoming_setup(QEMUFile *f)
|
||||
{
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
|
||||
if (!mis->from_src_file) {
|
||||
mis->from_src_file = f;
|
||||
}
|
||||
assert(!mis->from_src_file);
|
||||
mis->from_src_file = f;
|
||||
qemu_file_set_blocking(f, false);
|
||||
}
|
||||
|
||||
@@ -985,28 +997,19 @@ void migration_fd_process_incoming(QEMUFile *f)
|
||||
migration_incoming_process();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true when we want to start a new incoming migration process,
|
||||
* false otherwise.
|
||||
*/
|
||||
static bool migration_should_start_incoming(bool main_channel)
|
||||
static bool migration_has_main_and_multifd_channels(void)
|
||||
{
|
||||
/* Multifd doesn't start unless all channels are established */
|
||||
if (migrate_multifd()) {
|
||||
return migration_has_all_channels();
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
if (!mis->from_src_file) {
|
||||
/* main channel not established */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Preempt channel only starts when the main channel is created */
|
||||
if (migrate_postcopy_preempt()) {
|
||||
return main_channel;
|
||||
if (migrate_multifd() && !multifd_recv_all_channels_created()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* For all the rest types of migration, we should only reach here when
|
||||
* it's the main channel that's being created, and we should always
|
||||
* proceed with this channel.
|
||||
*/
|
||||
assert(main_channel);
|
||||
/* main and all multifd channels are established */
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1015,59 +1018,81 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
Error *local_err = NULL;
|
||||
QEMUFile *f;
|
||||
bool default_channel = true;
|
||||
uint8_t channel;
|
||||
uint32_t channel_magic = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (migrate_multifd() && !migrate_mapped_ram() &&
|
||||
!migrate_postcopy_ram() &&
|
||||
qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_READ_MSG_PEEK)) {
|
||||
/*
|
||||
* With multiple channels, it is possible that we receive channels
|
||||
* out of order on destination side, causing incorrect mapping of
|
||||
* source channels on destination side. Check channel MAGIC to
|
||||
* decide type of channel. Please note this is best effort, postcopy
|
||||
* preempt channel does not send any magic number so avoid it for
|
||||
* postcopy live migration. Also tls live migration already does
|
||||
* tls handshake while initializing main channel so with tls this
|
||||
* issue is not possible.
|
||||
*/
|
||||
ret = migration_channel_read_peek(ioc, (void *)&channel_magic,
|
||||
sizeof(channel_magic), errp);
|
||||
if (!migration_has_main_and_multifd_channels()) {
|
||||
if (qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_READ_MSG_PEEK)) {
|
||||
/*
|
||||
* With multiple channels, it is possible that we receive channels
|
||||
* out of order on destination side, causing incorrect mapping of
|
||||
* source channels on destination side. Check channel MAGIC to
|
||||
* decide type of channel. Please note this is best effort,
|
||||
* postcopy preempt channel does not send any magic number so
|
||||
* avoid it for postcopy live migration. Also tls live migration
|
||||
* already does tls handshake while initializing main channel so
|
||||
* with tls this issue is not possible.
|
||||
*/
|
||||
ret = migration_channel_read_peek(ioc, (void *)&channel_magic,
|
||||
sizeof(channel_magic), errp);
|
||||
if (ret != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
channel_magic = be32_to_cpu(channel_magic);
|
||||
if (channel_magic == QEMU_VM_FILE_MAGIC) {
|
||||
channel = CH_MAIN;
|
||||
} else if (channel_magic == MULTIFD_MAGIC) {
|
||||
assert(migrate_multifd());
|
||||
channel = CH_MULTIFD;
|
||||
} else if (!mis->from_src_file &&
|
||||
mis->state == MIGRATION_STATUS_POSTCOPY_PAUSED) {
|
||||
/* reconnect main channel for postcopy recovery */
|
||||
channel = CH_MAIN;
|
||||
} else {
|
||||
error_setg(errp, "unknown channel magic: %u", channel_magic);
|
||||
return;
|
||||
}
|
||||
} else if (mis->from_src_file && migrate_multifd()) {
|
||||
/*
|
||||
* Non-peekable channels like tls/file are processed as
|
||||
* multifd channels when multifd is enabled.
|
||||
*/
|
||||
channel = CH_MULTIFD;
|
||||
} else if (!mis->from_src_file) {
|
||||
channel = CH_MAIN;
|
||||
} else {
|
||||
error_setg(errp, "non-peekable channel used without multifd");
|
||||
return;
|
||||
}
|
||||
|
||||
default_channel = (channel_magic == cpu_to_be32(QEMU_VM_FILE_MAGIC));
|
||||
} else {
|
||||
default_channel = !mis->from_src_file;
|
||||
assert(migrate_postcopy_preempt());
|
||||
channel = CH_POSTCOPY;
|
||||
}
|
||||
|
||||
if (multifd_recv_setup(errp) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (default_channel) {
|
||||
if (channel == CH_MAIN) {
|
||||
f = qemu_file_new_input(ioc);
|
||||
migration_incoming_setup(f);
|
||||
} else {
|
||||
} else if (channel == CH_MULTIFD) {
|
||||
/* Multiple connections */
|
||||
assert(migration_needs_multiple_sockets());
|
||||
if (migrate_multifd()) {
|
||||
multifd_recv_new_channel(ioc, &local_err);
|
||||
} else {
|
||||
assert(migrate_postcopy_preempt());
|
||||
f = qemu_file_new_input(ioc);
|
||||
postcopy_preempt_new_channel(mis, f);
|
||||
}
|
||||
multifd_recv_new_channel(ioc, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
} else if (channel == CH_POSTCOPY) {
|
||||
assert(!mis->postcopy_qemufile_dst);
|
||||
f = qemu_file_new_input(ioc);
|
||||
postcopy_preempt_new_channel(mis, f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (migration_should_start_incoming(default_channel)) {
|
||||
if (migration_has_main_and_multifd_channels()) {
|
||||
/* If it's a recovery, we're done */
|
||||
if (postcopy_try_recover()) {
|
||||
return;
|
||||
@@ -1084,18 +1109,13 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
|
||||
*/
|
||||
bool migration_has_all_channels(void)
|
||||
{
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
|
||||
if (!mis->from_src_file) {
|
||||
if (!migration_has_main_and_multifd_channels()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (migrate_multifd()) {
|
||||
return multifd_recv_all_channels_created();
|
||||
}
|
||||
|
||||
if (migrate_postcopy_preempt()) {
|
||||
return mis->postcopy_qemufile_dst != NULL;
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
if (migrate_postcopy_preempt() && !mis->postcopy_qemufile_dst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2208,7 +2228,7 @@ void qmp_migrate(const char *uri, bool has_channels,
|
||||
}
|
||||
|
||||
/* transport mechanism not suitable for migration? */
|
||||
if (!migration_channels_and_transport_compatible(addr, errp)) {
|
||||
if (!migration_transport_compatible(addr, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2707,6 +2727,10 @@ static int postcopy_start(MigrationState *ms, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
if (!qemu_savevm_state_postcopy_prepare(ms->to_dst_file, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
trace_postcopy_start();
|
||||
bql_lock();
|
||||
trace_postcopy_start_set_run();
|
||||
|
||||
@@ -36,11 +36,6 @@
|
||||
#include "io/channel-socket.h"
|
||||
#include "yank_functions.h"
|
||||
|
||||
/* Multiple fd's */
|
||||
|
||||
#define MULTIFD_MAGIC 0x11223344U
|
||||
#define MULTIFD_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
|
||||
@@ -49,6 +49,11 @@ bool multifd_queue_page(RAMBlock *block, ram_addr_t offset);
|
||||
bool multifd_recv(void);
|
||||
MultiFDRecvData *multifd_get_recv_data(void);
|
||||
|
||||
/* Multiple fd's */
|
||||
|
||||
#define MULTIFD_MAGIC 0x11223344U
|
||||
#define MULTIFD_VERSION 1
|
||||
|
||||
/* Multifd Compression flags */
|
||||
#define MULTIFD_FLAG_SYNC (1 << 0)
|
||||
|
||||
|
||||
@@ -448,6 +448,24 @@ static bool migrate_incoming_started(void)
|
||||
return !!migration_incoming_get_current()->transport_data;
|
||||
}
|
||||
|
||||
bool migrate_rdma_caps_check(bool *caps, Error **errp)
|
||||
{
|
||||
if (caps[MIGRATION_CAPABILITY_XBZRLE]) {
|
||||
error_setg(errp, "RDMA and XBZRLE can't be used together");
|
||||
return false;
|
||||
}
|
||||
if (caps[MIGRATION_CAPABILITY_MULTIFD]) {
|
||||
error_setg(errp, "RDMA and multifd can't be used together");
|
||||
return false;
|
||||
}
|
||||
if (caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
|
||||
error_setg(errp, "RDMA and postcopy-ram can't be used together");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @migration_caps_check - check capability compatibility
|
||||
*
|
||||
@@ -611,6 +629,13 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* On destination side, check the cases that capability is being set
|
||||
* after incoming thread has started.
|
||||
*/
|
||||
if (migrate_rdma() && !migrate_rdma_caps_check(new_caps, errp)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1193,6 +1218,11 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
|
||||
dest->tls_hostname = params->tls_hostname->u.s;
|
||||
}
|
||||
|
||||
if (params->tls_authz) {
|
||||
assert(params->tls_authz->type == QTYPE_QSTRING);
|
||||
dest->tls_authz = params->tls_authz->u.s;
|
||||
}
|
||||
|
||||
if (params->has_max_bandwidth) {
|
||||
dest->max_bandwidth = params->max_bandwidth;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ bool migrate_tls(void);
|
||||
|
||||
/* capabilities helpers */
|
||||
|
||||
bool migrate_rdma_caps_check(bool *caps, Error **errp);
|
||||
bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp);
|
||||
|
||||
/* parameters */
|
||||
|
||||
+140
-28
@@ -91,6 +91,36 @@
|
||||
|
||||
XBZRLECacheStats xbzrle_counters;
|
||||
|
||||
/*
|
||||
* This structure locates a specific location of a guest page. In QEMU,
|
||||
* it's described in a tuple of (ramblock, offset).
|
||||
*/
|
||||
struct PageLocation {
|
||||
RAMBlock *block;
|
||||
unsigned long offset;
|
||||
};
|
||||
typedef struct PageLocation PageLocation;
|
||||
|
||||
/**
|
||||
* PageLocationHint: describes a hint to a page location
|
||||
*
|
||||
* @valid set if the hint is vaild and to be consumed
|
||||
* @location: the hint content
|
||||
*
|
||||
* In postcopy preempt mode, the urgent channel may provide hints to the
|
||||
* background channel, so that QEMU source can try to migrate whatever is
|
||||
* right after the requested urgent pages.
|
||||
*
|
||||
* This is based on the assumption that the VM (already running on the
|
||||
* destination side) tends to access the memory with spatial locality.
|
||||
* This is also the default behavior of vanilla postcopy (preempt off).
|
||||
*/
|
||||
struct PageLocationHint {
|
||||
bool valid;
|
||||
PageLocation location;
|
||||
};
|
||||
typedef struct PageLocationHint PageLocationHint;
|
||||
|
||||
/* used by the search for pages to send */
|
||||
struct PageSearchStatus {
|
||||
/* The migration channel used for a specific host page */
|
||||
@@ -395,6 +425,13 @@ struct RAMState {
|
||||
* RAM migration.
|
||||
*/
|
||||
unsigned int postcopy_bmap_sync_requested;
|
||||
/*
|
||||
* Page hint during postcopy when preempt mode is on. Return path
|
||||
* thread sets it, while background migration thread consumes it.
|
||||
*
|
||||
* Protected by @bitmap_mutex.
|
||||
*/
|
||||
PageLocationHint page_hint;
|
||||
};
|
||||
typedef struct RAMState RAMState;
|
||||
|
||||
@@ -1143,32 +1180,6 @@ static int save_zero_page(RAMState *rs, PageSearchStatus *pss,
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* @pages: the number of pages written by the control path,
|
||||
* < 0 - error
|
||||
* > 0 - number of pages written
|
||||
*
|
||||
* Return true if the pages has been saved, otherwise false is returned.
|
||||
*/
|
||||
static bool control_save_page(PageSearchStatus *pss,
|
||||
ram_addr_t offset, int *pages)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = rdma_control_save_page(pss->pss_channel, pss->block->offset, offset,
|
||||
TARGET_PAGE_SIZE);
|
||||
if (ret == RAM_SAVE_CONTROL_NOT_SUPP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret == RAM_SAVE_CONTROL_DELAYED) {
|
||||
*pages = 1;
|
||||
return true;
|
||||
}
|
||||
*pages = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* directly send the page to the stream
|
||||
*
|
||||
@@ -1965,7 +1976,13 @@ static int ram_save_target_page(RAMState *rs, PageSearchStatus *pss)
|
||||
int res;
|
||||
|
||||
/* Hand over to RDMA first */
|
||||
if (control_save_page(pss, offset, &res)) {
|
||||
if (migrate_rdma()) {
|
||||
res = rdma_control_save_page(pss->pss_channel, pss->block->offset,
|
||||
offset, TARGET_PAGE_SIZE);
|
||||
|
||||
if (res == RAM_SAVE_CONTROL_DELAYED) {
|
||||
res = 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -2039,6 +2056,21 @@ static void pss_host_page_finish(PageSearchStatus *pss)
|
||||
pss->host_page_start = pss->host_page_end = 0;
|
||||
}
|
||||
|
||||
static void ram_page_hint_update(RAMState *rs, PageSearchStatus *pss)
|
||||
{
|
||||
PageLocationHint *hint = &rs->page_hint;
|
||||
|
||||
/* If there's a pending hint not consumed, don't bother */
|
||||
if (hint->valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Provide a hint to the background stream otherwise */
|
||||
hint->location.block = pss->block;
|
||||
hint->location.offset = pss->page;
|
||||
hint->valid = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send an urgent host page specified by `pss'. Need to be called with
|
||||
* bitmap_mutex held.
|
||||
@@ -2084,6 +2116,7 @@ out:
|
||||
/* For urgent requests, flush immediately if sent */
|
||||
if (sent) {
|
||||
qemu_fflush(pss->pss_channel);
|
||||
ram_page_hint_update(rs, pss);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -2171,6 +2204,30 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss)
|
||||
return (res < 0 ? res : pages);
|
||||
}
|
||||
|
||||
static bool ram_page_hint_valid(RAMState *rs)
|
||||
{
|
||||
/* There's only page hint during postcopy preempt mode */
|
||||
if (!postcopy_preempt_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rs->page_hint.valid;
|
||||
}
|
||||
|
||||
static void ram_page_hint_collect(RAMState *rs, RAMBlock **block,
|
||||
unsigned long *page)
|
||||
{
|
||||
PageLocationHint *hint = &rs->page_hint;
|
||||
|
||||
assert(hint->valid);
|
||||
|
||||
*block = hint->location.block;
|
||||
*page = hint->location.offset;
|
||||
|
||||
/* Mark the hint consumed */
|
||||
hint->valid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ram_find_and_save_block: finds a dirty page and sends it to f
|
||||
*
|
||||
@@ -2187,6 +2244,8 @@ static int ram_save_host_page(RAMState *rs, PageSearchStatus *pss)
|
||||
static int ram_find_and_save_block(RAMState *rs)
|
||||
{
|
||||
PageSearchStatus *pss = &rs->pss[RAM_CHANNEL_PRECOPY];
|
||||
unsigned long next_page;
|
||||
RAMBlock *next_block;
|
||||
int pages = 0;
|
||||
|
||||
/* No dirty page as there is zero RAM */
|
||||
@@ -2206,7 +2265,14 @@ static int ram_find_and_save_block(RAMState *rs)
|
||||
rs->last_page = 0;
|
||||
}
|
||||
|
||||
pss_init(pss, rs->last_seen_block, rs->last_page);
|
||||
if (ram_page_hint_valid(rs)) {
|
||||
ram_page_hint_collect(rs, &next_block, &next_page);
|
||||
} else {
|
||||
next_block = rs->last_seen_block;
|
||||
next_page = rs->last_page;
|
||||
}
|
||||
|
||||
pss_init(pss, next_block, next_page);
|
||||
|
||||
while (true){
|
||||
if (!get_queued_page(rs, pss)) {
|
||||
@@ -2339,6 +2405,13 @@ static void ram_save_cleanup(void *opaque)
|
||||
ram_state_cleanup(rsp);
|
||||
}
|
||||
|
||||
static void ram_page_hint_reset(PageLocationHint *hint)
|
||||
{
|
||||
hint->location.block = NULL;
|
||||
hint->location.offset = 0;
|
||||
hint->valid = false;
|
||||
}
|
||||
|
||||
static void ram_state_reset(RAMState *rs)
|
||||
{
|
||||
int i;
|
||||
@@ -2351,6 +2424,8 @@ static void ram_state_reset(RAMState *rs)
|
||||
rs->last_page = 0;
|
||||
rs->last_version = ram_list.version;
|
||||
rs->xbzrle_started = false;
|
||||
|
||||
ram_page_hint_reset(&rs->page_hint);
|
||||
}
|
||||
|
||||
#define MAX_WAIT 50 /* ms, half buffered_file limit */
|
||||
@@ -4418,6 +4493,42 @@ static int ram_resume_prepare(MigrationState *s, void *opaque)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ram_save_postcopy_prepare(QEMUFile *f, void *opaque, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (migrate_multifd()) {
|
||||
/*
|
||||
* When multifd is enabled, source QEMU needs to make sure all the
|
||||
* pages queued before postcopy starts have been flushed.
|
||||
*
|
||||
* The load of these pages must happen before switching to postcopy.
|
||||
* It's because loading of guest pages (so far) in multifd recv
|
||||
* threads is still non-atomic, so the load cannot happen with vCPUs
|
||||
* running on the destination side.
|
||||
*
|
||||
* This flush and sync will guarantee that those pages are loaded
|
||||
* _before_ postcopy starts on the destination. The rationale is,
|
||||
* this happens before VM stops (and before source QEMU sends all
|
||||
* the rest of the postcopy messages). So when the destination QEMU
|
||||
* receives the postcopy messages, it must have received the sync
|
||||
* message on the main channel (either RAM_SAVE_FLAG_MULTIFD_FLUSH,
|
||||
* or RAM_SAVE_FLAG_EOS), and such message would guarantee that
|
||||
* all previous guest pages queued in the multifd channels are
|
||||
* completely loaded.
|
||||
*/
|
||||
ret = multifd_ram_flush_and_sync(f);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "%s: multifd flush and sync failed", __func__);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qemu_put_be64(f, RAM_SAVE_FLAG_EOS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void postcopy_preempt_shutdown_file(MigrationState *s)
|
||||
{
|
||||
qemu_put_be64(s->postcopy_qemufile_src, RAM_SAVE_FLAG_EOS);
|
||||
@@ -4437,6 +4548,7 @@ static SaveVMHandlers savevm_ram_handlers = {
|
||||
.load_setup = ram_load_setup,
|
||||
.load_cleanup = ram_load_cleanup,
|
||||
.resume_prepare = ram_resume_prepare,
|
||||
.save_postcopy_prepare = ram_save_postcopy_prepare,
|
||||
};
|
||||
|
||||
static void ram_mig_ram_block_resized(RAMBlockNotifier *n, void *host,
|
||||
|
||||
+8
-183
@@ -767,149 +767,6 @@ static void qemu_rdma_dump_gid(const char *who, struct rdma_cm_id *id)
|
||||
trace_qemu_rdma_dump_gid(who, sgid, dgid);
|
||||
}
|
||||
|
||||
/*
|
||||
* As of now, IPv6 over RoCE / iWARP is not supported by linux.
|
||||
* We will try the next addrinfo struct, and fail if there are
|
||||
* no other valid addresses to bind against.
|
||||
*
|
||||
* If user is listening on '[::]', then we will not have a opened a device
|
||||
* yet and have no way of verifying if the device is RoCE or not.
|
||||
*
|
||||
* In this case, the source VM will throw an error for ALL types of
|
||||
* connections (both IPv4 and IPv6) if the destination machine does not have
|
||||
* a regular infiniband network available for use.
|
||||
*
|
||||
* The only way to guarantee that an error is thrown for broken kernels is
|
||||
* for the management software to choose a *specific* interface at bind time
|
||||
* and validate what time of hardware it is.
|
||||
*
|
||||
* Unfortunately, this puts the user in a fix:
|
||||
*
|
||||
* If the source VM connects with an IPv4 address without knowing that the
|
||||
* destination has bound to '[::]' the migration will unconditionally fail
|
||||
* unless the management software is explicitly listening on the IPv4
|
||||
* address while using a RoCE-based device.
|
||||
*
|
||||
* If the source VM connects with an IPv6 address, then we're OK because we can
|
||||
* throw an error on the source (and similarly on the destination).
|
||||
*
|
||||
* But in mixed environments, this will be broken for a while until it is fixed
|
||||
* inside linux.
|
||||
*
|
||||
* We do provide a *tiny* bit of help in this function: We can list all of the
|
||||
* devices in the system and check to see if all the devices are RoCE or
|
||||
* Infiniband.
|
||||
*
|
||||
* If we detect that we have a *pure* RoCE environment, then we can safely
|
||||
* thrown an error even if the management software has specified '[::]' as the
|
||||
* bind address.
|
||||
*
|
||||
* However, if there is are multiple hetergeneous devices, then we cannot make
|
||||
* this assumption and the user just has to be sure they know what they are
|
||||
* doing.
|
||||
*
|
||||
* Patches are being reviewed on linux-rdma.
|
||||
*/
|
||||
static int qemu_rdma_broken_ipv6_kernel(struct ibv_context *verbs, Error **errp)
|
||||
{
|
||||
/* This bug only exists in linux, to our knowledge. */
|
||||
#ifdef CONFIG_LINUX
|
||||
struct ibv_port_attr port_attr;
|
||||
|
||||
/*
|
||||
* Verbs are only NULL if management has bound to '[::]'.
|
||||
*
|
||||
* Let's iterate through all the devices and see if there any pure IB
|
||||
* devices (non-ethernet).
|
||||
*
|
||||
* If not, then we can safely proceed with the migration.
|
||||
* Otherwise, there are no guarantees until the bug is fixed in linux.
|
||||
*/
|
||||
if (!verbs) {
|
||||
int num_devices;
|
||||
struct ibv_device **dev_list = ibv_get_device_list(&num_devices);
|
||||
bool roce_found = false;
|
||||
bool ib_found = false;
|
||||
|
||||
for (int x = 0; x < num_devices; x++) {
|
||||
verbs = ibv_open_device(dev_list[x]);
|
||||
/*
|
||||
* ibv_open_device() is not documented to set errno. If
|
||||
* it does, it's somebody else's doc bug. If it doesn't,
|
||||
* the use of errno below is wrong.
|
||||
* TODO Find out whether ibv_open_device() sets errno.
|
||||
*/
|
||||
if (!verbs) {
|
||||
if (errno == EPERM) {
|
||||
continue;
|
||||
} else {
|
||||
error_setg_errno(errp, errno,
|
||||
"could not open RDMA device context");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ibv_query_port(verbs, 1, &port_attr)) {
|
||||
ibv_close_device(verbs);
|
||||
error_setg(errp,
|
||||
"RDMA ERROR: Could not query initial IB port");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (port_attr.link_layer == IBV_LINK_LAYER_INFINIBAND) {
|
||||
ib_found = true;
|
||||
} else if (port_attr.link_layer == IBV_LINK_LAYER_ETHERNET) {
|
||||
roce_found = true;
|
||||
}
|
||||
|
||||
ibv_close_device(verbs);
|
||||
|
||||
}
|
||||
|
||||
if (roce_found) {
|
||||
if (ib_found) {
|
||||
warn_report("migrations may fail:"
|
||||
" IPv6 over RoCE / iWARP in linux"
|
||||
" is broken. But since you appear to have a"
|
||||
" mixed RoCE / IB environment, be sure to only"
|
||||
" migrate over the IB fabric until the kernel "
|
||||
" fixes the bug.");
|
||||
} else {
|
||||
error_setg(errp, "RDMA ERROR: "
|
||||
"You only have RoCE / iWARP devices in your systems"
|
||||
" and your management software has specified '[::]'"
|
||||
", but IPv6 over RoCE / iWARP is not supported in Linux.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have a verbs context, that means that some other than '[::]' was
|
||||
* used by the management software for binding. In which case we can
|
||||
* actually warn the user about a potentially broken kernel.
|
||||
*/
|
||||
|
||||
/* IB ports start with 1, not 0 */
|
||||
if (ibv_query_port(verbs, 1, &port_attr)) {
|
||||
error_setg(errp, "RDMA ERROR: Could not query initial IB port");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (port_attr.link_layer == IBV_LINK_LAYER_ETHERNET) {
|
||||
error_setg(errp, "RDMA ERROR: "
|
||||
"Linux kernel's RoCE / iWARP does not support IPv6 "
|
||||
"(but patches on linux-rdma in progress)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Figure out which RDMA device corresponds to the requested IP hostname
|
||||
* Also create the initial connection manager identifiers for opening
|
||||
@@ -917,7 +774,6 @@ static int qemu_rdma_broken_ipv6_kernel(struct ibv_context *verbs, Error **errp)
|
||||
*/
|
||||
static int qemu_rdma_resolve_host(RDMAContext *rdma, Error **errp)
|
||||
{
|
||||
Error *err = NULL;
|
||||
int ret;
|
||||
struct rdma_addrinfo *res;
|
||||
char port_str[16];
|
||||
@@ -953,9 +809,8 @@ static int qemu_rdma_resolve_host(RDMAContext *rdma, Error **errp)
|
||||
goto err_resolve_get_addr;
|
||||
}
|
||||
|
||||
/* Try all addresses, saving the first error in @err */
|
||||
/* Try all addresses, exit loop on first success of resolving address */
|
||||
for (struct rdma_addrinfo *e = res; e != NULL; e = e->ai_next) {
|
||||
Error **local_errp = err ? NULL : &err;
|
||||
|
||||
inet_ntop(e->ai_family,
|
||||
&((struct sockaddr_in *) e->ai_dst_addr)->sin_addr, ip, sizeof ip);
|
||||
@@ -964,25 +819,12 @@ static int qemu_rdma_resolve_host(RDMAContext *rdma, Error **errp)
|
||||
ret = rdma_resolve_addr(rdma->cm_id, NULL, e->ai_dst_addr,
|
||||
RDMA_RESOLVE_TIMEOUT_MS);
|
||||
if (ret >= 0) {
|
||||
if (e->ai_family == AF_INET6) {
|
||||
ret = qemu_rdma_broken_ipv6_kernel(rdma->cm_id->verbs,
|
||||
local_errp);
|
||||
if (ret < 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
error_free(err);
|
||||
goto route;
|
||||
}
|
||||
}
|
||||
|
||||
rdma_freeaddrinfo(res);
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
} else {
|
||||
error_setg(errp, "RDMA ERROR: could not resolve address %s",
|
||||
rdma->host);
|
||||
}
|
||||
error_setg(errp, "RDMA ERROR: could not resolve address %s", rdma->host);
|
||||
goto err_resolve_get_addr;
|
||||
|
||||
route:
|
||||
@@ -2611,7 +2453,6 @@ err_rdma_source_connect:
|
||||
|
||||
static int qemu_rdma_dest_init(RDMAContext *rdma, Error **errp)
|
||||
{
|
||||
Error *err = NULL;
|
||||
int ret;
|
||||
struct rdma_cm_id *listen_id;
|
||||
char ip[40] = "unknown";
|
||||
@@ -2661,9 +2502,8 @@ static int qemu_rdma_dest_init(RDMAContext *rdma, Error **errp)
|
||||
goto err_dest_init_bind_addr;
|
||||
}
|
||||
|
||||
/* Try all addresses, saving the first error in @err */
|
||||
/* Try all addresses */
|
||||
for (e = res; e != NULL; e = e->ai_next) {
|
||||
Error **local_errp = err ? NULL : &err;
|
||||
|
||||
inet_ntop(e->ai_family,
|
||||
&((struct sockaddr_in *) e->ai_dst_addr)->sin_addr, ip, sizeof ip);
|
||||
@@ -2672,24 +2512,12 @@ static int qemu_rdma_dest_init(RDMAContext *rdma, Error **errp)
|
||||
if (ret < 0) {
|
||||
continue;
|
||||
}
|
||||
if (e->ai_family == AF_INET6) {
|
||||
ret = qemu_rdma_broken_ipv6_kernel(listen_id->verbs,
|
||||
local_errp);
|
||||
if (ret < 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
error_free(err);
|
||||
break;
|
||||
}
|
||||
|
||||
rdma_freeaddrinfo(res);
|
||||
if (!e) {
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
} else {
|
||||
error_setg(errp, "RDMA ERROR: Error: could not rdma_bind_addr!");
|
||||
}
|
||||
error_setg(errp, "RDMA ERROR: Error: could not rdma_bind_addr!");
|
||||
goto err_dest_init_bind_addr;
|
||||
}
|
||||
|
||||
@@ -3284,14 +3112,11 @@ err:
|
||||
int rdma_control_save_page(QEMUFile *f, ram_addr_t block_offset,
|
||||
ram_addr_t offset, size_t size)
|
||||
{
|
||||
if (!migrate_rdma() || migration_in_postcopy()) {
|
||||
return RAM_SAVE_CONTROL_NOT_SUPP;
|
||||
}
|
||||
assert(migrate_rdma());
|
||||
|
||||
int ret = qemu_rdma_save_page(f, block_offset, offset, size);
|
||||
|
||||
if (ret != RAM_SAVE_CONTROL_DELAYED &&
|
||||
ret != RAM_SAVE_CONTROL_NOT_SUPP) {
|
||||
if (ret != RAM_SAVE_CONTROL_DELAYED) {
|
||||
if (ret < 0) {
|
||||
qemu_file_set_error(f, ret);
|
||||
}
|
||||
@@ -3829,7 +3654,7 @@ int rdma_block_notification_handle(QEMUFile *f, const char *name)
|
||||
|
||||
int rdma_registration_start(QEMUFile *f, uint64_t flags)
|
||||
{
|
||||
if (!migrate_rdma() || migration_in_postcopy()) {
|
||||
if (!migrate_rdma()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3861,7 +3686,7 @@ int rdma_registration_stop(QEMUFile *f, uint64_t flags)
|
||||
RDMAControlHeader head = { .len = 0, .repeat = 1 };
|
||||
int ret;
|
||||
|
||||
if (!migrate_rdma() || migration_in_postcopy()) {
|
||||
if (!migrate_rdma()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -33,7 +33,6 @@ void rdma_start_incoming_migration(InetSocketAddress *host_port, Error **errp);
|
||||
#define RAM_CONTROL_ROUND 1
|
||||
#define RAM_CONTROL_FINISH 3
|
||||
|
||||
#define RAM_SAVE_CONTROL_NOT_SUPP -1000
|
||||
#define RAM_SAVE_CONTROL_DELAYED -2000
|
||||
|
||||
#ifdef CONFIG_RDMA
|
||||
@@ -56,7 +55,7 @@ static inline
|
||||
int rdma_control_save_page(QEMUFile *f, ram_addr_t block_offset,
|
||||
ram_addr_t offset, size_t size)
|
||||
{
|
||||
return RAM_SAVE_CONTROL_NOT_SUPP;
|
||||
g_assert_not_reached();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1523,6 +1523,39 @@ void qemu_savevm_state_complete_postcopy(QEMUFile *f)
|
||||
qemu_fflush(f);
|
||||
}
|
||||
|
||||
bool qemu_savevm_state_postcopy_prepare(QEMUFile *f, Error **errp)
|
||||
{
|
||||
SaveStateEntry *se;
|
||||
bool ret;
|
||||
|
||||
QTAILQ_FOREACH(se, &savevm_state.handlers, entry) {
|
||||
if (!se->ops || !se->ops->save_postcopy_prepare) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (se->ops->is_active) {
|
||||
if (!se->ops->is_active(se->opaque)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
trace_savevm_section_start(se->idstr, se->section_id);
|
||||
|
||||
save_section_header(f, se, QEMU_VM_SECTION_PART);
|
||||
ret = se->ops->save_postcopy_prepare(f, se->opaque, errp);
|
||||
save_section_footer(f, se);
|
||||
|
||||
trace_savevm_section_end(se->idstr, se->section_id, ret);
|
||||
|
||||
if (!ret) {
|
||||
assert(*errp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy)
|
||||
{
|
||||
int64_t start_ts_each, end_ts_each;
|
||||
|
||||
@@ -45,6 +45,7 @@ void qemu_savevm_state_pending_exact(uint64_t *must_precopy,
|
||||
void qemu_savevm_state_pending_estimate(uint64_t *must_precopy,
|
||||
uint64_t *can_postcopy);
|
||||
int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy);
|
||||
bool qemu_savevm_state_postcopy_prepare(QEMUFile *f, Error **errp);
|
||||
void qemu_savevm_send_ping(QEMUFile *f, uint32_t value);
|
||||
void qemu_savevm_send_open_return_path(QEMUFile *f);
|
||||
int qemu_savevm_send_packaged(QEMUFile *f, const uint8_t *buf, size_t len);
|
||||
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copied from blktests
|
||||
get_ipv4_addr()
|
||||
{
|
||||
ip -4 -o addr show dev "$1" |
|
||||
sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
|
||||
head -1 | tr -d '\n'
|
||||
}
|
||||
|
||||
# existing rdma interfaces
|
||||
rdma_interfaces()
|
||||
{
|
||||
rdma link show | sed -nE 's/^link .* netdev ([^ ]+).*$/\1 /p'
|
||||
}
|
||||
|
||||
# existing valid ipv4 interfaces
|
||||
ipv4_interfaces()
|
||||
{
|
||||
ip -o addr show | awk '/inet / {print $2}' | grep -v -w lo
|
||||
}
|
||||
|
||||
rdma_rxe_detect()
|
||||
{
|
||||
for r in $(rdma_interfaces)
|
||||
do
|
||||
ipv4_interfaces | grep -qw $r && get_ipv4_addr $r && return
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
rdma_rxe_setup()
|
||||
{
|
||||
for i in $(ipv4_interfaces)
|
||||
do
|
||||
rdma_interfaces | grep -qw $i && continue
|
||||
rdma link add "${i}_rxe" type rxe netdev "$i" && {
|
||||
echo "Setup new rdma/rxe ${i}_rxe for $i with $(get_ipv4_addr $i)"
|
||||
return
|
||||
}
|
||||
done
|
||||
|
||||
echo "Failed to setup any new rdma/rxe link" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
rdma_rxe_clean()
|
||||
{
|
||||
modprobe -r rdma_rxe
|
||||
}
|
||||
|
||||
operation=${1:-detect}
|
||||
|
||||
command -v rdma >/dev/null || {
|
||||
echo "Command 'rdma' is not available, please install it first." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$operation" == "setup" ] || [ "$operation" == "clean" ]; then
|
||||
[ "$UID" == 0 ] || {
|
||||
echo "Root privilege is required to setup/clean a rdma/rxe link" >&2
|
||||
exit 1
|
||||
}
|
||||
rdma_rxe_"$operation"
|
||||
elif [ "$operation" == "detect" ]; then
|
||||
rdma_rxe_detect
|
||||
else
|
||||
echo "Usage: $0 [setup | detect | clean]"
|
||||
fi
|
||||
@@ -42,6 +42,7 @@ def check_fields_match(name, s_field, d_field):
|
||||
# Some fields changed names between qemu versions. This list
|
||||
# is used to allow such changes in each section / description.
|
||||
changed_names = {
|
||||
'acpi-ghes': ['ghes_addr_le', 'hw_error_le'],
|
||||
'apic': ['timer', 'timer_expiry'],
|
||||
'e1000': ['dev', 'parent_obj'],
|
||||
'ehci': ['dev', 'pcidev'],
|
||||
|
||||
@@ -35,6 +35,9 @@ static void test_multifd_tcp_zstd(void)
|
||||
{
|
||||
MigrateCommon args = {
|
||||
.listen_uri = "defer",
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
.start_hook = migrate_hook_start_precopy_tcp_multifd_zstd,
|
||||
};
|
||||
test_precopy_common(&args);
|
||||
@@ -56,6 +59,9 @@ static void test_multifd_tcp_qatzip(void)
|
||||
{
|
||||
MigrateCommon args = {
|
||||
.listen_uri = "defer",
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
.start_hook = migrate_hook_start_precopy_tcp_multifd_qatzip,
|
||||
};
|
||||
test_precopy_common(&args);
|
||||
@@ -74,6 +80,9 @@ static void test_multifd_tcp_qpl(void)
|
||||
{
|
||||
MigrateCommon args = {
|
||||
.listen_uri = "defer",
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
.start_hook = migrate_hook_start_precopy_tcp_multifd_qpl,
|
||||
};
|
||||
test_precopy_common(&args);
|
||||
@@ -92,6 +101,9 @@ static void test_multifd_tcp_uadk(void)
|
||||
{
|
||||
MigrateCommon args = {
|
||||
.listen_uri = "defer",
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
.start_hook = migrate_hook_start_precopy_tcp_multifd_uadk,
|
||||
};
|
||||
test_precopy_common(&args);
|
||||
@@ -103,10 +115,6 @@ migrate_hook_start_xbzrle(QTestState *from,
|
||||
QTestState *to)
|
||||
{
|
||||
migrate_set_parameter_int(from, "xbzrle-cache-size", 33554432);
|
||||
|
||||
migrate_set_capability(from, "xbzrle", true);
|
||||
migrate_set_capability(to, "xbzrle", true);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -118,6 +126,9 @@ static void test_precopy_unix_xbzrle(void)
|
||||
.listen_uri = uri,
|
||||
.start_hook = migrate_hook_start_xbzrle,
|
||||
.iterations = 2,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_XBZRLE] = true,
|
||||
},
|
||||
/*
|
||||
* XBZRLE needs pages to be modified when doing the 2nd+ round
|
||||
* iteration to have real data pushed to the stream.
|
||||
@@ -146,6 +157,9 @@ static void test_multifd_tcp_zlib(void)
|
||||
{
|
||||
MigrateCommon args = {
|
||||
.listen_uri = "defer",
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
.start_hook = migrate_hook_start_precopy_tcp_multifd_zlib,
|
||||
};
|
||||
test_precopy_common(&args);
|
||||
|
||||
@@ -24,9 +24,6 @@ static void *migrate_hook_start_mode_reboot(QTestState *from, QTestState *to)
|
||||
migrate_set_parameter_str(from, "mode", "cpr-reboot");
|
||||
migrate_set_parameter_str(to, "mode", "cpr-reboot");
|
||||
|
||||
migrate_set_capability(from, "x-ignore-shared", true);
|
||||
migrate_set_capability(to, "x-ignore-shared", true);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -39,6 +36,9 @@ static void test_mode_reboot(void)
|
||||
.connect_uri = uri,
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_mode_reboot,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_X_IGNORE_SHARED] = true,
|
||||
},
|
||||
};
|
||||
|
||||
test_file_common(&args, true);
|
||||
|
||||
@@ -107,15 +107,6 @@ static void test_precopy_file_offset_bad(void)
|
||||
test_file_common(&args, false);
|
||||
}
|
||||
|
||||
static void *migrate_hook_start_mapped_ram(QTestState *from,
|
||||
QTestState *to)
|
||||
{
|
||||
migrate_set_capability(from, "mapped-ram", true);
|
||||
migrate_set_capability(to, "mapped-ram", true);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_precopy_file_mapped_ram_live(void)
|
||||
{
|
||||
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||
@@ -123,7 +114,9 @@ static void test_precopy_file_mapped_ram_live(void)
|
||||
MigrateCommon args = {
|
||||
.connect_uri = uri,
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_mapped_ram,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true,
|
||||
},
|
||||
};
|
||||
|
||||
test_file_common(&args, false);
|
||||
@@ -136,26 +129,14 @@ static void test_precopy_file_mapped_ram(void)
|
||||
MigrateCommon args = {
|
||||
.connect_uri = uri,
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_mapped_ram,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true,
|
||||
},
|
||||
};
|
||||
|
||||
test_file_common(&args, true);
|
||||
}
|
||||
|
||||
static void *migrate_hook_start_multifd_mapped_ram(QTestState *from,
|
||||
QTestState *to)
|
||||
{
|
||||
migrate_hook_start_mapped_ram(from, to);
|
||||
|
||||
migrate_set_parameter_int(from, "multifd-channels", 4);
|
||||
migrate_set_parameter_int(to, "multifd-channels", 4);
|
||||
|
||||
migrate_set_capability(from, "multifd", true);
|
||||
migrate_set_capability(to, "multifd", true);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_multifd_file_mapped_ram_live(void)
|
||||
{
|
||||
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||
@@ -163,7 +144,10 @@ static void test_multifd_file_mapped_ram_live(void)
|
||||
MigrateCommon args = {
|
||||
.connect_uri = uri,
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_multifd_mapped_ram,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true,
|
||||
},
|
||||
};
|
||||
|
||||
test_file_common(&args, false);
|
||||
@@ -176,7 +160,10 @@ static void test_multifd_file_mapped_ram(void)
|
||||
MigrateCommon args = {
|
||||
.connect_uri = uri,
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_multifd_mapped_ram,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true,
|
||||
},
|
||||
};
|
||||
|
||||
test_file_common(&args, true);
|
||||
@@ -185,8 +172,6 @@ static void test_multifd_file_mapped_ram(void)
|
||||
static void *migrate_hook_start_multifd_mapped_ram_dio(QTestState *from,
|
||||
QTestState *to)
|
||||
{
|
||||
migrate_hook_start_multifd_mapped_ram(from, to);
|
||||
|
||||
migrate_set_parameter_bool(from, "direct-io", true);
|
||||
migrate_set_parameter_bool(to, "direct-io", true);
|
||||
|
||||
@@ -201,6 +186,10 @@ static void test_multifd_file_mapped_ram_dio(void)
|
||||
.connect_uri = uri,
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_multifd_mapped_ram_dio,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true,
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
};
|
||||
|
||||
if (!probe_o_direct_support(tmpfs)) {
|
||||
@@ -246,7 +235,6 @@ static void *migrate_hook_start_multifd_mapped_ram_fdset_dio(QTestState *from,
|
||||
fdset_add_fds(from, file, O_WRONLY, 2, true);
|
||||
fdset_add_fds(to, file, O_RDONLY, 2, true);
|
||||
|
||||
migrate_hook_start_multifd_mapped_ram(from, to);
|
||||
migrate_set_parameter_bool(from, "direct-io", true);
|
||||
migrate_set_parameter_bool(to, "direct-io", true);
|
||||
|
||||
@@ -261,8 +249,6 @@ static void *migrate_hook_start_multifd_mapped_ram_fdset(QTestState *from,
|
||||
fdset_add_fds(from, file, O_WRONLY, 2, false);
|
||||
fdset_add_fds(to, file, O_RDONLY, 2, false);
|
||||
|
||||
migrate_hook_start_multifd_mapped_ram(from, to);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -275,6 +261,10 @@ static void test_multifd_file_mapped_ram_fdset(void)
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_multifd_mapped_ram_fdset,
|
||||
.end_hook = migrate_hook_end_multifd_mapped_ram_fdset,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true,
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
};
|
||||
|
||||
test_file_common(&args, true);
|
||||
@@ -289,6 +279,10 @@ static void test_multifd_file_mapped_ram_fdset_dio(void)
|
||||
.listen_uri = "defer",
|
||||
.start_hook = migrate_hook_start_multifd_mapped_ram_fdset_dio,
|
||||
.end_hook = migrate_hook_end_multifd_mapped_ram_fdset,
|
||||
.start = {
|
||||
.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true,
|
||||
.caps[MIGRATION_CAPABILITY_MULTIFD] = true,
|
||||
},
|
||||
};
|
||||
|
||||
if (!probe_o_direct_support(tmpfs)) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#define QEMU_VM_FILE_MAGIC 0x5145564d
|
||||
#define QEMU_ENV_SRC "QTEST_QEMU_BINARY_SRC"
|
||||
#define QEMU_ENV_DST "QTEST_QEMU_BINARY_DST"
|
||||
#define MULTIFD_TEST_CHANNELS 4
|
||||
|
||||
unsigned start_address;
|
||||
unsigned end_address;
|
||||
@@ -207,6 +208,51 @@ static QList *migrate_start_get_qmp_capabilities(const MigrateStart *args)
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
static void migrate_start_set_capabilities(QTestState *from, QTestState *to,
|
||||
MigrateStart *args)
|
||||
{
|
||||
/*
|
||||
* MigrationCapability_lookup and MIGRATION_CAPABILITY_ constants
|
||||
* are from qapi-types-migration.h.
|
||||
*/
|
||||
for (uint8_t i = 0; i < MIGRATION_CAPABILITY__MAX; i++) {
|
||||
if (!args->caps[i]) {
|
||||
continue;
|
||||
}
|
||||
if (from) {
|
||||
migrate_set_capability(from,
|
||||
MigrationCapability_lookup.array[i], true);
|
||||
}
|
||||
if (to) {
|
||||
migrate_set_capability(to,
|
||||
MigrationCapability_lookup.array[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Always enable migration events. Libvirt always uses it, let's try
|
||||
* to mimic as closer as that.
|
||||
*/
|
||||
migrate_set_capability(from, "events", true);
|
||||
if (!args->defer_target_connect) {
|
||||
migrate_set_capability(to, "events", true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Default number of channels should be fine for most
|
||||
* tests. Individual tests can override by calling
|
||||
* migrate_set_parameter() directly.
|
||||
*/
|
||||
if (args->caps[MIGRATION_CAPABILITY_MULTIFD]) {
|
||||
migrate_set_parameter_int(from, "multifd-channels",
|
||||
MULTIFD_TEST_CHANNELS);
|
||||
migrate_set_parameter_int(to, "multifd-channels",
|
||||
MULTIFD_TEST_CHANNELS);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int migrate_start(QTestState **from, QTestState **to, const char *uri,
|
||||
MigrateStart *args)
|
||||
{
|
||||
@@ -379,14 +425,7 @@ int migrate_start(QTestState **from, QTestState **to, const char *uri,
|
||||
unlink(shmem_path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Always enable migration events. Libvirt always uses it, let's try
|
||||
* to mimic as closer as that.
|
||||
*/
|
||||
migrate_set_capability(*from, "events", true);
|
||||
if (!args->defer_target_connect) {
|
||||
migrate_set_capability(*to, "events", true);
|
||||
}
|
||||
migrate_start_set_capabilities(*from, *to, args);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -432,6 +471,10 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
|
||||
{
|
||||
QTestState *from, *to;
|
||||
|
||||
/* set postcopy capabilities */
|
||||
args->start.caps[MIGRATION_CAPABILITY_POSTCOPY_BLOCKTIME] = true;
|
||||
args->start.caps[MIGRATION_CAPABILITY_POSTCOPY_RAM] = true;
|
||||
|
||||
if (migrate_start(&from, &to, "defer", &args->start)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -440,17 +483,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
|
||||
args->postcopy_data = args->start_hook(from, to);
|
||||
}
|
||||
|
||||
migrate_set_capability(from, "postcopy-ram", true);
|
||||
migrate_set_capability(to, "postcopy-ram", true);
|
||||
migrate_set_capability(to, "postcopy-blocktime", true);
|
||||
|
||||
if (args->postcopy_preempt) {
|
||||
migrate_set_capability(from, "postcopy-preempt", true);
|
||||
migrate_set_capability(to, "postcopy-preempt", true);
|
||||
}
|
||||
|
||||
migrate_ensure_non_converge(from);
|
||||
|
||||
migrate_prepare_for_dirty_mem(from);
|
||||
qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
|
||||
" 'arguments': { "
|
||||
@@ -948,15 +981,9 @@ void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from,
|
||||
QTestState *to,
|
||||
const char *method)
|
||||
{
|
||||
migrate_set_parameter_int(from, "multifd-channels", 16);
|
||||
migrate_set_parameter_int(to, "multifd-channels", 16);
|
||||
|
||||
migrate_set_parameter_str(from, "multifd-compression", method);
|
||||
migrate_set_parameter_str(to, "multifd-compression", method);
|
||||
|
||||
migrate_set_capability(from, "multifd", true);
|
||||
migrate_set_capability(to, "multifd", true);
|
||||
|
||||
/* Start incoming migration from the 1st socket */
|
||||
migrate_incoming_qmp(to, "tcp:127.0.0.1:0", NULL, "{}");
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#define TEST_FRAMEWORK_H
|
||||
|
||||
#include "libqtest.h"
|
||||
#include <qapi/qapi-types-migration.h>
|
||||
|
||||
#define FILE_TEST_FILENAME "migfile"
|
||||
#define FILE_TEST_OFFSET 0x1000
|
||||
@@ -120,6 +121,13 @@ typedef struct {
|
||||
|
||||
/* Do not connect to target monitor and qtest sockets in qtest_init */
|
||||
bool defer_target_connect;
|
||||
|
||||
/*
|
||||
* Migration capabilities to be set in both source and
|
||||
* destination. For unilateral capabilities, use
|
||||
* migration_set_capabilities().
|
||||
*/
|
||||
bool caps[MIGRATION_CAPABILITY__MAX];
|
||||
} MigrateStart;
|
||||
|
||||
typedef enum PostcopyRecoveryFailStage {
|
||||
@@ -207,7 +215,6 @@ typedef struct {
|
||||
|
||||
/* Postcopy specific fields */
|
||||
void *postcopy_data;
|
||||
bool postcopy_preempt;
|
||||
PostcopyRecoveryFailStage postcopy_recovery_fail_stage;
|
||||
} MigrateCommon;
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ static void test_ignore_shared(void)
|
||||
QTestState *from, *to;
|
||||
MigrateStart args = {
|
||||
.use_shmem = true,
|
||||
.caps[MIGRATION_CAPABILITY_X_IGNORE_SHARED] = true,
|
||||
};
|
||||
|
||||
if (migrate_start(&from, &to, uri, &args)) {
|
||||
@@ -107,9 +108,6 @@ static void test_ignore_shared(void)
|
||||
migrate_ensure_non_converge(from);
|
||||
migrate_prepare_for_dirty_mem(from);
|
||||
|
||||
migrate_set_capability(from, "x-ignore-shared", true);
|
||||
migrate_set_capability(to, "x-ignore-shared", true);
|
||||
|
||||
/* Wait for the first serial output from the source */
|
||||
wait_for_serial("src_serial");
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user