Adds the ability to monitor the nbdkit process so that we can take action in case the child exits unexpectedly. When the nbdkit process exits, we pause the vm, restart nbdkit, and then resume the vm. This allows the vm to continue working in the event of a nbdkit failure. Eventually we may want to generalize this functionality since we may need something similar for e.g. qemu-storage-daemon, etc. The process is monitored with the pidfd_open() syscall if it exists (since linux 5.3). Otherwise it resorts to checking whether the process is alive once a second. The one-second time period was chosen somewhat arbitrarily. Signed-off-by: Jonathon Jongsma <jjongsma@xxxxxxxxxx> --- meson.build | 7 +++ src/qemu/qemu_nbdkit.c | 136 ++++++++++++++++++++++++++++++++++++++-- src/qemu/qemu_nbdkit.h | 4 +- src/qemu/qemu_process.c | 4 +- 4 files changed, 143 insertions(+), 8 deletions(-) diff --git a/meson.build b/meson.build index 5fce470c6b..c6708ee37c 100644 --- a/meson.build +++ b/meson.build @@ -682,6 +682,13 @@ symbols = [ [ 'sched.h', 'cpu_set_t' ], ] +if host_machine.system() == 'linux' + symbols += [ + # process management + [ 'sys/syscall.h', 'SYS_pidfd_open' ], + ] +endif + foreach symbol : symbols if cc.has_header_symbol(symbol[0], symbol[1], args: '-D_GNU_SOURCE', prefix: symbol.get(2, '')) conf.set('WITH_DECL_@0@'.format(symbol[1].to_upper()), 1) diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c index c3b43ff3c0..1199acd501 100644 --- a/src/qemu/qemu_nbdkit.c +++ b/src/qemu/qemu_nbdkit.c @@ -19,9 +19,11 @@ #include <config.h> #include <glib.h> +#include <sys/syscall.h> #include "vircommand.h" #include "virerror.h" +#include "virevent.h" #include "virlog.h" #include "virpidfile.h" #include "virsecureerase.h" @@ -35,6 +37,7 @@ #include "qemu_nbdkit.h" #define LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW #include "qemu_nbdkitpriv.h" +#include "qemu_process.h" #include "qemu_security.h" #include <fcntl.h> @@ -613,6 +616,104 @@ qemuNbdkitCapsCacheNew(const char *cachedir) } +static void +qemuNbdkitProcessRestart(qemuNbdkitProcess *proc, + virDomainObj *vm) +{ + qemuDomainObjPrivate *vmpriv = vm->privateData; + virQEMUDriver *driver = vmpriv->driver; + + /* clean up resources associated with process */ + qemuNbdkitProcessStop(proc); + + if (qemuNbdkitProcessStart(proc, vm, driver) < 0) + VIR_WARN("Unable to restart nbkdit process"); +} + + +typedef struct { + qemuNbdkitProcess *proc; + virDomainObj *vm; +} qemuNbdkitProcessEventData; + + +static qemuNbdkitProcessEventData* +qemuNbdkitProcessEventDataNew(qemuNbdkitProcess *proc, + virDomainObj *vm) +{ + qemuNbdkitProcessEventData *d = g_new(qemuNbdkitProcessEventData, 1); + d->proc = proc; + d->vm = virObjectRef(vm); + return d; +} + + +static void +qemuNbdkitProcessEventDataFree(qemuNbdkitProcessEventData *d) +{ + virObjectUnref(d->vm); + g_free(d); +} + + +#if WITH_DECL_SYS_PIDFD_OPEN +static void +qemuNbdkitProcessPidfdCb(int watch G_GNUC_UNUSED, + int fd, + int events G_GNUC_UNUSED, + void *opaque) +{ + qemuNbdkitProcessEventData *d = opaque; + + VIR_FORCE_CLOSE(fd); + VIR_DEBUG("nbdkit process %i died", d->proc->pid); + qemuNbdkitProcessRestart(d->proc, d->vm); +} +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ + + +static int +qemuNbdkitProcessStartMonitor(qemuNbdkitProcess *proc, + virDomainObj *vm) +{ +#if WITH_DECL_SYS_PIDFD_OPEN + int pidfd; + + pidfd = syscall(SYS_pidfd_open, proc->pid, 0); + if (pidfd < 0) { + virReportSystemError(errno, _("pidfd_open failed for %1$i"), proc->pid); + return -1; + } + + proc->eventwatch = virEventAddHandle(pidfd, + VIR_EVENT_HANDLE_READABLE, + qemuNbdkitProcessPidfdCb, + qemuNbdkitProcessEventDataNew(proc, vm), + (virFreeCallback)qemuNbdkitProcessEventDataFree); + + VIR_DEBUG("Monitoring nbdkit process %i for exit", proc->pid); + + return 0; +#else + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("pidfd_open system call required for nbdkit support")); + return -1; +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ +} + + +static void +qemuNbdkitProcessStopMonitor(qemuNbdkitProcess *proc) +{ +#if WITH_DECL_SYS_PIDFD_OPEN + if (proc->eventwatch > 0) { + virEventRemoveHandle(proc->eventwatch); + proc->eventwatch = 0; + } +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ +} + + static qemuNbdkitProcess * qemuNbdkitProcessNew(virStorageSource *source, const char *pidfile, @@ -660,9 +761,11 @@ qemuNbdkitReconnectStorageSource(virStorageSource *source, static void -qemuNbdkitStorageSourceManageProcessOne(virStorageSource *source) +qemuNbdkitStorageSourceManageProcessOne(virStorageSource *source, + virDomainObj *vm) { qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(source); + qemuDomainObjPrivate *vmpriv = vm->privateData; qemuNbdkitProcess *proc; if (!srcpriv) @@ -673,6 +776,9 @@ qemuNbdkitStorageSourceManageProcessOne(virStorageSource *source) if (!proc) return; + if (!proc->caps) + proc->caps = qemuGetNbdkitCaps(vmpriv->driver); + if (proc->pid <= 0) { if (virPidFileReadPath(proc->pidfile, &proc->pid) < 0) { VIR_WARN("Unable to read pidfile '%s'", proc->pidfile); @@ -680,8 +786,14 @@ qemuNbdkitStorageSourceManageProcessOne(virStorageSource *source) } } - if (virProcessKill(proc->pid, 0) < 0) + if (virProcessKill(proc->pid, 0) < 0) { VIR_WARN("nbdkit process %i is not alive", proc->pid); + qemuNbdkitProcessRestart(proc, vm); + return; + } + + if (qemuNbdkitProcessStartMonitor(proc, vm) < 0) + VIR_WARN("unable monitor nbdkit process"); } /** @@ -694,11 +806,12 @@ qemuNbdkitStorageSourceManageProcessOne(virStorageSource *source) * disk and is attempting to re-connect to active domains. */ void -qemuNbdkitStorageSourceManageProcess(virStorageSource *source) +qemuNbdkitStorageSourceManageProcess(virStorageSource *source, + virDomainObj *vm) { virStorageSource *backing; for (backing = source; backing != NULL; backing = backing->backingStore) - qemuNbdkitStorageSourceManageProcessOne(backing); + qemuNbdkitStorageSourceManageProcessOne(backing, vm); } @@ -710,6 +823,7 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, uid_t user, gid_t group) { +#if WITH_DECL_SYS_PIDFD_OPEN qemuDomainStorageSourcePrivate *srcPriv = qemuDomainStorageSourcePrivateFetch(source); g_autofree char *pidname = g_strdup_printf("nbdkit-%s.pid", alias); g_autofree char *socketname = g_strdup_printf("nbdkit-%s.socket", alias); @@ -753,6 +867,11 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps, srcPriv->nbdkitProcess = proc; return true; +#else + /* we need pidfd_open in order to monitor the process, so don't construct + * the object in this case so we'll fall back to qemu storage drivers */ + return false; +#endif /* WITH_DECL_SYS_PIDFD_OPEN */ } @@ -970,6 +1089,8 @@ qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc) void qemuNbdkitProcessFree(qemuNbdkitProcess *proc) { + qemuNbdkitProcessStopMonitor(proc); + g_clear_pointer(&proc->pidfile, g_free); g_clear_pointer(&proc->socketfile, g_free); g_clear_object(&proc->caps); @@ -1039,8 +1160,11 @@ qemuNbdkitProcessStart(qemuNbdkitProcess *proc, goto error; while (virTimeBackOffWait(&timebackoff)) { - if (virFileExists(proc->socketfile)) + if (virFileExists(proc->socketfile)) { + if (qemuNbdkitProcessStartMonitor(proc, vm) < 0) + goto error; return 0; + } if (virProcessKill(proc->pid, 0) == 0) continue; @@ -1071,6 +1195,8 @@ qemuNbdkitProcessStart(qemuNbdkitProcess *proc, int qemuNbdkitProcessStop(qemuNbdkitProcess *proc) { + qemuNbdkitProcessStopMonitor(proc); + if (proc->pid < 0) return 0; diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h index 36a2219d82..326f3d5920 100644 --- a/src/qemu/qemu_nbdkit.h +++ b/src/qemu/qemu_nbdkit.h @@ -69,7 +69,8 @@ void qemuNbdkitStopStorageSource(virStorageSource *src); void -qemuNbdkitStorageSourceManageProcess(virStorageSource *src); +qemuNbdkitStorageSourceManageProcess(virStorageSource *src, + virDomainObj *vm); bool qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, @@ -91,6 +92,7 @@ struct _qemuNbdkitProcess { uid_t user; gid_t group; pid_t pid; + int eventwatch; }; int diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 83bc8252fc..85e82d5ee9 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -8990,10 +8990,10 @@ qemuProcessReconnect(void *opaque) } for (i = 0; i < obj->def->ndisks; i++) - qemuNbdkitStorageSourceManageProcess(obj->def->disks[i]->src); + qemuNbdkitStorageSourceManageProcess(obj->def->disks[i]->src, obj); if (obj->def->os.loader && obj->def->os.loader->nvram) - qemuNbdkitStorageSourceManageProcess(obj->def->os.loader->nvram); + qemuNbdkitStorageSourceManageProcess(obj->def->os.loader->nvram, obj); /* update domain state XML with possibly updated state in virDomainObj */ if (virDomainObjSave(obj, driver->xmlopt, cfg->stateDir) < 0) -- 2.41.0