Compare commits

...

11 Commits

Author SHA1 Message Date
Fabiano Rosas
12b4a9c05e tests/qtest: migration-test: Add tests for file-based migration
Add basic tests for file-based migration.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
2023-06-30 16:57:29 -03:00
Fabiano Rosas
69f57525d2 tests/qtest: migration: Add support for negative testing of qmp_migrate
There is currently no way to write a test for errors that happened in
qmp_migrate before the migration has started.

Add a version of qmp_migrate that ensures an error happens and tests
the error message. To make use of it a test needs to declare:

    MigrateCommon args = {
        .result = MIG_TEST_QMP_ERROR,
        .error_str = "error message"
    }

Signed-off-by: Fabiano Rosas <farosas@suse.de>
2023-06-30 16:57:29 -03:00
Fabiano Rosas
e00d1a4213 migration: Set migration status early in incoming side
We are sending a migration event of MIGRATION_STATUS_SETUP at
qemu_start_incoming_migration but never actually setting the state.

This creates a window between qmp_migrate_incoming and
process_incoming_migration_co where the migration status is still
MIGRATION_STATUS_NONE. Calling query-migrate during this time will
return an empty response even though the incoming migration command
has already been issued.

Commit 7cf1fe6d68 ("migration: Add migration events on target side")
has added support to the 'events' capability to the incoming part of
migration, but chose to send the SETUP event without setting the
state. I'm assuming this was a mistake.

This introduces a change in behavior, any QMP client waiting for the
SETUP event will hang, unless it has previously enabled the 'events'
capability. Having the capability enabled is sufficient to continue to
receive the event.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
2023-06-30 16:57:25 -03:00
Fabiano Rosas
afa710ea20 tests/qtest: migration: Use migrate_incoming_qmp where appropriate
Use the new migrate_incoming_qmp helper in the places that currently
open-code calling migrate-incoming.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Juan Quintela <quintela@redhat.com>
2023-06-30 16:40:16 -03:00
Fabiano Rosas
6f4e4392d0 tests/qtest: migration: Add migrate_incoming_qmp helper
file-based migration requires the target to initiate its migration after
the source has finished writing out the data in the file. Currently
there's no easy way to initiate 'migrate-incoming', allow this by
introducing migrate_incoming_qmp helper, similarly to migrate_qmp.

Also make sure migration events are enabled and wait for the incoming
migration to start before returning. This avoid a race when querying
the migration status too soon after issuing the command.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
2023-06-30 16:40:15 -03:00
Fabiano Rosas
7670030d45 tests/qtest: migration: Expose migrate_set_capability
The following patch will make use of this function from within
migrate-helpers.c, so move it there.

Reviewed-by: Juan Quintela <quintela@redhat.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
2023-06-30 16:40:15 -03:00
Steve Sistare
879822f1e7 migration: file URI offset
Allow an offset option to be specified as part of the file URI, in
the form "file:filename,offset=offset", where offset accepts the common
size suffixes, or the 0x prefix, but not both.  Migration data is written
to and read from the file starting at offset.  If unspecified, it defaults
to 0.

This is needed by libvirt to store its own data at the head of the file.

Suggested-by: Daniel P. Berrange <berrange@redhat.com>
Signed-off-by: Steve Sistare <steven.sistare@oracle.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
2023-06-30 16:40:15 -03:00
Steve Sistare
28f7429b27 migration: file URI
Extend the migration URI to support file:<filename>.  This can be used for
any migration scenario that does not require a reverse path.  It can be
used as an alternative to 'exec:cat > file' in minimized containers that
do not contain /bin/sh, and it is easier to use than the fd:<fdname> URI.
It can be used in HMP commands, and as a qemu command-line parameter.

For best performance, guest ram should be shared and x-ignore-shared
should be true, so guest pages are not written to the file, in which case
the guest may remain running.  If ram is not so configured, then the user
is advised to stop the guest first.  Otherwise, a busy guest may re-dirty
the same page, causing it to be appended to the file multiple times,
and the file may grow unboundedly.  That issue is being addressed in the
"fixed-ram" patch series.

Signed-off-by: Steve Sistare <steven.sistare@oracle.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Peter Xu <peterx@redhat.com>
2023-06-30 16:40:15 -03:00
Fabiano Rosas
4f34243b94 tests/qtest: Re-enable multifd cancel test
We've found the source of flakiness in this test, so re-enable it.

