qmp: introduce QMPCapability

There were no QMP capabilities defined.  Define the first capability,
"oob", to allow out-of-band messages.

After this patch, we will allow QMP clients to enable QMP capabilities
when sending the first "qmp_capabilities" command.  Originally we are
starting QMP session with no arguments like:

  { "execute": "qmp_capabilities" }

Now we can enable some QMP capabilities using (take OOB as example,
which is the only capability that we support):

  { "execute": "qmp_capabilities",
    "arguments": { "enable": [ "oob" ] } }

When the "arguments" key is not provided, no capability is enabled.

For capability "oob", the monitor needs to be run on a dedicated IO
thread, otherwise the command will fail.  For example, trying to enable
OOB on a MUXed typed QMP monitor will fail.

One thing to mention is that QMP capabilities are per-monitor, and also
when the connection is closed due to some reason, the capabilities will
be reset.

Also, touch up qmp-test.c to test the new bits.

Signed-off-by: Peter Xu <peterx@redhat.com>
Message-Id: <20180309090006.10018-11-peterx@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
[eblake: touch up commit message]
Signed-off-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
Peter Xu 2018-03-09 16:59:53 +08:00 committed by Eric Blake
parent a5ed352596
commit 02130314d8
3 changed files with 110 additions and 9 deletions

View file

@ -59,6 +59,7 @@
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/json-streamer.h"
#include "qapi/qmp/json-parser.h"
#include "qapi/qmp/qlist.h"
#include "qom/object_interfaces.h"
#include "trace-root.h"
#include "trace/control.h"
@ -170,6 +171,7 @@ typedef struct {
* mode.
*/
QmpCommandList *commands;
bool qmp_caps[QMP_CAPABILITY__MAX];
} MonitorQMP;
/*
@ -1042,8 +1044,42 @@ static void monitor_init_qmp_commands(void)
qmp_marshal_qmp_capabilities, QCO_NO_OPTIONS);
}
void qmp_qmp_capabilities(Error **errp)
static void qmp_caps_check(Monitor *mon, QMPCapabilityList *list,
Error **errp)
{
for (; list; list = list->next) {
assert(list->value < QMP_CAPABILITY__MAX);
switch (list->value) {
case QMP_CAPABILITY_OOB:
if (!mon->use_io_thr) {
/*
* Out-Of-Band only works with monitors that are
* running on dedicated IOThread.
*/
error_setg(errp, "This monitor does not support "
"Out-Of-Band (OOB)");
return;
}
break;
default:
break;
}
}
}
/* This function should only be called after capabilities are checked. */
static void qmp_caps_apply(Monitor *mon, QMPCapabilityList *list)
{
for (; list; list = list->next) {
mon->qmp.qmp_caps[list->value] = true;
}
}
void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
Error **errp)
{
Error *local_err = NULL;
if (cur_mon->qmp.commands == &qmp_commands) {
error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
"Capabilities negotiation is already complete, command "
@ -1051,6 +1087,21 @@ void qmp_qmp_capabilities(Error **errp)
return;
}
/* Enable QMP capabilities provided by the client if applicable. */
if (has_enable) {
qmp_caps_check(cur_mon, enable, &local_err);
if (local_err) {
/*
* Failed check on any of the capabilities will fail the
* entire command (and thus not apply any of the other
* capabilities that were also requested).
*/
error_propagate(errp, local_err);
return;
}
qmp_caps_apply(cur_mon, enable);
}
cur_mon->qmp.commands = &qmp_commands;
}
@ -3896,14 +3947,29 @@ void monitor_resume(Monitor *mon)
readline_show_prompt(mon->rs);
}
static QObject *get_qmp_greeting(void)
static QObject *get_qmp_greeting(Monitor *mon)
{
QList *cap_list = qlist_new();
QObject *ver = NULL;
QMPCapability cap;
qmp_marshal_query_version(NULL, &ver, NULL);
return qobject_from_jsonf("{'QMP': {'version': %p, 'capabilities': []}}",
ver);
for (cap = 0; cap < QMP_CAPABILITY__MAX; cap++) {
if (!mon->use_io_thr && cap == QMP_CAPABILITY_OOB) {
/* Monitors that are not using IOThread won't support OOB */
continue;
}
qlist_append(cap_list, qstring_from_str(QMPCapability_str(cap)));
}
return qobject_from_jsonf("{'QMP': {'version': %p, 'capabilities': %p}}",
ver, cap_list);
}
static void monitor_qmp_caps_reset(Monitor *mon)
{
memset(mon->qmp.qmp_caps, 0, sizeof(mon->qmp.qmp_caps));
}
static void monitor_qmp_event(void *opaque, int event)
@ -3914,7 +3980,8 @@ static void monitor_qmp_event(void *opaque, int event)
switch (event) {
case CHR_EVENT_OPENED:
mon->qmp.commands = &qmp_cap_negotiation_commands;
data = get_qmp_greeting();
monitor_qmp_caps_reset(mon);
data = get_qmp_greeting(mon);
monitor_json_emitter(mon, data);
qobject_decref(data);
mon_refcount++;

View file

@ -10,21 +10,47 @@
#
# Enable QMP capabilities.
#
# Arguments: None.
# Arguments:
#
# @enable: An optional list of QMPCapability values to enable. The
# client must not enable any capability that is not
# mentioned in the QMP greeting message. If the field is not
# provided, it means no QMP capabilities will be enabled.
# (since 2.12)
#
# Example:
#
# -> { "execute": "qmp_capabilities" }
# -> { "execute": "qmp_capabilities",
# "arguments": { "enable": [ "oob" ] } }
# <- { "return": {} }
#
# Notes: This command is valid exactly when first connecting: it must be
# issued before any other command will be accepted, and will fail once the
# monitor is accepting other commands. (see qemu docs/interop/qmp-spec.txt)
#
# The QMP client needs to explicitly enable QMP capabilities, otherwise
# all the QMP capabilities will be turned off by default.
#
# Since: 0.13
#
##
{ 'command': 'qmp_capabilities' }
{ 'command': 'qmp_capabilities',
'data': { '*enable': [ 'QMPCapability' ] } }
##
# @QMPCapability:
#
# 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.
# (Please refer to qmp-spec.txt for more information on OOB)
#
# Since: 2.12
#
##
{ 'enum': 'QMPCapability',
'data': [ 'oob' ] }
##
# @VersionTriple:

View file

@ -20,6 +20,7 @@
#include "qapi/qobject-input-visitor.h"
#include "qapi/util.h"
#include "qapi/visitor.h"
#include "qapi/qmp/qstring.h"
const char common_args[] = "-nodefaults -machine none";
@ -79,6 +80,8 @@ static void test_qmp_protocol(void)
QDict *resp, *q, *ret;
QList *capabilities;
QTestState *qts;
const QListEntry *entry;
QString *qstr;
qts = qtest_init_without_qmp_handshake(common_args);
@ -88,7 +91,12 @@ static void test_qmp_protocol(void)
g_assert(q);
test_version(qdict_get(q, "version"));
capabilities = qdict_get_qlist(q, "capabilities");
g_assert(capabilities && qlist_empty(capabilities));
g_assert(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");
QDECREF(resp);
/* Test valid command before handshake */