QEMU guest agent support

There is now a standard QEMU guest agent that can be installed
and given a virtio serial channel

    <channel type='unix'>
      <source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/>
      <target type='virtio' name='org.qemu.guest_agent.0'/>
    </channel>

The protocol that runs over the guest agent is JSON based and
very similar to the JSON monitor. We can't use exactly the same
code because there are some odd differences in the way messages
and errors are structured. The qemu_agent.c file is based on
a combination and simplification of qemu_monitor.c and
qemu_monitor_json.c

* src/qemu/qemu_agent.c, src/qemu/qemu_agent.h: Support for
  talking to the agent for shutdown
* src/qemu/qemu_domain.c, src/qemu/qemu_domain.h: Add thread
  helpers for talking to the agent
* src/qemu/qemu_process.c: Connect to agent whenever starting
  a guest
* src/qemu/qemu_monitor_json.c: Make variable static
This commit is contained in:
Daniel P. Berrange 2011-10-05 18:31:54 +01:00 committed by Michal Privoznik
parent 2f5519dcb6
commit c160ce3316
9 changed files with 1500 additions and 2 deletions

View File

@ -3017,6 +3017,10 @@ qemu-kvm -net nic,model=? /dev/null
&lt;channel type='pty'&gt;
&lt;target type='virtio' name='arbitrary.virtio.serial.port.name'/&gt;
&lt;/channel&gt;
&lt;channel type='unix'&gt;
&lt;source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/&gt;
&lt;target type='virtio' name='org.qemu.guest_agent.0'/&gt;
&lt;/channel&gt;
&lt;channel type='spicevmc'&gt;
&lt;target type='virtio' name='com.redhat.spice.0'/&gt;
&lt;/channel&gt;
@ -3045,7 +3049,11 @@ qemu-kvm -net nic,model=? /dev/null
optional element <code>address</code> can tie the channel to a
particular <code>type='virtio-serial'</code>
controller, <a href="#elementsAddress">documented above</a>.
<span class="since">Since 0.7.7</span></dd>
With qemu, if <code>name</code> is "org.qemu.guest_agent.0",
then libvirt can interact with a guest agent installed in the
guest, for actions such as guest shutdown or file system quiescing.
<span class="since">Since 0.7.7, guest agent interaction
since 0.9.10</span></dd>
<dt><code>spicevmc</code></dt>
<dd>Paravirtualized SPICE channel. The domain must also have a

View File

@ -57,6 +57,7 @@ src/nwfilter/nwfilter_learnipaddr.c
src/openvz/openvz_conf.c
src/openvz/openvz_driver.c
src/phyp/phyp_driver.c
src/qemu/qemu_agent.c
src/qemu/qemu_bridge_filter.c
src/qemu/qemu_capabilities.c
src/qemu/qemu_cgroup.c

View File

@ -364,6 +364,7 @@ VBOX_DRIVER_EXTRA_DIST = \
vbox/vbox_XPCOMCGlue.c vbox/vbox_XPCOMCGlue.h
QEMU_DRIVER_SOURCES = \
qemu/qemu_agent.c qemu/qemu_agent.h \
qemu/qemu_capabilities.c qemu/qemu_capabilities.h\
qemu/qemu_command.c qemu/qemu_command.h \
qemu/qemu_domain.c qemu/qemu_domain.h \

1112
src/qemu/qemu_agent.c Normal file

File diff suppressed because it is too large Load Diff

69
src/qemu/qemu_agent.h Normal file
View File

