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