/* * 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/qapi-types-migration.h" #include "qapi/qapi-visit-migration.h" #include "qobject/qdict.h" #include "qobject/qjson.h" #include "qobject/qlist.h" #include "qapi/qobject-input-visitor.h" #include "qapi/qobject-output-visitor.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); } /* * Convert a string representing a single channel to an object. * @str may be in JSON or dotted keys format. */ QObject *migrate_str_to_channel(const char *str) { Visitor *v; MigrationChannel *channel; QObject *obj; /* Create the channel */ v = qobject_input_visitor_new_str(str, "channel-type", &error_abort); visit_type_MigrationChannel(v, NULL, &channel, &error_abort); visit_free(v); /* Create the object */ v = qobject_output_visitor_new(&obj); visit_type_MigrationChannel(v, NULL, &channel, &error_abort); visit_complete(v, &obj); visit_free(v); qapi_free_MigrationChannel(channel); return obj; } void migrate_qmp_fail(QTestState *who, const char *uri, QObject *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) { qdict_put_obj(args, "channels", channels); } 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, QObject *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) { QList *channel_list = qobject_to(QList, channels); migrate_set_ports(to, channel_list); qdict_put_obj(args, "channels", channels); } 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, QObject *channels, 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")); if (uri) { qdict_put_str(args, "uri", uri); } g_assert(!qdict_haskey(args, "channels")); if (channels) { qdict_put_obj(args, "channels", channels); } /* 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"); }