Marc-André Lureau | d77799c | 2019-12-16 14:59:44 +0400 | [diff] [blame] | 1 | /* |
| 2 | * QTest migration helpers |
| 3 | * |
| 4 | * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates |
| 5 | * based on the vhost-user-test.c that is: |
| 6 | * Copyright (c) 2014 Virtual Open Systems Sarl. |
| 7 | * |
| 8 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 9 | * See the COPYING file in the top-level directory. |
| 10 | * |
| 11 | */ |
| 12 | |
| 13 | #include "qemu/osdep.h" |
| 14 | #include "qapi/qmp/qjson.h" |
| 15 | |
| 16 | #include "migration-helpers.h" |
| 17 | |
| 18 | bool got_stop; |
| 19 | |
| 20 | static void stop_cb(void *opaque, const char *name, QDict *data) |
| 21 | { |
| 22 | if (!strcmp(name, "STOP")) { |
| 23 | got_stop = true; |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | /* |
| 28 | * Events can get in the way of responses we are actually waiting for. |
| 29 | */ |
| 30 | QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...) |
| 31 | { |
| 32 | va_list ap; |
| 33 | |
| 34 | va_start(ap, command); |
| 35 | qtest_qmp_vsend_fds(who, &fd, 1, command, ap); |
| 36 | va_end(ap); |
| 37 | |
| 38 | return qtest_qmp_receive_success(who, stop_cb, NULL); |
| 39 | } |
| 40 | |
| 41 | /* |
| 42 | * Events can get in the way of responses we are actually waiting for. |
| 43 | */ |
| 44 | QDict *wait_command(QTestState *who, const char *command, ...) |
| 45 | { |
| 46 | va_list ap; |
| 47 | |
| 48 | va_start(ap, command); |
| 49 | qtest_qmp_vsend(who, command, ap); |
| 50 | va_end(ap); |
| 51 | |
| 52 | return qtest_qmp_receive_success(who, stop_cb, NULL); |
| 53 | } |
| 54 | |
| 55 | /* |
| 56 | * Send QMP command "migrate". |
| 57 | * Arguments are built from @fmt... (formatted like |
| 58 | * qobject_from_jsonf_nofail()) with "uri": @uri spliced in. |
| 59 | */ |
| 60 | void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...) |
| 61 | { |
| 62 | va_list ap; |
| 63 | QDict *args, *rsp; |
| 64 | |
| 65 | va_start(ap, fmt); |
| 66 | args = qdict_from_vjsonf_nofail(fmt, ap); |
| 67 | va_end(ap); |
| 68 | |
| 69 | g_assert(!qdict_haskey(args, "uri")); |
| 70 | qdict_put_str(args, "uri", uri); |
| 71 | |
| 72 | rsp = qtest_qmp(who, "{ 'execute': 'migrate', 'arguments': %p}", args); |
| 73 | |
| 74 | g_assert(qdict_haskey(rsp, "return")); |
| 75 | qobject_unref(rsp); |
| 76 | } |
| 77 | |
| 78 | /* |
| 79 | * Note: caller is responsible to free the returned object via |
| 80 | * qobject_unref() after use |
| 81 | */ |
| 82 | QDict *migrate_query(QTestState *who) |
| 83 | { |
| 84 | return wait_command(who, "{ 'execute': 'query-migrate' }"); |
| 85 | } |
| 86 | |
| 87 | /* |
| 88 | * Note: caller is responsible to free the returned object via |
| 89 | * g_free() after use |
| 90 | */ |
| 91 | static gchar *migrate_query_status(QTestState *who) |
| 92 | { |
| 93 | QDict *rsp_return = migrate_query(who); |
| 94 | gchar *status = g_strdup(qdict_get_str(rsp_return, "status")); |
| 95 | |
| 96 | g_assert(status); |
| 97 | qobject_unref(rsp_return); |
| 98 | |
| 99 | return status; |
| 100 | } |
| 101 | |
| 102 | static bool check_migration_status(QTestState *who, const char *goal, |
| 103 | const char **ungoals) |
| 104 | { |
| 105 | bool ready; |
| 106 | char *current_status; |
| 107 | const char **ungoal; |
| 108 | |
| 109 | current_status = migrate_query_status(who); |
| 110 | ready = strcmp(current_status, goal) == 0; |
| 111 | if (!ungoals) { |
| 112 | g_assert_cmpstr(current_status, !=, "failed"); |
| 113 | /* |
| 114 | * If looking for a state other than completed, |
| 115 | * completion of migration would cause the test to |
| 116 | * hang. |
| 117 | */ |
| 118 | if (strcmp(goal, "completed") != 0) { |
| 119 | g_assert_cmpstr(current_status, !=, "completed"); |
| 120 | } |
| 121 | } else { |
| 122 | for (ungoal = ungoals; *ungoal; ungoal++) { |
| 123 | g_assert_cmpstr(current_status, !=, *ungoal); |
| 124 | } |
| 125 | } |
| 126 | g_free(current_status); |
| 127 | return ready; |
| 128 | } |
| 129 | |
| 130 | void wait_for_migration_status(QTestState *who, |
| 131 | const char *goal, const char **ungoals) |
| 132 | { |
| 133 | while (!check_migration_status(who, goal, ungoals)) { |
| 134 | usleep(1000); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | void wait_for_migration_complete(QTestState *who) |
| 139 | { |
| 140 | wait_for_migration_status(who, "completed", NULL); |
| 141 | } |
| 142 | |
| 143 | void wait_for_migration_fail(QTestState *from, bool allow_active) |
| 144 | { |
| 145 | QDict *rsp_return; |
| 146 | char *status; |
| 147 | bool failed; |
| 148 | |
| 149 | do { |
| 150 | status = migrate_query_status(from); |
| 151 | bool result = !strcmp(status, "setup") || !strcmp(status, "failed") || |
| 152 | (allow_active && !strcmp(status, "active")); |
| 153 | if (!result) { |
| 154 | fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n", |
| 155 | __func__, status, allow_active); |
| 156 | } |
| 157 | g_assert(result); |
| 158 | failed = !strcmp(status, "failed"); |
| 159 | g_free(status); |
| 160 | } while (!failed); |
| 161 | |
| 162 | /* Is the machine currently running? */ |
| 163 | rsp_return = wait_command(from, "{ 'execute': 'query-status' }"); |
| 164 | g_assert(qdict_haskey(rsp_return, "running")); |
| 165 | g_assert(qdict_get_bool(rsp_return, "running")); |
| 166 | qobject_unref(rsp_return); |
| 167 | } |