mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-22 13:45:38 +00:00
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:
parent
1f490138ba
commit
8d78fd04be
2
cfg.mk
2
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$$)
|
||||
|
||||
|
@ -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
|
||||
|
499
tests/qemumonitortestutils.c
Normal file
499
tests/qemumonitortestutils.c
Normal 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;
|
||||
}
|
41
tests/qemumonitortestutils.h
Normal file
41
tests/qemumonitortestutils.h
Normal 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__ */
|
Loading…
Reference in New Issue
Block a user