]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
qemu: backup: Add support for VIR_DOMAIN_BACKUP_BEGIN_PRESERVE_SHUTDOWN_DOMAIN
authorPeter Krempa <pkrempa@redhat.com>
Thu, 13 Nov 2025 15:26:43 +0000 (16:26 +0100)
committerPeter Krempa <pkrempa@redhat.com>
Mon, 24 Nov 2025 15:40:24 +0000 (16:40 +0100)
Implement the support for VIR_DOMAIN_BACKUP_BEGIN_PRESERVE_SHUTDOWN_DOMAIN
which will keep the qemu process around while the backup is still
running.

The above is achieved by avoiding killing the qemu process in the
shutdown qemu monitor event handlers. Instead 'system_reset' QMP command
is issued and the domain object is transitioned into _PAUSED state in
sync with what qemu does.

Now once the backup job finishes (or is cancelled e.g. for pull mode
backups) the backup job termination code re-asseses if the qemu process
needs to be killed or the VM was re-started by un-pausing.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
src/qemu/qemu_backup.c
src/qemu/qemu_process.c

index 5eed35b471a2372a38f748699073c5d19ee10901..c3566bcd577580538a22ef7a5a842ecf3586f2b4 100644 (file)
@@ -27,6 +27,7 @@
 #include "qemu_checkpoint.h"
 #include "qemu_command.h"
 #include "qemu_security.h"
+#include "qemu_process.h"
 
 #include "storage_source.h"
 #include "storage_source_conf.h"
@@ -559,6 +560,8 @@ qemuBackupJobTerminate(virDomainObj *vm,
 {
     qemuDomainObjPrivate *priv = vm->privateData;
     g_autoptr(virQEMUDriverConfig) cfg = NULL;
+    /* some flags need to be probed after the private data is freed */
+    unsigned int apiFlags = priv->backup->apiFlags;
     size_t i;
 
     for (i = 0; i < priv->backup->ndisks; i++) {
@@ -623,6 +626,23 @@ qemuBackupJobTerminate(virDomainObj *vm,
 
     if (vm->job->asyncJob == VIR_ASYNC_JOB_BACKUP)
         virDomainObjEndAsyncJob(vm);
+
+    /* Users can request that the VM is preserved after a guest OS shutdown for
+     * the duration of the backup. This is the place where we need to check if
+     * this happened and optionally terminate the VM if the guest OS is still
+     * shut down */
+    if (apiFlags & VIR_DOMAIN_BACKUP_BEGIN_PRESERVE_SHUTDOWN_DOMAIN) {
+        int reason = -1;
+        virDomainState state = virDomainObjGetState(vm, &reason);
+
+        VIR_DEBUG("state: '%u', reason:'%d'", state, reason);
+
+        if (state == VIR_DOMAIN_SHUTDOWN ||
+            (state == VIR_DOMAIN_PAUSED && reason == VIR_DOMAIN_PAUSED_SHUTTING_DOWN)) {
+            VIR_DEBUG("backup job finished terminating the previously shutdown VM");
+            ignore_value(qemuProcessShutdownOrReboot(vm));
+        }
+    }
 }
 
 
@@ -766,7 +786,8 @@ qemuBackupBegin(virDomainObj *vm,
     int ret = -1;
     g_autoptr(qemuFDPassDirect) fdpass = NULL;
 
-    virCheckFlags(VIR_DOMAIN_BACKUP_BEGIN_REUSE_EXTERNAL, -1);
+    virCheckFlags(VIR_DOMAIN_BACKUP_BEGIN_REUSE_EXTERNAL |
+                  VIR_DOMAIN_BACKUP_BEGIN_PRESERVE_SHUTDOWN_DOMAIN, -1);
 
     if (!(def = virDomainBackupDefParseString(backupXML, priv->driver->xmlopt, 0)))
         return -1;
index e256e1933802b180d770d8f8fc8885af18a492ad..0e50cd1ccc0ecd2f57826070e1ef403092d9b109 100644 (file)
@@ -597,6 +597,51 @@ qemuProcessFakeReboot(void *opaque)
 }
 
 
+static void
+qemuProcessResetPreservedDomain(void *opaque)
+{
+    virDomainObj *vm = opaque;
+    qemuDomainObjPrivate *priv = vm->privateData;
+    virQEMUDriver *driver = priv->driver;
+    virObjectEvent *event = NULL;
+    int rc;
+
+    VIR_DEBUG("vm=%p", vm);
+
+    virObjectLock(vm);
+    if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    if (!virDomainObjIsActive(vm)) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("guest unexpectedly quit"));
+        goto endjob;
+    }
+
+    qemuDomainObjEnterMonitor(vm);
+    rc = qemuMonitorSystemReset(priv->mon);
+    qemuDomainObjExitMonitor(vm);
+
+    /* A guest-initiated OS shutdown completes qemu pauses the CPUs thus we need
+     * to also update the state */
+    virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_SHUTTING_DOWN);
+    event = virDomainEventLifecycleNewFromObj(vm,
+                                              VIR_DOMAIN_EVENT_SUSPENDED,
+                                              VIR_DOMAIN_EVENT_SUSPENDED_GUEST_SHUTDOWN);
+
+    if (rc < 0)
+        goto endjob;
+
+ endjob:
+    virDomainObjEndJob(vm);
+
+ cleanup:
+    qemuDomainSaveStatus(vm);
+    virDomainObjEndAPI(&vm);
+    virObjectEventStateQueue(driver->domainEventState, event);
+}
+
+
 /**
  * qemuProcessShutdownOrReboot:
  * @vm: domain object
@@ -630,6 +675,37 @@ qemuProcessShutdownOrReboot(virDomainObj *vm)
             virObjectUnref(vm);
         }
 
+        return false;
+    } else if (priv->backup && priv->backup->apiFlags & VIR_DOMAIN_BACKUP_BEGIN_PRESERVE_SHUTDOWN_DOMAIN) {
+        /* The users can request that while the 'backup' job is active (and
+         * possibly also other block jobs in the future) the qemu process will
+         * be kept around even when the guest OS shuts down, evem when the
+         * requested action is to terminate the VM.
+         *
+         * In such case we'll reset the VM and keep it paused with proper state
+         * so that users can re-start it if needed.
+         *
+         * Terminating of the qemu process once the backup job is
+         * completed/terminated (unless the guest was unpaused/restarted) is
+         * then done in qemuBackupJobTerminate by invoking this function once
+         * again.
+         */
+        g_autofree char *name = g_strdup_printf("reset-%s", vm->def->name);
+        virThread th;
+
+        VIR_DEBUG("preserving qemu process while backup job is running");
+
+        virObjectRef(vm);
+        if (virThreadCreateFull(&th,
+                                false,
+                                qemuProcessResetPreservedDomain,
+                                name,
+                                false,
+                                vm) < 0) {
+            VIR_WARN("Failed to create thread to reset shutdown VM");
+            virObjectUnref(vm);
+        }
+
         return false;
     } else {
         ignore_value(qemuProcessKill(vm, VIR_QEMU_PROCESS_KILL_NOWAIT));