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