fa4dcf577e
misc.json contains definitions that are related to the system emulator, so it can't be used for other tools like the storage daemon. This patch moves basic functionality that is shared between all tools (and mostly related to the monitor itself) into a new control.json, which could be used in tools as well. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20200129102239.31435-3-kwolf@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com>
345 lines
10 KiB
C
345 lines
10 KiB
C
/*
|
|
* QMP protocol test cases
|
|
*
|
|
* Copyright (c) 2017-2018 Red Hat Inc.
|
|
*
|
|
* Authors:
|
|
* Markus Armbruster <armbru@redhat.com>
|
|
*
|
|
* 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-control.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");
|
|
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(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);
|
|
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', 'read-zeroes': true } } }",
|
|
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(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);
|
|
}
|
|
|
|
static void test_qmp_missing_any_arg(void)
|
|
{
|
|
QTestState *qts;
|
|
QDict *resp;
|
|
|
|
qts = qtest_init(common_args);
|
|
resp = qtest_qmp(qts, "{'execute': 'qom-set', 'arguments':"
|
|
" { 'path': '/machine', 'property': 'rtc-time' } }");
|
|
g_assert_nonnull(resp);
|
|
qmp_assert_error_class(resp, "GenericError");
|
|
qtest_quit(qts);
|
|
}
|
|
|
|
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);
|
|
qtest_add_func("qmp/missing-any-arg", test_qmp_missing_any_arg);
|
|
|
|
return g_test_run();
|
|
}
|