[virt-tools-list] [PATCH 22/22] spice: hook into QMP port
Marc-André Lureau
marcandre.lureau at redhat.com
Wed Sep 5 09:31:24 UTC 2018
Hi
On Mon, Sep 3, 2018 at 8:53 AM, Victor Toso <victortoso at redhat.com> wrote:
> Hi,
>
> On Tue, Jul 31, 2018 at 03:41:25PM +0200, marcandre.lureau at redhat.com wrote:
>> From: Marc-André Lureau <marcandre.lureau at redhat.com>
>>
>> If the "org.qemu.monitor.qmp.0" port is available:
>> - enable the VM UI
>> - get and follow the VM state
>> - send the requested VM actions
>>
>> This adds a json-glib dependency on spice-gtk display. If necessary,
>> we could make it optional.
>
> Having some trouble playing with this. Considering that the
> patches that I'm looking at [0] and this series diverge a bit,
> would you mind sending another version with some info on the
> cover letter in order to have this properly configured to test?
>
> [0] https://pagure.io/fork/elmarco/virt-viewer/branch/qemu-ui
>
> I've configured my fedora 28 vm running on QEMU Session to
> consider org.qemu.console.serial.0 and org.qemu.monitor.qmp.0 as
> spice ports (like webdav) eg:
>
> <channel type='spiceport'>
> <source channel='org.qemu.monitor.qmp.0'/>
> <target type='virtio' name='org.qemu.monitor.qmp.0'/>
> <address type='virtio-serial' controller='0' bus='0' port='4'/>
> </channel>
>
> I see the debug of those channels in spice-gtk but nothing on
> virt-viewer. I'm thinking that some other configuration or
> initialization steps are needed to interact with monitor/serial.
>
Try with my qemu branch https://github.com/elmarco/qemu spice
If you start with -display app (with remote-viewer installed in your
prefix, and mime db correctly updated to deal with spice+unix://), it
will do the serial & monitor setup for you.
> Let me know if you need more info from my side.
>
> Also, feel free to push the acked patches as they don't overlap
> with these, I think.
>
> Cheers,
>
>> Signed-off-by: Marc-André Lureau <marcandre.lureau at redhat.com>
>> ---
>> configure.ac | 8 +-
>> src/Makefile.am | 2 +
>> src/virt-viewer-session-spice.c | 166 ++++++++++++++++++++++++++++++++
>> 3 files changed, 175 insertions(+), 1 deletion(-)
>>
>> diff --git a/configure.ac b/configure.ac
>> index 3c7522d..c5aa0dc 100644
>> --- a/configure.ac
>> +++ b/configure.ac
>> @@ -27,6 +27,7 @@ GTK_VNC_REQUIRED="0.4.0"
>> SPICE_GTK_REQUIRED="0.35"
>> SPICE_PROTOCOL_REQUIRED="0.12.7"
>> GOVIRT_REQUIRED="0.3.2"
>> +JSON_GLIB_REQUIRED="1.0"
>>
>> AC_SUBST([GLIB2_REQUIRED])
>> AC_SUBST([LIBXML2_REQUIRED])
>> @@ -37,6 +38,7 @@ AC_SUBST([GTK_VNC_REQUIRED])
>> AC_SUBST([SPICE_GTK_REQUIRED])
>> AC_SUBST([SPICE_PROTOCOL_REQUIRED])
>> AC_SUBST([GOVIRT_REQUIRED])
>> +AC_SUBST([JSON_GLIB_REQUIRED])
>>
>> AC_MSG_CHECKING([for native Win32])
>> case "$host_os" in
>> @@ -159,13 +161,15 @@ AC_ARG_WITH([spice-gtk],
>> AS_IF([test "x$with_spice_gtk" != "xno" && test "x$with_spice_gtk" != "xyes"],
>> [PKG_CHECK_EXISTS([spice-client-gtk-3.0 >= $SPICE_GTK_REQUIRED
>> spice-client-glib-2.0 >= $SPICE_GTK_REQUIRED
>> - spice-protocol >= $SPICE_PROTOCOL_REQUIRED],
>> + spice-protocol >= $SPICE_PROTOCOL_REQUIRED
>> + json-glib-1.0 >= $JSON_GLIB_REQUIRED],
>> [with_spice_gtk=yes], [with_spice_gtk=no])])
>>
>> AS_IF([test "x$with_spice_gtk" = "xyes"],
>> [PKG_CHECK_MODULES(SPICE_GTK, [spice-client-gtk-3.0 >= $SPICE_GTK_REQUIRED
>> spice-client-glib-2.0 >= $SPICE_GTK_REQUIRED])]
>> [PKG_CHECK_MODULES(SPICE_PROTOCOL, [spice-protocol >= $SPICE_PROTOCOL_REQUIRED])]
>> + [PKG_CHECK_MODULES(JSON_GLIB, [json-glib-1.0 >= JSON_GLIB_REQUIRED])]
>> [AC_DEFINE([HAVE_SPICE_GTK], 1, [Have spice-gtk?])]
>> )
>> AM_CONDITIONAL([HAVE_SPICE_GTK], [test "x$with_spice_gtk" = "xyes"])
>> @@ -294,3 +298,5 @@ AC_MSG_NOTICE([ LIBVIRT: $LIBVIRT_CFLAGS $LIBVIRT_LIBS])
>> AC_MSG_NOTICE([])
>> AC_MSG_NOTICE([ OVIRT: $OVIRT_CFLAGS $OVIRT_LIBS])
>> AC_MSG_NOTICE([])
>> +AC_MSG_NOTICE([ JSON_GLIB: $JSON_GLIB_CFLAGS $JSON_GLIB_LIBS])
>> +AC_MSG_NOTICE([])
>> diff --git a/src/Makefile.am b/src/Makefile.am
>> index 3a5d90d..b2dc786 100644
>> --- a/src/Makefile.am
>> +++ b/src/Makefile.am
>> @@ -114,6 +114,7 @@ COMMON_LIBS = \
>> $(GTK_VNC_LIBS) \
>> $(VTE_LIBS) \
>> $(SPICE_GTK_LIBS) \
>> + $(JSON_GLIB_LIBS) \
>> $(LIBXML2_LIBS) \
>> $(OVIRT_LIBS) \
>> $(NULL)
>> @@ -125,6 +126,7 @@ COMMON_CFLAGS = \
>> $(GTK_CFLAGS) \
>> $(GTK_VNC_CFLAGS) \
>> $(VTE_CFLAGS) \
>> + $(JSON_GLIB_CFLAGS) \
>> $(SPICE_GTK_CFLAGS) \
>> $(LIBXML2_CFLAGS) \
>> $(OVIRT_CFLAGS) \
>> diff --git a/src/virt-viewer-session-spice.c b/src/virt-viewer-session-spice.c
>> index 1c622e6..39aae1d 100644
>> --- a/src/virt-viewer-session-spice.c
>> +++ b/src/virt-viewer-session-spice.c
>> @@ -27,6 +27,7 @@
>> #include <glib/gi18n.h>
>>
>> #include <spice-client-gtk.h>
>> +#include <json-glib/json-glib.h>
>>
>> #include <usb-device-widget.h>
>> #include "virt-viewer-file.h"
>> @@ -39,6 +40,8 @@
>>
>> G_DEFINE_TYPE (VirtViewerSessionSpice, virt_viewer_session_spice, VIRT_VIEWER_TYPE_SESSION)
>>
>> +typedef void (QMPCb)(VirtViewerSessionSpice *self, JsonNode *node);
>> +
>> struct _VirtViewerSessionSpicePrivate {
>> GtkWindow *main_window;
>> SpiceSession *session;
>> @@ -51,7 +54,11 @@ struct _VirtViewerSessionSpicePrivate {
>> guint pass_try;
>> gboolean did_auto_conf;
>> VirtViewerFileTransferDialog *file_transfer_dialog;
>> + SpicePortChannel *qmp;
>>
>> + GString *qmp_data;
>> + JsonParser *qmp_parser;
>> + GHashTable *qmp_cb;
>> };
>>
>> #define VIRT_VIEWER_SESSION_SPICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), VIRT_VIEWER_TYPE_SESSION_SPICE, VirtViewerSessionSpicePrivate))
>> @@ -80,6 +87,7 @@ static void virt_viewer_session_spice_smartcard_insert(VirtViewerSession *sessio
>> static void virt_viewer_session_spice_smartcard_remove(VirtViewerSession *session);
>> static gboolean virt_viewer_session_spice_fullscreen_auto_conf(VirtViewerSessionSpice *self);
>> static void virt_viewer_session_spice_apply_monitor_geometry(VirtViewerSession *self, GHashTable *monitors);
>> +static void virt_viewer_session_spice_vm_action(VirtViewerSession *self, gint action);
>>
>> static void virt_viewer_session_spice_clear_displays(VirtViewerSessionSpice *self)
>> {
>> @@ -236,6 +244,16 @@ virt_viewer_session_spice_constructed(GObject *obj)
>> G_OBJECT_CLASS(virt_viewer_session_spice_parent_class)->constructed(obj);
>> }
>>
>> +static void
>> +virt_viewer_session_spice_finalize(GObject *obj)
>> +{
>> + VirtViewerSessionSpice *self = VIRT_VIEWER_SESSION_SPICE(obj);
>> +
>> + g_string_free(self->priv->qmp_data, TRUE);
>> + g_object_unref(self->priv->qmp_parser);
>> + g_hash_table_unref(self->priv->qmp_cb);
>> +}
>> +
>> static void
>> virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass)
>> {
>> @@ -246,6 +264,7 @@ virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass)
>> oclass->set_property = virt_viewer_session_spice_set_property;
>> oclass->dispose = virt_viewer_session_spice_dispose;
>> oclass->constructed = virt_viewer_session_spice_constructed;
>> + oclass->finalize = virt_viewer_session_spice_finalize;
>>
>> dclass->close = virt_viewer_session_spice_close;
>> dclass->open_fd = virt_viewer_session_spice_open_fd;
>> @@ -259,6 +278,7 @@ virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass)
>> dclass->apply_monitor_geometry = virt_viewer_session_spice_apply_monitor_geometry;
>> dclass->can_share_folder = virt_viewer_session_spice_can_share_folder;
>> dclass->can_retry_auth = virt_viewer_session_spice_can_retry_auth;
>> + dclass->vm_action = virt_viewer_session_spice_vm_action;
>>
>> g_type_class_add_private(klass, sizeof(VirtViewerSessionSpicePrivate));
>>
>> @@ -287,6 +307,9 @@ static void
>> virt_viewer_session_spice_init(VirtViewerSessionSpice *self G_GNUC_UNUSED)
>> {
>> self->priv = VIRT_VIEWER_SESSION_SPICE_GET_PRIVATE(self);
>> + self->priv->qmp_data = g_string_sized_new(256);
>> + self->priv->qmp_parser = json_parser_new();
>> + self->priv->qmp_cb = g_hash_table_new(g_direct_hash, g_direct_equal);
>> }
>>
>> static void
>> @@ -998,6 +1021,134 @@ port_name_to_vte_name(const char *name)
>> return NULL;
>> }
>>
>> +static void
>> +spice_qmp_data(VirtViewerSessionSpice *self, gpointer data,
>> + int size G_GNUC_UNUSED,
>> + SpicePortChannel *port G_GNUC_UNUSED)
>> +{
>> + GString *qmp = self->priv->qmp_data;
>> + gchar *str, *crlf;
>> +
>> + g_string_append_len(qmp, data, size);
>> +
>> + str = qmp->str;
>> + while ((crlf = memmem(str, qmp->len - (str - qmp->str), "\r\n", 2))) {
>> + GError *err = NULL;
>> +
>> + *crlf = '\0';
>> + json_parser_load_from_data(self->priv->qmp_parser, str, crlf - str, &err);
>> + if (err) {
>> + g_warning("JSON parsing error: %s", err->message);
>> + g_error_free(err);
>> + } else {
>> + JsonObject *obj = json_node_get_object(json_parser_get_root(self->priv->qmp_parser));
>> + JsonNode *node;
>> + const gchar *event;
>> +
>> + if (json_object_get_member(obj, "QMP")) {
>> + g_debug("QMP greeting received");
>> + } else if ((node = json_object_get_member(obj, "return"))) {
>> + gint id = json_object_get_int_member(obj, "id");
>> + QMPCb *cb;
>> +
>> + g_debug("QMP return id:%d", id);
>> + if ((cb = g_hash_table_lookup(self->priv->qmp_cb, GINT_TO_POINTER(id)))) {
>> + g_hash_table_remove(self->priv->qmp_cb, GINT_TO_POINTER(id));
>> + cb(self, node);
>> + }
>> + } else if ((event = json_object_get_string_member(obj, "event"))) {
>> + g_debug("%s", event);
>> + if (g_str_equal(event, "STOP")) {
>> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)),
>> + "vm-running", FALSE, NULL);
>> + } else if (g_str_equal(event, "RESUME")) {
>> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)),
>> + "vm-running", TRUE, NULL);
>> + } else {
>> + g_debug("unhandled QMP event: %s", event);
>> + }
>> + } else {
>> + g_debug("unhandled JSON %s", str);
>> + }
>> + }
>> + str = crlf + 2;
>> + }
>> +
>> + g_string_erase(qmp, 0, str - qmp->str);
>> +}
>> +
>> +static void
>> +qmp_query_status_cb(VirtViewerSessionSpice *self, JsonNode *node)
>> +{
>> + const char *status = json_object_get_string_member(json_node_get_object(node), "status");
>> + gboolean running = TRUE;
>> +
>> + if (g_str_equal(status, "paused")) {
>> + running = FALSE;
>> + }
>> +
>> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)),
>> + "vm-running", running, NULL);
>> +}
>> +
>> +static void
>> +qmp(VirtViewerSessionSpice *self,
>> + const char *cmd, const gchar *args, QMPCb *cb)
>> +{
>> + GString *str = g_string_sized_new(256);
>> + gsize len;
>> + gchar *data;
>> + static gint id = 0;
>> +
>> + if (!self->priv->qmp)
>> + return;
>> +
>> + g_string_append_printf(str, "{ 'execute': '%s'", cmd);
>> + if (args)
>> + g_string_append_printf(str, ", 'arguments': { %s }", args);
>> + g_string_append_printf(str, ", 'id': %d", id);
>> + g_string_append(str, " }");
>> +
>> + if (cb)
>> + g_hash_table_insert(self->priv->qmp_cb, GINT_TO_POINTER(id), cb);
>> +
>> + id++;
>> + len = str->len;
>> + data = g_string_free(str, FALSE);
>> + spice_port_channel_write_async(self->priv->qmp, data, len,
>> + NULL, spice_port_write_finished,
>> + data);
>> +}
>> +
>> +static void
>> +virt_viewer_session_spice_vm_action(VirtViewerSession *sess, gint action)
>> +{
>> + VirtViewerSessionSpice *self = VIRT_VIEWER_SESSION_SPICE(sess);
>> + const gchar *cmd;
>> +
>> + switch (action) {
>> + case VIRT_VIEWER_SESSION_VM_ACTION_QUIT:
>> + cmd = "quit";
>> + break;
>> + case VIRT_VIEWER_SESSION_VM_ACTION_RESET:
>> + cmd = "system_reset";
>> + break;
>> + case VIRT_VIEWER_SESSION_VM_ACTION_POWER_DOWN:
>> + cmd = "system_powerdown";
>> + break;
>> + case VIRT_VIEWER_SESSION_VM_ACTION_PAUSE:
>> + cmd = "stop";
>> + break;
>> + case VIRT_VIEWER_SESSION_VM_ACTION_CONTINUE:
>> + cmd = "cont";
>> + break;
>> + default:
>> + g_return_if_reached();
>> + }
>> +
>> + qmp(self, cmd, NULL, NULL);
>> +}
>> +
>> static void
>> spice_port_opened(SpiceChannel *channel, GParamSpec *pspec G_GNUC_UNUSED,
>> VirtViewerSessionSpice *self)
>> @@ -1018,6 +1169,21 @@ spice_port_opened(SpiceChannel *channel, GParamSpec *pspec G_GNUC_UNUSED,
>> g_return_if_fail(name != NULL);
>> g_debug("port#%d %s: %s", id, name, opened ? "opened" : "closed");
>>
>> + if (g_str_equal(name, "org.qemu.monitor.qmp.0") && opened) {
>> + g_return_if_fail(!self->priv->qmp);
>> +
>> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)),
>> + "vm-ui", TRUE, NULL);
>> +
>> + self->priv->qmp = port;
>> + qmp(self, "qmp_capabilities", NULL, NULL);
>> + qmp(self, "query-status", NULL, qmp_query_status_cb);
>> +
>> + virt_viewer_signal_connect_object(port, "port-data",
>> + G_CALLBACK(spice_qmp_data), self, G_CONNECT_SWAPPED);
>> + goto end;
>> + }
>> +
>> vte = g_object_get_data(G_OBJECT(port), "virt-viewer-vte");
>> if (vte) {
>> if (opened)
>> --
>> 2.18.0.321.gffc6fa0e39
>>
>> _______________________________________________
>> virt-tools-list mailing list
>> virt-tools-list at redhat.com
>> https://www.redhat.com/mailman/listinfo/virt-tools-list
More information about the virt-tools-list
mailing list