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 "qemu/ctype.h" 15 #include "qapi/qmp/qjson.h" 16 17 #include "migration-helpers.h" 18 19 /* 20 * Number of seconds we wait when looking for migration 21 * status changes, to avoid test suite hanging forever 22 * when things go wrong. Needs to be higher enough to 23 * avoid false positives on loaded hosts. 24 */ 25 #define MIGRATION_STATUS_WAIT_TIMEOUT 120 26 27 bool migrate_watch_for_events(QTestState *who, const char *name, 28 QDict *event, void *opaque) 29 { 30 QTestMigrationState *state = opaque; 31 32 if (g_str_equal(name, "STOP")) { 33 state->stop_seen = true; 34 return true; 35 } else if (g_str_equal(name, "SUSPEND")) { 36 state->suspend_seen = true; 37 return true; 38 } else if (g_str_equal(name, "RESUME")) { 39 state->resume_seen = true; 40 return true; 41 } 42 43 return false; 44 } 45 46 void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...) 47 { 48 va_list ap; 49 QDict *args, *err; 50 51 va_start(ap, fmt); 52 args = qdict_from_vjsonf_nofail(fmt, ap); 53 va_end(ap); 54 55 g_assert(!qdict_haskey(args, "uri")); 56 qdict_put_str(args, "uri", uri); 57 58 err = qtest_qmp_assert_failure_ref( 59 who, "{ 'execute': 'migrate', 'arguments': %p}", args); 60 61 g_assert(qdict_haskey(err, "desc")); 62 63 qobject_unref(err); 64 } 65 66 /* 67 * Send QMP command "migrate". 68 * Arguments are built from @fmt... (formatted like 69 * qobject_from_jsonf_nofail()) with "uri": @uri spliced in. 70 */ 71 void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...) 72 { 73 va_list ap; 74 QDict *args; 75 76 va_start(ap, fmt); 77 args = qdict_from_vjsonf_nofail(fmt, ap); 78 va_end(ap); 79 80 g_assert(!qdict_haskey(args, "uri")); 81 qdict_put_str(args, "uri", uri); 82 83 qtest_qmp_assert_success(who, 84 "{ 'execute': 'migrate', 'arguments': %p}", args); 85 } 86 87 void migrate_set_capability(QTestState *who, const char *capability, 88 bool value) 89 { 90 qtest_qmp_assert_success(who, 91 "{ 'execute': 'migrate-set-capabilities'," 92 "'arguments': { " 93 "'capabilities': [ { " 94 "'capability': %s, 'state': %i } ] } }", 95 capability, value); 96 } 97 98 void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...) 99 { 100 va_list ap; 101 QDict *args, *rsp, *data; 102 103 va_start(ap, fmt); 104 args = qdict_from_vjsonf_nofail(fmt, ap); 105 va_end(ap); 106 107 g_assert(!qdict_haskey(args, "uri")); 108 qdict_put_str(args, "uri", uri); 109 110 migrate_set_capability(to, "events", true); 111 112 rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}", 113 args); 114 g_assert(qdict_haskey(rsp, "return")); 115 qobject_unref(rsp); 116 117 rsp = qtest_qmp_eventwait_ref(to, "MIGRATION"); 118 g_assert(qdict_haskey(rsp, "data")); 119 120 data = qdict_get_qdict(rsp, "data"); 121 g_assert(qdict_haskey(data, "status")); 122 g_assert_cmpstr(qdict_get_str(data, "status"), ==, "setup"); 123 124 qobject_unref(rsp); 125 } 126 127 /* 128 * Note: caller is responsible to free the returned object via 129 * qobject_unref() after use 130 */ 131 QDict *migrate_query(QTestState *who) 132 { 133 return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }"); 134 } 135 136 QDict *migrate_query_not_failed(QTestState *who) 137 { 138 const char *status; 139 QDict *rsp = migrate_query(who); 140 status = qdict_get_str(rsp, "status"); 141 if (g_str_equal(status, "failed")) { 142 g_printerr("query-migrate shows failed migration: %s\n", 143 qdict_get_str(rsp, "error-desc")); 144 } 145 g_assert(!g_str_equal(status, "failed")); 146 return rsp; 147 } 148 149 /* 150 * Note: caller is responsible to free the returned object via 151 * g_free() after use 152 */ 153 static gchar *migrate_query_status(QTestState *who) 154 { 155 QDict *rsp_return = migrate_query(who); 156 gchar *status = g_strdup(qdict_get_str(rsp_return, "status")); 157 158 g_assert(status); 159 qobject_unref(rsp_return); 160 161 return status; 162 } 163 164 static bool check_migration_status(QTestState *who, const char *goal, 165 const char **ungoals) 166 { 167 bool ready; 168 char *current_status; 169 const char **ungoal; 170 171 current_status = migrate_query_status(who); 172 ready = strcmp(current_status, goal) == 0; 173 if (!ungoals) { 174 g_assert_cmpstr(current_status, !=, "failed"); 175 /* 176 * If looking for a state other than completed, 177 * completion of migration would cause the test to 178 * hang. 179 */ 180 if (strcmp(goal, "completed") != 0) { 181 g_assert_cmpstr(current_status, !=, "completed"); 182 } 183 } else { 184 for (ungoal = ungoals; *ungoal; ungoal++) { 185 g_assert_cmpstr(current_status, !=, *ungoal); 186 } 187 } 188 g_free(current_status); 189 return ready; 190 } 191 192 void wait_for_migration_status(QTestState *who, 193 const char *goal, const char **ungoals) 194 { 195 g_test_timer_start(); 196 while (!check_migration_status(who, goal, ungoals)) { 197 usleep(1000); 198 199 g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT); 200 } 201 } 202 203 void wait_for_migration_complete(QTestState *who) 204 { 205 wait_for_migration_status(who, "completed", NULL); 206 } 207 208 void wait_for_migration_fail(QTestState *from, bool allow_active) 209 { 210 g_test_timer_start(); 211 QDict *rsp_return; 212 char *status; 213 bool failed; 214 215 do { 216 status = migrate_query_status(from); 217 bool result = !strcmp(status, "setup") || !strcmp(status, "failed") || 218 (allow_active && !strcmp(status, "active")); 219 if (!result) { 220 fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n", 221 __func__, status, allow_active); 222 } 223 g_assert(result); 224 failed = !strcmp(status, "failed"); 225 g_free(status); 226 227 g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT); 228 } while (!failed); 229 230 /* Is the machine currently running? */ 231 rsp_return = qtest_qmp_assert_success_ref(from, 232 "{ 'execute': 'query-status' }"); 233 g_assert(qdict_haskey(rsp_return, "running")); 234 g_assert(qdict_get_bool(rsp_return, "running")); 235 qobject_unref(rsp_return); 236 } 237 238 char *find_common_machine_version(const char *mtype, const char *var1, 239 const char *var2) 240 { 241 g_autofree char *type1 = qtest_resolve_machine_alias(var1, mtype); 242 g_autofree char *type2 = qtest_resolve_machine_alias(var2, mtype); 243 244 g_assert(type1 && type2); 245 246 if (g_str_equal(type1, type2)) { 247 /* either can be used */ 248 return g_strdup(type1); 249 } 250 251 if (qtest_has_machine_with_env(var2, type1)) { 252 return g_strdup(type1); 253 } 254 255 if (qtest_has_machine_with_env(var1, type2)) { 256 return g_strdup(type2); 257 } 258 259 g_test_message("No common machine version for machine type '%s' between " 260 "binaries %s and %s", mtype, getenv(var1), getenv(var2)); 261 g_assert_not_reached(); 262 } 263 264 char *resolve_machine_version(const char *alias, const char *var1, 265 const char *var2) 266 { 267 const char *mname = g_getenv("QTEST_QEMU_MACHINE_TYPE"); 268 g_autofree char *machine_name = NULL; 269 270 if (mname) { 271 const char *dash = strrchr(mname, '-'); 272 const char *dot = strrchr(mname, '.'); 273 274 machine_name = g_strdup(mname); 275 276 if (dash && dot) { 277 assert(qtest_has_machine(machine_name)); 278 return g_steal_pointer(&machine_name); 279 } 280 /* else: probably an alias, let it be resolved below */ 281 } else { 282 /* use the hardcoded alias */ 283 machine_name = g_strdup(alias); 284 } 285 286 return find_common_machine_version(machine_name, var1, var2); 287 } 288