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-qmp.c

486 lines
14 KiB
C

/*
* QTest QMP helpers for migration
*
* 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 "libqtest.h"
#include "migration-qmp.h"
#include "migration-util.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qlist.h"
/*
* Number of seconds we wait when looking for migration
* status changes, to avoid test suite hanging forever
* when things go wrong. Needs to be higher enough to
* avoid false positives on loaded hosts.
*/
#define MIGRATION_STATUS_WAIT_TIMEOUT 120
/*
* Wait for a "MIGRATION" event. This is what Libvirt uses to track
* migration status changes.
*/
void migration_event_wait(QTestState *s, const char *target)
{
QDict *response, *data;
const char *status;
bool found;
do {
response = qtest_qmp_eventwait_ref(s, "MIGRATION");
data = qdict_get_qdict(response, "data");
g_assert(data);
status = qdict_get_str(data, "status");
found = (strcmp(status, target) == 0);
qobject_unref(response);
} while (!found);
}
void migrate_qmp_fail(QTestState *who, const char *uri,
const char *channels, 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"));
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);
g_assert(qdict_haskey(err, "desc"));
qobject_unref(err);
}
/*
* Send QMP command "migrate".
* Arguments are built from @fmt... (formatted like
* qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
*/
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"));
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);
}
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;
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);
/* This function relies on the event to work, make sure it's enabled */
migrate_set_capability(to, "events", true);
rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
args);
if (!qdict_haskey(rsp, "return")) {
g_autoptr(GString) s = qobject_to_json_pretty(QOBJECT(rsp), true);
g_test_message("%s", s->str);
}
g_assert(qdict_haskey(rsp, "return"));
qobject_unref(rsp);
migration_event_wait(to, "setup");
}
static bool check_migration_status(QTestState *who, const char *goal,
const char **ungoals)
{
bool ready;
char *current_status;
const char **ungoal;
current_status = migrate_query_status(who);
ready = strcmp(current_status, goal) == 0;
if (!ungoals) {
g_assert_cmpstr(current_status, !=, "failed");
/*
* If looking for a state other than completed,
* completion of migration would cause the test to
* hang.
*/
if (strcmp(goal, "completed") != 0) {
g_assert_cmpstr(current_status, !=, "completed");
}
} else {
for (ungoal = ungoals; *ungoal; ungoal++) {
g_assert_cmpstr(current_status, !=, *ungoal);
}
}
g_free(current_status);
return ready;
}
void wait_for_migration_status(QTestState *who,
const char *goal, const char **ungoals)
{
g_test_timer_start();
while (!check_migration_status(who, goal, ungoals)) {
usleep(1000);
g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
}
}
void wait_for_migration_complete(QTestState *who)
{
wait_for_migration_status(who, "completed", NULL);
}
void wait_for_migration_fail(QTestState *from, bool allow_active)
{
g_test_timer_start();
QDict *rsp_return;
char *status;
bool failed;
do {
status = migrate_query_status(from);
bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
(allow_active && !strcmp(status, "active"));
if (!result) {
fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
__func__, status, allow_active);
}
g_assert(result);
failed = !strcmp(status, "failed");
g_free(status);
g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
} while (!failed);
/* Is the machine currently running? */
rsp_return = qtest_qmp_assert_success_ref(from,
"{ 'execute': 'query-status' }");
g_assert(qdict_haskey(rsp_return, "running"));
g_assert(qdict_get_bool(rsp_return, "running"));
qobject_unref(rsp_return);
}
void wait_for_stop(QTestState *who, QTestMigrationState *state)
{
if (!state->stop_seen) {
qtest_qmp_eventwait(who, "STOP");
}
}
void wait_for_resume(QTestState *who, QTestMigrationState *state)
{
if (!state->resume_seen) {
qtest_qmp_eventwait(who, "RESUME");
}
}
void wait_for_suspend(QTestState *who, QTestMigrationState *state)
{
if (state->suspend_me && !state->suspend_seen) {
qtest_qmp_eventwait(who, "SUSPEND");
}
}
/*
* Note: caller is responsible to free the returned object via
* qobject_unref() after use
*/
QDict *migrate_query(QTestState *who)
{
return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }");
}
QDict *migrate_query_not_failed(QTestState *who)
{
const char *status;
QDict *rsp = migrate_query(who);
status = qdict_get_str(rsp, "status");
if (g_str_equal(status, "failed")) {
g_printerr("query-migrate shows failed migration: %s\n",
qdict_get_str(rsp, "error-desc"));
}
g_assert(!g_str_equal(status, "failed"));
return rsp;
}
/*
* Note: caller is responsible to free the returned object via
* g_free() after use
*/
gchar *migrate_query_status(QTestState *who)
{
QDict *rsp_return = migrate_query(who);
gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
g_assert(status);
qobject_unref(rsp_return);
return status;
}
int64_t read_ram_property_int(QTestState *who, const char *property)
{
QDict *rsp_return, *rsp_ram;
int64_t result;
rsp_return = migrate_query_not_failed(who);
if (!qdict_haskey(rsp_return, "ram")) {
/* Still in setup */
result = 0;
} else {
rsp_ram = qdict_get_qdict(rsp_return, "ram");
result = qdict_get_try_int(rsp_ram, property, 0);
}
qobject_unref(rsp_return);
return result;
}
int64_t read_migrate_property_int(QTestState *who, const char *property)
{
QDict *rsp_return;
int64_t result;
rsp_return = migrate_query_not_failed(who);
result = qdict_get_try_int(rsp_return, property, 0);
qobject_unref(rsp_return);
return result;
}
uint64_t get_migration_pass(QTestState *who)
{
return read_ram_property_int(who, "dirty-sync-count");
}
void read_blocktime(QTestState *who)
{
QDict *rsp_return;
rsp_return = migrate_query_not_failed(who);
g_assert(qdict_haskey(rsp_return, "postcopy-blocktime"));
qobject_unref(rsp_return);
}
/*
* Wait for two changes in the migration pass count, but bail if we stop.
*/
void wait_for_migration_pass(QTestState *who, QTestMigrationState *src_state)
{
uint64_t pass, prev_pass = 0, changes = 0;
while (changes < 2 && !src_state->stop_seen && !src_state->suspend_seen) {
usleep(1000);
pass = get_migration_pass(who);
changes += (pass != prev_pass);
prev_pass = pass;
}
}
static long long migrate_get_parameter_int(QTestState *who,
const char *parameter)
{
QDict *rsp;
long long result;
rsp = qtest_qmp_assert_success_ref(
who, "{ 'execute': 'query-migrate-parameters' }");
result = qdict_get_int(rsp, parameter);
qobject_unref(rsp);
return result;
}
static void migrate_check_parameter_int(QTestState *who, const char *parameter,
long long value)
{
long long result;
result = migrate_get_parameter_int(who, parameter);
g_assert_cmpint(result, ==, value);
}
void migrate_set_parameter_int(QTestState *who, const char *parameter,
long long value)
{
qtest_qmp_assert_success(who,
"{ 'execute': 'migrate-set-parameters',"
"'arguments': { %s: %lld } }",
parameter, value);
migrate_check_parameter_int(who, parameter, value);
}
static char *migrate_get_parameter_str(QTestState *who, const char *parameter)
{
QDict *rsp;
char *result;
rsp = qtest_qmp_assert_success_ref(
who, "{ 'execute': 'query-migrate-parameters' }");
result = g_strdup(qdict_get_str(rsp, parameter));
qobject_unref(rsp);
return result;
}
static void migrate_check_parameter_str(QTestState *who, const char *parameter,
const char *value)
{
g_autofree char *result = migrate_get_parameter_str(who, parameter);
g_assert_cmpstr(result, ==, value);
}
void migrate_set_parameter_str(QTestState *who, const char *parameter,
const char *value)
{
qtest_qmp_assert_success(who,
"{ 'execute': 'migrate-set-parameters',"
"'arguments': { %s: %s } }",
parameter, value);
migrate_check_parameter_str(who, parameter, value);
}
static long long migrate_get_parameter_bool(QTestState *who,
const char *parameter)
{
QDict *rsp;
int result;
rsp = qtest_qmp_assert_success_ref(
who, "{ 'execute': 'query-migrate-parameters' }");
result = qdict_get_bool(rsp, parameter);
qobject_unref(rsp);
return !!result;
}
static void migrate_check_parameter_bool(QTestState *who, const char *parameter,
int value)
{
int result;
result = migrate_get_parameter_bool(who, parameter);
g_assert_cmpint(result, ==, value);
}
void migrate_set_parameter_bool(QTestState *who, const char *parameter,
int value)
{
qtest_qmp_assert_success(who,
"{ 'execute': 'migrate-set-parameters',"
"'arguments': { %s: %i } }",
parameter, value);
migrate_check_parameter_bool(who, parameter, value);
}
void migrate_ensure_non_converge(QTestState *who)
{
/* Can't converge with 1ms downtime + 3 mbs bandwidth limit */
migrate_set_parameter_int(who, "max-bandwidth", 3 * 1000 * 1000);
migrate_set_parameter_int(who, "downtime-limit", 1);
}
void migrate_ensure_converge(QTestState *who)
{
/* Should converge with 30s downtime + 1 gbs bandwidth limit */
migrate_set_parameter_int(who, "max-bandwidth", 1 * 1000 * 1000 * 1000);
migrate_set_parameter_int(who, "downtime-limit", 30 * 1000);
}
void migrate_pause(QTestState *who)
{
qtest_qmp_assert_success(who, "{ 'execute': 'migrate-pause' }");
}
void migrate_continue(QTestState *who, const char *state)
{
qtest_qmp_assert_success(who,
"{ 'execute': 'migrate-continue',"
" 'arguments': { 'state': %s } }",
state);
}
void migrate_recover(QTestState *who, const char *uri)
{
qtest_qmp_assert_success(who,
"{ 'exec-oob': 'migrate-recover', "
" 'id': 'recover-cmd', "
" 'arguments': { 'uri': %s } }",
uri);
}
void migrate_cancel(QTestState *who)
{
qtest_qmp_assert_success(who, "{ 'execute': 'migrate_cancel' }");
}
void migrate_postcopy_start(QTestState *from, QTestState *to,
QTestMigrationState *src_state)
{
qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }");
wait_for_stop(from, src_state);
qtest_qmp_eventwait(to, "RESUME");
}