|  | /* | 
|  | * 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"); | 
|  | } |