/* * QMP protocol test cases * * Copyright (c) 2017-2018 Red Hat Inc. * * Authors: * Markus Armbruster * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ #include "qemu/osdep.h" #include "libqtest.h" #include "qapi/error.h" #include "qapi/qapi-visit-misc.h" #include "qapi/qmp/qdict.h" #include "qapi/qmp/qlist.h" #include "qapi/qobject-input-visitor.h" #include "qapi/qmp/qstring.h" const char common_args[] = "-nodefaults -machine none"; static void test_version(QObject *version) { Visitor *v; VersionInfo *vinfo; g_assert(version); v = qobject_input_visitor_new(version); visit_type_VersionInfo(v, "version", &vinfo, &error_abort); qapi_free_VersionInfo(vinfo); visit_free(v); } static void assert_recovered(QTestState *qts) { QDict *resp; resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd' }"); qmp_assert_error_class(resp, "CommandNotFound"); } static void test_malformed(QTestState *qts) { QDict *resp; /* syntax error */ qtest_qmp_send_raw(qts, "{]\n"); resp = qtest_qmp_receive(qts); qmp_assert_error_class(resp, "GenericError"); assert_recovered(qts); /* lexical error: impossible byte outside string */ qtest_qmp_send_raw(qts, "{\xFF"); resp = qtest_qmp_receive(qts); qmp_assert_error_class(resp, "GenericError"); assert_recovered(qts); /* lexical error: funny control character outside string */ qtest_qmp_send_raw(qts, "{\x01"); resp = qtest_qmp_receive(qts); qmp_assert_error_class(resp, "GenericError"); assert_recovered(qts); /* lexical error: impossible byte in string */ qtest_qmp_send_raw(qts, "{'bad \xFF"); resp = qtest_qmp_receive(qts); qmp_assert_error_class(resp, "GenericError"); assert_recovered(qts); /* lexical error: control character in string */ qtest_qmp_send_raw(qts, "{'execute': 'nonexistent', 'id':'\n"); resp = qtest_qmp_receive(qts); qmp_assert_error_class(resp, "GenericError"); assert_recovered(qts); /* lexical error: interpolation */ qtest_qmp_send_raw(qts, "%%p\n"); /* two errors, one for "%", one for "p" */ resp = qtest_qmp_receive(qts); qmp_assert_error_class(resp, "GenericError"); resp = qtest_qmp_receive(qts); qmp_assert_error_class(resp, "GenericError"); assert_recovered(qts); /* Not even a dictionary */ resp = qtest_qmp(qts, "null"); qmp_assert_error_class(resp, "GenericError"); /* No "execute" key */ resp = qtest_qmp(qts, "{}"); qmp_assert_error_class(resp, "GenericError"); /* "execute" isn't a string */ resp = qtest_qmp(qts, "{ 'execute': true }"); qmp_assert_error_class(resp, "GenericError"); /* "arguments" isn't a dictionary */ resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'arguments': [] }"); qmp_assert_error_class(resp, "GenericError"); /* extra key */ resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'extra': true }"); qmp_assert_error_class(resp, "GenericError"); } static void test_qmp_protocol(void) { QDict *resp, *q, *ret; QList *capabilities; QTestState *qts; qts = qtest_init_without_qmp_handshake(false, common_args); /* Test greeting */ resp = qtest_qmp_receive(qts); q = qdict_get_qdict(resp, "QMP"); g_assert(q); test_version(qdict_get(q, "version")); capabilities = qdict_get_qlist(q, "capabilities"); g_assert(capabilities && qlist_empty(capabilities)); qobject_unref(resp); /* Test valid command before handshake */ resp = qtest_qmp(qts, "{ 'execute': 'query-version' }"); qmp_assert_error_class(resp, "CommandNotFound"); /* Test malformed commands before handshake */ test_malformed(qts); /* Test handshake */ resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }"); ret = qdict_get_qdict(resp, "return"); g_assert(ret && !qdict_size(ret)); qobject_unref(resp); /* Test repeated handshake */ resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }"); qmp_assert_error_class(resp, "CommandNotFound"); /* Test valid command */ resp = qtest_qmp(qts, "{ 'execute': 'query-version' }"); test_version(qdict_get(resp, "return")); qobject_unref(resp); /* Test malformed commands */ test_malformed(qts); /* Test 'id' */ resp = qtest_qmp(qts, "{ 'execute': 'query-name', 'id': 'cookie#1' }"); ret = qdict_get_qdict(resp, "return"); g_assert(ret); g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, "cookie#1"); qobject_unref(resp); /* Test command failure with 'id' */ resp = qtest_qmp(qts, "{ 'execute': 'human-monitor-command', 'id': 2 }"); g_assert_cmpint(qdict_get_int(resp, "id"), ==, 2); qmp_assert_error_class(resp, "GenericError"); qtest_quit(qts); } /* Out-of-band tests */ char tmpdir[] = "/tmp/qmp-test-XXXXXX"; char *fifo_name; static void setup_blocking_cmd(void) { if (!mkdtemp(tmpdir)) { g_error("mkdtemp: %s", strerror(errno)); } fifo_name = g_strdup_printf("%s/fifo", tmpdir); if (mkfifo(fifo_name, 0666)) { g_error("mkfifo: %s", strerror(errno)); } } static void cleanup_blocking_cmd(void) { unlink(fifo_name); rmdir(tmpdir); } static void send_cmd_that_blocks(QTestState *s, const char *id) { qtest_qmp_send(s, "{ 'execute': 'blockdev-add', 'id': %s," " 'arguments': {" " 'driver': 'blkdebug', 'node-name': %s," " 'config': %s," " 'image': { 'driver': 'null-co' } } }", id, id, fifo_name); } static void unblock_blocked_cmd(void) { int fd = open(fifo_name, O_WRONLY); g_assert(fd >= 0); close(fd); } static void send_oob_cmd_that_fails(QTestState *s, const char *id) { qtest_qmp_send(s, "{ 'exec-oob': 'migrate-pause', 'id': %s }", id); } static void recv_cmd_id(QTestState *s, const char *id) { QDict *resp = qtest_qmp_receive(s); g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, id); qobject_unref(resp); } static void test_qmp_oob(void) { QTestState *qts; QDict *resp, *q; const QListEntry *entry; QList *capabilities; QString *qstr; qts = qtest_init_without_qmp_handshake(true, common_args); /* Check the greeting message. */ resp = qtest_qmp_receive(qts); q = qdict_get_qdict(resp, "QMP"); g_assert(q); capabilities = qdict_get_qlist(q, "capabilities"); g_assert(capabilities && !qlist_empty(capabilities)); entry = qlist_first(capabilities); g_assert(entry); qstr = qobject_to(QString, entry->value); g_assert(qstr); g_assert_cmpstr(qstring_get_str(qstr), ==, "oob"); qobject_unref(resp); /* Try a fake capability, it should fail. */ resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities', " " 'arguments': { 'enable': [ 'cap-does-not-exist' ] } }"); g_assert(qdict_haskey(resp, "error")); qobject_unref(resp); /* Now, enable OOB in current QMP session, it should succeed. */ resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities', " " 'arguments': { 'enable': [ 'oob' ] } }"); g_assert(qdict_haskey(resp, "return")); qobject_unref(resp); /* * Try any command that does not support OOB but with OOB flag. We * should get failure. */ resp = qtest_qmp(qts, "{ 'exec-oob': 'query-cpus' }"); g_assert(qdict_haskey(resp, "error")); qobject_unref(resp); /* OOB command overtakes slow in-band command */ setup_blocking_cmd(); send_cmd_that_blocks(qts, "ib-blocks-1"); qtest_qmp_send(qts, "{ 'execute': 'query-name', 'id': 'ib-quick-1' }"); send_oob_cmd_that_fails(qts, "oob-1"); recv_cmd_id(qts, "oob-1"); unblock_blocked_cmd(); recv_cmd_id(qts, "ib-blocks-1"); recv_cmd_id(qts, "ib-quick-1"); /* Even malformed in-band command fails in-band */ send_cmd_that_blocks(qts, "blocks-2"); qtest_qmp_send(qts, "{ 'id': 'err-2' }"); unblock_blocked_cmd(); recv_cmd_id(qts, "blocks-2"); recv_cmd_id(qts, "err-2"); cleanup_blocking_cmd(); qtest_quit(qts); } /* Preconfig tests */ static void test_qmp_preconfig(void) { QDict *rsp, *ret; QTestState *qs = qtest_initf("%s --preconfig", common_args); /* preconfig state */ /* enabled commands, no error expected */ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-commands' }"))); /* forbidden commands, expected error */ g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }"))); /* check that query-status returns preconfig state */ rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }"); ret = qdict_get_qdict(rsp, "return"); g_assert(ret); g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "preconfig"); qobject_unref(rsp); /* exit preconfig state */ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'x-exit-preconfig' }"))); qtest_qmp_eventwait(qs, "RESUME"); /* check that query-status returns running state */ rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }"); ret = qdict_get_qdict(rsp, "return"); g_assert(ret); g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "running"); qobject_unref(rsp); /* check that x-exit-preconfig returns error after exiting preconfig */ g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'x-exit-preconfig' }"))); /* enabled commands, no error expected */ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }"))); qtest_quit(qs); } int main(int argc, char *argv[]) { g_test_init(&argc, &argv, NULL); qtest_add_func("qmp/protocol", test_qmp_protocol); qtest_add_func("qmp/oob", test_qmp_oob); qtest_add_func("qmp/preconfig", test_qmp_preconfig); return g_test_run(); }