xref: /qemu/tests/qtest/migration-helpers.c (revision 5db05230)
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