[libvirt] [PATCH 2/5] Add helper library for testing the qemu monitor code

Daniel P. Berrange berrange at redhat.com
Mon Aug 20 13:49:29 UTC 2012


From: "Daniel P. Berrange" <berrange at 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




More information about the libvir-list mailing list