@ -0,0 +1,69 @@
/*
* qemu_agent.h: interaction with QEMU guest agent
*
* Copyright (C) 2006-2012 Red Hat, Inc.
* 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
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#ifndef __QEMU_AGENT_H__
# define __QEMU_AGENT_H__
# include "internal.h"
# include "domain_conf.h"
typedef struct _qemuAgent qemuAgent;
typedef qemuAgent *qemuAgentPtr;
typedef struct _qemuAgentCallbacks qemuAgentCallbacks;
typedef qemuAgentCallbacks *qemuAgentCallbacksPtr;
struct _qemuAgentCallbacks {
void (*destroy)(qemuAgentPtr mon,
virDomainObjPtr vm);
void (*eofNotify)(qemuAgentPtr mon,
virDomainObjPtr vm);
void (*errorNotify)(qemuAgentPtr mon,
virDomainObjPtr vm);
};
qemuAgentPtr qemuAgentOpen(virDomainObjPtr vm,
virDomainChrSourceDefPtr config,
qemuAgentCallbacksPtr cb);
void qemuAgentLock(qemuAgentPtr mon);
void qemuAgentUnlock(qemuAgentPtr mon);
int qemuAgentRef(qemuAgentPtr mon);
int qemuAgentUnref(qemuAgentPtr mon) ATTRIBUTE_RETURN_CHECK;
void qemuAgentClose(qemuAgentPtr mon);
typedef enum {
QEMU_AGENT_SHUTDOWN_POWERDOWN,
QEMU_AGENT_SHUTDOWN_REBOOT,
QEMU_AGENT_SHUTDOWN_HALT,
QEMU_AGENT_SHUTDOWN_LAST,
} qemuAgentShutdownMode;
int qemuAgentShutdown(qemuAgentPtr mon,
qemuAgentShutdownMode mode);
#endif /* __QEMU_AGENT_H__ */

View File

