From 8d78fd04be695a069ca166d86c3eae780e81dbf1 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Mon, 20 Aug 2012 14:06:21 +0100 Subject: [PATCH] Add helper library for testing the qemu monitor code 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 Signed-off-by: Daniel P. Berrange --- 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 333563fd9a..bca363c1ff 100644 --- a/cfg.mk +++ b/cfg.mk @@ -746,7 +746,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 8cf8015598..051f87a47e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -219,12 +219,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) \ @@ -298,8 +303,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 @@ -342,7 +357,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 0000000000..76b11e6775 --- /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 + * . + * + */ + +#include + +#include +#include +#include +#include + +#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 0000000000..0e9117c9ef --- /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 + * . + * + */ + +#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__ */