qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Michael Roth <mdroth@linux.vnet.ibm.com>
To: qemu-devel@nongnu.org
Cc: aliguori@linux.vnet.ibm.com, agl@linux.vnet.ibm.com,
	mdroth@linux.vnet.ibm.com, Jes.Sorensen@redhat.com
Subject: [Qemu-devel] [RFC][PATCH v2 09/17] qmp proxy: core code for proxying qmp requests to guest
Date: Mon, 18 Apr 2011 10:02:25 -0500	[thread overview]
Message-ID: <1303138953-1334-10-git-send-email-mdroth@linux.vnet.ibm.com> (raw)
In-Reply-To: <1303138953-1334-1-git-send-email-mdroth@linux.vnet.ibm.com>

This provides a QmpProxy class, 1 instance of which is shared by all QMP
servers/sessions to send/receive QMP requests/responses between QEMU and
the QEMU guest agent.

A single qmp_proxy_send_request() is the only interface currently needed
by a QMP session, QAPI/QMP's existing async support handles all the work
of doing callbacks and routing responses to the proper session.

Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
 qapi-schema.json |    7 ++
 qmp-core.c       |    8 ++
 qmp-core.h       |    7 +-
 qmp-proxy-core.h |   21 ++++
 qmp-proxy.c      |  294 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vl.c             |    1 +
 6 files changed, 332 insertions(+), 6 deletions(-)
 create mode 100644 qmp-proxy-core.h
 create mode 100644 qmp-proxy.c

diff --git a/qapi-schema.json b/qapi-schema.json
index de6c9a3..5292938 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1498,3 +1498,10 @@
 # Since: 0.14.0
 ##
 { 'option': 'vnc', 'data': 'VncConfig', 'implicit': 'address' }
+
+## 0.15.0 Events ##
+{ 'event': 'GUEST_AGENT_UP' }
+{ 'command': 'get-guest-agent-up-event', 'returns': 'GUEST_AGENT_UP' }
+
+{ 'event': 'GUEST_AGENT_RESET' }
+{ 'command': 'get-guest-agent-reset-event', 'returns': 'GUEST_AGENT_RESET' }
diff --git a/qmp-core.c b/qmp-core.c
index 9f3d182..dab50a1 100644
--- a/qmp-core.c
+++ b/qmp-core.c
@@ -937,7 +937,15 @@ void qmp_async_complete_command(QmpCommandState *cmd, QObject *retval, Error *er
     qemu_free(cmd);
 }
 
+extern QmpProxy *qmp_proxy_default;
+
 void qmp_guest_dispatch(const char *name, const QDict *args, Error **errp,
                         QmpGuestCompletionFunc *cb, void *opaque)
 {
+    if (!qmp_proxy_default) {
+        /* TODO: should set errp here */
+        fprintf(stderr, "qmp proxy: no guest proxy found\n");
+        return;
+    }
+    qmp_proxy_send_request(qmp_proxy_default, name, args, errp, cb, opaque);
 }
diff --git a/qmp-core.h b/qmp-core.h
index b676020..114d290 100644
--- a/qmp-core.h
+++ b/qmp-core.h
@@ -4,6 +4,7 @@
 #include "monitor.h"
 #include "qmp-marshal-types.h"
 #include "error_int.h"
+#include "qmp-proxy-core.h"
 
 struct QmpCommandState
 {
@@ -85,11 +86,5 @@ int qmp_state_get_fd(QmpState *sess);
     }                                                        \
 } while(0)
 
-typedef void (QmpGuestCompletionFunc)(void *opaque, QObject *ret_data, Error *err);
-
-void qmp_guest_dispatch(const char *name, const QDict *args, Error **errp,
-                        QmpGuestCompletionFunc *cb, void *opaque);
-
-
 #endif
 
