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 <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrange 2012-08-20 14:06:21 +01:00
parent 1f490138ba
commit 8d78fd04be
4 changed files with 560 additions and 4 deletions

2
cfg.mk
View File

@ -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$$)

View File

@ -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

View File

@ -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;
}

View File

@ -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__ */