Reviewed-by: Juan Quintela <quintela@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
2023-06-30 16:40:15 -03:00
Fabiano Rosas
f6d4af8ec4 migration/multifd: Protect accesses to migration_threads
This doubly linked list is common for all the multifd and migration
threads so we need to avoid concurrent access.

Add a mutex to protect the data from concurrent access. This fixes a
crash when removing two MigrationThread objects from the list at the
same time during cleanup of multifd threads.

Fixes: 671326201d ("migration: Introduce interface query-migrationthreads")
Signed-off-by: Fabiano Rosas <farosas@suse.de>
2023-06-30 16:40:15 -03:00
Fabiano Rosas
aa86afaa1a migration/multifd: Rename threadinfo.c functions
We're about to add more functions to this file so make it use the same
coding style as the rest of the code.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Juan Quintela <quintela@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
2023-06-30 16:40:15 -03:00
16 changed files with 418 additions and 114 deletions

103
migration/file.c Normal file
View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2021-2023 Oracle and/or its affiliates.
*
* This work is licensed under the terms of the GNU GPL, version 2.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/cutils.h"
#include "qapi/error.h"
#include "channel.h"
#include "file.h"
#include "migration.h"
#include "io/channel-file.h"
#include "io/channel-util.h"
#include "trace.h"
#define OFFSET_OPTION ",offset="
/* Remove the offset option from @filespec and return it in @offsetp. */
static int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp)
{
char *option = strstr(filespec, OFFSET_OPTION);
int ret;
if (option) {
*option = 0;
option += sizeof(OFFSET_OPTION) - 1;
ret = qemu_strtosz(option, NULL, offsetp);
if (ret) {
error_setg_errno(errp, -ret, "file URI has bad offset %s", option);
return -1;
}
}
return 0;
}
void file_start_outgoing_migration(MigrationState *s, const char *filespec,
Error **errp)
{
g_autofree char *filename = g_strdup(filespec);
g_autoptr(QIOChannelFile) fioc = NULL;
uint64_t offset = 0;
QIOChannel *ioc;
trace_migration_file_outgoing(filename);
if (file_parse_offset(filename, &offset, errp)) {
return;
}
fioc = qio_channel_file_new_path(filename, O_CREAT | O_WRONLY | O_TRUNC,
0600, errp);
if (!fioc) {
return;
}
ioc = QIO_CHANNEL(fioc);
if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) {
return;
}
qio_channel_set_name(ioc, "migration-file-outgoing");
migration_channel_connect(s, ioc, NULL, NULL);
}
static gboolean file_accept_incoming_migration(QIOChannel *ioc,
GIOCondition condition,
gpointer opaque)
{
migration_channel_process_incoming(ioc);
object_unref(OBJECT(ioc));
return G_SOURCE_REMOVE;
}
void file_start_incoming_migration(const char *filespec, Error **errp)
{
g_autofree char *filename = g_strdup(filespec);
QIOChannelFile *fioc = NULL;
uint64_t offset = 0;
QIOChannel *ioc;
trace_migration_file_incoming(filename);
if (file_parse_offset(filename, &offset, errp)) {
return;
}
fioc = qio_channel_file_new_path(filename, O_RDONLY, 0, errp);
if (!fioc) {
return;
}
ioc = QIO_CHANNEL(fioc);
if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) {
return;
}
qio_channel_set_name(QIO_CHANNEL(ioc), "migration-file-incoming");
qio_channel_add_watch_full(ioc, G_IO_IN,
file_accept_incoming_migration,
NULL, NULL,
g_main_context_get_thread_default());
}

14
migration/file.h Normal file
View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2021-2023 Oracle and/or its affiliates.
*
* This work is licensed under the terms of the GNU GPL, version 2.
* See the COPYING file in the top-level directory.
*/
#ifndef QEMU_MIGRATION_FILE_H
#define QEMU_MIGRATION_FILE_H
void file_start_incoming_migration(const char *filename, Error **errp);
void file_start_outgoing_migration(MigrationState *s, const char *filename,
Error **errp);
#endif

View File