diff --git a/qmp-proxy-core.h b/qmp-proxy-core.h
new file mode 100644
index 0000000..6afdc23
--- /dev/null
+++ b/qmp-proxy-core.h
@@ -0,0 +1,21 @@
+#ifndef QMP_PROXY_CORE_H
+#define QMP_PROXY_CORE_H
+
+#define QMP_PROXY_PATH_DEFAULT "/tmp/qmp-proxy.sock"
+
+typedef void (QmpGuestCompletionFunc)(void *opaque, QObject *ret_data,
+                                      Error *err);
+
+void qmp_guest_dispatch(const char *name, const QDict *args, Error **errp,
+                        QmpGuestCompletionFunc *cb, void *opaque);
+
+typedef struct QmpProxy QmpProxy;
+
+void qmp_proxy_send_request(QmpProxy *p, const char *name,
+                            const QDict *args, Error **errp,
+                            QmpGuestCompletionFunc *cb, void *opaque);
+QmpProxy *qmp_proxy_new(CharDriverState *chr);
+void qmp_proxy_close(QmpProxy *p);
+int qmp_proxy_read(QmpProxy *p, const uint8_t *buf, int len);
+
+#endif
diff --git a/qmp-proxy.c b/qmp-proxy.c
new file mode 100644
index 0000000..a4c7eea
--- /dev/null
+++ b/qmp-proxy.c
@@ -0,0 +1,294 @@
+/*
+ * QMP definitions for communicating with guest agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth      <mdroth@linux.vnet.ibm.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 <glib.h>
+#include "qmp.h"
+#include "qmp-core.h"
+#include "qemu-queue.h"
+#include "json-parser.h"
+#include "json-streamer.h"
+#include "qemu_socket.h"
+
+#define QMP_SENTINEL 0xFF
+
+typedef struct QmpProxyRequest {
+    const char *name;
+    const QDict *args;
+    QmpGuestCompletionFunc *cb;
+    void *opaque;
+    QString *json;
+    QTAILQ_ENTRY(QmpProxyRequest) entry;
+} QmpProxyRequest;
+
+struct QmpProxy {
+    int session_id;
+    JSONMessageParser parser;
+    GString *tx;
+    QEMUTimer *tx_timer;
+    int tx_timer_interval;
+    QTAILQ_HEAD(, QmpProxyRequest) requests;
+    CharDriverState *chr;
+};
+
+static GuestAgentUpEvent guest_agent_up_event;
+static GuestAgentResetEvent guest_agent_reset_event;
+static void qmp_proxy_write(QmpProxy *p);
+
+GuestAgentUpEvent *qmp_get_guest_agent_up_event(Error **errp)
+{
+    return &guest_agent_up_event;
+}
+
+GuestAgentResetEvent *qmp_get_guest_agent_reset_event(Error **errp)
+{
+    return &guest_agent_reset_event;
+}
+
+static int qmp_proxy_cancel_request(QmpProxy *p, QmpProxyRequest *r)
+{
+    if (r && r->cb) {
+        r->cb(r->opaque, NULL, NULL);
+    }
+
+    return 0;
+}
+
+static int qmp_proxy_cancel_all(QmpProxy *p)
+{
+    QmpProxyRequest *r, *tmp;
+    QTAILQ_FOREACH_SAFE(r, &p->requests, entry, tmp) {
+        qmp_proxy_cancel_request(p, r);
+        QTAILQ_REMOVE(&p->requests, r, entry);
+    }
+
+    return 0;
+}
+
+static void qmp_proxy_send_control_event(QmpProxy *p, QDict *evt)
+{
+    QString *json = qobject_to_json(QOBJECT(evt));;
+
+    /* currently there's no reason to send any queued data or control
+     * events ahead of the most recent event, so empty the buffer first
+     */
+    g_string_truncate(p->tx, p->tx->len);
+    g_string_append_c(p->tx, QMP_SENTINEL);
+    g_string_append(p->tx, qstring_get_str(json));
+    QDECREF(json);
+    qmp_proxy_write(p);
+}
+
+static void qmp_proxy_send_host_ack(QmpProxy *p, int guest_sid)
+{
+    QDict *evt = qdict_new();
+
+    qdict_put_obj(evt, "_control_event",
+                  QOBJECT(qstring_from_str("host_ack")));
+    qdict_put_obj(evt, "_control_arg_host_sid",
+                  QOBJECT(qint_from_int(p->session_id)));
+    qdict_put_obj(evt, "_control_arg_guest_sid",
+                  QOBJECT(qint_from_int(guest_sid)));
+
+    qmp_proxy_send_control_event(p, evt);
+}
+
+static void qmp_proxy_send_host_init(QmpProxy *p)
+{
+    QDict *evt = qdict_new();
+
+    p->session_id = g_random_int_range(1, G_MAXINT32);
+    qdict_put_obj(evt, "_control_event",
+                  QOBJECT(qstring_from_str("host_init")));
+    qdict_put_obj(evt, "_control_arg_host_sid",
+                  QOBJECT(qint_from_int(p->session_id)));
+
+    qmp_proxy_send_control_event(p, evt);
+}
+
+static void qmp_proxy_process_control_event(QmpProxy *p, const QDict *evt)
+{
+    const char *cmd;
+    int host_sid, guest_sid;
+
+    cmd = qdict_get_try_str(evt, "_control_event");
+    if (!cmd) {
+        fprintf(stderr, "received NULL control event\n");
+    } else if (strcmp(cmd, "guest_ack") == 0) {
+        host_sid = qdict_get_try_int(evt, "_control_arg_host_sid", 0);
+        if (!host_sid) {
+            fprintf(stderr, "invalid guest_ack: wrong host sid\n");
+            return;
+        }
+        /* guest responded to host_init, return a host_ack */
+        /* reset outstanding requests, then send an ack with the
+         * session id they passed us
+         */
+        guest_sid = qdict_get_try_int(evt, "_control_arg_guest_sid", 0);
+        if (!guest_sid) {
+            fprintf(stderr, "invalid guest_ack: missing guest sid\n");
+            return;
+        }
+        qmp_proxy_send_host_ack(p, guest_sid);
+    } else if (strcmp(cmd, "guest_up") == 0) {
+        /* guest agent [re-]started, cancel any outstanding request and
+         * start negotiation */
+        /* TODO: should generate a qmp event for this as well */
+        signal_notify(&guest_agent_up_event);
+        signal_notify(&guest_agent_reset_event);
+        qmp_proxy_cancel_all(p);
+        qmp_proxy_send_host_init(p);
+    } else if (strcmp(cmd, "guest_reset") == 0) {
+        /* guest agent reset it's state (likely due to a command timeout)
+         * cancel all outstanding requests */
+        signal_notify(&guest_agent_reset_event);
+        qmp_proxy_cancel_all(p);
+    } else {
+        fprintf(stderr, "received unknown control event\n");
+    }
+}
+
+static void qmp_proxy_process_event(JSONMessageParser *parser, QList *tokens)
+{
+    QmpProxy *p = container_of(parser, QmpProxy, parser);
+    QmpProxyRequest *r;
+    QObject *obj;
+    QDict *qdict;
+    Error *err = NULL;
+
+    fprintf(stderr, "qmp proxy: called\n");
+    obj = json_parser_parse_err(tokens, NULL, &err);
+    if (!obj) {
+        fprintf(stderr, "qmp proxy: failed to parse\n");
+        return;
+    } else {
+        fprintf(stderr, "qmp proxy: parse successful\n");
+        qdict = qobject_to_qdict(obj);
+    }
+
+    if (qdict_haskey(qdict, "_control_event")) {
+        /* handle transport-level control event */
+        qmp_proxy_process_control_event(p, qdict);
+    } else if (qdict_haskey(qdict, "return")) {
+        /* handle proxied qmp command response */
+        fprintf(stderr, "received return\n");
+        r = QTAILQ_FIRST(&p->requests);
+        if (!r) {
+            fprintf(stderr, "received return, but no request queued\n");
+            return;
+        }
+        /* XXX: can't assume type here */
+        fprintf(stderr, "recieved response for cmd: %s\nreturn: %s\n",
+                r->name, qstring_get_str(qobject_to_json(QOBJECT(qdict))));
+        r->cb(r->opaque, qdict_get(qdict, "return"), NULL);
+        QTAILQ_REMOVE(&p->requests, r, entry);
+        qemu_free(r);
+        fprintf(stderr, "done handling response\n");
+    } else {
+        fprintf(stderr, "received invalid payload format\n");
+    }
+
+    QDECREF(qdict);
+}
+
+static void qmp_proxy_write_handler(void *opaque)
+{
+    QmpProxy *p = opaque;
+    int max, len;
+
+    if (!p->tx->len) {
+        return;
+    }
+
+    max = qemu_chr_can_read(p->chr);
+    if (max) {
+        len = MIN(max, p->tx->len);
+        qemu_chr_read(p->chr, (uint8_t *)p->tx->str, len);
+        g_string_erase(p->tx, 0, len);
+    }
+
+    if (p->tx->len) {
+        /* defer transfer of remaining data */
+        qemu_mod_timer(p->tx_timer,
+                       qemu_get_clock(rt_clock) + p->tx_timer_interval);
+    }
+}
+
+int qmp_proxy_read(QmpProxy *p, const uint8_t *buf, int len)
+{
+    return json_message_parser_feed(&p->parser, (const char *)buf, len);
+}
+
+void qmp_proxy_write(QmpProxy *p)
+{
+    /* more stuff in our buffer, kick the write handler */
+    qmp_proxy_write_handler(p);
+}
+
+void qmp_proxy_send_request(QmpProxy *p, const char *name,
+                            const QDict *args, Error **errp,
+                            QmpGuestCompletionFunc *cb, void *opaque)
+{
+    QmpProxyRequest *r = qemu_mallocz(sizeof(QmpProxyRequest));
+    QDict *payload = qdict_new();
+    QString *json;
+
+    /* TODO: don't really need to hold on to name/args after encoding */
+    r->name = name;
+    r->args = args;
+    r->cb = cb;
+    r->opaque = opaque;
+
+    qdict_put_obj(payload, "execute", QOBJECT(qstring_from_str(r->name)));
+    /* TODO: casting a const so we can add it to our dictionary. bad. */
+    qdict_put_obj(payload, "arguments", QOBJECT((QDict *)args));
+
+    json = qobject_to_json(QOBJECT((QDict *)payload));
+    if (!json) {
+        goto out_bad;
+    }
+
+    QTAILQ_INSERT_TAIL(&p->requests, r, entry);
+    g_string_append(p->tx, qstring_get_str(json));
+    QDECREF(json);
+    qmp_proxy_write(p);
+    return;
+
+out_bad:
+    cb(opaque, NULL, NULL);
+    qemu_free(r);
+}
+
+QmpProxy *qmp_proxy_new(CharDriverState *chr)
+{
+    QmpProxy *p = qemu_mallocz(sizeof(QmpProxy));
+
+    signal_init(&guest_agent_up_event);
+    signal_init(&guest_agent_reset_event);
+
+    /* there's a reason for this madness */
+    p->tx_timer = qemu_new_timer(rt_clock, qmp_proxy_write_handler, p);
+    p->tx_timer_interval = 10;
+    p->tx = g_string_new("");
+    p->chr = chr;
+    json_message_parser_init(&p->parser, qmp_proxy_process_event);
+    QTAILQ_INIT(&p->requests);
+
+    return p;
+}
+
+void qmp_proxy_close(QmpProxy *p)
+{
+    qmp_proxy_cancel_all(p);
+    g_string_free(p->tx, TRUE);
+    qemu_free(p);
+}
diff --git a/vl.c b/vl.c
index 3fdc7cc..e8c49ef 100644
--- a/vl.c
+++ b/vl.c
@@ -231,6 +231,7 @@ int ctrl_grab = 0;
 unsigned int nb_prom_envs = 0;
 const char *prom_envs[MAX_PROM_ENVS];
 int boot_menu;
