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 got_stop;
27 
28 static void check_stop_event(QTestState *who)
29 {
30     QDict *event = qtest_qmp_event_ref(who, "STOP");
31     if (event) {
32         got_stop = true;
33         qobject_unref(event);
34     }
35 }
36 
37 /*
38  * Events can get in the way of responses we are actually waiting for.
39  */
40 QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...)
41 {
42     va_list ap;
43     QDict *resp, *ret;
44 
45     va_start(ap, command);
46     qtest_qmp_vsend_fds(who, &fd, 1, command, ap);
47     va_end(ap);
48 
49     resp = qtest_qmp_receive(who);
50     check_stop_event(who);
51 
52     g_assert(!qdict_haskey(resp, "error"));
53     g_assert(qdict_haskey(resp, "return"));
54 
55     ret = qdict_get_qdict(resp, "return");
56     qobject_ref(ret);
57     qobject_unref(resp);
58 
59     return ret;
60 }
61 
62 /*
63  * Events can get in the way of responses we are actually waiting for.
64  */
65 QDict *wait_command(QTestState *who, const char *command, ...)
66 {
67     va_list ap;
68     QDict *resp, *ret;
69 
70     va_start(ap, command);
71     resp = qtest_vqmp(who, command, ap);
72     va_end(ap);
73 
74     check_stop_event(who);
75 
76     g_assert(!qdict_haskey(resp, "error"));
77     g_assert(qdict_haskey(resp, "return"));
78 
79     ret = qdict_get_qdict(resp, "return");
80     qobject_ref(ret);
81     qobject_unref(resp);
82 
83     return ret;
84 }
85 
86 /*
87  * Send QMP command "migrate".
88  * Arguments are built from @fmt... (formatted like
89  * qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
90  */
91 void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
92 {
93     va_list ap;
94     QDict *args, *rsp;
95 
96     va_start(ap, fmt);
97     args = qdict_from_vjsonf_nofail(fmt, ap);
98     va_end(ap);
99 
100     g_assert(!qdict_haskey(args, "uri"));
101     qdict_put_str(args, "uri", uri);
102 
103     rsp = qtest_qmp(who, "{ 'execute': 'migrate', 'arguments': %p}", args);
104 
105     g_assert(qdict_haskey(rsp, "return"));
106     qobject_unref(rsp);
107 }
108 
109 /*
110  * Note: caller is responsible to free the returned object via
111  * qobject_unref() after use
112  */
113 QDict *migrate_query(QTestState *who)
114 {
115     return wait_command(who, "{ 'execute': 'query-migrate' }");
116 }
117 
118 QDict *migrate_query_not_failed(QTestState *who)
119 {
120     const char *status;
121     QDict *rsp = migrate_query(who);
122     status = qdict_get_str(rsp, "status");
123     if (g_str_equal(status, "failed")) {
124         g_printerr("query-migrate shows failed migration: %s\n",
125                    qdict_get_str(rsp, "error-desc"));
126     }
127     g_assert(!g_str_equal(status, "failed"));
128     return rsp;
129 }
130 
131 /*
132  * Note: caller is responsible to free the returned object via
133  * g_free() after use
134  */
135 static gchar *migrate_query_status(QTestState *who)
136 {
137     QDict *rsp_return = migrate_query(who);
138     gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
139 
140     g_assert(status);
141     qobject_unref(rsp_return);
142 
143     return status;
144 }
145 
146 static bool check_migration_status(QTestState *who, const char *goal,
147                                    const char **ungoals)
148 {
149     bool ready;
150     char *current_status;
151     const char **ungoal;
152 
153     current_status = migrate_query_status(who);
154     ready = strcmp(current_status, goal) == 0;
155     if (!ungoals) {
156         g_assert_cmpstr(current_status, !=, "failed");
157         /*
158          * If looking for a state other than completed,
159          * completion of migration would cause the test to
160          * hang.
161          */
162         if (strcmp(goal, "completed") != 0) {
163             g_assert_cmpstr(current_status, !=, "completed");
164         }
165     } else {
166         for (ungoal = ungoals; *ungoal; ungoal++) {
167             g_assert_cmpstr(current_status, !=,  *ungoal);
168         }
169     }
170     g_free(current_status);
171     return ready;
172 }
173 
174 void wait_for_migration_status(QTestState *who,
175                                const char *goal, const char **ungoals)
176 {
177     g_test_timer_start();
178     while (!check_migration_status(who, goal, ungoals)) {
179         usleep(1000);
180 
181         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
182     }
183 }
184 
185 void wait_for_migration_complete(QTestState *who)
186 {
187     wait_for_migration_status(who, "completed", NULL);
188 }
189 
190 void wait_for_migration_fail(QTestState *from, bool allow_active)
191 {
192     g_test_timer_start();
193     QDict *rsp_return;
194     char *status;
195     bool failed;
196 
197     do {
198         status = migrate_query_status(from);
199         bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
200             (allow_active && !strcmp(status, "active"));
201         if (!result) {
202             fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
203                     __func__, status, allow_active);
204         }
205         g_assert(result);
206         failed = !strcmp(status, "failed");
207         g_free(status);
208 
209         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
210     } while (!failed);
211 
212     /* Is the machine currently running? */
213     rsp_return = wait_command(from, "{ 'execute': 'query-status' }");
214     g_assert(qdict_haskey(rsp_return, "running"));
215     g_assert(qdict_get_bool(rsp_return, "running"));
216     qobject_unref(rsp_return);
217 }
218