2012-08-20 13:06:21 +00:00
|
|
|
/*
|
2013-01-31 01:50:09 +00:00
|
|
|
* Copyright (C) 2011-2013 Red Hat, Inc.
|
2012-08-20 13:06:21 +00:00
|
|
|
*
|
|
|
|
* 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
|
2012-09-20 22:30:55 +00:00
|
|
|
* License along with this library. If not, see
|
2012-08-20 13:06:21 +00:00
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <time.h>
|
|
|
|
|
2016-06-06 11:46:41 +00:00
|
|
|
#include "testutils.h"
|
2018-03-22 18:05:26 +00:00
|
|
|
#include "testutilsqemuschema.h"
|
2012-08-20 13:06:21 +00:00
|
|
|
#include "qemumonitortestutils.h"
|
|
|
|
|
2012-12-13 15:49:48 +00:00
|
|
|
#include "virthread.h"
|
2018-12-13 14:53:50 +00:00
|
|
|
#define LIBVIRT_QEMU_PROCESSPRIV_H_ALLOW
|
2013-07-26 12:22:10 +00:00
|
|
|
#include "qemu/qemu_processpriv.h"
|
2012-08-20 13:06:21 +00:00
|
|
|
#include "qemu/qemu_monitor.h"
|
2013-07-25 10:08:25 +00:00
|
|
|
#include "qemu/qemu_agent.h"
|
2018-03-22 18:05:26 +00:00
|
|
|
#include "qemu/qemu_qapi.h"
|
2012-08-20 13:06:21 +00:00
|
|
|
#include "rpc/virnetsocket.h"
|
2012-12-12 18:06:53 +00:00
|
|
|
#include "viralloc.h"
|
2012-12-12 17:59:27 +00:00
|
|
|
#include "virlog.h"
|
2012-12-13 18:21:53 +00:00
|
|
|
#include "virerror.h"
|
2013-04-03 10:36:23 +00:00
|
|
|
#include "virstring.h"
|
2020-02-12 14:56:29 +00:00
|
|
|
#include "vireventthread.h"
|
2012-08-20 13:06:21 +00:00
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NONE
|
|
|
|
|
2014-02-28 12:16:17 +00:00
|
|
|
VIR_LOG_INIT("tests.qemumonitortestutils");
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
struct _qemuMonitorTestItem {
|
2020-04-23 14:52:12 +00:00
|
|
|
char *identifier;
|
2013-07-25 09:48:11 +00:00
|
|
|
qemuMonitorTestResponseCallback cb;
|
|
|
|
void *opaque;
|
|
|
|
virFreeCallback freecb;
|
2012-08-20 13:06:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct _qemuMonitorTest {
|
|
|
|
virMutex lock;
|
|
|
|
virThread thread;
|
|
|
|
|
|
|
|
bool quit;
|
|
|
|
bool running;
|
2013-10-02 16:20:18 +00:00
|
|
|
bool started;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2020-04-23 14:57:31 +00:00
|
|
|
bool allowUnusedCommands;
|
|
|
|
|
2020-04-29 09:03:02 +00:00
|
|
|
bool skipValidationDeprecated;
|
|
|
|
bool skipValidationRemoved;
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
char *incoming;
|
|
|
|
size_t incomingLength;
|
|
|
|
size_t incomingCapacity;
|
|
|
|
|
|
|
|
char *outgoing;
|
|
|
|
size_t outgoingLength;
|
|
|
|
size_t outgoingCapacity;
|
|
|
|
|
|
|
|
virNetSocketPtr server;
|
|
|
|
virNetSocketPtr client;
|
|
|
|
|
2020-02-12 14:56:29 +00:00
|
|
|
virEventThread *eventThread;
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
qemuMonitorPtr mon;
|
2013-07-25 10:08:25 +00:00
|
|
|
qemuAgentPtr agent;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2012-11-12 14:33:55 +00:00
|
|
|
char *tmpdir;
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
size_t nitems;
|
|
|
|
qemuMonitorTestItemPtr *items;
|
|
|
|
|
|
|
|
virDomainObjPtr vm;
|
2020-10-22 17:04:18 +00:00
|
|
|
GHashTable *qapischema;
|
2012-08-20 13:06:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-07-25 09:48:11 +00:00
|
|
|
static void
|
|
|
|
qemuMonitorTestItemFree(qemuMonitorTestItemPtr item)
|
|
|
|
{
|
|
|
|
if (!item)
|
|
|
|
return;
|
|
|
|
|
2020-04-23 14:52:12 +00:00
|
|
|
g_free(item->identifier);
|
|
|
|
|
2013-07-25 09:48:11 +00:00
|
|
|
if (item->freecb)
|
|
|
|
(item->freecb)(item->opaque);
|
|
|
|
|
2021-02-03 19:35:02 +00:00
|
|
|
g_free(item);
|
2013-07-25 09:48:11 +00:00
|
|
|
}
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
/*
|
2013-04-19 20:18:14 +00:00
|
|
|
* Appends data for a reply to the outgoing buffer
|
2012-08-20 13:06:21 +00:00
|
|
|
*/
|
2013-07-25 09:48:11 +00:00
|
|
|
int
|
2016-06-07 11:46:01 +00:00
|
|
|
qemuMonitorTestAddResponse(qemuMonitorTestPtr test,
|
|
|
|
const char *response)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
size_t want = strlen(response) + 2;
|
|
|
|
size_t have = test->outgoingCapacity - test->outgoingLength;
|
|
|
|
|
2013-07-29 12:29:15 +00:00
|
|
|
VIR_DEBUG("Adding response to monitor command: '%s", response);
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
if (have < want) {
|
|
|
|
size_t need = want - have;
|
2013-07-04 10:20:21 +00:00
|
|
|
if (VIR_EXPAND_N(test->outgoing, test->outgoingCapacity, need) < 0)
|
2012-08-20 13:06:21 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
want -= 2;
|
2013-07-25 09:02:00 +00:00
|
|
|
memcpy(test->outgoing + test->outgoingLength, response, want);
|
|
|
|
memcpy(test->outgoing + test->outgoingLength + want, "\r\n", 2);
|
2012-08-20 13:06:21 +00:00
|
|
|
test->outgoingLength += want + 2;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-12-01 08:22:44 +00:00
|
|
|
static int
|
2020-04-23 09:50:59 +00:00
|
|
|
qemuMonitorTestAddErrorResponseInternal(qemuMonitorTestPtr test,
|
|
|
|
const char *usermsg)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
2020-07-02 23:35:41 +00:00
|
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *escapemsg = NULL;
|
|
|
|
g_autofree char *jsonmsg = NULL;
|
2016-12-01 08:22:44 +00:00
|
|
|
char *tmp;
|
|
|
|
|
|
|
|
if (!usermsg)
|
|
|
|
usermsg = "unexpected command";
|
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
virBufferEscape(&buf, '\\', "\"", "%s", usermsg);
|
|
|
|
escapemsg = virBufferContentAndReset(&buf);
|
2016-12-01 08:22:44 +00:00
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
/* replace newline/carriage return with space */
|
|
|
|
tmp = escapemsg;
|
|
|
|
while (*tmp) {
|
|
|
|
if (*tmp == '\r' || *tmp == '\n')
|
|
|
|
*tmp = ' ';
|
2016-12-01 08:22:44 +00:00
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
tmp++;
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
2016-12-01 08:22:44 +00:00
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
/* format the JSON error message */
|
2019-10-22 13:26:14 +00:00
|
|
|
jsonmsg = g_strdup_printf("{ \"error\": " " { \"desc\": \"%s\", "
|
|
|
|
" \"class\": \"UnexpectedCommand\" } }", escapemsg);
|
2019-06-15 10:09:41 +00:00
|
|
|
|
|
|
|
return qemuMonitorTestAddResponse(test, jsonmsg);
|
2016-12-01 08:22:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuMonitorTestAddInvalidCommandResponse(qemuMonitorTestPtr test,
|
|
|
|
const char *expectedcommand,
|
|
|
|
const char *actualcommand)
|
|
|
|
{
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *msg = NULL;
|
2016-12-01 08:22:44 +00:00
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
msg = g_strdup_printf("expected command '%s' got '%s'", expectedcommand,
|
|
|
|
actualcommand);
|
2016-12-01 08:22:44 +00:00
|
|
|
|
2020-04-23 09:50:59 +00:00
|
|
|
return qemuMonitorTestAddErrorResponseInternal(test, msg);
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-15 11:35:07 +00:00
|
|
|
int G_GNUC_PRINTF(2, 3)
|
2020-04-23 09:50:59 +00:00
|
|
|
qemuMonitorTestAddErrorResponse(qemuMonitorTestPtr test, const char *errmsg, ...)
|
2013-07-29 12:29:15 +00:00
|
|
|
{
|
|
|
|
va_list msgargs;
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *msg = NULL;
|
|
|
|
g_autofree char *jsonmsg = NULL;
|
2013-07-29 12:29:15 +00:00
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
va_start(msgargs, errmsg);
|
|
|
|
|
2019-10-22 12:11:15 +00:00
|
|
|
msg = g_strdup_vprintf(errmsg, msgargs);
|
2013-07-29 12:29:15 +00:00
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
jsonmsg = g_strdup_printf("{ \"error\": " " { \"desc\": \"%s\", "
|
|
|
|
" \"class\": \"UnexpectedCommand\" } }", msg);
|
2013-07-29 12:29:15 +00:00
|
|
|
|
2016-06-07 11:46:01 +00:00
|
|
|
ret = qemuMonitorTestAddResponse(test, jsonmsg);
|
2013-07-29 12:29:15 +00:00
|
|
|
|
|
|
|
va_end(msgargs);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-23 09:57:14 +00:00
|
|
|
static void G_GNUC_NORETURN G_GNUC_PRINTF(1, 2)
|
|
|
|
qemuMonitorTestError(const char *errmsg,
|
|
|
|
...)
|
|
|
|
{
|
|
|
|
va_list msgargs;
|
|
|
|
|
|
|
|
va_start(msgargs, errmsg);
|
|
|
|
|
|
|
|
g_fprintf(stderr, "\n");
|
|
|
|
g_vfprintf(stderr, errmsg, msgargs);
|
|
|
|
g_fprintf(stderr, "\n");
|
|
|
|
exit(EXIT_FAILURE); /* exempt from syntax-check */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void G_GNUC_NORETURN
|
|
|
|
qemuMonitorTestErrorInvalidCommand(const char *expectedcommand,
|
|
|
|
const char *actualcommand)
|
|
|
|
{
|
|
|
|
qemuMonitorTestError("expected command '%s' got '%s'",
|
|
|
|
expectedcommand, actualcommand);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
static int
|
2013-07-25 09:48:11 +00:00
|
|
|
qemuMonitorTestProcessCommand(qemuMonitorTestPtr test,
|
|
|
|
const char *cmdstr)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
2013-07-25 09:48:11 +00:00
|
|
|
int ret;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2013-07-29 12:29:15 +00:00
|
|
|
VIR_DEBUG("Processing string from monitor handler: '%s", cmdstr);
|
|
|
|
|
2013-07-25 09:48:11 +00:00
|
|
|
if (test->nitems == 0) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("unexpected command: '%s'", cmdstr);
|
2012-08-20 13:06:21 +00:00
|
|
|
} else {
|
2013-07-25 09:48:11 +00:00
|
|
|
qemuMonitorTestItemPtr item = test->items[0];
|
|
|
|
ret = (item->cb)(test, item, cmdstr);
|
|
|
|
qemuMonitorTestItemFree(item);
|
|
|
|
if (VIR_DELETE_ELEMENT(test->items, 0, test->nitems) < 0)
|
|
|
|
return -1;
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
/*
|
|
|
|
* Handles read/write of monitor data on the monitor server side
|
|
|
|
*/
|
2013-07-18 14:17:31 +00:00
|
|
|
static void
|
|
|
|
qemuMonitorTestIO(virNetSocketPtr sock,
|
|
|
|
int events,
|
|
|
|
void *opaque)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
qemuMonitorTestPtr test = opaque;
|
|
|
|
bool err = false;
|
|
|
|
|
|
|
|
virMutexLock(&test->lock);
|
2013-02-22 21:56:21 +00:00
|
|
|
if (test->quit) {
|
|
|
|
virMutexUnlock(&test->lock);
|
|
|
|
return;
|
|
|
|
}
|
2012-08-20 13:06:21 +00:00
|
|
|
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;
|
2015-03-10 12:10:20 +00:00
|
|
|
while ((t2 = strstr(t1, "\n")) ||
|
2019-06-14 18:39:24 +00:00
|
|
|
(test->agent && (t2 = strstr(t1, "\r")))) {
|
2012-08-20 13:06:21 +00:00
|
|
|
*t2 = '\0';
|
|
|
|
|
|
|
|
if (qemuMonitorTestProcessCommand(test, t1) < 0) {
|
|
|
|
err = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2013-07-25 10:03:29 +00:00
|
|
|
t1 = t2 + 1;
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
cleanup:
|
2012-08-20 13:06:21 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
static void
|
|
|
|
qemuMonitorTestWorker(void *opaque)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
qemuMonitorTestPtr test = opaque;
|
|
|
|
|
|
|
|
virMutexLock(&test->lock);
|
|
|
|
|
|
|
|
while (!test->quit) {
|
|
|
|
virMutexUnlock(&test->lock);
|
|
|
|
|
|
|
|
if (virEventRunDefaultImpl() < 0) {
|
2013-02-04 18:31:46 +00:00
|
|
|
virMutexLock(&test->lock);
|
2012-08-20 13:06:21 +00:00
|
|
|
test->quit = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
virMutexLock(&test->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
test->running = false;
|
|
|
|
|
|
|
|
virMutexUnlock(&test->lock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
|
2012-11-12 10:34:41 +00:00
|
|
|
static void
|
2019-10-14 12:45:03 +00:00
|
|
|
qemuMonitorTestFreeTimer(int timer G_GNUC_UNUSED,
|
|
|
|
void *opaque G_GNUC_UNUSED)
|
2012-11-12 10:34:41 +00:00
|
|
|
{
|
|
|
|
/* nothing to be done here */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
void
|
|
|
|
qemuMonitorTestFree(qemuMonitorTestPtr test)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
size_t i;
|
2012-11-12 10:34:41 +00:00
|
|
|
int timer = -1;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
|
|
|
if (!test)
|
|
|
|
return;
|
|
|
|
|
|
|
|
virMutexLock(&test->lock);
|
|
|
|
if (test->running) {
|
|
|
|
test->quit = true;
|
2012-11-12 10:34:41 +00:00
|
|
|
/* HACK: Add a dummy timeout to break event loop */
|
|
|
|
timer = virEventAddTimeout(0, qemuMonitorTestFreeTimer, NULL, NULL);
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
|
|
|
virMutexUnlock(&test->lock);
|
|
|
|
|
|
|
|
if (test->client) {
|
|
|
|
virNetSocketRemoveIOCallback(test->client);
|
|
|
|
virNetSocketClose(test->client);
|
|
|
|
virObjectUnref(test->client);
|
|
|
|
}
|
|
|
|
|
|
|
|
virObjectUnref(test->server);
|
|
|
|
if (test->mon) {
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectUnlock(test->mon);
|
2012-08-20 13:06:21 +00:00
|
|
|
qemuMonitorClose(test->mon);
|
|
|
|
}
|
|
|
|
|
2013-07-25 10:08:25 +00:00
|
|
|
if (test->agent) {
|
|
|
|
virObjectUnlock(test->agent);
|
|
|
|
qemuAgentClose(test->agent);
|
|
|
|
}
|
|
|
|
|
2020-02-12 14:56:29 +00:00
|
|
|
g_object_unref(test->eventThread);
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
virObjectUnref(test->vm);
|
|
|
|
|
2013-10-02 16:20:18 +00:00
|
|
|
if (test->started)
|
2013-07-22 14:57:08 +00:00
|
|
|
virThreadJoin(&test->thread);
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2012-11-12 10:34:41 +00:00
|
|
|
if (timer != -1)
|
|
|
|
virEventRemoveTimeout(timer);
|
|
|
|
|
2021-02-03 19:35:02 +00:00
|
|
|
g_free(test->incoming);
|
|
|
|
g_free(test->outgoing);
|
2013-02-04 18:31:46 +00:00
|
|
|
|
2020-04-23 14:57:31 +00:00
|
|
|
for (i = 0; i < test->nitems; i++) {
|
|
|
|
if (!test->allowUnusedCommands) {
|
|
|
|
g_fprintf(stderr,
|
|
|
|
"\nunused test monitor item '%s'",
|
|
|
|
NULLSTR(test->items[i]->identifier));
|
|
|
|
}
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
qemuMonitorTestItemFree(test->items[i]);
|
2020-04-23 14:57:31 +00:00
|
|
|
}
|
2021-02-03 19:35:02 +00:00
|
|
|
g_free(test->items);
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2012-11-12 14:33:55 +00:00
|
|
|
if (test->tmpdir && rmdir(test->tmpdir) < 0)
|
2019-10-02 15:30:36 +00:00
|
|
|
VIR_WARN("Failed to remove tempdir: %s", g_strerror(errno));
|
2012-11-12 14:33:55 +00:00
|
|
|
|
2021-02-03 19:35:02 +00:00
|
|
|
g_free(test->tmpdir);
|
2012-11-12 14:33:55 +00:00
|
|
|
|
2020-04-23 14:57:31 +00:00
|
|
|
if (!test->allowUnusedCommands &&
|
|
|
|
test->nitems != 0) {
|
|
|
|
qemuMonitorTestError("unused test monitor items are not allowed for this test\n");
|
|
|
|
}
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
virMutexDestroy(&test->lock);
|
2021-02-03 19:35:02 +00:00
|
|
|
g_free(test);
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
2013-07-25 09:48:11 +00:00
|
|
|
qemuMonitorTestAddHandler(qemuMonitorTestPtr test,
|
2020-04-23 14:52:12 +00:00
|
|
|
const char *identifier,
|
2013-07-25 09:48:11 +00:00
|
|
|
qemuMonitorTestResponseCallback cb,
|
|
|
|
void *opaque,
|
|
|
|
virFreeCallback freecb)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
qemuMonitorTestItemPtr item;
|
|
|
|
|
2020-09-22 22:42:45 +00:00
|
|
|
item = g_new0(qemuMonitorTestItem, 1);
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2020-04-23 14:52:12 +00:00
|
|
|
item->identifier = g_strdup(identifier);
|
2013-07-25 09:48:11 +00:00
|
|
|
item->cb = cb;
|
|
|
|
item->freecb = freecb;
|
|
|
|
item->opaque = opaque;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
|
|
|
virMutexLock(&test->lock);
|
2013-07-18 15:09:41 +00:00
|
|
|
if (VIR_APPEND_ELEMENT(test->items, test->nitems, item) < 0) {
|
2012-08-20 13:06:21 +00:00
|
|
|
virMutexUnlock(&test->lock);
|
2013-07-04 10:20:21 +00:00
|
|
|
goto error;
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
|
|
|
virMutexUnlock(&test->lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
error:
|
2013-07-25 09:48:11 +00:00
|
|
|
if (freecb)
|
|
|
|
(freecb)(opaque);
|
|
|
|
VIR_FREE(item);
|
2012-08-20 13:06:21 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-07-25 09:48:11 +00:00
|
|
|
void *
|
|
|
|
qemuMonitorTestItemGetPrivateData(qemuMonitorTestItemPtr item)
|
|
|
|
{
|
|
|
|
return item ? item->opaque : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-25 13:42:38 +00:00
|
|
|
typedef struct _qemuMonitorTestCommandArgs qemuMonitorTestCommandArgs;
|
|
|
|
typedef qemuMonitorTestCommandArgs *qemuMonitorTestCommandArgsPtr;
|
|
|
|
struct _qemuMonitorTestCommandArgs {
|
|
|
|
char *argname;
|
|
|
|
char *argval;
|
2013-07-25 09:48:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-07-25 13:42:38 +00:00
|
|
|
struct qemuMonitorTestHandlerData {
|
|
|
|
char *command_name;
|
2016-11-25 09:43:23 +00:00
|
|
|
char *cmderr;
|
2013-07-25 13:42:38 +00:00
|
|
|
char *response;
|
|
|
|
size_t nargs;
|
|
|
|
qemuMonitorTestCommandArgsPtr args;
|
2016-09-27 15:11:55 +00:00
|
|
|
char *expectArgs;
|
2013-07-25 13:42:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuMonitorTestHandlerDataFree(void *opaque)
|
|
|
|
{
|
|
|
|
struct qemuMonitorTestHandlerData *data = opaque;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!data)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < data->nargs; i++) {
|
2021-02-03 19:35:02 +00:00
|
|
|
g_free(data->args[i].argname);
|
|
|
|
g_free(data->args[i].argval);
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 19:35:02 +00:00
|
|
|
g_free(data->command_name);
|
|
|
|
g_free(data->cmderr);
|
|
|
|
g_free(data->response);
|
|
|
|
g_free(data->args);
|
|
|
|
g_free(data->expectArgs);
|
|
|
|
g_free(data);
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
2018-03-22 18:05:26 +00:00
|
|
|
|
2020-04-29 08:38:16 +00:00
|
|
|
/* Returns -1 on error, 0 if validation was successful/not necessary */
|
2018-03-22 18:05:26 +00:00
|
|
|
static int
|
|
|
|
qemuMonitorTestProcessCommandDefaultValidate(qemuMonitorTestPtr test,
|
|
|
|
const char *cmdname,
|
|
|
|
virJSONValuePtr args)
|
|
|
|
{
|
2019-10-15 12:47:50 +00:00
|
|
|
g_auto(virBuffer) debug = VIR_BUFFER_INITIALIZER;
|
2018-03-22 18:05:26 +00:00
|
|
|
|
2019-06-15 09:47:34 +00:00
|
|
|
if (!test->qapischema)
|
2018-03-22 18:05:26 +00:00
|
|
|
return 0;
|
|
|
|
|
2019-06-15 09:47:34 +00:00
|
|
|
if (test->agent) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
"Command validation testing is not "
|
|
|
|
"implemented for the guest agent");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-03-22 18:05:26 +00:00
|
|
|
/* 'device_add' needs to be skipped as it does not have fully defined schema */
|
|
|
|
if (STREQ(cmdname, "device_add"))
|
|
|
|
return 0;
|
|
|
|
|
2020-04-29 15:52:43 +00:00
|
|
|
if (testQEMUSchemaValidateCommand(cmdname, args, test->qapischema,
|
|
|
|
test->skipValidationDeprecated,
|
|
|
|
test->skipValidationRemoved,
|
|
|
|
&debug) < 0) {
|
2020-03-04 09:22:25 +00:00
|
|
|
if (virTestGetDebug() == 2) {
|
|
|
|
g_autofree char *argstr = NULL;
|
2018-03-22 18:05:26 +00:00
|
|
|
|
2020-03-04 09:22:25 +00:00
|
|
|
if (args)
|
|
|
|
argstr = virJSONValueToString(args, true);
|
2018-03-22 18:05:26 +00:00
|
|
|
|
2020-04-01 10:28:17 +00:00
|
|
|
fprintf(stderr,
|
|
|
|
"\nfailed to validate arguments of '%s' against QAPI schema\n"
|
|
|
|
"args:\n%s\nvalidator output:\n %s\n",
|
|
|
|
cmdname, NULLSTR(argstr), virBufferCurrentContent(&debug));
|
|
|
|
}
|
|
|
|
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("failed to validate arguments of '%s' "
|
|
|
|
"against QAPI schema "
|
|
|
|
"(to see debug output use VIR_TEST_DEBUG=2)",
|
|
|
|
cmdname);
|
2019-06-15 09:54:03 +00:00
|
|
|
return -1;
|
2018-03-22 18:05:26 +00:00
|
|
|
}
|
|
|
|
|
2019-06-15 09:54:03 +00:00
|
|
|
return 0;
|
2018-03-22 18:05:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-25 09:48:11 +00:00
|
|
|
static int
|
|
|
|
qemuMonitorTestProcessCommandDefault(qemuMonitorTestPtr test,
|
|
|
|
qemuMonitorTestItemPtr item,
|
|
|
|
const char *cmdstr)
|
|
|
|
{
|
2013-07-25 13:42:38 +00:00
|
|
|
struct qemuMonitorTestHandlerData *data = item->opaque;
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) val = NULL;
|
2018-03-22 18:05:26 +00:00
|
|
|
virJSONValuePtr cmdargs = NULL;
|
2013-07-25 09:48:11 +00:00
|
|
|
const char *cmdname;
|
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
if (!(val = virJSONValueFromString(cmdstr)))
|
|
|
|
return -1;
|
2013-07-25 09:48:11 +00:00
|
|
|
|
2020-04-23 09:57:14 +00:00
|
|
|
if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
|
|
|
|
qemuMonitorTestError("Missing command name in %s", cmdstr);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-07-25 09:48:11 +00:00
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
cmdargs = virJSONValueObjectGet(val, "arguments");
|
2020-04-29 08:38:16 +00:00
|
|
|
if (qemuMonitorTestProcessCommandDefaultValidate(test, cmdname, cmdargs) < 0)
|
2019-06-15 09:43:09 +00:00
|
|
|
return -1;
|
2018-03-22 18:05:26 +00:00
|
|
|
|
2019-06-15 09:43:09 +00:00
|
|
|
if (data->command_name && STRNEQ(data->command_name, cmdname)) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestErrorInvalidCommand(data->command_name, cmdname);
|
|
|
|
return -1;
|
2019-06-15 09:43:09 +00:00
|
|
|
} else {
|
|
|
|
return qemuMonitorTestAddResponse(test, data->response);
|
2018-03-22 18:05:26 +00:00
|
|
|
}
|
2013-07-25 09:48:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuMonitorTestAddItem(qemuMonitorTestPtr test,
|
|
|
|
const char *command_name,
|
|
|
|
const char *response)
|
|
|
|
{
|
2013-07-25 13:42:38 +00:00
|
|
|
struct qemuMonitorTestHandlerData *data;
|
2013-07-25 09:48:11 +00:00
|
|
|
|
2020-09-22 22:42:45 +00:00
|
|
|
data = g_new0(struct qemuMonitorTestHandlerData, 1);
|
2013-07-25 09:48:11 +00:00
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
data->command_name = g_strdup(command_name);
|
|
|
|
data->response = g_strdup(response);
|
2013-07-25 09:48:11 +00:00
|
|
|
|
|
|
|
return qemuMonitorTestAddHandler(test,
|
2020-04-23 14:52:12 +00:00
|
|
|
command_name,
|
2013-07-25 09:48:11 +00:00
|
|
|
qemuMonitorTestProcessCommandDefault,
|
2013-07-25 13:42:38 +00:00
|
|
|
data, qemuMonitorTestHandlerDataFree);
|
2013-07-25 09:48:11 +00:00
|
|
|
}
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2016-11-25 09:43:23 +00:00
|
|
|
static int
|
|
|
|
qemuMonitorTestProcessCommandVerbatim(qemuMonitorTestPtr test,
|
|
|
|
qemuMonitorTestItemPtr item,
|
|
|
|
const char *cmdstr)
|
|
|
|
{
|
|
|
|
struct qemuMonitorTestHandlerData *data = item->opaque;
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *reformatted = NULL;
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) json = NULL;
|
2019-06-07 08:38:06 +00:00
|
|
|
virJSONValuePtr cmdargs;
|
|
|
|
const char *cmdname;
|
2016-11-25 09:43:23 +00:00
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
/* JSON strings will be reformatted to simplify checking */
|
2019-06-15 10:09:41 +00:00
|
|
|
if (!(json = virJSONValueFromString(cmdstr)) ||
|
|
|
|
!(reformatted = virJSONValueToString(json, false)))
|
|
|
|
return -1;
|
2016-11-25 09:43:23 +00:00
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
cmdstr = reformatted;
|
2019-06-07 08:38:06 +00:00
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
/* in this case we do a best-effort schema check if we can find the command */
|
|
|
|
if ((cmdname = virJSONValueObjectGetString(json, "execute"))) {
|
|
|
|
cmdargs = virJSONValueObjectGet(json, "arguments");
|
2020-04-29 08:38:16 +00:00
|
|
|
if (qemuMonitorTestProcessCommandDefaultValidate(test, cmdname, cmdargs) < 0)
|
2019-06-15 10:09:41 +00:00
|
|
|
return -1;
|
2016-11-25 09:43:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (STREQ(data->command_name, cmdstr)) {
|
|
|
|
ret = qemuMonitorTestAddResponse(test, data->response);
|
|
|
|
} else {
|
|
|
|
if (data->cmderr) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("%s: %s", data->cmderr, cmdstr);
|
2016-11-25 09:43:23 +00:00
|
|
|
} else {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestErrorInvalidCommand(data->command_name, cmdstr);
|
2016-11-25 09:43:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuMonitorTestAddItemVerbatim:
|
|
|
|
* @test: monitor test object
|
|
|
|
* @command: full expected command syntax
|
|
|
|
* @cmderr: possible explanation of expected command (may be NULL)
|
|
|
|
* @response: full reply of @command
|
|
|
|
*
|
|
|
|
* Adds a test command for the simulated monitor. The full syntax is checked
|
|
|
|
* as specified in @command. For JSON monitor tests formatting/whitespace is
|
|
|
|
* ignored. If the command on the monitor is not as expected an error containing
|
|
|
|
* @cmderr is returned. Otherwise @response is put as-is on the monitor.
|
|
|
|
*
|
|
|
|
* Returns 0 when command was successfully added, -1 on error.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuMonitorTestAddItemVerbatim(qemuMonitorTestPtr test,
|
|
|
|
const char *command,
|
|
|
|
const char *cmderr,
|
|
|
|
const char *response)
|
|
|
|
{
|
|
|
|
struct qemuMonitorTestHandlerData *data;
|
|
|
|
|
2020-09-22 22:42:45 +00:00
|
|
|
data = g_new0(struct qemuMonitorTestHandlerData, 1);
|
2016-11-25 09:43:23 +00:00
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
data->response = g_strdup(response);
|
|
|
|
data->cmderr = g_strdup(cmderr);
|
2016-11-25 09:43:23 +00:00
|
|
|
|
2019-06-15 10:09:41 +00:00
|
|
|
data->command_name = virJSONStringReformat(command, false);
|
2016-11-25 09:43:23 +00:00
|
|
|
if (!data->command_name)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
return qemuMonitorTestAddHandler(test,
|
2020-04-23 14:52:12 +00:00
|
|
|
command,
|
2016-11-25 09:43:23 +00:00
|
|
|
qemuMonitorTestProcessCommandVerbatim,
|
|
|
|
data, qemuMonitorTestHandlerDataFree);
|
|
|
|
|
|
|
|
error:
|
|
|
|
qemuMonitorTestHandlerDataFree(data);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-25 10:08:25 +00:00
|
|
|
static int
|
|
|
|
qemuMonitorTestProcessGuestAgentSync(qemuMonitorTestPtr test,
|
2019-10-14 12:45:03 +00:00
|
|
|
qemuMonitorTestItemPtr item G_GNUC_UNUSED,
|
2013-07-25 10:08:25 +00:00
|
|
|
const char *cmdstr)
|
|
|
|
{
|
2020-07-28 19:58:18 +00:00
|
|
|
g_autoptr(virJSONValue) val = NULL;
|
2013-07-25 10:08:25 +00:00
|
|
|
virJSONValuePtr args;
|
|
|
|
unsigned long long id;
|
|
|
|
const char *cmdname;
|
2020-07-28 19:57:28 +00:00
|
|
|
g_autofree char *retmsg = NULL;
|
2013-07-25 10:08:25 +00:00
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
if (!(val = virJSONValueFromString(cmdstr)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
|
2020-04-23 09:50:59 +00:00
|
|
|
ret = qemuMonitorTestAddErrorResponse(test, "Missing guest-sync command name");
|
2013-07-25 10:08:25 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (STRNEQ(cmdname, "guest-sync")) {
|
2016-12-01 08:22:44 +00:00
|
|
|
ret = qemuMonitorTestAddInvalidCommandResponse(test, "guest-sync", cmdname);
|
2013-07-25 10:08:25 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(args = virJSONValueObjectGet(val, "arguments"))) {
|
2020-04-23 09:50:59 +00:00
|
|
|
ret = qemuMonitorTestAddErrorResponse(test, "Missing arguments for guest-sync");
|
2013-07-25 10:08:25 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberUlong(args, "id", &id)) {
|
2020-04-23 09:50:59 +00:00
|
|
|
ret = qemuMonitorTestAddErrorResponse(test, "Missing id for guest sync");
|
2013-07-25 10:08:25 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
retmsg = g_strdup_printf("{\"return\":%llu}", id);
|
2013-07-25 10:08:25 +00:00
|
|
|
|
|
|
|
|
2016-06-07 11:46:01 +00:00
|
|
|
ret = qemuMonitorTestAddResponse(test, retmsg);
|
2013-07-25 10:08:25 +00:00
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
cleanup:
|
2013-07-25 10:08:25 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuMonitorTestAddAgentSyncResponse(qemuMonitorTestPtr test)
|
|
|
|
{
|
|
|
|
if (!test->agent) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
"This test is not an agent test");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qemuMonitorTestAddHandler(test,
|
2020-04-23 14:52:12 +00:00
|
|
|
"agent-sync",
|
2013-07-25 10:08:25 +00:00
|
|
|
qemuMonitorTestProcessGuestAgentSync,
|
|
|
|
NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-25 13:42:38 +00:00
|
|
|
static int
|
|
|
|
qemuMonitorTestProcessCommandWithArgs(qemuMonitorTestPtr test,
|
|
|
|
qemuMonitorTestItemPtr item,
|
|
|
|
const char *cmdstr)
|
|
|
|
{
|
|
|
|
struct qemuMonitorTestHandlerData *data = item->opaque;
|
2020-07-28 19:58:18 +00:00
|
|
|
g_autoptr(virJSONValue) val = NULL;
|
2013-07-25 13:42:38 +00:00
|
|
|
virJSONValuePtr args;
|
|
|
|
virJSONValuePtr argobj;
|
|
|
|
const char *cmdname;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!(val = virJSONValueFromString(cmdstr)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("Missing command name in %s", cmdstr);
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
2013-09-19 13:19:08 +00:00
|
|
|
if (data->command_name &&
|
|
|
|
STRNEQ(data->command_name, cmdname)) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestErrorInvalidCommand(data->command_name, cmdname);
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(args = virJSONValueObjectGet(val, "arguments"))) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("Missing arguments section for command '%s'",
|
|
|
|
NULLSTR(data->command_name));
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* validate the args */
|
|
|
|
for (i = 0; i < data->nargs; i++) {
|
|
|
|
qemuMonitorTestCommandArgsPtr arg = &data->args[i];
|
2020-07-28 20:01:22 +00:00
|
|
|
g_autofree char *argstr = NULL;
|
|
|
|
|
2013-07-25 13:42:38 +00:00
|
|
|
if (!(argobj = virJSONValueObjectGet(args, arg->argname))) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("Missing argument '%s' for command '%s'",
|
|
|
|
arg->argname,
|
|
|
|
NULLSTR(data->command_name));
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* convert the argument to string */
|
|
|
|
if (!(argstr = virJSONValueToString(argobj, false)))
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2013-07-25 13:42:38 +00:00
|
|
|
|
|
|
|
/* verify that the argument value is expected */
|
|
|
|
if (STRNEQ(argstr, arg->argval)) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("Invalid value of argument '%s' of command '%s': "
|
|
|
|
"expected '%s' got '%s'",
|
|
|
|
arg->argname,
|
|
|
|
NULLSTR(data->command_name),
|
|
|
|
arg->argval, argstr);
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* arguments checked out, return the response */
|
2020-07-28 21:17:02 +00:00
|
|
|
return qemuMonitorTestAddResponse(test, data->response);
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-27 15:11:55 +00:00
|
|
|
|
2013-07-25 13:42:38 +00:00
|
|
|
/* this allows to add a responder that is able to check
|
|
|
|
* a (shallow) structure of arguments for a command */
|
|
|
|
int
|
|
|
|
qemuMonitorTestAddItemParams(qemuMonitorTestPtr test,
|
|
|
|
const char *cmdname,
|
|
|
|
const char *response,
|
|
|
|
...)
|
|
|
|
{
|
|
|
|
struct qemuMonitorTestHandlerData *data;
|
|
|
|
const char *argname;
|
|
|
|
const char *argval;
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
va_start(args, response);
|
|
|
|
|
2020-09-22 22:42:45 +00:00
|
|
|
data = g_new0(struct qemuMonitorTestHandlerData, 1);
|
2013-07-25 13:42:38 +00:00
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
data->command_name = g_strdup(cmdname);
|
|
|
|
data->response = g_strdup(response);
|
2013-07-25 13:42:38 +00:00
|
|
|
|
|
|
|
while ((argname = va_arg(args, char *))) {
|
|
|
|
size_t i;
|
|
|
|
if (!(argval = va_arg(args, char *))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
"Missing argument value for argument '%s'",
|
|
|
|
argname);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
i = data->nargs;
|
|
|
|
if (VIR_EXPAND_N(data->args, data->nargs, 1))
|
|
|
|
goto error;
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
data->args[i].argname = g_strdup(argname);
|
|
|
|
data->args[i].argval = g_strdup(argval);
|
2013-07-25 13:42:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
return qemuMonitorTestAddHandler(test,
|
2020-04-23 14:52:12 +00:00
|
|
|
cmdname,
|
2013-07-25 13:42:38 +00:00
|
|
|
qemuMonitorTestProcessCommandWithArgs,
|
|
|
|
data, qemuMonitorTestHandlerDataFree);
|
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
error:
|
2013-07-25 13:42:38 +00:00
|
|
|
va_end(args);
|
|
|
|
qemuMonitorTestHandlerDataFree(data);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-27 15:11:55 +00:00
|
|
|
static int
|
|
|
|
qemuMonitorTestProcessCommandWithArgStr(qemuMonitorTestPtr test,
|
|
|
|
qemuMonitorTestItemPtr item,
|
|
|
|
const char *cmdstr)
|
|
|
|
{
|
|
|
|
struct qemuMonitorTestHandlerData *data = item->opaque;
|
2020-07-28 19:58:18 +00:00
|
|
|
g_autoptr(virJSONValue) val = NULL;
|
2016-09-27 15:11:55 +00:00
|
|
|
virJSONValuePtr args;
|
2020-07-28 19:57:28 +00:00
|
|
|
g_autofree char *argstr = NULL;
|
2016-09-27 15:11:55 +00:00
|
|
|
const char *cmdname;
|
|
|
|
|
|
|
|
if (!(val = virJSONValueFromString(cmdstr)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("Missing command name in %s", cmdstr);
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2016-09-27 15:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (STRNEQ(data->command_name, cmdname)) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestErrorInvalidCommand(data->command_name, cmdname);
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2016-09-27 15:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(args = virJSONValueObjectGet(val, "arguments"))) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("Missing arguments section for command '%s'",
|
|
|
|
data->command_name);
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2016-09-27 15:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* convert the arguments to string */
|
|
|
|
if (!(argstr = virJSONValueToString(args, false)))
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2016-09-27 15:11:55 +00:00
|
|
|
|
|
|
|
/* verify that the argument value is expected */
|
|
|
|
if (STRNEQ(argstr, data->expectArgs)) {
|
2020-04-23 09:57:14 +00:00
|
|
|
qemuMonitorTestError("%s: expected arguments: '%s', got: '%s'",
|
|
|
|
data->command_name,
|
|
|
|
data->expectArgs, argstr);
|
2020-07-28 21:17:02 +00:00
|
|
|
return -1;
|
2016-09-27 15:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* arguments checked out, return the response */
|
2020-07-28 21:17:02 +00:00
|
|
|
return qemuMonitorTestAddResponse(test, data->response);
|
2016-09-27 15:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuMonitorTestAddItemExpect:
|
|
|
|
*
|
|
|
|
* @test: test monitor object
|
|
|
|
* @cmdname: command name
|
|
|
|
* @cmdargs: expected arguments of the command
|
|
|
|
* @apostrophe: convert apostrophes (') in @cmdargs to quotes (")
|
|
|
|
* @response: simulated response of the command
|
|
|
|
*
|
|
|
|
* Simulates a qemu monitor command. Checks that the 'arguments' of the qmp
|
|
|
|
* command are expected. If @apostrophe is true apostrophes are converted to
|
|
|
|
* quotes for simplification of writing the strings into code.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuMonitorTestAddItemExpect(qemuMonitorTestPtr test,
|
|
|
|
const char *cmdname,
|
|
|
|
const char *cmdargs,
|
|
|
|
bool apostrophe,
|
|
|
|
const char *response)
|
|
|
|
{
|
|
|
|
struct qemuMonitorTestHandlerData *data;
|
|
|
|
|
2020-09-22 22:42:45 +00:00
|
|
|
data = g_new0(struct qemuMonitorTestHandlerData, 1);
|
2016-09-27 15:11:55 +00:00
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
data->command_name = g_strdup(cmdname);
|
|
|
|
data->response = g_strdup(response);
|
|
|
|
data->expectArgs = g_strdup(cmdargs);
|
2016-09-27 15:11:55 +00:00
|
|
|
|
|
|
|
if (apostrophe) {
|
|
|
|
char *tmp = data->expectArgs;
|
|
|
|
|
|
|
|
while (*tmp != '\0') {
|
|
|
|
if (*tmp == '\'')
|
|
|
|
*tmp = '"';
|
|
|
|
|
|
|
|
tmp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return qemuMonitorTestAddHandler(test,
|
2020-04-23 14:52:12 +00:00
|
|
|
cmdname,
|
2016-09-27 15:11:55 +00:00
|
|
|
qemuMonitorTestProcessCommandWithArgStr,
|
|
|
|
data, qemuMonitorTestHandlerDataFree);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
static void
|
2019-10-14 12:45:03 +00:00
|
|
|
qemuMonitorTestEOFNotify(qemuMonitorPtr mon G_GNUC_UNUSED,
|
|
|
|
virDomainObjPtr vm G_GNUC_UNUSED,
|
|
|
|
void *opaque G_GNUC_UNUSED)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
|
|
|
|
static void
|
2019-10-14 12:45:03 +00:00
|
|
|
qemuMonitorTestErrorNotify(qemuMonitorPtr mon G_GNUC_UNUSED,
|
|
|
|
virDomainObjPtr vm G_GNUC_UNUSED,
|
|
|
|
void *opaque G_GNUC_UNUSED)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-25 09:48:11 +00:00
|
|
|
static qemuMonitorCallbacks qemuMonitorTestCallbacks = {
|
2012-08-20 13:06:21 +00:00
|
|
|
.eofNotify = qemuMonitorTestEOFNotify,
|
|
|
|
.errorNotify = qemuMonitorTestErrorNotify,
|
2013-07-26 12:22:10 +00:00
|
|
|
.domainDeviceDeleted = qemuProcessHandleDeviceDeleted,
|
2012-08-20 13:06:21 +00:00
|
|
|
};
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
|
2013-07-25 10:08:25 +00:00
|
|
|
static void
|
2019-10-14 12:45:03 +00:00
|
|
|
qemuMonitorTestAgentNotify(qemuAgentPtr agent G_GNUC_UNUSED,
|
|
|
|
virDomainObjPtr vm G_GNUC_UNUSED)
|
2013-07-25 10:08:25 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static qemuAgentCallbacks qemuMonitorTestAgentCallbacks = {
|
|
|
|
.eofNotify = qemuMonitorTestAgentNotify,
|
|
|
|
.errorNotify = qemuMonitorTestAgentNotify,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-07-22 14:59:22 +00:00
|
|
|
static qemuMonitorTestPtr
|
|
|
|
qemuMonitorCommonTestNew(virDomainXMLOptionPtr xmlopt,
|
2013-07-25 17:17:44 +00:00
|
|
|
virDomainObjPtr vm,
|
2013-07-22 14:59:22 +00:00
|
|
|
virDomainChrSourceDefPtr src)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
2012-10-29 08:28:15 +00:00
|
|
|
qemuMonitorTestPtr test = NULL;
|
2012-11-12 14:33:55 +00:00
|
|
|
char *path = NULL;
|
|
|
|
char *tmpdir_template = NULL;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2020-09-22 22:42:45 +00:00
|
|
|
test = g_new0(qemuMonitorTest, 1);
|
2012-08-20 13:06:21 +00:00
|
|
|
|
|
|
|
if (virMutexInit(&test->lock) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
"Cannot initialize mutex");
|
|
|
|
VIR_FREE(test);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
tmpdir_template = g_strdup("/tmp/libvirt_XXXXXX");
|
2012-11-12 14:33:55 +00:00
|
|
|
|
2019-11-13 21:35:47 +00:00
|
|
|
if (!(test->tmpdir = g_mkdtemp(tmpdir_template))) {
|
2012-11-12 14:33:55 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
"Failed to create temporary directory");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpdir_template = NULL;
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
path = g_strdup_printf("%s/qemumonitorjsontest.sock", test->tmpdir);
|
2012-11-12 14:33:55 +00:00
|
|
|
|
2013-07-25 17:17:44 +00:00
|
|
|
if (vm) {
|
2017-04-03 08:24:39 +00:00
|
|
|
test->vm = virObjectRef(vm);
|
2013-07-25 17:17:44 +00:00
|
|
|
} else {
|
|
|
|
test->vm = virDomainObjNew(xmlopt);
|
|
|
|
if (!test->vm)
|
|
|
|
goto error;
|
qemu: Fix @vm locking issue when connecting to the monitor
When connecting to qemu's monitor the @vm object is unlocked.
This is justified - connecting may take a long time and we don't
want to wait with the domain object locked. However, just before
the domain object is locked again, the monitor's FD is registered
in the event loop. Therefore, there is a small window where the
event loop has a chance to call a handler for an event that
occurred on the monitor FD but vm is not initalized properly just
yet (i.e. priv->mon is not set). For instance, if there's an
incoming migration, qemu creates its socket but then fails to
initialize (for various reasons, I'm reproducing this by using
hugepages but leaving the HP pool empty) then the following may
happen:
1) qemuConnectMonitor() unlocks @vm
2) qemuMonitorOpen() connects to the monitor socket and by
calling qemuMonitorOpenInternal() which subsequently calls
qemuMonitorRegister() the event handler is installed
3) qemu fails to initialize and exit()-s, which closes the
monitor
4) The even loop sees EOF on the monitor and the control gets to
qemuProcessEventHandler() which locks @vm and calls
processMonitorEOFEvent() which then calls
qemuMonitorLastError(priv->mon). But priv->mon is not set just
yet.
5) qemuMonitorLastError() dereferences NULL pointer
The solution is to unlock the domain object for a shorter time
and most importantly, register event handler with domain object
locked so that any possible event processing is done only after
@vm's private data was properly initialized.
This issue is also mentioned in v4.2.0-99-ga5a777a8ba.
Since we are unlocking @vm and locking it back, another thread
might have destroyed the domain meanwhile. Therefore we have to
check if domain is still active, and we have to do it at the
same place where domain lock is acquired back, i.e. in
qemuMonitorOpen(). This creates a small problem for our test
suite which calls qemuMonitorOpen() directly and passes @vm which
has no definition. This makes virDomainObjIsActive() call crash.
Fortunately, allocating empty domain definition is sufficient.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
2019-10-08 07:24:23 +00:00
|
|
|
if (!(test->vm->def = virDomainDefNew()))
|
|
|
|
goto error;
|
2013-07-25 17:17:44 +00:00
|
|
|
}
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2013-10-09 11:13:45 +00:00
|
|
|
if (virNetSocketNewListenUNIX(path, 0700, geteuid(), getegid(),
|
2012-08-20 13:06:21 +00:00
|
|
|
&test->server) < 0)
|
|
|
|
goto error;
|
|
|
|
|
2013-07-22 14:59:22 +00:00
|
|
|
memset(src, 0, sizeof(*src));
|
|
|
|
src->type = VIR_DOMAIN_CHR_TYPE_UNIX;
|
|
|
|
src->data.nix.path = (char *)path;
|
|
|
|
src->data.nix.listen = false;
|
2013-09-25 14:28:55 +00:00
|
|
|
path = NULL;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
|
|
|
if (virNetSocketListen(test->server, 1) < 0)
|
|
|
|
goto error;
|
|
|
|
|
2013-07-22 14:59:22 +00:00
|
|
|
return test;
|
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
error:
|
2013-09-25 14:28:55 +00:00
|
|
|
VIR_FREE(path);
|
2013-07-22 14:59:22 +00:00
|
|
|
VIR_FREE(tmpdir_template);
|
|
|
|
qemuMonitorTestFree(test);
|
2020-01-06 21:57:49 +00:00
|
|
|
return NULL;
|
2013-07-22 14:59:22 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuMonitorCommonTestInit(qemuMonitorTestPtr test)
|
|
|
|
{
|
2013-07-25 09:48:11 +00:00
|
|
|
int events = VIR_EVENT_HANDLE_READABLE;
|
|
|
|
|
2013-07-22 14:59:22 +00:00
|
|
|
if (!test)
|
|
|
|
return -1;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
|
|
|
if (virNetSocketAccept(test->server, &test->client) < 0)
|
2020-01-06 21:57:49 +00:00
|
|
|
return -1;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
2013-07-22 14:59:22 +00:00
|
|
|
if (!test->client)
|
2020-01-06 21:57:49 +00:00
|
|
|
return -1;
|
2012-09-06 15:14:25 +00:00
|
|
|
|
2013-07-25 09:48:11 +00:00
|
|
|
if (test->outgoingLength > 0)
|
|
|
|
events = VIR_EVENT_HANDLE_WRITABLE;
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
if (virNetSocketAddIOCallback(test->client,
|
2013-07-25 09:48:11 +00:00
|
|
|
events,
|
2012-08-20 13:06:21 +00:00
|
|
|
qemuMonitorTestIO,
|
|
|
|
test,
|
|
|
|
NULL) < 0)
|
2020-01-06 21:57:49 +00:00
|
|
|
return -1;
|
2012-08-20 13:06:21 +00:00
|
|
|
|
|
|
|
virMutexLock(&test->lock);
|
|
|
|
if (virThreadCreate(&test->thread,
|
|
|
|
true,
|
|
|
|
qemuMonitorTestWorker,
|
|
|
|
test) < 0) {
|
|
|
|
virMutexUnlock(&test->lock);
|
2020-01-06 21:57:49 +00:00
|
|
|
return -1;
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
2013-10-02 16:20:18 +00:00
|
|
|
test->started = test->running = true;
|
2012-08-20 13:06:21 +00:00
|
|
|
virMutexUnlock(&test->lock);
|
|
|
|
|
2013-07-22 14:59:22 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define QEMU_JSON_GREETING "{\"QMP\":"\
|
|
|
|
" {\"version\":"\
|
|
|
|
" {\"qemu\":"\
|
|
|
|
" {\"micro\": 1,"\
|
|
|
|
" \"minor\": 0,"\
|
|
|
|
" \"major\": 1"\
|
|
|
|
" },"\
|
|
|
|
" \"package\": \"(qemu-kvm-1.0.1)"\
|
|
|
|
" \"},"\
|
|
|
|
" \"capabilities\": []"\
|
|
|
|
" }"\
|
|
|
|
"}"
|
|
|
|
/* We skip the normal handshake reply of "{\"execute\":\"qmp_capabilities\"}" */
|
|
|
|
|
2020-02-12 14:56:29 +00:00
|
|
|
|
2013-07-22 14:59:22 +00:00
|
|
|
qemuMonitorTestPtr
|
2019-06-14 18:14:23 +00:00
|
|
|
qemuMonitorTestNew(virDomainXMLOptionPtr xmlopt,
|
2013-07-25 17:28:51 +00:00
|
|
|
virDomainObjPtr vm,
|
2013-09-19 11:56:30 +00:00
|
|
|
virQEMUDriverPtr driver,
|
2018-03-22 18:05:26 +00:00
|
|
|
const char *greeting,
|
2020-10-22 17:04:18 +00:00
|
|
|
GHashTable *schema)
|
2013-07-22 14:59:22 +00:00
|
|
|
{
|
|
|
|
qemuMonitorTestPtr test = NULL;
|
|
|
|
virDomainChrSourceDef src;
|
|
|
|
|
2013-09-25 14:30:27 +00:00
|
|
|
memset(&src, 0, sizeof(src));
|
|
|
|
|
2013-07-25 17:17:44 +00:00
|
|
|
if (!(test = qemuMonitorCommonTestNew(xmlopt, vm, &src)))
|
2013-07-22 14:59:22 +00:00
|
|
|
goto error;
|
|
|
|
|
2020-02-12 14:56:29 +00:00
|
|
|
if (!(test->eventThread = virEventThreadNew("mon-test")))
|
|
|
|
goto error;
|
|
|
|
|
2018-03-22 18:05:26 +00:00
|
|
|
test->qapischema = schema;
|
2013-07-22 14:59:22 +00:00
|
|
|
if (!(test->mon = qemuMonitorOpen(test->vm,
|
|
|
|
&src,
|
2019-06-14 18:14:23 +00:00
|
|
|
true,
|
2017-03-11 06:23:42 +00:00
|
|
|
0,
|
2020-02-12 14:54:19 +00:00
|
|
|
virEventThreadGetContext(test->eventThread),
|
2013-07-25 17:26:15 +00:00
|
|
|
&qemuMonitorTestCallbacks,
|
2013-07-25 17:28:51 +00:00
|
|
|
driver)))
|
2013-07-22 14:59:22 +00:00
|
|
|
goto error;
|
|
|
|
|
|
|
|
virObjectLock(test->mon);
|
|
|
|
|
2013-09-19 11:56:30 +00:00
|
|
|
if (!greeting)
|
2019-06-14 18:14:23 +00:00
|
|
|
greeting = QEMU_JSON_GREETING;
|
2013-09-19 11:56:30 +00:00
|
|
|
|
2016-06-07 11:46:01 +00:00
|
|
|
if (qemuMonitorTestAddResponse(test, greeting) < 0)
|
2013-07-22 14:59:22 +00:00
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (qemuMonitorCommonTestInit(test) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
virDomainChrSourceDefClear(&src);
|
|
|
|
|
2012-08-20 13:06:21 +00:00
|
|
|
return test;
|
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
error:
|
2013-07-25 10:08:25 +00:00
|
|
|
virDomainChrSourceDefClear(&src);
|
|
|
|
qemuMonitorTestFree(test);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-06-06 11:46:41 +00:00
|
|
|
|
2016-11-30 08:58:12 +00:00
|
|
|
/**
|
|
|
|
* qemuMonitorTestNewFromFile:
|
|
|
|
* @fileName: File name to load monitor replies from
|
|
|
|
* @xmlopt: XML parser configuration object
|
|
|
|
* @simple: see below
|
|
|
|
*
|
|
|
|
* Create a JSON test monitor simulator object and fill it with replies
|
|
|
|
* specified in @fileName. The file contains JSON reply objects separated by
|
|
|
|
* empty lines. If @simple is true a generic QMP greeting is automatically
|
|
|
|
* added as the first reply, otherwise the first entry in the file is used.
|
|
|
|
*
|
|
|
|
* Returns the monitor object on success; NULL on error.
|
|
|
|
*/
|
2016-06-06 11:46:41 +00:00
|
|
|
qemuMonitorTestPtr
|
|
|
|
qemuMonitorTestNewFromFile(const char *fileName,
|
2016-06-06 14:55:05 +00:00
|
|
|
virDomainXMLOptionPtr xmlopt,
|
|
|
|
bool simple)
|
2016-06-06 11:46:41 +00:00
|
|
|
{
|
|
|
|
qemuMonitorTestPtr test = NULL;
|
2020-07-28 19:57:28 +00:00
|
|
|
g_autofree char *json = NULL;
|
2016-06-06 11:46:41 +00:00
|
|
|
char *tmp;
|
|
|
|
char *singleReply;
|
|
|
|
|
|
|
|
if (virTestLoadFile(fileName, &json) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2019-06-14 18:10:15 +00:00
|
|
|
if (simple && !(test = qemuMonitorTestNewSimple(xmlopt)))
|
2016-06-06 14:55:05 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2016-06-06 11:46:41 +00:00
|
|
|
/* Our JSON parser expects replies to be separated by a newline character.
|
|
|
|
* Hence we must preprocess the file a bit. */
|
|
|
|
tmp = singleReply = json;
|
|
|
|
while ((tmp = strchr(tmp, '\n'))) {
|
|
|
|
/* It is safe to touch tmp[1] since all strings ends with '\0'. */
|
|
|
|
bool eof = !tmp[1];
|
|
|
|
|
|
|
|
if (*(tmp + 1) != '\n') {
|
|
|
|
*tmp = ' ';
|
|
|
|
tmp++;
|
|
|
|
} else {
|
|
|
|
/* Cut off a single reply. */
|
|
|
|
*(tmp + 1) = '\0';
|
|
|
|
|
|
|
|
if (test) {
|
|
|
|
if (qemuMonitorTestAddItem(test, NULL, singleReply) < 0)
|
|
|
|
goto error;
|
|
|
|
} else {
|
|
|
|
/* Create new mocked monitor with our greeting */
|
2019-06-14 18:14:23 +00:00
|
|
|
if (!(test = qemuMonitorTestNew(xmlopt, NULL, NULL,
|
2018-03-22 18:05:26 +00:00
|
|
|
singleReply, NULL)))
|
2016-06-06 11:46:41 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!eof) {
|
|
|
|
/* Move the @tmp and @singleReply. */
|
|
|
|
tmp += 2;
|
|
|
|
singleReply = tmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (eof)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (test && qemuMonitorTestAddItem(test, NULL, singleReply) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
return test;
|
|
|
|
|
|
|
|
error:
|
|
|
|
qemuMonitorTestFree(test);
|
|
|
|
test = NULL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-23 14:57:31 +00:00
|
|
|
/**
|
|
|
|
* qemuMonitorTestAllowUnusedCommands:
|
|
|
|
* @test: test monitor object
|
|
|
|
*
|
|
|
|
* By default all test items/commands must be used by the test. This function
|
|
|
|
* allows to override the requirement for individual tests e.g. if it's necessary
|
|
|
|
* to test some negative scenarios which would not use all commands.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
qemuMonitorTestAllowUnusedCommands(qemuMonitorTestPtr test)
|
|
|
|
{
|
|
|
|
test->allowUnusedCommands = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-29 09:03:02 +00:00
|
|
|
/**
|
|
|
|
* qemuMonitorTestSkipDeprecatedValidation:
|
|
|
|
* @test: test monitor object
|
|
|
|
* @allowRemoved: don't produce errors if command was removed from QMP schema
|
|
|
|
*
|
|
|
|
* By default if the QMP schema is provided all test items/commands are
|
|
|
|
* validated against the schema. This function allows to override the validation
|
|
|
|
* and additionally if @allowRemoved is true and if such a command is no longer
|
|
|
|
* present in the QMP, only a warning is printed.
|
|
|
|
*
|
|
|
|
* '@allowRemoved' must be used only if a suitable replacement is already in
|
|
|
|
* use and the code tests legacy interactions.
|
|
|
|
*
|
|
|
|
* Note that currently '@allowRemoved' influences only removed commands. If an
|
|
|
|
* argument is removed it will still fail validation.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
qemuMonitorTestSkipDeprecatedValidation(qemuMonitorTestPtr test,
|
|
|
|
bool allowRemoved)
|
|
|
|
{
|
|
|
|
test->skipValidationDeprecated = true;
|
|
|
|
test->skipValidationRemoved = allowRemoved;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-30 13:43:50 +00:00
|
|
|
static int
|
|
|
|
qemuMonitorTestFullAddItem(qemuMonitorTestPtr test,
|
|
|
|
const char *filename,
|
|
|
|
const char *command,
|
|
|
|
const char *response,
|
|
|
|
size_t line)
|
|
|
|
{
|
2020-07-28 19:57:28 +00:00
|
|
|
g_autofree char *cmderr = NULL;
|
2016-11-30 13:43:50 +00:00
|
|
|
int ret;
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
cmderr = g_strdup_printf("wrong expected command in %s:%zu: ", filename, line);
|
2016-11-30 13:43:50 +00:00
|
|
|
|
|
|
|
ret = qemuMonitorTestAddItemVerbatim(test, command, cmderr, response);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuMonitorTestNewFromFileFull:
|
|
|
|
* @fileName: File name to load monitor replies from
|
|
|
|
* @driver: qemu driver object
|
|
|
|
* @vm: domain object (may be null if it's not needed by the test)
|
2019-06-07 11:53:45 +00:00
|
|
|
* @qmpschema: QMP schema data hash table if QMP checking is required
|
2016-11-30 13:43:50 +00:00
|
|
|
*
|
|
|
|
* Create a JSON test monitor simulator object and fill it with expected command
|
|
|
|
* sequence and replies specified in @fileName.
|
|
|
|
*
|
|
|
|
* The file contains a sequence of JSON commands and reply objects separated by
|
|
|
|
* empty lines. A command is followed by a reply. The QMP greeting is added
|
|
|
|
* automatically.
|
|
|
|
*
|
|
|
|
* Returns the monitor object on success; NULL on error.
|
|
|
|
*/
|
|
|
|
qemuMonitorTestPtr
|
|
|
|
qemuMonitorTestNewFromFileFull(const char *fileName,
|
|
|
|
virQEMUDriverPtr driver,
|
2019-06-07 11:53:45 +00:00
|
|
|
virDomainObjPtr vm,
|
2020-10-22 17:04:18 +00:00
|
|
|
GHashTable *qmpschema)
|
2016-11-30 13:43:50 +00:00
|
|
|
{
|
|
|
|
qemuMonitorTestPtr ret = NULL;
|
2020-07-28 19:57:28 +00:00
|
|
|
g_autofree char *jsonstr = NULL;
|
2016-11-30 13:43:50 +00:00
|
|
|
char *tmp;
|
|
|
|
size_t line = 0;
|
|
|
|
|
|
|
|
char *command = NULL;
|
|
|
|
char *response = NULL;
|
|
|
|
size_t commandln = 0;
|
|
|
|
|
|
|
|
if (virTestLoadFile(fileName, &jsonstr) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
2019-06-14 18:14:23 +00:00
|
|
|
if (!(ret = qemuMonitorTestNew(driver->xmlopt, vm, driver, NULL,
|
2019-06-07 11:53:45 +00:00
|
|
|
qmpschema)))
|
2016-11-30 13:43:50 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
tmp = jsonstr;
|
|
|
|
command = tmp;
|
|
|
|
while ((tmp = strchr(tmp, '\n'))) {
|
|
|
|
line++;
|
|
|
|
|
2018-05-03 09:38:23 +00:00
|
|
|
/* eof */
|
|
|
|
if (!tmp[1])
|
|
|
|
break;
|
|
|
|
|
2018-05-03 09:43:34 +00:00
|
|
|
/* concatenate block which was broken up for readability */
|
2016-11-30 13:43:50 +00:00
|
|
|
if (*(tmp + 1) != '\n') {
|
|
|
|
*tmp = ' ';
|
|
|
|
tmp++;
|
2018-05-03 09:43:34 +00:00
|
|
|
continue;
|
|
|
|
}
|
2016-11-30 13:43:50 +00:00
|
|
|
|
2018-05-03 09:43:34 +00:00
|
|
|
/* Cut off a single reply. */
|
|
|
|
*(tmp + 1) = '\0';
|
2016-11-30 13:43:50 +00:00
|
|
|
|
2018-05-03 09:43:34 +00:00
|
|
|
if (response) {
|
|
|
|
if (qemuMonitorTestFullAddItem(ret, fileName, command,
|
|
|
|
response, commandln) < 0)
|
|
|
|
goto error;
|
|
|
|
command = NULL;
|
|
|
|
response = NULL;
|
|
|
|
}
|
2016-11-30 13:43:50 +00:00
|
|
|
|
2018-05-03 09:43:34 +00:00
|
|
|
/* Move the @tmp and @singleReply. */
|
|
|
|
tmp += 2;
|
|
|
|
|
|
|
|
if (!command) {
|
|
|
|
commandln = line;
|
|
|
|
command = tmp;
|
|
|
|
} else {
|
|
|
|
response = tmp;
|
2016-11-30 13:43:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (command) {
|
|
|
|
if (!response) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "missing response for command "
|
|
|
|
"on line '%zu' in '%s'", commandln, fileName);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qemuMonitorTestFullAddItem(ret, fileName, command,
|
|
|
|
response, commandln) < 0)
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
error:
|
|
|
|
qemuMonitorTestFree(ret);
|
|
|
|
ret = NULL;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-25 10:08:25 +00:00
|
|
|
qemuMonitorTestPtr
|
|
|
|
qemuMonitorTestNewAgent(virDomainXMLOptionPtr xmlopt)
|
|
|
|
{
|
|
|
|
qemuMonitorTestPtr test = NULL;
|
|
|
|
virDomainChrSourceDef src;
|
|
|
|
|
2013-09-25 14:30:27 +00:00
|
|
|
memset(&src, 0, sizeof(src));
|
|
|
|
|
2013-07-25 17:17:44 +00:00
|
|
|
if (!(test = qemuMonitorCommonTestNew(xmlopt, NULL, &src)))
|
2013-07-25 10:08:25 +00:00
|
|
|
goto error;
|
|
|
|
|
2020-02-12 14:56:29 +00:00
|
|
|
if (!(test->eventThread = virEventThreadNew("agent-test")))
|
|
|
|
goto error;
|
|
|
|
|
2013-07-25 10:08:25 +00:00
|
|
|
if (!(test->agent = qemuAgentOpen(test->vm,
|
|
|
|
&src,
|
2020-02-12 14:54:19 +00:00
|
|
|
virEventThreadGetContext(test->eventThread),
|
2020-03-05 14:47:01 +00:00
|
|
|
&qemuMonitorTestAgentCallbacks,
|
|
|
|
false)))
|
2013-07-25 10:08:25 +00:00
|
|
|
goto error;
|
|
|
|
|
|
|
|
virObjectLock(test->agent);
|
|
|
|
|
|
|
|
if (qemuMonitorCommonTestInit(test) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
virDomainChrSourceDefClear(&src);
|
|
|
|
|
|
|
|
return test;
|
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
error:
|
2013-07-25 10:08:25 +00:00
|
|
|
virDomainChrSourceDefClear(&src);
|
2012-08-20 13:06:21 +00:00
|
|
|
qemuMonitorTestFree(test);
|
2013-07-22 14:59:22 +00:00
|
|
|
return NULL;
|
2012-08-20 13:06:21 +00:00
|
|
|
}
|
|
|
|
|
2013-07-18 14:17:31 +00:00
|
|
|
|
|
|
|
qemuMonitorPtr
|
|
|
|
qemuMonitorTestGetMonitor(qemuMonitorTestPtr test)
|
2012-08-20 13:06:21 +00:00
|
|
|
{
|
|
|
|
return test->mon;
|
|
|
|
}
|
2013-07-25 10:08:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
qemuAgentPtr
|
|
|
|
qemuMonitorTestGetAgent(qemuMonitorTestPtr test)
|
|
|
|
{
|
|
|
|
return test->agent;
|
|
|
|
}
|
2018-04-04 14:45:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
virDomainObjPtr
|
|
|
|
qemuMonitorTestGetDomainObj(qemuMonitorTestPtr test)
|
|
|
|
{
|
|
|
|
return test->vm;
|
|
|
|
}
|