Migration pull for 9.1

- Het's new test cases for "channels"
 - Het's fix for a typo for vsock parsing
 - Cedric's VFIO error report series
 - Cedric's one more patch for dirty-bitmap error reports
 - Zhijian's rdma deprecation patch
 - Yuan's zeropage optimization to fix double faults on anon mem
 - Zhijian's COLO fix on a crash
 -----BEGIN PGP SIGNATURE-----
 
 iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCZig4HxIccGV0ZXJ4QHJl
 ZGhhdC5jb20ACgkQO1/MzfOr1wbQiwD/V5nSJzSuAG4Ra1Fjo+LRG2TT6qk8eNCi
 fIytehSw6cYA/0wqarxOF0tr7ikeyhtG3w4xFf44kk6KcPkoVSl1tqoL
 =pJmQ
 -----END PGP SIGNATURE-----

Merge tag 'migration-20240423-pull-request' of https://gitlab.com/peterx/qemu into staging

Migration pull for 9.1

- Het's new test cases for "channels"
- Het's fix for a typo for vsock parsing
- Cedric's VFIO error report series
- Cedric's one more patch for dirty-bitmap error reports
- Zhijian's rdma deprecation patch
- Yuan's zeropage optimization to fix double faults on anon mem
- Zhijian's COLO fix on a crash

# -----BEGIN PGP SIGNATURE-----
#
# iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCZig4HxIccGV0ZXJ4QHJl
# ZGhhdC5jb20ACgkQO1/MzfOr1wbQiwD/V5nSJzSuAG4Ra1Fjo+LRG2TT6qk8eNCi
# fIytehSw6cYA/0wqarxOF0tr7ikeyhtG3w4xFf44kk6KcPkoVSl1tqoL
# =pJmQ
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 23 Apr 2024 03:37:19 PM PDT
# gpg:                using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706
# gpg:                issuer "peterx@redhat.com"
# gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [unknown]
# gpg:                 aka "Peter Xu <peterx@redhat.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D  D1A9 3B5F CCCD F3AB D706

