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
migrate_watch_for_stop(QTestState * who,const char * name,QDict * event,void * opaque)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
migrate_watch_for_resume(QTestState * who,const char * name,QDict * event,void * opaque)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
migrate_qmp_fail(QTestState * who,const char * uri,const char * fmt,...)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 */
migrate_qmp(QTestState * who,const char * uri,const char * fmt,...)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
migrate_set_capability(QTestState * who,const char * capability,bool value)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
migrate_incoming_qmp(QTestState * to,const char * uri,const char * fmt,...)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 */
migrate_query(QTestState * who)138 QDict *migrate_query(QTestState *who)
139 {
140 return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }");
141 }
142
migrate_query_not_failed(QTestState * who)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 */
migrate_query_status(QTestState * who)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
check_migration_status(QTestState * who,const char * goal,const char ** ungoals)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
wait_for_migration_status(QTestState * who,const char * goal,const char ** ungoals)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
wait_for_migration_complete(QTestState * who)210 void wait_for_migration_complete(QTestState *who)
211 {
212 wait_for_migration_status(who, "completed", NULL);
213 }
214
wait_for_migration_fail(QTestState * from,bool allow_active)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
find_common_machine_version(const char * mtype,const char * var1,const char * var2)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
resolve_machine_version(const char * alias,const char * var1,const char * var2)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