mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-01 06:43:53 -06:00

Merge qtest_init_with_env_and_capabilities() and qtest_init_with_env() into one qtest_init_ext(). Reasons: 1. qtest_init_with_env() is just wrong: it gets do_connect parameter but always pass true to qtest_init_with_env_and_capabilities(). Happily, all qtest_init_with_env() callers pass true as well. 2. qtest_init_with_env() is not used outside of libqtest.c, so no reason to keep it as public function 3. and in libqtest.c it's used not often, so no problem to use more generic function instead. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Steve Sistare <steven.sistare@oracle.com> Reviewed-by: Fabiano Rosas <farosas@suse.de> Message-ID: <20250410162250.329941-1-vsementsov@yandex-team.ru> Signed-off-by: Fabiano Rosas <farosas@suse.de>
1066 lines
33 KiB
C
1066 lines
33 KiB
C
/*
|
|
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
|
|
* based on the vhost-user-test.c that is:
|
|
* Copyright (c) 2014 Virtual Open Systems Sarl.
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "chardev/char.h"
|
|
#include "crypto/tlscredspsk.h"
|
|
#include "libqtest.h"
|
|
#include "migration/bootfile.h"
|
|
#include "migration/framework.h"
|
|
#include "migration/migration-qmp.h"
|
|
#include "migration/migration-util.h"
|
|
#include "ppc-util.h"
|
|
#include "qapi/error.h"
|
|
#include "qobject/qjson.h"
|
|
#include "qobject/qlist.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/option.h"
|
|
#include "qemu/range.h"
|
|
#include "qemu/sockets.h"
|
|
|
|
|
|
#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;
|
|
static QTestMigrationState src_state;
|
|
static QTestMigrationState dst_state;
|
|
static char *tmpfs;
|
|
|
|
/*
|
|
* An initial 3 MB offset is used as that corresponds
|
|
* to ~1 sec of data transfer with our bandwidth setting.
|
|
*/
|
|
#define MAGIC_OFFSET_BASE (3 * 1024 * 1024)
|
|
/*
|
|
* A further 1k is added to ensure we're not a multiple
|
|
* of TEST_MEM_PAGE_SIZE, thus avoid clash with writes
|
|
* from the migration guest workload.
|
|
*/
|
|
#define MAGIC_OFFSET_SHUFFLE 1024
|
|
#define MAGIC_OFFSET (MAGIC_OFFSET_BASE + MAGIC_OFFSET_SHUFFLE)
|
|
#define MAGIC_MARKER 0xFEED12345678CAFEULL
|
|
|
|
|
|
/*
|
|
* Wait for some output in the serial output file,
|
|
* we get an 'A' followed by an endless string of 'B's
|
|
* but on the destination we won't have the A (unless we enabled suspend/resume)
|
|
*/
|
|
void wait_for_serial(const char *side)
|
|
{
|
|
g_autofree char *serialpath = g_strdup_printf("%s/%s", tmpfs, side);
|
|
FILE *serialfile = fopen(serialpath, "r");
|
|
|
|
do {
|
|
int readvalue = fgetc(serialfile);
|
|
|
|
switch (readvalue) {
|
|
case 'A':
|
|
/* Fine */
|
|
break;
|
|
|
|
case 'B':
|
|
/* It's alive! */
|
|
fclose(serialfile);
|
|
return;
|
|
|
|
case EOF:
|
|
fseek(serialfile, 0, SEEK_SET);
|
|
usleep(1000);
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Unexpected %d on %s serial\n", readvalue, side);
|
|
g_assert_not_reached();
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
void migrate_prepare_for_dirty_mem(QTestState *from)
|
|
{
|
|
/*
|
|
* The guest workflow iterates from start_address to
|
|
* end_address, writing 1 byte every TEST_MEM_PAGE_SIZE
|
|
* bytes.
|
|
*
|
|
* IOW, if we write to mem at a point which is NOT
|
|
* a multiple of TEST_MEM_PAGE_SIZE, our write won't
|
|
* conflict with the migration workflow.
|
|
*
|
|
* We put in a marker here, that we'll use to determine
|
|
* when the data has been transferred to the dst.
|
|
*/
|
|
qtest_writeq(from, start_address + MAGIC_OFFSET, MAGIC_MARKER);
|
|
}
|
|
|
|
void migrate_wait_for_dirty_mem(QTestState *from, QTestState *to)
|
|
{
|
|
uint64_t watch_address = start_address + MAGIC_OFFSET_BASE;
|
|
uint64_t marker_address = start_address + MAGIC_OFFSET;
|
|
uint8_t watch_byte;
|
|
|
|
/*
|
|
* Wait for the MAGIC_MARKER to get transferred, as an
|
|
* indicator that a migration pass has made some known
|
|
* amount of progress.
|
|
*/
|
|
do {
|
|
usleep(1000 * 10);
|
|
} while (qtest_readq(to, marker_address) != MAGIC_MARKER);
|
|
|
|
|
|
/* If suspended, src only iterates once, and watch_byte may never change */
|
|
if (src_state.suspend_me) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Now ensure that already transferred bytes are
|
|
* dirty again from the guest workload. Note the
|
|
* guest byte value will wrap around and by chance
|
|
* match the original watch_byte. This is harmless
|
|
* as we'll eventually see a different value if we
|
|
* keep watching
|
|
*/
|
|
watch_byte = qtest_readb(from, watch_address);
|
|
do {
|
|
usleep(1000 * 10);
|
|
} while (qtest_readb(from, watch_address) == watch_byte);
|
|
}
|
|
|
|
static void check_guests_ram(QTestState *who)
|
|
{
|
|
/*
|
|
* Our ASM test will have been incrementing one byte from each page from
|
|
* start_address to < end_address in order. This gives us a constraint
|
|
* that any page's byte should be equal or less than the previous pages
|
|
* byte (mod 256); and they should all be equal except for one transition
|
|
* at the point where we meet the incrementer. (We're running this with
|
|
* the guest stopped).
|
|
*/
|
|
unsigned address;
|
|
uint8_t first_byte;
|
|
uint8_t last_byte;
|
|
bool hit_edge = false;
|
|
int bad = 0;
|
|
|
|
qtest_memread(who, start_address, &first_byte, 1);
|
|
last_byte = first_byte;
|
|
|
|
for (address = start_address + TEST_MEM_PAGE_SIZE; address < end_address;
|
|
address += TEST_MEM_PAGE_SIZE)
|
|
{
|
|
uint8_t b;
|
|
qtest_memread(who, address, &b, 1);
|
|
if (b != last_byte) {
|
|
if (((b + 1) % 256) == last_byte && !hit_edge) {
|
|
/*
|
|
* This is OK, the guest stopped at the point of
|
|
* incrementing the previous page but didn't get
|
|
* to us yet.
|
|
*/
|
|
hit_edge = true;
|
|
last_byte = b;
|
|
} else {
|
|
bad++;
|
|
if (bad <= 10) {
|
|
fprintf(stderr, "Memory content inconsistency at %x"
|
|
" first_byte = %x last_byte = %x current = %x"
|
|
" hit_edge = %x\n",
|
|
address, first_byte, last_byte, b, hit_edge);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (bad >= 10) {
|
|
fprintf(stderr, "and in another %d pages", bad - 10);
|
|
}
|
|
g_assert(bad == 0);
|
|
}
|
|
|
|
static void cleanup(const char *filename)
|
|
{
|
|
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, filename);
|
|
|
|
unlink(path);
|
|
}
|
|
|
|
static QList *migrate_start_get_qmp_capabilities(const MigrateStart *args)
|
|
{
|
|
QList *capabilities = NULL;
|
|
|
|
if (args->oob) {
|
|
capabilities = qlist_new();
|
|
qlist_append_str(capabilities, "oob");
|
|
}
|
|
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)
|
|
{
|
|
/* options for source and target */
|
|
g_autofree gchar *arch_opts = NULL;
|
|
g_autofree gchar *cmd_source = NULL;
|
|
g_autofree gchar *cmd_target = NULL;
|
|
const gchar *ignore_stderr;
|
|
g_autofree char *shmem_opts = NULL;
|
|
g_autofree char *shmem_path = NULL;
|
|
const char *kvm_opts = NULL;
|
|
const char *arch = qtest_get_arch();
|
|
const char *memory_size;
|
|
const char *machine_alias, *machine_opts = "";
|
|
g_autofree char *machine = NULL;
|
|
const char *bootpath;
|
|
g_autoptr(QList) capabilities = migrate_start_get_qmp_capabilities(args);
|
|
g_autofree char *memory_backend = NULL;
|
|
const char *events;
|
|
|
|
if (args->use_shmem) {
|
|
if (!g_file_test("/dev/shm", G_FILE_TEST_IS_DIR)) {
|
|
g_test_skip("/dev/shm is not supported");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
dst_state = (QTestMigrationState) { };
|
|
src_state = (QTestMigrationState) { };
|
|
bootpath = bootfile_create(arch, tmpfs, args->suspend_me);
|
|
src_state.suspend_me = args->suspend_me;
|
|
|
|
if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
|
|
memory_size = "150M";
|
|
|
|
if (g_str_equal(arch, "i386")) {
|
|
machine_alias = "pc";
|
|
} else {
|
|
machine_alias = "q35";
|
|
}
|
|
arch_opts = g_strdup_printf(
|
|
"-drive if=none,id=d0,file=%s,format=raw "
|
|
"-device ide-hd,drive=d0,secs=1,cyls=1,heads=1", bootpath);
|
|
start_address = X86_TEST_MEM_START;
|
|
end_address = X86_TEST_MEM_END;
|
|
} else if (g_str_equal(arch, "s390x")) {
|
|
memory_size = "128M";
|
|
machine_alias = "s390-ccw-virtio";
|
|
arch_opts = g_strdup_printf("-bios %s", bootpath);
|
|
start_address = S390_TEST_MEM_START;
|
|
end_address = S390_TEST_MEM_END;
|
|
} else if (strcmp(arch, "ppc64") == 0) {
|
|
memory_size = "256M";
|
|
start_address = PPC_TEST_MEM_START;
|
|
end_address = PPC_TEST_MEM_END;
|
|
machine_alias = "pseries";
|
|
machine_opts = "vsmt=8";
|
|
arch_opts = g_strdup_printf(
|
|
"-nodefaults -machine " PSERIES_DEFAULT_CAPABILITIES " "
|
|
"-bios %s", bootpath);
|
|
} else if (strcmp(arch, "aarch64") == 0) {
|
|
memory_size = "150M";
|
|
machine_alias = "virt";
|
|
machine_opts = "gic-version=3";
|
|
arch_opts = g_strdup_printf("-cpu max -kernel %s", bootpath);
|
|
start_address = ARM_TEST_MEM_START;
|
|
end_address = ARM_TEST_MEM_END;
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
if (!getenv("QTEST_LOG") && args->hide_stderr) {
|
|
#ifndef _WIN32
|
|
ignore_stderr = "2>/dev/null";
|
|
#else
|
|
/*
|
|
* On Windows the QEMU executable is created via CreateProcess() and
|
|
* IO redirection does not work, so don't bother adding IO redirection
|
|
* to the command line.
|
|
*/
|
|
ignore_stderr = "";
|
|
#endif
|
|
} else {
|
|
ignore_stderr = "";
|
|
}
|
|
|
|
if (args->use_shmem) {
|
|
shmem_path = g_strdup_printf("/dev/shm/qemu-%d", getpid());
|
|
shmem_opts = g_strdup_printf(
|
|
"-object memory-backend-file,id=mem0,size=%s"
|
|
",mem-path=%s,share=on -numa node,memdev=mem0",
|
|
memory_size, shmem_path);
|
|
}
|
|
|
|
if (args->memory_backend) {
|
|
memory_backend = g_strdup_printf(args->memory_backend, memory_size);
|
|
} else {
|
|
memory_backend = g_strdup_printf("-m %s ", memory_size);
|
|
}
|
|
|
|
if (args->use_dirty_ring) {
|
|
kvm_opts = ",dirty-ring-size=4096";
|
|
}
|
|
|
|
if (!qtest_has_machine(machine_alias)) {
|
|
g_autofree char *msg = g_strdup_printf("machine %s not supported", machine_alias);
|
|
g_test_skip(msg);
|
|
return -1;
|
|
}
|
|
|
|
machine = resolve_machine_version(machine_alias, QEMU_ENV_SRC,
|
|
QEMU_ENV_DST);
|
|
|
|
g_test_message("Using machine type: %s", machine);
|
|
|
|
cmd_source = g_strdup_printf("-accel kvm%s -accel tcg "
|
|
"-machine %s,%s "
|
|
"-name source,debug-threads=on "
|
|
"%s "
|
|
"-serial file:%s/src_serial "
|
|
"%s %s %s %s",
|
|
kvm_opts ? kvm_opts : "",
|
|
machine, machine_opts,
|
|
memory_backend, tmpfs,
|
|
arch_opts ? arch_opts : "",
|
|
shmem_opts ? shmem_opts : "",
|
|
args->opts_source ? args->opts_source : "",
|
|
ignore_stderr);
|
|
if (!args->only_target) {
|
|
*from = qtest_init_ext(QEMU_ENV_SRC, cmd_source, capabilities, true);
|
|
qtest_qmp_set_event_callback(*from,
|
|
migrate_watch_for_events,
|
|
&src_state);
|
|
}
|
|
|
|
/*
|
|
* If the monitor connection is deferred, enable events on the command line
|
|
* so none are missed. This is for testing only, do not set migration
|
|
* options like this in general.
|
|
*/
|
|
events = args->defer_target_connect ? "-global migration.x-events=on" : "";
|
|
|
|
cmd_target = g_strdup_printf("-accel kvm%s -accel tcg "
|
|
"-machine %s,%s "
|
|
"-name target,debug-threads=on "
|
|
"%s "
|
|
"-serial file:%s/dest_serial "
|
|
"-incoming %s "
|
|
"%s %s %s %s %s",
|
|
kvm_opts ? kvm_opts : "",
|
|
machine, machine_opts,
|
|
memory_backend, tmpfs, uri,
|
|
events,
|
|
arch_opts ? arch_opts : "",
|
|
shmem_opts ? shmem_opts : "",
|
|
args->opts_target ? args->opts_target : "",
|
|
ignore_stderr);
|
|
*to = qtest_init_ext(QEMU_ENV_DST, cmd_target, capabilities,
|
|
!args->defer_target_connect);
|
|
qtest_qmp_set_event_callback(*to,
|
|
migrate_watch_for_events,
|
|
&dst_state);
|
|
|
|
/*
|
|
* Remove shmem file immediately to avoid memory leak in test failed case.
|
|
* It's valid because QEMU has already opened this file
|
|
*/
|
|
if (args->use_shmem) {
|
|
unlink(shmem_path);
|
|
}
|
|
|
|
migrate_start_set_capabilities(*from, *to, args);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void migrate_end(QTestState *from, QTestState *to, bool test_dest)
|
|
{
|
|
unsigned char dest_byte_a, dest_byte_b, dest_byte_c, dest_byte_d;
|
|
|
|
qtest_quit(from);
|
|
|
|
if (test_dest) {
|
|
qtest_memread(to, start_address, &dest_byte_a, 1);
|
|
|
|
/* Destination still running, wait for a byte to change */
|
|
do {
|
|
qtest_memread(to, start_address, &dest_byte_b, 1);
|
|
usleep(1000 * 10);
|
|
} while (dest_byte_a == dest_byte_b);
|
|
|
|
qtest_qmp_assert_success(to, "{ 'execute' : 'stop'}");
|
|
|
|
/* With it stopped, check nothing changes */
|
|
qtest_memread(to, start_address, &dest_byte_c, 1);
|
|
usleep(1000 * 200);
|
|
qtest_memread(to, start_address, &dest_byte_d, 1);
|
|
g_assert_cmpint(dest_byte_c, ==, dest_byte_d);
|
|
|
|
check_guests_ram(to);
|
|
}
|
|
|
|
qtest_quit(to);
|
|
|
|
cleanup("migsocket");
|
|
cleanup("cpr.sock");
|
|
cleanup("src_serial");
|
|
cleanup("dest_serial");
|
|
cleanup(FILE_TEST_FILENAME);
|
|
}
|
|
|
|
static int migrate_postcopy_prepare(QTestState **from_ptr,
|
|
QTestState **to_ptr,
|
|
MigrateCommon *args)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (args->start_hook) {
|
|
args->postcopy_data = args->start_hook(from, to);
|
|
}
|
|
|
|
migrate_ensure_non_converge(from);
|
|
migrate_prepare_for_dirty_mem(from);
|
|
qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
|
|
" 'arguments': { "
|
|
" 'channels': [ { 'channel-type': 'main',"
|
|
" 'addr': { 'transport': 'socket',"
|
|
" 'type': 'inet',"
|
|
" 'host': '127.0.0.1',"
|
|
" 'port': '0' } } ] } }");
|
|
|
|
/* Wait for the first serial output from the source */
|
|
wait_for_serial("src_serial");
|
|
wait_for_suspend(from, &src_state);
|
|
|
|
migrate_qmp(from, to, NULL, NULL, "{}");
|
|
|
|
migrate_wait_for_dirty_mem(from, to);
|
|
|
|
*from_ptr = from;
|
|
*to_ptr = to;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void migrate_postcopy_complete(QTestState *from, QTestState *to,
|
|
MigrateCommon *args)
|
|
{
|
|
MigrationTestEnv *env = migration_get_env();
|
|
|
|
wait_for_migration_complete(from);
|
|
|
|
if (args->start.suspend_me) {
|
|
/* wakeup succeeds only if guest is suspended */
|
|
qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
|
|
}
|
|
|
|
/* Make sure we get at least one "B" on destination */
|
|
wait_for_serial("dest_serial");
|
|
|
|
if (env->uffd_feature_thread_id) {
|
|
read_blocktime(to);
|
|
}
|
|
|
|
if (args->end_hook) {
|
|
args->end_hook(from, to, args->postcopy_data);
|
|
args->postcopy_data = NULL;
|
|
}
|
|
|
|
migrate_end(from, to, true);
|
|
}
|
|
|
|
void test_postcopy_common(MigrateCommon *args)
|
|
{
|
|
QTestState *from, *to;
|
|
|
|
if (migrate_postcopy_prepare(&from, &to, args)) {
|
|
return;
|
|
}
|
|
migrate_postcopy_start(from, to, &src_state);
|
|
migrate_postcopy_complete(from, to, args);
|
|
}
|
|
|
|
static void wait_for_postcopy_status(QTestState *one, const char *status)
|
|
{
|
|
wait_for_migration_status(one, status,
|
|
(const char * []) {
|
|
"failed", "active",
|
|
"completed", NULL
|
|
});
|
|
}
|
|
|
|
static void postcopy_recover_fail(QTestState *from, QTestState *to,
|
|
PostcopyRecoveryFailStage stage)
|
|
{
|
|
#ifndef _WIN32
|
|
bool fail_early = (stage == POSTCOPY_FAIL_CHANNEL_ESTABLISH);
|
|
int ret, pair1[2], pair2[2];
|
|
char c;
|
|
|
|
g_assert(stage > POSTCOPY_FAIL_NONE && stage < POSTCOPY_FAIL_MAX);
|
|
|
|
/* Create two unrelated socketpairs */
|
|
ret = qemu_socketpair(PF_LOCAL, SOCK_STREAM, 0, pair1);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
ret = qemu_socketpair(PF_LOCAL, SOCK_STREAM, 0, pair2);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/*
|
|
* Give the guests unpaired ends of the sockets, so they'll all blocked
|
|
* at reading. This mimics a wrong channel established.
|
|
*/
|
|
qtest_qmp_fds_assert_success(from, &pair1[0], 1,
|
|
"{ 'execute': 'getfd',"
|
|
" 'arguments': { 'fdname': 'fd-mig' }}");
|
|
qtest_qmp_fds_assert_success(to, &pair2[0], 1,
|
|
"{ 'execute': 'getfd',"
|
|
" 'arguments': { 'fdname': 'fd-mig' }}");
|
|
|
|
/*
|
|
* Write the 1st byte as QEMU_VM_COMMAND (0x8) for the dest socket, to
|
|
* emulate the 1st byte of a real recovery, but stops from there to
|
|
* keep dest QEMU in RECOVER. This is needed so that we can kick off
|
|
* the recover process on dest QEMU (by triggering the G_IO_IN event).
|
|
*
|
|
* NOTE: this trick is not needed on src QEMUs, because src doesn't
|
|
* rely on an pre-existing G_IO_IN event, so it will always trigger the
|
|
* upcoming recovery anyway even if it can read nothing.
|
|
*/
|
|
#define QEMU_VM_COMMAND 0x08
|
|
c = QEMU_VM_COMMAND;
|
|
ret = send(pair2[1], &c, 1, 0);
|
|
g_assert_cmpint(ret, ==, 1);
|
|
|
|
if (stage == POSTCOPY_FAIL_CHANNEL_ESTABLISH) {
|
|
/*
|
|
* This will make src QEMU to fail at an early stage when trying to
|
|
* resume later, where it shouldn't reach RECOVER stage at all.
|
|
*/
|
|
close(pair1[1]);
|
|
}
|
|
|
|
migrate_recover(to, "fd:fd-mig");
|
|
migrate_qmp(from, to, "fd:fd-mig", NULL, "{'resume': true}");
|
|
|
|
/*
|
|
* Source QEMU has an extra RECOVER_SETUP phase, dest doesn't have it.
|
|
* Make sure it appears along the way.
|
|
*/
|
|
migration_event_wait(from, "postcopy-recover-setup");
|
|
|
|
if (fail_early) {
|
|
/*
|
|
* When fails at reconnection, src QEMU will automatically goes
|
|
* back to PAUSED state. Making sure there is an event in this
|
|
* case: Libvirt relies on this to detect early reconnection
|
|
* errors.
|
|
*/
|
|
migration_event_wait(from, "postcopy-paused");
|
|
} else {
|
|
/*
|
|
* We want to test "fail later" at RECOVER stage here. Make sure
|
|
* both QEMU instances will go into RECOVER stage first, then test
|
|
* kicking them out using migrate-pause.
|
|
*
|
|
* Explicitly check the RECOVER event on src, that's what Libvirt
|
|
* relies on, rather than polling.
|
|
*/
|
|
migration_event_wait(from, "postcopy-recover");
|
|
wait_for_postcopy_status(from, "postcopy-recover");
|
|
|
|
/* Need an explicit kick on src QEMU in this case */
|
|
migrate_pause(from);
|
|
}
|
|
|
|
/*
|
|
* For all failure cases, we'll reach such states on both sides now.
|
|
* Check them.
|
|
*/
|
|
wait_for_postcopy_status(from, "postcopy-paused");
|
|
wait_for_postcopy_status(to, "postcopy-recover");
|
|
|
|
/*
|
|
* Kick dest QEMU out too. This is normally not needed in reality
|
|
* because when the channel is shutdown it should also happen on src.
|
|
* However here we used separate socket pairs so we need to do that
|
|
* explicitly.
|
|
*/
|
|
migrate_pause(to);
|
|
wait_for_postcopy_status(to, "postcopy-paused");
|
|
|
|
close(pair1[0]);
|
|
close(pair2[0]);
|
|
close(pair2[1]);
|
|
|
|
if (stage != POSTCOPY_FAIL_CHANNEL_ESTABLISH) {
|
|
close(pair1[1]);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void test_postcopy_recovery_common(MigrateCommon *args)
|
|
{
|
|
QTestState *from, *to;
|
|
g_autofree char *uri = NULL;
|
|
|
|
/*
|
|
* Always enable OOB QMP capability for recovery tests, migrate-recover is
|
|
* executed out-of-band
|
|
*/
|
|
args->start.oob = true;
|
|
|
|
/* Always hide errors for postcopy recover tests since they're expected */
|
|
args->start.hide_stderr = true;
|
|
|
|
if (migrate_postcopy_prepare(&from, &to, args)) {
|
|
return;
|
|
}
|
|
|
|
/* Turn postcopy speed down, 4K/s is slow enough on any machines */
|
|
migrate_set_parameter_int(from, "max-postcopy-bandwidth", 4096);
|
|
|
|
/* Now we start the postcopy */
|
|
migrate_postcopy_start(from, to, &src_state);
|
|
|
|
/*
|
|
* Wait until postcopy is really started; we can only run the
|
|
* migrate-pause command during a postcopy
|
|
*/
|
|
wait_for_migration_status(from, "postcopy-active", NULL);
|
|
|
|
/*
|
|
* Manually stop the postcopy migration. This emulates a network
|
|
* failure with the migration socket
|
|
*/
|
|
migrate_pause(from);
|
|
|
|
/*
|
|
* Wait for destination side to reach postcopy-paused state. The
|
|
* migrate-recover command can only succeed if destination machine
|
|
* is in the paused state
|
|
*/
|
|
wait_for_postcopy_status(to, "postcopy-paused");
|
|
wait_for_postcopy_status(from, "postcopy-paused");
|
|
|
|
if (args->postcopy_recovery_fail_stage) {
|
|
/*
|
|
* Test when a wrong socket specified for recover, and then the
|
|
* ability to kick it out, and continue with a correct socket.
|
|
*/
|
|
postcopy_recover_fail(from, to, args->postcopy_recovery_fail_stage);
|
|
/* continue with a good recovery */
|
|
}
|
|
|
|
/*
|
|
* Create a new socket to emulate a new channel that is different
|
|
* from the broken migration channel; tell the destination to
|
|
* listen to the new port
|
|
*/
|
|
uri = g_strdup_printf("unix:%s/migsocket-recover", tmpfs);
|
|
migrate_recover(to, uri);
|
|
|
|
/*
|
|
* Try to rebuild the migration channel using the resume flag and
|
|
* the newly created channel
|
|
*/
|
|
migrate_qmp(from, to, uri, NULL, "{'resume': true}");
|
|
|
|
/* Restore the postcopy bandwidth to unlimited */
|
|
migrate_set_parameter_int(from, "max-postcopy-bandwidth", 0);
|
|
|
|
migrate_postcopy_complete(from, to, args);
|
|
}
|
|
|
|
void test_precopy_common(MigrateCommon *args)
|
|
{
|
|
QTestState *from, *to;
|
|
void *data_hook = NULL;
|
|
QObject *in_channels = NULL;
|
|
QObject *out_channels = NULL;
|
|
|
|
g_assert(!args->cpr_channel || args->connect_channels);
|
|
|
|
if (migrate_start(&from, &to, args->listen_uri, &args->start)) {
|
|
return;
|
|
}
|
|
|
|
if (args->start_hook) {
|
|
data_hook = args->start_hook(from, to);
|
|
}
|
|
|
|
/* Wait for the first serial output from the source */
|
|
if (args->result == MIG_TEST_SUCCEED) {
|
|
wait_for_serial("src_serial");
|
|
wait_for_suspend(from, &src_state);
|
|
}
|
|
|
|
if (args->live) {
|
|
migrate_ensure_non_converge(from);
|
|
migrate_prepare_for_dirty_mem(from);
|
|
} else {
|
|
/*
|
|
* Testing non-live migration, we allow it to run at
|
|
* full speed to ensure short test case duration.
|
|
* For tests expected to fail, we don't need to
|
|
* change anything.
|
|
*/
|
|
if (args->result == MIG_TEST_SUCCEED) {
|
|
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
|
wait_for_stop(from, &src_state);
|
|
migrate_ensure_converge(from);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The cpr channel must be included in outgoing channels, but not in
|
|
* migrate-incoming channels.
|
|
*/
|
|
if (args->connect_channels) {
|
|
if (args->start.defer_target_connect &&
|
|
!strcmp(args->listen_uri, "defer")) {
|
|
in_channels = qobject_from_json(args->connect_channels,
|
|
&error_abort);
|
|
}
|
|
out_channels = qobject_from_json(args->connect_channels, &error_abort);
|
|
|
|
if (args->cpr_channel) {
|
|
QList *channels_list = qobject_to(QList, out_channels);
|
|
QObject *obj = migrate_str_to_channel(args->cpr_channel);
|
|
|
|
qlist_append(channels_list, obj);
|
|
}
|
|
}
|
|
|
|
if (args->result == MIG_TEST_QMP_ERROR) {
|
|
migrate_qmp_fail(from, args->connect_uri, out_channels, "{}");
|
|
goto finish;
|
|
}
|
|
|
|
migrate_qmp(from, to, args->connect_uri, out_channels, "{}");
|
|
|
|
if (args->start.defer_target_connect) {
|
|
qtest_connect(to);
|
|
qtest_qmp_handshake(to, NULL);
|
|
if (!strcmp(args->listen_uri, "defer")) {
|
|
migrate_incoming_qmp(to, args->connect_uri, in_channels, "{}");
|
|
}
|
|
}
|
|
|
|
if (args->result != MIG_TEST_SUCCEED) {
|
|
bool allow_active = args->result == MIG_TEST_FAIL;
|
|
wait_for_migration_fail(from, allow_active);
|
|
|
|
if (args->result == MIG_TEST_FAIL_DEST_QUIT_ERR) {
|
|
qtest_set_expected_status(to, EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
if (args->live) {
|
|
/*
|
|
* For initial iteration(s) we must do a full pass,
|
|
* but for the final iteration, we need only wait
|
|
* for some dirty mem before switching to converge
|
|
*/
|
|
while (args->iterations > 1) {
|
|
wait_for_migration_pass(from, &src_state);
|
|
args->iterations--;
|
|
}
|
|
migrate_wait_for_dirty_mem(from, to);
|
|
|
|
migrate_ensure_converge(from);
|
|
|
|
/*
|
|
* We do this first, as it has a timeout to stop us
|
|
* hanging forever if migration didn't converge
|
|
*/
|
|
wait_for_migration_complete(from);
|
|
|
|
wait_for_stop(from, &src_state);
|
|
|
|
} else {
|
|
wait_for_migration_complete(from);
|
|
/*
|
|
* Must wait for dst to finish reading all incoming
|
|
* data on the socket before issuing 'cont' otherwise
|
|
* it'll be ignored
|
|
*/
|
|
wait_for_migration_complete(to);
|
|
|
|
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
|
}
|
|
|
|
wait_for_resume(to, &dst_state);
|
|
|
|
if (args->start.suspend_me) {
|
|
/* wakeup succeeds only if guest is suspended */
|
|
qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
|
|
}
|
|
|
|
wait_for_serial("dest_serial");
|
|
}
|
|
|
|
finish:
|
|
if (args->end_hook) {
|
|
args->end_hook(from, to, data_hook);
|
|
}
|
|
|
|
migrate_end(from, to, args->result == MIG_TEST_SUCCEED);
|
|
}
|
|
|
|
static void file_dirty_offset_region(void)
|
|
{
|
|
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
|
|
size_t size = FILE_TEST_OFFSET;
|
|
g_autofree char *data = g_new0(char, size);
|
|
|
|
memset(data, FILE_TEST_MARKER, size);
|
|
g_assert(g_file_set_contents(path, data, size, NULL));
|
|
}
|
|
|
|
static void file_check_offset_region(void)
|
|
{
|
|
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
|
|
size_t size = FILE_TEST_OFFSET;
|
|
g_autofree char *expected = g_new0(char, size);
|
|
g_autofree char *actual = NULL;
|
|
uint64_t *stream_start;
|
|
|
|
/*
|
|
* Ensure the skipped offset region's data has not been touched
|
|
* and the migration stream starts at the right place.
|
|
*/
|
|
|
|
memset(expected, FILE_TEST_MARKER, size);
|
|
|
|
g_assert(g_file_get_contents(path, &actual, NULL, NULL));
|
|
g_assert(!memcmp(actual, expected, size));
|
|
|
|
stream_start = (uint64_t *)(actual + size);
|
|
g_assert_cmpint(cpu_to_be64(*stream_start) >> 32, ==, QEMU_VM_FILE_MAGIC);
|
|
}
|
|
|
|
void test_file_common(MigrateCommon *args, bool stop_src)
|
|
{
|
|
QTestState *from, *to;
|
|
void *data_hook = NULL;
|
|
bool check_offset = false;
|
|
|
|
if (migrate_start(&from, &to, args->listen_uri, &args->start)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* File migration is never live. We can keep the source VM running
|
|
* during migration, but the destination will not be running
|
|
* concurrently.
|
|
*/
|
|
g_assert_false(args->live);
|
|
|
|
if (g_strrstr(args->connect_uri, "offset=")) {
|
|
check_offset = true;
|
|
/*
|
|
* This comes before the start_hook because it's equivalent to
|
|
* a management application creating the file and writing to
|
|
* it so hooks should expect the file to be already present.
|
|
*/
|
|
file_dirty_offset_region();
|
|
}
|
|
|
|
if (args->start_hook) {
|
|
data_hook = args->start_hook(from, to);
|
|
}
|
|
|
|
migrate_ensure_converge(from);
|
|
wait_for_serial("src_serial");
|
|
|
|
if (stop_src) {
|
|
qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
|
|
wait_for_stop(from, &src_state);
|
|
}
|
|
|
|
if (args->result == MIG_TEST_QMP_ERROR) {
|
|
migrate_qmp_fail(from, args->connect_uri, NULL, "{}");
|
|
goto finish;
|
|
}
|
|
|
|
migrate_qmp(from, to, args->connect_uri, NULL, "{}");
|
|
wait_for_migration_complete(from);
|
|
|
|
/*
|
|
* We need to wait for the source to finish before starting the
|
|
* destination.
|
|
*/
|
|
migrate_incoming_qmp(to, args->connect_uri, NULL, "{}");
|
|
wait_for_migration_complete(to);
|
|
|
|
if (stop_src) {
|
|
qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
|
|
}
|
|
wait_for_resume(to, &dst_state);
|
|
|
|
wait_for_serial("dest_serial");
|
|
|
|
if (check_offset) {
|
|
file_check_offset_region();
|
|
}
|
|
|
|
finish:
|
|
if (args->end_hook) {
|
|
args->end_hook(from, to, data_hook);
|
|
}
|
|
|
|
migrate_end(from, to, args->result == MIG_TEST_SUCCEED);
|
|
}
|
|
|
|
void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from,
|
|
QTestState *to,
|
|
const char *method)
|
|
{
|
|
migrate_set_parameter_str(from, "multifd-compression", method);
|
|
migrate_set_parameter_str(to, "multifd-compression", method);
|
|
|
|
/* Start incoming migration from the 1st socket */
|
|
migrate_incoming_qmp(to, "tcp:127.0.0.1:0", NULL, "{}");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
QTestMigrationState *get_src(void)
|
|
{
|
|
return &src_state;
|
|
}
|
|
|
|
MigrationTestEnv *migration_get_env(void)
|
|
{
|
|
static MigrationTestEnv *env;
|
|
g_autoptr(GError) err = NULL;
|
|
|
|
if (env) {
|
|
return env;
|
|
}
|
|
|
|
env = g_new0(MigrationTestEnv, 1);
|
|
env->qemu_src = getenv(QEMU_ENV_SRC);
|
|
env->qemu_dst = getenv(QEMU_ENV_DST);
|
|
|
|
/*
|
|
* The default QTEST_QEMU_BINARY must always be provided because
|
|
* that is what helpers use to query the accel type and
|
|
* architecture.
|
|
*/
|
|
if (env->qemu_src && env->qemu_dst) {
|
|
g_test_message("Only one of %s, %s is allowed",
|
|
QEMU_ENV_SRC, QEMU_ENV_DST);
|
|
exit(1);
|
|
}
|
|
|
|
env->has_kvm = qtest_has_accel("kvm");
|
|
env->has_tcg = qtest_has_accel("tcg");
|
|
|
|
if (!env->has_tcg && !env->has_kvm) {
|
|
g_test_skip("No KVM or TCG accelerator available");
|
|
return env;
|
|
}
|
|
|
|
env->has_dirty_ring = kvm_dirty_ring_supported();
|
|
env->has_uffd = ufd_version_check(&env->uffd_feature_thread_id);
|
|
env->arch = qtest_get_arch();
|
|
env->is_x86 = !strcmp(env->arch, "i386") || !strcmp(env->arch, "x86_64");
|
|
|
|
env->tmpfs = g_dir_make_tmp("migration-test-XXXXXX", &err);
|
|
if (!env->tmpfs) {
|
|
g_test_message("Can't create temporary directory in %s: %s",
|
|
g_get_tmp_dir(), err->message);
|
|
}
|
|
g_assert(env->tmpfs);
|
|
|
|
tmpfs = env->tmpfs;
|
|
|
|
return env;
|
|
}
|
|
|
|
int migration_env_clean(MigrationTestEnv *env)
|
|
{
|
|
char *tmpfs;
|
|
int ret = 0;
|
|
|
|
if (!env) {
|
|
return ret;
|
|
}
|
|
|
|
bootfile_delete();
|
|
|
|
tmpfs = env->tmpfs;
|
|
ret = rmdir(tmpfs);
|
|
if (ret != 0) {
|
|
g_test_message("unable to rmdir: path (%s): %s",
|
|
tmpfs, strerror(errno));
|
|
}
|
|
g_free(tmpfs);
|
|
|
|
return ret;
|
|
}
|