* tag 'migration-20240423-pull-request' of https://gitlab.com/peterx/qemu: (26 commits)
  migration/colo: Fix bdrv_graph_rdlock_main_loop: Assertion `!qemu_in_coroutine()' failed.
  migration/multifd: solve zero page causing multiple page faults
  migration: Add Error** argument to add_bitmaps_to_list()
  migration: Modify ram_init_bitmaps() to report dirty tracking errors
  migration: Add Error** argument to xbzrle_init()
  migration: Add Error** argument to ram_state_init()
  memory: Add Error** argument to the global_dirty_log routines
  migration: Introduce ram_bitmaps_destroy()
  memory: Add Error** argument to .log_global_start() handler
  migration: Add Error** argument to .load_setup() handler
  migration: Add Error** argument to .save_setup() handler
  migration: Add Error** argument to qemu_savevm_state_setup()
  migration: Add Error** argument to vmstate_save()
  migration: Always report an error in ram_save_setup()
  migration: Always report an error in block_save_setup()
  vfio: Always report an error in vfio_save_setup()
  s390/stattrib: Add Error** argument to set_migrationmode() handler
  tests/qtest/migration: Fix typo for vsock in SocketAddress_to_str
  tests/qtest/migration: Add negative tests to validate migration QAPIs
  tests/qtest/migration: Add multifd_tcp_plain test using list of channels instead of uri
  ...

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2024-04-23 21:32:22 -07:00
commit 88daa112d4
28 changed files with 558 additions and 216 deletions

View file

@ -13,6 +13,11 @@
#include "qemu/osdep.h"
#include "qemu/ctype.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qapi-visit-sockets.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/error.h"
#include "qapi/qmp/qlist.h"
#include "qemu/cutils.h"
#include "migration-helpers.h"
@ -24,6 +29,127 @@
*/
#define MIGRATION_STATUS_WAIT_TIMEOUT 120
static char *SocketAddress_to_str(SocketAddress *addr)
{
switch (addr->type) {
case SOCKET_ADDRESS_TYPE_INET:
return g_strdup_printf("tcp:%s:%s",
addr->u.inet.host,
addr->u.inet.port);
case SOCKET_ADDRESS_TYPE_UNIX:
return g_strdup_printf("unix:%s",
addr->u.q_unix.path);
case SOCKET_ADDRESS_TYPE_FD:
return g_strdup_printf("fd:%s", addr->u.fd.str);
case SOCKET_ADDRESS_TYPE_VSOCK:
return g_strdup_printf("vsock:%s:%s",
addr->u.vsock.cid,
addr->u.vsock.port);
default:
return g_strdup("unknown address type");
}
}
static QDict *SocketAddress_to_qdict(SocketAddress *addr)
{
QDict *dict = qdict_new();
switch (addr->type) {
case SOCKET_ADDRESS_TYPE_INET:
qdict_put_str(dict, "type", "inet");
qdict_put_str(dict, "host", addr->u.inet.host);
qdict_put_str(dict, "port", addr->u.inet.port);
break;
case SOCKET_ADDRESS_TYPE_UNIX:
qdict_put_str(dict, "type", "unix");
qdict_put_str(dict, "path", addr->u.q_unix.path);
break;
case SOCKET_ADDRESS_TYPE_FD:
qdict_put_str(dict, "type", "fd");
qdict_put_str(dict, "str", addr->u.fd.str);
break;
case SOCKET_ADDRESS_TYPE_VSOCK:
qdict_put_str(dict, "type", "vsock");
qdict_put_str(dict, "cid", addr->u.vsock.cid);
qdict_put_str(dict, "port", addr->u.vsock.port);
break;
default:
g_assert_not_reached();
break;
}
return dict;
}
static SocketAddress *migrate_get_socket_address(QTestState *who)
{
QDict *rsp;
SocketAddressList *addrs;
SocketAddress *addr;
Visitor *iv = NULL;
QObject *object;
rsp = migrate_query(who);
object = qdict_get(rsp, "socket-address");
iv = qobject_input_visitor_new(object);
visit_type_SocketAddressList(iv, NULL, &addrs, &error_abort);
addr = addrs->value;
visit_free(iv);
qobject_unref(rsp);
return addr;
}
static char *
migrate_get_connect_uri(QTestState *who)
{
SocketAddress *addrs;
char *connect_uri;
addrs = migrate_get_socket_address(who);
connect_uri = SocketAddress_to_str(addrs);
qapi_free_SocketAddress(addrs);
return connect_uri;
}
static QDict *
migrate_get_connect_qdict(QTestState *who)
{
SocketAddress *addrs;
QDict *connect_qdict;
addrs = migrate_get_socket_address(who);
connect_qdict = SocketAddress_to_qdict(addrs);
qapi_free_SocketAddress(addrs);
return connect_qdict;
}
static void migrate_set_ports(QTestState *to, QList *channel_list)
{
QDict *addr;
QListEntry *entry;
const char *addr_port = NULL;
addr = migrate_get_connect_qdict(to);
QLIST_FOREACH_ENTRY(channel_list, entry) {
QDict *channel = qobject_to(QDict, qlist_entry_obj(entry));
QDict *addrdict = qdict_get_qdict(channel, "addr");
if (qdict_haskey(addrdict, "port") &&
qdict_haskey(addr, "port") &&
(strcmp(qdict_get_str(addrdict, "port"), "0") == 0)) {
addr_port = qdict_get_str(addr, "port");
qdict_put_str(addrdict, "port", g_strdup(addr_port));
}
}
qobject_unref(addr);
}
bool migrate_watch_for_events(QTestState *who, const char *name,
QDict *event, void *opaque)
{
@ -43,7 +169,8 @@ bool migrate_watch_for_events(QTestState *who, const char *name,
return false;
}
void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...)
void migrate_qmp_fail(QTestState *who, const char *uri,
const char *channels, const char *fmt, ...)
{
va_list ap;
QDict *args, *err;
@ -53,7 +180,15 @@ void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...)
va_end(ap);
g_assert(!qdict_haskey(args, "uri"));
qdict_put_str(args, "uri", uri);
if (uri) {
qdict_put_str(args, "uri", uri);
}
g_assert(!qdict_haskey(args, "channels"));
if (channels) {
QObject *channels_obj = qobject_from_json(channels, &error_abort);
qdict_put_obj(args, "channels", channels_obj);
}
err = qtest_qmp_assert_failure_ref(
who, "{ 'execute': 'migrate', 'arguments': %p}", args);
@ -68,17 +203,32 @@ void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...)
* Arguments are built from @fmt... (formatted like
* qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
*/
void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
void migrate_qmp(QTestState *who, QTestState *to, const char *uri,
const char *channels, const char *fmt, ...)
{
va_list ap;
QDict *args;
g_autofree char *connect_uri = NULL;
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);
if (uri) {
qdict_put_str(args, "uri", uri);
} else if (!channels) {
connect_uri = migrate_get_connect_uri(to);
qdict_put_str(args, "uri", connect_uri);
}
g_assert(!qdict_haskey(args, "channels"));
if (channels) {
QObject *channels_obj = qobject_from_json(channels, &error_abort);
QList *channel_list = qobject_to(QList, channels_obj);
migrate_set_ports(to, channel_list);
qdict_put_obj(args, "channels", channels_obj);
}
qtest_qmp_assert_success(who,
"{ 'execute': 'migrate', 'arguments': %p}", args);

View file

@ -25,15 +25,17 @@ typedef struct QTestMigrationState {
bool migrate_watch_for_events(QTestState *who, const char *name,
QDict *event, void *opaque);
G_GNUC_PRINTF(3, 4)
void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...);
G_GNUC_PRINTF(5, 6)
void migrate_qmp(QTestState *who, QTestState *to, const char *uri,
const char *channels, 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, ...);
G_GNUC_PRINTF(4, 5)
void migrate_qmp_fail(QTestState *who, const char *uri,
const char *channels, const char *fmt, ...);
void migrate_set_capability(QTestState *who, const char *capability,
bool value);

View file

@ -13,16 +13,12 @@
#include "qemu/osdep.h"
#include "libqtest.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/range.h"
#include "qemu/sockets.h"
#include "chardev/char.h"
#include "qapi/qapi-visit-sockets.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
#include "crypto/tlscredspsk.h"
#include "qapi/qmp/qlist.h"
@ -369,50 +365,6 @@ static void cleanup(const char *filename)
unlink(path);
}
static char *SocketAddress_to_str(SocketAddress *addr)
{
switch (addr->type) {
case SOCKET_ADDRESS_TYPE_INET:
return g_strdup_printf("tcp:%s:%s",
addr->u.inet.host,
addr->u.inet.port);
case SOCKET_ADDRESS_TYPE_UNIX:
return g_strdup_printf("unix:%s",
addr->u.q_unix.path);
case SOCKET_ADDRESS_TYPE_FD:
return g_strdup_printf("fd:%s", addr->u.fd.str);
case SOCKET_ADDRESS_TYPE_VSOCK:
return g_strdup_printf("tcp:%s:%s",
addr->u.vsock.cid,
addr->u.vsock.port);
default:
return g_strdup("unknown address type");
}
}
static char *migrate_get_socket_address(QTestState *who, const char *parameter)
{
QDict *rsp;
char *result;
SocketAddressList *addrs;
Visitor *iv = NULL;
QObject *object;
rsp = migrate_query(who);
object = qdict_get(rsp, parameter);
iv = qobject_input_visitor_new(object);
visit_type_SocketAddressList(iv, NULL, &addrs, &error_abort);
visit_free(iv);
/* we are only using a single address */
result = SocketAddress_to_str(addrs->value);
qapi_free_SocketAddressList(addrs);
qobject_unref(rsp);
return result;
}
static long long migrate_get_parameter_int(QTestState *who,
const char *parameter)
{
@ -703,6 +655,13 @@ typedef struct {
*/
const char *connect_uri;
/*
* Optional: JSON-formatted list of src QEMU URIs. If a port is
* defined as '0' in any QDict key a value of '0' will be
* automatically converted to the correct destination port.
*/
const char *connect_channels;
/* Optional: callback to run at start to set migration parameters */
TestMigrateStartHook start_hook;
/* Optional: callback to run at finish to cleanup */
@ -1349,8 +1308,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
wait_for_serial("src_serial");
wait_for_suspend(from, &src_state);
g_autofree char *uri = migrate_get_socket_address(to, "socket-address");
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, NULL, NULL, "{}");
migrate_wait_for_dirty_mem(from, to);
@ -1500,7 +1458,7 @@ static void postcopy_recover_fail(QTestState *from, QTestState *to)
g_assert_cmpint(ret, ==, 1);
migrate_recover(to, "fd:fd-mig");
migrate_qmp(from, "fd:fd-mig", "{'resume': true}");
migrate_qmp(from, to, "fd:fd-mig", NULL, "{'resume': true}");
/*
* Make sure both QEMU instances will go into RECOVER stage, then test
@ -1588,7 +1546,7 @@ static void test_postcopy_recovery_common(MigrateCommon *args)
* Try to rebuild the migration channel using the resume flag and
* the newly created channel
*/
migrate_qmp(from, uri, "{'resume': true}");
migrate_qmp(from, to, uri, NULL, "{'resume': true}");
/* Restore the postcopy bandwidth to unlimited */
migrate_set_parameter_int(from, "max-postcopy-bandwidth", 0);
@ -1669,7 +1627,7 @@ static void test_baddest(void)
if (test_migrate_start(&from, &to, "tcp:127.0.0.1:0", &args)) {
return;
}
migrate_qmp(from, "tcp:127.0.0.1:0", "{}");
migrate_qmp(from, to, "tcp:127.0.0.1:0", NULL, "{}");
wait_for_migration_fail(from, false);
test_migrate_end(from, to, false);
}
@ -1708,7 +1666,7 @@ static void test_analyze_script(void)
uri = g_strdup_printf("exec:cat > %s", file);
migrate_ensure_converge(from);
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, uri, NULL, "{}");
wait_for_migration_complete(from);
pid = fork();
@ -1733,7 +1691,6 @@ 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;
@ -1766,18 +1723,12 @@ static void test_precopy_common(MigrateCommon *args)
}
}
if (!args->connect_uri) {
connect_uri = migrate_get_socket_address(to, "socket-address");
} else {
connect_uri = g_strdup(args->connect_uri);
}
if (args->result == MIG_TEST_QMP_ERROR) {
migrate_qmp_fail(from, connect_uri, "{}");
migrate_qmp_fail(from, args->connect_uri, args->connect_channels, "{}");
goto finish;
}
migrate_qmp(from, connect_uri, "{}");
migrate_qmp(from, to, args->connect_uri, args->connect_channels, "{}");
if (args->result != MIG_TEST_SUCCEED) {
bool allow_active = args->result == MIG_TEST_FAIL;
@ -1843,7 +1794,6 @@ static void test_file_common(MigrateCommon *args, bool stop_src)
{
QTestState *from, *to;
void *data_hook = NULL;
g_autofree char *connect_uri = g_strdup(args->connect_uri);
if (test_migrate_start(&from, &to, args->listen_uri, &args->start)) {
return;
@ -1869,18 +1819,18 @@ static void test_file_common(MigrateCommon *args, bool stop_src)
}
if (args->result == MIG_TEST_QMP_ERROR) {
migrate_qmp_fail(from, connect_uri, "{}");
migrate_qmp_fail(from, args->connect_uri, NULL, "{}");
goto finish;
}
migrate_qmp(from, connect_uri, "{}");
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, connect_uri, "{}");
migrate_incoming_qmp(to, args->connect_uri, "{}");
wait_for_migration_complete(to);
if (stop_src) {
@ -2029,7 +1979,7 @@ static void test_ignore_shared(void)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, uri, NULL, "{}");
migrate_wait_for_dirty_mem(from, to);
@ -2568,7 +2518,7 @@ static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, uri, NULL, "{}");
if (should_fail) {
qtest_set_expected_status(to, EXIT_FAILURE);
@ -2621,6 +2571,55 @@ static void test_validate_uuid_dst_not_set(void)
do_test_validate_uuid(&args, false);
}
static void do_test_validate_uri_channel(MigrateCommon *args)
{
QTestState *from, *to;
if (test_migrate_start(&from, &to, args->listen_uri, &args->start)) {
return;
}
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
/*
* 'uri' and 'channels' validation is checked even before the migration
* starts.
*/
migrate_qmp_fail(from, args->connect_uri, args->connect_channels, "{}");
test_migrate_end(from, to, false);
}
static void test_validate_uri_channels_both_set(void)
{
MigrateCommon args = {
.start = {
.hide_stderr = true,
},
.listen_uri = "defer",
.connect_uri = "tcp:127.0.0.1:0",
.connect_channels = "[ { 'channel-type': 'main',"
" 'addr': { 'transport': 'socket',"
" 'type': 'inet',"
" 'host': '127.0.0.1',"
" 'port': '0' } } ]",
};
do_test_validate_uri_channel(&args);
}
static void test_validate_uri_channels_none_set(void)
{
MigrateCommon args = {
.start = {
.hide_stderr = true,
},
.listen_uri = "defer",
};
do_test_validate_uri_channel(&args);
}
/*
* The way auto_converge works, we need to do too many passes to
* run this test. Auto_converge logic is only run once every
@ -2671,7 +2670,7 @@ static void test_migrate_auto_converge(void)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, uri, NULL, "{}");
/* Wait for throttling begins */
percentage = 0;
@ -2778,7 +2777,7 @@ test_migrate_precopy_tcp_multifd_zstd_start(QTestState *from,
}
#endif /* CONFIG_ZSTD */
static void test_multifd_tcp_none(void)
static void test_multifd_tcp_uri_none(void)
{
MigrateCommon args = {
.listen_uri = "defer",
@ -2823,6 +2822,21 @@ static void test_multifd_tcp_no_zero_page(void)
test_precopy_common(&args);
}
static void test_multifd_tcp_channels_none(void)
{
MigrateCommon args = {
.listen_uri = "defer",
.start_hook = test_migrate_precopy_tcp_multifd_start,
.live = true,
.connect_channels = "[ { 'channel-type': 'main',"
" 'addr': { 'transport': 'socket',"
" 'type': 'inet',"
" 'host': '127.0.0.1',"
" 'port': '0' } } ]",
};
test_precopy_common(&args);
}
static void test_multifd_tcp_zlib(void)
{
MigrateCommon args = {
@ -3017,7 +3031,6 @@ static void test_multifd_tcp_cancel(void)
.hide_stderr = true,
};
QTestState *from, *to, *to2;
g_autofree char *uri = NULL;
if (test_migrate_start(&from, &to, "defer", &args)) {
return;
@ -3038,9 +3051,7 @@ static void test_multifd_tcp_cancel(void)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
uri = migrate_get_socket_address(to, "socket-address");
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, NULL, NULL, "{}");
migrate_wait_for_dirty_mem(from, to);
@ -3065,14 +3076,11 @@ static void test_multifd_tcp_cancel(void)
/* Start incoming migration from the 1st socket */
migrate_incoming_qmp(to2, "tcp:127.0.0.1:0", "{}");
g_free(uri);
uri = migrate_get_socket_address(to2, "socket-address");
wait_for_migration_status(from, "cancelled", NULL);
migrate_ensure_non_converge(from);
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to2, NULL, NULL, "{}");
migrate_wait_for_dirty_mem(from, to2);
@ -3405,7 +3413,7 @@ static void test_migrate_dirty_limit(void)
migrate_dirty_limit_wait_showup(from, dirtylimit_period, dirtylimit_value);
/* Start migrate */
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, args.connect_uri, NULL, "{}");
/* Wait for dirty limit throttle begin */
throttle_us_per_full = 0;
@ -3446,7 +3454,7 @@ static void test_migrate_dirty_limit(void)
}
/* Start migrate */
migrate_qmp(from, uri, "{}");
migrate_qmp(from, to, args.connect_uri, NULL, "{}");
/* Wait for dirty limit throttle begin */
throttle_us_per_full = 0;
@ -3720,6 +3728,10 @@ int main(int argc, char **argv)
test_validate_uuid_src_not_set);
migration_test_add("/migration/validate_uuid_dst_not_set",
test_validate_uuid_dst_not_set);
migration_test_add("/migration/validate_uri/channels/both_set",
test_validate_uri_channels_both_set);
migration_test_add("/migration/validate_uri/channels/none_set",
test_validate_uri_channels_none_set);
/*
* See explanation why this test is slow on function definition
*/
@ -3732,8 +3744,10 @@ int main(int argc, char **argv)
test_migrate_dirty_limit);
}
}
migration_test_add("/migration/multifd/tcp/plain/none",
test_multifd_tcp_none);
migration_test_add("/migration/multifd/tcp/uri/plain/none",
test_multifd_tcp_uri_none);
migration_test_add("/migration/multifd/tcp/channels/plain/none",
test_multifd_tcp_channels_none);
migration_test_add("/migration/multifd/tcp/plain/zero-page/legacy",
test_multifd_tcp_zero_page_legacy);
migration_test_add("/migration/multifd/tcp/plain/zero-page/none",