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