@@ -16,6 +16,7 @@ system_ss.add(files(
'dirtyrate.c',
'exec.c',
'fd.c',
'file.c',
'global_state.c',
'migration-hmp-cmds.c',
'migration.c',

View File

@@ -20,6 +20,7 @@
#include "migration/blocker.h"
#include "exec.h"
#include "fd.h"
#include "file.h"
#include "socket.h"
#include "sysemu/runstate.h"
#include "sysemu/sysemu.h"
@@ -424,13 +425,16 @@ void migrate_add_address(SocketAddress *address)
static void qemu_start_incoming_migration(const char *uri, Error **errp)
{
const char *p = NULL;
MigrationIncomingState *mis = migration_incoming_get_current();
/* URI is not suitable for migration? */
if (!migration_channels_and_uri_compatible(uri, errp)) {
return;
}
qapi_event_send_migration(MIGRATION_STATUS_SETUP);
migrate_set_state(&mis->state, MIGRATION_STATUS_NONE,
MIGRATION_STATUS_SETUP);
if (strstart(uri, "tcp:", &p) ||
strstart(uri, "unix:", NULL) ||
strstart(uri, "vsock:", NULL)) {
@@ -443,6 +447,8 @@ static void qemu_start_incoming_migration(const char *uri, Error **errp)
exec_start_incoming_migration(p, errp);
} else if (strstart(uri, "fd:", &p)) {
fd_start_incoming_migration(p, errp);
} else if (strstart(uri, "file:", &p)) {
file_start_incoming_migration(p, errp);
} else {
error_setg(errp, "unknown migration protocol: %s", uri);
}
@@ -522,7 +528,7 @@ process_incoming_migration_co(void *opaque)
mis->largest_page_size = qemu_ram_pagesize_largest();
postcopy_state_set(POSTCOPY_INCOMING_NONE);
migrate_set_state(&mis->state, MIGRATION_STATUS_NONE,
migrate_set_state(&mis->state, MIGRATION_STATUS_SETUP,
MIGRATION_STATUS_ACTIVE);
mis->loadvm_co = qemu_coroutine_self();
@@ -1670,6 +1676,8 @@ void qmp_migrate(const char *uri, bool has_blk, bool blk,
exec_start_outgoing_migration(s, p, &local_err);
} else if (strstart(uri, "fd:", &p)) {
fd_start_outgoing_migration(s, p, &local_err);
} else if (strstart(uri, "file:", &p)) {
file_start_outgoing_migration(s, p, &local_err);
} else {
if (!(has_resume && resume)) {
yank_unregister_instance(MIGRATION_YANK_INSTANCE);
@@ -2951,7 +2959,7 @@ static void *migration_thread(void *opaque)
MigThrError thr_error;
bool urgent = false;
thread = MigrationThreadAdd("live_migration", qemu_get_thread_id());
thread = migration_threads_add("live_migration", qemu_get_thread_id());
rcu_register_thread();
@@ -3029,7 +3037,7 @@ static void *migration_thread(void *opaque)
migration_iteration_finish(s);
object_unref(OBJECT(s));
rcu_unregister_thread();
MigrationThreadDel(thread);
migration_threads_remove(thread);
return NULL;
}

View File

@@ -651,7 +651,7 @@ static void *multifd_send_thread(void *opaque)
int ret = 0;
bool use_zero_copy_send = migrate_zero_copy_send();
thread = MigrationThreadAdd(p->name, qemu_get_thread_id());
thread = migration_threads_add(p->name, qemu_get_thread_id());
trace_multifd_send_thread_start(p->id);
rcu_register_thread();
@@ -767,7 +767,7 @@ out:
qemu_mutex_unlock(&p->mutex);
rcu_unregister_thread();
MigrationThreadDel(thread);
migration_threads_remove(thread);
trace_multifd_send_thread_end(p->id, p->num_packets, p->total_normal_pages);
return NULL;

View File

@@ -10,23 +10,35 @@
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/queue.h"
#include "qemu/lockable.h"
#include "threadinfo.h"
QemuMutex migration_threads_lock;
static QLIST_HEAD(, MigrationThread) migration_threads;
MigrationThread *MigrationThreadAdd(const char *name, int thread_id)
static void __attribute__((constructor)) migration_threads_init(void)
{
qemu_mutex_init(&migration_threads_lock);
}
MigrationThread *migration_threads_add(const char *name, int thread_id)
{
MigrationThread *thread = g_new0(MigrationThread, 1);
thread->name = name;
thread->thread_id = thread_id;
QLIST_INSERT_HEAD(&migration_threads, thread, node);
WITH_QEMU_LOCK_GUARD(&migration_threads_lock) {
QLIST_INSERT_HEAD(&migration_threads, thread, node);
}
return thread;
}
void MigrationThreadDel(MigrationThread *thread)
void migration_threads_remove(MigrationThread *thread)
{
QEMU_LOCK_GUARD(&migration_threads_lock);
if (thread) {
QLIST_REMOVE(thread, node);
g_free(thread);
@@ -39,6 +51,7 @@ MigrationThreadInfoList *qmp_query_migrationthreads(Error **errp)
MigrationThreadInfoList **tail = &head;
MigrationThread *thread = NULL;
QEMU_LOCK_GUARD(&migration_threads_lock);
QLIST_FOREACH(thread, &migration_threads, node) {
MigrationThreadInfo *info = g_new0(MigrationThreadInfo, 1);
info->name = g_strdup(thread->name);

View File

@@ -10,8 +10,6 @@
* See the COPYING file in the top-level directory.
*/
#include "qemu/queue.h"
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-migration.h"
@@ -23,6 +21,5 @@ struct MigrationThread {
QLIST_ENTRY(MigrationThread) node;
};
MigrationThread *MigrationThreadAdd(const char *name, int thread_id);
void MigrationThreadDel(MigrationThread *info);
MigrationThread *migration_threads_add(const char *name, int thread_id);
void migration_threads_remove(MigrationThread *info);

View File

@@ -310,6 +310,10 @@ migration_exec_incoming(const char *cmd) "cmd=%s"
migration_fd_outgoing(int fd) "fd=%d"
migration_fd_incoming(int fd) "fd=%d"
# file.c
migration_file_outgoing(const char *filename) "filename=%s"
migration_file_incoming(const char *filename) "filename=%s"
# socket.c
migration_socket_incoming_accepted(void) ""
migration_socket_outgoing_connected(const char *hostname) "hostname=%s"

View File

@@ -4622,6 +4622,7 @@ DEF("incoming", HAS_ARG, QEMU_OPTION_incoming, \
" prepare for incoming migration, listen on\n" \
" specified protocol and socket address\n" \
"-incoming fd:fd\n" \
"-incoming file:filename[,offset=offset]\n" \
"-incoming exec:cmdline\n" \
" accept incoming migration on given file descriptor\n" \
" or from given external command\n" \
@@ -4638,7 +4639,11 @@ SRST
Prepare for incoming migration, listen on a given unix socket.
``-incoming fd:fd``
Accept incoming migration from a given filedescriptor.
Accept incoming migration from a given file descriptor.
``-incoming file:filename[,offset=offset]``
Accept incoming migration from a given file starting at offset.
offset allows the common size suffixes, or a 0x prefix, but not both.
``-incoming exec:cmdline``
Accept incoming migration as an output from specified external

View File

@@ -1248,6 +1248,28 @@ void qtest_memset(QTestState *s, uint64_t addr, uint8_t pattern, size_t size)
qtest_rsp(s);
}
QDict *qtest_vqmp_assert_failure_ref(QTestState *qts,
const char *fmt, va_list args)
{
QDict *response;
QDict *ret;
response = qtest_vqmp(qts, fmt, args);
g_assert(response);
if (!qdict_haskey(response, "error")) {
g_autoptr(GString) s = qobject_to_json_pretty(QOBJECT(response), true);
g_test_message("%s", s->str);
}
g_assert(qdict_haskey(response, "error"));
g_assert(!qdict_haskey(response, "return"));
ret = qdict_get_qdict(response, "error");
qobject_ref(ret);
qobject_unref(response);
return ret;
}
QDict *qtest_vqmp_assert_success_ref(QTestState *qts,
const char *fmt, va_list args)
{
@@ -1310,6 +1332,17 @@ void qtest_vqmp_fds_assert_success(QTestState *qts, int *fds, size_t nfds,
}
#endif /* !_WIN32 */
QDict *qtest_qmp_assert_failure_ref(QTestState *qts, const char *fmt, ...)
{
QDict *response;
va_list ap;
va_start(ap, fmt);
response = qtest_vqmp_assert_failure_ref(qts, fmt, ap);
va_end(ap);
return response;
}
QDict *qtest_qmp_assert_success_ref(QTestState *qts, const char *fmt, ...)
{
QDict *response;

View File

@@ -799,6 +799,34 @@ void qtest_vqmp_fds_assert_success(QTestState *qts, int *fds, size_t nfds,
G_GNUC_PRINTF(4, 0);
#endif /* !_WIN32 */
/**
* qtest_qmp_assert_failure_ref:
* @qts: QTestState instance to operate on
* @fmt: QMP message to send to qemu, formatted like
* qobject_from_jsonf_nofail(). See parse_interpolation() for what's
* supported after '%'.
*
* Sends a QMP message to QEMU, asserts that an 'error' key is present in
* the response, and returns the response.
*/
QDict *qtest_qmp_assert_failure_ref(QTestState *qts, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
/**
* qtest_vqmp_assert_failure_ref:
* @qts: QTestState instance to operate on
* @fmt: QMP message to send to qemu, formatted like
* qobject_from_jsonf_nofail(). See parse_interpolation() for what's
* supported after '%'.
* @args: variable arguments for @fmt
*
* Sends a QMP message to QEMU, asserts that an 'error' key is present in
* the response, and returns the response.
*/
QDict *qtest_vqmp_assert_failure_ref(QTestState *qts,
const char *fmt, va_list args)
G_GNUC_PRINTF(2, 0);
/**
* qtest_qmp_assert_success_ref:
* @qts: QTestState instance to operate on

View File

@@ -313,6 +313,7 @@ qtests = {
'tpm-tis-i2c-test': [io, tpmemu_files, 'qtest_aspeed.c'],
'tpm-tis-device-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'],
'tpm-tis-device-test': [io, tpmemu_files, 'tpm-tis-util.c'],
'virtio-net-failover': files('migration-helpers.c'),
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
}

View File

@@ -49,6 +49,26 @@ bool migrate_watch_for_resume(QTestState *who, const char *name,
return false;
}
void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...)
{
va_list ap;
QDict *args, *err;
va_start(ap, fmt);
args = qdict_from_vjsonf_nofail(fmt, ap);
va_end(ap);
g_assert(!qdict_haskey(args, "uri"));
qdict_put_str(args, "uri", uri);
err = qtest_qmp_assert_failure_ref(
who, "{ 'execute': 'migrate', 'arguments': %p}", args);
g_assert(qdict_haskey(err, "desc"));
qobject_unref(err);
}
/*
* Send QMP command "migrate".
* Arguments are built from @fmt... (formatted like
@@ -70,6 +90,46 @@ void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
"{ 'execute': 'migrate', 'arguments': %p}", args);
}
void migrate_set_capability(QTestState *who, const char *capability,
bool value)
{
qtest_qmp_assert_success(who,
"{ 'execute': 'migrate-set-capabilities',"
"'arguments': { "
"'capabilities': [ { "
"'capability': %s, 'state': %i } ] } }",
capability, value);
}
void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...)
{
va_list ap;
QDict *args, *rsp, *data;
va_start(ap, fmt);
args = qdict_from_vjsonf_nofail(fmt, ap);
va_end(ap);
g_assert(!qdict_haskey(args, "uri"));
qdict_put_str(args, "uri", uri);
migrate_set_capability(to, "events", true);
rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
args);
g_assert(qdict_haskey(rsp, "return"));
qobject_unref(rsp);
rsp = qtest_qmp_eventwait_ref(to, "MIGRATION");
g_assert(qdict_haskey(rsp, "data"));
data = qdict_get_qdict(rsp, "data");
g_assert(qdict_haskey(data, "status"));
g_assert_cmpstr(qdict_get_str(data, "status"), ==, "setup");
qobject_unref(rsp);
}
/*
* Note: caller is responsible to free the returned object via
* qobject_unref() after use

View File

@@ -23,6 +23,16 @@ bool migrate_watch_for_resume(QTestState *who, const char *name,
G_GNUC_PRINTF(3, 4)
void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...);
G_GNUC_PRINTF(3, 4)
void migrate_incoming_qmp(QTestState *who, const char *uri,
const char *fmt, ...);
G_GNUC_PRINTF(3, 4)
void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...);
void migrate_set_capability(QTestState *who, const char *capability,
bool value);
QDict *migrate_query(QTestState *who);
QDict *migrate_query_not_failed(QTestState *who);

View File

@@ -52,6 +52,10 @@ static bool got_dst_resume;
*/
#define DIRTYLIMIT_TOLERANCE_RANGE 25 /* MB/s */
#define QEMU_VM_FILE_MAGIC 0x5145564d
#define FILE_TEST_FILENAME "migfile"
#define FILE_TEST_OFFSET 0x1000
#if defined(__linux__)
#include <sys/syscall.h>
#include <sys/vfs.h>
@@ -472,17 +476,6 @@ static void migrate_cancel(QTestState *who)
qtest_qmp_assert_success(who, "{ 'execute': 'migrate_cancel' }");
}
static void migrate_set_capability(QTestState *who, const char *capability,
bool value)
{
qtest_qmp_assert_success(who,
"{ 'execute': 'migrate-set-capabilities',"
"'arguments': { "
"'capabilities': [ { "
"'capability': %s, 'state': %i } ] } }",
capability, value);
}
static void migrate_postcopy_start(QTestState *from, QTestState *to)
{
qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }");
@@ -575,6 +568,8 @@ typedef struct {
MIG_TEST_FAIL,
/* This test should fail, dest qemu should fail with abnormal status */
MIG_TEST_FAIL_DEST_QUIT_ERR,
/* The QMP command for this migration should fail with an error */
MIG_TEST_QMP_ERROR,
} result;
/* Optional: set number of migration passes to wait for, if live==true */
@@ -771,6 +766,7 @@ static void test_migrate_end(QTestState *from, QTestState *to, bool test_dest)
cleanup("migsocket");
cleanup("src_serial");
cleanup("dest_serial");
cleanup(FILE_TEST_FILENAME);
}
#ifdef CONFIG_GNUTLS
@@ -1390,6 +1386,7 @@ static void test_precopy_common(MigrateCommon *args)
{
QTestState *from, *to;
void *data_hook = NULL;
g_autofree char *connect_uri = NULL;
if (test_migrate_start(&from, &to, args->listen_uri, &args->start)) {
return;
@@ -1430,13 +1427,17 @@ static void test_precopy_common(MigrateCommon *args)
}
if (!args->connect_uri) {
g_autofree char *local_connect_uri =
migrate_get_socket_address(to, "socket-address");
migrate_qmp(from, local_connect_uri, "{}");
connect_uri = migrate_get_socket_address(to, "socket-address");
} else {
migrate_qmp(from, args->connect_uri, "{}");
connect_uri = g_strdup(args->connect_uri);
}
if (args->result == MIG_TEST_QMP_ERROR) {
migrate_qmp_fail(from, connect_uri, "{}");
goto finish;
}
migrate_qmp(from, connect_uri, "{}");
if (args->result != MIG_TEST_SUCCEED) {
bool allow_active = args->result == MIG_TEST_FAIL;
@@ -1463,11 +1464,28 @@ static void test_precopy_common(MigrateCommon *args)
*/
wait_for_migration_complete(from);
/*
* For file based migration the target must begin its
* migration after the source has finished.
*/
if (strstr(connect_uri, "file:")) {
migrate_incoming_qmp(to, connect_uri, "{}");
}
if (!got_src_stop) {
qtest_qmp_eventwait(from, "STOP");
}
} else {
wait_for_migration_complete(from);
/*
* For file based migration the target must begin its
* migration after the source has finished.
*/
if (strstr(connect_uri, "file:")) {
migrate_incoming_qmp(to, connect_uri, "{}");
}
/*
* Must wait for dst to finish reading all incoming
* data on the socket before issuing 'cont' otherwise
@@ -1485,6 +1503,7 @@ static void test_precopy_common(MigrateCommon *args)
wait_for_serial("dest_serial");
}
finish:
if (args->finish_hook) {
args->finish_hook(from, to, data_hook);
}
@@ -1684,6 +1703,75 @@ static void test_precopy_unix_compress_nowait(void)
test_precopy_common(&args);
}
static void test_precopy_file(void)
{
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
FILE_TEST_FILENAME);
MigrateCommon args = {
.connect_uri = uri,
.listen_uri = "defer",
};
test_precopy_common(&args);
}
static void file_offset_finish_hook(QTestState *from, QTestState *to, void *opaque)
{
#if defined(__linux__)
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
size_t size = FILE_TEST_OFFSET + sizeof(QEMU_VM_FILE_MAGIC);
uintptr_t *addr, *p;
int fd;
fd = open(path, O_RDONLY);
g_assert(fd != -1);
addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
g_assert(addr != MAP_FAILED);
/*
* Ensure the skipped offset contains zeros and the migration
* stream starts at the right place.
*/
p = addr;
while (p < addr + FILE_TEST_OFFSET / sizeof(uintptr_t)) {
g_assert(*p == 0);
p++;
}
g_assert_cmpint(cpu_to_be32(*p), ==, QEMU_VM_FILE_MAGIC);
munmap(addr, size);
close(fd);
#endif
}
static void test_precopy_file_offset(void)
{
g_autofree char *uri = g_strdup_printf("file:%s/%s,offset=%d", tmpfs,
FILE_TEST_FILENAME,
FILE_TEST_OFFSET);
MigrateCommon args = {
.connect_uri = uri,
.listen_uri = "defer",
.finish_hook = file_offset_finish_hook,
};
test_precopy_common(&args);
}
static void test_precopy_file_offset_bad(void)
{
/* using a value not supported by qemu_strtosz() */
g_autofree char *uri = g_strdup_printf("file:%s/migfile,offset=0x20M",
tmpfs);
MigrateCommon args = {
.connect_uri = uri,
.listen_uri = "defer",
.result = MIG_TEST_QMP_ERROR,
};
test_precopy_common(&args);
}
static void test_precopy_tcp_plain(void)
{
MigrateCommon args = {
@@ -1857,8 +1945,7 @@ static void *test_migrate_fd_start_hook(QTestState *from,
close(pair[0]);
/* Start incoming migration from the 1st socket */
qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
" 'arguments': { 'uri': 'fd:fd-mig' }}");
migrate_incoming_qmp(to, "fd:fd-mig", "{}");
/* Send the 2nd socket to the target */
qtest_qmp_fds_assert_success(from, &pair[1], 1,
@@ -2080,8 +2167,7 @@ test_migrate_precopy_tcp_multifd_start_common(QTestState *from,
migrate_set_capability(to, "multifd", true);
/* Start incoming migration from the 1st socket */
qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
" 'arguments': { 'uri': 'tcp:127.0.0.1:0' }}");
migrate_incoming_qmp(to, "tcp:127.0.0.1:0", "{}");
return NULL;
}
@@ -2333,8 +2419,7 @@ static void test_multifd_tcp_cancel(void)
migrate_set_capability(to, "multifd", true);
/* Start incoming migration from the 1st socket */
qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
" 'arguments': { 'uri': 'tcp:127.0.0.1:0' }}");
migrate_incoming_qmp(to, "tcp:127.0.0.1:0", "{}");
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
@@ -2364,8 +2449,7 @@ static void test_multifd_tcp_cancel(void)
migrate_set_capability(to2, "multifd", true);
/* Start incoming migration from the 1st socket */
qtest_qmp_assert_success(to2, "{ 'execute': 'migrate-incoming',"
" 'arguments': { 'uri': 'tcp:127.0.0.1:0' }}");
migrate_incoming_qmp(to2, "tcp:127.0.0.1:0", "{}");
g_free(uri);
uri = migrate_get_socket_address(to2, "socket-address");
@@ -2737,6 +2821,14 @@ int main(int argc, char **argv)
qtest_add_func("/migration/precopy/unix/compress/nowait",
test_precopy_unix_compress_nowait);
}
qtest_add_func("/migration/precopy/file",
test_precopy_file);
qtest_add_func("/migration/precopy/file/offset",
test_precopy_file_offset);
qtest_add_func("/migration/precopy/file/offset/bad",
test_precopy_file_offset_bad);
#ifdef CONFIG_GNUTLS
qtest_add_func("/migration/precopy/unix/tls/psk",
test_precopy_unix_tls_psk);
@@ -2809,14 +2901,8 @@ int main(int argc, char **argv)
}
qtest_add_func("/migration/multifd/tcp/plain/none",
test_multifd_tcp_none);
/*
* This test is flaky and sometimes fails in CI and otherwise:
* don't run unless user opts in via environment variable.
*/
if (getenv("QEMU_TEST_FLAKY_TESTS")) {
qtest_add_func("/migration/multifd/tcp/plain/cancel",
test_multifd_tcp_cancel);
}
qtest_add_func("/migration/multifd/tcp/plain/cancel",
test_multifd_tcp_cancel);
qtest_add_func("/migration/multifd/tcp/plain/zlib",
test_multifd_tcp_zlib);
#ifdef CONFIG_ZSTD

View File

@@ -11,6 +11,7 @@
#include "libqtest.h"
#include "libqos/pci.h"
#include "libqos/pci-pc.h"
#include "migration-helpers.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qjson.h"
@@ -736,26 +737,10 @@ static void test_migrate_out(gconstpointer opaque)
machine_stop(qts);
}
static QDict *get_migration_event(QTestState *qts)
{
QDict *resp;
QDict *data;
resp = qtest_qmp_eventwait_ref(qts, "MIGRATION");
g_assert(qdict_haskey(resp, "data"));
data = qdict_get_qdict(resp, "data");
g_assert(qdict_haskey(data, "status"));
qobject_ref(data);
qobject_unref(resp);
return data;
}
static void test_migrate_in(gconstpointer opaque)
{
QTestState *qts;
QDict *resp, *args, *ret;
QDict *resp, *ret;
g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
qts = machine_start(BASE_MACHINE
@@ -787,18 +772,7 @@ static void test_migrate_in(gconstpointer opaque)
check_one_card(qts, true, "standby0", MAC_STANDBY0);
check_one_card(qts, false, "primary0", MAC_PRIMARY0);
args = qdict_from_jsonf_nofail("{}");
g_assert_nonnull(args);
qdict_put_str(args, "uri", uri);
resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
args);
g_assert(qdict_haskey(resp, "return"));
qobject_unref(resp);
resp = get_migration_event(qts);
g_assert_cmpstr(qdict_get_str(resp, "status"), ==, "setup");
qobject_unref(resp);
migrate_incoming_qmp(qts, uri, "{}");
resp = get_failover_negociated_event(qts);
g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "standby0");
@@ -888,7 +862,7 @@ static void test_off_migrate_out(gconstpointer opaque)
static void test_off_migrate_in(gconstpointer opaque)
{
QTestState *qts;
QDict *resp, *args, *ret;
QDict *ret;
g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
qts = machine_start(BASE_MACHINE
@@ -920,18 +894,7 @@ static void test_off_migrate_in(gconstpointer opaque)
check_one_card(qts, true, "standby0", MAC_STANDBY0);
check_one_card(qts, true, "primary0", MAC_PRIMARY0);
args = qdict_from_jsonf_nofail("{}");
g_assert_nonnull(args);
qdict_put_str(args, "uri", uri);
resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
args);
g_assert(qdict_haskey(resp, "return"));
qobject_unref(resp);
resp = get_migration_event(qts);
g_assert_cmpstr(qdict_get_str(resp, "status"), ==, "setup");
qobject_unref(resp);
migrate_incoming_qmp(qts, uri, "{}");
check_one_card(qts, true, "standby0", MAC_STANDBY0);
check_one_card(qts, true, "primary0", MAC_PRIMARY0);
@@ -1026,7 +989,7 @@ static void test_guest_off_migrate_out(gconstpointer opaque)
static void test_guest_off_migrate_in(gconstpointer opaque)
{
QTestState *qts;
QDict *resp, *args, *ret;
QDict *ret;
g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
qts = machine_start(BASE_MACHINE
@@ -1058,18 +1021,7 @@ static void test_guest_off_migrate_in(gconstpointer opaque)
check_one_card(qts, true, "standby0", MAC_STANDBY0);
check_one_card(qts, false, "primary0", MAC_PRIMARY0);
args = qdict_from_jsonf_nofail("{}");
g_assert_nonnull(args);
qdict_put_str(args, "uri", uri);
resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
args);
g_assert(qdict_haskey(resp, "return"));
qobject_unref(resp);
resp = get_migration_event(qts);
g_assert_cmpstr(qdict_get_str(resp, "status"), ==, "setup");
qobject_unref(resp);
migrate_incoming_qmp(qts, uri, "{}");
check_one_card(qts, true, "standby0", MAC_STANDBY0);
check_one_card(qts, false, "primary0", MAC_PRIMARY0);
@@ -1728,7 +1680,7 @@ static void test_multi_out(gconstpointer opaque)
static void test_multi_in(gconstpointer opaque)
{
QTestState *qts;
QDict *resp, *args, *ret;
QDict *resp, *ret;
g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
qts = machine_start(BASE_MACHINE
@@ -1794,18 +1746,7 @@ static void test_multi_in(gconstpointer opaque)
check_one_card(qts, true, "standby1", MAC_STANDBY1);
check_one_card(qts, false, "primary1", MAC_PRIMARY1);
args = qdict_from_jsonf_nofail("{}");
g_assert_nonnull(args);
qdict_put_str(args, "uri", uri);
resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
args);
g_assert(qdict_haskey(resp, "return"));
qobject_unref(resp);
resp = get_migration_event(qts);
g_assert_cmpstr(qdict_get_str(resp, "status"), ==, "setup");
qobject_unref(resp);
migrate_incoming_qmp(qts, uri, "{}");
resp = get_failover_negociated_event(qts);
g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "standby0");