<anthony.perard@citrix.com>
2011-Jun-06 13:26 UTC
[Xen-devel] [PATCH] libxl, Introduce a QMP client
From: Anthony PERARD <anthony.perard@citrix.com> QMP stands for QEMU Monitor Protocol and it is used to query information from QEMU or to control QEMU. This implementation will ask QEMU the list of chardevice and store the path to serial0 in xenstored. So we will be able to use xl console with QEMU upstream. In order to connect to the QMP server, a socket file is created in /var/run/xen/qmp-$(domid). Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> --- tools/libxl/Makefile | 4 + tools/libxl/libxl_dm.c | 5 + tools/libxl/libxl_qmp.c | 864 ++++++++++++++++++++++++++++++++++++++++++++++ tools/libxl/libxl_qmp.h | 25 ++ tools/libxl/xl_cmdimpl.c | 29 ++- 5 files changed, 926 insertions(+), 1 deletions(-) create mode 100644 tools/libxl/libxl_qmp.c create mode 100644 tools/libxl/libxl_qmp.h diff --git a/tools/libxl/Makefile b/tools/libxl/Makefile index 538cd16..7da5e59 100644 --- a/tools/libxl/Makefile +++ b/tools/libxl/Makefile @@ -32,6 +32,9 @@ endif LIBXL_OBJS-$(CONFIG_X86) += libxl_cpuid.o LIBXL_OBJS-$(CONFIG_IA64) += libxl_nocpuid.o +LIBXL_OBJS-y += libxl_qmp.o +LIBXL_LIBS += -lyajl + LIBXL_OBJS = flexarray.o libxl.o libxl_create.o libxl_dm.o libxl_pci.o \ libxl_dom.o libxl_exec.o libxl_xshelp.o libxl_device.o \ libxl_internal.o libxl_utils.o libxl_uuid.o $(LIBXL_OBJS-y) @@ -115,6 +118,7 @@ install: all $(INSTALL_DIR) $(DESTDIR)$(LIBDIR) $(INSTALL_DIR) $(DESTDIR)$(INCLUDEDIR) $(INSTALL_DIR) $(DESTDIR)$(BASH_COMPLETION_DIR) + $(INSTALL_DIR) $(DESTDIR)/var/run/xen $(INSTALL_PROG) xl $(DESTDIR)$(SBINDIR) $(INSTALL_PROG) libxenlight.so.$(MAJOR).$(MINOR) $(DESTDIR)$(LIBDIR) ln -sf libxenlight.so.$(MAJOR).$(MINOR) $(DESTDIR)$(LIBDIR)/libxenlight.so.$(MAJOR) diff --git a/tools/libxl/libxl_dm.c b/tools/libxl/libxl_dm.c index 47a51c8..a7ff750 100644 --- a/tools/libxl/libxl_dm.c +++ b/tools/libxl/libxl_dm.c @@ -245,6 +245,11 @@ static char ** libxl__build_device_model_args_new(libxl__gc *gc, flexarray_vappend(dm_args, dm, "-xen-domid", libxl__sprintf(gc, "%d", info->domid), NULL); + flexarray_append(dm_args, "-qmp"); + flexarray_append(dm_args, + libxl__sprintf(gc, "unix:/var/run/xen/qmp-%d,server,nowait", + info->domid)); + if (info->type == LIBXL_DOMAIN_TYPE_PV) { flexarray_append(dm_args, "-xen-attach"); } diff --git a/tools/libxl/libxl_qmp.c b/tools/libxl/libxl_qmp.c new file mode 100644 index 0000000..6cd1a74 --- /dev/null +++ b/tools/libxl/libxl_qmp.c @@ -0,0 +1,864 @@ +/* + * Copyright (C) 2011 Citrix Ltd. + * Author Anthony PERARD <anthony.perard@citrix.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +/* + * This file implement a client for QMP (QEMU Monitor Protocol). For the + * Specification, see in the QEMU repository. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/un.h> +#include <sys/queue.h> + +#include <yajl/yajl_parse.h> +#include <yajl/yajl_gen.h> + +#include "libxl_internal.h" +#include "flexarray.h" +#include "libxl_qmp.h" + +/* #define DEBUG_ANSWER */ +/* #define DEBUG_RECEIVE */ + +/* + * json_object types + */ + +typedef struct json_object json_object; +typedef struct json_map_node json_map_node; +typedef enum node_type node_type_e; + +enum node_type { + JSON_ERROR, + JSON_NULL, + JSON_BOOL, + JSON_INTEGER, + JSON_DOUBLE, + JSON_STRING, + JSON_MAP, + JSON_ARRAY +}; + +struct json_object { + node_type_e type; + union { + bool boolean; + long integer; + double floating; + const char *string; + /* List of json_object */ + flexarray_t *array; + /* List of json_map_node */ + flexarray_t *map; + } u; + json_object *parent; +}; + +struct json_map_node { + const char *map_key; + json_object *obj; +}; + +/* + * QMP types & constant + */ + +typedef int (*qmp_callback_t)(libxl_qmp_handler *qmp, const json_object *tree); + +typedef enum message_type { + QMP_ANY, + QMP_QMP, + QMP_RETURN, + QMP_ERROR, + QMP_EVENT +} message_type_e; + +struct { + const char *name; + message_type_e type; +} member_name_to_message_type[] = { + { "QMP", QMP_QMP }, + { "return", QMP_RETURN }, + { "error", QMP_ERROR }, + { "event", QMP_EVENT }, + { "", QMP_ANY }, +}; + +typedef struct callback_id_pair { + int id; + qmp_callback_t callback; + SIMPLEQ_ENTRY(callback_id_pair) next; +} callback_id_pair; + +struct libxl_qmp_handler { + struct sockaddr_un addr; + int qmp_fd; + + unsigned char *buffer; + yajl_handle hand; + + json_object *head; + json_object *current; + + libxl_ctx *ctx; + uint32_t domid; + + int last_id_used; + SIMPLEQ_HEAD(callback_list, callback_id_pair) callback_list; +#ifdef DEBUG_ANSWER + yajl_gen g; +#endif +}; + +static int qmp_send(libxl_qmp_handler *qmp, const char *cmd, qmp_callback_t callback); + +static const size_t QMP_RECEIVE_BUFFER_SIZE = 65536; + +/* + * json_object functions + */ + +static int json_object_append_to(libxl_qmp_handler *qmp, json_object *obj, json_object *dst) +{ + if (!dst) + return -1; + + switch (dst->type) { + case JSON_MAP: { + json_map_node *last; + + flexarray_get(dst->u.map, dst->u.map->count - 1, (void**)&last); + last->obj = obj; + break; + } + case JSON_ARRAY: + flexarray_append(dst->u.array, obj); + break; + default: + LIBXL__LOG(qmp->ctx, LIBXL__LOG_ERROR, + "error, try append an object to a %i\n", dst->type); + return -1; + } + + obj->parent = dst; + return 0; +} + +static void json_object_free(libxl_qmp_handler *qmp, json_object *obj) +{ + int index = 0; + + if (obj == NULL) + return; + switch (obj->type) { + case JSON_ERROR: + case JSON_NULL: + case JSON_BOOL: + case JSON_INTEGER: + case JSON_DOUBLE: + break; + case JSON_STRING: + free((void*)obj->u.string); + break; + case JSON_MAP: { + json_map_node *node = NULL; + + for (index = 0; index < obj->u.map->count; index++) { + if (flexarray_get(obj->u.map, index, (void**)&node) != 0) + break; + json_object_free(qmp, node->obj); + free((void*)node->map_key); + free(node); + node = NULL; + } + flexarray_free(obj->u.map); + break; + } + case JSON_ARRAY:{ + json_object *node = NULL; + break; + + for (index = 0; index < obj->u.array->count; index++) { + if (flexarray_get(obj->u.array, index, (void**)&node) != 0) + break; + json_object_free(qmp, node); + node = NULL; + } + flexarray_free(obj->u.array); + break; + } + } + free(obj); +} + +static inline const char *json_object_get_string(const json_object *o) +{ + if (o && o->type == JSON_STRING) { + return o->u.string; + } + return NULL; +} + +static const json_object *json_object_map_get(const char *key, const json_object *o) +{ + flexarray_t *maps = NULL; + int index = 0; + + if (o && o->type == JSON_MAP) { + json_map_node *node = NULL; + + maps = o->u.map; + for (index = 0; index < maps->count; index++) { + if (flexarray_get(maps, index, (void**)&node) != 0) + break; + if (strcmp(key, node->map_key) == 0) { + return node->obj; + } + } + } + return NULL; +} + +/* + * JSON callbacks + */ + +static int json_callback_null(void *opaque) +{ + libxl_qmp_handler *qmp = opaque; + json_object *obj; + +#ifdef DEBUG_ANSWER + yajl_gen_null(qmp->g); +#endif + + obj = calloc(1, sizeof (json_object)); + obj->type = JSON_NULL; + json_object_append_to(qmp, obj, qmp->current); + + return 1; +} + +static int json_callback_boolean(void *opaque, int boolean) +{ + libxl_qmp_handler *qmp = opaque; + json_object *obj; + +#ifdef DEBUG_ANSWER + yajl_gen_bool(qmp->g, boolean); +#endif + + obj = calloc(1, sizeof (json_object)); + obj->type = JSON_BOOL; + obj->u.boolean = boolean; + json_object_append_to(qmp, obj, qmp->current); + + return 1; +} + +static int json_callback_integer(void *opaque, long value) +{ + libxl_qmp_handler *qmp = opaque; + json_object *obj; + +#ifdef DEBUG_ANSWER + yajl_gen_integer(qmp->g, value); +#endif + + obj = calloc(1, sizeof (json_object)); + obj->type = JSON_INTEGER; + obj->u.integer = value; + json_object_append_to(qmp, obj, qmp->current); + + return 1; +} + +static int json_callback_double(void *opaque, double value) +{ + libxl_qmp_handler *qmp = opaque; + json_object *obj; + +#ifdef DEBUG_ANSWER + yajl_gen_double(qmp->g, value); +#endif + + obj = calloc(1, sizeof (json_object)); + obj->type = JSON_DOUBLE; + obj->u.floating = value; + json_object_append_to(qmp, obj, qmp->current); + + return 1; +} + +static int json_callback_string(void *opaque, const unsigned char *str, + unsigned int len) +{ + libxl_qmp_handler *qmp = opaque; + char *t = malloc(len + 1); + json_object *obj = NULL; + +#ifdef DEBUG_ANSWER + yajl_gen_string(qmp->g, str, len); +#endif + + strncpy(t, (const char *) str, len); + t[len] = 0; + + obj = calloc(1, sizeof (json_object)); + obj->type = JSON_STRING; + obj->u.string = t; + + json_object_append_to(qmp, obj, qmp->current); + + return 1; +} + +static int json_callback_map_key(void *opaque, const unsigned char *str, + unsigned int len) +{ + libxl_qmp_handler *qmp = opaque; + char *t = malloc(len + 1); + json_object *obj = qmp->current; + +#ifdef DEBUG_ANSWER + yajl_gen_string(qmp->g, str, len); +#endif + + strncpy(t, (const char *) str, len); + t[len] = 0; + + if (obj->type == JSON_MAP) { + json_map_node *node = malloc(sizeof (json_map_node)); + + node->map_key = t; + node->obj = NULL; + + flexarray_append(obj->u.map, node); + } else { + return 0; + } + + return 1; +} + +static int json_callback_start_map(void *opaque) +{ + libxl_qmp_handler *qmp = opaque; + json_object *obj = NULL; + +#ifdef DEBUG_ANSWER + yajl_gen_map_open(qmp->g); +#endif + + obj = calloc(1, sizeof (json_object)); + if (qmp->head == NULL) { + qmp->head = obj; + } + + obj->type = JSON_MAP; + obj->u.map = flexarray_make(1, 1); + + json_object_append_to(qmp, obj, qmp->current); + + obj->parent = qmp->current; + qmp->current = obj; + + return 1; +} + +static int json_callback_end_map(void *opaque) +{ + libxl_qmp_handler *qmp = opaque; + +#ifdef DEBUG_ANSWER + yajl_gen_map_close(qmp->g); +#endif + + if (qmp->current) { + qmp->current = qmp->current->parent; + } else { + LIBXL__LOG(qmp->ctx, LIBXL__LOG_ERROR, "no parents for a json_object"); + return 0; + } + + return 1; +} + +static int json_callback_start_array(void *opaque) +{ + libxl_qmp_handler *qmp = opaque; + json_object *obj = NULL; + +#ifdef DEBUG_ANSWER + yajl_gen_array_open(qmp->g); +#endif + + obj = calloc(1, sizeof (json_object)); + if (qmp->head == NULL) { + qmp->head = obj; + } + obj->type = JSON_ARRAY; + obj->u.array = flexarray_make(1, 1); + json_object_append_to(qmp, obj, qmp->current); + qmp->current = obj; + + return 1; +} + +static int json_callback_end_array(void *opaque) +{ + libxl_qmp_handler *qmp = opaque; + +#ifdef DEBUG_ANSWER + yajl_gen_array_close(qmp->g); +#endif + + if (qmp->current) { + qmp->current = qmp->current->parent; + } else { + LIBXL__LOG(qmp->ctx, LIBXL__LOG_ERROR, "no parents for a json_object"); + return 0; + } + + return 1; +} + +static yajl_callbacks callbacks = { + json_callback_null, + json_callback_boolean, + json_callback_integer, + json_callback_double, + NULL, + json_callback_string, + json_callback_start_map, + json_callback_map_key, + json_callback_end_map, + json_callback_start_array, + json_callback_end_array +}; + +/* + * QMP callbacks functions + */ + +static const char *get_serial0_chardev(libxl_qmp_handler *qmp, const json_object *tree) +{ + const json_object *ret = NULL; + const json_object *obj = NULL; + const json_object *label = NULL; + const char *s = NULL; + flexarray_t *array = NULL; + int index = 0; + + ret = json_object_map_get("return", tree); + + if (!ret || ret->type != JSON_ARRAY) { + return NULL; + } + array = ret->u.array; + for (index = 0; index < array->count; index++) { + if (flexarray_get(array, index, (void**)&obj) != 0) + break; + label = json_object_map_get("label", obj); + s = json_object_get_string(label); + + /* TODO Could replace serial0 by serial and get all serial ttys, if sevral */ + if (s && strcmp("serial0", s) == 0) { + const json_object *filename = NULL; + filename = json_object_map_get("filename", obj); + return json_object_get_string(filename); + } + }; + + return NULL; +} + +static int register_serial0_chardev_callback(libxl_qmp_handler *qmp, const json_object *tree) +{ + libxl__gc gc = LIBXL_INIT_GC(qmp->ctx); + char *path = NULL; + const char *chardev = NULL; + int ret = 0; + + chardev = get_serial0_chardev(qmp, tree); + if (!(chardev && strncmp("pty:", chardev, 4) == 0)) { + return 1; + } + + path = libxl__xs_get_dompath(&gc, qmp->domid); + path = libxl__sprintf(&gc, "%s/serial/%d/tty", path, 0); + + LIBXL__LOG(qmp->ctx, LIBXL__LOG_DEBUG, + "qmp serial0: %s (%s)", chardev + 4, path); + ret = libxl__xs_write(&gc, XBT_NULL, path, "%s", chardev + 4); + libxl__free_all(&gc); + return ret; +} + +static int qmp_capabilities_callback(libxl_qmp_handler *qmp, const json_object *tree) +{ + /* The purpuse of this function is to ask to QEMU every information needed + * by xl. */ + int ret = 0; + + ret = qmp_send(qmp, "query-chardev", register_serial0_chardev_callback); + + return ret; +} + +/* + * QMP commands + */ + +static int enable_qmp_capabilities(libxl_qmp_handler *qmp) +{ + return qmp_send(qmp, "qmp_capabilities", qmp_capabilities_callback); +} + +/* + * Helpers + */ + +static message_type_e qmp_response_type(libxl_qmp_handler *qmp, json_object *resp) +{ + if (resp && resp->type == JSON_MAP) { + flexarray_t *maps = NULL; + json_map_node *node = NULL; + int index = 0; + + maps = resp->u.map; + for (index = 0; index < maps->count; index++) { + int i = 0; + if (flexarray_get(maps, index, (void**)&node) != 0) + break; + for (i = 0; member_name_to_message_type[i].type != QMP_ANY; i++) { + if (strcmp(member_name_to_message_type[i].name, node->map_key) == 0) { + return member_name_to_message_type[i].type; + } + } + } + } + + return QMP_ANY; +} + +static callback_id_pair *qmp_get_callback_from_id(libxl_qmp_handler *qmp, json_object *o) +{ + const json_object *id_object = json_object_map_get("id", o); + int id = -1; + callback_id_pair *pp = NULL; + + if (id_object && id_object->type == JSON_INTEGER) { + id = id_object->u.integer; + + SIMPLEQ_FOREACH(pp, &qmp->callback_list, next) { + if (pp->id == id) { + return pp; + } + } + } + return NULL; +} + +static void qmp_handle_error_response(libxl_qmp_handler *qmp, json_object *resp) +{ + callback_id_pair *pp = qmp_get_callback_from_id(qmp, resp); + const json_object *error = json_object_map_get("error", resp); + const char *msg = json_object_get_string(json_object_map_get("desc", error)); + + if (pp) { + SIMPLEQ_REMOVE(&qmp->callback_list, pp, callback_id_pair, next); + free(pp); + } + + LIBXL__LOG(qmp->ctx, LIBXL__LOG_ERROR, + "receive an error message from QMP server: %s", + msg); +} + +static int qmp_handle_response(libxl_qmp_handler *qmp, json_object *resp) +{ + message_type_e type = QMP_ANY; + + type = qmp_response_type(qmp, resp); + LIBXL__LOG(qmp->ctx, LIBXL__LOG_DEBUG, "message type: %i", type); + + switch (type) { + case QMP_QMP: + /* On the greeting message from the server, enable qmp capabilities */ + enable_qmp_capabilities(qmp); + break; + case QMP_RETURN: { + callback_id_pair *pp = qmp_get_callback_from_id(qmp, resp); + + if (pp) { + pp->callback(qmp, resp); + SIMPLEQ_REMOVE(&qmp->callback_list, pp, callback_id_pair, next); + free(pp); + } + break; + } + case QMP_ERROR: + qmp_handle_error_response(qmp, resp); + break; + case QMP_EVENT: + break; + case QMP_ANY: + return -1; + } + return 0; +} + +/* + * Handler functions + */ + +static int qmp_connect(libxl_qmp_handler *qmp, const char *qmp_socket_path, + int timeout) +{ + int ret; + int i = 0; + + qmp->qmp_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (qmp->qmp_fd < 0) { + return -1; + } + + memset(&qmp->addr, 0, sizeof (&qmp->addr)); + qmp->addr.sun_family = AF_UNIX; + strncpy(qmp->addr.sun_path, qmp_socket_path, sizeof (qmp->addr.sun_path)); + + do { + ret = connect(qmp->qmp_fd, (struct sockaddr *) &qmp->addr, + sizeof (qmp->addr)); + if (ret == 0) + break; + if (errno == ENOENT || errno == ECONNREFUSED) { + /* ENOENT : Socket may not have shown up yet + * ECONNREFUSED : Leftover socket hasn''t been removed yet */ + continue; + } + return -1; + } while ((++i <= timeout * 5) && (usleep(.2 * 1000000) <= 0)); + + if (ret == -1) + return -1; + + return 0; +} + +static void qmp_close(libxl_qmp_handler *qmp) +{ + callback_id_pair *pp = NULL; + callback_id_pair *tmp = NULL; +#ifdef DEBUG_ANSWER + if (qmp->g) + yajl_gen_free(qmp->g); +#endif + if (qmp->hand) + yajl_free(qmp->hand); + close(qmp->qmp_fd); + unlink(qmp->addr.sun_path); + SIMPLEQ_FOREACH(pp, &qmp->callback_list, next) { + if (tmp) + free(tmp); + tmp = pp; + } + if (tmp) + free(tmp); +} + +static int qmp_next(libxl_qmp_handler *qmp) +{ + yajl_status status; + ssize_t rd; + ssize_t bytes_parsed = 0; + + /* read the socket */ + rd = read(qmp->qmp_fd, qmp->buffer, QMP_RECEIVE_BUFFER_SIZE); + if (rd <= 0) { + /* either an error, or nothing */ + return rd; + } +#ifdef DEBUG_RECEIVE + qmp->buffer[rd] = 0; + LIBXL__LOG(qmp->ctx, LIBXL__LOG_DEBUG, "received: ''%s''", qmp->buffer); +#endif + + while (bytes_parsed < rd) { +#ifdef DEBUG_ANSWER + if (qmp->g == NULL) { + yajl_gen_config conf = { 1, " " }; + qmp->g = yajl_gen_alloc(&conf, NULL); + } +#endif + /* parse the input */ + if (qmp->hand == NULL) { + /* allow comments */ + yajl_parser_config cfg = { 1, 1 }; + qmp->hand = yajl_alloc(&callbacks, &cfg, NULL, qmp); + } + status = yajl_parse(qmp->hand, qmp->buffer + bytes_parsed, rd - bytes_parsed); + bytes_parsed += yajl_get_bytes_consumed(qmp->hand); + + /* handle the answer */ + if (status != yajl_status_ok && status != yajl_status_insufficient_data) { + unsigned char *str = yajl_get_error(qmp->hand, 1, qmp->buffer, rd); + + LIBXL__LOG(qmp->ctx, LIBXL__LOG_ERROR, "yajl error: %s", str); + yajl_free_error(qmp->hand, str); + } + + if (status == yajl_status_ok) { +#ifdef DEBUG_ANSWER + const unsigned char *buf = NULL; + unsigned int len = 0; + + yajl_gen_get_buf(qmp->g, &buf, &len); + LIBXL__LOG(qmp->ctx, LIBXL__LOG_DEBUG, "response:\n%s", buf); + yajl_gen_free(qmp->g); + qmp->g = NULL; +#endif + + qmp_handle_response(qmp, qmp->head); + + json_object_free(qmp, qmp->head); + qmp->head = NULL; + qmp->current = NULL; + + yajl_free(qmp->hand); + qmp->hand = NULL; + } + /* skip the CRLF of the end of a command */ + while (bytes_parsed < rd && (qmp->buffer[bytes_parsed] == ''\r'' + || qmp->buffer[bytes_parsed] == ''\n'')) { + bytes_parsed++; + } + } + return 1; +} + +static int qmp_send(libxl_qmp_handler *qmp, const char *cmd, qmp_callback_t callback) +{ + yajl_gen_config conf = { 0, NULL }; + const unsigned char *buf; + const char *ex = "execute"; + unsigned int len = 0; + yajl_gen_status s; + yajl_gen hand; + + hand = yajl_gen_alloc(&conf, NULL); + if (!hand) { + return -1; + } + + yajl_gen_map_open(hand); + yajl_gen_string(hand, (const unsigned char *)ex, strlen(ex)); + yajl_gen_string(hand, (const unsigned char *)cmd, strlen(cmd)); + yajl_gen_string(hand, (const unsigned char *)"id", 2); + yajl_gen_integer(hand, ++qmp->last_id_used); + yajl_gen_map_close(hand); + + s = yajl_gen_get_buf(hand, &buf, &len); + + if (s) { + LIBXL__LOG(qmp->ctx, LIBXL__LOG_ERROR, + "could not generate a qmp command"); + return -1; + } + + if (callback) { + callback_id_pair *elm = malloc(sizeof (callback_id_pair)); + elm->id = qmp->last_id_used; + elm->callback = callback; + SIMPLEQ_INSERT_TAIL(&qmp->callback_list, elm, next); + } + + LIBXL__LOG(qmp->ctx, LIBXL__LOG_DEBUG, "next qmp command: ''%s''", buf); + + write(qmp->qmp_fd, buf, len); + write(qmp->qmp_fd, "\r\n", 2); + yajl_gen_free(hand); + + return 0; +} + +/* + * API + */ + +libxl_qmp_handler *libxl_qmp_initialize(libxl_ctx *ctx, const char *qmp_socket, uint32_t domid) +{ + int ret = 0; + libxl_qmp_handler *qmp = NULL; + + qmp = calloc(1, sizeof (libxl_qmp_handler)); + qmp->ctx = ctx; + qmp->domid = domid; + if ((qmp->buffer = malloc(QMP_RECEIVE_BUFFER_SIZE)) == NULL) { + LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "Can not allocate the reception buffer"); + free(qmp); + return NULL; + } + + + if ((ret = qmp_connect(qmp, qmp_socket, 5)) < 0) { + LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "Connection error"); + free(qmp->buffer); + free(qmp); + return NULL; + } + + SIMPLEQ_INIT(&qmp->callback_list); + + LIBXL__LOG(qmp->ctx, LIBXL__LOG_DEBUG, "connected to %s", qmp_socket); + + return qmp; +} + +int libxl_qmp_get_fd(libxl_qmp_handler *qmp) +{ + if (qmp) + return qmp->qmp_fd; + else + return -1; +} + +int libxl_qmp_do_next(libxl_qmp_handler *qmp) +{ + int ret; + + if (!qmp) + return -1; + ret = qmp_next(qmp); + if (ret < 0) { + LIBXL__LOG_ERRNO(qmp->ctx, LIBXL__LOG_ERROR, "QMP, read error"); + } + + return ret; +} + +void libxl_qmp_close(libxl_qmp_handler *qmp) +{ + if (!qmp) + return; + qmp_close(qmp); + free(qmp->buffer); + free(qmp); +} diff --git a/tools/libxl/libxl_qmp.h b/tools/libxl/libxl_qmp.h new file mode 100644 index 0000000..efac9b8 --- /dev/null +++ b/tools/libxl/libxl_qmp.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 Citrix Ltd. + * Author Anthony PERARD <anthony.perard@citrix.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; version 2.1 only. with the special + * exception on linking described in file LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +#include "libxl.h" + +typedef struct libxl_qmp_handler libxl_qmp_handler; + +libxl_qmp_handler *libxl_qmp_initialize(libxl_ctx *ctx, + const char *qmp_socket, + uint32_t domid); +int libxl_qmp_get_fd(libxl_qmp_handler *qmp); +int libxl_qmp_do_next(libxl_qmp_handler *qmp); +void libxl_qmp_close(libxl_qmp_handler *qmp); diff --git a/tools/libxl/xl_cmdimpl.c b/tools/libxl/xl_cmdimpl.c index 5415bc5..6df56f6 100644 --- a/tools/libxl/xl_cmdimpl.c +++ b/tools/libxl/xl_cmdimpl.c @@ -39,6 +39,7 @@ #include "libxl.h" #include "libxl_utils.h" #include "libxlutil.h" +#include "libxl_qmp.h" #include "xl.h" #define CHK_ERRNO( call ) ({ \ @@ -1367,6 +1368,9 @@ static int create_domain(struct domain_create *dom_info) pid_t child_console_pid = -1; struct save_file_header hdr; + libxl_qmp_handler *qmp_handler = NULL; + int qmp_fd = -1; + memset(&d_config, 0x00, sizeof(d_config)); if (restore_file) { @@ -1606,6 +1610,19 @@ start: libxl_wait_for_disk_ejects(ctx, domid, d_config.disks, d_config.num_disks, w1); libxl_wait_for_domain_death(ctx, domid, w2); libxl_get_wait_fd(ctx, &fd); + if (d_config.dm_info.device_model_version == LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN) { + char socket[40]; + snprintf(socket, sizeof (socket), "/var/run/xen/qmp-%d", domid); + LOG("Try initialize QMP connection, path: %s", socket); + qmp_handler = libxl_qmp_initialize(ctx, socket, domid); + if (qmp_handler) { + qmp_fd = libxl_qmp_get_fd(qmp_handler); + } else { + LOG("Could not connect to the QMP socket."); + qmp_fd = -1; + } + } + while (1) { int ret; fd_set rfds; @@ -1615,10 +1632,12 @@ start: FD_ZERO(&rfds); FD_SET(fd, &rfds); + FD_SET(qmp_fd, &rfds); - ret = select(fd + 1, &rfds, NULL, NULL, NULL); + ret = select(fd > qmp_fd ? fd + 1 : qmp_fd + 1, &rfds, NULL, NULL, NULL); if (!ret) continue; + if (FD_ISSET(fd, &rfds)) { libxl_get_event(ctx, &event); switch (event.type) { case LIBXL_EVENT_TYPE_DOMAIN_DEATH: @@ -1687,6 +1706,10 @@ start: break; } libxl_free_event(&event); + } + if (FD_ISSET(qmp_fd, &rfds)) { + libxl_qmp_do_next(qmp_handler); + } } error_out: @@ -1697,6 +1720,10 @@ error_out: out: if (logfile != 2) close(logfile); + if (qmp_handler) { + libxl_qmp_close(qmp_handler); + qmp_handler = NULL; + } libxl_domain_config_destroy(&d_config); -- 1.7.2.5 _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
On Mon, 2011-06-06 at 14:26 +0100, Anthony Perard wrote:> From: Anthony PERARD <anthony.perard@citrix.com> > > QMP stands for QEMU Monitor Protocol and it is used to query information > from QEMU or to control QEMU. > > This implementation will ask QEMU the list of chardevice and store the > path to serial0 in xenstored. So we will be able to use xl console with > QEMU upstream. > > In order to connect to the QMP server, a socket file is created in > /var/run/xen/qmp-$(domid). > > Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>I didn''t yet review this in detail but I have a few initial questions/thoughts. Am I correct that QMP is intended to provide a stable interface going forwards? Or are we tying ourselves to a particular qemu version and/or will we need version specific bits (and associated configuration stuff) in the future? I think we should try where possible to keep this stuff entirely within libxl. The existing libxl event API is a bit of a mess but I think if it were cleaned up (IanJ has a plan I think) then it would be the right place to integrate the libxl and caller event loops. For the time being though I think libxl should provide the fd and not expect the caller to construct the path and open it etc. IOW libxl_qmp_initialize should not take a socket option, it should construct the path, do the open internally and return the fd.> - ret = select(fd + 1, &rfds, NULL, NULL, NULL); > + ret = select(fd > qmp_fd ? fd + 1 : qmp_fd + 1, &rfds, NULL, NULL, NULL); > if (!ret) > continue; > + if (FD_ISSET(fd, &rfds)) { > libxl_get_event(ctx, &event); > switch (event.type) { > case LIBXL_EVENT_TYPE_DOMAIN_DEATH: > @@ -1687,6 +1706,10 @@ start: > break; > } > libxl_free_event(&event); > + } > + if (FD_ISSET(qmp_fd, &rfds)) { > + libxl_qmp_do_next(qmp_handler); > + }Looks like some re-indentation is needed in these two hunks?> } > > error_out: > @@ -1697,6 +1720,10 @@ error_out: > out: > if (logfile != 2) > close(logfile); > + if (qmp_handler) { > + libxl_qmp_close(qmp_handler); > + qmp_handler = NULL; > + }Ian. _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Anthony PERARD
2011-Jun-06 16:15 UTC
Re: [Xen-devel] Re: [PATCH] libxl, Introduce a QMP client
On Mon, Jun 6, 2011 at 16:45, Ian Campbell <Ian.Campbell@eu.citrix.com> wrote:> 2011-06-06 at 14:26 +0100, Anthony Perard wrote: >> From: Anthony PERARD <anthony.perard@citrix.com> >> >> QMP stands for QEMU Monitor Protocol and it is used to query information >> from QEMU or to control QEMU. >> >> This implementation will ask QEMU the list of chardevice and store the >> path to serial0 in xenstored. So we will be able to use xl console with >> QEMU upstream. >> >> In order to connect to the QMP server, a socket file is created in >> /var/run/xen/qmp-$(domid). >> >> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com> > > I didn''t yet review this in detail but I have a few initial > questions/thoughts. > > Am I correct that QMP is intended to provide a stable interface going > forwards? Or are we tying ourselves to a particular qemu version and/or > will we need version specific bits (and associated configuration stuff) > in the future?QMP is intended to provide a stable interface. Once a command have been shipped, it should not been modified.> I think we should try where possible to keep this stuff entirely within > libxl. The existing libxl event API is a bit of a mess but I think if it > were cleaned up (IanJ has a plan I think) then it would be the right > place to integrate the libxl and caller event loops. > > For the time being though I think libxl should provide the fd and not > expect the caller to construct the path and open it etc. IOW > libxl_qmp_initialize should not take a socket option, it should > construct the path, do the open internally and return the fd.OK, I will do that, with a #define of the path somewhere.>> - ret = select(fd + 1, &rfds, NULL, NULL, NULL); >> + ret = select(fd > qmp_fd ? fd + 1 : qmp_fd + 1, &rfds, NULL, NULL, NULL); >> if (!ret) >> continue; >> + if (FD_ISSET(fd, &rfds)) { >> libxl_get_event(ctx, &event); >> switch (event.type) { >> case LIBXL_EVENT_TYPE_DOMAIN_DEATH: >> @@ -1687,6 +1706,10 @@ start: >> break; >> } >> libxl_free_event(&event); >> + } >> + if (FD_ISSET(qmp_fd, &rfds)) { >> + libxl_qmp_do_next(qmp_handler); >> + } > > Looks like some re-indentation is needed in these two hunks?Oops, indeed, I forget to change that. Thanks, -- Anthony PERARD _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Stefano Stabellini
2011-Jun-06 18:31 UTC
[Xen-devel] Re: [PATCH] libxl, Introduce a QMP client
On Mon, 6 Jun 2011, Ian Campbell wrote:> I think we should try where possible to keep this stuff entirely within > libxl. The existing libxl event API is a bit of a mess but I think if it > were cleaned up (IanJ has a plan I think) then it would be the right > place to integrate the libxl and caller event loops. > > For the time being though I think libxl should provide the fd and not > expect the caller to construct the path and open it etc. IOW > libxl_qmp_initialize should not take a socket option, it should > construct the path, do the open internally and return the fd.I agree on this. Libxl needs to use QMP internally for things like the serial. Libxl cannot rely on the caller (xl) to select on the fd and call libxl_qmp_do_next later for libxl to put the appropriate serial device on xenstore. Ideally QMP should be completely hidden inside libxl. I think all the initialization details should be handled internally by libxl_domain_create_new, including opening the QMP connection and reading back the serial device. After that libxl should probably expose a single event driven mechanism, both for xenstore and QMP, with some opaque callbacks in the QMP case. However this could be done together with the libxl events refactoring that Ian wants to do. _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
On Mon, 2011-06-06 at 19:31 +0100, Stefano Stabellini wrote:> On Mon, 6 Jun 2011, Ian Campbell wrote: > > I think we should try where possible to keep this stuff entirely within > > libxl. The existing libxl event API is a bit of a mess but I think if it > > were cleaned up (IanJ has a plan I think) then it would be the right > > place to integrate the libxl and caller event loops. > > > > For the time being though I think libxl should provide the fd and not > > expect the caller to construct the path and open it etc. IOW > > libxl_qmp_initialize should not take a socket option, it should > > construct the path, do the open internally and return the fd. > > I agree on this. > > Libxl needs to use QMP internally for things like the serial. Libxl > cannot rely on the caller (xl) to select on the fd and call > libxl_qmp_do_next later for libxl to put the appropriate serial device > on xenstore. > Ideally QMP should be completely hidden inside libxl. > > I think all the initialization details should be handled internally by > libxl_domain_create_new, including opening the QMP connection and > reading back the serial device.Yes I think it would be better to have libxl open a short lived QMP channel for specific operations entirely internally (including closing it again). If we don''t do this then we need to be mindful of multithreaded users of the library multiplexing over a single channel and all the inherent complexity of matching replies to requests, blocking the caller threads, handling async notifications while a request is in progress etc etc. Probably we will also need a long-running channel dedicated to feeding out into the user''s event loop to handle async notifications etc. How does QMP handle the async notifications with multiple connected clients? I suppose they must all see them (or else writing a client would be virtually impossible), in which case the function-specific connections can simply discard them.> After that libxl should probably expose a single event driven mechanism, > both for xenstore and QMP, with some opaque callbacks in the QMP case. > However this could be done together with the libxl events refactoring > that Ian wants to do._______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Anthony PERARD
2011-Jun-07 11:49 UTC
Re: [Xen-devel] Re: [PATCH] libxl, Introduce a QMP client
On Tue, Jun 7, 2011 at 09:58, Ian Campbell <Ian.Campbell@eu.citrix.com> wrote:> On Mon, 2011-06-06 at 19:31 +0100, Stefano Stabellini wrote: >> On Mon, 6 Jun 2011, Ian Campbell wrote: >> > I think we should try where possible to keep this stuff entirely within >> > libxl. The existing libxl event API is a bit of a mess but I think if it >> > were cleaned up (IanJ has a plan I think) then it would be the right >> > place to integrate the libxl and caller event loops. >> > >> > For the time being though I think libxl should provide the fd and not >> > expect the caller to construct the path and open it etc. IOW >> > libxl_qmp_initialize should not take a socket option, it should >> > construct the path, do the open internally and return the fd. >> >> I agree on this. >> >> Libxl needs to use QMP internally for things like the serial. Libxl >> cannot rely on the caller (xl) to select on the fd and call >> libxl_qmp_do_next later for libxl to put the appropriate serial device >> on xenstore. >> Ideally QMP should be completely hidden inside libxl. >> >> I think all the initialization details should be handled internally by >> libxl_domain_create_new, including opening the QMP connection and >> reading back the serial device. > > Yes I think it would be better to have libxl open a short lived QMP > channel for specific operations entirely internally (including closing > it again).Ok, I will change that.> If we don''t do this then we need to be mindful of multithreaded users of > the library multiplexing over a single channel and all the inherent > complexity of matching replies to requests, blocking the caller threads, > handling async notifications while a request is in progress etc etc. > > Probably we will also need a long-running channel dedicated to feeding > out into the user''s event loop to handle async notifications etc. > > How does QMP handle the async notifications with multiple connected > clients? I suppose they must all see them (or else writing a client > would be virtually impossible), in which case the function-specific > connections can simply discard them.Actually, QEMU doesn''t seem to handle more than one client at a time with a single socket. For more client, we can always open more than one QMP server with different path/port. In this case, they will be handle separately by QEMU. To handle the async request, it''s relatively easy. When we send a command, we can add an "id" to this request and the id will be part of the answer. I use that to handle the replies. For the QEMU event/async notification, indeed, all clients receive them. -- Anthony PERARD _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Stefano Stabellini
2011-Jun-07 12:05 UTC
Re: [Xen-devel] Re: [PATCH] libxl, Introduce a QMP client
On Tue, 7 Jun 2011, Anthony PERARD wrote:> On Tue, Jun 7, 2011 at 09:58, Ian Campbell <Ian.Campbell@eu.citrix.com> wrote: > > On Mon, 2011-06-06 at 19:31 +0100, Stefano Stabellini wrote: > >> On Mon, 6 Jun 2011, Ian Campbell wrote: > >> > I think we should try where possible to keep this stuff entirely within > >> > libxl. The existing libxl event API is a bit of a mess but I think if it > >> > were cleaned up (IanJ has a plan I think) then it would be the right > >> > place to integrate the libxl and caller event loops. > >> > > >> > For the time being though I think libxl should provide the fd and not > >> > expect the caller to construct the path and open it etc. IOW > >> > libxl_qmp_initialize should not take a socket option, it should > >> > construct the path, do the open internally and return the fd. > >> > >> I agree on this. > >> > >> Libxl needs to use QMP internally for things like the serial. Libxl > >> cannot rely on the caller (xl) to select on the fd and call > >> libxl_qmp_do_next later for libxl to put the appropriate serial device > >> on xenstore. > >> Ideally QMP should be completely hidden inside libxl. > >> > >> I think all the initialization details should be handled internally by > >> libxl_domain_create_new, including opening the QMP connection and > >> reading back the serial device. > > > > Yes I think it would be better to have libxl open a short lived QMP > > channel for specific operations entirely internally (including closing > > it again). > > Ok, I will change that. > > > If we don''t do this then we need to be mindful of multithreaded users of > > the library multiplexing over a single channel and all the inherent > > complexity of matching replies to requests, blocking the caller threads, > > handling async notifications while a request is in progress etc etc. > > > > Probably we will also need a long-running channel dedicated to feeding > > out into the user''s event loop to handle async notifications etc. > > > > How does QMP handle the async notifications with multiple connected > > clients? I suppose they must all see them (or else writing a client > > would be virtually impossible), in which case the function-specific > > connections can simply discard them. > > Actually, QEMU doesn''t seem to handle more than one client at a time > with a single socket. For more client, we can always open more than > one QMP server with different path/port. In this case, they will be > handle separately by QEMU. > > To handle the async request, it''s relatively easy. When we send a > command, we can add an "id" to this request and the id will be part of > the answer. I use that to handle the replies. > > For the QEMU event/async notification, indeed, all clients receive them.That means that if we provide a single QMP server socket we cannot expose QMP to xl at all, because we have to keep the server socket free for libxl to use whenever the library sees fit. _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Ian Campbell
2011-Jun-07 14:30 UTC
Re: [Xen-devel] Re: [PATCH] libxl, Introduce a QMP client
On Tue, 2011-06-07 at 12:49 +0100, Anthony PERARD wrote:> On Tue, Jun 7, 2011 at 09:58, Ian Campbell <Ian.Campbell@eu.citrix.com> wrote: > > On Mon, 2011-06-06 at 19:31 +0100, Stefano Stabellini wrote: > >> On Mon, 6 Jun 2011, Ian Campbell wrote: > >> > I think we should try where possible to keep this stuff entirely within > >> > libxl. The existing libxl event API is a bit of a mess but I think if it > >> > were cleaned up (IanJ has a plan I think) then it would be the right > >> > place to integrate the libxl and caller event loops. > >> > > >> > For the time being though I think libxl should provide the fd and not > >> > expect the caller to construct the path and open it etc. IOW > >> > libxl_qmp_initialize should not take a socket option, it should > >> > construct the path, do the open internally and return the fd. > >> > >> I agree on this. > >> > >> Libxl needs to use QMP internally for things like the serial. Libxl > >> cannot rely on the caller (xl) to select on the fd and call > >> libxl_qmp_do_next later for libxl to put the appropriate serial device > >> on xenstore. > >> Ideally QMP should be completely hidden inside libxl. > >> > >> I think all the initialization details should be handled internally by > >> libxl_domain_create_new, including opening the QMP connection and > >> reading back the serial device. > > > > Yes I think it would be better to have libxl open a short lived QMP > > channel for specific operations entirely internally (including closing > > it again). > > Ok, I will change that. > > > If we don''t do this then we need to be mindful of multithreaded users of > > the library multiplexing over a single channel and all the inherent > > complexity of matching replies to requests, blocking the caller threads, > > handling async notifications while a request is in progress etc etc. > > > > Probably we will also need a long-running channel dedicated to feeding > > out into the user''s event loop to handle async notifications etc. > > > > How does QMP handle the async notifications with multiple connected > > clients? I suppose they must all see them (or else writing a client > > would be virtually impossible), in which case the function-specific > > connections can simply discard them. > > Actually, QEMU doesn''t seem to handle more than one client at a time > with a single socket.That seems like a pretty obvious short coming, do you know if it is a deliberate policy or just a case of not implemented yet?> For more client, we can always open more than > one QMP server with different path/port. In this case, they will be > handle separately by QEMU.Problem is determining the correct number to create when we start qemu.> To handle the async request, it''s relatively easy. When we send a > command, we can add an "id" to this request and the id will be part of > the answer. I use that to handle the replies.Most of the uses of QMP (at least within libxl) are likely to want to be synchronous though, e.g. libxl functions usually do not return to the user in a semi completed state and require a callback for completion. This means that we need a means to arrange to wait for a specific response, which in a multithreaded user may mean blocking threads until their response comes in and waking them etc, ensuring that there is one (and only one) thread actually handling the incoming requests to do the dispatch/handle async notifications etc. It''s not the end of the world but it does add complexity within the library and a dependency on a threading library which IIRC we currently manage to avoid...> For the QEMU event/async notification, indeed, all clients receive them. >_______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Anthony PERARD
2011-Jun-07 15:50 UTC
Re: [Xen-devel] Re: [PATCH] libxl, Introduce a QMP client
On Tue, Jun 7, 2011 at 15:30, Ian Campbell <Ian.Campbell@eu.citrix.com> wrote:>> Actually, QEMU doesn''t seem to handle more than one client at a time >> with a single socket. > > That seems like a pretty obvious short coming, do you know if it is a > deliberate policy or just a case of not implemented yet?I think it because they use the same "char device" code to handle by example the serial port or the monitor (including QMP). So when a client is connected, there just stop to handle the listenning fd until the client disconnects itself.>> For more client, we can always open more than >> one QMP server with different path/port. In this case, they will be >> handle separately by QEMU. > > Problem is determining the correct number to create when we start qemu.Well, a simple answer would be two, one to listen the event from QEMU, if we need to do that, and the second one would be for the commands, one at a time, and libxl close the socket. If a command socket is already in use, then another libxl client whose trying to connect to the same socket will wait until QEMU accept the connection. -- Anthony PERARD _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Stefano Stabellini
2011-Jun-07 16:17 UTC
Re: [Xen-devel] Re: [PATCH] libxl, Introduce a QMP client
On Tue, 7 Jun 2011, Anthony PERARD wrote:> On Tue, Jun 7, 2011 at 15:30, Ian Campbell <Ian.Campbell@eu.citrix.com> wrote: > >> Actually, QEMU doesn''t seem to handle more than one client at a time > >> with a single socket. > > > > That seems like a pretty obvious short coming, do you know if it is a > > deliberate policy or just a case of not implemented yet? > > I think it because they use the same "char device" code to handle by > example the serial port or the monitor (including QMP). So when a > client is connected, there just stop to handle the listenning fd until > the client disconnects itself. > > >> For more client, we can always open more than > >> one QMP server with different path/port. In this case, they will be > >> handle separately by QEMU. > > > > Problem is determining the correct number to create when we start qemu. > > Well, a simple answer would be two, one to listen the event from QEMU, > if we need to do that, and the second one would be for the commands, > one at a time, and libxl close the socket. If a command socket is > already in use, then another libxl client whose trying to connect to > the same socket will wait until QEMU accept the connection.Yeah, we could start by implementing only the "libxl commands" socket, leaving out the event socket for later. --8323329-1260772888-1307463476=:12963 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel --8323329-1260772888-1307463476=:12963--