/* * Copyright (C) 2011-2013 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 "testutils.h" #include "qemumonitortestutils.h" #include "virthread.h" #include "qemu/qemu_processpriv.h" #include "qemu/qemu_monitor.h" #include "qemu/qemu_agent.h" #include "rpc/virnetsocket.h" #include "viralloc.h" #include "virlog.h" #include "virerror.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_NONE VIR_LOG_INIT("tests.qemumonitortestutils"); struct _qemuMonitorTestItem { qemuMonitorTestResponseCallback cb; void *opaque; virFreeCallback freecb; }; struct _qemuMonitorTest { virMutex lock; virThread thread; bool json; bool quit; bool running; bool started; char *incoming; size_t incomingLength; size_t incomingCapacity; char *outgoing; size_t outgoingLength; size_t outgoingCapacity; virNetSocketPtr server; virNetSocketPtr client; qemuMonitorPtr mon; qemuAgentPtr agent; char *tmpdir; size_t nitems; qemuMonitorTestItemPtr *items; virDomainObjPtr vm; }; static void qemuMonitorTestItemFree(qemuMonitorTestItemPtr item) { if (!item) return; if (item->freecb) (item->freecb)(item->opaque); VIR_FREE(item); } /* * Appends data for a reply to the outgoing buffer */ int qemuMonitorTestAddResponse(qemuMonitorTestPtr test, const char *response) { size_t want = strlen(response) + 2; size_t have = test->outgoingCapacity - test->outgoingLength; VIR_DEBUG("Adding response to monitor command: '%s", response); if (have < want) { size_t need = want - have; if (VIR_EXPAND_N(test->outgoing, test->outgoingCapacity, need) < 0) 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; } static int qemuMonitorTestAddErrorResponse(qemuMonitorTestPtr test, const char *usermsg) { virBuffer buf = VIR_BUFFER_INITIALIZER; char *escapemsg = NULL; char *jsonmsg = NULL; const char *monmsg = NULL; char *tmp; int ret = -1; if (!usermsg) usermsg = "unexpected command"; if (test->json || test->agent) { virBufferEscape(&buf, '\\', "\"", "%s", usermsg); if (virBufferCheckError(&buf) < 0) goto error; escapemsg = virBufferContentAndReset(&buf); /* replace newline/carriage return with space */ tmp = escapemsg; while (*tmp) { if (*tmp == '\r' || *tmp == '\n') *tmp = ' '; tmp++; } /* format the JSON error message */ if (virAsprintf(&jsonmsg, "{ \"error\": " " { \"desc\": \"%s\", " " \"class\": \"UnexpectedCommand\" } }", escapemsg) < 0) goto error; monmsg = jsonmsg; } else { monmsg = usermsg; } ret = qemuMonitorTestAddResponse(test, monmsg); error: VIR_FREE(escapemsg); VIR_FREE(jsonmsg); return ret; } static int qemuMonitorTestAddUnexpectedErrorResponse(qemuMonitorTestPtr test, const char *command) { char *msg; int ret; if (virAsprintf(&msg, "unexpected command: '%s'", command) < 0) return -1; ret = qemuMonitorTestAddErrorResponse(test, msg); VIR_FREE(msg); return ret; } int qemuMonitorTestAddInvalidCommandResponse(qemuMonitorTestPtr test, const char *expectedcommand, const char *actualcommand) { char *msg; int ret; if (virAsprintf(&msg, "expected command '%s' got '%s'", expectedcommand, actualcommand) < 0) return -1; ret = qemuMonitorTestAddErrorResponse(test, msg); VIR_FREE(msg); return ret; } int ATTRIBUTE_FMT_PRINTF(2, 3) qemuMonitorReportError(qemuMonitorTestPtr test, const char *errmsg, ...) { va_list msgargs; char *msg = NULL; char *jsonmsg = NULL; int ret = -1; va_start(msgargs, errmsg); if (virVasprintf(&msg, errmsg, msgargs) < 0) goto cleanup; if (test->agent || test->json) { char *tmp = msg; msg = qemuMonitorEscapeArg(tmp); VIR_FREE(tmp); if (!msg) goto cleanup; if (virAsprintf(&jsonmsg, "{ \"error\": " " { \"desc\": \"%s\", " " \"class\": \"UnexpectedCommand\" } }", msg) < 0) goto cleanup; } else { if (virAsprintf(&jsonmsg, "error: '%s'", msg) < 0) goto cleanup; } ret = qemuMonitorTestAddResponse(test, jsonmsg); cleanup: va_end(msgargs); VIR_FREE(msg); VIR_FREE(jsonmsg); return ret; } static int qemuMonitorTestProcessCommand(qemuMonitorTestPtr test, const char *cmdstr) { int ret; VIR_DEBUG("Processing string from monitor handler: '%s", cmdstr); if (test->nitems == 0) { return qemuMonitorTestAddUnexpectedErrorResponse(test, cmdstr); } else { 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; } return ret; } /* * 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 (test->quit) { virMutexUnlock(&test->lock); return; } 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, "\n")) || (!test->json && (t2 = strstr(t1, "\r")))) { *t2 = '\0'; if (qemuMonitorTestProcessCommand(test, t1) < 0) { err = true; goto cleanup; } t1 = t2 + 1; } 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) { virMutexLock(&test->lock); test->quit = true; break; } virMutexLock(&test->lock); } test->running = false; virMutexUnlock(&test->lock); return; } static void qemuMonitorTestFreeTimer(int timer ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { /* nothing to be done here */ } void qemuMonitorTestFree(qemuMonitorTestPtr test) { size_t i; int timer = -1; if (!test) return; virMutexLock(&test->lock); if (test->running) { test->quit = true; /* HACK: Add a dummy timeout to break event loop */ timer = virEventAddTimeout(0, qemuMonitorTestFreeTimer, NULL, NULL); } virMutexUnlock(&test->lock); if (test->client) { virNetSocketRemoveIOCallback(test->client); virNetSocketClose(test->client); virObjectUnref(test->client); } virObjectUnref(test->server); if (test->mon) { virObjectUnlock(test->mon); qemuMonitorClose(test->mon); } if (test->agent) { virObjectUnlock(test->agent); qemuAgentClose(test->agent); } virObjectUnref(test->vm); if (test->started) virThreadJoin(&test->thread); if (timer != -1) virEventRemoveTimeout(timer); VIR_FREE(test->incoming); VIR_FREE(test->outgoing); for (i = 0; i < test->nitems; i++) qemuMonitorTestItemFree(test->items[i]); VIR_FREE(test->items); if (test->tmpdir && rmdir(test->tmpdir) < 0) VIR_WARN("Failed to remove tempdir: %s", strerror(errno)); VIR_FREE(test->tmpdir); virMutexDestroy(&test->lock); VIR_FREE(test); } int qemuMonitorTestAddHandler(qemuMonitorTestPtr test, qemuMonitorTestResponseCallback cb, void *opaque, virFreeCallback freecb) { qemuMonitorTestItemPtr item; if (VIR_ALLOC(item) < 0) goto error; item->cb = cb; item->freecb = freecb; item->opaque = opaque; virMutexLock(&test->lock); if (VIR_APPEND_ELEMENT(test->items, test->nitems, item) < 0) { virMutexUnlock(&test->lock); goto error; } virMutexUnlock(&test->lock); return 0; error: if (freecb) (freecb)(opaque); VIR_FREE(item); return -1; } void * qemuMonitorTestItemGetPrivateData(qemuMonitorTestItemPtr item) { return item ? item->opaque : NULL; } typedef struct _qemuMonitorTestCommandArgs qemuMonitorTestCommandArgs; typedef qemuMonitorTestCommandArgs *qemuMonitorTestCommandArgsPtr; struct _qemuMonitorTestCommandArgs { char *argname; char *argval; }; struct qemuMonitorTestHandlerData { char *command_name; char *cmderr; char *response; size_t nargs; qemuMonitorTestCommandArgsPtr args; char *expectArgs; }; static void qemuMonitorTestHandlerDataFree(void *opaque) { struct qemuMonitorTestHandlerData *data = opaque; size_t i; if (!data) return; for (i = 0; i < data->nargs; i++) { VIR_FREE(data->args[i].argname); VIR_FREE(data->args[i].argval); } VIR_FREE(data->command_name); VIR_FREE(data->cmderr); VIR_FREE(data->response); VIR_FREE(data->args); VIR_FREE(data->expectArgs); VIR_FREE(data); } static int qemuMonitorTestProcessCommandDefault(qemuMonitorTestPtr test, qemuMonitorTestItemPtr item, const char *cmdstr) { struct qemuMonitorTestHandlerData *data = item->opaque; virJSONValuePtr val = NULL; char *cmdcopy = NULL; const char *cmdname; char *tmp; int ret = -1; if (test->agent || test->json) { if (!(val = virJSONValueFromString(cmdstr))) return -1; if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) { ret = qemuMonitorReportError(test, "Missing command name in %s", cmdstr); goto cleanup; } } else { if (VIR_STRDUP(cmdcopy, cmdstr) < 0) return -1; cmdname = cmdcopy; if (!(tmp = strchr(cmdcopy, ' '))) { ret = qemuMonitorReportError(test, "Cannot find command name in '%s'", cmdstr); goto cleanup; } *tmp = '\0'; } if (data->command_name && STRNEQ(data->command_name, cmdname)) ret = qemuMonitorTestAddInvalidCommandResponse(test, data->command_name, cmdname); else ret = qemuMonitorTestAddResponse(test, data->response); cleanup: VIR_FREE(cmdcopy); virJSONValueFree(val); return ret; } int qemuMonitorTestAddItem(qemuMonitorTestPtr test, const char *command_name, const char *response) { struct qemuMonitorTestHandlerData *data; if (VIR_ALLOC(data) < 0) return -1; if (VIR_STRDUP(data->command_name, command_name) < 0 || VIR_STRDUP(data->response, response) < 0) { qemuMonitorTestHandlerDataFree(data); return -1; } return qemuMonitorTestAddHandler(test, qemuMonitorTestProcessCommandDefault, data, qemuMonitorTestHandlerDataFree); } static int qemuMonitorTestProcessCommandVerbatim(qemuMonitorTestPtr test, qemuMonitorTestItemPtr item, const char *cmdstr) { struct qemuMonitorTestHandlerData *data = item->opaque; char *reformatted = NULL; char *errmsg = NULL; int ret = -1; /* JSON strings will be reformatted to simplify checking */ if (test->json || test->agent) { if (!(reformatted = virJSONStringReformat(cmdstr, false))) return -1; cmdstr = reformatted; } if (STREQ(data->command_name, cmdstr)) { ret = qemuMonitorTestAddResponse(test, data->response); } else { if (data->cmderr) { if (virAsprintf(&errmsg, "%s: %s", data->cmderr, cmdstr) < 0) goto cleanup; ret = qemuMonitorTestAddErrorResponse(test, errmsg); } else { ret = qemuMonitorTestAddInvalidCommandResponse(test, data->command_name, cmdstr); } } cleanup: VIR_FREE(errmsg); VIR_FREE(reformatted); 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; if (VIR_ALLOC(data) < 0) return -1; if (VIR_STRDUP(data->response, response) < 0 || VIR_STRDUP(data->cmderr, cmderr) < 0) goto error; if (test->json || test->agent) data->command_name = virJSONStringReformat(command, false); else ignore_value(VIR_STRDUP(data->command_name, command)); if (!data->command_name) goto error; return qemuMonitorTestAddHandler(test, qemuMonitorTestProcessCommandVerbatim, data, qemuMonitorTestHandlerDataFree); error: qemuMonitorTestHandlerDataFree(data); return -1; } static int qemuMonitorTestProcessGuestAgentSync(qemuMonitorTestPtr test, qemuMonitorTestItemPtr item ATTRIBUTE_UNUSED, const char *cmdstr) { virJSONValuePtr val = NULL; virJSONValuePtr args; unsigned long long id; const char *cmdname; char *retmsg = NULL; int ret = -1; if (!(val = virJSONValueFromString(cmdstr))) return -1; if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) { ret = qemuMonitorReportError(test, "Missing guest-sync command name"); goto cleanup; } if (STRNEQ(cmdname, "guest-sync")) { ret = qemuMonitorTestAddInvalidCommandResponse(test, "guest-sync", cmdname); goto cleanup; } if (!(args = virJSONValueObjectGet(val, "arguments"))) { ret = qemuMonitorReportError(test, "Missing arguments for guest-sync"); goto cleanup; } if (virJSONValueObjectGetNumberUlong(args, "id", &id)) { ret = qemuMonitorReportError(test, "Missing id for guest sync"); goto cleanup; } if (virAsprintf(&retmsg, "{\"return\":%llu}", id) < 0) goto cleanup; ret = qemuMonitorTestAddResponse(test, retmsg); cleanup: virJSONValueFree(val); VIR_FREE(retmsg); 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, qemuMonitorTestProcessGuestAgentSync, NULL, NULL); } static int qemuMonitorTestProcessCommandWithArgs(qemuMonitorTestPtr test, qemuMonitorTestItemPtr item, const char *cmdstr) { struct qemuMonitorTestHandlerData *data = item->opaque; virJSONValuePtr val = NULL; virJSONValuePtr args; virJSONValuePtr argobj; char *argstr = NULL; const char *cmdname; size_t i; int ret = -1; if (!(val = virJSONValueFromString(cmdstr))) return -1; if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) { ret = qemuMonitorReportError(test, "Missing command name in %s", cmdstr); goto cleanup; } if (data->command_name && STRNEQ(data->command_name, cmdname)) { ret = qemuMonitorTestAddInvalidCommandResponse(test, data->command_name, cmdname); goto cleanup; } if (!(args = virJSONValueObjectGet(val, "arguments"))) { ret = qemuMonitorReportError(test, "Missing arguments section for command '%s'", NULLSTR(data->command_name)); goto cleanup; } /* validate the args */ for (i = 0; i < data->nargs; i++) { qemuMonitorTestCommandArgsPtr arg = &data->args[i]; if (!(argobj = virJSONValueObjectGet(args, arg->argname))) { ret = qemuMonitorReportError(test, "Missing argument '%s' for command '%s'", arg->argname, NULLSTR(data->command_name)); goto cleanup; } /* convert the argument to string */ if (!(argstr = virJSONValueToString(argobj, false))) goto cleanup; /* verify that the argument value is expected */ if (STRNEQ(argstr, arg->argval)) { ret = qemuMonitorReportError(test, "Invalid value of argument '%s' " "of command '%s': " "expected '%s' got '%s'", arg->argname, NULLSTR(data->command_name), arg->argval, argstr); goto cleanup; } VIR_FREE(argstr); } /* arguments checked out, return the response */ ret = qemuMonitorTestAddResponse(test, data->response); cleanup: VIR_FREE(argstr); virJSONValueFree(val); return ret; } /* 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); if (VIR_ALLOC(data) < 0) goto error; if (VIR_STRDUP(data->command_name, cmdname) < 0 || VIR_STRDUP(data->response, response) < 0) goto error; 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; if (VIR_STRDUP(data->args[i].argname, argname) < 0 || VIR_STRDUP(data->args[i].argval, argval) < 0) goto error; } va_end(args); return qemuMonitorTestAddHandler(test, qemuMonitorTestProcessCommandWithArgs, data, qemuMonitorTestHandlerDataFree); error: va_end(args); qemuMonitorTestHandlerDataFree(data); return -1; } static int qemuMonitorTestProcessCommandWithArgStr(qemuMonitorTestPtr test, qemuMonitorTestItemPtr item, const char *cmdstr) { struct qemuMonitorTestHandlerData *data = item->opaque; virJSONValuePtr val = NULL; virJSONValuePtr args; char *argstr = NULL; const char *cmdname; int ret = -1; if (!(val = virJSONValueFromString(cmdstr))) return -1; if (!(cmdname = virJSONValueObjectGetString(val, "execute"))) { ret = qemuMonitorReportError(test, "Missing command name in %s", cmdstr); goto cleanup; } if (STRNEQ(data->command_name, cmdname)) { ret = qemuMonitorTestAddInvalidCommandResponse(test, data->command_name, cmdname); goto cleanup; } if (!(args = virJSONValueObjectGet(val, "arguments"))) { ret = qemuMonitorReportError(test, "Missing arguments section for command '%s'", data->command_name); goto cleanup; } /* convert the arguments to string */ if (!(argstr = virJSONValueToString(args, false))) goto cleanup; /* verify that the argument value is expected */ if (STRNEQ(argstr, data->expectArgs)) { ret = qemuMonitorReportError(test, "%s: expected arguments: '%s', got: '%s'", data->command_name, data->expectArgs, argstr); goto cleanup; } /* arguments checked out, return the response */ ret = qemuMonitorTestAddResponse(test, data->response); cleanup: VIR_FREE(argstr); virJSONValueFree(val); return ret; } /** * 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; if (VIR_ALLOC(data) < 0) goto error; if (VIR_STRDUP(data->command_name, cmdname) < 0 || VIR_STRDUP(data->response, response) < 0 || VIR_STRDUP(data->expectArgs, cmdargs) < 0) goto error; if (apostrophe) { char *tmp = data->expectArgs; while (*tmp != '\0') { if (*tmp == '\'') *tmp = '"'; tmp++; } } return qemuMonitorTestAddHandler(test, qemuMonitorTestProcessCommandWithArgStr, data, qemuMonitorTestHandlerDataFree); error: qemuMonitorTestHandlerDataFree(data); return -1; } static void qemuMonitorTestEOFNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { } static void qemuMonitorTestErrorNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { } static qemuMonitorCallbacks qemuMonitorTestCallbacks = { .eofNotify = qemuMonitorTestEOFNotify, .errorNotify = qemuMonitorTestErrorNotify, .domainDeviceDeleted = qemuProcessHandleDeviceDeleted, }; static void qemuMonitorTestAgentNotify(qemuAgentPtr agent ATTRIBUTE_UNUSED, virDomainObjPtr vm ATTRIBUTE_UNUSED) { } static qemuAgentCallbacks qemuMonitorTestAgentCallbacks = { .eofNotify = qemuMonitorTestAgentNotify, .errorNotify = qemuMonitorTestAgentNotify, }; static qemuMonitorTestPtr qemuMonitorCommonTestNew(virDomainXMLOptionPtr xmlopt, virDomainObjPtr vm, virDomainChrSourceDefPtr src) { qemuMonitorTestPtr test = NULL; char *path = NULL; char *tmpdir_template = NULL; if (VIR_ALLOC(test) < 0) goto error; if (virMutexInit(&test->lock) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "Cannot initialize mutex"); VIR_FREE(test); return NULL; } if (VIR_STRDUP(tmpdir_template, "/tmp/libvirt_XXXXXX") < 0) goto error; if (!(test->tmpdir = mkdtemp(tmpdir_template))) { virReportSystemError(errno, "%s", "Failed to create temporary directory"); goto error; } tmpdir_template = NULL; if (virAsprintf(&path, "%s/qemumonitorjsontest.sock", test->tmpdir) < 0) goto error; if (vm) { test->vm = virObjectRef(vm); } else { test->vm = virDomainObjNew(xmlopt); if (!test->vm) goto error; } if (virNetSocketNewListenUNIX(path, 0700, geteuid(), getegid(), &test->server) < 0) goto error; memset(src, 0, sizeof(*src)); src->type = VIR_DOMAIN_CHR_TYPE_UNIX; src->data.nix.path = (char *)path; src->data.nix.listen = false; path = NULL; if (virNetSocketListen(test->server, 1) < 0) goto error; cleanup: return test; error: VIR_FREE(path); VIR_FREE(tmpdir_template); qemuMonitorTestFree(test); test = NULL; goto cleanup; } static int qemuMonitorCommonTestInit(qemuMonitorTestPtr test) { int events = VIR_EVENT_HANDLE_READABLE; if (!test) return -1; if (virNetSocketAccept(test->server, &test->client) < 0) goto error; if (!test->client) goto error; if (test->outgoingLength > 0) events = VIR_EVENT_HANDLE_WRITABLE; if (virNetSocketAddIOCallback(test->client, events, qemuMonitorTestIO, test, NULL) < 0) goto error; virMutexLock(&test->lock); if (virThreadCreate(&test->thread, true, qemuMonitorTestWorker, test) < 0) { virMutexUnlock(&test->lock); goto error; } test->started = test->running = true; virMutexUnlock(&test->lock); return 0; error: return -1; } #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\"}" */ #define QEMU_TEXT_GREETING "QEMU 1.0,1 monitor - type 'help' for more information" qemuMonitorTestPtr qemuMonitorTestNew(bool json, virDomainXMLOptionPtr xmlopt, virDomainObjPtr vm, virQEMUDriverPtr driver, const char *greeting) { qemuMonitorTestPtr test = NULL; virDomainChrSourceDef src; memset(&src, 0, sizeof(src)); if (!(test = qemuMonitorCommonTestNew(xmlopt, vm, &src))) goto error; test->json = json; if (!(test->mon = qemuMonitorOpen(test->vm, &src, json, 0, &qemuMonitorTestCallbacks, driver))) goto error; virObjectLock(test->mon); if (!greeting) greeting = json ? QEMU_JSON_GREETING : QEMU_TEXT_GREETING; if (qemuMonitorTestAddResponse(test, greeting) < 0) goto error; if (qemuMonitorCommonTestInit(test) < 0) goto error; virDomainChrSourceDefClear(&src); return test; error: virDomainChrSourceDefClear(&src); qemuMonitorTestFree(test); return NULL; } /** * 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. */ qemuMonitorTestPtr qemuMonitorTestNewFromFile(const char *fileName, virDomainXMLOptionPtr xmlopt, bool simple) { qemuMonitorTestPtr test = NULL; char *json = NULL; char *tmp; char *singleReply; if (virTestLoadFile(fileName, &json) < 0) goto cleanup; if (simple && !(test = qemuMonitorTestNewSimple(true, xmlopt))) goto cleanup; /* 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 */ if (!(test = qemuMonitorTestNew(true, xmlopt, NULL, NULL, singleReply))) 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: VIR_FREE(json); return test; error: qemuMonitorTestFree(test); test = NULL; goto cleanup; } static int qemuMonitorTestFullAddItem(qemuMonitorTestPtr test, const char *filename, const char *command, const char *response, size_t line) { char *cmderr; int ret; if (virAsprintf(&cmderr, "wrong expected command in %s:%zu: ", filename, line) < 0) return -1; ret = qemuMonitorTestAddItemVerbatim(test, command, cmderr, response); VIR_FREE(cmderr); 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) * * 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, virDomainObjPtr vm) { qemuMonitorTestPtr ret = NULL; char *jsonstr = NULL; char *tmp; size_t line = 0; char *command = NULL; char *response = NULL; size_t commandln = 0; if (virTestLoadFile(fileName, &jsonstr) < 0) return NULL; if (!(ret = qemuMonitorTestNew(true, driver->xmlopt, vm, driver, NULL))) goto cleanup; tmp = jsonstr; command = tmp; while ((tmp = strchr(tmp, '\n'))) { bool eof = !tmp[1]; line++; if (*(tmp + 1) != '\n') { *tmp = ' '; tmp++; } else { /* Cut off a single reply. */ *(tmp + 1) = '\0'; if (response) { if (qemuMonitorTestFullAddItem(ret, fileName, command, response, commandln) < 0) goto error; command = NULL; response = NULL; } if (!eof) { /* Move the @tmp and @singleReply. */ tmp += 2; if (!command) { commandln = line; command = tmp; } else { response = tmp; } } } if (eof) break; } 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: VIR_FREE(jsonstr); return ret; error: qemuMonitorTestFree(ret); ret = NULL; goto cleanup; } qemuMonitorTestPtr qemuMonitorTestNewAgent(virDomainXMLOptionPtr xmlopt) { qemuMonitorTestPtr test = NULL; virDomainChrSourceDef src; memset(&src, 0, sizeof(src)); if (!(test = qemuMonitorCommonTestNew(xmlopt, NULL, &src))) goto error; if (!(test->agent = qemuAgentOpen(test->vm, &src, &qemuMonitorTestAgentCallbacks))) goto error; virObjectLock(test->agent); if (qemuMonitorCommonTestInit(test) < 0) goto error; virDomainChrSourceDefClear(&src); return test; error: virDomainChrSourceDefClear(&src); qemuMonitorTestFree(test); return NULL; } qemuMonitorPtr qemuMonitorTestGetMonitor(qemuMonitorTestPtr test) { return test->mon; } qemuAgentPtr qemuMonitorTestGetAgent(qemuMonitorTestPtr test) { return test->agent; }