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