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 "qapi/qmp/qjson.h"
15 
16 #include "migration-helpers.h"
17 
18 /*
19  * Number of seconds we wait when looking for migration
20  * status changes, to avoid test suite hanging forever
21  * when things go wrong. Needs to be higher enough to
22  * avoid false positives on loaded hosts.
23  */
24 #define MIGRATION_STATUS_WAIT_TIMEOUT 120
25 
26 bool migrate_watch_for_stop(QTestState *who, const char *name,
27                             QDict *event, void *opaque)
28 {
29     bool *seen = opaque;
30 
31     if (g_str_equal(name, "STOP")) {
32         *seen = true;
33         return true;
34     }
35 
36     return false;
37 }
38 
39 bool migrate_watch_for_resume(QTestState *who, const char *name,
40                               QDict *event, void *opaque)
41 {
42     bool *seen = opaque;
43 
44     if (g_str_equal(name, "RESUME")) {
45         *seen = true;
46         return true;
47     }
48 
49     return false;
50 }
51 
52 void migrate_qmp_fail(QTestState *who, const char *uri, const char *fmt, ...)
53 {
54     va_list ap;
55     QDict *args, *err;
56 
57     va_start(ap, fmt);
58     args = qdict_from_vjsonf_nofail(fmt, ap);
59     va_end(ap);
60 
61     g_assert(!qdict_haskey(args, "uri"));
62     qdict_put_str(args, "uri", uri);
63 
64     err = qtest_qmp_assert_failure_ref(
65         who, "{ 'execute': 'migrate', 'arguments': %p}", args);
66 
67     g_assert(qdict_haskey(err, "desc"));
68 
69     qobject_unref(err);
70 }
71 
72 /*
73  * Send QMP command "migrate".
74  * Arguments are built from @fmt... (formatted like
75  * qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
76  */
77 void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
78 {
79     va_list ap;
80     QDict *args;
81 
82     va_start(ap, fmt);
83     args = qdict_from_vjsonf_nofail(fmt, ap);
84     va_end(ap);
85 
86     g_assert(!qdict_haskey(args, "uri"));
87     qdict_put_str(args, "uri", uri);
88 
89     qtest_qmp_assert_success(who,
90                              "{ 'execute': 'migrate', 'arguments': %p}", args);
91 }
92 
93 void migrate_set_capability(QTestState *who, const char *capability,
94                             bool value)
95 {
96     qtest_qmp_assert_success(who,
97                              "{ 'execute': 'migrate-set-capabilities',"
98                              "'arguments': { "
99                              "'capabilities': [ { "
100                              "'capability': %s, 'state': %i } ] } }",
101                              capability, value);
102 }
103 
104 void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...)
105 {
106     va_list ap;
107     QDict *args, *rsp, *data;
108 
109     va_start(ap, fmt);
110     args = qdict_from_vjsonf_nofail(fmt, ap);
111     va_end(ap);
112 
113     g_assert(!qdict_haskey(args, "uri"));
114     qdict_put_str(args, "uri", uri);
115 
116     migrate_set_capability(to, "events", true);
117 
118     rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
119                     args);
120     g_assert(qdict_haskey(rsp, "return"));
121     qobject_unref(rsp);
122 
123     rsp = qtest_qmp_eventwait_ref(to, "MIGRATION");
124     g_assert(qdict_haskey(rsp, "data"));
125 
126     data = qdict_get_qdict(rsp, "data");
127     g_assert(qdict_haskey(data, "status"));
128     g_assert_cmpstr(qdict_get_str(data, "status"), ==, "setup");
129 
130     qobject_unref(rsp);
131 }
132 
133 /*
134  * Note: caller is responsible to free the returned object via
135  * qobject_unref() after use
136  */
137 QDict *migrate_query(QTestState *who)
138 {
139     return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }");
140 }
141 
142 QDict *migrate_query_not_failed(QTestState *who)
143 {
144     const char *status;
145     QDict *rsp = migrate_query(who);
146     status = qdict_get_str(rsp, "status");
147     if (g_str_equal(status, "failed")) {
148         g_printerr("query-migrate shows failed migration: %s\n",
149                    qdict_get_str(rsp, "error-desc"));
150     }
151     g_assert(!g_str_equal(status, "failed"));
152     return rsp;
153 }
154 
155 /*
156  * Note: caller is responsible to free the returned object via
157  * g_free() after use
158  */
159 static gchar *migrate_query_status(QTestState *who)
160 {
161     QDict *rsp_return = migrate_query(who);
162     gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
163 
164     g_assert(status);
165     qobject_unref(rsp_return);
166 
167     return status;
168 }
169 
170 static bool check_migration_status(QTestState *who, const char *goal,
171                                    const char **ungoals)
172 {
173     bool ready;
174     char *current_status;
175     const char **ungoal;
176 
177     current_status = migrate_query_status(who);
178     ready = strcmp(current_status, goal) == 0;
179     if (!ungoals) {
180         g_assert_cmpstr(current_status, !=, "failed");
181         /*
182          * If looking for a state other than completed,
183          * completion of migration would cause the test to
184          * hang.
185          */
186         if (strcmp(goal, "completed") != 0) {
187             g_assert_cmpstr(current_status, !=, "completed");
188         }
189     } else {
190         for (ungoal = ungoals; *ungoal; ungoal++) {
191             g_assert_cmpstr(current_status, !=,  *ungoal);
192         }
193     }
194     g_free(current_status);
195     return ready;
196 }
197 
198 void wait_for_migration_status(QTestState *who,
199                                const char *goal, const char **ungoals)
200 {
201     g_test_timer_start();
202     while (!check_migration_status(who, goal, ungoals)) {
203         usleep(1000);
204 
205         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
206     }
207 }
208 
209 void wait_for_migration_complete(QTestState *who)
210 {
211     wait_for_migration_status(who, "completed", NULL);
212 }
213 
214 void wait_for_migration_fail(QTestState *from, bool allow_active)
215 {
216     g_test_timer_start();
217     QDict *rsp_return;
218     char *status;
219     bool failed;
220 
221     do {
222         status = migrate_query_status(from);
223         bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
224             (allow_active && !strcmp(status, "active"));
225         if (!result) {
226             fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
227                     __func__, status, allow_active);
228         }
229         g_assert(result);
230         failed = !strcmp(status, "failed");
231         g_free(status);
232 
233         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
234     } while (!failed);
235 
236     /* Is the machine currently running? */
237     rsp_return = qtest_qmp_assert_success_ref(from,
238                                               "{ 'execute': 'query-status' }");
239     g_assert(qdict_haskey(rsp_return, "running"));
240     g_assert(qdict_get_bool(rsp_return, "running"));
241     qobject_unref(rsp_return);
242 }
243