/* * 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 "qemumonitortestutils.h" #include "virthread.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 struct _qemuMonitorTestItem { qemuMonitorTestResponseCallback cb; void *opaque; virFreeCallback freecb; }; 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; 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 qemuMonitorTestAddReponse(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; } int qemuMonitorTestAddUnexpectedErrorResponse(qemuMonitorTestPtr test) { if (test->agent || test->json) { return qemuMonitorTestAddReponse(test, "{ \"error\": " " { \"desc\": \"Unexpected command\", " " \"class\": \"UnexpectedCommand\" } }"); } else { return qemuMonitorTestAddReponse(test, "unexpected command"); } } 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) { if (virAsprintf(&jsonmsg, "{ \"error\": " " { \"desc\": \"%s\", " " \"class\": \"UnexpectedCommand\" } }", msg) < 0) goto cleanup; } else { if (virAsprintf(&jsonmsg, "error: '%s'", msg) < 0) goto cleanup; } ret = qemuMonitorTestAddReponse(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); } 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"))) { *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->running) 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 *response; size_t nargs; qemuMonitorTestCommandArgsPtr args; }; 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->response); VIR_FREE(data->args); 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 (STRNEQ(data->command_name, cmdname)) ret = qemuMonitorTestAddUnexpectedErrorResponse(test); else ret = qemuMonitorTestAddReponse(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 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 = qemuMonitorTestAddUnexpectedErrorResponse(test); 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 = qemuMonitorTestAddReponse(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 (STRNEQ(data->command_name, cmdname)) { ret = qemuMonitorTestAddUnexpectedErrorResponse(test); goto cleanup; } if (!(args = virJSONValueObjectGet(val, "arguments"))) { ret = qemuMonitorReportError(test, "Missing arguments section for command '%s'", 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, 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, data->command_name, arg->argval, argstr); goto cleanup; } VIR_FREE(argstr); } /* arguments checked out, return the response */ ret = qemuMonitorTestAddReponse(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 void qemuMonitorTestEOFNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm ATTRIBUTE_UNUSED) { } static void qemuMonitorTestErrorNotify(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm ATTRIBUTE_UNUSED) { } static qemuMonitorCallbacks qemuMonitorTestCallbacks = { .eofNotify = qemuMonitorTestEOFNotify, .errorNotify = qemuMonitorTestErrorNotify, }; static void qemuMonitorTestAgentNotify(qemuAgentPtr agent ATTRIBUTE_UNUSED, virDomainObjPtr vm ATTRIBUTE_UNUSED) { } static qemuAgentCallbacks qemuMonitorTestAgentCallbacks = { .eofNotify = qemuMonitorTestAgentNotify, .errorNotify = qemuMonitorTestAgentNotify, }; static qemuMonitorTestPtr qemuMonitorCommonTestNew(virDomainXMLOptionPtr xmlopt, 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 (!(test->vm = virDomainObjNew(xmlopt))) goto error; if (virNetSocketNewListenUNIX(path, 0700, getuid(), getgid(), &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; if (virNetSocketListen(test->server, 1) < 0) goto error; cleanup: return test; error: 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->running = true; virMutexUnlock(&test->lock); return 0; error: qemuMonitorTestFree(test); 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) { qemuMonitorTestPtr test = NULL; virDomainChrSourceDef src; if (!(test = qemuMonitorCommonTestNew(xmlopt, &src))) goto error; test->json = json; if (!(test->mon = qemuMonitorOpen(test->vm, &src, json, &qemuMonitorTestCallbacks))) goto error; virObjectLock(test->mon); if (qemuMonitorTestAddReponse(test, json ? QEMU_JSON_GREETING : QEMU_TEXT_GREETING) < 0) goto error; if (qemuMonitorCommonTestInit(test) < 0) goto error; virDomainChrSourceDefClear(&src); return test; error: virDomainChrSourceDefClear(&src); qemuMonitorTestFree(test); return NULL; } qemuMonitorTestPtr qemuMonitorTestNewAgent(virDomainXMLOptionPtr xmlopt) { qemuMonitorTestPtr test = NULL; virDomainChrSourceDef src; if (!(test = qemuMonitorCommonTestNew(xmlopt, &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; }