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