You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qemu/tests/qtest/migration/migration-util.c

368 lines
9.3 KiB
C

/*
* QTest migration utilities
*
* 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 "qemu/ctype.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 "qemu/memalign.h"
#include "migration/bootfile.h"
#include "migration/migration-util.h"
#if defined(__linux__)
#include <sys/ioctl.h>
#include <sys/syscall.h>
#endif
/* for uffd_version_check() */
#if defined(__linux__) && defined(__NR_userfaultfd) && defined(CONFIG_EVENTFD)
#include <sys/eventfd.h>
#include "qemu/userfaultfd.h"
#endif
/* For dirty ring test; so far only x86_64 is supported */
#if defined(__linux__) && defined(HOST_X86_64)
#include "linux/kvm.h"
#endif
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();
}
return dict;
}
static SocketAddressList *migrate_get_socket_address(QTestState *who)
{
QDict *rsp;
SocketAddressList *addrs;
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);
visit_free(iv);
qobject_unref(rsp);
return addrs;
}
char *migrate_get_connect_uri(QTestState *who)
{
SocketAddressList *addrs;
char *connect_uri;
addrs = migrate_get_socket_address(who);
connect_uri = SocketAddress_to_str(addrs->value);
qapi_free_SocketAddressList(addrs);
return connect_uri;
}
static QDict *
migrate_get_connect_qdict(QTestState *who)
{
SocketAddressList *addrs;
QDict *connect_qdict;
addrs = migrate_get_socket_address(who);
connect_qdict = SocketAddress_to_qdict(addrs->value);
qapi_free_SocketAddressList(addrs);
return connect_qdict;
}
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", addr_port);
}
}
qobject_unref(addr);
}
bool migrate_watch_for_events(QTestState *who, const char *name,
QDict *event, void *opaque)
{
QTestMigrationState *state = opaque;
if (g_str_equal(name, "STOP")) {
state->stop_seen = true;
return true;
} else if (g_str_equal(name, "SUSPEND")) {
state->suspend_seen = true;
return true;
} else if (g_str_equal(name, "RESUME")) {
state->resume_seen = true;
return true;
}
return false;
}
char *find_common_machine_version(const char *mtype, const char *var1,
const char *var2)
{
g_autofree char *type1 = qtest_resolve_machine_alias(var1, mtype);
g_autofree char *type2 = qtest_resolve_machine_alias(var2, mtype);
g_assert(type1 && type2);
if (g_str_equal(type1, type2)) {
/* either can be used */
return g_strdup(type1);
}
if (qtest_has_machine_with_env(var2, type1)) {
return g_strdup(type1);
}
if (qtest_has_machine_with_env(var1, type2)) {
return g_strdup(type2);
}
g_test_message("No common machine version for machine type '%s' between "
"binaries %s and %s", mtype, getenv(var1), getenv(var2));
g_assert_not_reached();
}
char *resolve_machine_version(const char *alias, const char *var1,
const char *var2)
{
const char *mname = g_getenv("QTEST_QEMU_MACHINE_TYPE");
g_autofree char *machine_name = NULL;
if (mname) {
const char *dash = strrchr(mname, '-');
const char *dot = strrchr(mname, '.');
machine_name = g_strdup(mname);
if (dash && dot) {
assert(qtest_has_machine(machine_name));
return g_steal_pointer(&machine_name);
}
/* else: probably an alias, let it be resolved below */
} else {
/* use the hardcoded alias */
machine_name = g_strdup(alias);
}
return find_common_machine_version(machine_name, var1, var2);
}
typedef struct {
char *name;
void (*func)(void);
} MigrationTest;
static void migration_test_destroy(gpointer data)
{
MigrationTest *test = (MigrationTest *)data;
g_free(test->name);
g_free(test);
}
static void migration_test_wrapper(const void *data)
{
MigrationTest *test = (MigrationTest *)data;
g_test_message("Running /%s%s", qtest_get_arch(), test->name);
test->func();
}
void migration_test_add(const char *path, void (*fn)(void))
{
MigrationTest *test = g_new0(MigrationTest, 1);
test->func = fn;
test->name = g_strdup(path);
qtest_add_data_func_full(path, test, migration_test_wrapper,
migration_test_destroy);
}
#ifdef O_DIRECT
/*
* Probe for O_DIRECT support on the filesystem. Since this is used
* for tests, be conservative, if anything fails, assume it's
* unsupported.
*/
bool probe_o_direct_support(const char *tmpfs)
{
g_autofree char *filename = g_strdup_printf("%s/probe-o-direct", tmpfs);
int fd, flags = O_CREAT | O_RDWR | O_TRUNC | O_DIRECT;
void *buf;
ssize_t ret, len;
uint64_t offset;
fd = open(filename, flags, 0660);
if (fd < 0) {
unlink(filename);
return false;
}
/*
* Using 1MB alignment as conservative choice to satisfy any
* plausible architecture default page size, and/or filesystem
* alignment restrictions.
*/
len = 0x100000;
offset = 0x100000;
buf = qemu_try_memalign(len, len);
g_assert(buf);
memset(buf, 0, len);
ret = pwrite(fd, buf, len, offset);
unlink(filename);
g_free(buf);
if (ret < 0) {
return false;
}
return true;
}
#endif
#if defined(__linux__) && defined(__NR_userfaultfd) && defined(CONFIG_EVENTFD)
bool ufd_version_check(bool *uffd_feature_thread_id)
{
struct uffdio_api api_struct;
uint64_t ioctl_mask;
int ufd = uffd_open(O_CLOEXEC);
if (ufd == -1) {
g_test_message("Skipping test: userfaultfd not available");
return false;
}
api_struct.api = UFFD_API;
api_struct.features = 0;
if (ioctl(ufd, UFFDIO_API, &api_struct)) {
g_test_message("Skipping test: UFFDIO_API failed");
return false;
}
if (uffd_feature_thread_id) {
*uffd_feature_thread_id = api_struct.features & UFFD_FEATURE_THREAD_ID;
}
ioctl_mask = (1ULL << _UFFDIO_REGISTER |
1ULL << _UFFDIO_UNREGISTER);
if ((api_struct.ioctls & ioctl_mask) != ioctl_mask) {
g_test_message("Skipping test: Missing userfault feature");
return false;
}
return true;
}
#else
bool ufd_version_check(bool *uffd_feature_thread_id)
{
g_test_message("Skipping test: Userfault not available (builtdtime)");
return false;
}
#endif
bool kvm_dirty_ring_supported(void)
{
#if defined(__linux__) && defined(HOST_X86_64)
int ret, kvm_fd = open("/dev/kvm", O_RDONLY);
if (kvm_fd < 0) {
return false;
}
ret = ioctl(kvm_fd, KVM_CHECK_EXTENSION, KVM_CAP_DIRTY_LOG_RING);
close(kvm_fd);
/* We test with 4096 slots */
if (ret < 4096) {
return false;
}
return true;
#else
return false;
#endif
}