From: "Daniel P. Berrange" <berrange(a)redhat.com>
To be able to test the QEMU monitor code, we need to have a fake
QEMU monitor server. This introduces a simple (dumb) framework
that can do this. The test case registers a series of items to
be sent back as replies to commands that will be executed. A
thread runs the event loop looking for incoming replies and
sending back this pre-registered data. This allows testing all
QEMU monitor code that deals with parsing responses and errors
from QEMU, without needing QEMU around
---
cfg.mk | 2 +-
tests/Makefile.am | 22 +-
tests/qemumonitortestutils.c | 499 +++++++++++++++++++++++++++++++++++++++++++
tests/qemumonitortestutils.h | 41 ++++
4 files changed, 560 insertions(+), 4 deletions(-)
create mode 100644 tests/qemumonitortestutils.c
create mode 100644 tests/qemumonitortestutils.h
diff --git a/cfg.mk b/cfg.mk
index d2e54e3..224f89f 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -741,7 +741,7 @@ exclude_file_name_regexp--sc_copyright_address = \
exclude_file_name_regexp--sc_flags_usage = ^(docs/|src/util/virnetdevtap\.c$$)
exclude_file_name_regexp--sc_libvirt_unmarked_diagnostics = \
- ^src/rpc/gendispatch\.pl$$
+ ^(src/rpc/gendispatch\.pl$$|tests/)
exclude_file_name_regexp--sc_po_check = ^(docs/|src/rpc/gendispatch\.pl$$)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 60d322d..22a7a26 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -215,12 +215,17 @@ endif
EXTRA_DIST += $(test_scripts)
+test_libraries = libshunload.la
+if WITH_QEMU
+test_libraries += libqemumonitortestutils.la
+endif
+
if WITH_TESTS
noinst_PROGRAMS = $(test_programs) $(test_helpers)
-noinst_LTLIBRARIES = libshunload.la
+noinst_LTLIBRARIES = $(test_libraries)
else
check_PROGRAMS = $(test_programs) $(test_helpers)
-check_LTLIBRARIES = libshunload.la
+check_LTLIBRARIES = $(test_libraries)
endif
TESTS = $(test_programs) \
@@ -294,8 +299,18 @@ EXTRA_DIST += xml2sexprtest.c sexpr2xmltest.c xmconfigtest.c \
testutilsxen.c testutilsxen.h
endif
+QEMUMONITORTESTUTILS_SOURCES = \
+ qemumonitortestutils.c \
+ qemumonitortestutils.h \
+ $(NULL)
+
if WITH_QEMU
+libqemumonitortestutils_la_SOURCES = $(QEMUMONITORTESTUTILS_SOURCES)
+libqemumonitortestutils_la_CFLAGS = \
+ -Dabs_builddir="\"`pwd`\"" $(AM_CFLAGS)
+
+
qemu_LDADDS = ../src/libvirt_driver_qemu_impl.la
if WITH_NETWORK
qemu_LDADDS += ../src/libvirt_driver_network_impl.la
@@ -338,7 +353,8 @@ domainsnapshotxml2xmltest_LDADD = $(qemu_LDADDS)
else
EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c qemuargv2xmltest.c \
qemuxmlnstest.c qemuhelptest.c domainsnapshotxml2xmltest.c \
- qemumonitortest.c testutilsqemu.c testutilsqemu.h
+ qemumonitortest.c testutilsqemu.c testutilsqemu.h \
+ $(QEMUMONITORTESTUTILS_SOURCES)
endif
if WITH_LXC
diff --git a/tests/qemumonitortestutils.c b/tests/qemumonitortestutils.c
new file mode 100644
index 0000000..76b11e6
--- /dev/null
+++ b/tests/qemumonitortestutils.c
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2011-2012 Red Hat, Inc.
+ *
+ * This library 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; If not, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "qemumonitortestutils.h"
+
+#include "threads.h"
+#include "qemu/qemu_monitor.h"
+#include "rpc/virnetsocket.h"
+#include "memory.h"
+#include "util.h"
+#include "logging.h"
+#include "virterror_internal.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+typedef struct _qemuMonitorTestItem qemuMonitorTestItem;
+typedef qemuMonitorTestItem *qemuMonitorTestItemPtr;
+
+struct _qemuMonitorTestItem {
+ char *command_name;
+ char *response;
+};
+
+struct _qemuMonitorTest {
+ virMutex lock;
+ virThread thread;
+
+ bool json;
+ bool quit;
+ bool running;
+
+ char *incoming;
+ size_t incomingLength;
+ size_t incomingCapacity;
+
+ char *outgoing;
+ size_t outgoingLength;
+ size_t outgoingCapacity;
+
+ virNetSocketPtr server;
+ virNetSocketPtr client;
+
+ qemuMonitorPtr mon;
+
+ size_t nitems;
+ qemuMonitorTestItemPtr *items;
+
+ virDomainObjPtr vm;
+};
+
+
+static void qemuMonitorTestItemFree(qemuMonitorTestItemPtr item);
+
+/*
+ * Appends data for a reply onto the outgoing buffer
+ */
+static int qemuMonitorTestAddReponse(qemuMonitorTestPtr test,
+ const char *response)
+{
+ size_t want = strlen(response) + 2;
+ size_t have = test->outgoingCapacity - test->outgoingLength;
+
+ if (have < want) {
+ size_t need = want - have;
+ if (VIR_EXPAND_N(test->outgoing, test->outgoingCapacity, need) < 0) {
+ virReportOOMError();
+ return -1;
+ }
+ }
+
+ want -= 2;
+ memcpy(test->outgoing + test->outgoingLength,
+ response,
+ want);
+ memcpy(test->outgoing + test->outgoingLength + want,
+ "\r\n",
+ 2);
+ test->outgoingLength += want + 2;
+ return 0;
+}
+
+
+/*
+ * Processes a single line, looking for a matching expected
+ * item to reply with, else replies with an error
+ */
+static int qemuMonitorTestProcessCommandJSON(qemuMonitorTestPtr test,
+ const char *cmdstr)
+{
+ virJSONValuePtr val;
+ const char *cmdname;
+ int ret = -1;
+
+ if (!(val = virJSONValueFromString(cmdstr)))
+ return -1;
+
+ if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "Missing command name in %s", cmdstr);
+ goto cleanup;
+ }
+
+ if (test->nitems == 0 ||
+ STRNEQ(test->items[0]->command_name, cmdname)) {
+ ret = qemuMonitorTestAddReponse(test,
+ "{ \"error\": "
+ " { \"desc\": \"Unexpected
command\", "
+ " \"class\":
\"UnexpectedCommand\" } }");
+ } else {
+ ret = qemuMonitorTestAddReponse(test,
+ test->items[0]->response);
+ qemuMonitorTestItemFree(test->items[0]);
+ if (test->nitems == 1) {
+ VIR_FREE(test->items);
+ test->nitems = 0;
+ } else {
+ memmove(test->items,
+ test->items + 1,
+ sizeof(test->items[0]) * (test->nitems - 1));
+ VIR_SHRINK_N(test->items, test->nitems, 1);
+ }
+ }
+
+cleanup:
+ virJSONValueFree(val);
+ return ret;
+}
+
+
+static int qemuMonitorTestProcessCommandText(qemuMonitorTestPtr test,
+ const char *cmdstr)
+{
+ char *tmp;
+ char *cmdname;
+ int ret = -1;
+
+ if (!(cmdname = strdup(cmdstr))) {
+ virReportOOMError();
+ return -1;
+ }
+ if (!(tmp = strchr(cmdname, ' '))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "Cannot find command name in '%s'", cmdstr);
+ goto cleanup;
+ }
+ *tmp = '\0';
+
+ if (test->nitems == 0 ||
+ STRNEQ(test->items[0]->command_name, cmdname)) {
+ ret = qemuMonitorTestAddReponse(test,
+ "unexpected command");
+ } else {
+ ret = qemuMonitorTestAddReponse(test,
+ test->items[0]->response);
+ qemuMonitorTestItemFree(test->items[0]);
+ if (test->nitems == 1) {
+ VIR_FREE(test->items);
+ test->nitems = 0;
+ } else {
+ memmove(test->items,
+ test->items + 1,
+ sizeof(test->items[0]) * (test->nitems - 1));
+ VIR_SHRINK_N(test->items, test->nitems, 1);
+ }
+ }
+
+cleanup:
+ VIR_FREE(cmdname);
+ return ret;
+}
+
+static int qemuMonitorTestProcessCommand(qemuMonitorTestPtr test,
+ const char *cmdstr)
+{
+ if (test->json)
+ return qemuMonitorTestProcessCommandJSON(test ,cmdstr);
+ else
+ return qemuMonitorTestProcessCommandText(test ,cmdstr);
+}
+
+/*
+ * Handles read/write of monitor data on the monitor server side
+ */
+static void qemuMonitorTestIO(virNetSocketPtr sock,
+ int events,
+ void *opaque)
+{
+ qemuMonitorTestPtr test = opaque;
+ bool err = false;
+
+ virMutexLock(&test->lock);
+ if (events & VIR_EVENT_HANDLE_WRITABLE) {
+ ssize_t ret;
+ if ((ret = virNetSocketWrite(sock,
+ test->outgoing,
+ test->outgoingLength)) < 0) {
+ err = true;
+ goto cleanup;
+ }
+
+ memmove(test->outgoing,
+ test->outgoing + ret,
+ test->outgoingLength - ret);
+ test->outgoingLength -= ret;
+
+ if ((test->outgoingCapacity - test->outgoingLength) > 1024)
+ VIR_SHRINK_N(test->outgoing, test->outgoingCapacity, 1024);
+ }
+
+ if (events & VIR_EVENT_HANDLE_READABLE) {
+ ssize_t ret, used;
+ char *t1, *t2;
+
+ if ((test->incomingCapacity - test->incomingLength) < 1024) {
+ if (VIR_EXPAND_N(test->incoming, test->incomingCapacity, 1024) < 0)
{
+ err = true;
+ goto cleanup;
+ }
+ }
+
+ if ((ret = virNetSocketRead(sock,
+ test->incoming + test->incomingLength,
+ (test->incomingCapacity - test->incomingLength)
- 1)) < 0) {
+ err = true;
+ goto cleanup;
+ }
+ test->incomingLength += ret;
+ test->incoming[test->incomingLength] = '\0';
+
+ /* Look to see if we've got a complete line, and
+ * if so, handle that command
+ */
+ t1 = test->incoming;
+ while ((t2 = strstr(t1, "\r\n"))) {
+ *t2 = '\0';
+
+ if (qemuMonitorTestProcessCommand(test, t1) < 0) {
+ err = true;
+ goto cleanup;
+ }
+
+ t1 = t2 + 2;
+ }
+ used = t1 - test->incoming;
+ memmove(test->incoming, t1, test->incomingLength - used);
+ test->incomingLength -= used;
+ if ((test->incomingCapacity - test->incomingLength) > 1024) {
+ VIR_SHRINK_N(test->incoming,
+ test->incomingCapacity,
+ 1024);
+ }
+ }
+
+ if (events & (VIR_EVENT_HANDLE_HANGUP |
+ VIR_EVENT_HANDLE_ERROR))
+ err = true;
+
+cleanup:
+ if (err) {
+ virNetSocketRemoveIOCallback(sock);
+ virNetSocketClose(sock);
+ virObjectUnref(test->client);
+ test->client = NULL;
+ } else {
+ events = VIR_EVENT_HANDLE_READABLE;
+
+ if (test->outgoingLength)
+ events |= VIR_EVENT_HANDLE_WRITABLE;
+
+ virNetSocketUpdateIOCallback(sock, events);
+ }
+ virMutexUnlock(&test->lock);
+}
+
+
+static void qemuMonitorTestWorker(void *opaque)
+{
+ qemuMonitorTestPtr test = opaque;
+
+ virMutexLock(&test->lock);
+
+ while (!test->quit) {
+ virMutexUnlock(&test->lock);
+
+ if (virEventRunDefaultImpl() < 0) {
+ test->quit = true;
+ break;
+ }
+
+ virMutexLock(&test->lock);
+ }
+
+ test->running = false;
+
+ virMutexUnlock(&test->lock);
+ return;
+}
+
+static void qemuMonitorTestItemFree(qemuMonitorTestItemPtr item)
+{
+ if (!item)
+ return;
+
+ VIR_FREE(item->command_name);
+ VIR_FREE(item->response);
+
+ VIR_FREE(item);
+}
+
+
+void qemuMonitorTestFree(qemuMonitorTestPtr test)
+{
+ size_t i;
+
+ if (!test)
+ return;
+
+ virMutexLock(&test->lock);
+ if (test->running) {
+ test->quit = true;
+ }
+ virMutexUnlock(&test->lock);
+
+ if (test->client) {
+ virNetSocketRemoveIOCallback(test->client);
+ virNetSocketClose(test->client);
+ virObjectUnref(test->client);
+ }
+
+ virObjectUnref(test->server);
+ if (test->mon) {
+ qemuMonitorUnlock(test->mon);
+ qemuMonitorClose(test->mon);
+ }
+
+ virObjectUnref(test->vm);
+
+ if (test->running)
+ virThreadJoin(&test->thread);
+
+ for (i = 0 ; i < test->nitems ; i++)
+ qemuMonitorTestItemFree(test->items[i]);
+ VIR_FREE(test->items);
+
+ virMutexDestroy(&test->lock);
+ VIR_FREE(test);
+}
+
+
+int
+qemuMonitorTestAddItem(qemuMonitorTestPtr test,
+ const char *command_name,
+ const char *response)
+{
+ qemuMonitorTestItemPtr item;
+
+ if (VIR_ALLOC(item) < 0)
+ goto no_memory;
+
+ if (!(item->command_name = strdup(command_name)) ||
+ !(item->response = strdup(response)))
+ goto no_memory;
+
+ virMutexLock(&test->lock);
+ if (VIR_EXPAND_N(test->items, test->nitems, 1) < 0) {
+ virMutexUnlock(&test->lock);
+ goto no_memory;
+ }
+ test->items[test->nitems - 1] = item;
+
+ virMutexUnlock(&test->lock);
+
+ return 0;
+
+no_memory:
+ virReportOOMError();
+ qemuMonitorTestItemFree(item);
+ return -1;
+}
+
+
+static void qemuMonitorTestEOFNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
+ virDomainObjPtr vm ATTRIBUTE_UNUSED)
+{
+}
+
+static void qemuMonitorTestErrorNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
+ virDomainObjPtr vm ATTRIBUTE_UNUSED)
+{
+}
+
+
+static qemuMonitorCallbacks qemuCallbacks = {
+ .eofNotify = qemuMonitorTestEOFNotify,
+ .errorNotify = qemuMonitorTestErrorNotify,
+};
+
+qemuMonitorTestPtr qemuMonitorTestNew(bool json, virCapsPtr caps)
+{
+ qemuMonitorTestPtr test;
+ const char *path = abs_builddir "/qemumonitorjsontest.sock";
+ virDomainChrSourceDef src;
+
+ memset(&src, 0, sizeof(src));
+ src.type = VIR_DOMAIN_CHR_TYPE_UNIX;
+ src.data.nix.path = (char *)path;
+ src.data.nix.listen = false;
+
+ if (VIR_ALLOC(test) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ if (virMutexInit(&test->lock) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "Cannot initialize mutex");
+ VIR_FREE(test);
+ return NULL;
+ }
+
+ test->json = json;
+ if (!(test->vm = virDomainObjNew(caps)))
+ goto error;
+
+ if (virNetSocketNewListenUNIX(path,
+ 0700,
+ getuid(),
+ getgid(),
+ &test->server) < 0)
+ goto error;
+
+
+ if (virNetSocketListen(test->server, 1) < 0)
+ goto error;
+
+ if (!(test->mon = qemuMonitorOpen(test->vm,
+ &src,
+ true,
+ &qemuCallbacks)))
+ goto error;
+ qemuMonitorLock(test->mon);
+
+ if (virNetSocketAccept(test->server, &test->client) < 0)
+ goto error;
+ if (!test->client)
+ goto error;
+
+ if (virNetSocketAddIOCallback(test->client,
+ VIR_EVENT_HANDLE_READABLE,
+ qemuMonitorTestIO,
+ test,
+ NULL) < 0)
+ goto error;
+
+ virMutexLock(&test->lock);
+ if (virThreadCreate(&test->thread,
+ true,
+ qemuMonitorTestWorker,
+ test) < 0) {
+ virMutexUnlock(&test->lock);
+ goto error;
+ }
+ test->running = true;
+ virMutexUnlock(&test->lock);
+
+ return test;
+
+error:
+ qemuMonitorTestFree(test);
+ return NULL;
+}
+
+qemuMonitorPtr qemuMonitorTestGetMonitor(qemuMonitorTestPtr test)
+{
+ return test->mon;
+}
diff --git a/tests/qemumonitortestutils.h b/tests/qemumonitortestutils.h
new file mode 100644
index 0000000..0e9117c
--- /dev/null
+++ b/tests/qemumonitortestutils.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011-2012 Red Hat, Inc.
+ *
+ * This library 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; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; If not, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __VIR_QEMU_MONITOR_TEST_UTILS_H__
+# define __VIR_QEMU_MONITOR_TEST_UTILS_H__
+
+# include "capabilities.h"
+# include "qemu/qemu_monitor.h"
+
+typedef struct _qemuMonitorTest qemuMonitorTest;
+typedef qemuMonitorTest *qemuMonitorTestPtr;
+
+int
+qemuMonitorTestAddItem(qemuMonitorTestPtr test,
+ const char *command_name,
+ const char *response);
+
+qemuMonitorTestPtr qemuMonitorTestNew(bool json,
+ virCapsPtr caps);
+
+void qemuMonitorTestFree(qemuMonitorTestPtr test);
+
+qemuMonitorPtr qemuMonitorTestGetMonitor(qemuMonitorTestPtr test);
+
+#endif /* __VIR_QEMU_MONITOR_TEST_UTILS_H__ */
--
1.7.11.2