2011-10-05 17:31:54 +00:00
|
|
|
/*
|
2014-03-07 13:38:51 +00:00
|
|
|
* qemu_agent.c: interaction with QEMU guest agent
|
2011-10-05 17:31:54 +00:00
|
|
|
*
|
2014-03-18 08:15:21 +00:00
|
|
|
* Copyright (C) 2006-2014 Red Hat, Inc.
|
2011-10-05 17:31:54 +00:00
|
|
|
* Copyright (C) 2006 Daniel P. Berrange
|
|
|
|
*
|
|
|
|
* 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-07-21 10:06:23 +00:00
|
|
|
* <http://www.gnu.org/licenses/>.
|
2011-10-05 17:31:54 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
|
|
#include "qemu_agent.h"
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
#include "qemu_domain.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"
|
2012-12-12 17:53:50 +00:00
|
|
|
#include "virjson.h"
|
2011-10-05 17:31:54 +00:00
|
|
|
#include "virfile.h"
|
2012-09-24 16:54:51 +00:00
|
|
|
#include "virprocess.h"
|
2012-02-01 14:44:53 +00:00
|
|
|
#include "virtime.h"
|
2012-07-11 13:35:47 +00:00
|
|
|
#include "virobject.h"
|
2013-04-03 10:36:23 +00:00
|
|
|
#include "virstring.h"
|
2019-04-01 10:14:26 +00:00
|
|
|
#include "virenum.h"
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
|
2014-02-28 12:16:17 +00:00
|
|
|
VIR_LOG_INIT("qemu.qemu_agent");
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
#define LINE_ENDING "\n"
|
|
|
|
|
|
|
|
#define DEBUG_IO 0
|
|
|
|
#define DEBUG_RAW_IO 0
|
|
|
|
|
2018-03-01 14:55:26 +00:00
|
|
|
/* We read from QEMU until seeing a \r\n pair to indicate a
|
|
|
|
* completed reply or event. To avoid memory denial-of-service
|
|
|
|
* though, we must have a size limit on amount of data we
|
|
|
|
* buffer. 10 MB is large enough that it ought to cope with
|
|
|
|
* normal QEMU replies, and small enough that we're not
|
|
|
|
* consuming unreasonable mem.
|
|
|
|
*/
|
|
|
|
#define QEMU_AGENT_MAX_RESPONSE (10 * 1024 * 1024)
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
/* When you are the first to uncomment this,
|
|
|
|
* don't forget to uncomment the corresponding
|
|
|
|
* part in qemuAgentIOProcessEvent as well.
|
|
|
|
*
|
|
|
|
static struct {
|
|
|
|
const char *type;
|
|
|
|
void (*handler)(qemuAgentPtr mon, virJSONValuePtr data);
|
|
|
|
} eventHandlers[] = {
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
|
|
|
|
typedef struct _qemuAgentMessage qemuAgentMessage;
|
|
|
|
typedef qemuAgentMessage *qemuAgentMessagePtr;
|
|
|
|
|
|
|
|
struct _qemuAgentMessage {
|
|
|
|
char *txBuffer;
|
|
|
|
int txOffset;
|
|
|
|
int txLength;
|
|
|
|
|
|
|
|
/* Used by the JSON monitor to hold reply / error */
|
|
|
|
char *rxBuffer;
|
|
|
|
int rxLength;
|
|
|
|
void *rxObject;
|
|
|
|
|
|
|
|
/* True if rxBuffer / rxObject are ready, or a
|
|
|
|
* fatal error occurred on the monitor channel
|
|
|
|
*/
|
|
|
|
bool finished;
|
2016-09-16 10:35:33 +00:00
|
|
|
/* true for sync command */
|
|
|
|
bool sync;
|
2016-09-16 10:35:34 +00:00
|
|
|
/* id of the issued sync comand */
|
|
|
|
unsigned long long id;
|
2016-09-16 10:35:35 +00:00
|
|
|
bool first;
|
2011-10-05 17:31:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct _qemuAgent {
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectLockable parent;
|
2012-07-11 13:35:47 +00:00
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
virCond notify;
|
|
|
|
|
|
|
|
int fd;
|
|
|
|
int watch;
|
|
|
|
|
processSerialChangedEvent: Close agent monitor early
https://bugzilla.redhat.com/show_bug.cgi?id=890648
So, imagine you've issued an API that involves guest agent. For
instance, you want to query guest's IP addresses. So the API acquires
QUERY_JOB, locks the guest agent and issues the agent command.
However, for some reason, guest agent replies to initial ping
correctly, but then crashes tragically while executing real command
(in this case guest-network-get-interfaces). Since initial ping went
well, libvirt thinks guest agent is accessible and awaits reply to the
real command. But it will never come. What will is a monitor event.
Our handler (processSerialChangedEvent) will try to acquire
MODIFY_JOB, which will fail obviously because the other thread that's
executing the API already holds a job. So the event handler exits
early, and the QUERY_JOB is never released nor ended.
The way how to solve this is to put flag somewhere in the monitor
internals. The flag is called @running and agent commands are issued
iff the flag is set. The flag itself is set when we connect to the
agent socket. And unset whenever we see DISCONNECT event from the
agent. Moreover, we must wake up all the threads waiting for the
agent. This is done by signalizing the condition they're waiting on.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2015-05-07 09:19:38 +00:00
|
|
|
bool running;
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
virDomainObjPtr vm;
|
|
|
|
|
|
|
|
qemuAgentCallbacksPtr cb;
|
|
|
|
|
|
|
|
/* If there's a command being processed this will be
|
|
|
|
* non-NULL */
|
|
|
|
qemuAgentMessagePtr msg;
|
|
|
|
|
|
|
|
/* Buffer incoming data ready for Agent monitor
|
|
|
|
* code to process & find message boundaries */
|
|
|
|
size_t bufferOffset;
|
|
|
|
size_t bufferLength;
|
|
|
|
char *buffer;
|
|
|
|
|
|
|
|
/* If anything went wrong, this will be fed back
|
|
|
|
* the next monitor msg */
|
|
|
|
virError lastError;
|
2012-06-15 16:00:13 +00:00
|
|
|
|
|
|
|
/* Some guest agent commands don't return anything
|
|
|
|
* but fire up an event on qemu monitor instead.
|
|
|
|
* Take that as indication of successful completion */
|
|
|
|
qemuAgentEvent await_event;
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
int timeout;
|
2011-10-05 17:31:54 +00:00
|
|
|
};
|
|
|
|
|
2012-07-11 13:35:47 +00:00
|
|
|
static virClassPtr qemuAgentClass;
|
|
|
|
static void qemuAgentDispose(void *obj);
|
|
|
|
|
|
|
|
static int qemuAgentOnceInit(void)
|
|
|
|
{
|
2018-04-17 15:42:33 +00:00
|
|
|
if (!VIR_CLASS_NEW(qemuAgent, virClassForObjectLockable()))
|
2012-07-11 13:35:47 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-01-20 17:23:29 +00:00
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuAgent);
|
2012-07-11 13:35:47 +00:00
|
|
|
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
#if DEBUG_RAW_IO
|
|
|
|
static char *
|
|
|
|
qemuAgentEscapeNonPrintable(const char *text)
|
|
|
|
{
|
Convert 'int i' to 'size_t i' in src/qemu files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i;
|
2011-10-05 17:31:54 +00:00
|
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
2013-05-21 07:21:20 +00:00
|
|
|
for (i = 0; text[i] != '\0'; i++) {
|
2011-10-05 17:31:54 +00:00
|
|
|
if (text[i] == '\\')
|
|
|
|
virBufferAddLit(&buf, "\\\\");
|
2019-11-18 14:14:47 +00:00
|
|
|
else if (g_ascii_isprint(text[i]) || text[i] == '\n' ||
|
2011-10-05 17:31:54 +00:00
|
|
|
(text[i] == '\r' && text[i+1] == '\n'))
|
|
|
|
virBufferAddChar(&buf, text[i]);
|
|
|
|
else
|
|
|
|
virBufferAsprintf(&buf, "\\x%02x", text[i]);
|
|
|
|
}
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2012-07-11 13:35:47 +00:00
|
|
|
static void qemuAgentDispose(void *obj)
|
2011-10-05 17:31:54 +00:00
|
|
|
{
|
2012-07-11 13:35:47 +00:00
|
|
|
qemuAgentPtr mon = obj;
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_DEBUG("mon=%p", mon);
|
|
|
|
if (mon->cb && mon->cb->destroy)
|
|
|
|
(mon->cb->destroy)(mon, mon->vm);
|
2013-02-07 14:03:17 +00:00
|
|
|
virCondDestroy(&mon->notify);
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_FREE(mon->buffer);
|
2013-12-03 02:59:00 +00:00
|
|
|
virResetError(&mon->lastError);
|
2011-10-05 17:31:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2018-04-16 14:44:16 +00:00
|
|
|
qemuAgentOpenUnix(const char *monitor)
|
2011-10-05 17:31:54 +00:00
|
|
|
{
|
|
|
|
struct sockaddr_un addr;
|
|
|
|
int monfd;
|
2016-04-08 11:11:10 +00:00
|
|
|
int ret = -1;
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
if ((monfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
"%s", _("failed to create socket"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virSetNonBlock(monfd) < 0) {
|
2012-08-27 11:49:21 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to put monitor "
|
|
|
|
"into non-blocking mode"));
|
2011-10-05 17:31:54 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virSetCloseExec(monfd) < 0) {
|
2012-08-27 11:49:21 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to set monitor "
|
|
|
|
"close-on-exec flag"));
|
2011-10-05 17:31:54 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
|
|
addr.sun_family = AF_UNIX;
|
2018-07-20 07:50:37 +00:00
|
|
|
if (virStrcpyStatic(addr.sun_path, monitor) < 0) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Agent path %s too big for destination"), monitor);
|
2011-10-05 17:31:54 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2018-04-16 14:44:16 +00:00
|
|
|
ret = connect(monfd, (struct sockaddr *)&addr, sizeof(addr));
|
|
|
|
if (ret < 0) {
|
2011-10-05 17:31:54 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("failed to connect to monitor socket"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return monfd;
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
error:
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_FORCE_CLOSE(monfd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuAgentOpenPty(const char *monitor)
|
|
|
|
{
|
|
|
|
int monfd;
|
|
|
|
|
|
|
|
if ((monfd = open(monitor, O_RDWR | O_NONBLOCK)) < 0) {
|
2012-08-27 11:49:21 +00:00
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Unable to open monitor path %s"), monitor);
|
2011-10-05 17:31:54 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virSetCloseExec(monfd) < 0) {
|
2012-08-27 11:49:21 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to set monitor close-on-exec flag"));
|
2011-10-05 17:31:54 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return monfd;
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
error:
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_FORCE_CLOSE(monfd);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuAgentIOProcessEvent(qemuAgentPtr mon,
|
|
|
|
virJSONValuePtr obj)
|
|
|
|
{
|
|
|
|
const char *type;
|
|
|
|
VIR_DEBUG("mon=%p obj=%p", mon, obj);
|
|
|
|
|
|
|
|
type = virJSONValueObjectGetString(obj, "event");
|
|
|
|
if (!type) {
|
|
|
|
VIR_WARN("missing event type in message");
|
|
|
|
errno = EINVAL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2019-10-15 11:55:26 +00:00
|
|
|
for (i = 0; i < G_N_ELEMENTS(eventHandlers); i++) {
|
2011-10-05 17:31:54 +00:00
|
|
|
if (STREQ(eventHandlers[i].type, type)) {
|
|
|
|
virJSONValuePtr data = virJSONValueObjectGet(obj, "data");
|
|
|
|
VIR_DEBUG("handle %s handler=%p data=%p", type,
|
|
|
|
eventHandlers[i].handler, data);
|
|
|
|
(eventHandlers[i].handler)(mon, data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuAgentIOProcessLine(qemuAgentPtr mon,
|
|
|
|
const char *line,
|
|
|
|
qemuAgentMessagePtr msg)
|
|
|
|
{
|
|
|
|
virJSONValuePtr obj = NULL;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
VIR_DEBUG("Line [%s]", line);
|
|
|
|
|
2016-09-16 10:35:33 +00:00
|
|
|
if (!(obj = virJSONValueFromString(line))) {
|
2016-09-16 10:35:35 +00:00
|
|
|
/* receiving garbage on first sync is regular situation */
|
|
|
|
if (msg && msg->sync && msg->first) {
|
2016-09-16 10:35:33 +00:00
|
|
|
VIR_DEBUG("Received garbage on sync");
|
|
|
|
msg->finished = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
goto cleanup;
|
2016-09-16 10:35:33 +00:00
|
|
|
}
|
2011-10-05 17:31:54 +00:00
|
|
|
|
2018-03-29 18:30:05 +00:00
|
|
|
if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Parsed JSON reply '%s' isn't an object"), line);
|
2011-10-05 17:31:54 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virJSONValueObjectHasKey(obj, "QMP") == 1) {
|
|
|
|
ret = 0;
|
|
|
|
} else if (virJSONValueObjectHasKey(obj, "event") == 1) {
|
|
|
|
ret = qemuAgentIOProcessEvent(mon, obj);
|
|
|
|
} else if (virJSONValueObjectHasKey(obj, "error") == 1 ||
|
|
|
|
virJSONValueObjectHasKey(obj, "return") == 1) {
|
|
|
|
if (msg) {
|
2016-09-16 10:35:34 +00:00
|
|
|
if (msg->sync) {
|
|
|
|
unsigned long long id;
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberUlong(obj, "return", &id) < 0) {
|
|
|
|
VIR_DEBUG("Ignoring delayed reply on sync");
|
|
|
|
ret = 0;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Guest returned ID: %llu", id);
|
|
|
|
|
|
|
|
if (msg->id != id) {
|
|
|
|
VIR_DEBUG("Guest agent returned ID: %llu instead of %llu",
|
|
|
|
id, msg->id);
|
|
|
|
ret = 0;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
2011-10-05 17:31:54 +00:00
|
|
|
msg->rxObject = obj;
|
|
|
|
msg->finished = 1;
|
|
|
|
obj = NULL;
|
|
|
|
} else {
|
2016-09-16 10:35:32 +00:00
|
|
|
/* we are out of sync */
|
|
|
|
VIR_DEBUG("Ignoring delayed reply");
|
2011-10-05 17:31:54 +00:00
|
|
|
}
|
2016-09-16 10:35:32 +00:00
|
|
|
ret = 0;
|
2011-10-05 17:31:54 +00:00
|
|
|
} else {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Unknown JSON reply '%s'"), line);
|
2011-10-05 17:31:54 +00:00
|
|
|
}
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2011-10-05 17:31:54 +00:00
|
|
|
virJSONValueFree(obj);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qemuAgentIOProcessData(qemuAgentPtr mon,
|
|
|
|
char *data,
|
|
|
|
size_t len,
|
|
|
|
qemuAgentMessagePtr msg)
|
|
|
|
{
|
|
|
|
int used = 0;
|
Convert 'int i' to 'size_t i' in src/qemu files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i = 0;
|
2011-10-05 17:31:54 +00:00
|
|
|
#if DEBUG_IO
|
|
|
|
# if DEBUG_RAW_IO
|
|
|
|
char *str1 = qemuAgentEscapeNonPrintable(data);
|
2019-01-08 14:24:07 +00:00
|
|
|
VIR_ERROR(_("[%s]"), str1);
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_FREE(str1);
|
|
|
|
# else
|
|
|
|
VIR_DEBUG("Data %zu bytes [%s]", len, data);
|
|
|
|
# endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
while (used < len) {
|
|
|
|
char *nl = strstr(data + used, LINE_ENDING);
|
|
|
|
|
|
|
|
if (nl) {
|
|
|
|
int got = nl - (data + used);
|
|
|
|
for (i = 0; i < strlen(LINE_ENDING); i++)
|
|
|
|
data[used + got + i] = '\0';
|
2014-11-13 14:25:30 +00:00
|
|
|
if (qemuAgentIOProcessLine(mon, data + used, msg) < 0)
|
2011-10-05 17:31:54 +00:00
|
|
|
return -1;
|
|
|
|
used += got + strlen(LINE_ENDING);
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Total used %d bytes out of %zd available in buffer", used, len);
|
|
|
|
return used;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This method processes data that has been received
|
|
|
|
* from the monitor. Looking for async events and
|
|
|
|
* replies/errors.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
qemuAgentIOProcess(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
qemuAgentMessagePtr msg = NULL;
|
|
|
|
|
|
|
|
/* See if there's a message ready for reply; that is,
|
|
|
|
* one that has completed writing all its data.
|
|
|
|
*/
|
|
|
|
if (mon->msg && mon->msg->txOffset == mon->msg->txLength)
|
|
|
|
msg = mon->msg;
|
|
|
|
|
|
|
|
#if DEBUG_IO
|
|
|
|
# if DEBUG_RAW_IO
|
|
|
|
char *str1 = qemuAgentEscapeNonPrintable(msg ? msg->txBuffer : "");
|
|
|
|
char *str2 = qemuAgentEscapeNonPrintable(mon->buffer);
|
|
|
|
VIR_ERROR(_("Process %zu %p %p [[[%s]]][[[%s]]]"),
|
|
|
|
mon->bufferOffset, mon->msg, msg, str1, str2);
|
|
|
|
VIR_FREE(str1);
|
|
|
|
VIR_FREE(str2);
|
|
|
|
# else
|
|
|
|
VIR_DEBUG("Process %zu", mon->bufferOffset);
|
|
|
|
# endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
len = qemuAgentIOProcessData(mon,
|
|
|
|
mon->buffer, mon->bufferOffset,
|
|
|
|
msg);
|
|
|
|
|
|
|
|
if (len < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (len < mon->bufferOffset) {
|
|
|
|
memmove(mon->buffer, mon->buffer + len, mon->bufferOffset - len);
|
|
|
|
mon->bufferOffset -= len;
|
|
|
|
} else {
|
|
|
|
VIR_FREE(mon->buffer);
|
|
|
|
mon->bufferOffset = mon->bufferLength = 0;
|
|
|
|
}
|
|
|
|
#if DEBUG_IO
|
|
|
|
VIR_DEBUG("Process done %zu used %d", mon->bufferOffset, len);
|
|
|
|
#endif
|
|
|
|
if (msg && msg->finished)
|
|
|
|
virCondBroadcast(&mon->notify);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called when the monitor is able to write data
|
|
|
|
* Call this function while holding the monitor lock.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
qemuAgentIOWrite(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
int done;
|
|
|
|
|
|
|
|
/* If no active message, or fully transmitted, then no-op */
|
|
|
|
if (!mon->msg || mon->msg->txOffset == mon->msg->txLength)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
done = safewrite(mon->fd,
|
|
|
|
mon->msg->txBuffer + mon->msg->txOffset,
|
|
|
|
mon->msg->txLength - mon->msg->txOffset);
|
|
|
|
|
|
|
|
if (done < 0) {
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to write to monitor"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
mon->msg->txOffset += done;
|
|
|
|
return done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called when the monitor has incoming data to read
|
|
|
|
* Call this function while holding the monitor lock.
|
|
|
|
*
|
|
|
|
* Returns -1 on error, or number of bytes read
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
qemuAgentIORead(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
size_t avail = mon->bufferLength - mon->bufferOffset;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (avail < 1024) {
|
2018-03-01 14:55:26 +00:00
|
|
|
if (mon->bufferLength >= QEMU_AGENT_MAX_RESPONSE) {
|
|
|
|
virReportSystemError(ERANGE,
|
|
|
|
_("No complete agent response found in %d bytes"),
|
|
|
|
QEMU_AGENT_MAX_RESPONSE);
|
|
|
|
return -1;
|
|
|
|
}
|
2011-10-05 17:31:54 +00:00
|
|
|
if (VIR_REALLOC_N(mon->buffer,
|
2013-07-04 10:14:12 +00:00
|
|
|
mon->bufferLength + 1024) < 0)
|
2011-10-05 17:31:54 +00:00
|
|
|
return -1;
|
|
|
|
mon->bufferLength += 1024;
|
|
|
|
avail += 1024;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read as much as we can get into our buffer,
|
|
|
|
until we block on EAGAIN, or hit EOF */
|
|
|
|
while (avail > 1) {
|
|
|
|
int got;
|
|
|
|
got = read(mon->fd,
|
|
|
|
mon->buffer + mon->bufferOffset,
|
|
|
|
avail - 1);
|
|
|
|
if (got < 0) {
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
break;
|
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("Unable to read from monitor"));
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (got == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
ret += got;
|
|
|
|
avail -= got;
|
|
|
|
mon->bufferOffset += got;
|
|
|
|
mon->buffer[mon->bufferOffset] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DEBUG_IO
|
|
|
|
VIR_DEBUG("Now read %zu bytes of data", mon->bufferOffset);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void qemuAgentUpdateWatch(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
int events =
|
|
|
|
VIR_EVENT_HANDLE_HANGUP |
|
|
|
|
VIR_EVENT_HANDLE_ERROR;
|
|
|
|
|
2018-10-12 02:19:28 +00:00
|
|
|
if (!mon->watch)
|
|
|
|
return;
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
if (mon->lastError.code == VIR_ERR_OK) {
|
|
|
|
events |= VIR_EVENT_HANDLE_READABLE;
|
|
|
|
|
|
|
|
if (mon->msg && mon->msg->txOffset < mon->msg->txLength)
|
|
|
|
events |= VIR_EVENT_HANDLE_WRITABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
virEventUpdateHandle(mon->watch, events);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2014-03-18 08:15:21 +00:00
|
|
|
qemuAgentIO(int watch, int fd, int events, void *opaque)
|
|
|
|
{
|
2011-10-05 17:31:54 +00:00
|
|
|
qemuAgentPtr mon = opaque;
|
|
|
|
bool error = false;
|
|
|
|
bool eof = false;
|
|
|
|
|
2012-07-11 13:35:47 +00:00
|
|
|
virObjectRef(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
/* lock access to the monitor and protect fd */
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectLock(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
#if DEBUG_IO
|
|
|
|
VIR_DEBUG("Agent %p I/O on watch %d fd %d events %d", mon, watch, fd, events);
|
|
|
|
#endif
|
|
|
|
|
2018-10-12 02:19:28 +00:00
|
|
|
if (mon->fd == -1 || mon->watch == 0) {
|
|
|
|
virObjectUnlock(mon);
|
|
|
|
virObjectUnref(mon);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
if (mon->fd != fd || mon->watch != watch) {
|
|
|
|
if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR))
|
|
|
|
eof = true;
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("event from unexpected fd %d!=%d / watch %d!=%d"),
|
|
|
|
mon->fd, fd, mon->watch, watch);
|
2011-10-05 17:31:54 +00:00
|
|
|
error = true;
|
|
|
|
} else if (mon->lastError.code != VIR_ERR_OK) {
|
|
|
|
if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR))
|
|
|
|
eof = true;
|
|
|
|
error = true;
|
|
|
|
} else {
|
|
|
|
if (events & VIR_EVENT_HANDLE_WRITABLE) {
|
2018-04-16 14:44:16 +00:00
|
|
|
if (qemuAgentIOWrite(mon) < 0)
|
|
|
|
error = true;
|
2011-10-05 17:31:54 +00:00
|
|
|
events &= ~VIR_EVENT_HANDLE_WRITABLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!error &&
|
|
|
|
events & VIR_EVENT_HANDLE_READABLE) {
|
|
|
|
int got = qemuAgentIORead(mon);
|
|
|
|
events &= ~VIR_EVENT_HANDLE_READABLE;
|
|
|
|
if (got < 0) {
|
|
|
|
error = true;
|
|
|
|
} else if (got == 0) {
|
|
|
|
eof = true;
|
|
|
|
} else {
|
|
|
|
/* Ignore hangup/error events if we read some data, to
|
|
|
|
* give time for that data to be consumed */
|
|
|
|
events = 0;
|
|
|
|
|
|
|
|
if (qemuAgentIOProcess(mon) < 0)
|
|
|
|
error = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!error &&
|
|
|
|
events & VIR_EVENT_HANDLE_HANGUP) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
2016-09-19 06:24:50 +00:00
|
|
|
_("End of file from agent monitor"));
|
2013-05-24 10:14:02 +00:00
|
|
|
eof = true;
|
2011-10-05 17:31:54 +00:00
|
|
|
events &= ~VIR_EVENT_HANDLE_HANGUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!error && !eof &&
|
|
|
|
events & VIR_EVENT_HANDLE_ERROR) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Invalid file descriptor while waiting for monitor"));
|
2013-05-24 10:14:02 +00:00
|
|
|
eof = true;
|
2011-10-05 17:31:54 +00:00
|
|
|
events &= ~VIR_EVENT_HANDLE_ERROR;
|
|
|
|
}
|
|
|
|
if (!error && events) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Unhandled event %d for monitor fd %d"),
|
|
|
|
events, mon->fd);
|
2013-05-24 10:14:02 +00:00
|
|
|
error = true;
|
2011-10-05 17:31:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error || eof) {
|
|
|
|
if (mon->lastError.code != VIR_ERR_OK) {
|
|
|
|
/* Already have an error, so clear any new error */
|
|
|
|
virResetLastError();
|
|
|
|
} else {
|
2018-05-05 12:04:21 +00:00
|
|
|
if (virGetLastErrorCode() == VIR_ERR_OK)
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Error while processing monitor IO"));
|
2011-10-05 17:31:54 +00:00
|
|
|
virCopyLastError(&mon->lastError);
|
|
|
|
virResetLastError();
|
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Error on monitor %s", NULLSTR(mon->lastError.message));
|
|
|
|
/* If IO process resulted in an error & we have a message,
|
|
|
|
* then wakeup that waiter */
|
|
|
|
if (mon->msg && !mon->msg->finished) {
|
|
|
|
mon->msg->finished = 1;
|
|
|
|
virCondSignal(&mon->notify);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
qemuAgentUpdateWatch(mon);
|
|
|
|
|
|
|
|
/* We have to unlock to avoid deadlock against command thread,
|
|
|
|
* but is this safe ? I think it is, because the callback
|
|
|
|
* will try to acquire the virDomainObjPtr mutex next */
|
|
|
|
if (eof) {
|
|
|
|
void (*eofNotify)(qemuAgentPtr, virDomainObjPtr)
|
|
|
|
= mon->cb->eofNotify;
|
|
|
|
virDomainObjPtr vm = mon->vm;
|
|
|
|
|
|
|
|
/* Make sure anyone waiting wakes up now */
|
|
|
|
virCondSignal(&mon->notify);
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectUnlock(mon);
|
2012-07-11 13:35:47 +00:00
|
|
|
virObjectUnref(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_DEBUG("Triggering EOF callback");
|
|
|
|
(eofNotify)(mon, vm);
|
|
|
|
} else if (error) {
|
|
|
|
void (*errorNotify)(qemuAgentPtr, virDomainObjPtr)
|
|
|
|
= mon->cb->errorNotify;
|
|
|
|
virDomainObjPtr vm = mon->vm;
|
|
|
|
|
|
|
|
/* Make sure anyone waiting wakes up now */
|
|
|
|
virCondSignal(&mon->notify);
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectUnlock(mon);
|
2012-07-11 13:35:47 +00:00
|
|
|
virObjectUnref(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_DEBUG("Triggering error callback");
|
|
|
|
(errorNotify)(mon, vm);
|
|
|
|
} else {
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectUnlock(mon);
|
2012-07-11 13:35:47 +00:00
|
|
|
virObjectUnref(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
qemuAgentPtr
|
|
|
|
qemuAgentOpen(virDomainObjPtr vm,
|
2016-01-08 15:21:30 +00:00
|
|
|
const virDomainChrSourceDef *config,
|
2011-10-05 17:31:54 +00:00
|
|
|
qemuAgentCallbacksPtr cb)
|
|
|
|
{
|
|
|
|
qemuAgentPtr mon;
|
|
|
|
|
|
|
|
if (!cb || !cb->eofNotify) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("EOF notify callback must be supplied"));
|
2011-10-05 17:31:54 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-07-11 13:35:47 +00:00
|
|
|
if (qemuAgentInitialize() < 0)
|
|
|
|
return NULL;
|
|
|
|
|
2013-01-09 21:00:32 +00:00
|
|
|
if (!(mon = virObjectLockableNew(qemuAgentClass)))
|
2011-10-05 17:31:54 +00:00
|
|
|
return NULL;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
mon->timeout = QEMU_DOMAIN_PRIVATE(vm)->agentTimeout;
|
2013-01-09 21:00:32 +00:00
|
|
|
mon->fd = -1;
|
2011-10-05 17:31:54 +00:00
|
|
|
if (virCondInit(&mon->notify) < 0) {
|
2012-08-27 11:49:21 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
|
|
|
_("cannot initialize monitor condition"));
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectUnref(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
mon->vm = vm;
|
|
|
|
mon->cb = cb;
|
|
|
|
|
|
|
|
switch (config->type) {
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
2018-04-16 14:44:16 +00:00
|
|
|
mon->fd = qemuAgentOpenUnix(config->data.nix.path);
|
2011-10-05 17:31:54 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
|
|
mon->fd = qemuAgentOpenPty(config->data.file.path);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unable to handle monitor type: %s"),
|
|
|
|
virDomainChrTypeToString(config->type));
|
2011-10-05 17:31:54 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mon->fd == -1)
|
|
|
|
goto cleanup;
|
|
|
|
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectRef(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
if ((mon->watch = virEventAddHandle(mon->fd,
|
|
|
|
VIR_EVENT_HANDLE_HANGUP |
|
|
|
|
VIR_EVENT_HANDLE_ERROR |
|
2018-04-16 14:44:16 +00:00
|
|
|
VIR_EVENT_HANDLE_READABLE,
|
2011-10-05 17:31:54 +00:00
|
|
|
qemuAgentIO,
|
2012-07-11 13:35:47 +00:00
|
|
|
mon,
|
|
|
|
virObjectFreeCallback)) < 0) {
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectUnref(mon);
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("unable to register monitor events"));
|
2011-10-05 17:31:54 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
processSerialChangedEvent: Close agent monitor early
https://bugzilla.redhat.com/show_bug.cgi?id=890648
So, imagine you've issued an API that involves guest agent. For
instance, you want to query guest's IP addresses. So the API acquires
QUERY_JOB, locks the guest agent and issues the agent command.
However, for some reason, guest agent replies to initial ping
correctly, but then crashes tragically while executing real command
(in this case guest-network-get-interfaces). Since initial ping went
well, libvirt thinks guest agent is accessible and awaits reply to the
real command. But it will never come. What will is a monitor event.
Our handler (processSerialChangedEvent) will try to acquire
MODIFY_JOB, which will fail obviously because the other thread that's
executing the API already holds a job. So the event handler exits
early, and the QUERY_JOB is never released nor ended.
The way how to solve this is to put flag somewhere in the monitor
internals. The flag is called @running and agent commands are issued
iff the flag is set. The flag itself is set when we connect to the
agent socket. And unset whenever we see DISCONNECT event from the
agent. Moreover, we must wake up all the threads waiting for the
agent. This is done by signalizing the condition they're waiting on.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2015-05-07 09:19:38 +00:00
|
|
|
mon->running = true;
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_DEBUG("New mon %p fd =%d watch=%d", mon, mon->fd, mon->watch);
|
|
|
|
|
|
|
|
return mon;
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2011-10-05 17:31:54 +00:00
|
|
|
/* We don't want the 'destroy' callback invoked during
|
|
|
|
* cleanup from construction failure, because that can
|
|
|
|
* give a double-unref on virDomainObjPtr in the caller,
|
|
|
|
* so kill the callbacks now.
|
|
|
|
*/
|
|
|
|
mon->cb = NULL;
|
|
|
|
qemuAgentClose(mon);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
processSerialChangedEvent: Close agent monitor early
https://bugzilla.redhat.com/show_bug.cgi?id=890648
So, imagine you've issued an API that involves guest agent. For
instance, you want to query guest's IP addresses. So the API acquires
QUERY_JOB, locks the guest agent and issues the agent command.
However, for some reason, guest agent replies to initial ping
correctly, but then crashes tragically while executing real command
(in this case guest-network-get-interfaces). Since initial ping went
well, libvirt thinks guest agent is accessible and awaits reply to the
real command. But it will never come. What will is a monitor event.
Our handler (processSerialChangedEvent) will try to acquire
MODIFY_JOB, which will fail obviously because the other thread that's
executing the API already holds a job. So the event handler exits
early, and the QUERY_JOB is never released nor ended.
The way how to solve this is to put flag somewhere in the monitor
internals. The flag is called @running and agent commands are issued
iff the flag is set. The flag itself is set when we connect to the
agent socket. And unset whenever we see DISCONNECT event from the
agent. Moreover, we must wake up all the threads waiting for the
agent. This is done by signalizing the condition they're waiting on.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2015-05-07 09:19:38 +00:00
|
|
|
static void
|
|
|
|
qemuAgentNotifyCloseLocked(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
if (mon) {
|
|
|
|
mon->running = false;
|
|
|
|
|
|
|
|
/* If there is somebody waiting for a message
|
|
|
|
* wake him up. No message will arrive anyway. */
|
|
|
|
if (mon->msg && !mon->msg->finished) {
|
|
|
|
mon->msg->finished = 1;
|
|
|
|
virCondSignal(&mon->notify);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
qemuAgentNotifyClose(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
if (!mon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VIR_DEBUG("mon=%p", mon);
|
|
|
|
|
|
|
|
virObjectLock(mon);
|
|
|
|
qemuAgentNotifyCloseLocked(mon);
|
|
|
|
virObjectUnlock(mon);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
void qemuAgentClose(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
if (!mon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VIR_DEBUG("mon=%p", mon);
|
|
|
|
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectLock(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
if (mon->fd >= 0) {
|
2018-10-12 02:19:28 +00:00
|
|
|
if (mon->watch) {
|
2011-10-05 17:31:54 +00:00
|
|
|
virEventRemoveHandle(mon->watch);
|
2018-10-12 02:19:28 +00:00
|
|
|
mon->watch = 0;
|
|
|
|
}
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_FORCE_CLOSE(mon->fd);
|
|
|
|
}
|
|
|
|
|
processSerialChangedEvent: Close agent monitor early
https://bugzilla.redhat.com/show_bug.cgi?id=890648
So, imagine you've issued an API that involves guest agent. For
instance, you want to query guest's IP addresses. So the API acquires
QUERY_JOB, locks the guest agent and issues the agent command.
However, for some reason, guest agent replies to initial ping
correctly, but then crashes tragically while executing real command
(in this case guest-network-get-interfaces). Since initial ping went
well, libvirt thinks guest agent is accessible and awaits reply to the
real command. But it will never come. What will is a monitor event.
Our handler (processSerialChangedEvent) will try to acquire
MODIFY_JOB, which will fail obviously because the other thread that's
executing the API already holds a job. So the event handler exits
early, and the QUERY_JOB is never released nor ended.
The way how to solve this is to put flag somewhere in the monitor
internals. The flag is called @running and agent commands are issued
iff the flag is set. The flag itself is set when we connect to the
agent socket. And unset whenever we see DISCONNECT event from the
agent. Moreover, we must wake up all the threads waiting for the
agent. This is done by signalizing the condition they're waiting on.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2015-05-07 09:19:38 +00:00
|
|
|
qemuAgentNotifyCloseLocked(mon);
|
2013-01-09 21:00:32 +00:00
|
|
|
virObjectUnlock(mon);
|
2012-06-15 16:00:13 +00:00
|
|
|
|
2012-07-11 13:35:47 +00:00
|
|
|
virObjectUnref(mon);
|
2011-10-05 17:31:54 +00:00
|
|
|
}
|
|
|
|
|
2012-08-30 12:19:02 +00:00
|
|
|
#define QEMU_AGENT_WAIT_TIME 5
|
2011-10-05 17:31:54 +00:00
|
|
|
|
2012-02-01 14:44:53 +00:00
|
|
|
/**
|
|
|
|
* qemuAgentSend:
|
|
|
|
* @mon: Monitor
|
|
|
|
* @msg: Message
|
2012-08-30 12:19:02 +00:00
|
|
|
* @seconds: number of seconds to wait for the result, it can be either
|
|
|
|
* -2, -1, 0 or positive.
|
2012-02-01 14:44:53 +00:00
|
|
|
*
|
2012-08-30 12:19:02 +00:00
|
|
|
* Send @msg to agent @mon. If @seconds is equal to
|
|
|
|
* VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK(-2), this function will block forever
|
|
|
|
* waiting for the result. The value of
|
|
|
|
* VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT(-1) means use default timeout value
|
2017-04-22 19:06:20 +00:00
|
|
|
* and VIR_DOMAIN_QEMU_AGENT_COMMAND_NOWAIT(0) makes this function return
|
2012-08-30 12:19:02 +00:00
|
|
|
* immediately without waiting. Any positive value means the number of seconds
|
|
|
|
* to wait for the result.
|
2012-02-01 14:44:53 +00:00
|
|
|
*
|
|
|
|
* Returns: 0 on success,
|
|
|
|
* -2 on timeout,
|
|
|
|
* -1 otherwise
|
|
|
|
*/
|
2011-10-05 17:31:54 +00:00
|
|
|
static int qemuAgentSend(qemuAgentPtr mon,
|
2012-02-01 14:44:53 +00:00
|
|
|
qemuAgentMessagePtr msg,
|
2012-08-23 03:29:21 +00:00
|
|
|
int seconds)
|
2011-10-05 17:31:54 +00:00
|
|
|
{
|
|
|
|
int ret = -1;
|
2012-08-30 12:19:02 +00:00
|
|
|
unsigned long long then = 0;
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
/* Check whether qemu quit unexpectedly */
|
|
|
|
if (mon->lastError.code != VIR_ERR_OK) {
|
|
|
|
VIR_DEBUG("Attempt to send command while error is set %s",
|
|
|
|
NULLSTR(mon->lastError.message));
|
|
|
|
virSetError(&mon->lastError);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-08-30 12:19:02 +00:00
|
|
|
if (seconds > VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) {
|
|
|
|
unsigned long long now;
|
2012-02-01 14:44:53 +00:00
|
|
|
if (virTimeMillisNow(&now) < 0)
|
|
|
|
return -1;
|
2012-08-30 12:19:02 +00:00
|
|
|
if (seconds == VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT)
|
|
|
|
seconds = QEMU_AGENT_WAIT_TIME;
|
|
|
|
then = now + seconds * 1000ull;
|
2012-02-01 14:44:53 +00:00
|
|
|
}
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
mon->msg = msg;
|
|
|
|
qemuAgentUpdateWatch(mon);
|
|
|
|
|
|
|
|
while (!mon->msg->finished) {
|
2013-01-09 21:00:32 +00:00
|
|
|
if ((then && virCondWaitUntil(&mon->notify, &mon->parent.lock, then) < 0) ||
|
|
|
|
(!then && virCondWait(&mon->notify, &mon->parent.lock) < 0)) {
|
2012-02-01 14:44:53 +00:00
|
|
|
if (errno == ETIMEDOUT) {
|
2012-08-27 10:24:59 +00:00
|
|
|
virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
|
2012-07-18 15:22:03 +00:00
|
|
|
_("Guest agent not available for now"));
|
2012-02-01 14:44:53 +00:00
|
|
|
ret = -2;
|
|
|
|
} else {
|
2012-08-27 11:49:21 +00:00
|
|
|
virReportSystemError(errno, "%s",
|
2012-11-28 12:30:40 +00:00
|
|
|
_("Unable to wait on agent monitor "
|
|
|
|
"condition"));
|
2012-02-01 14:44:53 +00:00
|
|
|
}
|
2011-10-05 17:31:54 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mon->lastError.code != VIR_ERR_OK) {
|
|
|
|
VIR_DEBUG("Send command resulted in error %s",
|
|
|
|
NULLSTR(mon->lastError.message));
|
|
|
|
virSetError(&mon->lastError);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2011-10-05 17:31:54 +00:00
|
|
|
mon->msg = NULL;
|
|
|
|
qemuAgentUpdateWatch(mon);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-01 14:44:53 +00:00
|
|
|
/**
|
|
|
|
* qemuAgentGuestSync:
|
|
|
|
* @mon: Monitor
|
|
|
|
*
|
|
|
|
* Send guest-sync with unique ID
|
|
|
|
* and wait for reply. If we get one, check if
|
|
|
|
* received ID is equal to given.
|
|
|
|
*
|
|
|
|
* Returns: 0 on success,
|
|
|
|
* -1 otherwise
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
qemuAgentGuestSync(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
int send_ret;
|
2016-09-16 10:35:34 +00:00
|
|
|
unsigned long long id;
|
2012-02-01 14:44:53 +00:00
|
|
|
qemuAgentMessage sync_msg;
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
int timeout = VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT;
|
|
|
|
|
|
|
|
/* if user specified a custom agent timeout that is lower than the
|
|
|
|
* default timeout, use the shorter timeout instead */
|
|
|
|
if ((mon->timeout >= 0) && (mon->timeout < timeout))
|
|
|
|
timeout = mon->timeout;
|
2012-02-01 14:44:53 +00:00
|
|
|
|
|
|
|
memset(&sync_msg, 0, sizeof(sync_msg));
|
2016-09-16 10:35:35 +00:00
|
|
|
/* set only on first sync */
|
|
|
|
sync_msg.first = true;
|
2012-02-01 14:44:53 +00:00
|
|
|
|
2016-09-16 10:35:35 +00:00
|
|
|
retry:
|
2012-02-01 14:44:53 +00:00
|
|
|
if (virTimeMillisNow(&id) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
sync_msg.txBuffer = g_strdup_printf("{\"execute\":\"guest-sync\", "
|
|
|
|
"\"arguments\":{\"id\":%llu}}\n", id);
|
2012-02-01 14:44:53 +00:00
|
|
|
|
|
|
|
sync_msg.txLength = strlen(sync_msg.txBuffer);
|
2016-09-16 10:35:33 +00:00
|
|
|
sync_msg.sync = true;
|
2016-09-16 10:35:34 +00:00
|
|
|
sync_msg.id = id;
|
2012-02-01 14:44:53 +00:00
|
|
|
|
|
|
|
VIR_DEBUG("Sending guest-sync command with ID: %llu", id);
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
send_ret = qemuAgentSend(mon, &sync_msg, timeout);
|
2012-02-01 14:44:53 +00:00
|
|
|
|
|
|
|
VIR_DEBUG("qemuAgentSend returned: %d", send_ret);
|
|
|
|
|
2013-07-25 08:59:21 +00:00
|
|
|
if (send_ret < 0)
|
2012-02-01 14:44:53 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (!sync_msg.rxObject) {
|
2016-09-16 10:35:35 +00:00
|
|
|
if (sync_msg.first) {
|
|
|
|
VIR_FREE(sync_msg.txBuffer);
|
|
|
|
memset(&sync_msg, 0, sizeof(sync_msg));
|
|
|
|
goto retry;
|
|
|
|
} else {
|
2016-09-16 10:35:36 +00:00
|
|
|
if (mon->running)
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing monitor reply object"));
|
|
|
|
else
|
|
|
|
virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
|
|
|
|
_("Guest agent disappeared while executing command"));
|
2016-09-16 10:35:35 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2012-02-01 14:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2012-02-01 14:44:53 +00:00
|
|
|
virJSONValueFree(sync_msg.rxObject);
|
|
|
|
VIR_FREE(sync_msg.txBuffer);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-04-12 14:35:24 +00:00
|
|
|
static const char *
|
|
|
|
qemuAgentStringifyErrorClass(const char *klass)
|
|
|
|
{
|
|
|
|
if (STREQ_NULLABLE(klass, "BufferOverrun"))
|
|
|
|
return "Buffer overrun";
|
|
|
|
else if (STREQ_NULLABLE(klass, "CommandDisabled"))
|
|
|
|
return "The command has been disabled for this instance";
|
|
|
|
else if (STREQ_NULLABLE(klass, "CommandNotFound"))
|
|
|
|
return "The command has not been found";
|
|
|
|
else if (STREQ_NULLABLE(klass, "FdNotFound"))
|
|
|
|
return "File descriptor not found";
|
|
|
|
else if (STREQ_NULLABLE(klass, "InvalidParameter"))
|
|
|
|
return "Invalid parameter";
|
|
|
|
else if (STREQ_NULLABLE(klass, "InvalidParameterType"))
|
|
|
|
return "Invalid parameter type";
|
|
|
|
else if (STREQ_NULLABLE(klass, "InvalidParameterValue"))
|
|
|
|
return "Invalid parameter value";
|
|
|
|
else if (STREQ_NULLABLE(klass, "OpenFileFailed"))
|
|
|
|
return "Cannot open file";
|
|
|
|
else if (STREQ_NULLABLE(klass, "QgaCommandFailed"))
|
|
|
|
return "Guest agent command failed";
|
|
|
|
else if (STREQ_NULLABLE(klass, "QMPBadInputObjectMember"))
|
|
|
|
return "Bad QMP input object member";
|
|
|
|
else if (STREQ_NULLABLE(klass, "QMPExtraInputObjectMember"))
|
|
|
|
return "Unexpected extra object member";
|
|
|
|
else if (STREQ_NULLABLE(klass, "UndefinedError"))
|
|
|
|
return "An undefined error has occurred";
|
|
|
|
else if (STREQ_NULLABLE(klass, "Unsupported"))
|
|
|
|
return "this feature or command is not currently supported";
|
|
|
|
else if (klass)
|
|
|
|
return klass;
|
|
|
|
else
|
|
|
|
return "unknown QEMU command error";
|
|
|
|
}
|
2011-10-05 17:31:54 +00:00
|
|
|
|
2019-08-27 20:35:54 +00:00
|
|
|
/* Checks whether the agent reply msg is an error caused by an unsupported
|
|
|
|
* command.
|
|
|
|
*
|
|
|
|
* Returns true when reply is CommandNotFound or CommandDisabled
|
|
|
|
* false otherwise
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
qemuAgentErrorCommandUnsupported(virJSONValuePtr reply)
|
|
|
|
{
|
|
|
|
const char *klass;
|
2019-08-30 16:09:09 +00:00
|
|
|
virJSONValuePtr error;
|
|
|
|
|
|
|
|
if (!reply)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
error = virJSONValueObjectGet(reply, "error");
|
2019-08-27 20:35:54 +00:00
|
|
|
|
|
|
|
if (!error)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
klass = virJSONValueObjectGetString(error, "class");
|
|
|
|
return STREQ_NULLABLE(klass, "CommandNotFound") ||
|
|
|
|
STREQ_NULLABLE(klass, "CommandDisabled");
|
|
|
|
}
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
/* Ignoring OOM in this method, since we're already reporting
|
|
|
|
* a more important error
|
|
|
|
*
|
|
|
|
* XXX see qerror.h for different klasses & fill out useful params
|
|
|
|
*/
|
|
|
|
static const char *
|
|
|
|
qemuAgentStringifyError(virJSONValuePtr error)
|
|
|
|
{
|
|
|
|
const char *klass = virJSONValueObjectGetString(error, "class");
|
2014-10-28 02:41:27 +00:00
|
|
|
const char *detail = virJSONValueObjectGetString(error, "desc");
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
/* The QMP 'desc' field is usually sufficient for our generic
|
2014-10-28 02:41:27 +00:00
|
|
|
* error reporting needs. However, if not present, translate
|
|
|
|
* the class into something readable.
|
2011-10-05 17:31:54 +00:00
|
|
|
*/
|
|
|
|
if (!detail)
|
2012-04-12 14:35:24 +00:00
|
|
|
detail = qemuAgentStringifyErrorClass(klass);
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
return detail;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
qemuAgentCommandName(virJSONValuePtr cmd)
|
|
|
|
{
|
|
|
|
const char *name = virJSONValueObjectGetString(cmd, "execute");
|
|
|
|
if (name)
|
|
|
|
return name;
|
|
|
|
else
|
|
|
|
return "<unknown>";
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuAgentCheckError(virJSONValuePtr cmd,
|
|
|
|
virJSONValuePtr reply)
|
|
|
|
{
|
|
|
|
if (virJSONValueObjectHasKey(reply, "error")) {
|
|
|
|
virJSONValuePtr error = virJSONValueObjectGet(reply, "error");
|
2012-08-09 10:46:29 +00:00
|
|
|
char *cmdstr = virJSONValueToString(cmd, false);
|
|
|
|
char *replystr = virJSONValueToString(reply, false);
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
/* Log the full JSON formatted command & error */
|
2012-11-28 12:30:40 +00:00
|
|
|
VIR_DEBUG("unable to execute QEMU agent command %s: %s",
|
2013-02-22 16:41:38 +00:00
|
|
|
NULLSTR(cmdstr), NULLSTR(replystr));
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
/* Only send the user the command name + friendly error */
|
|
|
|
if (!error)
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2012-11-28 12:30:40 +00:00
|
|
|
_("unable to execute QEMU agent command '%s'"),
|
2012-07-18 15:22:03 +00:00
|
|
|
qemuAgentCommandName(cmd));
|
2011-10-05 17:31:54 +00:00
|
|
|
else
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2012-11-28 12:30:40 +00:00
|
|
|
_("unable to execute QEMU agent command '%s': %s"),
|
2012-07-18 15:22:03 +00:00
|
|
|
qemuAgentCommandName(cmd),
|
|
|
|
qemuAgentStringifyError(error));
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
VIR_FREE(cmdstr);
|
|
|
|
VIR_FREE(replystr);
|
|
|
|
return -1;
|
|
|
|
} else if (!virJSONValueObjectHasKey(reply, "return")) {
|
2012-08-09 10:46:29 +00:00
|
|
|
char *cmdstr = virJSONValueToString(cmd, false);
|
|
|
|
char *replystr = virJSONValueToString(reply, false);
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: %s",
|
2013-02-22 16:41:38 +00:00
|
|
|
NULLSTR(cmdstr), NULLSTR(replystr));
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2012-11-28 12:30:40 +00:00
|
|
|
_("unable to execute QEMU agent command '%s'"),
|
2012-07-18 15:22:03 +00:00
|
|
|
qemuAgentCommandName(cmd));
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_FREE(cmdstr);
|
|
|
|
VIR_FREE(replystr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-04-02 06:57:59 +00:00
|
|
|
static int
|
|
|
|
qemuAgentCommand(qemuAgentPtr mon,
|
|
|
|
virJSONValuePtr cmd,
|
|
|
|
virJSONValuePtr *reply,
|
2014-04-03 05:20:25 +00:00
|
|
|
bool needReply,
|
2014-04-02 06:57:59 +00:00
|
|
|
int seconds)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
qemuAgentMessage msg;
|
|
|
|
char *cmdstr = NULL;
|
|
|
|
int await_event = mon->await_event;
|
|
|
|
|
|
|
|
*reply = NULL;
|
|
|
|
|
processSerialChangedEvent: Close agent monitor early
https://bugzilla.redhat.com/show_bug.cgi?id=890648
So, imagine you've issued an API that involves guest agent. For
instance, you want to query guest's IP addresses. So the API acquires
QUERY_JOB, locks the guest agent and issues the agent command.
However, for some reason, guest agent replies to initial ping
correctly, but then crashes tragically while executing real command
(in this case guest-network-get-interfaces). Since initial ping went
well, libvirt thinks guest agent is accessible and awaits reply to the
real command. But it will never come. What will is a monitor event.
Our handler (processSerialChangedEvent) will try to acquire
MODIFY_JOB, which will fail obviously because the other thread that's
executing the API already holds a job. So the event handler exits
early, and the QUERY_JOB is never released nor ended.
The way how to solve this is to put flag somewhere in the monitor
internals. The flag is called @running and agent commands are issued
iff the flag is set. The flag itself is set when we connect to the
agent socket. And unset whenever we see DISCONNECT event from the
agent. Moreover, we must wake up all the threads waiting for the
agent. This is done by signalizing the condition they're waiting on.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2015-05-07 09:19:38 +00:00
|
|
|
if (!mon->running) {
|
|
|
|
virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
|
|
|
|
_("Guest agent disappeared while executing command"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-04-02 06:57:59 +00:00
|
|
|
if (qemuAgentGuestSync(mon) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
|
|
|
|
|
|
if (!(cmdstr = virJSONValueToString(cmd, false)))
|
|
|
|
goto cleanup;
|
2019-10-22 13:26:14 +00:00
|
|
|
msg.txBuffer = g_strdup_printf("%s" LINE_ENDING, cmdstr);
|
2014-04-02 06:57:59 +00:00
|
|
|
msg.txLength = strlen(msg.txBuffer);
|
|
|
|
|
|
|
|
VIR_DEBUG("Send command '%s' for write, seconds = %d", cmdstr, seconds);
|
|
|
|
|
|
|
|
ret = qemuAgentSend(mon, &msg, seconds);
|
|
|
|
|
|
|
|
VIR_DEBUG("Receive command reply ret=%d rxObject=%p",
|
|
|
|
ret, msg.rxObject);
|
|
|
|
|
|
|
|
if (ret == 0) {
|
|
|
|
/* If we haven't obtained any reply but we wait for an
|
|
|
|
* event, then don't report this as error */
|
|
|
|
if (!msg.rxObject) {
|
2014-04-03 05:20:25 +00:00
|
|
|
if (await_event && !needReply) {
|
2014-04-02 06:57:59 +00:00
|
|
|
VIR_DEBUG("Woken up by event %d", await_event);
|
|
|
|
} else {
|
processSerialChangedEvent: Close agent monitor early
https://bugzilla.redhat.com/show_bug.cgi?id=890648
So, imagine you've issued an API that involves guest agent. For
instance, you want to query guest's IP addresses. So the API acquires
QUERY_JOB, locks the guest agent and issues the agent command.
However, for some reason, guest agent replies to initial ping
correctly, but then crashes tragically while executing real command
(in this case guest-network-get-interfaces). Since initial ping went
well, libvirt thinks guest agent is accessible and awaits reply to the
real command. But it will never come. What will is a monitor event.
Our handler (processSerialChangedEvent) will try to acquire
MODIFY_JOB, which will fail obviously because the other thread that's
executing the API already holds a job. So the event handler exits
early, and the QUERY_JOB is never released nor ended.
The way how to solve this is to put flag somewhere in the monitor
internals. The flag is called @running and agent commands are issued
iff the flag is set. The flag itself is set when we connect to the
agent socket. And unset whenever we see DISCONNECT event from the
agent. Moreover, we must wake up all the threads waiting for the
agent. This is done by signalizing the condition they're waiting on.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2015-05-07 09:19:38 +00:00
|
|
|
if (mon->running)
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing monitor reply object"));
|
|
|
|
else
|
|
|
|
virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
|
|
|
|
_("Guest agent disappeared while executing command"));
|
2014-04-02 06:57:59 +00:00
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*reply = msg.rxObject;
|
|
|
|
ret = qemuAgentCheckError(cmd, *reply);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
VIR_FREE(cmdstr);
|
|
|
|
VIR_FREE(msg.txBuffer);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-10-14 12:13:31 +00:00
|
|
|
static virJSONValuePtr G_GNUC_NULL_TERMINATED
|
2011-10-05 17:31:54 +00:00
|
|
|
qemuAgentMakeCommand(const char *cmdname,
|
|
|
|
...)
|
|
|
|
{
|
|
|
|
virJSONValuePtr obj;
|
|
|
|
virJSONValuePtr jargs = NULL;
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
va_start(args, cmdname);
|
|
|
|
|
|
|
|
if (!(obj = virJSONValueNewObject()))
|
2013-07-04 10:14:12 +00:00
|
|
|
goto error;
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
if (virJSONValueObjectAppendString(obj, "execute", cmdname) < 0)
|
2013-07-04 10:14:12 +00:00
|
|
|
goto error;
|
2011-10-05 17:31:54 +00:00
|
|
|
|
2015-04-24 13:32:15 +00:00
|
|
|
if (virJSONValueObjectCreateVArgs(&jargs, args) < 0)
|
|
|
|
goto error;
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
if (jargs &&
|
|
|
|
virJSONValueObjectAppend(obj, "arguments", jargs) < 0)
|
2013-07-04 10:14:12 +00:00
|
|
|
goto error;
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
error:
|
2011-10-05 17:31:54 +00:00
|
|
|
virJSONValueFree(obj);
|
|
|
|
virJSONValueFree(jargs);
|
|
|
|
va_end(args);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-05-02 00:06:19 +00:00
|
|
|
static virJSONValuePtr
|
|
|
|
qemuAgentMakeStringsArray(const char **strings, unsigned int len)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
virJSONValuePtr ret = virJSONValueNewArray(), str;
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
str = virJSONValueNewString(strings[i]);
|
|
|
|
if (!str)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (virJSONValueArrayAppend(ret, str) < 0) {
|
|
|
|
virJSONValueFree(str);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
error:
|
|
|
|
virJSONValueFree(ret);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-06-15 16:00:13 +00:00
|
|
|
void qemuAgentNotifyEvent(qemuAgentPtr mon,
|
|
|
|
qemuAgentEvent event)
|
|
|
|
{
|
2016-12-12 09:13:44 +00:00
|
|
|
virObjectLock(mon);
|
|
|
|
|
2016-04-08 08:08:37 +00:00
|
|
|
VIR_DEBUG("mon=%p event=%d await_event=%d", mon, event, mon->await_event);
|
2012-06-15 16:00:13 +00:00
|
|
|
if (mon->await_event == event) {
|
|
|
|
mon->await_event = QEMU_AGENT_EVENT_NONE;
|
|
|
|
/* somebody waiting for this event, wake him up. */
|
|
|
|
if (mon->msg && !mon->msg->finished) {
|
|
|
|
mon->msg->finished = 1;
|
|
|
|
virCondSignal(&mon->notify);
|
|
|
|
}
|
|
|
|
}
|
2016-12-12 09:13:44 +00:00
|
|
|
|
|
|
|
virObjectUnlock(mon);
|
2012-06-15 16:00:13 +00:00
|
|
|
}
|
|
|
|
|
2011-10-05 17:31:54 +00:00
|
|
|
VIR_ENUM_DECL(qemuAgentShutdownMode);
|
|
|
|
|
|
|
|
VIR_ENUM_IMPL(qemuAgentShutdownMode,
|
|
|
|
QEMU_AGENT_SHUTDOWN_LAST,
|
2019-01-20 16:30:15 +00:00
|
|
|
"powerdown", "reboot", "halt",
|
|
|
|
);
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
int qemuAgentShutdown(qemuAgentPtr mon,
|
|
|
|
qemuAgentShutdownMode mode)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
|
|
|
cmd = qemuAgentMakeCommand("guest-shutdown",
|
|
|
|
"s:mode", qemuAgentShutdownModeTypeToString(mode),
|
|
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
|
|
return -1;
|
|
|
|
|
2012-09-04 10:01:43 +00:00
|
|
|
if (mode == QEMU_AGENT_SHUTDOWN_REBOOT)
|
|
|
|
mon->await_event = QEMU_AGENT_EVENT_RESET;
|
|
|
|
else
|
|
|
|
mon->await_event = QEMU_AGENT_EVENT_SHUTDOWN;
|
2014-04-03 05:20:25 +00:00
|
|
|
ret = qemuAgentCommand(mon, cmd, &reply, false,
|
2015-04-01 09:13:08 +00:00
|
|
|
VIR_DOMAIN_QEMU_AGENT_COMMAND_SHUTDOWN);
|
2011-10-05 17:31:54 +00:00
|
|
|
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
2012-01-24 20:13:40 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* qemuAgentFSFreeze:
|
|
|
|
* @mon: Agent
|
2014-05-02 00:06:19 +00:00
|
|
|
* @mountpoints: Array of mountpoint paths to be frozen, or NULL for all
|
|
|
|
* @nmountpoints: Number of mountpoints to be frozen, or 0 for all
|
2012-01-24 20:13:40 +00:00
|
|
|
*
|
|
|
|
* Issue guest-fsfreeze-freeze command to guest agent,
|
2014-05-02 00:06:19 +00:00
|
|
|
* which freezes file systems mounted on specified mountpoints
|
|
|
|
* (or all file systems when @mountpoints is NULL), and returns
|
2012-01-24 20:13:40 +00:00
|
|
|
* number of frozen file systems on success.
|
|
|
|
*
|
|
|
|
* Returns: number of file system frozen on success,
|
|
|
|
* -1 on error.
|
|
|
|
*/
|
2014-05-02 00:06:19 +00:00
|
|
|
int qemuAgentFSFreeze(qemuAgentPtr mon, const char **mountpoints,
|
|
|
|
unsigned int nmountpoints)
|
2012-01-24 20:13:40 +00:00
|
|
|
{
|
|
|
|
int ret = -1;
|
2014-08-27 18:42:41 +00:00
|
|
|
virJSONValuePtr cmd, arg = NULL;
|
2012-01-24 20:13:40 +00:00
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
2014-05-02 00:06:19 +00:00
|
|
|
if (mountpoints && nmountpoints) {
|
|
|
|
arg = qemuAgentMakeStringsArray(mountpoints, nmountpoints);
|
|
|
|
if (!arg)
|
|
|
|
return -1;
|
|
|
|
|
2014-08-08 20:03:41 +00:00
|
|
|
cmd = qemuAgentMakeCommand("guest-fsfreeze-freeze-list",
|
2018-03-30 09:12:57 +00:00
|
|
|
"a:mountpoints", &arg, NULL);
|
2014-05-02 00:06:19 +00:00
|
|
|
} else {
|
|
|
|
cmd = qemuAgentMakeCommand("guest-fsfreeze-freeze", NULL);
|
|
|
|
}
|
2012-01-24 20:13:40 +00:00
|
|
|
|
|
|
|
if (!cmd)
|
2014-08-27 18:42:41 +00:00
|
|
|
goto cleanup;
|
2012-01-24 20:13:40 +00:00
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2012-01-24 20:13:40 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("malformed return value"));
|
2012-01-24 20:13:40 +00:00
|
|
|
}
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2014-08-27 18:42:41 +00:00
|
|
|
virJSONValueFree(arg);
|
2012-01-24 20:13:40 +00:00
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* qemuAgentFSThaw:
|
|
|
|
* @mon: Agent
|
|
|
|
*
|
|
|
|
* Issue guest-fsfreeze-thaw command to guest agent,
|
|
|
|
* which unfreezes all mounted file systems and returns
|
|
|
|
* number of thawed file systems on success.
|
|
|
|
*
|
|
|
|
* Returns: number of file system thawed on success,
|
|
|
|
* -1 on error.
|
|
|
|
*/
|
|
|
|
int qemuAgentFSThaw(qemuAgentPtr mon)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
|
|
|
cmd = qemuAgentMakeCommand("guest-fsfreeze-thaw", NULL);
|
|
|
|
|
|
|
|
if (!cmd)
|
|
|
|
return -1;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2012-01-24 20:13:40 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) {
|
2012-07-18 15:22:03 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("malformed return value"));
|
2012-01-24 20:13:40 +00:00
|
|
|
}
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2012-01-24 20:13:40 +00:00
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
2012-02-13 11:27:25 +00:00
|
|
|
|
|
|
|
VIR_ENUM_DECL(qemuAgentSuspendMode);
|
|
|
|
|
|
|
|
VIR_ENUM_IMPL(qemuAgentSuspendMode,
|
|
|
|
VIR_NODE_SUSPEND_TARGET_LAST,
|
|
|
|
"guest-suspend-ram",
|
|
|
|
"guest-suspend-disk",
|
2019-01-20 16:30:15 +00:00
|
|
|
"guest-suspend-hybrid",
|
|
|
|
);
|
2012-02-13 11:27:25 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
qemuAgentSuspend(qemuAgentPtr mon,
|
|
|
|
unsigned int target)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
|
|
|
cmd = qemuAgentMakeCommand(qemuAgentSuspendModeTypeToString(target),
|
|
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
|
|
return -1;
|
|
|
|
|
2012-06-15 16:00:13 +00:00
|
|
|
mon->await_event = QEMU_AGENT_EVENT_SUSPEND;
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
ret = qemuAgentCommand(mon, cmd, &reply, false, mon->timeout);
|
2012-02-13 11:27:25 +00:00
|
|
|
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
2012-08-23 03:29:22 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
qemuAgentArbitraryCommand(qemuAgentPtr mon,
|
|
|
|
const char *cmd_str,
|
|
|
|
char **result,
|
|
|
|
int timeout)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
2013-06-03 13:58:31 +00:00
|
|
|
virJSONValuePtr cmd = NULL;
|
2012-08-23 03:29:22 +00:00
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
|
|
|
*result = NULL;
|
2013-06-03 13:58:31 +00:00
|
|
|
if (timeout < VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
|
|
_("guest agent timeout '%d' is "
|
|
|
|
"less than the minimum '%d'"),
|
|
|
|
timeout, VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2012-08-23 03:29:22 +00:00
|
|
|
|
2013-06-03 13:58:31 +00:00
|
|
|
if (!(cmd = virJSONValueFromString(cmd_str)))
|
|
|
|
goto cleanup;
|
|
|
|
|
2014-04-03 05:20:25 +00:00
|
|
|
if ((ret = qemuAgentCommand(mon, cmd, &reply, true, timeout)) < 0)
|
2013-06-03 13:58:31 +00:00
|
|
|
goto cleanup;
|
2012-08-23 03:29:22 +00:00
|
|
|
|
2013-06-03 13:58:31 +00:00
|
|
|
if (!(*result = virJSONValueToString(reply, false)))
|
|
|
|
ret = -1;
|
2012-08-23 03:29:22 +00:00
|
|
|
|
2013-06-03 13:58:31 +00:00
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2012-08-23 03:29:22 +00:00
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
2012-11-20 16:10:29 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
qemuAgentFSTrim(qemuAgentPtr mon,
|
|
|
|
unsigned long long minimum)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
|
|
|
cmd = qemuAgentMakeCommand("guest-fstrim",
|
|
|
|
"U:minimum", minimum,
|
|
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
|
|
return ret;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
ret = qemuAgentCommand(mon, cmd, &reply, false, mon->timeout);
|
2012-11-20 16:10:29 +00:00
|
|
|
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
2013-04-12 10:14:02 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
qemuAgentGetVCPUs(qemuAgentPtr mon,
|
|
|
|
qemuAgentCPUInfoPtr *info)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
Convert 'int i' to 'size_t i' in src/qemu files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i;
|
2013-04-12 10:14:02 +00:00
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
virJSONValuePtr data = NULL;
|
2018-04-19 21:29:02 +00:00
|
|
|
size_t ndata;
|
2013-04-12 10:14:02 +00:00
|
|
|
|
|
|
|
if (!(cmd = qemuAgentMakeCommand("guest-get-vcpus", NULL)))
|
|
|
|
return -1;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2013-04-12 10:14:02 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2018-03-29 18:34:57 +00:00
|
|
|
if (!(data = virJSONValueObjectGetArray(reply, "return"))) {
|
2013-04-12 10:14:02 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("guest-get-vcpus reply was missing return data"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2018-04-19 21:29:02 +00:00
|
|
|
if (!virJSONValueIsArray(data)) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Malformed guest-get-vcpus data array"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2013-04-12 10:14:02 +00:00
|
|
|
ndata = virJSONValueArraySize(data);
|
|
|
|
|
2013-07-04 10:14:12 +00:00
|
|
|
if (VIR_ALLOC_N(*info, ndata) < 0)
|
2013-04-12 10:14:02 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
for (i = 0; i < ndata; i++) {
|
|
|
|
virJSONValuePtr entry = virJSONValueArrayGet(data, i);
|
|
|
|
qemuAgentCPUInfoPtr in = *info + i;
|
|
|
|
|
|
|
|
if (!entry) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("array element missing in guest-get-vcpus return "
|
|
|
|
"value"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberUint(entry, "logical-id", &in->id) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'logical-id' missing in reply of guest-get-vcpus"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetBoolean(entry, "online", &in->online) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'online' missing in reply of guest-get-vcpus"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetBoolean(entry, "can-offline",
|
|
|
|
&in->offlinable) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'can-offline' missing in reply of guest-get-vcpus"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = ndata;
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2013-04-12 10:14:02 +00:00
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-06-20 12:15:50 +00:00
|
|
|
|
|
|
|
/* returns the value provided by the guest agent or -1 on internal error */
|
|
|
|
static int
|
|
|
|
qemuAgentSetVCPUsCommand(qemuAgentPtr mon,
|
|
|
|
qemuAgentCPUInfoPtr info,
|
|
|
|
size_t ninfo,
|
|
|
|
int *nmodified)
|
2013-04-12 10:14:02 +00:00
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd = NULL;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
virJSONValuePtr cpus = NULL;
|
|
|
|
virJSONValuePtr cpu = NULL;
|
|
|
|
size_t i;
|
|
|
|
|
2016-06-20 12:15:50 +00:00
|
|
|
*nmodified = 0;
|
|
|
|
|
2013-04-12 10:14:02 +00:00
|
|
|
/* create the key data array */
|
|
|
|
if (!(cpus = virJSONValueNewArray()))
|
2013-07-04 10:14:12 +00:00
|
|
|
goto cleanup;
|
2013-04-12 10:14:02 +00:00
|
|
|
|
|
|
|
for (i = 0; i < ninfo; i++) {
|
|
|
|
qemuAgentCPUInfoPtr in = &info[i];
|
|
|
|
|
2016-06-20 12:15:50 +00:00
|
|
|
/* don't set state for cpus that were not touched */
|
|
|
|
if (!in->modified)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
(*nmodified)++;
|
|
|
|
|
2013-04-12 10:14:02 +00:00
|
|
|
/* create single cpu object */
|
|
|
|
if (!(cpu = virJSONValueNewObject()))
|
2013-07-04 10:14:12 +00:00
|
|
|
goto cleanup;
|
2013-04-12 10:14:02 +00:00
|
|
|
|
|
|
|
if (virJSONValueObjectAppendNumberInt(cpu, "logical-id", in->id) < 0)
|
2013-07-04 10:14:12 +00:00
|
|
|
goto cleanup;
|
2013-04-12 10:14:02 +00:00
|
|
|
|
|
|
|
if (virJSONValueObjectAppendBoolean(cpu, "online", in->online) < 0)
|
2013-07-04 10:14:12 +00:00
|
|
|
goto cleanup;
|
2013-04-12 10:14:02 +00:00
|
|
|
|
|
|
|
if (virJSONValueArrayAppend(cpus, cpu) < 0)
|
2013-07-04 10:14:12 +00:00
|
|
|
goto cleanup;
|
2013-04-12 10:14:02 +00:00
|
|
|
|
|
|
|
cpu = NULL;
|
|
|
|
}
|
|
|
|
|
2016-06-20 12:15:50 +00:00
|
|
|
if (*nmodified == 0) {
|
|
|
|
ret = 0;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2013-04-12 10:14:02 +00:00
|
|
|
if (!(cmd = qemuAgentMakeCommand("guest-set-vcpus",
|
2018-03-30 09:12:57 +00:00
|
|
|
"a:vcpus", &cpus,
|
2013-04-12 10:14:02 +00:00
|
|
|
NULL)))
|
|
|
|
goto cleanup;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2013-04-12 10:14:02 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2016-06-20 12:15:50 +00:00
|
|
|
/* All negative values are invalid. Return of 0 is bogus since we wouldn't
|
|
|
|
* call the guest agent so that 0 cpus would be set successfully. Reporting
|
|
|
|
* more successfully set vcpus that we've asked for is invalid. */
|
|
|
|
if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0 ||
|
|
|
|
ret <= 0 || ret > *nmodified) {
|
2013-04-12 10:14:02 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
2016-06-20 12:15:50 +00:00
|
|
|
_("guest agent returned malformed or invalid return value"));
|
|
|
|
ret = -1;
|
2013-04-12 10:14:02 +00:00
|
|
|
}
|
|
|
|
|
2014-03-25 06:49:44 +00:00
|
|
|
cleanup:
|
2013-04-12 10:14:02 +00:00
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
virJSONValueFree(cpu);
|
|
|
|
virJSONValueFree(cpus);
|
|
|
|
return ret;
|
|
|
|
}
|
2013-07-30 10:04:21 +00:00
|
|
|
|
|
|
|
|
2016-06-20 12:15:50 +00:00
|
|
|
/**
|
|
|
|
* Set the VCPU state using guest agent.
|
|
|
|
*
|
|
|
|
* Attempts to set the guest agent state for all cpus or until a proper error is
|
|
|
|
* reported by the guest agent. This may require multiple calls.
|
|
|
|
*
|
|
|
|
* Returns -1 on error, 0 on success.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuAgentSetVCPUs(qemuAgentPtr mon,
|
|
|
|
qemuAgentCPUInfoPtr info,
|
|
|
|
size_t ninfo)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
int nmodified;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
do {
|
|
|
|
if ((rv = qemuAgentSetVCPUsCommand(mon, info, ninfo, &nmodified)) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* all vcpus were set successfully */
|
|
|
|
if (rv == nmodified)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* un-mark vcpus that were already set */
|
|
|
|
for (i = 0; i < ninfo && rv > 0; i++) {
|
|
|
|
if (!info[i].modified)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
info[i].modified = false;
|
|
|
|
rv--;
|
|
|
|
}
|
|
|
|
} while (1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-30 10:04:21 +00:00
|
|
|
/* modify the cpu info structure to set the correct amount of cpus */
|
|
|
|
int
|
|
|
|
qemuAgentUpdateCPUInfo(unsigned int nvcpus,
|
|
|
|
qemuAgentCPUInfoPtr cpuinfo,
|
|
|
|
int ncpuinfo)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
int nonline = 0;
|
|
|
|
int nofflinable = 0;
|
2015-06-26 11:58:20 +00:00
|
|
|
ssize_t cpu0 = -1;
|
2013-07-30 10:04:21 +00:00
|
|
|
|
|
|
|
/* count the active and offlinable cpus */
|
|
|
|
for (i = 0; i < ncpuinfo; i++) {
|
2015-06-26 11:58:20 +00:00
|
|
|
if (cpuinfo[i].id == 0)
|
|
|
|
cpu0 = i;
|
|
|
|
|
2013-07-30 10:04:21 +00:00
|
|
|
if (cpuinfo[i].online)
|
|
|
|
nonline++;
|
|
|
|
|
|
|
|
if (cpuinfo[i].offlinable && cpuinfo[i].online)
|
|
|
|
nofflinable++;
|
|
|
|
|
|
|
|
/* This shouldn't happen, but we can't trust the guest agent */
|
|
|
|
if (!cpuinfo[i].online && !cpuinfo[i].offlinable) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Invalid data provided by guest agent"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-26 11:58:20 +00:00
|
|
|
/* CPU0 was made offlinable in linux a while ago, but certain parts (suspend
|
|
|
|
* to ram) of the kernel still don't cope well with that. Make sure that if
|
|
|
|
* all remaining vCPUs are offlinable, vCPU0 will not be selected to be
|
|
|
|
* offlined automatically */
|
|
|
|
if (nofflinable == nonline && cpu0 >= 0 && cpuinfo[cpu0].online) {
|
|
|
|
cpuinfo[cpu0].offlinable = false;
|
|
|
|
nofflinable--;
|
|
|
|
}
|
|
|
|
|
2013-07-30 10:04:21 +00:00
|
|
|
/* the guest agent reported less cpus than requested */
|
|
|
|
if (nvcpus > ncpuinfo) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("guest agent reports less cpu than requested"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* not enough offlinable CPUs to support the request */
|
|
|
|
if (nvcpus < nonline - nofflinable) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
|
|
_("Cannot offline enough CPUs"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ncpuinfo; i++) {
|
|
|
|
if (nvcpus < nonline) {
|
|
|
|
/* unplug */
|
|
|
|
if (cpuinfo[i].offlinable && cpuinfo[i].online) {
|
|
|
|
cpuinfo[i].online = false;
|
2016-06-20 12:15:50 +00:00
|
|
|
cpuinfo[i].modified = true;
|
2013-07-30 10:04:21 +00:00
|
|
|
nonline--;
|
|
|
|
}
|
|
|
|
} else if (nvcpus > nonline) {
|
|
|
|
/* plug */
|
|
|
|
if (!cpuinfo[i].online) {
|
|
|
|
cpuinfo[i].online = true;
|
2016-06-20 12:15:50 +00:00
|
|
|
cpuinfo[i].modified = true;
|
2013-07-30 10:04:21 +00:00
|
|
|
nonline++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* done */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2014-04-02 17:05:42 +00:00
|
|
|
|
|
|
|
|
2018-09-05 04:20:53 +00:00
|
|
|
int
|
|
|
|
qemuAgentGetHostname(qemuAgentPtr mon,
|
|
|
|
char **hostname)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
virJSONValuePtr data = NULL;
|
|
|
|
const char *result = NULL;
|
|
|
|
|
|
|
|
cmd = qemuAgentMakeCommand("guest-get-host-name",
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
if (!cmd)
|
|
|
|
return ret;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0) {
|
2019-08-27 20:35:54 +00:00
|
|
|
if (qemuAgentErrorCommandUnsupported(reply))
|
|
|
|
ret = -2;
|
2018-09-05 04:20:53 +00:00
|
|
|
goto cleanup;
|
2019-08-27 20:35:54 +00:00
|
|
|
}
|
2018-09-05 04:20:53 +00:00
|
|
|
|
|
|
|
if (!(data = virJSONValueObjectGet(reply, "return"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("malformed return value"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(result = virJSONValueObjectGetString(data, "host-name"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'host-name' missing in guest-get-host-name reply"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
*hostname = g_strdup(result);
|
2018-09-05 04:20:53 +00:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-04-02 17:05:42 +00:00
|
|
|
int
|
|
|
|
qemuAgentGetTime(qemuAgentPtr mon,
|
|
|
|
long long *seconds,
|
|
|
|
unsigned int *nseconds)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
unsigned long long json_time;
|
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
|
|
|
cmd = qemuAgentMakeCommand("guest-get-time",
|
|
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
|
|
return ret;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2014-04-02 17:05:42 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberUlong(reply, "return", &json_time) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("malformed return value"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* guest agent returns time in nanoseconds,
|
|
|
|
* we need it in seconds here */
|
|
|
|
*seconds = json_time / 1000000000LL;
|
|
|
|
*nseconds = json_time % 1000000000LL;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuAgentSetTime:
|
|
|
|
* @setTime: time to set
|
|
|
|
* @sync: let guest agent to read domain's RTC (@setTime is ignored)
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuAgentSetTime(qemuAgentPtr mon,
|
|
|
|
long long seconds,
|
|
|
|
unsigned int nseconds,
|
2014-05-19 14:36:55 +00:00
|
|
|
bool rtcSync)
|
2014-04-02 17:05:42 +00:00
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
|
2014-05-19 14:36:55 +00:00
|
|
|
if (rtcSync) {
|
2014-04-02 17:05:42 +00:00
|
|
|
cmd = qemuAgentMakeCommand("guest-set-time", NULL);
|
|
|
|
} else {
|
|
|
|
/* guest agent expect time with nanosecond granularity.
|
|
|
|
* Impressing. */
|
|
|
|
long long json_time;
|
|
|
|
|
|
|
|
/* Check if we overflow. For some reason qemu doesn't handle unsigned
|
|
|
|
* long long on the monitor well as it silently truncates numbers to
|
|
|
|
* signed long long. Therefore we must check overflow against LLONG_MAX
|
|
|
|
* not ULLONG_MAX. */
|
|
|
|
if (seconds > LLONG_MAX / 1000000000LL) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
|
|
_("Time '%lld' is too big for guest agent"),
|
|
|
|
seconds);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
json_time = seconds * 1000000000LL;
|
|
|
|
json_time += nseconds;
|
|
|
|
cmd = qemuAgentMakeCommand("guest-set-time",
|
|
|
|
"I:time", json_time,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cmd)
|
|
|
|
return ret;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2014-04-02 17:05:42 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-11-22 01:27:38 +00:00
|
|
|
|
2019-08-23 16:31:20 +00:00
|
|
|
static void
|
|
|
|
qemuAgentDiskInfoFree(qemuAgentDiskInfoPtr info)
|
|
|
|
{
|
|
|
|
if (!info)
|
|
|
|
return;
|
|
|
|
|
2020-01-10 23:32:16 +00:00
|
|
|
g_free(info->serial);
|
|
|
|
g_free(info->bus_type);
|
|
|
|
g_free(info->devnode);
|
|
|
|
g_free(info);
|
2019-08-23 16:31:20 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 23:32:13 +00:00
|
|
|
void
|
2019-08-23 16:31:20 +00:00
|
|
|
qemuAgentFSInfoFree(qemuAgentFSInfoPtr info)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!info)
|
|
|
|
return;
|
|
|
|
|
2020-01-10 23:32:16 +00:00
|
|
|
g_free(info->mountpoint);
|
|
|
|
g_free(info->name);
|
|
|
|
g_free(info->fstype);
|
2019-08-23 16:31:20 +00:00
|
|
|
|
|
|
|
for (i = 0; i < info->ndisks; i++)
|
|
|
|
qemuAgentDiskInfoFree(info->disks[i]);
|
2020-01-10 23:32:16 +00:00
|
|
|
g_free(info->disks);
|
2019-08-23 16:31:20 +00:00
|
|
|
|
2020-01-10 23:32:16 +00:00
|
|
|
g_free(info);
|
2019-08-23 16:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2020-01-10 23:32:10 +00:00
|
|
|
qemuAgentGetFSInfoFillDisks(virJSONValuePtr jsondisks,
|
2020-01-10 23:32:12 +00:00
|
|
|
qemuAgentFSInfoPtr fsinfo)
|
2019-08-23 16:31:20 +00:00
|
|
|
{
|
|
|
|
size_t ndisks;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!virJSONValueIsArray(jsondisks)) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Malformed guest-get-fsinfo 'disk' data array"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ndisks = virJSONValueArraySize(jsondisks);
|
|
|
|
|
2020-01-10 23:32:15 +00:00
|
|
|
if (ndisks)
|
|
|
|
fsinfo->disks = g_new0(qemuAgentDiskInfoPtr, ndisks);
|
2019-08-23 16:31:20 +00:00
|
|
|
fsinfo->ndisks = ndisks;
|
|
|
|
|
|
|
|
for (i = 0; i < fsinfo->ndisks; i++) {
|
|
|
|
virJSONValuePtr jsondisk = virJSONValueArrayGet(jsondisks, i);
|
|
|
|
virJSONValuePtr pci;
|
|
|
|
qemuAgentDiskInfoPtr disk;
|
|
|
|
const char *val;
|
|
|
|
|
|
|
|
if (!jsondisk) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("array element '%zd' of '%zd' missing in "
|
|
|
|
"guest-get-fsinfo 'disk' data"),
|
|
|
|
i, fsinfo->ndisks);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:32:15 +00:00
|
|
|
fsinfo->disks[i] = g_new0(qemuAgentDiskInfo, 1);
|
2019-08-23 16:31:20 +00:00
|
|
|
disk = fsinfo->disks[i];
|
|
|
|
|
2020-01-10 23:32:11 +00:00
|
|
|
if ((val = virJSONValueObjectGetString(jsondisk, "bus-type")))
|
|
|
|
disk->bus_type = g_strdup(val);
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
if ((val = virJSONValueObjectGetString(jsondisk, "serial")))
|
|
|
|
disk->serial = g_strdup(val);
|
2019-08-23 16:31:20 +00:00
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
if ((val = virJSONValueObjectGetString(jsondisk, "dev")))
|
|
|
|
disk->devnode = g_strdup(val);
|
2019-08-23 16:31:20 +00:00
|
|
|
|
|
|
|
#define GET_DISK_ADDR(jsonObject, var, name) \
|
|
|
|
do { \
|
|
|
|
if (virJSONValueObjectGetNumberUint(jsonObject, name, var) < 0) { \
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, \
|
|
|
|
_("'%s' missing in guest-get-fsinfo " \
|
|
|
|
"'disk' data"), name); \
|
|
|
|
return -1; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
2020-01-10 23:32:11 +00:00
|
|
|
GET_DISK_ADDR(jsondisk, &disk->bus, "bus");
|
|
|
|
GET_DISK_ADDR(jsondisk, &disk->target, "target");
|
|
|
|
GET_DISK_ADDR(jsondisk, &disk->unit, "unit");
|
2019-08-23 16:31:20 +00:00
|
|
|
|
|
|
|
if (!(pci = virJSONValueObjectGet(jsondisk, "pci-controller"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'pci-controller' missing in guest-get-fsinfo "
|
|
|
|
"'disk' data"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:32:11 +00:00
|
|
|
GET_DISK_ADDR(pci, &disk->pci_controller.domain, "domain");
|
|
|
|
GET_DISK_ADDR(pci, &disk->pci_controller.bus, "bus");
|
|
|
|
GET_DISK_ADDR(pci, &disk->pci_controller.slot, "slot");
|
|
|
|
GET_DISK_ADDR(pci, &disk->pci_controller.function, "function");
|
2019-08-23 16:31:20 +00:00
|
|
|
#undef GET_DISK_ADDR
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-08-27 20:35:54 +00:00
|
|
|
/* Returns: 0 on success
|
|
|
|
* -2 when agent command is not supported by the agent
|
|
|
|
* -1 otherwise
|
|
|
|
*/
|
2020-01-10 23:32:13 +00:00
|
|
|
int
|
|
|
|
qemuAgentGetFSInfo(qemuAgentPtr mon,
|
|
|
|
qemuAgentFSInfoPtr **info)
|
2014-11-22 01:27:38 +00:00
|
|
|
{
|
2019-08-23 16:31:20 +00:00
|
|
|
size_t i;
|
2014-11-22 01:27:38 +00:00
|
|
|
int ret = -1;
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
|
|
g_autoptr(virJSONValue) reply = NULL;
|
2014-11-22 01:27:38 +00:00
|
|
|
virJSONValuePtr data;
|
2019-08-23 16:31:20 +00:00
|
|
|
size_t ndata = 0;
|
|
|
|
qemuAgentFSInfoPtr *info_ret = NULL;
|
2014-11-22 01:27:38 +00:00
|
|
|
|
|
|
|
cmd = qemuAgentMakeCommand("guest-get-fsinfo", NULL);
|
|
|
|
if (!cmd)
|
|
|
|
return ret;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0) {
|
2019-08-27 20:35:54 +00:00
|
|
|
if (qemuAgentErrorCommandUnsupported(reply))
|
|
|
|
ret = -2;
|
2014-11-22 01:27:38 +00:00
|
|
|
goto cleanup;
|
2019-08-27 20:35:54 +00:00
|
|
|
}
|
2014-11-22 01:27:38 +00:00
|
|
|
|
|
|
|
if (!(data = virJSONValueObjectGet(reply, "return"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("guest-get-fsinfo reply was missing return data"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2018-04-19 21:29:02 +00:00
|
|
|
if (!virJSONValueIsArray(data)) {
|
2014-11-22 01:27:38 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
2018-04-19 21:29:02 +00:00
|
|
|
_("Malformed guest-get-fsinfo data array"));
|
2014-11-22 01:27:38 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ndata = virJSONValueArraySize(data);
|
2018-04-19 21:29:02 +00:00
|
|
|
if (ndata == 0) {
|
2014-11-22 01:27:38 +00:00
|
|
|
ret = 0;
|
2016-12-12 09:13:42 +00:00
|
|
|
*info = NULL;
|
2014-11-22 01:27:38 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2020-01-10 23:32:16 +00:00
|
|
|
info_ret = g_new0(qemuAgentFSInfoPtr, ndata);
|
2014-11-22 01:27:38 +00:00
|
|
|
|
|
|
|
for (i = 0; i < ndata; i++) {
|
|
|
|
/* Reverse the order to arrange in mount order */
|
|
|
|
virJSONValuePtr entry = virJSONValueArrayGet(data, ndata - 1 - i);
|
2019-08-23 16:31:20 +00:00
|
|
|
virJSONValuePtr disk;
|
|
|
|
unsigned long long bytes_val;
|
|
|
|
const char *result = NULL;
|
2014-11-22 01:27:38 +00:00
|
|
|
|
|
|
|
if (!entry) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2015-10-08 08:17:42 +00:00
|
|
|
_("array element '%zd' of '%zd' missing in "
|
2014-11-22 01:27:38 +00:00
|
|
|
"guest-get-fsinfo return data"),
|
|
|
|
i, ndata);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2020-01-10 23:32:16 +00:00
|
|
|
info_ret[i] = g_new0(qemuAgentFSInfo, 1);
|
2014-11-22 01:27:38 +00:00
|
|
|
|
2018-09-05 04:20:56 +00:00
|
|
|
if (!(result = virJSONValueObjectGetString(entry, "mountpoint"))) {
|
2014-11-22 01:27:38 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'mountpoint' missing in reply of "
|
|
|
|
"guest-get-fsinfo"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
info_ret[i]->mountpoint = g_strdup(result);
|
2018-09-05 04:20:56 +00:00
|
|
|
|
|
|
|
if (!(result = virJSONValueObjectGetString(entry, "name"))) {
|
2014-11-22 01:27:38 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'name' missing in reply of guest-get-fsinfo"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
info_ret[i]->name = g_strdup(result);
|
2018-09-05 04:20:56 +00:00
|
|
|
|
|
|
|
if (!(result = virJSONValueObjectGetString(entry, "type"))) {
|
2014-11-22 01:27:38 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'type' missing in reply of guest-get-fsinfo"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
info_ret[i]->fstype = g_strdup(result);
|
2018-09-05 04:20:56 +00:00
|
|
|
|
2014-11-22 01:27:38 +00:00
|
|
|
|
2019-08-23 16:31:20 +00:00
|
|
|
/* 'used-bytes' and 'total-bytes' were added in qemu-ga 3.0 */
|
|
|
|
if (virJSONValueObjectHasKey(entry, "used-bytes")) {
|
|
|
|
if (virJSONValueObjectGetNumberUlong(entry, "used-bytes",
|
|
|
|
&bytes_val) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Error getting 'used-bytes' in reply of guest-get-fsinfo"));
|
2014-11-22 01:27:38 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2019-08-23 16:31:20 +00:00
|
|
|
info_ret[i]->used_bytes = bytes_val;
|
|
|
|
} else {
|
|
|
|
info_ret[i]->used_bytes = -1;
|
|
|
|
}
|
2014-11-22 01:27:38 +00:00
|
|
|
|
2019-08-23 16:31:20 +00:00
|
|
|
if (virJSONValueObjectHasKey(entry, "total-bytes")) {
|
|
|
|
if (virJSONValueObjectGetNumberUlong(entry, "total-bytes",
|
|
|
|
&bytes_val) < 0) {
|
2014-11-22 01:27:38 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
2019-08-23 16:31:20 +00:00
|
|
|
_("Error getting 'total-bytes' in reply of guest-get-fsinfo"));
|
2014-11-22 01:27:38 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2019-08-23 16:31:20 +00:00
|
|
|
info_ret[i]->total_bytes = bytes_val;
|
|
|
|
} else {
|
|
|
|
info_ret[i]->total_bytes = -1;
|
|
|
|
}
|
2014-11-22 01:27:38 +00:00
|
|
|
|
2019-08-23 16:31:20 +00:00
|
|
|
if (!(disk = virJSONValueObjectGet(entry, "disk"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'disk' missing in reply of guest-get-fsinfo"));
|
|
|
|
goto cleanup;
|
2014-11-22 01:27:38 +00:00
|
|
|
}
|
2019-08-23 16:31:20 +00:00
|
|
|
|
2020-01-10 23:32:12 +00:00
|
|
|
if (qemuAgentGetFSInfoFillDisks(disk, info_ret[i]) < 0)
|
2019-08-23 16:31:20 +00:00
|
|
|
goto cleanup;
|
2014-11-22 01:27:38 +00:00
|
|
|
}
|
|
|
|
|
2019-10-16 11:43:18 +00:00
|
|
|
*info = g_steal_pointer(&info_ret);
|
2014-11-22 01:27:38 +00:00
|
|
|
ret = ndata;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (info_ret) {
|
|
|
|
for (i = 0; i < ndata; i++)
|
2019-08-23 16:31:20 +00:00
|
|
|
qemuAgentFSInfoFree(info_ret[i]);
|
2020-01-10 23:32:16 +00:00
|
|
|
g_free(info_ret);
|
2014-11-22 01:27:38 +00:00
|
|
|
}
|
2019-08-23 16:31:20 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-01-25 18:38:48 +00:00
|
|
|
/*
|
|
|
|
* qemuAgentGetInterfaces:
|
|
|
|
* @mon: Agent monitor
|
|
|
|
* @ifaces: pointer to an array of pointers pointing to interface objects
|
|
|
|
*
|
|
|
|
* Issue guest-network-get-interfaces to guest agent, which returns a
|
|
|
|
* list of interfaces of a running domain along with their IP and MAC
|
|
|
|
* addresses.
|
|
|
|
*
|
|
|
|
* Returns: number of interfaces on success, -1 on error.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuAgentGetInterfaces(qemuAgentPtr mon,
|
|
|
|
virDomainInterfacePtr **ifaces)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
size_t i, j;
|
|
|
|
virJSONValuePtr cmd = NULL;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
virJSONValuePtr ret_array = NULL;
|
|
|
|
size_t ifaces_count = 0;
|
|
|
|
size_t addrs_count = 0;
|
|
|
|
virDomainInterfacePtr *ifaces_ret = NULL;
|
|
|
|
virHashTablePtr ifaces_store = NULL;
|
|
|
|
char **ifname = NULL;
|
|
|
|
|
|
|
|
/* Hash table to handle the interface alias */
|
|
|
|
if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) {
|
|
|
|
virHashFree(ifaces_store);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL)))
|
|
|
|
goto cleanup;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2015-01-25 18:38:48 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (!(ret_array = virJSONValueObjectGet(reply, "return"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("qemu agent didn't provide 'return' field"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2018-04-19 21:29:02 +00:00
|
|
|
if (!virJSONValueIsArray(ret_array)) {
|
2015-01-25 18:38:48 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("qemu agent didn't return an array of interfaces"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2018-04-19 21:29:02 +00:00
|
|
|
for (i = 0; i < virJSONValueArraySize(ret_array); i++) {
|
2015-01-25 18:38:48 +00:00
|
|
|
virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i);
|
|
|
|
virJSONValuePtr ip_addr_arr = NULL;
|
|
|
|
const char *hwaddr, *ifname_s, *name = NULL;
|
|
|
|
virDomainInterfacePtr iface = NULL;
|
|
|
|
|
|
|
|
/* Shouldn't happen but doesn't hurt to check neither */
|
|
|
|
if (!tmp_iface) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("qemu agent reply missing interface entry in array"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* interface name is required to be presented */
|
|
|
|
name = virJSONValueObjectGetString(tmp_iface, "name");
|
|
|
|
if (!name) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("qemu agent didn't provide 'name' field"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle interface alias (<ifname>:<alias>) */
|
|
|
|
ifname = virStringSplit(name, ":", 2);
|
|
|
|
ifname_s = ifname[0];
|
|
|
|
|
|
|
|
iface = virHashLookup(ifaces_store, ifname_s);
|
|
|
|
|
|
|
|
/* If the hash table doesn't contain this iface, add it */
|
|
|
|
if (!iface) {
|
|
|
|
if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (virHashAddEntry(ifaces_store, ifname_s,
|
|
|
|
ifaces_ret[ifaces_count - 1]) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
iface = ifaces_ret[ifaces_count - 1];
|
|
|
|
iface->naddrs = 0;
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
iface->name = g_strdup(ifname_s);
|
2015-01-25 18:38:48 +00:00
|
|
|
|
|
|
|
hwaddr = virJSONValueObjectGetString(tmp_iface, "hardware-address");
|
2019-10-20 11:49:46 +00:00
|
|
|
iface->hwaddr = g_strdup(hwaddr);
|
2015-01-25 18:38:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Has to be freed for each interface. */
|
2016-11-25 08:18:35 +00:00
|
|
|
virStringListFree(ifname);
|
2015-01-25 18:38:48 +00:00
|
|
|
|
|
|
|
/* as well as IP address which - moreover -
|
|
|
|
* can be presented multiple times */
|
|
|
|
ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses");
|
|
|
|
if (!ip_addr_arr)
|
|
|
|
continue;
|
|
|
|
|
2018-04-19 21:29:02 +00:00
|
|
|
if (!virJSONValueIsArray(ip_addr_arr)) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Malformed ip-addresses array"));
|
2015-01-25 18:38:48 +00:00
|
|
|
goto error;
|
2018-04-19 21:29:02 +00:00
|
|
|
}
|
2015-01-25 18:38:48 +00:00
|
|
|
|
|
|
|
/* If current iface already exists, continue with the count */
|
|
|
|
addrs_count = iface->naddrs;
|
|
|
|
|
2018-04-19 21:29:02 +00:00
|
|
|
for (j = 0; j < virJSONValueArraySize(ip_addr_arr); j++) {
|
2015-01-25 18:38:48 +00:00
|
|
|
const char *type, *addr;
|
|
|
|
virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j);
|
|
|
|
virDomainIPAddressPtr ip_addr;
|
|
|
|
|
|
|
|
if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
ip_addr = &iface->addrs[addrs_count - 1];
|
|
|
|
|
|
|
|
/* Shouldn't happen but doesn't hurt to check neither */
|
|
|
|
if (!ip_addr_obj) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("qemu agent reply missing IP addr in array"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
type = virJSONValueObjectGetString(ip_addr_obj, "ip-address-type");
|
|
|
|
if (!type) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("qemu agent didn't provide 'ip-address-type'"
|
|
|
|
" field for interface '%s'"), name);
|
|
|
|
goto error;
|
|
|
|
} else if (STREQ(type, "ipv4")) {
|
|
|
|
ip_addr->type = VIR_IP_ADDR_TYPE_IPV4;
|
|
|
|
} else if (STREQ(type, "ipv6")) {
|
|
|
|
ip_addr->type = VIR_IP_ADDR_TYPE_IPV6;
|
|
|
|
} else {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unknown ip address type '%s'"),
|
|
|
|
type);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address");
|
|
|
|
if (!addr) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("qemu agent didn't provide 'ip-address'"
|
|
|
|
" field for interface '%s'"), name);
|
|
|
|
goto error;
|
|
|
|
}
|
2019-10-20 11:49:46 +00:00
|
|
|
ip_addr->addr = g_strdup(addr);
|
2015-01-25 18:38:48 +00:00
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix",
|
|
|
|
&ip_addr->prefix) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("malformed 'prefix' field"));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
iface->naddrs = addrs_count;
|
|
|
|
}
|
|
|
|
|
2019-10-16 11:43:18 +00:00
|
|
|
*ifaces = g_steal_pointer(&ifaces_ret);
|
2015-01-25 18:38:48 +00:00
|
|
|
ret = ifaces_count;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
virHashFree(ifaces_store);
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (ifaces_ret) {
|
|
|
|
for (i = 0; i < ifaces_count; i++)
|
|
|
|
virDomainInterfaceFree(ifaces_ret[i]);
|
|
|
|
}
|
|
|
|
VIR_FREE(ifaces_ret);
|
2016-11-25 08:18:35 +00:00
|
|
|
virStringListFree(ifname);
|
2015-01-25 18:38:48 +00:00
|
|
|
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2015-05-18 10:42:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuAgentSetUserPassword(qemuAgentPtr mon,
|
|
|
|
const char *user,
|
|
|
|
const char *password,
|
|
|
|
bool crypted)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
virJSONValuePtr cmd = NULL;
|
|
|
|
virJSONValuePtr reply = NULL;
|
|
|
|
char *password64 = NULL;
|
|
|
|
|
2019-09-16 12:29:20 +00:00
|
|
|
password64 = g_base64_encode((unsigned char *)password,
|
|
|
|
strlen(password));
|
2015-05-18 10:42:07 +00:00
|
|
|
|
|
|
|
if (!(cmd = qemuAgentMakeCommand("guest-set-user-password",
|
|
|
|
"b:crypted", crypted,
|
|
|
|
"s:username", user,
|
|
|
|
"s:password", password64,
|
|
|
|
NULL)))
|
|
|
|
goto cleanup;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0)
|
2015-05-18 10:42:07 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
virJSONValueFree(cmd);
|
|
|
|
virJSONValueFree(reply);
|
|
|
|
VIR_FREE(password64);
|
|
|
|
return ret;
|
|
|
|
}
|
2019-08-23 16:31:17 +00:00
|
|
|
|
2019-08-27 20:35:54 +00:00
|
|
|
/* Returns: 0 on success
|
|
|
|
* -2 when agent command is not supported by the agent
|
|
|
|
* -1 otherwise
|
|
|
|
*/
|
2019-08-23 16:31:17 +00:00
|
|
|
int
|
|
|
|
qemuAgentGetUsers(qemuAgentPtr mon,
|
|
|
|
virTypedParameterPtr *params,
|
|
|
|
int *nparams,
|
|
|
|
int *maxparams)
|
|
|
|
{
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
|
|
g_autoptr(virJSONValue) reply = NULL;
|
2019-08-23 16:31:17 +00:00
|
|
|
virJSONValuePtr data = NULL;
|
|
|
|
size_t ndata;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!(cmd = qemuAgentMakeCommand("guest-get-users", NULL)))
|
|
|
|
return -1;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0) {
|
2019-08-27 20:35:54 +00:00
|
|
|
if (qemuAgentErrorCommandUnsupported(reply))
|
|
|
|
return -2;
|
2019-08-23 16:31:17 +00:00
|
|
|
return -1;
|
2019-08-27 20:35:54 +00:00
|
|
|
}
|
2019-08-23 16:31:17 +00:00
|
|
|
|
|
|
|
if (!(data = virJSONValueObjectGetArray(reply, "return"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("guest-get-users reply was missing return data"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!virJSONValueIsArray(data)) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Malformed guest-get-users data array"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ndata = virJSONValueArraySize(data);
|
|
|
|
|
|
|
|
if (virTypedParamsAddUInt(params, nparams, maxparams,
|
|
|
|
"user.count", ndata) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for (i = 0; i < ndata; i++) {
|
|
|
|
virJSONValuePtr entry = virJSONValueArrayGet(data, i);
|
|
|
|
char param_name[VIR_TYPED_PARAM_FIELD_LENGTH];
|
|
|
|
const char *strvalue;
|
|
|
|
double logintime;
|
|
|
|
|
|
|
|
if (!entry) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("array element missing in guest-get-users return "
|
|
|
|
"value"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(strvalue = virJSONValueObjectGetString(entry, "user"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'user' missing in reply of guest-get-users"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-11-13 13:53:42 +00:00
|
|
|
g_snprintf(param_name, VIR_TYPED_PARAM_FIELD_LENGTH, "user.%zu.name", i);
|
2019-08-23 16:31:17 +00:00
|
|
|
if (virTypedParamsAddString(params, nparams, maxparams,
|
|
|
|
param_name, strvalue) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* 'domain' is only present for windows guests */
|
|
|
|
if ((strvalue = virJSONValueObjectGetString(entry, "domain"))) {
|
2019-11-13 13:53:42 +00:00
|
|
|
g_snprintf(param_name, VIR_TYPED_PARAM_FIELD_LENGTH,
|
|
|
|
"user.%zu.domain", i);
|
2019-08-23 16:31:17 +00:00
|
|
|
if (virTypedParamsAddString(params, nparams, maxparams,
|
|
|
|
param_name, strvalue) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virJSONValueObjectGetNumberDouble(entry, "login-time", &logintime) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'login-time' missing in reply of guest-get-users"));
|
|
|
|
return -1;
|
|
|
|
}
|
2019-11-13 13:53:42 +00:00
|
|
|
g_snprintf(param_name, VIR_TYPED_PARAM_FIELD_LENGTH,
|
|
|
|
"user.%zu.login-time", i);
|
2019-08-23 16:31:17 +00:00
|
|
|
if (virTypedParamsAddULLong(params, nparams, maxparams,
|
|
|
|
param_name, logintime * 1000) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ndata;
|
|
|
|
}
|
2019-08-23 16:31:18 +00:00
|
|
|
|
2019-08-27 20:35:54 +00:00
|
|
|
/* Returns: 0 on success
|
|
|
|
* -2 when agent command is not supported by the agent
|
|
|
|
* -1 otherwise
|
|
|
|
*/
|
2019-08-23 16:31:18 +00:00
|
|
|
int
|
|
|
|
qemuAgentGetOSInfo(qemuAgentPtr mon,
|
|
|
|
virTypedParameterPtr *params,
|
|
|
|
int *nparams,
|
|
|
|
int *maxparams)
|
|
|
|
{
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
|
|
g_autoptr(virJSONValue) reply = NULL;
|
2019-08-23 16:31:18 +00:00
|
|
|
virJSONValuePtr data = NULL;
|
|
|
|
|
|
|
|
if (!(cmd = qemuAgentMakeCommand("guest-get-osinfo", NULL)))
|
|
|
|
return -1;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0) {
|
2019-08-27 20:35:54 +00:00
|
|
|
if (qemuAgentErrorCommandUnsupported(reply))
|
|
|
|
return -2;
|
2019-08-23 16:31:18 +00:00
|
|
|
return -1;
|
2019-08-27 20:35:54 +00:00
|
|
|
}
|
2019-08-23 16:31:18 +00:00
|
|
|
|
|
|
|
if (!(data = virJSONValueObjectGetObject(reply, "return"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("guest-get-osinfo reply was missing return data"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define OSINFO_ADD_PARAM(agent_string_, param_string_) \
|
|
|
|
do { \
|
|
|
|
const char *result; \
|
|
|
|
if ((result = virJSONValueObjectGetString(data, agent_string_))) { \
|
|
|
|
if (virTypedParamsAddString(params, nparams, maxparams, \
|
|
|
|
param_string_, result) < 0) { \
|
|
|
|
return -1; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
OSINFO_ADD_PARAM("id", "os.id");
|
|
|
|
OSINFO_ADD_PARAM("name", "os.name");
|
|
|
|
OSINFO_ADD_PARAM("pretty-name", "os.pretty-name");
|
|
|
|
OSINFO_ADD_PARAM("version", "os.version");
|
|
|
|
OSINFO_ADD_PARAM("version-id", "os.version-id");
|
|
|
|
OSINFO_ADD_PARAM("machine", "os.machine");
|
|
|
|
OSINFO_ADD_PARAM("variant", "os.variant");
|
|
|
|
OSINFO_ADD_PARAM("variant-id", "os.variant-id");
|
|
|
|
OSINFO_ADD_PARAM("kernel-release", "os.kernel-release");
|
|
|
|
OSINFO_ADD_PARAM("kernel-version", "os.kernel-version");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2019-08-23 16:31:19 +00:00
|
|
|
|
2019-08-27 20:35:54 +00:00
|
|
|
/* Returns: 0 on success
|
|
|
|
* -2 when agent command is not supported by the agent
|
|
|
|
* -1 otherwise
|
|
|
|
*/
|
2019-08-23 16:31:19 +00:00
|
|
|
int
|
|
|
|
qemuAgentGetTimezone(qemuAgentPtr mon,
|
|
|
|
virTypedParameterPtr *params,
|
|
|
|
int *nparams,
|
|
|
|
int *maxparams)
|
|
|
|
{
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
|
|
g_autoptr(virJSONValue) reply = NULL;
|
2019-08-23 16:31:19 +00:00
|
|
|
virJSONValuePtr data = NULL;
|
|
|
|
const char *name;
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
if (!(cmd = qemuAgentMakeCommand("guest-get-timezone", NULL)))
|
|
|
|
return -1;
|
|
|
|
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
if (qemuAgentCommand(mon, cmd, &reply, true, mon->timeout) < 0) {
|
2019-08-27 20:35:54 +00:00
|
|
|
if (qemuAgentErrorCommandUnsupported(reply))
|
|
|
|
return -2;
|
2019-08-23 16:31:19 +00:00
|
|
|
return -1;
|
2019-08-27 20:35:54 +00:00
|
|
|
}
|
2019-08-23 16:31:19 +00:00
|
|
|
|
|
|
|
if (!(data = virJSONValueObjectGetObject(reply, "return"))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("guest-get-timezone reply was missing return data"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((name = virJSONValueObjectGetString(data, "zone")) &&
|
|
|
|
virTypedParamsAddString(params, nparams, maxparams,
|
|
|
|
"timezone.name", name) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if ((virJSONValueObjectGetNumberInt(data, "offset", &offset)) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("'offset' missing in reply of guest-get-timezone"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virTypedParamsAddInt(params, nparams, maxparams,
|
|
|
|
"timezone.offset", offset) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
Add API to change qemu agent response timeout
Some layered products such as oVirt have requested a way to avoid being
blocked by guest agent commands when querying a loaded vm. For example,
many guest agent commands are polled periodically to monitor changes,
and rather than blocking the calling process, they'd prefer to simply
time out when an agent query is taking too long.
This patch adds a way for the user to specify a custom agent timeout
that is applied to all agent commands.
One special case to note here is the 'guest-sync' command. 'guest-sync'
is issued internally prior to calling any other command. (For example,
when libvirt wants to call 'guest-get-fsinfo', we first call
'guest-sync' and then call 'guest-get-fsinfo').
Previously, the 'guest-sync' command used a 5-second timeout
(VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT), whereas the actual command that
followed always blocked indefinitely
(VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK). As part of this patch, if a
custom timeout is specified that is shorter than
5 seconds, this new timeout is also used for 'guest-sync'. If there is
no custom timeout or if the custom timeout is longer than 5 seconds, we
will continue to use the 5-second timeout.
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2019-11-13 22:06:09 +00:00
|
|
|
|
|
|
|
/* qemuAgentSetResponseTimeout:
|
|
|
|
* mon: agent monitor
|
|
|
|
* timeout: number of seconds to wait for agent response
|
|
|
|
*
|
|
|
|
* The agent object must be locked prior to calling this function.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
qemuAgentSetResponseTimeout(qemuAgentPtr mon,
|
|
|
|
int timeout)
|
|
|
|
{
|
|
|
|
mon->timeout = timeout;
|
|
|
|
}
|