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