]> Git Repo - qemu.git/blob - tests/qmp-test.c
Merge remote-tracking branch 'remotes/bonzini/tags/for-upstream' into staging
[qemu.git] / tests / qmp-test.c
1 /*
2  * QMP protocol test cases
3  *
4  * Copyright (c) 2017 Red Hat Inc.
5  *
6  * Authors:
7  *  Markus Armbruster <[email protected]>,
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2 or later.
10  * See the COPYING file in the top-level directory.
11  */
12
13 #include "qemu/osdep.h"
14 #include "libqtest.h"
15 #include "qapi/error.h"
16 #include "qapi/qapi-visit-introspect.h"
17 #include "qapi/qapi-visit-misc.h"
18 #include "qapi/qmp/qdict.h"
19 #include "qapi/qmp/qlist.h"
20 #include "qapi/qobject-input-visitor.h"
21 #include "qapi/util.h"
22 #include "qapi/visitor.h"
23 #include "qapi/qmp/qstring.h"
24
25 const char common_args[] = "-nodefaults -machine none";
26
27 static const char *get_error_class(QDict *resp)
28 {
29     QDict *error = qdict_get_qdict(resp, "error");
30     const char *desc = qdict_get_try_str(error, "desc");
31
32     g_assert(desc);
33     return error ? qdict_get_try_str(error, "class") : NULL;
34 }
35
36 static void test_version(QObject *version)
37 {
38     Visitor *v;
39     VersionInfo *vinfo;
40
41     g_assert(version);
42     v = qobject_input_visitor_new(version);
43     visit_type_VersionInfo(v, "version", &vinfo, &error_abort);
44     qapi_free_VersionInfo(vinfo);
45     visit_free(v);
46 }
47
48 static void test_malformed(QTestState *qts)
49 {
50     QDict *resp;
51
52     /* Not even a dictionary */
53     resp = qtest_qmp(qts, "null");
54     g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
55     qobject_unref(resp);
56
57     /* No "execute" key */
58     resp = qtest_qmp(qts, "{}");
59     g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
60     qobject_unref(resp);
61
62     /* "execute" isn't a string */
63     resp = qtest_qmp(qts, "{ 'execute': true }");
64     g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
65     qobject_unref(resp);
66
67     /* "arguments" isn't a dictionary */
68     resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'arguments': [] }");
69     g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
70     qobject_unref(resp);
71
72     /* extra key */
73     resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'extra': true }");
74     g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
75     qobject_unref(resp);
76 }
77
78 static void test_qmp_protocol(void)
79 {
80     QDict *resp, *q, *ret;
81     QList *capabilities;
82     QTestState *qts;
83
84     qts = qtest_init_without_qmp_handshake(false, common_args);
85
86     /* Test greeting */
87     resp = qtest_qmp_receive(qts);
88     q = qdict_get_qdict(resp, "QMP");
89     g_assert(q);
90     test_version(qdict_get(q, "version"));
91     capabilities = qdict_get_qlist(q, "capabilities");
92     g_assert(capabilities && qlist_empty(capabilities));
93     qobject_unref(resp);
94
95     /* Test valid command before handshake */
96     resp = qtest_qmp(qts, "{ 'execute': 'query-version' }");
97     g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound");
98     qobject_unref(resp);
99
100     /* Test malformed commands before handshake */
101     test_malformed(qts);
102
103     /* Test handshake */
104     resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }");
105     ret = qdict_get_qdict(resp, "return");
106     g_assert(ret && !qdict_size(ret));
107     qobject_unref(resp);
108
109     /* Test repeated handshake */
110     resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }");
111     g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound");
112     qobject_unref(resp);
113
114     /* Test valid command */
115     resp = qtest_qmp(qts, "{ 'execute': 'query-version' }");
116     test_version(qdict_get(resp, "return"));
117     qobject_unref(resp);
118
119     /* Test malformed commands */
120     test_malformed(qts);
121
122     /* Test 'id' */
123     resp = qtest_qmp(qts, "{ 'execute': 'query-name', 'id': 'cookie#1' }");
124     ret = qdict_get_qdict(resp, "return");
125     g_assert(ret);
126     g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, "cookie#1");
127     qobject_unref(resp);
128
129     /* Test command failure with 'id' */
130     resp = qtest_qmp(qts, "{ 'execute': 'human-monitor-command', 'id': 2 }");
131     g_assert_cmpstr(get_error_class(resp), ==, "GenericError");
132     g_assert_cmpint(qdict_get_int(resp, "id"), ==, 2);
133     qobject_unref(resp);
134
135     qtest_quit(qts);
136 }
137
138 /* Tests for Out-Of-Band support. */
139 static void test_qmp_oob(void)
140 {
141     QTestState *qts;
142     QDict *resp, *q;
143     int acks = 0;
144     const QListEntry *entry;
145     QList *capabilities;
146     QString *qstr;
147     const char *cmd_id;
148
149     qts = qtest_init_without_qmp_handshake(true, common_args);
150
151     /* Check the greeting message. */
152     resp = qtest_qmp_receive(qts);
153     q = qdict_get_qdict(resp, "QMP");
154     g_assert(q);
155     capabilities = qdict_get_qlist(q, "capabilities");
156     g_assert(capabilities && !qlist_empty(capabilities));
157     entry = qlist_first(capabilities);
158     g_assert(entry);
159     qstr = qobject_to(QString, entry->value);
160     g_assert(qstr);
161     g_assert_cmpstr(qstring_get_str(qstr), ==, "oob");
162     qobject_unref(resp);
163
164     /* Try a fake capability, it should fail. */
165     resp = qtest_qmp(qts,
166                      "{ 'execute': 'qmp_capabilities', "
167                      "  'arguments': { 'enable': [ 'cap-does-not-exist' ] } }");
168     g_assert(qdict_haskey(resp, "error"));
169     qobject_unref(resp);
170
171     /* Now, enable OOB in current QMP session, it should succeed. */
172     resp = qtest_qmp(qts,
173                      "{ 'execute': 'qmp_capabilities', "
174                      "  'arguments': { 'enable': [ 'oob' ] } }");
175     g_assert(qdict_haskey(resp, "return"));
176     qobject_unref(resp);
177
178     /*
179      * Try any command that does not support OOB but with OOB flag. We
180      * should get failure.
181      */
182     resp = qtest_qmp(qts,
183                      "{ 'execute': 'query-cpus',"
184                      "  'control': { 'run-oob': true } }");
185     g_assert(qdict_haskey(resp, "error"));
186     qobject_unref(resp);
187
188     /*
189      * First send the "x-oob-test" command with lock=true and
190      * oob=false, it should hang the dispatcher and main thread;
191      * later, we send another lock=false with oob=true to continue
192      * that thread processing.  Finally we should receive replies from
193      * both commands.
194      */
195     qtest_async_qmp(qts,
196                     "{ 'execute': 'x-oob-test',"
197                     "  'arguments': { 'lock': true }, "
198                     "  'id': 'lock-cmd'}");
199     qtest_async_qmp(qts,
200                     "{ 'execute': 'x-oob-test', "
201                     "  'arguments': { 'lock': false }, "
202                     "  'control': { 'run-oob': true }, "
203                     "  'id': 'unlock-cmd' }");
204
205     /* Ignore all events.  Wait for 2 acks */
206     while (acks < 2) {
207         resp = qtest_qmp_receive(qts);
208         cmd_id = qdict_get_str(resp, "id");
209         if (!g_strcmp0(cmd_id, "lock-cmd") ||
210             !g_strcmp0(cmd_id, "unlock-cmd")) {
211             acks++;
212         }
213         qobject_unref(resp);
214     }
215
216     qtest_quit(qts);
217 }
218
219 static int query_error_class(const char *cmd)
220 {
221     static struct {
222         const char *cmd;
223         int err_class;
224     } fails[] = {
225         /* Success depends on build configuration: */
226 #ifndef CONFIG_SPICE
227         { "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
228 #endif
229 #ifndef CONFIG_VNC
230         { "query-vnc", ERROR_CLASS_GENERIC_ERROR },
231         { "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
232 #endif
233 #ifndef CONFIG_REPLICATION
234         { "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
235 #endif
236         /* Likewise, and require special QEMU command-line arguments: */
237         { "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
238         { "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
239         { "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
240         { "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
241         { NULL, -1 }
242     };
243     int i;
244
245     for (i = 0; fails[i].cmd; i++) {
246         if (!strcmp(cmd, fails[i].cmd)) {
247             return fails[i].err_class;
248         }
249     }
250     return -1;
251 }
252
253 static void test_query(const void *data)
254 {
255     const char *cmd = data;
256     int expected_error_class = query_error_class(cmd);
257     QDict *resp, *error;
258     const char *error_class;
259
260     qtest_start(common_args);
261
262     resp = qmp("{ 'execute': %s }", cmd);
263     error = qdict_get_qdict(resp, "error");
264     error_class = error ? qdict_get_str(error, "class") : NULL;
265
266     if (expected_error_class < 0) {
267         g_assert(qdict_haskey(resp, "return"));
268     } else {
269         g_assert(error);
270         g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
271                                         -1, &error_abort),
272                         ==, expected_error_class);
273     }
274     qobject_unref(resp);
275
276     qtest_end();
277 }
278
279 static bool query_is_blacklisted(const char *cmd)
280 {
281     const char *blacklist[] = {
282         /* Not actually queries: */
283         "add-fd",
284         /* Success depends on target arch: */
285         "query-cpu-definitions",  /* arm, i386, ppc, s390x */
286         "query-gic-capabilities", /* arm */
287         /* Success depends on target-specific build configuration: */
288         "query-pci",              /* CONFIG_PCI */
289         /* Success depends on launching SEV guest */
290         "query-sev-launch-measure",
291         /* Success depends on Host or Hypervisor SEV support */
292         "query-sev",
293         "query-sev-capabilities",
294         NULL
295     };
296     int i;
297
298     for (i = 0; blacklist[i]; i++) {
299         if (!strcmp(cmd, blacklist[i])) {
300             return true;
301         }
302     }
303     return false;
304 }
305
306 typedef struct {
307     SchemaInfoList *list;
308     GHashTable *hash;
309 } QmpSchema;
310
311 static void qmp_schema_init(QmpSchema *schema)
312 {
313     QDict *resp;
314     Visitor *qiv;
315     SchemaInfoList *tail;
316
317     qtest_start(common_args);
318     resp = qmp("{ 'execute': 'query-qmp-schema' }");
319
320     qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
321     visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
322     visit_free(qiv);
323
324     qobject_unref(resp);
325     qtest_end();
326
327     schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
328
329     /* Build @schema: hash table mapping entity name to SchemaInfo */
330     for (tail = schema->list; tail; tail = tail->next) {
331         g_hash_table_insert(schema->hash, tail->value->name, tail->value);
332     }
333 }
334
335 static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
336 {
337     return g_hash_table_lookup(schema->hash, name);
338 }
339
340 static void qmp_schema_cleanup(QmpSchema *schema)
341 {
342     qapi_free_SchemaInfoList(schema->list);
343     g_hash_table_destroy(schema->hash);
344 }
345
346 static bool object_type_has_mandatory_members(SchemaInfo *type)
347 {
348     SchemaInfoObjectMemberList *tail;
349
350     g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
351
352     for (tail = type->u.object.members; tail; tail = tail->next) {
353         if (!tail->value->has_q_default) {
354             return true;
355         }
356     }
357
358     return false;
359 }
360
361 static void add_query_tests(QmpSchema *schema)
362 {
363     SchemaInfoList *tail;
364     SchemaInfo *si, *arg_type, *ret_type;
365     char *test_name;
366
367     /* Test the query-like commands */
368     for (tail = schema->list; tail; tail = tail->next) {
369         si = tail->value;
370         if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
371             continue;
372         }
373
374         if (query_is_blacklisted(si->name)) {
375             continue;
376         }
377
378         arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
379         if (object_type_has_mandatory_members(arg_type)) {
380             continue;
381         }
382
383         ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
384         if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
385             && !ret_type->u.object.members) {
386             continue;
387         }
388
389         test_name = g_strdup_printf("qmp/%s", si->name);
390         qtest_add_data_func(test_name, si->name, test_query);
391         g_free(test_name);
392     }
393 }
394
395 static void test_qmp_preconfig(void)
396 {
397     QDict *rsp, *ret;
398     QTestState *qs = qtest_startf("%s --preconfig", common_args);
399
400     /* preconfig state */
401     /* enabled commands, no error expected  */
402     g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-commands' }")));
403
404     /* forbidden commands, expected error */
405     g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }")));
406
407     /* check that query-status returns preconfig state */
408     rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }");
409     ret = qdict_get_qdict(rsp, "return");
410     g_assert(ret);
411     g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "preconfig");
412     qobject_unref(rsp);
413
414     /* exit preconfig state */
415     g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'exit-preconfig' }")));
416     qtest_qmp_eventwait(qs, "RESUME");
417
418     /* check that query-status returns running state */
419     rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }");
420     ret = qdict_get_qdict(rsp, "return");
421     g_assert(ret);
422     g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "running");
423     qobject_unref(rsp);
424
425     /* check that exit-preconfig returns error after exiting preconfig */
426     g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'exit-preconfig' }")));
427
428     /* enabled commands, no error expected  */
429     g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }")));
430
431     qtest_quit(qs);
432 }
433
434 int main(int argc, char *argv[])
435 {
436     QmpSchema schema;
437     int ret;
438
439     g_test_init(&argc, &argv, NULL);
440
441     qtest_add_func("qmp/protocol", test_qmp_protocol);
442     qtest_add_func("qmp/oob", test_qmp_oob);
443     qmp_schema_init(&schema);
444     add_query_tests(&schema);
445     qtest_add_func("qmp/preconfig", test_qmp_preconfig);
446
447     ret = g_test_run();
448
449     qmp_schema_cleanup(&schema);
450     return ret;
451 }
This page took 0.049368 seconds and 4 git commands to generate.