From: Peter Krempa <[email protected]> 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 <[email protected]> --- src/qemu/qemu_backup.c | 23 ++++++++++++- src/qemu/qemu_process.c | 76 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_backup.c b/src/qemu/qemu_backup.c index 5eed35b471..c3566bcd57 100644 --- a/src/qemu/qemu_backup.c +++ b/src/qemu/qemu_backup.c @@ -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; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index bbd9859ef4..4769d6694d 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -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)); -- 2.51.1
