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