+QmpProxy *qmp_proxy_default;
 
 ShutdownEvent qemu_shutdown_event;
 ResetEvent qemu_reset_event;
-- 
1.7.0.4

  parent reply	other threads:[~2011-04-18 15:03 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-04-18 15:02 [Qemu-devel] [RFC][PATCH v2 00/11] QEMU Guest Agent: QMP-based host/guest communication (virtagent) Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 01/17] json-lexer: make lexer error-recovery more deterministic Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 02/17] json-streamer: add handling for JSON_ERROR token/state Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 03/17] json-parser: add handling for NULL token list Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 04/17] qapi: fix function name typo in qmp-gen.py Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 05/17] qapi: fix handling for null-return async callbacks Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 06/17] qapi: fix memory leak for async marshalling code Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 07/17] qapi: qmp-gen.py, use basename of path for guard/core prefix Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 08/17] qapi: fix Error usage in qemu-sockets.c Michael Roth
2011-04-21  8:20   ` Jes Sorensen
2011-04-18 15:02 ` Michael Roth [this message]
2011-04-21  8:30   ` [Qemu-devel] [RFC][PATCH v2 09/17] qmp proxy: core code for proxying qmp requests to guest Jes Sorensen
2011-04-21 12:57     ` Michael Roth
2011-04-26 13:21   ` Stefan Hajnoczi
2011-04-26 14:38     ` Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 10/17] qmp proxy: add qmp_proxy chardev Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 11/17] qmp proxy: build QEMU with qmp proxy Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 12/17] guest agent: worker thread class Michael Roth
2011-04-21  8:44   ` Jes Sorensen
2011-04-21 13:15     ` Michael Roth
2011-04-21 13:19       ` Jes Sorensen
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 13/17] guest agent: command state class Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 14/17] guest agent: core marshal/dispatch interfaces Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 15/17] guest agent: qemu-ga daemon Michael Roth
2011-04-21  8:50   ` Jes Sorensen
2011-04-21 13:21     ` Michael Roth
2011-04-22  9:23       ` Ian Molton
2011-04-22 11:51         ` Jes Sorensen
2011-04-25 12:27           ` Ian Molton
2011-04-26 13:39             ` Jes Sorensen
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 16/17] guest agent: add guest agent RPCs/commands Michael Roth
2011-04-18 15:02 ` [Qemu-devel] [RFC][PATCH v2 17/17] guest agent: build qemu-ga, add QEMU-wide gio dep Michael Roth
2011-04-21  9:46 ` [Qemu-devel] [RFC][PATCH v2 00/11] QEMU Guest Agent: QMP-based host/guest communication (virtagent) Jes Sorensen
2011-04-21 13:55   ` Michael Roth
2011-05-03 12:51     ` Jes Sorensen
2011-05-03 13:53       ` Michael Roth
2011-05-03 14:12         ` Jes Sorensen
2011-05-03 14:56           ` Michael Roth
2011-04-21 14:10 ` Jes Sorensen
2011-04-21 20:58   ` Michael Roth
2011-04-26  6:57     ` Jes Sorensen
2011-04-26 14:27       ` Michael Roth
2011-04-26 14:34         ` Jes Sorensen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1303138953-1334-10-git-send-email-mdroth@linux.vnet.ibm.com \
    --to=mdroth@linux.vnet.ibm.com \
    --cc=Jes.Sorensen@redhat.com \
    --cc=agl@linux.vnet.ibm.com \
    --cc=aliguori@linux.vnet.ibm.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).