mirror of
https://github.com/libvirt/libvirt.git
synced 2026-10-02 12:20:06 -05:00
Add support for a new domain event which can be used to track the state of any virtio channel. Previously one could only monitor the "org.qemu.guest_agent.0" channel which had a dedicated agent lifecycle event. The channel lifecycle event will be emitted alongside the agent specific one. Signed-off-by: Lucas Kornicki <lucas.kornicki@nutanix.com> Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
1417 lines
45 KiB
C
1417 lines
45 KiB
C
/*
|
|
* virsh-domain-event.c: Domain event listening commands
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "virsh-util.h"
|
|
|
|
#include "internal.h"
|
|
#include "viralloc.h"
|
|
#include "virenum.h"
|
|
#include "virtime.h"
|
|
#include "virtypedparam.h"
|
|
#include "virxml.h"
|
|
|
|
/*
|
|
* "event" command
|
|
*/
|
|
|
|
VIR_ENUM_DECL(virshDomainEvent);
|
|
VIR_ENUM_IMPL(virshDomainEvent,
|
|
VIR_DOMAIN_EVENT_LAST,
|
|
N_("Defined"),
|
|
N_("Undefined"),
|
|
N_("Started"),
|
|
N_("Suspended"),
|
|
N_("Resumed"),
|
|
N_("Stopped"),
|
|
N_("Shutdown"),
|
|
N_("PMSuspended"),
|
|
N_("Crashed"));
|
|
|
|
static const char *
|
|
virshDomainEventToString(int event)
|
|
{
|
|
const char *str = virshDomainEventTypeToString(event);
|
|
return str ? _(str) : _("unknown");
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshDomainEventDefined);
|
|
VIR_ENUM_IMPL(virshDomainEventDefined,
|
|
VIR_DOMAIN_EVENT_DEFINED_LAST,
|
|
N_("Added"),
|
|
N_("Updated"),
|
|
N_("Renamed"),
|
|
N_("Snapshot"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventUndefined);
|
|
VIR_ENUM_IMPL(virshDomainEventUndefined,
|
|
VIR_DOMAIN_EVENT_UNDEFINED_LAST,
|
|
N_("Removed"),
|
|
N_("Renamed"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventStarted);
|
|
VIR_ENUM_IMPL(virshDomainEventStarted,
|
|
VIR_DOMAIN_EVENT_STARTED_LAST,
|
|
N_("Booted"),
|
|
N_("Migrated"),
|
|
N_("Restored"),
|
|
N_("Snapshot"),
|
|
N_("Event wakeup"),
|
|
N_("Recreated"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventSuspended);
|
|
VIR_ENUM_IMPL(virshDomainEventSuspended,
|
|
VIR_DOMAIN_EVENT_SUSPENDED_LAST,
|
|
N_("Paused"),
|
|
N_("Migrated"),
|
|
N_("I/O Error"),
|
|
N_("Watchdog"),
|
|
N_("Restored"),
|
|
N_("Snapshot"),
|
|
N_("API error"),
|
|
N_("Post-copy"),
|
|
N_("Post-copy Error"),
|
|
N_("guest shutdown"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventResumed);
|
|
VIR_ENUM_IMPL(virshDomainEventResumed,
|
|
VIR_DOMAIN_EVENT_RESUMED_LAST,
|
|
N_("Unpaused"),
|
|
N_("Migrated"),
|
|
N_("Snapshot"),
|
|
N_("Post-copy"),
|
|
N_("Post-copy Error"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventStopped);
|
|
VIR_ENUM_IMPL(virshDomainEventStopped,
|
|
VIR_DOMAIN_EVENT_STOPPED_LAST,
|
|
N_("Shutdown"),
|
|
N_("Destroyed"),
|
|
N_("Crashed"),
|
|
N_("Migrated"),
|
|
N_("Saved"),
|
|
N_("Failed"),
|
|
N_("Snapshot"),
|
|
N_("Recreated"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventShutdown);
|
|
VIR_ENUM_IMPL(virshDomainEventShutdown,
|
|
VIR_DOMAIN_EVENT_SHUTDOWN_LAST,
|
|
N_("Finished"),
|
|
N_("Finished after guest request"),
|
|
N_("Finished after host request"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventPMSuspended);
|
|
VIR_ENUM_IMPL(virshDomainEventPMSuspended,
|
|
VIR_DOMAIN_EVENT_PMSUSPENDED_LAST,
|
|
N_("Memory"),
|
|
N_("Disk"));
|
|
|
|
VIR_ENUM_DECL(virshDomainEventCrashed);
|
|
VIR_ENUM_IMPL(virshDomainEventCrashed,
|
|
VIR_DOMAIN_EVENT_CRASHED_LAST,
|
|
N_("Panicked"),
|
|
N_("Crashloaded"));
|
|
|
|
static const char *
|
|
virshDomainEventDetailToString(int event, int detail)
|
|
{
|
|
const char *str = NULL;
|
|
switch ((virDomainEventType) event) {
|
|
case VIR_DOMAIN_EVENT_DEFINED:
|
|
str = virshDomainEventDefinedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_UNDEFINED:
|
|
str = virshDomainEventUndefinedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_STARTED:
|
|
str = virshDomainEventStartedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_SUSPENDED:
|
|
str = virshDomainEventSuspendedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_RESUMED:
|
|
str = virshDomainEventResumedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_STOPPED:
|
|
str = virshDomainEventStoppedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_SHUTDOWN:
|
|
str = virshDomainEventShutdownTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_PMSUSPENDED:
|
|
str = virshDomainEventPMSuspendedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_CRASHED:
|
|
str = virshDomainEventCrashedTypeToString(detail);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_LAST:
|
|
break;
|
|
}
|
|
return str ? _(str) : _("unknown");
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshGraphicsPhase);
|
|
VIR_ENUM_IMPL(virshGraphicsPhase,
|
|
VIR_DOMAIN_EVENT_GRAPHICS_LAST,
|
|
N_("connect"),
|
|
N_("initialize"),
|
|
N_("disconnect"));
|
|
|
|
static const char *
|
|
virshGraphicsPhaseToString(int phase)
|
|
{
|
|
const char *str = virshGraphicsPhaseTypeToString(phase);
|
|
return str ? _(str) : _("unknown");
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshGraphicsAddress);
|
|
VIR_ENUM_IMPL(virshGraphicsAddress,
|
|
VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_LAST,
|
|
"IPv4",
|
|
"IPv6",
|
|
"unix");
|
|
|
|
static const char *
|
|
virshGraphicsAddressToString(int family)
|
|
{
|
|
const char *str = virshGraphicsAddressTypeToString(family);
|
|
|
|
if (str)
|
|
return str;
|
|
|
|
return _("unknown");
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshDomainBlockJobStatus);
|
|
VIR_ENUM_IMPL(virshDomainBlockJobStatus,
|
|
VIR_DOMAIN_BLOCK_JOB_LAST,
|
|
N_("completed"),
|
|
N_("failed"),
|
|
N_("canceled"),
|
|
N_("ready"));
|
|
|
|
static const char *
|
|
virshDomainBlockJobStatusToString(int status)
|
|
{
|
|
const char *str = virshDomainBlockJobStatusTypeToString(status);
|
|
return str ? _(str) : _("unknown");
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshDomainEventDiskChange);
|
|
VIR_ENUM_IMPL(virshDomainEventDiskChange,
|
|
VIR_DOMAIN_EVENT_DISK_CHANGE_LAST,
|
|
N_("changed"),
|
|
N_("dropped"));
|
|
|
|
static const char *
|
|
virshDomainEventDiskChangeToString(int reason)
|
|
{
|
|
const char *str = virshDomainEventDiskChangeTypeToString(reason);
|
|
return str ? _(str) : _("unknown");
|
|
}
|
|
|
|
struct virshDomainEventCallback {
|
|
const char *name;
|
|
virConnectDomainEventGenericCallback cb;
|
|
};
|
|
typedef struct virshDomainEventCallback virshDomainEventCallback;
|
|
|
|
|
|
struct virshDomEventData {
|
|
vshControl *ctl;
|
|
int event;
|
|
bool loop;
|
|
int *count;
|
|
bool timestamp;
|
|
virshDomainEventCallback *cb;
|
|
int id;
|
|
|
|
virMutex *m; /* needed to signal that handler was unregistered for clean shutdown */
|
|
virCond *c;
|
|
};
|
|
typedef struct virshDomEventData virshDomEventData;
|
|
|
|
|
|
static void
|
|
virshDomEventDataUnregistered(virshDomEventData *d)
|
|
{
|
|
g_auto(virLockGuard) name = virLockGuardLock(d->m);
|
|
/* signal that the handler was unregistered */
|
|
d->id = -1;
|
|
virCondSignal(d->c);
|
|
}
|
|
|
|
|
|
static void G_GNUC_PRINTF(2, 3)
|
|
virshEventPrintf(virshDomEventData *data,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (!data->loop && *data->count)
|
|
return;
|
|
|
|
if (data->timestamp) {
|
|
char timestamp[VIR_TIME_STRING_BUFLEN] = "";
|
|
|
|
ignore_value(virTimeStringNowRaw(timestamp));
|
|
vshPrint(data->ctl, "%s: ", timestamp);
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
vshPrintVa(data->ctl, fmt, ap);
|
|
va_end(ap);
|
|
|
|
(*data->count)++;
|
|
if (!data->loop)
|
|
vshEventDone(data->ctl);
|
|
}
|
|
|
|
/**
|
|
* virshEventPrint:
|
|
*
|
|
* @data: opaque data passed to all event callbacks
|
|
* @buf: string buffer describing the event
|
|
*
|
|
* Print the event description found in @buf and update virshDomEventData.
|
|
*
|
|
* This function resets @buf and frees all memory consumed by its content.
|
|
*/
|
|
static void
|
|
virshEventPrint(virshDomEventData *data,
|
|
virBuffer *buf)
|
|
{
|
|
g_autofree char *msg = NULL;
|
|
|
|
if (!(msg = virBufferContentAndReset(buf)))
|
|
return;
|
|
|
|
virshEventPrintf(data, "%s", msg);
|
|
}
|
|
|
|
|
|
static void
|
|
virshEventGenericPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event '%1$s' for domain '%2$s'\n"),
|
|
((virshDomEventData *) opaque)->cb->name,
|
|
virDomainGetName(dom));
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int event,
|
|
int detail,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'lifecycle' for domain '%1$s': %2$s %3$s\n"),
|
|
virDomainGetName(dom),
|
|
virshDomainEventToString(event),
|
|
virshDomainEventDetailToString(event, detail));
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventRTCChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
long long utcoffset,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'rtc-change' for domain '%1$s': %2$lld\n"),
|
|
virDomainGetName(dom),
|
|
utcoffset);
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventWatchdogPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int action,
|
|
void *opaque)
|
|
{
|
|
switch ((virDomainEventWatchdogAction) action) {
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_NONE:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': none\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_PAUSE:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': pause\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_RESET:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': reset\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_POWEROFF:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': poweroff\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_SHUTDOWN:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': shutdown\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_DEBUG:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': debug\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_INJECTNMI:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': inject-nmi\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
case VIR_DOMAIN_EVENT_WATCHDOG_LAST:
|
|
default:
|
|
virshEventPrintf(opaque, _("event 'watchdog' for domain '%1$s': unknown\n"),
|
|
virDomainGetName(dom));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
virshEventIOErrorPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *srcPath,
|
|
const char *devAlias,
|
|
int action,
|
|
void *opaque)
|
|
{
|
|
switch ((virDomainEventIOErrorAction) action) {
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_NONE:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) none\n"),
|
|
virDomainGetName(dom), srcPath, devAlias);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_PAUSE:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) pause\n"),
|
|
virDomainGetName(dom), srcPath, devAlias);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_REPORT:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) report\n"),
|
|
virDomainGetName(dom), srcPath, devAlias);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_LAST:
|
|
default:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) unknown\n"),
|
|
virDomainGetName(dom), srcPath, devAlias);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
virshEventGraphicsPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int phase,
|
|
const virDomainEventGraphicsAddress *local,
|
|
const virDomainEventGraphicsAddress *remote,
|
|
const char *authScheme,
|
|
const virDomainEventGraphicsSubject *subject,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
size_t i;
|
|
|
|
virBufferAsprintf(&buf, _("event 'graphics' for domain '%1$s': %2$s local[%3$s %4$s %5$s] remote[%6$s %7$s %8$s] %9$s\n"),
|
|
virDomainGetName(dom),
|
|
virshGraphicsPhaseToString(phase),
|
|
virshGraphicsAddressToString(local->family),
|
|
local->node,
|
|
local->service,
|
|
virshGraphicsAddressToString(remote->family),
|
|
remote->node,
|
|
remote->service,
|
|
authScheme);
|
|
for (i = 0; i < subject->nidentity; i++) {
|
|
virBufferAsprintf(&buf, "\t%s=%s\n",
|
|
subject->identities[i].type,
|
|
subject->identities[i].name);
|
|
}
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventIOErrorReasonPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *srcPath,
|
|
const char *devAlias,
|
|
int action,
|
|
const char *reason,
|
|
void *opaque)
|
|
{
|
|
switch ((virDomainEventIOErrorAction) action) {
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_NONE:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) none due to %4$s\n"),
|
|
virDomainGetName(dom), srcPath, devAlias, reason);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_PAUSE:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) pause due to %4$s\n"),
|
|
virDomainGetName(dom), srcPath, devAlias, reason);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_REPORT:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) report due to %4$s\n"),
|
|
virDomainGetName(dom), srcPath, devAlias, reason);
|
|
break;
|
|
case VIR_DOMAIN_EVENT_IO_ERROR_LAST:
|
|
default:
|
|
virshEventPrintf(opaque, _("event 'io-error' for domain '%1$s': %2$s (%3$s) unknown due to %4$s\n"),
|
|
virDomainGetName(dom), srcPath, devAlias, reason);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
virshEventBlockJobPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *disk,
|
|
int type,
|
|
int status,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event '%1$s' for domain '%2$s': %3$s for %4$s %5$s\n"),
|
|
((virshDomEventData *) opaque)->cb->name,
|
|
virDomainGetName(dom),
|
|
virshDomainBlockJobToString(type),
|
|
disk,
|
|
virshDomainBlockJobStatusToString(status));
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventDiskChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *oldSrc,
|
|
const char *newSrc,
|
|
const char *alias,
|
|
int reason,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'disk-change' for domain '%1$s' disk %2$s: %3$s -> %4$s: %5$s\n"),
|
|
virDomainGetName(dom),
|
|
alias,
|
|
NULLSTR(oldSrc),
|
|
NULLSTR(newSrc),
|
|
virshDomainEventDiskChangeToString(reason));
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventTrayChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *alias,
|
|
int reason,
|
|
void *opaque)
|
|
{
|
|
switch ((virDomainEventTrayChangeReason) reason) {
|
|
case VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN:
|
|
virshEventPrintf(opaque, _("event 'tray-change' for domain '%1$s' disk %2$s: opened\n"),
|
|
virDomainGetName(dom), alias);
|
|
break;
|
|
|
|
case VIR_DOMAIN_EVENT_TRAY_CHANGE_CLOSE:
|
|
virshEventPrintf(opaque, _("event 'tray-change' for domain '%1$s' disk %2$s: closed\n"),
|
|
virDomainGetName(dom), alias);
|
|
break;
|
|
|
|
case VIR_DOMAIN_EVENT_TRAY_CHANGE_LAST:
|
|
default:
|
|
virshEventPrintf(opaque, _("event 'tray-change' for domain '%1$s' disk %2$s: unknown\n"),
|
|
virDomainGetName(dom), alias);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
virshEventPMChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int reason G_GNUC_UNUSED,
|
|
void *opaque)
|
|
{
|
|
/* As long as libvirt.h doesn't define any reasons, we might as
|
|
* well treat all PM state changes as generic events. */
|
|
virshEventGenericPrint(conn, dom, opaque);
|
|
}
|
|
|
|
static void
|
|
virshEventBalloonChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
unsigned long long actual,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'balloon-change' for domain '%1$s': %2$lluKiB\n"),
|
|
virDomainGetName(dom),
|
|
actual);
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventDeviceRemovedPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *alias,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'device-removed' for domain '%1$s': %2$s\n"),
|
|
virDomainGetName(dom),
|
|
alias);
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventDeviceAddedPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *alias,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'device-added' for domain '%1$s': %2$s\n"),
|
|
virDomainGetName(dom),
|
|
alias);
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventTunablePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
virTypedParameterPtr params,
|
|
int nparams,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
size_t i;
|
|
char *value;
|
|
|
|
virBufferAsprintf(&buf, _("event 'tunable' for domain '%1$s':\n"),
|
|
virDomainGetName(dom));
|
|
for (i = 0; i < nparams; i++) {
|
|
value = virTypedParameterToString(¶ms[i]);
|
|
if (value) {
|
|
virBufferAsprintf(&buf, "\t%s: %s\n", params[i].field, value);
|
|
VIR_FREE(value);
|
|
}
|
|
}
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshEventAgentLifecycleState);
|
|
VIR_ENUM_IMPL(virshEventAgentLifecycleState,
|
|
VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_LAST,
|
|
N_("unknown"),
|
|
N_("connected"),
|
|
N_("disconnected"));
|
|
|
|
VIR_ENUM_DECL(virshEventAgentLifecycleReason);
|
|
VIR_ENUM_IMPL(virshEventAgentLifecycleReason,
|
|
VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_LAST,
|
|
N_("unknown"),
|
|
N_("domain started"),
|
|
N_("channel event"));
|
|
|
|
#define UNKNOWNSTR(str) (str ? str : N_("unsupported value"))
|
|
static void
|
|
virshEventAgentLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int state,
|
|
int reason,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'agent-lifecycle' for domain '%1$s': state: '%2$s' reason: '%3$s'\n"),
|
|
virDomainGetName(dom),
|
|
UNKNOWNSTR(virshEventAgentLifecycleStateTypeToString(state)),
|
|
UNKNOWNSTR(virshEventAgentLifecycleReasonTypeToString(reason)));
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshEventChannelLifecycleState);
|
|
VIR_ENUM_IMPL(virshEventChannelLifecycleState,
|
|
VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_LAST,
|
|
N_("unknown"),
|
|
N_("connected"),
|
|
N_("disconnected"));
|
|
|
|
VIR_ENUM_DECL(virshEventChannelLifecycleReason);
|
|
VIR_ENUM_IMPL(virshEventChannelLifecycleReason,
|
|
VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_LAST,
|
|
N_("unknown"),
|
|
N_("domain started"),
|
|
N_("channel event"));
|
|
|
|
static void
|
|
virshEventChannelLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *channelName,
|
|
int state,
|
|
int reason,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf,
|
|
_("event 'channel-lifecycle' for domain '%1$s': channel name: '%2$s' state: '%3$s' reason: '%4$s'\n"),
|
|
virDomainGetName(dom),
|
|
channelName,
|
|
UNKNOWNSTR(virshEventChannelLifecycleStateTypeToString(state)),
|
|
UNKNOWNSTR(virshEventChannelLifecycleReasonTypeToString(reason)));
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventMigrationIterationPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int iteration,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'migration-iteration' for domain '%1$s': iteration: '%2$d'\n"),
|
|
virDomainGetName(dom),
|
|
iteration);
|
|
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventJobCompletedPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
virTypedParameterPtr params,
|
|
int nparams,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
size_t i;
|
|
|
|
virBufferAsprintf(&buf, _("event 'job-completed' for domain '%1$s':\n"),
|
|
virDomainGetName(dom));
|
|
for (i = 0; i < nparams; i++) {
|
|
g_autofree char *value = virTypedParameterToString(¶ms[i]);
|
|
if (value)
|
|
virBufferAsprintf(&buf, "\t%s: %s\n", params[i].field, value);
|
|
}
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
|
|
static void
|
|
virshEventDeviceRemovalFailedPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *alias,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'device-removal-failed' for domain '%1$s': %2$s\n"),
|
|
virDomainGetName(dom),
|
|
alias);
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
static void
|
|
virshEventVcpuRemovedPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
unsigned int vcpuid,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf,
|
|
_("event 'vcpu-removed' for domain '%1$s': vcpu %2$u\n"),
|
|
virDomainGetName(dom), vcpuid);
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
VIR_ENUM_DECL(virshEventMetadataChangeType);
|
|
VIR_ENUM_IMPL(virshEventMetadataChangeType,
|
|
VIR_DOMAIN_METADATA_LAST,
|
|
N_("description"),
|
|
N_("title"),
|
|
N_("element"));
|
|
|
|
static void
|
|
virshEventMetadataChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int type,
|
|
const char *nsuri,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'metadata-change' for domain '%1$s': type %2$s, uri %3$s\n"),
|
|
virDomainGetName(dom),
|
|
UNKNOWNSTR(virshEventMetadataChangeTypeTypeToString(type)),
|
|
NULLSTR(nsuri));
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
|
|
static void
|
|
virshEventBlockThresholdPrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *dev,
|
|
const char *path,
|
|
unsigned long long threshold,
|
|
unsigned long long excess,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'block-threshold' for domain '%1$s': dev: %2$s(%3$s) %4$llu %5$llu\n"),
|
|
virDomainGetName(dom),
|
|
dev, NULLSTR(path), threshold, excess);
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
|
|
VIR_ENUM_DECL(virshEventMemoryFailureRecipientType);
|
|
VIR_ENUM_IMPL(virshEventMemoryFailureRecipientType,
|
|
VIR_DOMAIN_EVENT_MEMORY_FAILURE_RECIPIENT_LAST,
|
|
N_("hypervisor"),
|
|
N_("guest"));
|
|
|
|
VIR_ENUM_DECL(virshEventMemoryFailureActionType);
|
|
VIR_ENUM_IMPL(virshEventMemoryFailureActionType,
|
|
VIR_DOMAIN_EVENT_MEMORY_FAILURE_ACTION_LAST,
|
|
N_("ignore"),
|
|
N_("inject"),
|
|
N_("fatal"),
|
|
N_("reset"));
|
|
|
|
static void
|
|
virshEventMemoryFailurePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
int recipient,
|
|
int action,
|
|
unsigned int flags,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf, _("event 'memory-failure' for domain '%1$s':\nrecipient: %2$s\naction: %3$s\n"),
|
|
virDomainGetName(dom),
|
|
UNKNOWNSTR(virshEventMemoryFailureRecipientTypeTypeToString(recipient)),
|
|
UNKNOWNSTR(virshEventMemoryFailureActionTypeTypeToString(action)));
|
|
virBufferAsprintf(&buf, _("flags:\n\taction required: %1$d\n\trecursive: %2$d\n"),
|
|
!!(flags & VIR_DOMAIN_MEMORY_FAILURE_ACTION_REQUIRED),
|
|
!!(flags & VIR_DOMAIN_MEMORY_FAILURE_RECURSIVE));
|
|
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
|
|
static void
|
|
virshEventMemoryDeviceSizeChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *alias,
|
|
unsigned long long size,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf,
|
|
_("event 'memory-device-size-change' for domain '%1$s':\nalias: %2$s\nsize: %3$llu\n"),
|
|
virDomainGetName(dom), alias, size);
|
|
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
|
|
static void
|
|
virshEventNICMACChangePrint(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom,
|
|
const char *alias,
|
|
const char *oldMAC,
|
|
const char *newMAC,
|
|
void *opaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(&buf,
|
|
_("event 'nic-mac-change' for domain '%1$s':\nalias: %2$s\noldMAC: %3$s\nnewMAC: %4$s\n"),
|
|
virDomainGetName(dom), alias, oldMAC, newMAC);
|
|
|
|
virshEventPrint(opaque, &buf);
|
|
}
|
|
|
|
|
|
virshDomainEventCallback virshDomainEventCallbacks[] = {
|
|
{ "lifecycle",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventLifecyclePrint), },
|
|
{ "reboot", virshEventGenericPrint, },
|
|
{ "rtc-change",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventRTCChangePrint), },
|
|
{ "watchdog",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventWatchdogPrint), },
|
|
{ "io-error",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventIOErrorPrint), },
|
|
{ "graphics",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventGraphicsPrint), },
|
|
{ "io-error-reason",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventIOErrorReasonPrint), },
|
|
{ "control-error", virshEventGenericPrint, },
|
|
{ "block-job",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventBlockJobPrint), },
|
|
{ "disk-change",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventDiskChangePrint), },
|
|
{ "tray-change",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventTrayChangePrint), },
|
|
{ "pm-wakeup",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventPMChangePrint), },
|
|
{ "pm-suspend",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventPMChangePrint), },
|
|
{ "balloon-change",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventBalloonChangePrint), },
|
|
{ "pm-suspend-disk",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventPMChangePrint), },
|
|
{ "device-removed",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventDeviceRemovedPrint), },
|
|
{ "block-job-2",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventBlockJobPrint), },
|
|
{ "tunable",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventTunablePrint), },
|
|
{ "agent-lifecycle",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventAgentLifecyclePrint), },
|
|
{ "device-added",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventDeviceAddedPrint), },
|
|
{ "migration-iteration",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventMigrationIterationPrint), },
|
|
{ "job-completed",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventJobCompletedPrint), },
|
|
{ "device-removal-failed",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventDeviceRemovalFailedPrint), },
|
|
{ "metadata-change",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventMetadataChangePrint), },
|
|
{ "block-threshold",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventBlockThresholdPrint), },
|
|
{ "memory-failure",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventMemoryFailurePrint), },
|
|
{ "memory-device-size-change",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventMemoryDeviceSizeChangePrint), },
|
|
{ "nic-mac-change",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventNICMACChangePrint), },
|
|
{ "vcpu-removed",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventVcpuRemovedPrint), },
|
|
{ "channel-lifecycle",
|
|
VIR_DOMAIN_EVENT_CALLBACK(virshEventChannelLifecyclePrint), },
|
|
};
|
|
G_STATIC_ASSERT(VIR_DOMAIN_EVENT_ID_LAST == G_N_ELEMENTS(virshDomainEventCallbacks));
|
|
|
|
|
|
static char **
|
|
virshDomainEventNameCompleter(vshControl *ctl G_GNUC_UNUSED,
|
|
const vshCmd *cmd G_GNUC_UNUSED,
|
|
unsigned int flags)
|
|
{
|
|
size_t i = 0;
|
|
g_auto(GStrv) tmp = NULL;
|
|
|
|
virCheckFlags(0, NULL);
|
|
|
|
tmp = g_new0(char *, VIR_DOMAIN_EVENT_ID_LAST + 1);
|
|
|
|
for (i = 0; i < VIR_DOMAIN_EVENT_ID_LAST; i++)
|
|
tmp[i] = g_strdup(virshDomainEventCallbacks[i].name);
|
|
|
|
return g_steal_pointer(&tmp);
|
|
}
|
|
|
|
|
|
static const vshCmdInfo info_event = {
|
|
.help = N_("Domain Events"),
|
|
.desc = N_("List event types, or wait for domain events to occur"),
|
|
};
|
|
|
|
static const vshCmdOptDef opts_event[] = {
|
|
{.name = "domain",
|
|
.type = VSH_OT_STRING,
|
|
.unwanted_positional = true,
|
|
.help = N_("filter by domain name, id or uuid"),
|
|
.completer = virshDomainNameCompleter,
|
|
},
|
|
{.name = "event",
|
|
.type = VSH_OT_STRING,
|
|
.unwanted_positional = true,
|
|
.completer = virshDomainEventNameCompleter,
|
|
.help = N_("which event type to wait for")
|
|
},
|
|
{.name = "all",
|
|
.type = VSH_OT_BOOL,
|
|
.help = N_("wait for all events instead of just one type")
|
|
},
|
|
{.name = "loop",
|
|
.type = VSH_OT_BOOL,
|
|
.help = N_("loop until timeout or interrupt, rather than one-shot")
|
|
},
|
|
{.name = "timeout",
|
|
.type = VSH_OT_INT,
|
|
.unwanted_positional = true,
|
|
.help = N_("timeout seconds")
|
|
},
|
|
{.name = "list",
|
|
.type = VSH_OT_BOOL,
|
|
.help = N_("list valid event types")
|
|
},
|
|
{.name = "timestamp",
|
|
.type = VSH_OT_BOOL,
|
|
.help = N_("show timestamp for each printed event")
|
|
},
|
|
{.name = NULL}
|
|
};
|
|
|
|
static bool
|
|
cmdEvent(vshControl *ctl, const vshCmd *cmd)
|
|
{
|
|
g_autoptr(virshDomain) dom = NULL;
|
|
bool ret = false;
|
|
int timeout = 0;
|
|
g_autofree virshDomEventData *data = NULL;
|
|
size_t ndata = 0;
|
|
size_t i;
|
|
const char *eventName = NULL;
|
|
bool all = vshCommandOptBool(cmd, "all");
|
|
bool loop = vshCommandOptBool(cmd, "loop");
|
|
bool timestamp = vshCommandOptBool(cmd, "timestamp");
|
|
int count = 0;
|
|
virshControl *priv = ctl->privData;
|
|
g_auto(virMutex) m = VIR_MUTEX_INITIALIZER;
|
|
g_auto(virCond) c = VIR_COND_INITIALIZER;
|
|
|
|
VSH_EXCLUSIVE_OPTIONS("all", "event");
|
|
VSH_EXCLUSIVE_OPTIONS("list", "all");
|
|
VSH_EXCLUSIVE_OPTIONS("list", "event");
|
|
|
|
if (vshCommandOptBool(cmd, "list")) {
|
|
for (i = 0; i < G_N_ELEMENTS(virshDomainEventCallbacks); i++)
|
|
vshPrint(ctl, "%s\n", virshDomainEventCallbacks[i].name);
|
|
return true;
|
|
}
|
|
|
|
if (vshCommandOptString(ctl, cmd, "event", &eventName) < 0)
|
|
return false;
|
|
|
|
if (!eventName && !all) {
|
|
vshError(ctl, "%s",
|
|
_("one of --list, --all, or --event <type> is required"));
|
|
return false;
|
|
}
|
|
|
|
data = g_new0(virshDomEventData, G_N_ELEMENTS(virshDomainEventCallbacks));
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(virshDomainEventCallbacks); i++) {
|
|
if (eventName &&
|
|
STRNEQ(eventName, virshDomainEventCallbacks[i].name))
|
|
continue;
|
|
|
|
data[ndata].event = i;
|
|
data[ndata].ctl = ctl;
|
|
data[ndata].loop = loop;
|
|
data[ndata].count = &count;
|
|
data[ndata].timestamp = timestamp;
|
|
data[ndata].cb = &virshDomainEventCallbacks[i];
|
|
data[ndata].m = &m;
|
|
data[ndata].c = &c;
|
|
data[ndata].id = -1;
|
|
ndata++;
|
|
}
|
|
|
|
if (ndata == 0) {
|
|
vshError(ctl, _("unknown event type %1$s"), eventName);
|
|
return false;
|
|
}
|
|
|
|
if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0)
|
|
goto cleanup;
|
|
|
|
if (vshCommandOptBool(cmd, "domain")) {
|
|
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
|
|
goto cleanup;
|
|
}
|
|
|
|
if (vshEventStart(ctl, timeout) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < ndata; i++) {
|
|
if ((data[i].id = virConnectDomainEventRegisterAny(priv->conn, dom,
|
|
data[i].event,
|
|
data[i].cb->cb,
|
|
&data[i],
|
|
(virFreeCallback) virshDomEventDataUnregistered)) < 0) {
|
|
/* When registering for all events: if the first
|
|
* registration succeeds, silently ignore failures on all
|
|
* later registrations on the assumption that the server
|
|
* is older and didn't know quite as many events. */
|
|
if (i)
|
|
vshResetLibvirtError();
|
|
else
|
|
goto cleanup;
|
|
}
|
|
}
|
|
switch (vshEventWait(ctl)) {
|
|
case VSH_EVENT_INTERRUPT:
|
|
vshPrint(ctl, "%s", _("event loop interrupted\n"));
|
|
break;
|
|
case VSH_EVENT_TIMEOUT:
|
|
vshPrint(ctl, "%s", _("event loop timed out\n"));
|
|
break;
|
|
case VSH_EVENT_DONE:
|
|
break;
|
|
default:
|
|
goto cleanup;
|
|
}
|
|
vshPrint(ctl, _("events received: %1$d\n"), count);
|
|
if (count)
|
|
ret = true;
|
|
|
|
cleanup:
|
|
if (data) {
|
|
for (i = 0; i < ndata; i++) {
|
|
if (data[i].id >= 0 &&
|
|
virConnectDomainEventDeregisterAny(priv->conn, data[i].id) < 0)
|
|
ret = false;
|
|
}
|
|
|
|
virMutexLock(&m);
|
|
while (true) {
|
|
for (i = 0; i < ndata; i++) {
|
|
if (data[i].id >= 0)
|
|
break;
|
|
}
|
|
|
|
if (i == ndata ||
|
|
virCondWait(&c, &m) < 0)
|
|
break;
|
|
}
|
|
virMutexUnlock(&m);
|
|
}
|
|
vshEventCleanup(ctl);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* virsh event-await */
|
|
|
|
struct virshDomEventAwaitConditionData;
|
|
|
|
struct virshDomainEventAwaitCondition {
|
|
const char *name;
|
|
int event;
|
|
int (*handler)(struct virshDomEventAwaitConditionData *data);
|
|
};
|
|
|
|
struct virshDomEventAwaitConditionData {
|
|
vshControl *ctl;
|
|
virshDomain *dom;
|
|
const struct virshDomainEventAwaitCondition *cond;
|
|
|
|
virMutex *m; /* synchronization to ensure clean shutdown */
|
|
virCond *c;
|
|
bool done;
|
|
};
|
|
|
|
|
|
static void
|
|
virshDomEventAwaitConditionDataUnregistered(struct virshDomEventAwaitConditionData *data)
|
|
{
|
|
g_auto(virLockGuard) name = virLockGuardLock(data->m);
|
|
/* signal that the handler was unregistered */
|
|
data->done = true;
|
|
virCondSignal(data->c);
|
|
}
|
|
|
|
|
|
static void
|
|
virshDomainEventAwaitCallbackLifecycle(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom G_GNUC_UNUSED,
|
|
int event G_GNUC_UNUSED,
|
|
int detail G_GNUC_UNUSED,
|
|
void *opaque)
|
|
{
|
|
struct virshDomEventAwaitConditionData *data = opaque;
|
|
|
|
if (data->cond->handler(data) < 1)
|
|
vshEventDone(data->ctl);
|
|
}
|
|
|
|
|
|
static void
|
|
virshDomainEventAwaitAgentLifecycle(virConnectPtr conn G_GNUC_UNUSED,
|
|
virDomainPtr dom G_GNUC_UNUSED,
|
|
int state G_GNUC_UNUSED,
|
|
int reason G_GNUC_UNUSED,
|
|
void *opaque G_GNUC_UNUSED)
|
|
{
|
|
struct virshDomEventAwaitConditionData *data = opaque;
|
|
|
|
if (data->cond->handler(data) < 1)
|
|
vshEventDone(data->ctl);
|
|
}
|
|
|
|
|
|
struct virshDomainEventAwaitCallbackTuple {
|
|
int event;
|
|
virConnectDomainEventGenericCallback eventCB;
|
|
};
|
|
|
|
|
|
static const struct virshDomainEventAwaitCallbackTuple callbacks[] =
|
|
{
|
|
{ .event = VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
|
.eventCB = VIR_DOMAIN_EVENT_CALLBACK(virshDomainEventAwaitCallbackLifecycle),
|
|
},
|
|
{ .event = VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE,
|
|
.eventCB = VIR_DOMAIN_EVENT_CALLBACK(virshDomainEventAwaitAgentLifecycle),
|
|
},
|
|
};
|
|
|
|
|
|
static int
|
|
virshDomainEventAwaitConditionDomainInactive(struct virshDomEventAwaitConditionData *data)
|
|
{
|
|
int state = -1;
|
|
|
|
if (virDomainGetState(data->dom, &state, NULL, 0) < 0) {
|
|
vshError(data->ctl, "%s", _("failed to update domain state"));
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainState) state) {
|
|
case VIR_DOMAIN_SHUTOFF:
|
|
case VIR_DOMAIN_CRASHED:
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_NOSTATE:
|
|
case VIR_DOMAIN_RUNNING:
|
|
case VIR_DOMAIN_BLOCKED:
|
|
case VIR_DOMAIN_PAUSED:
|
|
case VIR_DOMAIN_SHUTDOWN:
|
|
case VIR_DOMAIN_PMSUSPENDED:
|
|
case VIR_DOMAIN_LAST:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
virshDomainEventAwaitConditionGuestAgentAvailable(struct virshDomEventAwaitConditionData *data)
|
|
{
|
|
g_autoptr(xmlDoc) xml = NULL;
|
|
g_autoptr(xmlXPathContext) ctxt = NULL;
|
|
g_autofree char *state = NULL;
|
|
|
|
if (virshDomainGetXMLFromDom(data->ctl, data->dom, 0, &xml, &ctxt) < 0)
|
|
return -1;
|
|
|
|
if ((state = virXPathString("string(//devices/channel/target[@name = 'org.qemu.guest_agent.0']/@state)",
|
|
ctxt))) {
|
|
if (STREQ(state, "connected"))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static const struct virshDomainEventAwaitCondition conditions[] = {
|
|
{ .name = "domain-inactive",
|
|
.event = VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
|
.handler = virshDomainEventAwaitConditionDomainInactive,
|
|
},
|
|
{ .name = "guest-agent-available",
|
|
.event = VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE,
|
|
.handler = virshDomainEventAwaitConditionGuestAgentAvailable,
|
|
},
|
|
};
|
|
|
|
|
|
static char **
|
|
virshDomainAwaitConditionNameCompleter(vshControl *ctl G_GNUC_UNUSED,
|
|
const vshCmd *cmd G_GNUC_UNUSED,
|
|
unsigned int flags)
|
|
{
|
|
size_t i = 0;
|
|
GStrv ret = NULL;
|
|
|
|
virCheckFlags(0, NULL);
|
|
|
|
ret = g_new0(char *, G_N_ELEMENTS(conditions) + 1);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(conditions); i++)
|
|
ret[i] = g_strdup(conditions[i].name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const vshCmdInfo info_await = {
|
|
.help = N_("await a domain event"),
|
|
.desc = N_("waits for a certain domain event to happen and then terminates"),
|
|
};
|
|
|
|
static const vshCmdOptDef opts_await[] = {
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(0),
|
|
{.name = "condition",
|
|
.type = VSH_OT_STRING,
|
|
.required = true,
|
|
.completer = virshDomainAwaitConditionNameCompleter,
|
|
.help = N_("which condition to wait until")
|
|
},
|
|
{.name = "timeout",
|
|
.type = VSH_OT_INT,
|
|
.help = N_("timeout seconds")
|
|
},
|
|
{.name = NULL}
|
|
};
|
|
|
|
|
|
static int
|
|
cmdAwait(vshControl *ctl,
|
|
const vshCmd *cmd)
|
|
{
|
|
g_autoptr(virshDomain) dom = NULL;
|
|
int timeout = 0;
|
|
size_t i;
|
|
const char *conditionName = NULL;
|
|
virshControl *priv = ctl->privData;
|
|
const struct virshDomainEventAwaitCallbackTuple *callback = NULL;
|
|
int evid = -1;
|
|
g_auto(virMutex) m = VIR_MUTEX_INITIALIZER;
|
|
g_auto(virCond) c = VIR_COND_INITIALIZER;
|
|
struct virshDomEventAwaitConditionData data = {
|
|
.ctl = ctl,
|
|
.m = &m,
|
|
.c = &c,
|
|
};
|
|
int ret = -1;
|
|
|
|
if (vshCommandOptString(ctl, cmd, "condition", &conditionName) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(conditions); i++) {
|
|
if (STREQ(conditionName, conditions[i].name)) {
|
|
data.cond = conditions + i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!data.cond) {
|
|
vshError(ctl, _("Unsupported await condition name '%1$s'"), conditionName);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(callbacks); i++) {
|
|
if (data.cond->event == callbacks[i].event) {
|
|
callback = callbacks + i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!callback) {
|
|
vshError(ctl, _("Missing callback definition for event type '%1$d'"), data.cond->event);
|
|
return -1;
|
|
}
|
|
|
|
if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0)
|
|
return -1;
|
|
|
|
if (!(data.dom = dom = virshCommandOptDomain(ctl, cmd, NULL)))
|
|
return -1;
|
|
|
|
if (vshEventStart(ctl, timeout) < 0)
|
|
goto cleanup;
|
|
|
|
if ((evid = virConnectDomainEventRegisterAny(priv->conn, dom,
|
|
callback->event,
|
|
callback->eventCB,
|
|
&data,
|
|
(virFreeCallback) virshDomEventAwaitConditionDataUnregistered)) < 0)
|
|
goto cleanup;
|
|
|
|
/* invoke the handler to ensure initial state update */
|
|
if ((ret = data.cond->handler(&data)) <= 0)
|
|
goto cleanup;
|
|
|
|
switch (vshEventWait(ctl)) {
|
|
case VSH_EVENT_INTERRUPT:
|
|
vshPrintExtra(ctl, "%s", _("event loop interrupted\n"));
|
|
ret = 2;
|
|
break;
|
|
|
|
case VSH_EVENT_TIMEOUT:
|
|
vshPrintExtra(ctl, "%s", _("event loop timed out\n"));
|
|
ret = 2;
|
|
break;
|
|
|
|
case VSH_EVENT_DONE:
|
|
ret = 0;
|
|
break;
|
|
|
|
default:
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
if (evid >= 0) {
|
|
virConnectDomainEventDeregisterAny(priv->conn, evid);
|
|
|
|
virMutexLock(&m);
|
|
while (!data.done) {
|
|
if (virCondWait(&c, &m) < 0)
|
|
break;
|
|
}
|
|
virMutexUnlock(&m);
|
|
}
|
|
vshEventCleanup(ctl);
|
|
|
|
return ret;
|
|
}
|
|
|
|
const vshCmdDef domEventCmds[] = {
|
|
{.name = "event",
|
|
.handler = cmdEvent,
|
|
.opts = opts_event,
|
|
.info = &info_event,
|
|
.flags = 0
|
|
},
|
|
{.name = "await",
|
|
.handler_rv = cmdAwait,
|
|
.opts = opts_await,
|
|
.info = &info_await,
|
|
.flags = 0
|
|
},
|
|
{.name = NULL}
|
|
};
|