From c06982122036546fcbfe40f5b22ae7088d28c9a2 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:27 +0200 Subject: qmp: Say "out-of-band" instead of "Out-Of-Band" Affects documentation and a few error messages. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-2-armbru@redhat.com> --- qapi/misc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'qapi') diff --git a/qapi/misc.json b/qapi/misc.json index 29da785..0446c3e 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -46,7 +46,7 @@ # Enumeration of capabilities to be advertised during initial client # connection, used for agreeing on particular QMP extension behaviors. # -# @oob: QMP ability to support Out-Of-Band requests. +# @oob: QMP ability to support out-of-band requests. # (Please refer to qmp-spec.txt for more information on OOB) # # Since: 2.12 -- cgit v1.1 From d621cfe0a177978b17711a712293221294430f53 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:30 +0200 Subject: qmp: Document COMMAND_DROPPED design flaw Events are broadcast to all monitors. If another monitor's client has a command with the same ID in flight, the event will incorrectly claim that command was dropped. This must be fixed before out-of-band execution can graduate from "experimental". Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-5-armbru@redhat.com> --- qapi/misc.json | 3 +++ 1 file changed, 3 insertions(+) (limited to 'qapi') diff --git a/qapi/misc.json b/qapi/misc.json index 0446c3e..74cd97f 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -3454,6 +3454,9 @@ # only be dropped when the oob capability is enabled. # # @id: The dropped command's "id" field. +# FIXME Broken by design. Events are broadcast to all monitors. If +# another monitor's client has a command with the same ID in flight, +# the event will incorrectly claim that command was dropped. # # @reason: The reason why the command is dropped. # -- cgit v1.1 From 97ca0712c8f2b65479649693ffe01e7358418955 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:31 +0200 Subject: qmp: Get rid of x-oob-test command tests/qmp-test tests an out-of-band command overtaking a slow in-band command. To do that, it needs: 1. An in-band command that *reliably* takes long enough to be overtaken. 2. An out-of-band command to do the overtaking. 3. To avoid delays, a way to make the in-band command complete quickly after it was overtaken. To satisfy these needs, commit 469638f9cb3 provides the rather peculiar oob-capable QMP command x-oob-test: * With "lock": true, it waits for a global semaphore. * With "lock": false, it signals the global semaphore. To satisfy 1., the test runs x-oob-test in-band with "lock": true. To satisfy 2. and 3., it runs x-oob-test out-of-band with "lock": false. Note that waiting for a semaphore violates the rules for oob-capable commands. Running x-oob-test with "lock": true hangs the monitor until you run x-oob-test with "lock": false on another monitor (which you might not have set up). Having an externally visible QMP command that may hang the monitor is not nice. Let's apply a little more ingenuity to the problem. Idea: have an existing command block on reading a FIFO special file, unblock it by opening the FIFO for writing. For 1., use {"execute": "blockdev-add", "id": ID1, "arguments": { "driver": "blkdebug", "node-name": ID1, "config": FIFO, "image": { "driver": "null-co"}}} where ID1 is an arbitrary string, and FIFO is the name of the FIFO. For 2., use {"execute": "migrate-pause", "id": ID2, "control": {"run-oob": true}} where ID2 is a different arbitrary string. Since there's no migration to pause, the command will fail, but that's fine; instant failure is still a test of out-of-band responses overtaking in-band commands. For 3., open FIFO for writing. Drop QMP command x-oob-test. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-6-armbru@redhat.com> [Error checking tweaked] --- qapi/misc.json | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'qapi') diff --git a/qapi/misc.json b/qapi/misc.json index 74cd97f..f186041 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -3473,24 +3473,6 @@ 'data': { 'id': 'any', 'reason': 'CommandDropReason' } } ## -# @x-oob-test: -# -# Test OOB functionality. When sending this command with lock=true, -# it'll try to hang the dispatcher. When sending it with lock=false, -# it'll try to notify the locked thread to continue. Note: it should -# only be used by QMP test program rather than anything else. -# -# Since: 2.12 -# -# Example: -# -# { "execute": "x-oob-test", -# "arguments": { "lock": true } } -## -{ 'command': 'x-oob-test', 'data' : { 'lock': 'bool' }, - 'allow-oob': true } - -## # @set-numa-node: # # Runtime equivalent of '-numa' CLI option, available at -- cgit v1.1 From 0fa39d0b0374b983535de8591e5e561401d1d5c6 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:35 +0200 Subject: qmp qemu-ga: Revert change that accidentally made qemu-ga accept "id" Commit cf869d53172 "qmp: support out-of-band (oob) execution" changed how we check "id": Note that in the patch I exported qmp_dispatch_check_obj() to be used to check the request earlier, and at the same time allowed "id" field to be there since actually we always allow that. The part after "and" is ill-advised: it makes qemu-ga accept and ignore "id". Revert. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-10-armbru@redhat.com> --- qapi/qmp-dispatch.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index 935f9e1..3d5d5e1 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -52,8 +52,6 @@ QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) "QMP input member 'arguments' must be an object"); return NULL; } - } else if (!strcmp(arg_name, "id")) { - continue; } else if (!strcmp(arg_name, "control")) { if (qobject_type(arg_obj) != QTYPE_QDICT) { error_setg(errp, -- cgit v1.1 From 674ed7228f03150d15703961ea2a59cd744f3beb Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:37 +0200 Subject: qmp qemu-ga: Fix qemu-ga not to accept "control" Commit cf869d53172 "qmp: support out-of-band (oob) execution" accidentally made qemu-ga accept and ignore "control". Fix that. Out-of-band execution in a monitor that doesn't support it now fails with {"error": {"class": "GenericError", "desc": "QMP input member 'control' is unexpected"}} instead of {"error": {"class": "GenericError", "desc": "Please enable out-of-band first for the session during capabilities negotiation"}} The old description is suboptimal when out-of-band cannot not be enabled, or the command doesn't support out-of-band execution. The new description is a bit unspecific, but it'll do. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-12-armbru@redhat.com> --- qapi/qmp-dispatch.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index 3d5d5e1..0ad0fab 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -20,7 +20,8 @@ #include "qapi/qmp/qbool.h" #include "sysemu/sysemu.h" -QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) +QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, + Error **errp) { const QDictEntry *ent; const char *arg_name; @@ -52,7 +53,7 @@ QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) "QMP input member 'arguments' must be an object"); return NULL; } - } else if (!strcmp(arg_name, "control")) { + } else if (!strcmp(arg_name, "control") && allow_oob) { if (qobject_type(arg_obj) != QTYPE_QDICT) { error_setg(errp, "QMP input member 'control' must be a dict"); @@ -74,7 +75,7 @@ QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) } static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, - Error **errp) + bool allow_oob, Error **errp) { Error *local_err = NULL; const char *command; @@ -82,7 +83,7 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, QmpCommand *cmd; QObject *ret = NULL; - dict = qmp_dispatch_check_obj(request, errp); + dict = qmp_dispatch_check_obj(request, allow_oob, errp); if (!dict) { return NULL; } @@ -157,13 +158,14 @@ bool qmp_is_oob(QDict *dict) return qbool_get_bool(bool_obj); } -QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request) +QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request, + bool allow_oob) { Error *err = NULL; QObject *ret; QDict *rsp; - ret = do_qmp_dispatch(cmds, request, &err); + ret = do_qmp_dispatch(cmds, request, allow_oob, &err); rsp = qdict_new(); if (err) { -- cgit v1.1 From 00ecec151d2323e742af94cccf2de77025f3c0c1 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:38 +0200 Subject: qmp: Redo how the client requests out-of-band execution Commit cf869d53172 "qmp: support out-of-band (oob) execution" added a general mechanism for command-independent arguments just for an out-of-band flag: The "control" key is introduced to store this extra flag. "control" field is used to store arguments that are shared by all the commands, rather than command specific arguments. Let "run-oob" be the first. However, it failed to reject unknown members of "control". For instance, in QMP command {"execute": "query-name", "id": 42, "control": {"crap": true}} "crap" gets silently ignored. Instead of fixing this, revert the general "control" mechanism (because YAGNI), and do it the way I initially proposed, with key "exec-oob". Simpler code, simpler interface. An out-of-band command {"execute": "migrate-pause", "id": 42, "control": {"run-oob": true}} becomes {"exec-oob": "migrate-pause", "id": 42} Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-13-armbru@redhat.com> [Commit message typo fixed] --- qapi/qmp-dispatch.c | 51 +++++++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 30 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index 0ad0fab..12be120 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -23,11 +23,11 @@ QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, Error **errp) { + const char *exec_key = NULL; const QDictEntry *ent; const char *arg_name; const QObject *arg_obj; - bool has_exec_key = false; - QDict *dict = NULL; + QDict *dict; dict = qobject_to(QDict, request); if (!dict) { @@ -40,23 +40,23 @@ QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, arg_name = qdict_entry_key(ent); arg_obj = qdict_entry_value(ent); - if (!strcmp(arg_name, "execute")) { + if (!strcmp(arg_name, "execute") + || (!strcmp(arg_name, "exec-oob") && allow_oob)) { if (qobject_type(arg_obj) != QTYPE_QSTRING) { - error_setg(errp, - "QMP input member 'execute' must be a string"); + error_setg(errp, "QMP input member '%s' must be a string", + arg_name); return NULL; } - has_exec_key = true; - } else if (!strcmp(arg_name, "arguments")) { - if (qobject_type(arg_obj) != QTYPE_QDICT) { - error_setg(errp, - "QMP input member 'arguments' must be an object"); + if (exec_key) { + error_setg(errp, "QMP input member '%s' clashes with '%s'", + arg_name, exec_key); return NULL; } - } else if (!strcmp(arg_name, "control") && allow_oob) { + exec_key = arg_name; + } else if (!strcmp(arg_name, "arguments")) { if (qobject_type(arg_obj) != QTYPE_QDICT) { error_setg(errp, - "QMP input member 'control' must be a dict"); + "QMP input member 'arguments' must be an object"); return NULL; } } else { @@ -66,7 +66,7 @@ QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, } } - if (!has_exec_key) { + if (!exec_key) { error_setg(errp, "QMP input lacks member 'execute'"); return NULL; } @@ -88,7 +88,11 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, return NULL; } - command = qdict_get_str(dict, "execute"); + command = qdict_get_try_str(dict, "execute"); + if (!command) { + assert(allow_oob); + command = qdict_get_str(dict, "exec-oob"); + } cmd = qmp_find_command(cmds, command); if (cmd == NULL) { error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, @@ -137,25 +141,12 @@ QObject *qmp_build_error_object(Error *err) } /* - * Detect whether a request should be run out-of-band, by quickly - * peeking at whether we have: { "control": { "run-oob": true } }. By - * default commands are run in-band. + * Does @qdict look like a command to be run out-of-band? */ bool qmp_is_oob(QDict *dict) { - QBool *bool_obj; - - dict = qdict_get_qdict(dict, "control"); - if (!dict) { - return false; - } - - bool_obj = qobject_to(QBool, qdict_get(dict, "run-oob")); - if (!bool_obj) { - return false; - } - - return qbool_get_bool(bool_obj); + return qdict_haskey(dict, "exec-oob") + && !qdict_haskey(dict, "execute"); } QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request, -- cgit v1.1 From 69240fe62d1ae02257bc0694a11c478b10378948 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:43 +0200 Subject: qmp: Don't let malformed in-band commands jump the queue handle_qmp_command() reports certain errors right away. This is wrong when OOB is enabled, because the errors can "jump the queue" then, as the previous commit demonstrates. To fix, we need to delay errors until dispatch. Do that for semantic errors, mostly by reverting ill-advised parts of commit cf869d53172 "qmp: support out-of-band (oob) execution". Bonus: doesn't run qmp_dispatch_check_obj() twice, once in handle_qmp_command(), and again in do_qmp_dispatch(). That's also due to commit cf869d53172. The next commit will fix queue jumping for syntax errors. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-18-armbru@redhat.com> --- qapi/qmp-dispatch.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index 12be120..6d78f3e 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -20,8 +20,8 @@ #include "qapi/qmp/qbool.h" #include "sysemu/sysemu.h" -QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, - Error **errp) +static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob, + Error **errp) { const char *exec_key = NULL; const QDictEntry *ent; @@ -78,6 +78,7 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, bool allow_oob, Error **errp) { Error *local_err = NULL; + bool oob; const char *command; QDict *args, *dict; QmpCommand *cmd; @@ -89,9 +90,11 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, } command = qdict_get_try_str(dict, "execute"); + oob = false; if (!command) { assert(allow_oob); command = qdict_get_str(dict, "exec-oob"); + oob = true; } cmd = qmp_find_command(cmds, command); if (cmd == NULL) { @@ -104,6 +107,11 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, command); return NULL; } + if (oob && !(cmd->options & QCO_ALLOW_OOB)) { + error_setg(errp, "The command %s does not support OOB", + command); + return false; + } if (runstate_check(RUN_STATE_PRECONFIG) && !(cmd->options & QCO_ALLOW_PRECONFIG)) { -- cgit v1.1 From cee32796cadc9510ee00f029a933009df7a28ae2 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:48 +0200 Subject: qmp: De-duplicate error response building All callers of qmp_build_error_object() duplicate the code to wrap it in a response object. Replace it by qmp_error_response() that captures the duplicated code, including error_free(). Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-23-armbru@redhat.com> --- qapi/qmp-dispatch.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index 6d78f3e..c85748a 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -141,11 +141,15 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, return ret; } -QObject *qmp_build_error_object(Error *err) +QDict *qmp_error_response(Error *err) { - return qobject_from_jsonf("{ 'class': %s, 'desc': %s }", - QapiErrorClass_str(error_get_class(err)), - error_get_pretty(err)); + QDict *rsp; + + rsp = qdict_from_jsonf_nofail("{ 'error': { 'class': %s, 'desc': %s } }", + QapiErrorClass_str(error_get_class(err)), + error_get_pretty(err)); + error_free(err); + return rsp; } /* @@ -166,15 +170,13 @@ QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request, ret = do_qmp_dispatch(cmds, request, allow_oob, &err); - rsp = qdict_new(); if (err) { - qdict_put_obj(rsp, "error", qmp_build_error_object(err)); - error_free(err); + rsp = qmp_error_response(err); } else if (ret) { + rsp = qdict_new(); qdict_put_obj(rsp, "return", ret); } else { - qobject_unref(rsp); - return NULL; + rsp = NULL; } return QOBJECT(rsp); -- cgit v1.1 From d43b16945afa8457b03aee543a110c4ff0b7f070 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:49 +0200 Subject: qmp: Use QDict * instead of QObject * for response objects By using the more specific type, we get fewer downcasts. The downcasts are safe, but not obviously so, at least not locally. Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-24-armbru@redhat.com> --- qapi/qmp-dispatch.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index c85748a..761812e 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -161,8 +161,8 @@ bool qmp_is_oob(QDict *dict) && !qdict_haskey(dict, "execute"); } -QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request, - bool allow_oob) +QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request, + bool allow_oob) { Error *err = NULL; QObject *ret; @@ -179,5 +179,5 @@ QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request, rsp = NULL; } - return QOBJECT(rsp); + return rsp; } -- cgit v1.1 From 62e93be286626839ef2597da81f70a3ba4a31fff Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:53 +0200 Subject: qmp: Add some comments around null responses Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-28-armbru@redhat.com> --- qapi/qmp-dispatch.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index 761812e..6f2d466 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -133,6 +133,7 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, } else if (cmd->options & QCO_NO_SUCCESS_RESP) { g_assert(!ret); } else if (!ret) { + /* TODO turn into assertion */ ret = QOBJECT(qdict_new()); } @@ -176,6 +177,7 @@ QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request, rsp = qdict_new(); qdict_put_obj(rsp, "return", ret); } else { + /* Can only happen for commands with QCO_NO_SUCCESS_RESP */ rsp = NULL; } -- cgit v1.1 From cd499d2058adb657b034023e9e7428c94b0f212d Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 3 Jul 2018 10:53:54 +0200 Subject: qmp: Switch timestamp_put() to qdict_from_jsonf_nofail() There's just one use of qobject_from_jsonf() to parse a JSON object left: timestamp_put(). Switch it to qdict_from_jsonf_nofail(). Signed-off-by: Markus Armbruster Reviewed-by: Eric Blake Message-Id: <20180703085358.13941-29-armbru@redhat.com> --- qapi/qmp-event.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-event.c b/qapi/qmp-event.c index 9d7e88e..5b88540 100644 --- a/qapi/qmp-event.c +++ b/qapi/qmp-event.c @@ -34,15 +34,15 @@ QMPEventFuncEmit qmp_event_get_func_emit(void) static void timestamp_put(QDict *qdict) { int err; - QObject *obj; + QDict *ts; qemu_timeval tv; err = qemu_gettimeofday(&tv); /* Put -1 to indicate failure of getting host time */ - obj = qobject_from_jsonf("{ 'seconds': %lld, 'microseconds': %lld }", - err < 0 ? -1LL : (long long)tv.tv_sec, - err < 0 ? -1LL : (long long)tv.tv_usec); - qdict_put_obj(qdict, "timestamp", obj); + ts = qdict_from_jsonf_nofail("{ 'seconds': %lld, 'microseconds': %lld }", + err < 0 ? -1LL : (long long)tv.tv_sec, + err < 0 ? -1LL : (long long)tv.tv_usec); + qdict_put(qdict, "timestamp", ts); } /* -- cgit v1.1