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 #include "qapi/qapi-visit-sockets.h" 17 #include "qapi/qobject-input-visitor.h" 18 #include "qapi/error.h" 19 20 #include "migration-helpers.h" 21 22 /* 23 * Number of seconds we wait when looking for migration 24 * status changes, to avoid test suite hanging forever 25 * when things go wrong. Needs to be higher enough to 26 * avoid false positives on loaded hosts. 27 */ 28 #define MIGRATION_STATUS_WAIT_TIMEOUT 120 29 30 static char *SocketAddress_to_str(SocketAddress *addr) 31 { 32 switch (addr->type) { 33 case SOCKET_ADDRESS_TYPE_INET: 34 return g_strdup_printf("tcp:%s:%s", 35 addr->u.inet.host, 36 addr->u.inet.port); 37 case SOCKET_ADDRESS_TYPE_UNIX: 38 return g_strdup_printf("unix:%s", 39 addr->u.q_unix.path); 40 case SOCKET_ADDRESS_TYPE_FD: 41 return g_strdup_printf("fd:%s", addr->u.fd.str); 42 case SOCKET_ADDRESS_TYPE_VSOCK: 43 return g_strdup_printf("tcp:%s:%s", 44 addr->u.vsock.cid, 45 addr->u.vsock.port); 46 default: 47 return g_strdup("unknown address type"); 48 } 49 } 50 51 static char * 52 migrate_get_socket_address(QTestState *who, const char *parameter) 53 { 54 QDict *rsp; 55 char *result; 56 SocketAddressList *addrs; 57 Visitor *iv = NULL; 58 QObject *object; 59 60 rsp = migrate_query(who); 61 object = qdict_get(rsp, parameter); 62 63 iv = qobject_input_visitor_new(object); 64 visit_type_SocketAddressList(iv, NULL, &addrs, &error_abort); 65 visit_free(iv); 66 67 /* we are only using a single address */ 68 result = SocketAddress_to_str(addrs->value); 69 70 qapi_free_SocketAddressList(addrs); 71 qobject_unref(rsp); 72 return result; 73 } 74 75 bool migrate_watch_for_events(QTestState *who, const char *name, 76 QDict *event, void *opaque) 77 { 78 QTestMigrationState *state = opaque; 79 80 if (g_str_equal(name, "STOP")) { 81 state->stop_seen = true; 82 return true; 83 } else if (g_str_equal(name, "SUSPEND")) { 84 state->suspend_seen = true; 85 return true; 86 } else if (g_str_equal(name, "RESUME")) { 87 state->resume_seen = true; 88 return true; 89 } 90 91 return false; 92 } 93 94 void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...) 95 { 96 va_list ap; 97 QDict *args, *err; 98 99 va_start(ap, fmt); 100 args = qdict_from_vjsonf_nofail(fmt, ap); 101 va_end(ap); 102 103 g_assert(!qdict_haskey(args, "uri")); 104 qdict_put_str(args, "uri", uri); 105 106 err = qtest_qmp_assert_failure_ref( 107 who, "{ 'execute': 'migrate', 'arguments': %p}", args); 108 109 g_assert(qdict_haskey(err, "desc")); 110 111 qobject_unref(err); 112 } 113 114 /* 115 * Send QMP command "migrate". 116 * Arguments are built from @fmt... (formatted like 117 * qobject_from_jsonf_nofail()) with "uri": @uri spliced in. 118 */ 119 void migrate_qmp(QTestState *who, QTestState *to, const char *uri, 120 const char *fmt, ...) 121 { 122 va_list ap; 123 QDict *args; 124 g_autofree char *connect_uri = NULL; 125 126 va_start(ap, fmt); 127 args = qdict_from_vjsonf_nofail(fmt, ap); 128 va_end(ap); 129 130 g_assert(!qdict_haskey(args, "uri")); 131 if (!uri) { 132 connect_uri = migrate_get_socket_address(to, "socket-address"); 133 } 134 qdict_put_str(args, "uri", uri ? uri : connect_uri); 135 136 qtest_qmp_assert_success(who, 137 "{ 'execute': 'migrate', 'arguments': %p}", args); 138 } 139 140 void migrate_set_capability(QTestState *who, const char *capability, 141 bool value) 142 { 143 qtest_qmp_assert_success(who, 144 "{ 'execute': 'migrate-set-capabilities'," 145 "'arguments': { " 146 "'capabilities': [ { " 147 "'capability': %s, 'state': %i } ] } }", 148 capability, value); 149 } 150 151 void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...) 152 { 153 va_list ap; 154 QDict *args, *rsp, *data; 155 156 va_start(ap, fmt); 157 args = qdict_from_vjsonf_nofail(fmt, ap); 158 va_end(ap); 159 160 g_assert(!qdict_haskey(args, "uri")); 161 qdict_put_str(args, "uri", uri); 162 163 migrate_set_capability(to, "events", true); 164 165 rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}", 166 args); 167 168 if (!qdict_haskey(rsp, "return")) { 169 g_autoptr(GString) s = qobject_to_json_pretty(QOBJECT(rsp), true); 170 g_test_message("%s", s->str); 171 } 172 173 g_assert(qdict_haskey(rsp, "return")); 174 qobject_unref(rsp); 175 176 rsp = qtest_qmp_eventwait_ref(to, "MIGRATION"); 177 g_assert(qdict_haskey(rsp, "data")); 178 179 data = qdict_get_qdict(rsp, "data"); 180 g_assert(qdict_haskey(data, "status")); 181 g_assert_cmpstr(qdict_get_str(data, "status"), ==, "setup"); 182 183 qobject_unref(rsp); 184 } 185 186 /* 187 * Note: caller is responsible to free the returned object via 188 * qobject_unref() after use 189 */ 190 QDict *migrate_query(QTestState *who) 191 { 192 return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }"); 193 } 194 195 QDict *migrate_query_not_failed(QTestState *who) 196 { 197 const char *status; 198 QDict *rsp = migrate_query(who); 199 status = qdict_get_str(rsp, "status"); 200 if (g_str_equal(status, "failed")) { 201 g_printerr("query-migrate shows failed migration: %s\n", 202 qdict_get_str(rsp, "error-desc")); 203 } 204 g_assert(!g_str_equal(status, "failed")); 205 return rsp; 206 } 207 208 /* 209 * Note: caller is responsible to free the returned object via 210 * g_free() after use 211 */ 212 static gchar *migrate_query_status(QTestState *who) 213 { 214 QDict *rsp_return = migrate_query(who); 215 gchar *status = g_strdup(qdict_get_str(rsp_return, "status")); 216 217 g_assert(status); 218 qobject_unref(rsp_return); 219 220 return status; 221 } 222 223 static bool check_migration_status(QTestState *who, const char *goal, 224 const char **ungoals) 225 { 226 bool ready; 227 char *current_status; 228 const char **ungoal; 229 230 current_status = migrate_query_status(who); 231 ready = strcmp(current_status, goal) == 0; 232 if (!ungoals) { 233 g_assert_cmpstr(current_status, !=, "failed"); 234 /* 235 * If looking for a state other than completed, 236 * completion of migration would cause the test to 237 * hang. 238 */ 239 if (strcmp(goal, "completed") != 0) { 240 g_assert_cmpstr(current_status, !=, "completed"); 241 } 242 } else { 243 for (ungoal = ungoals; *ungoal; ungoal++) { 244 g_assert_cmpstr(current_status, !=, *ungoal); 245 } 246 } 247 g_free(current_status); 248 return ready; 249 } 250 251 void wait_for_migration_status(QTestState *who, 252 const char *goal, const char **ungoals) 253 { 254 g_test_timer_start(); 255 while (!check_migration_status(who, goal, ungoals)) { 256 usleep(1000); 257 258 g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT); 259 } 260 } 261 262 void wait_for_migration_complete(QTestState *who) 263 { 264 wait_for_migration_status(who, "completed", NULL); 265 } 266 267 void wait_for_migration_fail(QTestState *from, bool allow_active) 268 { 269 g_test_timer_start(); 270 QDict *rsp_return; 271 char *status; 272 bool failed; 273 274 do { 275 status = migrate_query_status(from); 276 bool result = !strcmp(status, "setup") || !strcmp(status, "failed") || 277 (allow_active && !strcmp(status, "active")); 278 if (!result) { 279 fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n", 280 __func__, status, allow_active); 281 } 282 g_assert(result); 283 failed = !strcmp(status, "failed"); 284 g_free(status); 285 286 g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT); 287 } while (!failed); 288 289 /* Is the machine currently running? */ 290 rsp_return = qtest_qmp_assert_success_ref(from, 291 "{ 'execute': 'query-status' }"); 292 g_assert(qdict_haskey(rsp_return, "running")); 293 g_assert(qdict_get_bool(rsp_return, "running")); 294 qobject_unref(rsp_return); 295 } 296 297 char *find_common_machine_version(const char *mtype, const char *var1, 298 const char *var2) 299 { 300 g_autofree char *type1 = qtest_resolve_machine_alias(var1, mtype); 301 g_autofree char *type2 = qtest_resolve_machine_alias(var2, mtype); 302 303 g_assert(type1 && type2); 304 305 if (g_str_equal(type1, type2)) { 306 /* either can be used */ 307 return g_strdup(type1); 308 } 309 310 if (qtest_has_machine_with_env(var2, type1)) { 311 return g_strdup(type1); 312 } 313 314 if (qtest_has_machine_with_env(var1, type2)) { 315 return g_strdup(type2); 316 } 317 318 g_test_message("No common machine version for machine type '%s' between " 319 "binaries %s and %s", mtype, getenv(var1), getenv(var2)); 320 g_assert_not_reached(); 321 } 322 323 char *resolve_machine_version(const char *alias, const char *var1, 324 const char *var2) 325 { 326 const char *mname = g_getenv("QTEST_QEMU_MACHINE_TYPE"); 327 g_autofree char *machine_name = NULL; 328 329 if (mname) { 330 const char *dash = strrchr(mname, '-'); 331 const char *dot = strrchr(mname, '.'); 332 333 machine_name = g_strdup(mname); 334 335 if (dash && dot) { 336 assert(qtest_has_machine(machine_name)); 337 return g_steal_pointer(&machine_name); 338 } 339 /* else: probably an alias, let it be resolved below */ 340 } else { 341 /* use the hardcoded alias */ 342 machine_name = g_strdup(alias); 343 } 344 345 return find_common_machine_version(machine_name, var1, var2); 346 } 347 348 typedef struct { 349 char *name; 350 void (*func)(void); 351 } MigrationTest; 352 353 static void migration_test_destroy(gpointer data) 354 { 355 MigrationTest *test = (MigrationTest *)data; 356 357 g_free(test->name); 358 g_free(test); 359 } 360 361 static void migration_test_wrapper(const void *data) 362 { 363 MigrationTest *test = (MigrationTest *)data; 364 365 g_test_message("Running /%s%s", qtest_get_arch(), test->name); 366 test->func(); 367 } 368 369 void migration_test_add(const char *path, void (*fn)(void)) 370 { 371 MigrationTest *test = g_new0(MigrationTest, 1); 372 373 test->func = fn; 374 test->name = g_strdup(path); 375 376 qtest_add_data_func_full(path, test, migration_test_wrapper, 377 migration_test_destroy); 378 } 379