Introduce a new QEMU hook operation "stop" that is called before a domain is terminated (via virsh shutdown or virsh destroy). This allows external scripts to perform cleanup or veto the stop process by returning a non-zero exit code. The hook is called as: /etc/libvirt/hooks/qemu <guest_name> stop begin - The full domain XML is provided on stdin. Example qemu hook that protects domains from being stopped (to be dropped in /etc/libfirt/hooks/qemu.d/10-vm-protection or similar): ```bash #!/bin/bash # Protection hook - vetos domain stops for domains listed in # /etc/libvirt/protected-domains set -euo pipefail readonly PROTECT_DIR="/etc/libvirt" readonly DOMAIN_NAME="${1:-}" readonly OPERATION="${2:-}" # Only act on stop operation if [[ "$OPERATION" != "stop" ]]; then exit 0 fi readonly PROTECT_LIST="${PROTECT_DIR}/protected-domains" if [[ -f "$PROTECT_LIST" ]]; then # Read file, strip comments and empty lines, check for exact domain match if grep -Fxq -- "$DOMAIN_NAME" <(sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' "$PROTECT_LIST"); then logger -t libvirt-hook-test "20-protection: BLOCKING stop for protected domain '$DOMAIN_NAME'" echo "$(date '+%Y-%m-%d %H:%M:%S'): BLOCKED stop for '$DOMAIN_NAME'" \ >> /tmp/libvirt-test-logs/protection.log printf 'vm-protection: stop blocked for %s\n' "$DOMAIN_NAME" >&2 exit 1 fi fi exit 0 ``` With a list of domains to protect in /etc/libvirt/protected-domains. With the above hook in place, attempting to destroy a domain listed in /etc/libvirt/protected-domains will fail: ```
virsh list --all Id Name State
2 protected-vm running - test-vm shut off
virsh destroy protected-vm error: Failed to destroy domain 'protected-vm' error: Hook script execution failed: internal error: Child process (LC_ALL=C LD_LIBRARY_PATH=/opt/libvirt-test/lib/x86_64-linux-gnu:/opt/libvirt-test/lib PATH=/usr/local/sbin:/usr/local/bin:/usr/bin HOME=/root USER=root LOGNAME=root /opt/libvirt-test/etc/libvirt/hooks/qemu.d/20-protection protected-vm stop begin -) unexpected exit status 1: vm-protection: stop blocked for protected-vm
virsh list --all Id Name State
2 protected-vm running - test-vm shut off ``` Signed-off-by: Mitchel Humpherys <mitch.special@gmail.com> --- docs/hooks.rst | 14 +++++++++++--- src/qemu/qemu_process.c | 17 +++++++++++++++++ src/util/virhook.c | 1 + src/util/virhook.h | 1 + 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/hooks.rst b/docs/hooks.rst index e1745b8cc7..5ae03a36cd 100644 --- a/docs/hooks.rst +++ b/docs/hooks.rst @@ -202,9 +202,17 @@ operation. There is no specific operation to indicate a "restart" is occurring. /etc/libvirt/hooks/qemu guest_name started begin - -- When a QEMU guest is stopped, the qemu hook script is called in two - locations, to match the startup. First, :since:`since 0.8.0`, the hook is - called before libvirt restores any labels: +- When a QEMU guest is stopped, the qemu hook script is called in three + locations, to match the startup. The first location, :since:`since 11.10.0`, + is called before the domain is stopped. This allows the hook to perform + cleanup tasks or veto the stop operation by returning a non-zero exit code: + + :: + + /etc/libvirt/hooks/qemu guest_name stop begin - + + The second location, :since:`since 0.8.0`, is called after the QEMU process + has terminated but before libvirt restores any labels: :: diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 0e50cd1ccc..fa2b691d99 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -9015,6 +9015,23 @@ qemuProcessBeginStopJob(virDomainObj *vm, qemuDomainObjPrivate *priv = vm->privateData; unsigned int killFlags = forceKill ? VIR_QEMU_PROCESS_KILL_FORCE : 0; + /* call stop hook if present */ + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + virQEMUDriver *driver = priv->driver; + g_autofree char *xml = qemuDomainDefFormatXML(driver, NULL, vm->def, 0); + int hookret; + + if (!xml) + return -1; + + hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, + VIR_HOOK_QEMU_OP_STOP, VIR_HOOK_SUBOP_BEGIN, + NULL, xml, NULL); + + if (hookret < 0) + return -1; + } + /* We need to prevent monitor EOF callback from doing our work (and * sending misleading events) while the vm is unlocked inside * BeginJob/ProcessKill API or any other code path before 'vm->def->id' is diff --git a/src/util/virhook.c b/src/util/virhook.c index d012bb1825..01ba17e406 100644 --- a/src/util/virhook.c +++ b/src/util/virhook.c @@ -76,6 +76,7 @@ VIR_ENUM_IMPL(virHookSubop, VIR_ENUM_IMPL(virHookQemuOp, VIR_HOOK_QEMU_OP_LAST, "start", + "stop", "stopped", "prepare", "release", diff --git a/src/util/virhook.h b/src/util/virhook.h index d8237c837e..ea8c540c3f 100644 --- a/src/util/virhook.h +++ b/src/util/virhook.h @@ -52,6 +52,7 @@ typedef enum { typedef enum { VIR_HOOK_QEMU_OP_START, /* domain is about to start */ + VIR_HOOK_QEMU_OP_STOP, /* domain is about to stop */ VIR_HOOK_QEMU_OP_STOPPED, /* domain has stopped */ VIR_HOOK_QEMU_OP_PREPARE, /* domain startup initiated */ VIR_HOOK_QEMU_OP_RELEASE, /* domain destruction is over */ -- 2.52.0