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 /*
53  * Send QMP command "migrate".
54  * Arguments are built from @fmt... (formatted like
55  * qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
56  */
57 void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
58 {
59     va_list ap;
60     QDict *args;
61 
62     va_start(ap, fmt);
63     args = qdict_from_vjsonf_nofail(fmt, ap);
64     va_end(ap);
65 
66     g_assert(!qdict_haskey(args, "uri"));
67     qdict_put_str(args, "uri", uri);
68 
69     qtest_qmp_assert_success(who,
70                              "{ 'execute': 'migrate', 'arguments': %p}", args);
71 }
72 
73 /*
74  * Note: caller is responsible to free the returned object via
75  * qobject_unref() after use
76  */
77 QDict *migrate_query(QTestState *who)
78 {
79     return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }");
80 }
81 
82 QDict *migrate_query_not_failed(QTestState *who)
83 {
84     const char *status;
85     QDict *rsp = migrate_query(who);
86     status = qdict_get_str(rsp, "status");
87     if (g_str_equal(status, "failed")) {
88         g_printerr("query-migrate shows failed migration: %s\n",
89                    qdict_get_str(rsp, "error-desc"));
90     }
91     g_assert(!g_str_equal(status, "failed"));
92     return rsp;
93 }
94 
95 /*
96  * Note: caller is responsible to free the returned object via
97  * g_free() after use
98  */
99 static gchar *migrate_query_status(QTestState *who)
100 {
101     QDict *rsp_return = migrate_query(who);
102     gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
103 
104     g_assert(status);
105     qobject_unref(rsp_return);
106 
107     return status;
108 }
109 
110 static bool check_migration_status(QTestState *who, const char *goal,
111                                    const char **ungoals)
112 {
113     bool ready;
114     char *current_status;
115     const char **ungoal;
116 
117     current_status = migrate_query_status(who);
118     ready = strcmp(current_status, goal) == 0;
119     if (!ungoals) {
120         g_assert_cmpstr(current_status, !=, "failed");
121         /*
122          * If looking for a state other than completed,
123          * completion of migration would cause the test to
124          * hang.
125          */
126         if (strcmp(goal, "completed") != 0) {
127             g_assert_cmpstr(current_status, !=, "completed");
128         }
129     } else {
130         for (ungoal = ungoals; *ungoal; ungoal++) {
131             g_assert_cmpstr(current_status, !=,  *ungoal);
132         }
133     }
134     g_free(current_status);
135     return ready;
136 }
137 
138 void wait_for_migration_status(QTestState *who,
139                                const char *goal, const char **ungoals)
140 {
141     g_test_timer_start();
142     while (!check_migration_status(who, goal, ungoals)) {
143         usleep(1000);
144 
145         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
146     }
147 }
148 
149 void wait_for_migration_complete(QTestState *who)
150 {
151     wait_for_migration_status(who, "completed", NULL);
152 }
153 
154 void wait_for_migration_fail(QTestState *from, bool allow_active)
155 {
156     g_test_timer_start();
157     QDict *rsp_return;
158     char *status;
159     bool failed;
160 
161     do {
162         status = migrate_query_status(from);
163         bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
164             (allow_active && !strcmp(status, "active"));
165         if (!result) {
166             fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
167                     __func__, status, allow_active);
168         }
169         g_assert(result);
170         failed = !strcmp(status, "failed");
171         g_free(status);
172 
173         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
174     } while (!failed);
175 
176     /* Is the machine currently running? */
177     rsp_return = qtest_qmp_assert_success_ref(from,
178                                               "{ '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