@ -219,6 +219,10 @@ static void qemuDomainObjPrivateFree(void *data)
VIR_ERROR(_("Unexpected QEMU monitor still active during domain deletion"));
qemuMonitorClose(priv->mon);
}
if (priv->agent) {
VIR_ERROR(_("Unexpected QEMU agent still active during domain deletion"));
qemuAgentClose(priv->agent);
}
VIR_FREE(priv);
}
@ -1025,6 +1029,99 @@ void qemuDomainObjExitMonitorWithDriver(struct qemud_driver *driver,
qemuDomainObjExitMonitorInternal(driver, true, obj);
}
static int
qemuDomainObjEnterAgentInternal(struct qemud_driver *driver,
bool driver_locked,
virDomainObjPtr obj)
{
qemuDomainObjPrivatePtr priv = obj->privateData;
qemuAgentLock(priv->agent);
qemuAgentRef(priv->agent);
ignore_value(virTimeMillisNow(&priv->agentStart));
virDomainObjUnlock(obj);
if (driver_locked)
qemuDriverUnlock(driver);
return 0;
}
static void ATTRIBUTE_NONNULL(1)
qemuDomainObjExitAgentInternal(struct qemud_driver *driver,
bool driver_locked,
virDomainObjPtr obj)
{
qemuDomainObjPrivatePtr priv = obj->privateData;
int refs;
refs = qemuAgentUnref(priv->agent);
if (refs > 0)
qemuAgentUnlock(priv->agent);
if (driver_locked)
qemuDriverLock(driver);
virDomainObjLock(obj);
priv->agentStart = 0;
if (refs == 0) {
priv->agent = NULL;
}
}
/*
* obj must be locked before calling, qemud_driver must be unlocked
*
* To be called immediately before any QEMU agent API call
* Must have already either called qemuDomainObjBeginJob() and checked
* that the VM is still active;
*
* To be followed with qemuDomainObjExitAgent() once complete
*/
void qemuDomainObjEnterAgent(struct qemud_driver *driver,
virDomainObjPtr obj)
{
ignore_value(qemuDomainObjEnterAgentInternal(driver, false, obj));
}
/* obj must NOT be locked before calling, qemud_driver must be unlocked
*
* Should be paired with an earlier qemuDomainObjEnterAgent() call
*/
void qemuDomainObjExitAgent(struct qemud_driver *driver,
virDomainObjPtr obj)
{
qemuDomainObjExitAgentInternal(driver, false, obj);
}
/*
* obj must be locked before calling, qemud_driver must be locked
*
* To be called immediately before any QEMU agent API call
* Must have already either called qemuDomainObjBeginJobWithDriver() and
* checked that the VM is still active; may not be used for nested async jobs.
*
* To be followed with qemuDomainObjExitAgentWithDriver() once complete
*/
void qemuDomainObjEnterAgentWithDriver(struct qemud_driver *driver,
virDomainObjPtr obj)
{
ignore_value(qemuDomainObjEnterAgentInternal(driver, true, obj));
}
/* obj must NOT be locked before calling, qemud_driver must be unlocked,
* and will be locked after returning
*
* Should be paired with an earlier qemuDomainObjEnterAgentWithDriver() call
*/
void qemuDomainObjExitAgentWithDriver(struct qemud_driver *driver,
virDomainObjPtr obj)
{
qemuDomainObjExitAgentInternal(driver, true, obj);
}
void qemuDomainObjEnterRemoteWithDriver(struct qemud_driver *driver,
virDomainObjPtr obj)
{

View File

@ -27,6 +27,7 @@
# include "threads.h"
# include "domain_conf.h"
# include "qemu_monitor.h"
# include "qemu_agent.h"
# include "qemu_conf.h"
# include "bitmap.h"
@ -102,6 +103,11 @@ struct _qemuDomainObjPrivate {
int monJSON;
bool monError;
unsigned long long monStart;
qemuAgentPtr agent;
bool agentError;
unsigned long long agentStart;
bool gotShutdown;
bool beingDestroyed;
char *pidfile;
@ -192,6 +198,22 @@ int qemuDomainObjEnterMonitorAsync(struct qemud_driver *driver,
void qemuDomainObjExitMonitorWithDriver(struct qemud_driver *driver,
virDomainObjPtr obj)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
void qemuDomainObjEnterAgent(struct qemud_driver *driver,
virDomainObjPtr obj)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
void qemuDomainObjExitAgent(struct qemud_driver *driver,
virDomainObjPtr obj)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
void qemuDomainObjEnterAgentWithDriver(struct qemud_driver *driver,
virDomainObjPtr obj)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
void qemuDomainObjExitAgentWithDriver(struct qemud_driver *driver,
virDomainObjPtr obj)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
void qemuDomainObjEnterRemoteWithDriver(struct qemud_driver *driver,
virDomainObjPtr obj)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);

View File

@ -59,7 +59,7 @@ static void qemuMonitorJSONHandleVNCInitialize(qemuMonitorPtr mon, virJSONValueP
static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValuePtr data);
static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr data);
struct {
static struct {
const char *type;
void (*handler)(qemuMonitorPtr mon, virJSONValuePtr data);
} eventHandlers[] = {

View File

@ -106,6 +106,164 @@ qemuProcessRemoveDomainStatus(struct qemud_driver *driver,
/* XXX figure out how to remove this */
extern struct qemud_driver *qemu_driver;
/*
* This is a callback registered with a qemuAgentPtr instance,
* and to be invoked when the agent console hits an end of file
* condition, or error, thus indicating VM shutdown should be
* performed
*/
static void
qemuProcessHandleAgentEOF(qemuAgentPtr agent ATTRIBUTE_UNUSED,
virDomainObjPtr vm)
{
struct qemud_driver *driver = qemu_driver;
qemuDomainObjPrivatePtr priv;
VIR_DEBUG("Received EOF from agent on %p '%s'", vm, vm->def->name);
qemuDriverLock(driver);
virDomainObjLock(vm);
priv = vm->privateData;
qemuAgentClose(agent);
priv->agent = NULL;
virDomainObjUnlock(vm);
qemuDriverUnlock(driver);
}
/*
* This is invoked when there is some kind of error
* parsing data to/from the agent. The VM can continue
* to run, but no further agent commands will be
* allowed
*/
static void
qemuProcessHandleAgentError(qemuAgentPtr agent ATTRIBUTE_UNUSED,
virDomainObjPtr vm)
{
struct qemud_driver *driver = qemu_driver;
qemuDomainObjPrivatePtr priv;
VIR_DEBUG("Received error from agent on %p '%s'", vm, vm->def->name);
qemuDriverLock(driver);
virDomainObjLock(vm);
priv = vm->privateData;
priv->agentError = true;
virDomainObjUnlock(vm);
qemuDriverUnlock(driver);
}
static void qemuProcessHandleAgentDestroy(qemuAgentPtr agent,
virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv;
virDomainObjLock(vm);
priv = vm->privateData;
if (priv->agent == agent)
priv->agent = NULL;
if (virDomainObjUnref(vm) > 0)
virDomainObjUnlock(vm);
}
static qemuAgentCallbacks agentCallbacks = {
.destroy = qemuProcessHandleAgentDestroy,
.eofNotify = qemuProcessHandleAgentEOF,
.errorNotify = qemuProcessHandleAgentError,
};
static virDomainChrSourceDefPtr
qemuFindAgentConfig(virDomainDefPtr def)
{
virDomainChrSourceDefPtr config = NULL;
int i;
for (i = 0 ; i < def->nchannels ; i++) {
virDomainChrDefPtr channel = def->channels[i];
if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
continue;
if (STREQ(channel->target.name, "org.qemu.guest_agent.0")) {
config = &channel->source;
break;
}
}
return config;
}
static int
qemuConnectAgent(struct qemud_driver *driver, virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
int ret = -1;
qemuAgentPtr agent = NULL;
virDomainChrSourceDefPtr config = qemuFindAgentConfig(vm->def);
if (!config)
return 0;
if (virSecurityManagerSetDaemonSocketLabel(driver->securityManager,
vm->def) < 0) {
VIR_ERROR(_("Failed to set security context for agent for %s"),
vm->def->name);
goto cleanup;
}
/* Hold an extra reference because we can't allow 'vm' to be
* deleted while the agent is active */
virDomainObjRef(vm);
ignore_value(virTimeMillisNow(&priv->agentStart));
virDomainObjUnlock(vm);
qemuDriverUnlock(driver);
agent = qemuAgentOpen(vm,
config,
&agentCallbacks);
qemuDriverLock(driver);
virDomainObjLock(vm);
priv->agentStart = 0;
if (virSecurityManagerClearSocketLabel(driver->securityManager,
vm->def) < 0) {
VIR_ERROR(_("Failed to clear security context for agent for %s"),
vm->def->name);
goto cleanup;
}
/* Safe to ignore value since ref count was incremented above */
if (agent == NULL)
ignore_value(virDomainObjUnref(vm));
if (!virDomainObjIsActive(vm)) {
qemuAgentClose(agent);
goto cleanup;
}
priv->agent = agent;
if (priv->agent == NULL) {
VIR_INFO("Failed to connect agent for %s", vm->def->name);
goto cleanup;
}
ret = 0;
cleanup:
return ret;
}
/*
* This is a callback registered with a qemuMonitorPtr instance,
* and to be invoked when the monitor console hits an end of file
@ -2691,6 +2849,14 @@ qemuProcessReconnect(void *opaque)
if (qemuConnectMonitor(driver, obj) < 0)
goto error;
/* Failure to connect to agent shouldn't be fatal */
if (qemuConnectAgent(driver, obj) < 0) {
VIR_WARN("Cannot connect to QEMU guest agent for %s",
obj->def->name);
virResetLastError();
priv->agentError = true;
}
if (qemuUpdateActivePciHostdevs(driver, obj->def) < 0) {
goto error;
}
@ -3258,6 +3424,14 @@ int qemuProcessStart(virConnectPtr conn,
if (qemuProcessWaitForMonitor(driver, vm, priv->qemuCaps, pos) < 0)
goto cleanup;
/* Failure to connect to agent shouldn't be fatal */
if (qemuConnectAgent(driver, vm) < 0) {
VIR_WARN("Cannot connect to QEMU guest agent for %s",
vm->def->name);
virResetLastError();
priv->agentError = true;
}
VIR_DEBUG("Detecting VCPU PIDs");
if (qemuProcessDetectVcpuPIDs(driver, vm) < 0)
goto cleanup;
@ -3460,6 +3634,12 @@ void qemuProcessStop(struct qemud_driver *driver,
}
}
if (priv->agent) {
qemuAgentClose(priv->agent);
priv->agent = NULL;
priv->agentError = false;
}
if (priv->mon)
qemuMonitorClose(priv->mon);
@ -3713,6 +3893,14 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED,
if (qemuProcessWaitForMonitor(driver, vm, priv->qemuCaps, -1) < 0)
goto cleanup;
/* Failure to connect to agent shouldn't be fatal */
if (qemuConnectAgent(driver, vm) < 0) {
VIR_WARN("Cannot connect to QEMU guest agent for %s",
vm->def->name);
virResetLastError();
priv->agentError = true;
}
VIR_DEBUG("Detecting VCPU PIDs");
if (qemuProcessDetectVcpuPIDs(driver, vm) < 0)
goto cleanup;