[PATCH 00/26] integrate auto-shutdown of VMs with daemons

This series starts the work needed to obsolete the libvirt-guests.sh script which has grown a surprisingly large amount of functionality. Currently the virt daemons will acquire inhibitors to delay OS shutdown when VMs are running. The libvirt-guests.service unit can be used to call libvirt-guests.sh to shutdown running VMs on system shutdown. This split is a bad architecture because libvirt-guests.service will only run once the system has decided to initiate the shutdown sequence. When the user requests as shutdown while inhibitors are present, logind will emit a "PrepareForShutdown" signal over dbus. Applications are supposed to respond to this by preserving state & releasing their inhibitors, which in turns allows shutdown to be initiated. The remote daemon already has support for listening for the "PrepareForShutdown" signal, but only does this for session instances, not system instances. This series essentially takes that logic and expands it to run in the system instances too, thus conceptually making libvirt-guests.service obsolete. It is slightly more complicated than that though for many reasons... Saving running VMs can take a very long time. The inhibitor delay can be as low as 5 seconds, and when killing a service, systemd may not wait very long for it to terminate. libvirt-guests.service deals with this by setting TimeoutStopSecs=0 to make systemd wait forever. This is undesirable to set in libvirtd.service though, as we would like systemd to kill the daemon aggressively if it hangs. The series thus uses the notification protocol to request systemd give it more time to shutdown, as long as we're in the phase of saving running VMs. A bug in this code will still result in systemd waiting forever, which is no different from libvirt-guests.service, but a bug in any other part of the libvirt daemon shutdown code will result in systemd killing us. The existing logic for saving VMs in the session daemons had many feature gaps compared to libvirt-guests.sh. Thus there is code to add support * Requesting graceful OS shutdown if managed save failed * Force poweroff of VMs if no other action worked * Optionally enabling/disabling use of managed save, graceful shutdown and force poweroff, which is more flexible than ON_SHUTDOWN=nnn, as we can try the whole sequence of options * Ability to bypass cache in managed save * Support for one-time autostart of VMs as an official API To aid in testing this logic, virt-admin gains a new command 'virt-admin daemon-shutdown --preserve' All this new functionality is wired up into the QEMU driver, and is made easily accessible to other hypervisor drivers, so would easily be extendable to Xen, CH, LXC drivers, but this is not done in this series. IOW, libvirt-guests.service is not yet fully obsolete. The new functionality is also not enabled by default for the system daemon, it requires explicit admin changes to /etc/libvirt/qemu.conf to enable it. This is because it would clash with execution of the libvirt-guests.service if both were enabled. It is highly desirable that we enable this by default though, so we need to figure out a upgrade story wrt libvirt-guests.service. The only libvirt-guests.sh features not implemented are: * PARALLEL_SHUTDOWN=nn. When doing a graceful shutdown we initiate it on every single VM at once, and then monitor progress of all of them in parallel. * SYNC_TIME=nn When make not attempt to sync guest time when restoring from managed save. This ought to be fixed Daniel P. Berrangé (26): util: add APIs for more systemd notifications remote: notify systemd when reloading config hypervisor: introduce helper for autostart src: convert drivers over to use new autostart helper hypervisor: add support for delay interval during autostart qemu: add 'auto_start_delay' configuration parameter hypervisor: move support for auto-shutdown out of QEMU driver remote: always invoke virStateStop for all daemons hypervisor: expand available shutdown actions hypervisor: custom shutdown actions for transient vs persistent VMs qemu: support automatic VM managed save in system daemon qemu: improve shutdown defaults for session daemon qemu: configurable delay for shutdown before poweroff hypervisor: support bypassing cache for managed save qemu: add config parameter to control auto-save bypass cache src: add new APIs for marking a domain to autostart once conf: implement support for autostart once feature hypervisor: wire up support for auto restore of running domains qemu: wire up support for once only autostart qemu: add config to control if auto-shutdown VMs are restored rpc: move state stop into virNetDaemon class rpc: don't unconditionally quit after preserving state rpc: fix shutdown sequence when preserving state admin: add 'daemon-shutdown' command rpc: don't let systemd shutdown daemon while saving VMs hypervisor: send systemd status messages while saving include/libvirt/libvirt-admin.h | 13 ++ include/libvirt/libvirt-domain.h | 4 + src/admin/admin_protocol.x | 11 +- src/admin/admin_server_dispatch.c | 13 ++ src/admin/libvirt-admin.c | 33 ++++ src/admin/libvirt_admin_public.syms | 5 + src/bhyve/bhyve_driver.c | 53 ++---- src/conf/domain_conf.c | 6 +- src/conf/domain_conf.h | 1 + src/conf/virdomainobjlist.c | 7 +- src/driver-hypervisor.h | 10 ++ src/hypervisor/domain_driver.c | 250 ++++++++++++++++++++++++++++ src/hypervisor/domain_driver.h | 42 +++++ src/libvirt-domain.c | 87 ++++++++++ src/libvirt_private.syms | 10 +- src/libvirt_public.syms | 6 + src/libvirt_remote.syms | 2 +- src/libxl/libxl_driver.c | 36 ++-- src/lxc/lxc_driver.c | 13 +- src/lxc/lxc_process.c | 18 +- src/lxc/lxc_process.h | 2 + src/qemu/libvirtd_qemu.aug | 7 + src/qemu/qemu.conf.in | 59 +++++++ src/qemu/qemu_conf.c | 63 +++++++ src/qemu/qemu_conf.h | 7 + src/qemu/qemu_driver.c | 203 +++++++++++++--------- src/qemu/test_libvirtd_qemu.aug.in | 7 + src/remote/libvirtd.service.in | 2 +- src/remote/remote_daemon.c | 78 +++------ src/remote/remote_driver.c | 2 + src/remote/remote_protocol.x | 30 +++- src/remote_protocol-structs | 12 ++ src/rpc/gendispatch.pl | 4 +- src/rpc/virnetdaemon.c | 212 +++++++++++++++++++---- src/rpc/virnetdaemon.h | 20 ++- src/util/virsystemd.c | 41 ++++- src/util/virsystemd.h | 6 +- src/virtd.service.in | 2 +- tools/virsh-domain-monitor.c | 5 + tools/virsh-domain.c | 39 ++++- tools/virt-admin.c | 41 +++++ 41 files changed, 1181 insertions(+), 281 deletions(-) -- 2.47.1

We have a way to notify systemd when we're done starting the daemon. Systemd supports many more notifications, however, and many of them are quite relevant to our needs: https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html This renames the existing notification API to better reflect its semantics, and adds new APIs for reporting * Initiation of config file reload * Initiation of daemon shutdown process * Adhoc progress status messages * Request to extend service shutdown timeout Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 6 +++++- src/rpc/virnetdaemon.c | 2 +- src/util/virsystemd.c | 41 +++++++++++++++++++++++++++++++++++++--- src/util/virsystemd.h | 6 +++++- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 33b93cbd3e..d0c116b78c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3524,7 +3524,11 @@ virSystemdHasResolved; virSystemdHasResolvedResetCachedValue; virSystemdMakeScopeName; virSystemdMakeSliceName; -virSystemdNotifyStartup; +virSystemdNotifyExtendTimeout; +virSystemdNotifyReady; +virSystemdNotifyReload; +virSystemdNotifyStatus; +virSystemdNotifyStopping; virSystemdResolvedRegisterNameServer; virSystemdTerminateMachine; diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index e4c6261536..bb7d2ff9a0 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -749,7 +749,7 @@ virNetDaemonRun(virNetDaemon *dmn) /* We are accepting connections now. Notify systemd * so it can start dependent services. */ - virSystemdNotifyStartup(); + virSystemdNotifyReady(); VIR_DEBUG("dmn=%p quit=%d", dmn, dmn->quit); while (!dmn->finished) { diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index 5b772e29dd..2c09b9ab2b 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -624,12 +624,11 @@ int virSystemdTerminateMachine(const char *name) return 0; } -void -virSystemdNotifyStartup(void) +static void +virSystemdNotify(const char *msg) { #ifndef WIN32 const char *path; - const char *msg = "READY=1"; int fd; struct sockaddr_un un = { .sun_family = AF_UNIX, @@ -644,6 +643,8 @@ virSystemdNotifyStartup(void) .msg_iovlen = 1, }; + VIR_DEBUG("Notify '%s'", msg); + if (!(path = getenv("NOTIFY_SOCKET"))) { VIR_DEBUG("Skipping systemd notify, not requested"); return; @@ -674,6 +675,40 @@ virSystemdNotifyStartup(void) #endif /* !WIN32 */ } +void virSystemdNotifyReady(void) +{ + virSystemdNotify("READY=1"); +} + +void virSystemdNotifyReload(void) +{ + virSystemdNotify("RELOAD=1"); +} + +void virSystemdNotifyStopping(void) +{ + virSystemdNotify("STOPPING=1"); +} + +void virSystemdNotifyExtendTimeout(int secs) +{ + g_autofree char *msg = g_strdup_printf("EXTEND_TIMEOUT_USEC=%llu", + secs * 1000ull * 1000ul); + virSystemdNotify(msg); +} + +void virSystemdNotifyStatus(const char *fmt, ...) +{ + g_autofree char *msg1 = NULL; + g_autofree char *msg2 = NULL; + va_list ap; + va_start(ap, fmt); + msg1 = g_strdup_vprintf(fmt, ap); + va_end(ap); + msg2 = g_strdup_printf("STATUS=%s", msg1); + virSystemdNotify(msg2); +} + static int virSystemdPMSupportTarget(const char *methodName, bool *result) { diff --git a/src/util/virsystemd.h b/src/util/virsystemd.h index b7fdaf67df..98460dbc3a 100644 --- a/src/util/virsystemd.h +++ b/src/util/virsystemd.h @@ -44,7 +44,11 @@ int virSystemdCreateMachine(const char *name, int virSystemdTerminateMachine(const char *name); -void virSystemdNotifyStartup(void); +void virSystemdNotifyReady(void); +void virSystemdNotifyReload(void); +void virSystemdNotifyStopping(void); +void virSystemdNotifyExtendTimeout(int secs); +void virSystemdNotifyStatus(const char *fmt, ...) G_GNUC_PRINTF(1, 2); int virSystemdHasMachined(void); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:34 +0000, Daniel P. Berrangé wrote:
We have a way to notify systemd when we're done starting the daemon.
Systemd supports many more notifications, however, and many of them are quite relevant to our needs:
https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html
This renames the existing notification API to better reflect its semantics, and adds new APIs for reporting
* Initiation of config file reload * Initiation of daemon shutdown process * Adhoc progress status messages * Request to extend service shutdown timeout
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 6 +++++- src/rpc/virnetdaemon.c | 2 +- src/util/virsystemd.c | 41 +++++++++++++++++++++++++++++++++++++--- src/util/virsystemd.h | 6 +++++- 4 files changed, 49 insertions(+), 6 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Wed, Jan 08, 2025 at 19:42:34 +0000, Daniel P. Berrangé wrote:
We have a way to notify systemd when we're done starting the daemon.
Systemd supports many more notifications, however, and many of them are quite relevant to our needs:
https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html
This renames the existing notification API to better reflect its semantics, and adds new APIs for reporting
* Initiation of config file reload * Initiation of daemon shutdown process * Adhoc progress status messages * Request to extend service shutdown timeout
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_private.syms | 6 +++++- src/rpc/virnetdaemon.c | 2 +- src/util/virsystemd.c | 41 +++++++++++++++++++++++++++++++++++++--- src/util/virsystemd.h | 6 +++++- 4 files changed, 49 insertions(+), 6 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 33b93cbd3e..d0c116b78c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3524,7 +3524,11 @@ virSystemdHasResolved; virSystemdHasResolvedResetCachedValue; virSystemdMakeScopeName; virSystemdMakeSliceName; -virSystemdNotifyStartup; +virSystemdNotifyExtendTimeout; +virSystemdNotifyReady; +virSystemdNotifyReload; +virSystemdNotifyStatus; +virSystemdNotifyStopping; virSystemdResolvedRegisterNameServer; virSystemdTerminateMachine;
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index e4c6261536..bb7d2ff9a0 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -749,7 +749,7 @@ virNetDaemonRun(virNetDaemon *dmn)
/* We are accepting connections now. Notify systemd * so it can start dependent services. */ - virSystemdNotifyStartup(); + virSystemdNotifyReady();
VIR_DEBUG("dmn=%p quit=%d", dmn, dmn->quit); while (!dmn->finished) { diff --git a/src/util/virsystemd.c b/src/util/virsystemd.c index 5b772e29dd..2c09b9ab2b 100644 --- a/src/util/virsystemd.c +++ b/src/util/virsystemd.c @@ -624,12 +624,11 @@ int virSystemdTerminateMachine(const char *name) return 0; }
-void -virSystemdNotifyStartup(void) +static void +virSystemdNotify(const char *msg) { #ifndef WIN32 const char *path; - const char *msg = "READY=1"; int fd; struct sockaddr_un un = { .sun_family = AF_UNIX, @@ -644,6 +643,8 @@ virSystemdNotifyStartup(void) .msg_iovlen = 1, };
+ VIR_DEBUG("Notify '%s'", msg); + if (!(path = getenv("NOTIFY_SOCKET"))) { VIR_DEBUG("Skipping systemd notify, not requested"); return; @@ -674,6 +675,40 @@ virSystemdNotifyStartup(void) #endif /* !WIN32 */ }
+void virSystemdNotifyReady(void) +{ + virSystemdNotify("READY=1"); +} + +void virSystemdNotifyReload(void) +{ + virSystemdNotify("RELOAD=1"); +}
Per the man page for 'sd_notify' this should be 'RELOADING=1';

Switch to the 'notify-reload' service type and send notifications to systemd when reloading configuration. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/libvirtd.service.in | 2 +- src/remote/remote_daemon.c | 2 ++ src/virtd.service.in | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/remote/libvirtd.service.in b/src/remote/libvirtd.service.in index 250b4a6fc3..b0a062e885 100644 --- a/src/remote/libvirtd.service.in +++ b/src/remote/libvirtd.service.in @@ -26,7 +26,7 @@ After=xencommons.service Conflicts=xendomains.service [Service] -Type=notify +Type=notify-reload Environment=LIBVIRTD_ARGS="--timeout 120" EnvironmentFile=-@initconfdir@/libvirtd ExecStart=@sbindir@/libvirtd $LIBVIRTD_ARGS diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index 1d079c7e4b..d44a365000 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -452,9 +452,11 @@ static void daemonReloadHandlerThread(void *opaque G_GNUC_UNUSED) virHookCall(VIR_HOOK_DRIVER_DAEMON, "-", VIR_HOOK_DAEMON_OP_RELOAD, SIGHUP, "SIGHUP", NULL, NULL); + virSystemdNotifyReload(); if (virStateReload() < 0) { VIR_WARN("Error while reloading drivers"); } + virSystemdNotifyReady(); /* Drivers are initialized again. */ g_atomic_int_set(&driversInitialized, 1); diff --git a/src/virtd.service.in b/src/virtd.service.in index 651a8d82d7..7ffb77e339 100644 --- a/src/virtd.service.in +++ b/src/virtd.service.in @@ -15,7 +15,7 @@ After=dbus.service After=apparmor.service [Service] -Type=notify +Type=notify-reload Environment=@SERVICE@_ARGS="--timeout 120" EnvironmentFile=-@initconfdir@/@service@ ExecStart=@sbindir@/@service@ $@SERVICE@_ARGS -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:35 +0000, Daniel P. Berrangé wrote:
Switch to the 'notify-reload' service type and send notifications to systemd when reloading configuration.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/libvirtd.service.in | 2 +- src/remote/remote_daemon.c | 2 ++ src/virtd.service.in | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/remote/libvirtd.service.in b/src/remote/libvirtd.service.in index 250b4a6fc3..b0a062e885 100644 --- a/src/remote/libvirtd.service.in +++ b/src/remote/libvirtd.service.in @@ -26,7 +26,7 @@ After=xencommons.service Conflicts=xendomains.service
[Service] -Type=notify +Type=notify-reload
^^^
Environment=LIBVIRTD_ARGS="--timeout 120" EnvironmentFile=-@initconfdir@/libvirtd ExecStart=@sbindir@/libvirtd $LIBVIRTD_ARGS diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index 1d079c7e4b..d44a365000 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -452,9 +452,11 @@ static void daemonReloadHandlerThread(void *opaque G_GNUC_UNUSED) virHookCall(VIR_HOOK_DRIVER_DAEMON, "-", VIR_HOOK_DAEMON_OP_RELOAD, SIGHUP, "SIGHUP", NULL, NULL);
+ virSystemdNotifyReload();
The man page says for 'notify-reload': • Behavior of notify-reload is similar to notify, with one difference: the SIGHUP UNIX process signal is sent to the service's main process when the service is asked to reload and the manager will wait for a notification about the reload being finished. When initiating the reload process the service is expected to reply with a notification message via sd_notify(3) that contains the "RELOADING=1" field in combination with "MONOTONIC_USEC=" set to the current monotonic time (i.e. CLOCK_MONOTONIC in clock_gettime(2)) in μs, formatted as decimal string. Once reloading is complete another notification message must be sent, containing "READY=1". Using this service type and implementing this reload protocol is an efficient alternative to providing an ExecReload= command for reloading of the service's configuration. So IIUC we need to be also sending MONOTONIC_USEC= in addition to RELOADING=1 in order to do the handshake for 'notify-reload' properly.
if (virStateReload() < 0) { VIR_WARN("Error while reloading drivers"); } + virSystemdNotifyReady();
/* Drivers are initialized again. */ g_atomic_int_set(&driversInitialized, 1); diff --git a/src/virtd.service.in b/src/virtd.service.in index 651a8d82d7..7ffb77e339 100644 --- a/src/virtd.service.in +++ b/src/virtd.service.in @@ -15,7 +15,7 @@ After=dbus.service After=apparmor.service
[Service] -Type=notify +Type=notify-reload Environment=@SERVICE@_ARGS="--timeout 120" EnvironmentFile=-@initconfdir@/@service@ ExecStart=@sbindir@/@service@ $@SERVICE@_ARGS -- 2.47.1

On Thu, Jan 30, 2025 at 12:22:52PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:35 +0000, Daniel P. Berrangé wrote:
Switch to the 'notify-reload' service type and send notifications to systemd when reloading configuration.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/libvirtd.service.in | 2 +- src/remote/remote_daemon.c | 2 ++ src/virtd.service.in | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/remote/libvirtd.service.in b/src/remote/libvirtd.service.in index 250b4a6fc3..b0a062e885 100644 --- a/src/remote/libvirtd.service.in +++ b/src/remote/libvirtd.service.in @@ -26,7 +26,7 @@ After=xencommons.service Conflicts=xendomains.service
[Service] -Type=notify +Type=notify-reload
^^^
Environment=LIBVIRTD_ARGS="--timeout 120" EnvironmentFile=-@initconfdir@/libvirtd ExecStart=@sbindir@/libvirtd $LIBVIRTD_ARGS diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index 1d079c7e4b..d44a365000 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -452,9 +452,11 @@ static void daemonReloadHandlerThread(void *opaque G_GNUC_UNUSED) virHookCall(VIR_HOOK_DRIVER_DAEMON, "-", VIR_HOOK_DAEMON_OP_RELOAD, SIGHUP, "SIGHUP", NULL, NULL);
+ virSystemdNotifyReload();
The man page says for 'notify-reload':
• Behavior of notify-reload is similar to notify, with one difference: the SIGHUP UNIX process signal is sent to the service's main process when the service is asked to reload and the manager will wait for a notification about the reload being finished.
When initiating the reload process the service is expected to reply with a notification message via sd_notify(3) that contains the "RELOADING=1" field in combination with "MONOTONIC_USEC=" set to the current monotonic time (i.e. CLOCK_MONOTONIC in clock_gettime(2)) in μs, formatted as decimal string. Once reloading is complete another notification message must be sent, containing "READY=1". Using this service type and implementing this reload protocol is an efficient alternative to providing an ExecReload= command for reloading of the service's configuration.
So IIUC we need to be also sending MONOTONIC_USEC= in addition to RELOADING=1 in order to do the handshake for 'notify-reload' properly.
Opps, yes, well spotted. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

There's a common pattern for autostart of iterating over VMs, acquiring a lock and ref count, then checking the autostart & is-active flags. Wrap this all up into a helper method. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 40 ++++++++++++++++++++++++++++++++++ src/hypervisor/domain_driver.h | 17 +++++++++++++++ src/libvirt_private.syms | 1 + 3 files changed, 58 insertions(+) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 85d68b056c..c5b082fd00 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -29,9 +29,12 @@ #include "viraccessapicheck.h" #include "datatypes.h" #include "driver.h" +#include "virlog.h" #define VIR_FROM_THIS VIR_FROM_DOMAIN +VIR_LOG_INIT("hypervisor.domain_driver"); + char * virDomainDriverGenerateRootHash(const char *drivername, const char *root) @@ -652,3 +655,40 @@ virDomainDriverGetIOThreadsConfig(virDomainDef *targetDef, return ret; } + +static int +virDomainDriverAutoStartOne(virDomainObj *vm, + void *opaque) +{ + virDomainDriverAutoStartConfig *cfg = opaque; + + virObjectLock(vm); + virObjectRef(vm); + + VIR_DEBUG("Autostart %s: autostart=%d", + vm->def->name, vm->autostart); + + if (vm->autostart && !virDomainObjIsActive(vm)) { + virResetLastError(); + cfg->callback(vm, cfg->opaque); + } + + virDomainObjEndAPI(&vm); + virResetLastError(); + + return 0; +} + +void virDomainDriverAutoStart(virDomainObjList *domains, + virDomainDriverAutoStartConfig *cfg) +{ + bool autostart; + VIR_DEBUG("Run autostart stateDir=%s", cfg->stateDir); + if (virDriverShouldAutostart(cfg->stateDir, &autostart) < 0 || + !autostart) { + VIR_DEBUG("Autostart already processed"); + return; + } + + virDomainObjListForEach(domains, false, virDomainDriverAutoStartOne, cfg); +} diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index 9942f58fda..c27ed9155e 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -23,6 +23,7 @@ #include "node_device_conf.h" #include "virhostdev.h" #include "virpci.h" +#include "virdomainobjlist.h" char * virDomainDriverGenerateRootHash(const char *drivername, @@ -71,3 +72,19 @@ int virDomainDriverDelIOThreadCheck(virDomainDef *def, int virDomainDriverGetIOThreadsConfig(virDomainDef *targetDef, virDomainIOThreadInfoPtr **info, unsigned int bitmap_size); + +/* + * Will be called with 'vm' locked and ref count held, + * which will be released when this returns. + */ +typedef void (*virDomainDriverAutoStartCallback)(virDomainObj *vm, + void *opaque); + +typedef struct _virDomainDriverAutoStartConfig { + char *stateDir; + virDomainDriverAutoStartCallback callback; + void *opaque; +} virDomainDriverAutoStartConfig; + +void virDomainDriverAutoStart(virDomainObjList *domains, + virDomainDriverAutoStartConfig *cfg); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d0c116b78c..d90b15e215 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1640,6 +1640,7 @@ virDomainCgroupSetupVcpuBW; # hypervisor/domain_driver.h virDomainDriverAddIOThreadCheck; +virDomainDriverAutoStart; virDomainDriverDelIOThreadCheck; virDomainDriverGenerateMachineName; virDomainDriverGenerateRootHash; -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:36 +0000, Daniel P. Berrangé wrote:
There's a common pattern for autostart of iterating over VMs, acquiring a lock and ref count, then checking the autostart & is-active flags. Wrap this all up into a helper method.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 40 ++++++++++++++++++++++++++++++++++ src/hypervisor/domain_driver.h | 17 +++++++++++++++ src/libvirt_private.syms | 1 + 3 files changed, 58 insertions(+)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 85d68b056c..c5b082fd00 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -29,9 +29,12 @@ #include "viraccessapicheck.h" #include "datatypes.h" #include "driver.h" +#include "virlog.h"
#define VIR_FROM_THIS VIR_FROM_DOMAIN
+VIR_LOG_INIT("hypervisor.domain_driver"); + char * virDomainDriverGenerateRootHash(const char *drivername, const char *root) @@ -652,3 +655,40 @@ virDomainDriverGetIOThreadsConfig(virDomainDef *targetDef,
return ret; } + +static int +virDomainDriverAutoStartOne(virDomainObj *vm,
^^^^
+ void *opaque) +{ + virDomainDriverAutoStartConfig *cfg = opaque; + + virObjectLock(vm); + virObjectRef(vm); + + VIR_DEBUG("Autostart %s: autostart=%d", + vm->def->name, vm->autostart); + + if (vm->autostart && !virDomainObjIsActive(vm)) { + virResetLastError(); + cfg->callback(vm, cfg->opaque); + } + + virDomainObjEndAPI(&vm); + virResetLastError(); + + return 0; +} + +void virDomainDriverAutoStart(virDomainObjList *domains, + virDomainDriverAutoStartConfig *cfg)
^^^ Return type is inconsistently formatted.
+{ + bool autostart; + VIR_DEBUG("Run autostart stateDir=%s", cfg->stateDir); + if (virDriverShouldAutostart(cfg->stateDir, &autostart) < 0 || + !autostart) { + VIR_DEBUG("Autostart already processed"); + return; + } + + virDomainObjListForEach(domains, false, virDomainDriverAutoStartOne, cfg); +}
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/bhyve/bhyve_driver.c | 53 ++++++++++++--------------------------- src/libxl/libxl_driver.c | 36 ++++++++------------------- src/lxc/lxc_driver.c | 13 +++++----- src/lxc/lxc_process.c | 18 ++------------ src/lxc/lxc_process.h | 2 ++ src/qemu/qemu_driver.c | 54 ++++++++++++---------------------------- 6 files changed, 53 insertions(+), 123 deletions(-) diff --git a/src/bhyve/bhyve_driver.c b/src/bhyve/bhyve_driver.c index 8f97ac032c..ec997a38f5 100644 --- a/src/bhyve/bhyve_driver.c +++ b/src/bhyve/bhyve_driver.c @@ -71,41 +71,19 @@ VIR_LOG_INIT("bhyve.bhyve_driver"); struct _bhyveConn *bhyve_driver = NULL; static int -bhyveAutostartDomain(virDomainObj *vm, void *opaque) +bhyveAutostartDomain(virDomainObj *vm, void *opaque G_GNUC_UNUSED) { - const struct bhyveAutostartData *data = opaque; int ret = 0; - VIR_LOCK_GUARD lock = virObjectLockGuard(vm); - - if (vm->autostart && !virDomainObjIsActive(vm)) { - virResetLastError(); - ret = virBhyveProcessStart(data->conn, vm, - VIR_DOMAIN_RUNNING_BOOTED, 0); - if (ret < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Failed to autostart VM '%1$s': %2$s"), - vm->def->name, virGetLastErrorMessage()); - } - } - return ret; -} - -static void -bhyveAutostartDomains(struct _bhyveConn *driver) -{ - /* XXX: Figure out a better way todo this. The domain - * startup code needs a connection handle in order - * to lookup the bridge associated with a virtual - * network - */ - virConnectPtr conn = virConnectOpen("bhyve:///system"); - /* Ignoring NULL conn which is mostly harmless here */ - struct bhyveAutostartData data = { driver, conn }; - - virDomainObjListForEach(driver->domains, false, bhyveAutostartDomain, &data); + ret = virBhyveProcessStart(NULL, vm, + VIR_DOMAIN_RUNNING_BOOTED, 0); + if (ret < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to autostart VM '%1$s': %2$s"), + vm->def->name, virGetLastErrorMessage()); + } - virObjectUnref(conn); + return ret; } /** @@ -1181,7 +1159,7 @@ bhyveStateInitialize(bool privileged, virStateInhibitCallback callback G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED) { - bool autostart = true; + virDomainDriverAutoStartConfig autostartCfg; if (root != NULL) { virReportError(VIR_ERR_INVALID_ARG, "%s", @@ -1266,11 +1244,12 @@ bhyveStateInitialize(bool privileged, virBhyveProcessReconnectAll(bhyve_driver); - if (virDriverShouldAutostart(BHYVE_STATE_DIR, &autostart) < 0) - goto cleanup; - - if (autostart) - bhyveAutostartDomains(bhyve_driver); + autostartCfg = (virDomainDriverAutoStartConfig) { + .stateDir = BHYVE_STATE_DIR, + .callback = bhyveAutostartDomain, + .opaque = bhyve_driver, + }; + virDomainDriverAutoStart(bhyve_driver->domains, &autostartCfg); return VIR_DRV_STATE_INIT_COMPLETE; diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index 494b1ad9bc..b6e17b47de 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -315,36 +315,22 @@ libxlDomObjFromDomain(virDomainPtr dom) return vm; } -static int +static void libxlAutostartDomain(virDomainObj *vm, void *opaque) { libxlDriverPrivate *driver = opaque; - int ret = -1; - - virObjectRef(vm); - virObjectLock(vm); - virResetLastError(); if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) - goto cleanup; + return; - if (vm->autostart && !virDomainObjIsActive(vm) && - libxlDomainStartNew(driver, vm, false) < 0) { + if (libxlDomainStartNew(driver, vm, false) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Failed to autostart VM '%1$s': %2$s"), vm->def->name, virGetLastErrorMessage()); - goto endjob; } - ret = 0; - - endjob: virDomainObjEndJob(vm); - cleanup: - virDomainObjEndAPI(&vm); - - return ret; } @@ -654,7 +640,7 @@ libxlStateInitialize(bool privileged, { libxlDriverConfig *cfg; g_autofree char *driverConf = NULL; - bool autostart = true; + virDomainDriverAutoStartConfig autostartCfg; if (root != NULL) { virReportError(VIR_ERR_INVALID_ARG, "%s", @@ -807,14 +793,12 @@ libxlStateInitialize(bool privileged, NULL, NULL) < 0) goto error; - if (virDriverShouldAutostart(cfg->stateDir, &autostart) < 0) - goto error; - - if (autostart) { - virDomainObjListForEach(libxl_driver->domains, false, - libxlAutostartDomain, - libxl_driver); - } + autostartCfg = (virDomainDriverAutoStartConfig) { + .stateDir = cfg->stateDir, + .callback = libxlAutostartDomain, + .opaque = libxl_driver, + }; + virDomainDriverAutoStart(libxl_driver->domains, &autostartCfg); virDomainObjListForEach(libxl_driver->domains, false, libxlDomainManagedSaveLoad, diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index 4740aeed52..e63732dbea 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -1402,8 +1402,8 @@ lxcStateInitialize(bool privileged, void *opaque) { virLXCDriverConfig *cfg = NULL; - bool autostart = true; const char *defsecmodel; + virDomainDriverAutoStartConfig autostartCfg; if (root != NULL) { virReportError(VIR_ERR_INVALID_ARG, "%s", @@ -1499,11 +1499,12 @@ lxcStateInitialize(bool privileged, NULL, NULL) < 0) goto cleanup; - if (virDriverShouldAutostart(cfg->stateDir, &autostart) < 0) - goto cleanup; - - if (autostart) - virLXCProcessAutostartAll(lxc_driver); + autostartCfg = (virDomainDriverAutoStartConfig) { + .stateDir = cfg->stateDir, + .callback = virLXCProcessAutostartDomain, + .opaque = NULL, + }; + virDomainDriverAutoStart(lxc_driver->domains, &autostartCfg); return VIR_DRV_STATE_INIT_COMPLETE; diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c index c2982244f0..4e152c193c 100644 --- a/src/lxc/lxc_process.c +++ b/src/lxc/lxc_process.c @@ -1562,19 +1562,14 @@ int virLXCProcessStart(virLXCDriver * driver, } -static int +void virLXCProcessAutostartDomain(virDomainObj *vm, void *opaque G_GNUC_UNUSED) { - VIR_LOCK_GUARD lock = virObjectLockGuard(vm); virLXCDomainObjPrivate *priv = vm->privateData; virObjectEvent *event; int rc = 0; - if (!vm->autostart || - virDomainObjIsActive(vm)) - return 0; - rc = virLXCProcessStart(priv->driver, vm, 0, NULL, NULL, VIR_DOMAIN_RUNNING_BOOTED); virDomainAuditStart(vm, "booted", rc >= 0); @@ -1582,22 +1577,13 @@ virLXCProcessAutostartDomain(virDomainObj *vm, VIR_ERROR(_("Failed to autostart VM '%1$s': %2$s"), vm->def->name, virGetLastErrorMessage()); - return -1; + return; } event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_BOOTED); virObjectEventStateQueue(priv->driver->domainEventState, event); - - return 0; -} - - -void -virLXCProcessAutostartAll(virLXCDriver *driver) -{ - virDomainObjListForEach(driver->domains, false, virLXCProcessAutostartDomain, NULL); } diff --git a/src/lxc/lxc_process.h b/src/lxc/lxc_process.h index 95eacdd1e5..fc464690f8 100644 --- a/src/lxc/lxc_process.h +++ b/src/lxc/lxc_process.h @@ -34,6 +34,8 @@ int virLXCProcessStop(virLXCDriver *driver, unsigned int cleanupFlags); void virLXCProcessAutostartAll(virLXCDriver *driver); +void virLXCProcessAutostartDomain(virDomainObj *vm, + void *opaque); int virLXCProcessReconnectAll(virLXCDriver *driver, virDomainObjList *doms); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d2eddbd9ae..45bfbd3727 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -167,52 +167,29 @@ qemuDomObjFromSnapshot(virDomainSnapshotPtr snapshot) -static int +static void qemuAutostartDomain(virDomainObj *vm, void *opaque) { virQEMUDriver *driver = opaque; int flags = 0; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); - int ret = -1; if (cfg->autoStartBypassCache) flags |= VIR_DOMAIN_START_BYPASS_CACHE; - virObjectLock(vm); - virObjectRef(vm); - virResetLastError(); - if (vm->autostart && - !virDomainObjIsActive(vm)) { - if (qemuProcessBeginJob(vm, VIR_DOMAIN_JOB_OPERATION_START, - flags) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Failed to start job on VM '%1$s': %2$s"), - vm->def->name, virGetLastErrorMessage()); - goto cleanup; - } + if (qemuProcessBeginJob(vm, VIR_DOMAIN_JOB_OPERATION_START, + flags) < 0) + return; - if (qemuDomainObjStart(NULL, driver, vm, flags, - VIR_ASYNC_JOB_START) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Failed to autostart VM '%1$s': %2$s"), + if (qemuDomainObjStart(NULL, driver, vm, flags, + VIR_ASYNC_JOB_START) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to autostart VM '%1$s': %2$s"), vm->def->name, virGetLastErrorMessage()); - } - - qemuProcessEndJob(vm); } - ret = 0; - cleanup: - virDomainObjEndAPI(&vm); - return ret; -} - - -static void -qemuAutostartDomains(virQEMUDriver *driver) -{ - virDomainObjListForEach(driver->domains, false, qemuAutostartDomain, driver); + qemuProcessEndJob(vm); } @@ -557,10 +534,10 @@ qemuStateInitialize(bool privileged, virQEMUDriverConfig *cfg; uid_t run_uid = -1; gid_t run_gid = -1; - bool autostart = true; size_t i; const char *defsecmodel = NULL; g_autoptr(virIdentity) identity = virIdentityGetCurrent(); + virDomainDriverAutoStartConfig autostartCfg; qemu_driver = g_new0(virQEMUDriver, 1); @@ -906,11 +883,12 @@ qemuStateInitialize(bool privileged, qemuProcessReconnectAll(qemu_driver); - if (virDriverShouldAutostart(cfg->stateDir, &autostart) < 0) - goto error; - - if (autostart) - qemuAutostartDomains(qemu_driver); + autostartCfg = (virDomainDriverAutoStartConfig) { + .stateDir = cfg->stateDir, + .callback = qemuAutostartDomain, + .opaque = qemu_driver, + }; + virDomainDriverAutoStart(qemu_driver->domains, &autostartCfg); return VIR_DRV_STATE_INIT_COMPLETE; -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:37 +0000, Daniel P. Berrangé wrote:
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/bhyve/bhyve_driver.c | 53 ++++++++++++--------------------------- src/libxl/libxl_driver.c | 36 ++++++++------------------- src/lxc/lxc_driver.c | 13 +++++----- src/lxc/lxc_process.c | 18 ++------------ src/lxc/lxc_process.h | 2 ++ src/qemu/qemu_driver.c | 54 ++++++++++++---------------------------- 6 files changed, 53 insertions(+), 123 deletions(-)
diff --git a/src/bhyve/bhyve_driver.c b/src/bhyve/bhyve_driver.c index 8f97ac032c..ec997a38f5 100644 --- a/src/bhyve/bhyve_driver.c +++ b/src/bhyve/bhyve_driver.c @@ -71,41 +71,19 @@ VIR_LOG_INIT("bhyve.bhyve_driver"); struct _bhyveConn *bhyve_driver = NULL;
static int -bhyveAutostartDomain(virDomainObj *vm, void *opaque) +bhyveAutostartDomain(virDomainObj *vm, void *opaque G_GNUC_UNUSED) { - const struct bhyveAutostartData *data = opaque; int ret = 0; - VIR_LOCK_GUARD lock = virObjectLockGuard(vm); - - if (vm->autostart && !virDomainObjIsActive(vm)) { - virResetLastError(); - ret = virBhyveProcessStart(data->conn, vm, - VIR_DOMAIN_RUNNING_BOOTED, 0); - if (ret < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Failed to autostart VM '%1$s': %2$s"), - vm->def->name, virGetLastErrorMessage()); - } - } - return ret; -} - -static void -bhyveAutostartDomains(struct _bhyveConn *driver) -{ - /* XXX: Figure out a better way todo this. The domain - * startup code needs a connection handle in order - * to lookup the bridge associated with a virtual - * network - */ - virConnectPtr conn = virConnectOpen("bhyve:///system"); - /* Ignoring NULL conn which is mostly harmless here */
- struct bhyveAutostartData data = { driver, conn }; - - virDomainObjListForEach(driver->domains, false, bhyveAutostartDomain, &data); + ret = virBhyveProcessStart(NULL, vm, + VIR_DOMAIN_RUNNING_BOOTED, 0);
int virBhyveProcessStart(virConnectPtr conn, virDomainObj *vm, virDomainRunningReason reason, unsigned int flags) { struct _bhyveConn *driver = conn->privateData; The first thing that virBhyveProcessStart() does is to dereference 'conn'. So firstly the note about NULL con being "mostly harmless" is not true and we also can't do this here in the current state. [...] The rest looks good. If you plan to fix the bhyve stuff separately (e.g. drop 'conn') and what remains in this patch will be a trivial change, you can add: Reviewed-by: Peter Krempa <pkrempa@redhat.com> Here.

On Thu, Jan 30, 2025 at 01:08:00PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:37 +0000, Daniel P. Berrangé wrote:
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/bhyve/bhyve_driver.c | 53 ++++++++++++--------------------------- src/libxl/libxl_driver.c | 36 ++++++++------------------- src/lxc/lxc_driver.c | 13 +++++----- src/lxc/lxc_process.c | 18 ++------------ src/lxc/lxc_process.h | 2 ++ src/qemu/qemu_driver.c | 54 ++++++++++++---------------------------- 6 files changed, 53 insertions(+), 123 deletions(-)
diff --git a/src/bhyve/bhyve_driver.c b/src/bhyve/bhyve_driver.c index 8f97ac032c..ec997a38f5 100644 --- a/src/bhyve/bhyve_driver.c +++ b/src/bhyve/bhyve_driver.c @@ -71,41 +71,19 @@ VIR_LOG_INIT("bhyve.bhyve_driver"); struct _bhyveConn *bhyve_driver = NULL;
static int -bhyveAutostartDomain(virDomainObj *vm, void *opaque) +bhyveAutostartDomain(virDomainObj *vm, void *opaque G_GNUC_UNUSED) { - const struct bhyveAutostartData *data = opaque; int ret = 0; - VIR_LOCK_GUARD lock = virObjectLockGuard(vm); - - if (vm->autostart && !virDomainObjIsActive(vm)) { - virResetLastError(); - ret = virBhyveProcessStart(data->conn, vm, - VIR_DOMAIN_RUNNING_BOOTED, 0); - if (ret < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Failed to autostart VM '%1$s': %2$s"), - vm->def->name, virGetLastErrorMessage()); - } - } - return ret; -} - -static void -bhyveAutostartDomains(struct _bhyveConn *driver) -{ - /* XXX: Figure out a better way todo this. The domain - * startup code needs a connection handle in order - * to lookup the bridge associated with a virtual - * network - */ - virConnectPtr conn = virConnectOpen("bhyve:///system"); - /* Ignoring NULL conn which is mostly harmless here */
- struct bhyveAutostartData data = { driver, conn }; - - virDomainObjListForEach(driver->domains, false, bhyveAutostartDomain, &data); + ret = virBhyveProcessStart(NULL, vm, + VIR_DOMAIN_RUNNING_BOOTED, 0);
int virBhyveProcessStart(virConnectPtr conn, virDomainObj *vm, virDomainRunningReason reason, unsigned int flags) { struct _bhyveConn *driver = conn->privateData;
The first thing that virBhyveProcessStart() does is to dereference 'conn'. So firstly the note about NULL con being "mostly harmless" is not true and we also can't do this here in the current state.
[...]
The rest looks good. If you plan to fix the bhyve stuff separately (e.g. drop 'conn') and what remains in this patch will be a trivial change, you can add:
Urgh, don't know why I didn't check that more closely. We should just pass 'driver' into virBhyveProcessStart directly, since all the callers have access to it already.
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
Here.
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

This delay can reduce the CPU/IO load storm when autostarting many guests. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 20 +++++++++++++++++--- src/hypervisor/domain_driver.h | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index c5b082fd00..44c8fad4d5 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -656,11 +656,16 @@ virDomainDriverGetIOThreadsConfig(virDomainDef *targetDef, return ret; } +typedef struct _virDomainDriverAutoStartState { + virDomainDriverAutoStartConfig *cfg; + bool first; +} virDomainDriverAutoStartState; + static int virDomainDriverAutoStartOne(virDomainObj *vm, void *opaque) { - virDomainDriverAutoStartConfig *cfg = opaque; + virDomainDriverAutoStartState *state = opaque; virObjectLock(vm); virObjectRef(vm); @@ -670,7 +675,15 @@ virDomainDriverAutoStartOne(virDomainObj *vm, if (vm->autostart && !virDomainObjIsActive(vm)) { virResetLastError(); - cfg->callback(vm, cfg->opaque); + if (state->cfg->delayMS) { + if (!state->first) { + g_usleep(state->cfg->delayMS * 1000ull); + } else { + state->first = false; + } + } + + state->cfg->callback(vm, state->cfg->opaque); } virDomainObjEndAPI(&vm); @@ -682,6 +695,7 @@ virDomainDriverAutoStartOne(virDomainObj *vm, void virDomainDriverAutoStart(virDomainObjList *domains, virDomainDriverAutoStartConfig *cfg) { + virDomainDriverAutoStartState state = { .cfg = cfg, .first = true }; bool autostart; VIR_DEBUG("Run autostart stateDir=%s", cfg->stateDir); if (virDriverShouldAutostart(cfg->stateDir, &autostart) < 0 || @@ -690,5 +704,5 @@ void virDomainDriverAutoStart(virDomainObjList *domains, return; } - virDomainObjListForEach(domains, false, virDomainDriverAutoStartOne, cfg); + virDomainObjListForEach(domains, false, virDomainDriverAutoStartOne, &state); } diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index c27ed9155e..d695e26f90 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -84,6 +84,7 @@ typedef struct _virDomainDriverAutoStartConfig { char *stateDir; virDomainDriverAutoStartCallback callback; void *opaque; + int delayMS; /* milliseconds between starting each guest */ } virDomainDriverAutoStartConfig; void virDomainDriverAutoStart(virDomainObjList *domains, -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:38 +0000, Daniel P. Berrangé wrote:
This delay can reduce the CPU/IO load storm when autostarting many guests.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 20 +++++++++++++++++--- src/hypervisor/domain_driver.h | 1 + 2 files changed, 18 insertions(+), 3 deletions(-)
[...]
diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index c27ed9155e..d695e26f90 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -84,6 +84,7 @@ typedef struct _virDomainDriverAutoStartConfig { char *stateDir; virDomainDriverAutoStartCallback callback; void *opaque; + int delayMS; /* milliseconds between starting each guest */
As negative delay doesn't make sense consider declaring this as unsigned. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Thu, Jan 30, 2025 at 01:12:15PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:38 +0000, Daniel P. Berrangé wrote:
This delay can reduce the CPU/IO load storm when autostarting many guests.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 20 +++++++++++++++++--- src/hypervisor/domain_driver.h | 1 + 2 files changed, 18 insertions(+), 3 deletions(-)
[...]
diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index c27ed9155e..d695e26f90 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -84,6 +84,7 @@ typedef struct _virDomainDriverAutoStartConfig { char *stateDir; virDomainDriverAutoStartCallback callback; void *opaque; + int delayMS; /* milliseconds between starting each guest */
As negative delay doesn't make sense consider declaring this as unsigned.
Yep, makes sense. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

This allows a user specified delay between autostart of each VM, giving parity with the equivalent feature of libvirt-guests. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 4 ++++ src/qemu/qemu_conf.c | 2 ++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 10 insertions(+) diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 1377fd89cc..642093c40b 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -97,6 +97,7 @@ module Libvirtd_qemu = | str_entry "auto_dump_path" | bool_entry "auto_dump_bypass_cache" | bool_entry "auto_start_bypass_cache" + | int_entry "auto_start_delay" let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d853136f10..a3e9bbfcf3 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -634,6 +634,10 @@ # #auto_start_bypass_cache = 0 +# Delay in milliseconds between starting each VM, during autostart +# +#auto_start_delay = 0 + # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 8b9fe4e381..0b6b923bcb 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -638,6 +638,8 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, return -1; if (virConfGetValueBool(conf, "auto_start_bypass_cache", &cfg->autoStartBypassCache) < 0) return -1; + if (virConfGetValueInt(conf, "auto_start_delay", &cfg->autoStartDelayMS) < 0) + return -1; return 0; } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 42cdb6f883..61a2bdce51 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -200,6 +200,7 @@ struct _virQEMUDriverConfig { char *autoDumpPath; bool autoDumpBypassCache; bool autoStartBypassCache; + int autoStartDelayMS; char *lockManagerName; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 45bfbd3727..f689dadc0a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -887,6 +887,7 @@ qemuStateInitialize(bool privileged, .stateDir = cfg->stateDir, .callback = qemuAutostartDomain, .opaque = qemu_driver, + .delayMS = cfg->autoStartDelayMS, }; virDomainDriverAutoStart(qemu_driver->domains, &autostartCfg); diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index 69fdae215a..c2a1d7d829 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -75,6 +75,7 @@ module Test_libvirtd_qemu = { "auto_dump_path" = "/var/lib/libvirt/qemu/dump" } { "auto_dump_bypass_cache" = "0" } { "auto_start_bypass_cache" = "0" } +{ "auto_start_delay" = "0" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } { "set_process_name" = "1" } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:39 +0000, Daniel P. Berrangé wrote:
This allows a user specified delay between autostart of each VM, giving parity with the equivalent feature of libvirt-guests.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 4 ++++ src/qemu/qemu_conf.c | 2 ++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 10 insertions(+)
[...]
diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d853136f10..a3e9bbfcf3 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -634,6 +634,10 @@ # #auto_start_bypass_cache = 0
+# Delay in milliseconds between starting each VM, during autostart
I'd suggest you mention that the delay is between kicking off the startup of the autostarted VMs, so that it's obvious that we don't/can't see when the VM actually booted (whatever the definition of 'booted' would be for users).
+# +#auto_start_delay = 0 + # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 8b9fe4e381..0b6b923bcb 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -638,6 +638,8 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, return -1; if (virConfGetValueBool(conf, "auto_start_bypass_cache", &cfg->autoStartBypassCache) < 0) return -1; + if (virConfGetValueInt(conf, "auto_start_delay", &cfg->autoStartDelayMS) < 0) + return -1;
return 0; } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 42cdb6f883..61a2bdce51 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -200,6 +200,7 @@ struct _virQEMUDriverConfig { char *autoDumpPath; bool autoDumpBypassCache; bool autoStartBypassCache; + int autoStartDelayMS;
Once again, preferrably declare this as unsigned. Especially prevent users passing negative values. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Thu, Jan 30, 2025 at 01:16:42PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:39 +0000, Daniel P. Berrangé wrote:
This allows a user specified delay between autostart of each VM, giving parity with the equivalent feature of libvirt-guests.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 4 ++++ src/qemu/qemu_conf.c | 2 ++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 10 insertions(+)
[...]
diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d853136f10..a3e9bbfcf3 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -634,6 +634,10 @@ # #auto_start_bypass_cache = 0
+# Delay in milliseconds between starting each VM, during autostart
I'd suggest you mention that the delay is between kicking off the startup of the autostarted VMs, so that it's obvious that we don't/can't see when the VM actually booted (whatever the definition of 'booted' would be for users).
Yeah, good idea. Side note, for modern Linux guests users can now seen when a VM has fully booted if they add a virtio-vsock device, as systemd is able to notify the host over that. Not something we can/should rely on here though.
+# +#auto_start_delay = 0 + # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 8b9fe4e381..0b6b923bcb 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -638,6 +638,8 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, return -1; if (virConfGetValueBool(conf, "auto_start_bypass_cache", &cfg->autoStartBypassCache) < 0) return -1; + if (virConfGetValueInt(conf, "auto_start_delay", &cfg->autoStartDelayMS) < 0) + return -1;
return 0; } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 42cdb6f883..61a2bdce51 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -200,6 +200,7 @@ struct _virQEMUDriverConfig { char *autoDumpPath; bool autoDumpBypassCache; bool autoStartBypassCache; + int autoStartDelayMS;
Once again, preferrably declare this as unsigned. Especially prevent users passing negative values.
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

This is a move of the code that currently exists in the QEMU driver, into the common layer that can be used by multiple drivers. The code currently supports performing managed save of all running guests, ignoring any failures. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 48 ++++++++++++++++++++++++++++++++++ src/hypervisor/domain_driver.h | 8 +++++- src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 47 ++++----------------------------- 4 files changed, 61 insertions(+), 43 deletions(-) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 44c8fad4d5..949e3d01f1 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -706,3 +706,51 @@ void virDomainDriverAutoStart(virDomainObjList *domains, virDomainObjListForEach(domains, false, virDomainDriverAutoStartOne, &state); } + + +void +virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) +{ + g_autoptr(virConnect) conn = NULL; + int numDomains = 0; + size_t i; + int state; + virDomainPtr *domains = NULL; + g_autofree unsigned int *flags = NULL; + + if (!(conn = virConnectOpen(cfg->uri))) + goto cleanup; + + if ((numDomains = virConnectListAllDomains(conn, + &domains, + VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) + goto cleanup; + + flags = g_new0(unsigned int, numDomains); + + /* First we pause all VMs to make them stop dirtying + pages, etc. We remember if any VMs were paused so + we can restore that on resume. */ + for (i = 0; i < numDomains; i++) { + flags[i] = VIR_DOMAIN_SAVE_RUNNING; + if (virDomainGetState(domains[i], &state, NULL, 0) == 0) { + if (state == VIR_DOMAIN_PAUSED) + flags[i] = VIR_DOMAIN_SAVE_PAUSED; + } + virDomainSuspend(domains[i]); + } + + /* Then we save the VMs to disk */ + for (i = 0; i < numDomains; i++) + if (virDomainManagedSave(domains[i], flags[i]) < 0) + VIR_WARN("Unable to perform managed save of '%s': %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + + cleanup: + if (domains) { + for (i = 0; i < numDomains; i++) + virObjectUnref(domains[i]); + VIR_FREE(domains); + } +} diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index d695e26f90..d1588ea712 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -81,7 +81,7 @@ typedef void (*virDomainDriverAutoStartCallback)(virDomainObj *vm, void *opaque); typedef struct _virDomainDriverAutoStartConfig { - char *stateDir; + const char *stateDir; virDomainDriverAutoStartCallback callback; void *opaque; int delayMS; /* milliseconds between starting each guest */ @@ -89,3 +89,9 @@ typedef struct _virDomainDriverAutoStartConfig { void virDomainDriverAutoStart(virDomainObjList *domains, virDomainDriverAutoStartConfig *cfg); + +typedef struct _virDomainDriverAutoShutdownConfig { + const char *uri; +} virDomainDriverAutoShutdownConfig; + +void virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d90b15e215..781bb566d2 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1640,6 +1640,7 @@ virDomainCgroupSetupVcpuBW; # hypervisor/domain_driver.h virDomainDriverAddIOThreadCheck; +virDomainDriverAutoShutdown; virDomainDriverAutoStart; virDomainDriverDelIOThreadCheck; virDomainDriverGenerateMachineName; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f689dadc0a..8c16566ce8 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -944,51 +944,14 @@ qemuStateReload(void) static int qemuStateStop(void) { - int ret = -1; - g_autoptr(virConnect) conn = NULL; - int numDomains = 0; - size_t i; - int state; - virDomainPtr *domains = NULL; - g_autofree unsigned int *flags = NULL; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(qemu_driver); + virDomainDriverAutoShutdownConfig ascfg = { + .uri = cfg->uri, + }; - if (!(conn = virConnectOpen(cfg->uri))) - goto cleanup; - - if ((numDomains = virConnectListAllDomains(conn, - &domains, - VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) - goto cleanup; - - flags = g_new0(unsigned int, numDomains); - - /* First we pause all VMs to make them stop dirtying - pages, etc. We remember if any VMs were paused so - we can restore that on resume. */ - for (i = 0; i < numDomains; i++) { - flags[i] = VIR_DOMAIN_SAVE_RUNNING; - if (virDomainGetState(domains[i], &state, NULL, 0) == 0) { - if (state == VIR_DOMAIN_PAUSED) - flags[i] = VIR_DOMAIN_SAVE_PAUSED; - } - virDomainSuspend(domains[i]); - } - - ret = 0; - /* Then we save the VMs to disk */ - for (i = 0; i < numDomains; i++) - if (virDomainManagedSave(domains[i], flags[i]) < 0) - ret = -1; - - cleanup: - if (domains) { - for (i = 0; i < numDomains; i++) - virObjectUnref(domains[i]); - VIR_FREE(domains); - } + virDomainDriverAutoShutdown(&ascfg); - return ret; + return 0; } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:40 +0000, Daniel P. Berrangé wrote:
This is a move of the code that currently exists in the QEMU driver, into the common layer that can be used by multiple drivers.
The code currently supports performing managed save of all running guests, ignoring any failures.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 48 ++++++++++++++++++++++++++++++++++ src/hypervisor/domain_driver.h | 8 +++++- src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 47 ++++----------------------------- 4 files changed, 61 insertions(+), 43 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Currently the virStateStop method is only wired up to run save for the unprivileged daemons, so there is no functional change. IOW, session exit, or host OS shutdown will trigger VM managed saved for QEMU session daemon, but not the system daemon. This changes the daemon code to always run virStateStop for all daemons. Instead the QEMU driver is responsible for skipping its own logic when running privileged...for now. This means that virStateStop will now be triggered by logind's PrepareForShutdown signal. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/qemu_driver.c | 3 ++- src/remote/remote_daemon.c | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8c16566ce8..103369ac93 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -949,7 +949,8 @@ qemuStateStop(void) .uri = cfg->uri, }; - virDomainDriverAutoShutdown(&ascfg); + if (!qemu_driver->privileged) + virDomainDriverAutoShutdown(&ascfg); return 0; } diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index d44a365000..c4b930cb70 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -628,27 +628,29 @@ static void daemonRunStateInit(void *opaque) virStateShutdownPrepare, virStateShutdownWait); - /* Tie the non-privileged daemons to the session/shutdown lifecycle */ + /* Signal for VM shutdown when desktop session is terminated, in + * unprivileged daemons */ if (!virNetDaemonIsPrivileged(dmn)) { - sessionBus = virGDBusGetSessionBus(); if (sessionBus != NULL) g_dbus_connection_add_filter(sessionBus, handleSessionMessageFunc, dmn, NULL); + } - systemBus = virGDBusGetSystemBus(); - if (systemBus != NULL) - g_dbus_connection_signal_subscribe(systemBus, - "org.freedesktop.login1", - "org.freedesktop.login1.Manager", - "PrepareForShutdown", - NULL, - NULL, - G_DBUS_SIGNAL_FLAGS_NONE, - handleSystemMessageFunc, + /* Signal for VM shutdown when host OS shutdown is requested, in + * both privileged and unprivileged daemons */ + systemBus = virGDBusGetSystemBus(); + if (systemBus != NULL) + g_dbus_connection_signal_subscribe(systemBus, + "org.freedesktop.login1", + "org.freedesktop.login1.Manager", + "PrepareForShutdown", + NULL, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + handleSystemMessageFunc, dmn, NULL); - } /* Only now accept clients from network */ virNetDaemonUpdateServices(dmn, true); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:41 +0000, Daniel P. Berrangé wrote:
Currently the virStateStop method is only wired up to run save for the unprivileged daemons, so there is no functional change.
IOW, session exit, or host OS shutdown will trigger VM managed saved for QEMU session daemon, but not the system daemon.
This changes the daemon code to always run virStateStop for all daemons. Instead the QEMU driver is responsible for skipping its own logic when running privileged...for now.
This means that virStateStop will now be triggered by logind's PrepareForShutdown signal.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/qemu_driver.c | 3 ++- src/remote/remote_daemon.c | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8c16566ce8..103369ac93 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -949,7 +949,8 @@ qemuStateStop(void) .uri = cfg->uri, };
- virDomainDriverAutoShutdown(&ascfg); + if (!qemu_driver->privileged) + virDomainDriverAutoShutdown(&ascfg);
return 0; } diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index d44a365000..c4b930cb70 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -628,27 +628,29 @@ static void daemonRunStateInit(void *opaque) virStateShutdownPrepare, virStateShutdownWait);
- /* Tie the non-privileged daemons to the session/shutdown lifecycle */ + /* Signal for VM shutdown when desktop session is terminated, in + * unprivileged daemons */ if (!virNetDaemonIsPrivileged(dmn)) { - sessionBus = virGDBusGetSessionBus(); if (sessionBus != NULL) g_dbus_connection_add_filter(sessionBus, handleSessionMessageFunc, dmn, NULL); + }
- systemBus = virGDBusGetSystemBus(); - if (systemBus != NULL) - g_dbus_connection_signal_subscribe(systemBus, - "org.freedesktop.login1", - "org.freedesktop.login1.Manager", - "PrepareForShutdown", - NULL, - NULL, - G_DBUS_SIGNAL_FLAGS_NONE, - handleSystemMessageFunc, + /* Signal for VM shutdown when host OS shutdown is requested, in + * both privileged and unprivileged daemons */ + systemBus = virGDBusGetSystemBus(); + if (systemBus != NULL) + g_dbus_connection_signal_subscribe(systemBus, + "org.freedesktop.login1", + "org.freedesktop.login1.Manager", + "PrepareForShutdown", + NULL, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + handleSystemMessageFunc, dmn, NULL);
This patch leaves the code mis-aligned. You later fix it in 23/26. Move the hunk here.
- }
/* Only now accept clients from network */ virNetDaemonUpdateServices(dmn, true); -- 2.47.1
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

The auto shutdown code can currently only perform managed save, which may fail in some cases, for example when PCI devices are assigned. On failure, shutdown inhibitors remain in place which may be undesirable. This expands the logic to try a sequence of operations * Managed save * Graceful shutdown * Forced poweroff Each of these operations can be enabled or disabled, but they are always applied in this order. With the shutdown option, a configurable time is allowed for shutdown to complete, defaulting to 30 seconds, before moving onto the forced poweroff phase. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 113 +++++++++++++++++++++++++++------ src/hypervisor/domain_driver.h | 4 ++ src/qemu/qemu_driver.c | 3 + 3 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 949e3d01f1..ea3f1cbfcd 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -714,9 +714,26 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autoptr(virConnect) conn = NULL; int numDomains = 0; size_t i; - int state; virDomainPtr *domains = NULL; - g_autofree unsigned int *flags = NULL; + + VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" + "waitShutdownSecs=%d", + cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, + cfg->waitShutdownSecs); + + /* + * Ideally guests will shutdown in a few seconds, but it would + * not be unsual for it to take a while longer, especially under + * load, or if the guest OS has inhibitors slowing down shutdown. + * + * If we wait too long, then guests which ignore the shutdown + * request will significantly delay host shutdown. + * + * Pick 30 seconds as a moderately safe default, assuming that + * most guests are well behaved. + */ + if (cfg->waitShutdownSecs <= 0) + cfg->waitShutdownSecs = 30; if (!(conn = virConnectOpen(cfg->uri))) goto cleanup; @@ -726,31 +743,87 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) goto cleanup; - flags = g_new0(unsigned int, numDomains); + VIR_DEBUG("Auto shutdown with %d running domains", numDomains); + if (cfg->trySave) { + g_autofree unsigned int *flags = g_new0(unsigned int, numDomains); + for (i = 0; i < numDomains; i++) { + int state; + /* + * Pause all VMs to make them stop dirtying pages, + * so save is quicker. We remember if any VMs were + * paused so we can restore that on resume. + */ + flags[i] = VIR_DOMAIN_SAVE_RUNNING; + if (virDomainGetState(domains[i], &state, NULL, 0) == 0) { + if (state == VIR_DOMAIN_PAUSED) + flags[i] = VIR_DOMAIN_SAVE_PAUSED; + } + virDomainSuspend(domains[i]); + } + + for (i = 0; i < numDomains; i++) { + if (virDomainManagedSave(domains[i], flags[i]) < 0) { + VIR_WARN("Unable to perform managed save of '%s': %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + continue; + } + virObjectUnref(domains[i]); + domains[i] = NULL; + } + } + + if (cfg->tryShutdown) { + GTimer *timer = NULL; + for (i = 0; i < numDomains; i++) { + if (domains[i] == NULL) + continue; + if (virDomainShutdown(domains[i]) < 0) { + VIR_WARN("Unable to perform graceful shutdown of '%s': %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + break; + } + } + + timer = g_timer_new(); + while (1) { + bool anyRunning = false; + for (i = 0; i < numDomains; i++) { + if (!domains[i]) + continue; - /* First we pause all VMs to make them stop dirtying - pages, etc. We remember if any VMs were paused so - we can restore that on resume. */ - for (i = 0; i < numDomains; i++) { - flags[i] = VIR_DOMAIN_SAVE_RUNNING; - if (virDomainGetState(domains[i], &state, NULL, 0) == 0) { - if (state == VIR_DOMAIN_PAUSED) - flags[i] = VIR_DOMAIN_SAVE_PAUSED; + if (virDomainIsActive(domains[i]) == 1) { + anyRunning = true; + } else { + virObjectUnref(domains[i]); + domains[i] = NULL; + } + } + + if (!anyRunning) + break; + if (g_timer_elapsed(timer, NULL) > cfg->waitShutdownSecs) + break; + g_usleep(1000*500); } - virDomainSuspend(domains[i]); + g_timer_destroy(timer); } - /* Then we save the VMs to disk */ - for (i = 0; i < numDomains; i++) - if (virDomainManagedSave(domains[i], flags[i]) < 0) - VIR_WARN("Unable to perform managed save of '%s': %s", - virDomainGetName(domains[i]), - virGetLastErrorMessage()); + if (cfg->poweroff) { + for (i = 0; i < numDomains; i++) { + if (domains[i] == NULL) + continue; + virDomainDestroy(domains[i]); + } + } cleanup: if (domains) { - for (i = 0; i < numDomains; i++) - virObjectUnref(domains[i]); + for (i = 0; i < numDomains; i++) { + if (domains[i]) + virObjectUnref(domains[i]); + } VIR_FREE(domains); } } diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index d1588ea712..25c4b0c664 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -92,6 +92,10 @@ void virDomainDriverAutoStart(virDomainObjList *domains, typedef struct _virDomainDriverAutoShutdownConfig { const char *uri; + bool trySave; + bool tryShutdown; + bool poweroff; + int waitShutdownSecs; } virDomainDriverAutoShutdownConfig; void virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 103369ac93..2c97a6fb98 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -947,6 +947,9 @@ qemuStateStop(void) g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(qemu_driver); virDomainDriverAutoShutdownConfig ascfg = { .uri = cfg->uri, + .trySave = true, + .tryShutdown = false, + .poweroff = false, }; if (!qemu_driver->privileged) -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:42 +0000, Daniel P. Berrangé wrote:
The auto shutdown code can currently only perform managed save, which may fail in some cases, for example when PCI devices are assigned. On failure, shutdown inhibitors remain in place which may be undesirable.
This expands the logic to try a sequence of operations
* Managed save * Graceful shutdown * Forced poweroff
Each of these operations can be enabled or disabled, but they are always applied in this order.
With the shutdown option, a configurable time is allowed for shutdown to complete, defaulting to 30 seconds, before moving onto the forced poweroff phase.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 113 +++++++++++++++++++++++++++------ src/hypervisor/domain_driver.h | 4 ++ src/qemu/qemu_driver.c | 3 + 3 files changed, 100 insertions(+), 20 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 949e3d01f1..ea3f1cbfcd 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -714,9 +714,26 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autoptr(virConnect) conn = NULL; int numDomains = 0; size_t i; - int state; virDomainPtr *domains = NULL; - g_autofree unsigned int *flags = NULL; + + VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" + "waitShutdownSecs=%d",
Please avoid linebreaks in debug messages. Also this way there's a missing space.
+ cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, + cfg->waitShutdownSecs); + + /* + * Ideally guests will shutdown in a few seconds, but it would + * not be unsual for it to take a while longer, especially under + * load, or if the guest OS has inhibitors slowing down shutdown. + * + * If we wait too long, then guests which ignore the shutdown + * request will significantly delay host shutdown. + * + * Pick 30 seconds as a moderately safe default, assuming that + * most guests are well behaved. + */
I'll have to see how this is in the end exposed to the user but ...
+ if (cfg->waitShutdownSecs <= 0) + cfg->waitShutdownSecs = 30;
... I find that this is not the correct place for this kind of logic. All the time it'll look as if the shutdown timer is configured to what the caller intended, but at the very last moment it'll be overridden. Including the debug message above mentioning the old value of waitShutdownSecs=%d.
if (!(conn = virConnectOpen(cfg->uri))) goto cleanup; @@ -726,31 +743,87 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) goto cleanup;
- flags = g_new0(unsigned int, numDomains); + VIR_DEBUG("Auto shutdown with %d running domains", numDomains);
This looks like we could do also a VIR_INFO perhaps? So that it's possibly in the system log?
+ if (cfg->trySave) { + g_autofree unsigned int *flags = g_new0(unsigned int, numDomains); + for (i = 0; i < numDomains; i++) { + int state; + /* + * Pause all VMs to make them stop dirtying pages, + * so save is quicker. We remember if any VMs were + * paused so we can restore that on resume. + */ + flags[i] = VIR_DOMAIN_SAVE_RUNNING; + if (virDomainGetState(domains[i], &state, NULL, 0) == 0) { + if (state == VIR_DOMAIN_PAUSED) + flags[i] = VIR_DOMAIN_SAVE_PAUSED; + } + virDomainSuspend(domains[i]); + } + + for (i = 0; i < numDomains; i++) { + if (virDomainManagedSave(domains[i], flags[i]) < 0) { + VIR_WARN("Unable to perform managed save of '%s': %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + continue; + }
In similar spirit of adding log entries, should we perhaps add an entry about the final state/decision that was used for given VM?
+ virObjectUnref(domains[i]); + domains[i] = NULL; + } + } + + if (cfg->tryShutdown) { + GTimer *timer = NULL; + for (i = 0; i < numDomains; i++) { + if (domains[i] == NULL) + continue; + if (virDomainShutdown(domains[i]) < 0) { + VIR_WARN("Unable to perform graceful shutdown of '%s': %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + break; + } + } + + timer = g_timer_new(); + while (1) { + bool anyRunning = false; + for (i = 0; i < numDomains; i++) { + if (!domains[i]) + continue;
- /* First we pause all VMs to make them stop dirtying - pages, etc. We remember if any VMs were paused so - we can restore that on resume. */ - for (i = 0; i < numDomains; i++) { - flags[i] = VIR_DOMAIN_SAVE_RUNNING; - if (virDomainGetState(domains[i], &state, NULL, 0) == 0) { - if (state == VIR_DOMAIN_PAUSED) - flags[i] = VIR_DOMAIN_SAVE_PAUSED; + if (virDomainIsActive(domains[i]) == 1) { + anyRunning = true; + } else {
Here too possibly.
+ virObjectUnref(domains[i]); + domains[i] = NULL; + } + } + + if (!anyRunning) + break; + if (g_timer_elapsed(timer, NULL) > cfg->waitShutdownSecs) + break; + g_usleep(1000*500); } - virDomainSuspend(domains[i]); + g_timer_destroy(timer); }
- /* Then we save the VMs to disk */ - for (i = 0; i < numDomains; i++) - if (virDomainManagedSave(domains[i], flags[i]) < 0) - VIR_WARN("Unable to perform managed save of '%s': %s", - virDomainGetName(domains[i]), - virGetLastErrorMessage()); + if (cfg->poweroff) { + for (i = 0; i < numDomains; i++) { + if (domains[i] == NULL) + continue; + virDomainDestroy(domains[i]); + } + }
cleanup: if (domains) { - for (i = 0; i < numDomains; i++) - virObjectUnref(domains[i]); + for (i = 0; i < numDomains; i++) { + if (domains[i]) + virObjectUnref(domains[i]);
virObjectUnref should be NULL-safe.
+ } VIR_FREE(domains); } } diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index d1588ea712..25c4b0c664 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -92,6 +92,10 @@ void virDomainDriverAutoStart(virDomainObjList *domains,
typedef struct _virDomainDriverAutoShutdownConfig { const char *uri; + bool trySave; + bool tryShutdown; + bool poweroff; + int waitShutdownSecs;
As before; consider 'unsigned' as negative shutdown wait time doesn't seem to make sense. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Thu, Jan 30, 2025 at 04:02:46PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:42 +0000, Daniel P. Berrangé wrote:
The auto shutdown code can currently only perform managed save, which may fail in some cases, for example when PCI devices are assigned. On failure, shutdown inhibitors remain in place which may be undesirable.
This expands the logic to try a sequence of operations
* Managed save * Graceful shutdown * Forced poweroff
Each of these operations can be enabled or disabled, but they are always applied in this order.
With the shutdown option, a configurable time is allowed for shutdown to complete, defaulting to 30 seconds, before moving onto the forced poweroff phase.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 113 +++++++++++++++++++++++++++------ src/hypervisor/domain_driver.h | 4 ++ src/qemu/qemu_driver.c | 3 + 3 files changed, 100 insertions(+), 20 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 949e3d01f1..ea3f1cbfcd 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -714,9 +714,26 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autoptr(virConnect) conn = NULL; int numDomains = 0; size_t i; - int state; virDomainPtr *domains = NULL; - g_autofree unsigned int *flags = NULL; + + VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" + "waitShutdownSecs=%d",
Please avoid linebreaks in debug messages. Also this way there's a missing space.
+ cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, + cfg->waitShutdownSecs); + + /* + * Ideally guests will shutdown in a few seconds, but it would + * not be unsual for it to take a while longer, especially under + * load, or if the guest OS has inhibitors slowing down shutdown. + * + * If we wait too long, then guests which ignore the shutdown + * request will significantly delay host shutdown. + * + * Pick 30 seconds as a moderately safe default, assuming that + * most guests are well behaved. + */
I'll have to see how this is in the end exposed to the user but ...
+ if (cfg->waitShutdownSecs <= 0) + cfg->waitShutdownSecs = 30;
... I find that this is not the correct place for this kind of logic. All the time it'll look as if the shutdown timer is configured to what the caller intended, but at the very last moment it'll be overridden. Including the debug message above mentioning the old value of waitShutdownSecs=%d.
I did it here because this is common code for all drivers. This series wires up QEMU, but we should also wire up bhyve, libxl, lxc, ch too with the same APIs. The qemu.conf settnig introduced ina later patch makes it clear that the default value will be '30' if unset, so this shouldn't really be a surprise, especially since '0' is clearly not a setting you would otherwise pick.
@@ -726,31 +743,87 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) goto cleanup;
- flags = g_new0(unsigned int, numDomains); + VIR_DEBUG("Auto shutdown with %d running domains", numDomains);
This looks like we could do also a VIR_INFO perhaps? So that it's possibly in the system log?
Later in the series (last patch), I wire up systemd notifiers for sending status messages. This reminds me though, I was kinda hoping those would end up in the journal, but they didn't seem to and I've not yet figured out what systemd does with them. If they don't, we could make the virSystemdNotifyStatus API itself issue a VIR_INFO though. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Thu, Jan 30, 2025 at 15:10:55 +0000, Daniel P. Berrangé wrote:
On Thu, Jan 30, 2025 at 04:02:46PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:42 +0000, Daniel P. Berrangé wrote:
The auto shutdown code can currently only perform managed save, which may fail in some cases, for example when PCI devices are assigned. On failure, shutdown inhibitors remain in place which may be undesirable.
This expands the logic to try a sequence of operations
* Managed save * Graceful shutdown * Forced poweroff
Each of these operations can be enabled or disabled, but they are always applied in this order.
With the shutdown option, a configurable time is allowed for shutdown to complete, defaulting to 30 seconds, before moving onto the forced poweroff phase.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 113 +++++++++++++++++++++++++++------ src/hypervisor/domain_driver.h | 4 ++ src/qemu/qemu_driver.c | 3 + 3 files changed, 100 insertions(+), 20 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 949e3d01f1..ea3f1cbfcd 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -714,9 +714,26 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autoptr(virConnect) conn = NULL; int numDomains = 0; size_t i; - int state; virDomainPtr *domains = NULL; - g_autofree unsigned int *flags = NULL; + + VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" + "waitShutdownSecs=%d",
Please avoid linebreaks in debug messages. Also this way there's a missing space.
+ cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, + cfg->waitShutdownSecs); + + /* + * Ideally guests will shutdown in a few seconds, but it would + * not be unsual for it to take a while longer, especially under + * load, or if the guest OS has inhibitors slowing down shutdown. + * + * If we wait too long, then guests which ignore the shutdown + * request will significantly delay host shutdown. + * + * Pick 30 seconds as a moderately safe default, assuming that + * most guests are well behaved. + */
I'll have to see how this is in the end exposed to the user but ...
+ if (cfg->waitShutdownSecs <= 0) + cfg->waitShutdownSecs = 30;
... I find that this is not the correct place for this kind of logic. All the time it'll look as if the shutdown timer is configured to what the caller intended, but at the very last moment it'll be overridden. Including the debug message above mentioning the old value of waitShutdownSecs=%d.
I did it here because this is common code for all drivers.
This series wires up QEMU, but we should also wire up bhyve, libxl, lxc, ch too with the same APIs.
The qemu.conf settnig introduced ina later patch makes it clear that the default value will be '30' if unset, so this shouldn't really be a surprise, especially since '0' is clearly not a setting you would otherwise pick.
Fair enough; but consider moving the VIR_DEBUG statement after this tweak.
@@ -726,31 +743,87 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) goto cleanup;
- flags = g_new0(unsigned int, numDomains); + VIR_DEBUG("Auto shutdown with %d running domains", numDomains);
This looks like we could do also a VIR_INFO perhaps? So that it's possibly in the system log?
Later in the series (last patch), I wire up systemd notifiers for sending status messages. This reminds me though, I was kinda hoping those would end up in the journal, but they didn't seem to and I've not yet figured out what systemd does with them.
If they don't, we could make the virSystemdNotifyStatus API itself issue a VIR_INFO though.
Ah, those messages are exactly what I was looking for. I'd just note that any failures should also be delivered via the same mechanism (e.g. if suspend fails, I'd expect to also use the notification instead of it being recorded in the libvirt-related logs via VIR_WARN

On Thu, Jan 30, 2025 at 03:10:55PM +0000, Daniel P. Berrangé wrote:
On Thu, Jan 30, 2025 at 04:02:46PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:42 +0000, Daniel P. Berrangé wrote:
The auto shutdown code can currently only perform managed save, which may fail in some cases, for example when PCI devices are assigned. On failure, shutdown inhibitors remain in place which may be undesirable.
This expands the logic to try a sequence of operations
* Managed save * Graceful shutdown * Forced poweroff
Each of these operations can be enabled or disabled, but they are always applied in this order.
With the shutdown option, a configurable time is allowed for shutdown to complete, defaulting to 30 seconds, before moving onto the forced poweroff phase.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 113 +++++++++++++++++++++++++++------ src/hypervisor/domain_driver.h | 4 ++ src/qemu/qemu_driver.c | 3 + 3 files changed, 100 insertions(+), 20 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 949e3d01f1..ea3f1cbfcd 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -714,9 +714,26 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autoptr(virConnect) conn = NULL; int numDomains = 0; size_t i; - int state; virDomainPtr *domains = NULL; - g_autofree unsigned int *flags = NULL; + + VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" + "waitShutdownSecs=%d",
Please avoid linebreaks in debug messages. Also this way there's a missing space.
+ cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, + cfg->waitShutdownSecs); + + /* + * Ideally guests will shutdown in a few seconds, but it would + * not be unsual for it to take a while longer, especially under + * load, or if the guest OS has inhibitors slowing down shutdown. + * + * If we wait too long, then guests which ignore the shutdown + * request will significantly delay host shutdown. + * + * Pick 30 seconds as a moderately safe default, assuming that + * most guests are well behaved. + */
I'll have to see how this is in the end exposed to the user but ...
+ if (cfg->waitShutdownSecs <= 0) + cfg->waitShutdownSecs = 30;
... I find that this is not the correct place for this kind of logic. All the time it'll look as if the shutdown timer is configured to what the caller intended, but at the very last moment it'll be overridden. Including the debug message above mentioning the old value of waitShutdownSecs=%d.
I did it here because this is common code for all drivers.
This series wires up QEMU, but we should also wire up bhyve, libxl, lxc, ch too with the same APIs.
The qemu.conf settnig introduced ina later patch makes it clear that the default value will be '30' if unset, so this shouldn't really be a surprise, especially since '0' is clearly not a setting you would otherwise pick.
@@ -726,31 +743,87 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) goto cleanup;
- flags = g_new0(unsigned int, numDomains); + VIR_DEBUG("Auto shutdown with %d running domains", numDomains);
This looks like we could do also a VIR_INFO perhaps? So that it's possibly in the system log?
Later in the series (last patch), I wire up systemd notifiers for sending status messages. This reminds me though, I was kinda hoping those would end up in the journal, but they didn't seem to and I've not yet figured out what systemd does with them.
....what is does with them is practically nothing at all. They are used to set the 'StatusText' property on a unit, which can b queried over dbus, or seen with 'systemctl show'. IOW it is practically useless for telling the user what's happening during a long shutdown operation. I was kinda of hoping systemd would display them when it is stuck in the busy loop waiting for services to quit. Perhaps I'll file an RFE.
If they don't, we could make the virSystemdNotifyStatus API itself issue a VIR_INFO though.
Guess we need to log them directly ourselves after all. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Wed, Jan 08, 2025 at 19:42:42 +0000, Daniel P. Berrangé wrote:
The auto shutdown code can currently only perform managed save, which may fail in some cases, for example when PCI devices are assigned. On failure, shutdown inhibitors remain in place which may be undesirable.
This expands the logic to try a sequence of operations
* Managed save * Graceful shutdown * Forced poweroff
Each of these operations can be enabled or disabled, but they are always applied in this order.
With the shutdown option, a configurable time is allowed for shutdown to complete, defaulting to 30 seconds, before moving onto the forced poweroff phase.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 113 +++++++++++++++++++++++++++------ src/hypervisor/domain_driver.h | 4 ++ src/qemu/qemu_driver.c | 3 + 3 files changed, 100 insertions(+), 20 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 949e3d01f1..ea3f1cbfcd 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c
Noticed while reviewing last patch:
@@ -726,31 +743,87 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) VIR_CONNECT_LIST_DOMAINS_ACTIVE)) < 0) goto cleanup;
- flags = g_new0(unsigned int, numDomains); + VIR_DEBUG("Auto shutdown with %d running domains", numDomains); + if (cfg->trySave) { + g_autofree unsigned int *flags = g_new0(unsigned int, numDomains); + for (i = 0; i < numDomains; i++) { + int state; + /* + * Pause all VMs to make them stop dirtying pages, + * so save is quicker. We remember if any VMs were + * paused so we can restore that on resume. + */ + flags[i] = VIR_DOMAIN_SAVE_RUNNING; + if (virDomainGetState(domains[i], &state, NULL, 0) == 0) { + if (state == VIR_DOMAIN_PAUSED) + flags[i] = VIR_DOMAIN_SAVE_PAUSED; + } + virDomainSuspend(domains[i]);
Any VM where we attempt 'save' is paused ...
+ } + + for (i = 0; i < numDomains; i++) { + if (virDomainManagedSave(domains[i], flags[i]) < 0) { + VIR_WARN("Unable to perform managed save of '%s': %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + continue;
... but not unpaused if saving fails ...
+ } + virObjectUnref(domains[i]); + domains[i] = NULL; + } + } + + if (cfg->tryShutdown) { + GTimer *timer = NULL; + for (i = 0; i < numDomains; i++) { + if (domains[i] == NULL) + continue; + if (virDomainShutdown(domains[i]) < 0) {
... so if we then request a graceful shutdown the guest OS can't respond to it.
+ VIR_WARN("Unable to perform graceful shutdown of '%s': %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + break; + } + }

It may be desirable to treat transient VMs differently from persistent VMs. For example, while performing managed save on persistent VMs makes sense, the same not usually true of transient VMs, since by their nature they will have no config to restore from. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 46 +++++++++++++++++++++++++++++++--- src/hypervisor/domain_driver.h | 18 ++++++++++--- src/libvirt_private.syms | 2 ++ src/qemu/qemu_driver.c | 6 ++--- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index ea3f1cbfcd..4fecaf7e5c 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -35,6 +35,13 @@ VIR_LOG_INIT("hypervisor.domain_driver"); +VIR_ENUM_IMPL(virDomainDriverAutoShutdownScope, + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_LAST, + "none", + "persistent", + "transient", + "all"); + char * virDomainDriverGenerateRootHash(const char *drivername, const char *root) @@ -715,6 +722,7 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) int numDomains = 0; size_t i; virDomainPtr *domains = NULL; + g_autofree bool *transient = NULL; VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" "waitShutdownSecs=%d", @@ -735,6 +743,12 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) if (cfg->waitShutdownSecs <= 0) cfg->waitShutdownSecs = 30; + /* Short-circuit if all actions are disabled */ + if (cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE && + cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE && + cfg->poweroff == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) + return; + if (!(conn = virConnectOpen(cfg->uri))) goto cleanup; @@ -744,10 +758,22 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) goto cleanup; VIR_DEBUG("Auto shutdown with %d running domains", numDomains); - if (cfg->trySave) { + + transient = g_new0(bool, numDomains); + for (i = 0; i < numDomains; i++) { + if (virDomainIsPersistent(domains[i]) == 0) + transient[i] = true; + } + + if (cfg->trySave != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { g_autofree unsigned int *flags = g_new0(unsigned int, numDomains); for (i = 0; i < numDomains; i++) { int state; + + if ((transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue; + /* * Pause all VMs to make them stop dirtying pages, * so save is quicker. We remember if any VMs were @@ -773,11 +799,16 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) } } - if (cfg->tryShutdown) { + if (cfg->tryShutdown != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { GTimer *timer = NULL; for (i = 0; i < numDomains; i++) { if (domains[i] == NULL) continue; + + if ((transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue; + if (virDomainShutdown(domains[i]) < 0) { VIR_WARN("Unable to perform graceful shutdown of '%s': %s", virDomainGetName(domains[i]), @@ -793,6 +824,10 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) if (!domains[i]) continue; + if ((transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue; + if (virDomainIsActive(domains[i]) == 1) { anyRunning = true; } else { @@ -810,10 +845,15 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_timer_destroy(timer); } - if (cfg->poweroff) { + if (cfg->poweroff != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { for (i = 0; i < numDomains; i++) { if (domains[i] == NULL) continue; + + if ((transient[i] && cfg->poweroff == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->poweroff == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue; + virDomainDestroy(domains[i]); } } diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index 25c4b0c664..acb7a41b5d 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -24,6 +24,7 @@ #include "virhostdev.h" #include "virpci.h" #include "virdomainobjlist.h" +#include "virenum.h" char * virDomainDriverGenerateRootHash(const char *drivername, @@ -90,11 +91,22 @@ typedef struct _virDomainDriverAutoStartConfig { void virDomainDriverAutoStart(virDomainObjList *domains, virDomainDriverAutoStartConfig *cfg); +typedef enum { + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE, + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT, + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT, + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL, + + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_LAST, +} virDomainDriverAutoShutdownScope; + +VIR_ENUM_DECL(virDomainDriverAutoShutdownScope); + typedef struct _virDomainDriverAutoShutdownConfig { const char *uri; - bool trySave; - bool tryShutdown; - bool poweroff; + virDomainDriverAutoShutdownScope trySave; + virDomainDriverAutoShutdownScope tryShutdown; + virDomainDriverAutoShutdownScope poweroff; int waitShutdownSecs; } virDomainDriverAutoShutdownConfig; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 781bb566d2..301240452d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1641,6 +1641,8 @@ virDomainCgroupSetupVcpuBW; # hypervisor/domain_driver.h virDomainDriverAddIOThreadCheck; virDomainDriverAutoShutdown; +virDomainDriverAutoShutdownScopeTypeFromString; +virDomainDriverAutoShutdownScopeTypeToString; virDomainDriverAutoStart; virDomainDriverDelIOThreadCheck; virDomainDriverGenerateMachineName; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 2c97a6fb98..8e15a77be2 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -947,9 +947,9 @@ qemuStateStop(void) g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(qemu_driver); virDomainDriverAutoShutdownConfig ascfg = { .uri = cfg->uri, - .trySave = true, - .tryShutdown = false, - .poweroff = false, + .trySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL, + .tryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE, + .poweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE }; if (!qemu_driver->privileged) -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:43 +0000, Daniel P. Berrangé wrote:
It may be desirable to treat transient VMs differently from persistent VMs. For example, while performing managed save on persistent VMs makes sense, the same not usually true of transient VMs, since by their nature they will have no config to restore from.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 46 +++++++++++++++++++++++++++++++--- src/hypervisor/domain_driver.h | 18 ++++++++++--- src/libvirt_private.syms | 2 ++ src/qemu/qemu_driver.c | 6 ++--- 4 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index ea3f1cbfcd..4fecaf7e5c 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -35,6 +35,13 @@
VIR_LOG_INIT("hypervisor.domain_driver");
+VIR_ENUM_IMPL(virDomainDriverAutoShutdownScope, + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_LAST, + "none", + "persistent", + "transient", + "all");
since we have the 'ToString' function ...
+ char * virDomainDriverGenerateRootHash(const char *drivername, const char *root) @@ -715,6 +722,7 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) int numDomains = 0; size_t i; virDomainPtr *domains = NULL; + g_autofree bool *transient = NULL;
VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" "waitShutdownSecs=%d",
... consider logging the string here instead of the number.
@@ -735,6 +743,12 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) if (cfg->waitShutdownSecs <= 0) cfg->waitShutdownSecs = 30;
+ /* Short-circuit if all actions are disabled */ + if (cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE && + cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE && + cfg->poweroff == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) + return; + if (!(conn = virConnectOpen(cfg->uri))) goto cleanup;
@@ -744,10 +758,22 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) goto cleanup;
VIR_DEBUG("Auto shutdown with %d running domains", numDomains); - if (cfg->trySave) { + + transient = g_new0(bool, numDomains); + for (i = 0; i < numDomains; i++) { + if (virDomainIsPersistent(domains[i]) == 0) + transient[i] = true; + } + + if (cfg->trySave != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { g_autofree unsigned int *flags = g_new0(unsigned int, numDomains); for (i = 0; i < numDomains; i++) { int state; + + if ((transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue; + /* * Pause all VMs to make them stop dirtying pages, * so save is quicker. We remember if any VMs were @@ -773,11 +799,16 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) } }
- if (cfg->tryShutdown) { + if (cfg->tryShutdown != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { GTimer *timer = NULL; for (i = 0; i < numDomains; i++) { if (domains[i] == NULL) continue; + + if ((transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue;
Trying to 'managedSave' a transient VM will/should fail as managedSave doesn't make sense with transient VMS: static int qemuDomainManagedSaveHelper(virQEMUDriver *driver, virDomainObj *vm, const char *dxml, unsigned int flags) { g_autoptr(virQEMUDriverConfig) cfg = NULL; g_autoptr(virCommand) compressor = NULL; g_autofree char *path = NULL; int format; if (virDomainObjCheckActive(vm) < 0) return -1; if (!vm->persistent) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot do managed save for transient domain")); return -1; }
+ if (virDomainShutdown(domains[i]) < 0) { VIR_WARN("Unable to perform graceful shutdown of '%s': %s", virDomainGetName(domains[i]),
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Thu, Jan 30, 2025 at 05:44:08PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:43 +0000, Daniel P. Berrangé wrote:
It may be desirable to treat transient VMs differently from persistent VMs. For example, while performing managed save on persistent VMs makes sense, the same not usually true of transient VMs, since by their nature they will have no config to restore from.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 46 +++++++++++++++++++++++++++++++--- src/hypervisor/domain_driver.h | 18 ++++++++++--- src/libvirt_private.syms | 2 ++ src/qemu/qemu_driver.c | 6 ++--- 4 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index ea3f1cbfcd..4fecaf7e5c 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -35,6 +35,13 @@
VIR_LOG_INIT("hypervisor.domain_driver");
+VIR_ENUM_IMPL(virDomainDriverAutoShutdownScope, + VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_LAST, + "none", + "persistent", + "transient", + "all");
since we have the 'ToString' function ...
+ char * virDomainDriverGenerateRootHash(const char *drivername, const char *root) @@ -715,6 +722,7 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) int numDomains = 0; size_t i; virDomainPtr *domains = NULL; + g_autofree bool *transient = NULL;
VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" "waitShutdownSecs=%d",
... consider logging the string here instead of the number.
@@ -735,6 +743,12 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) if (cfg->waitShutdownSecs <= 0) cfg->waitShutdownSecs = 30;
+ /* Short-circuit if all actions are disabled */ + if (cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE && + cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE && + cfg->poweroff == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) + return; + if (!(conn = virConnectOpen(cfg->uri))) goto cleanup;
@@ -744,10 +758,22 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) goto cleanup;
VIR_DEBUG("Auto shutdown with %d running domains", numDomains); - if (cfg->trySave) { + + transient = g_new0(bool, numDomains); + for (i = 0; i < numDomains; i++) { + if (virDomainIsPersistent(domains[i]) == 0) + transient[i] = true; + } + + if (cfg->trySave != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { g_autofree unsigned int *flags = g_new0(unsigned int, numDomains); for (i = 0; i < numDomains; i++) { int state; + + if ((transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue; + /* * Pause all VMs to make them stop dirtying pages, * so save is quicker. We remember if any VMs were @@ -773,11 +799,16 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) } }
- if (cfg->tryShutdown) { + if (cfg->tryShutdown != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { GTimer *timer = NULL; for (i = 0; i < numDomains; i++) { if (domains[i] == NULL) continue; + + if ((transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT) || + (!transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) + continue;
Trying to 'managedSave' a transient VM will/should fail as managedSave doesn't make sense with transient VMS:
Hmmm, true, we should reject 'trySave == transient' right at the start. Also if virDomainManagedSave() fails, we need to add a call to virDomainResume, otherwise the fallthrough to virDomainShutdown is useless as the VM will be paused still.
+ if (virDomainShutdown(domains[i]) < 0) { VIR_WARN("Unable to perform graceful shutdown of '%s': %s", virDomainGetName(domains[i]),
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Currently automatic VM managed save is only performed in session daemons, on desktop session close, or host OS shutdown request. With this change it is possible to control shutdown behaviour for all daemons. A recommended setup might be: auto_shutdown_try_save = "persistent" auto_shutdown_try_shutdown = "all" auto_shutdown_poweroff = "all" Each setting accepts 'none', 'persistent', 'transient', and 'all' to control what types of guest it applies to. For historical compatibility, for the system daemon, the settings currently default to: auto_shutdown_try_save = "none" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none" while for the session daemon they currently default to auto_shutdown_try_save = "all" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none" The system daemon settings should NOT be enabled if the traditional libvirt-guests.service is already enabled. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 3 ++ src/qemu/qemu.conf.in | 37 ++++++++++++++++++++++ src/qemu/qemu_conf.c | 51 ++++++++++++++++++++++++++++++ src/qemu/qemu_conf.h | 3 ++ src/qemu/qemu_driver.c | 9 +++--- src/qemu/test_libvirtd_qemu.aug.in | 3 ++ 6 files changed, 101 insertions(+), 5 deletions(-) diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 642093c40b..e465a231fc 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -98,6 +98,9 @@ module Libvirtd_qemu = | bool_entry "auto_dump_bypass_cache" | bool_entry "auto_start_bypass_cache" | int_entry "auto_start_delay" + | str_entry "auto_shutdown_try_save" + | str_entry "auto_shutdown_try_shutdown" + | str_entry "auto_shutdown_powerdown" let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index a3e9bbfcf3..d8890c4c32 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -638,6 +638,43 @@ # #auto_start_delay = 0 +# Whether to perform managed save of running VMs if a host OS +# shutdown is requested (system/session daemons), or the desktop +# session terminates (session daemon only). Accepts +# +# * "none" - do not try to save any running VMs +# * "persistent" - only try to save persistent running VMs +# * "transient" - only try to save transient running VMs +# * "all" - try to save all running VMs +# +# Defaults to "all" for session daemons and "none" +# for system daemons +# +# If 'libvirt-guests.service' is enabled, then this must be +# set to 'none' for system daemons to avoid dueling actions +#auto_shutdown_try_save = "all" + +# As above, but with a graceful shutdown action instead of +# managed save. If managed save is enabled, shutdown will +# be tried only on failure to perform managed save. +# +# Defaults to "none" +# +# If 'libvirt-guests.service' is enabled, then this must be +# set to 'none' for system daemons to avoid dueling actions +#auto_shutdown_try_shutdown = "none" + +# As above, but with a forced poweroff instead of managed +# save. If managed save or graceful shutdown are enabled, +# forced poweroff will be tried only on failure of the +# other options. +# +# Defaults to "none" +# +# If 'libvirt-guests.service' is enabled, then this must be +# set to 'none' for system daemons to avoid dueling actions +#auto_shutdown_powerdown = "none" + # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 0b6b923bcb..a57eccf569 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -303,6 +303,21 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->dumpGuestCore = true; #endif + if (privileged) { + /* + * Defer to libvirt-guests.service. + * + * XXX, or query if libvirt-guests.service is enabled perhaps ? + */ + cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + } else { + cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; + cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + } + return g_steal_pointer(&cfg); } @@ -626,6 +641,8 @@ static int virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, virConf *conf) { + g_autofree char *autoShutdownScope = NULL; + if (virConfGetValueString(conf, "save_image_format", &cfg->saveImageFormat) < 0) return -1; if (virConfGetValueString(conf, "dump_image_format", &cfg->dumpImageFormat) < 0) @@ -640,6 +657,40 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, return -1; if (virConfGetValueInt(conf, "auto_start_delay", &cfg->autoStartDelayMS) < 0) return -1; + if (virConfGetValueString(conf, "auto_shutdown_try_save", &autoShutdownScope) < 0) + return -1; + + if (autoShutdownScope != NULL && + (cfg->autoShutdownTrySave = + virDomainDriverAutoShutdownScopeTypeFromString(autoShutdownScope)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown auto_shutdown_try_save '%1$s'"), + autoShutdownScope); + return -1; + } + + if (virConfGetValueString(conf, "auto_shutdown_try_shutdown", &autoShutdownScope) < 0) + return -1; + + if (autoShutdownScope != NULL && + (cfg->autoShutdownTryShutdown = + virDomainDriverAutoShutdownScopeTypeFromString(autoShutdownScope)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown auto_shutdown_try_shutdown '%1$s'"), + autoShutdownScope); + return -1; + } + if (virConfGetValueString(conf, "auto_shutdown_poweroff", &autoShutdownScope) < 0) + return -1; + + if (autoShutdownScope != NULL && + (cfg->autoShutdownPoweroff = + virDomainDriverAutoShutdownScopeTypeFromString(autoShutdownScope)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown auto_shutdown_poweroff '%1$s'"), + autoShutdownScope); + return -1; + } return 0; } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 61a2bdce51..5d9ace6dcc 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -201,6 +201,9 @@ struct _virQEMUDriverConfig { bool autoDumpBypassCache; bool autoStartBypassCache; int autoStartDelayMS; + int autoShutdownTrySave; + int autoShutdownTryShutdown; + int autoShutdownPoweroff; char *lockManagerName; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8e15a77be2..641b45fd8f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -947,13 +947,12 @@ qemuStateStop(void) g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(qemu_driver); virDomainDriverAutoShutdownConfig ascfg = { .uri = cfg->uri, - .trySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL, - .tryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE, - .poweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE + .trySave = cfg->autoShutdownTrySave, + .tryShutdown = cfg->autoShutdownTryShutdown, + .poweroff = cfg->autoShutdownPoweroff, }; - if (!qemu_driver->privileged) - virDomainDriverAutoShutdown(&ascfg); + virDomainDriverAutoShutdown(&ascfg); return 0; } diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index c2a1d7d829..e7137f686f 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -76,6 +76,9 @@ module Test_libvirtd_qemu = { "auto_dump_bypass_cache" = "0" } { "auto_start_bypass_cache" = "0" } { "auto_start_delay" = "0" } +{ "auto_shutdown_try_save" = "all" } +{ "auto_shutdown_try_shutdown" = "none" } +{ "auto_shutdown_poweroff" = "none" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } { "set_process_name" = "1" } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:44 +0000, Daniel P. Berrangé wrote:
Currently automatic VM managed save is only performed in session daemons, on desktop session close, or host OS shutdown request.
With this change it is possible to control shutdown behaviour for all daemons. A recommended setup might be:
auto_shutdown_try_save = "persistent" auto_shutdown_try_shutdown = "all" auto_shutdown_poweroff = "all"
Each setting accepts 'none', 'persistent', 'transient', and 'all' to control what types of guest it applies to.
For historical compatibility, for the system daemon, the settings currently default to:
auto_shutdown_try_save = "none" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
while for the session daemon they currently default to
auto_shutdown_try_save = "all" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
The system daemon settings should NOT be enabled if the traditional libvirt-guests.service is already enabled.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 3 ++
Proper review later, but for now this fails 'check-augeas-libvirtd_qemu': Listing only the last 100 lines from a long log. { "migrate_tls_x509_verify" = "1" } { "migrate_tls_x509_secret_uuid" = "00000000-0000-0000-0000-000000000000" } { "migrate_tls_force" = "0" } { "backup_tls_x509_cert_dir" = "/etc/pki/libvirt-backup" } { "backup_tls_x509_verify" = "1" } { "backup_tls_x509_secret_uuid" = "00000000-0000-0000-0000-000000000000" } { "nographics_allow_host_audio" = "1" } { "remote_display_port_min" = "5900" } { "remote_display_port_max" = "65535" } { "remote_websocket_port_min" = "5700" } { "remote_websocket_port_max" = "65535" } { "security_driver" = "selinux" } { "security_default_confined" = "1" } { "security_require_confined" = "1" } { "user" = "qemu" } { "group" = "qemu" } { "dynamic_ownership" = "1" } { "remember_owner" = "1" } { "cgroup_controllers" { "1" = "cpu" } { "2" = "devices" } { "3" = "memory" } { "4" = "blkio" } { "5" = "cpuset" } { "6" = "cpuacct" } } { "cgroup_device_acl" { "1" = "/dev/null" } { "2" = "/dev/full" } { "3" = "/dev/zero" } { "4" = "/dev/random" } { "5" = "/dev/urandom" } { "6" = "/dev/ptmx" } { "7" = "/dev/kvm" } { "8" = "/dev/userfaultfd" } } { "save_image_format" = "raw" } { "dump_image_format" = "raw" } { "snapshot_image_format" = "raw" } { "auto_dump_path" = "/var/lib/libvirt/qemu/dump" } { "auto_dump_bypass_cache" = "0" } { "auto_start_bypass_cache" = "0" } { "auto_start_delay" = "0" } { "auto_shutdown_try_save" = "all" } { "auto_shutdown_try_shutdown" = "none" } { "auto_shutdown_powerdown" = "none" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } { "set_process_name" = "1" } { "max_processes" = "0" } { "max_files" = "0" } { "max_threads_per_process" = "0" } { "max_core" = "unlimited" } { "dump_guest_core" = "1" } { "mac_filter" = "1" } { "relaxed_acs_check" = "1" } { "lock_manager" = "lockd" } { "max_queued" = "0" } { "keepalive_interval" = "5" } { "keepalive_count" = "5" } { "seccomp_sandbox" = "1" } { "migration_address" = "0.0.0.0" } { "migration_host" = "host.example.com" } { "migration_port_min" = "49152" } { "migration_port_max" = "49215" } { "log_timestamp" = "0" } { "nvram" { "1" = "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd" } { "2" = "/usr/share/OVMF/OVMF_CODE.secboot.fd:/usr/share/OVMF/OVMF_VARS.fd" } { "3" = "/usr/share/AAVMF/AAVMF_CODE.fd:/usr/share/AAVMF/AAVMF_VARS.fd" } { "4" = "/usr/share/AAVMF/AAVMF32_CODE.fd:/usr/share/AAVMF/AAVMF32_VARS.fd" } } { "stdio_handler" = "logd" } { "gluster_debug_level" = "9" } { "virtiofsd_debug" = "1" } { "namespaces" { "1" = "mount" } } { "memory_backing_dir" = "/var/lib/libvirt/qemu/ram" } { "pr_helper" = "qemu-pr-helper" } { "slirp_helper" = "/usr/bin/slirp-helper" } { "dbus_daemon" = "dbus-daemon" } { "swtpm_user" = "tss" } { "swtpm_group" = "tss" } { "capability_filters" { "1" = "capname" } } { "deprecation_behavior" = "none" } { "sched_core" = "none" } { "storage_use_nbdkit" = "0" } { "shared_filesystems" { "1" = "/path/to/images" } { "2" = "/path/to/nvram" } { "3" = "/path/to/swtpm" } } } stderr: Syntax error in lens definition Failed to load /home/pipo/build/libvirt/gcc/src/test_libvirtd_qemu.aug

On Wed, Jan 29, 2025 at 17:24:47 +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:44 +0000, Daniel P. Berrangé wrote:
Currently automatic VM managed save is only performed in session daemons, on desktop session close, or host OS shutdown request.
With this change it is possible to control shutdown behaviour for all daemons. A recommended setup might be:
auto_shutdown_try_save = "persistent" auto_shutdown_try_shutdown = "all" auto_shutdown_poweroff = "all"
Each setting accepts 'none', 'persistent', 'transient', and 'all' to control what types of guest it applies to.
For historical compatibility, for the system daemon, the settings currently default to:
auto_shutdown_try_save = "none" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
while for the session daemon they currently default to
auto_shutdown_try_save = "all" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
The system daemon settings should NOT be enabled if the traditional libvirt-guests.service is already enabled.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 3 ++
Proper review later, but for now this fails 'check-augeas-libvirtd_qemu':
It fixed itself after applying next patch so some hunks will be misplaced.

On Wed, Jan 08, 2025 at 19:42:44 +0000, Daniel P. Berrangé wrote:
Currently automatic VM managed save is only performed in session daemons, on desktop session close, or host OS shutdown request.
With this change it is possible to control shutdown behaviour for all daemons. A recommended setup might be:
auto_shutdown_try_save = "persistent" auto_shutdown_try_shutdown = "all" auto_shutdown_poweroff = "all"
Each setting accepts 'none', 'persistent', 'transient', and 'all' to control what types of guest it applies to.
For historical compatibility, for the system daemon, the settings currently default to:
auto_shutdown_try_save = "none" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
while for the session daemon they currently default to
auto_shutdown_try_save = "all" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
The system daemon settings should NOT be enabled if the traditional libvirt-guests.service is already enabled.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 3 ++ src/qemu/qemu.conf.in | 37 ++++++++++++++++++++++ src/qemu/qemu_conf.c | 51 ++++++++++++++++++++++++++++++ src/qemu/qemu_conf.h | 3 ++ src/qemu/qemu_driver.c | 9 +++--- src/qemu/test_libvirtd_qemu.aug.in | 3 ++ 6 files changed, 101 insertions(+), 5 deletions(-)
diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 642093c40b..e465a231fc 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -98,6 +98,9 @@ module Libvirtd_qemu = | bool_entry "auto_dump_bypass_cache" | bool_entry "auto_start_bypass_cache" | int_entry "auto_start_delay" + | str_entry "auto_shutdown_try_save" + | str_entry "auto_shutdown_try_shutdown" + | str_entry "auto_shutdown_powerdown"
Don't forget to merge in the appropriate hunk from the next patch to make tests pass.
let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index a3e9bbfcf3..d8890c4c32 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -638,6 +638,43 @@ # #auto_start_delay = 0
+# Whether to perform managed save of running VMs if a host OS +# shutdown is requested (system/session daemons), or the desktop +# session terminates (session daemon only). Accepts +# +# * "none" - do not try to save any running VMs +# * "persistent" - only try to save persistent running VMs +# * "transient" - only try to save transient running VMs +# * "all" - try to save all running VMs +# +# Defaults to "all" for session daemons and "none" +# for system daemons +# +# If 'libvirt-guests.service' is enabled, then this must be +# set to 'none' for system daemons to avoid dueling actions +#auto_shutdown_try_save = "all"
As demonstrated in previous patch, the 'transient' and 'all' make no sense here, as all you'll get is an error per semantics of managed save. The only way we could make saving of transient VMs work is to invoke the normal save API and dump them in a directory configured for this reason. But then you get problem of what to do at startup as IIUC the native replacement to libvirt-guests will only support startup via 'autostart', thus that'd overcomplicate things a bit more. I'd suggest to simply don't do save for transient VMs.
+ +# As above, but with a graceful shutdown action instead of +# managed save. If managed save is enabled, shutdown will +# be tried only on failure to perform managed save. +# +# Defaults to "none" +# +# If 'libvirt-guests.service' is enabled, then this must be +# set to 'none' for system daemons to avoid dueling actions +#auto_shutdown_try_shutdown = "none" + +# As above, but with a forced poweroff instead of managed +# save. If managed save or graceful shutdown are enabled, +# forced poweroff will be tried only on failure of the +# other options. +# +# Defaults to "none" +# +# If 'libvirt-guests.service' is enabled, then this must be +# set to 'none' for system daemons to avoid dueling actions +#auto_shutdown_powerdown = "none" + # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 0b6b923bcb..a57eccf569 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -303,6 +303,21 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->dumpGuestCore = true; #endif
+ if (privileged) { + /* + * Defer to libvirt-guests.service. + * + * XXX, or query if libvirt-guests.service is enabled perhaps ? + */ + cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + } else { + cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; + cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + } + return g_steal_pointer(&cfg); }
@@ -626,6 +641,8 @@ static int virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, virConf *conf) { + g_autofree char *autoShutdownScope = NULL; + if (virConfGetValueString(conf, "save_image_format", &cfg->saveImageFormat) < 0) return -1; if (virConfGetValueString(conf, "dump_image_format", &cfg->dumpImageFormat) < 0) @@ -640,6 +657,40 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, return -1; if (virConfGetValueInt(conf, "auto_start_delay", &cfg->autoStartDelayMS) < 0) return -1; + if (virConfGetValueString(conf, "auto_shutdown_try_save", &autoShutdownScope) < 0) + return -1; + + if (autoShutdownScope != NULL && + (cfg->autoShutdownTrySave = + virDomainDriverAutoShutdownScopeTypeFromString(autoShutdownScope)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown auto_shutdown_try_save '%1$s'"), + autoShutdownScope); + return -1; + }
Here it'll need to reject the two unsupported options as well.
+ + if (virConfGetValueString(conf, "auto_shutdown_try_shutdown", &autoShutdownScope) < 0) + return -1; + + if (autoShutdownScope != NULL && + (cfg->autoShutdownTryShutdown = + virDomainDriverAutoShutdownScopeTypeFromString(autoShutdownScope)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown auto_shutdown_try_shutdown '%1$s'"), + autoShutdownScope); + return -1; + } + if (virConfGetValueString(conf, "auto_shutdown_poweroff", &autoShutdownScope) < 0) + return -1; + + if (autoShutdownScope != NULL && + (cfg->autoShutdownPoweroff = + virDomainDriverAutoShutdownScopeTypeFromString(autoShutdownScope)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown auto_shutdown_poweroff '%1$s'"), + autoShutdownScope); + return -1; + }
return 0; } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 61a2bdce51..5d9ace6dcc 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -201,6 +201,9 @@ struct _virQEMUDriverConfig { bool autoDumpBypassCache; bool autoStartBypassCache; int autoStartDelayMS; + int autoShutdownTrySave; + int autoShutdownTryShutdown; + int autoShutdownPoweroff;
Preferrably use the enum type, although that'll make the parser a bit more verbose due to absence of something like virXMLPropEnum. With saving of transient VMs rejected: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Thu, Jan 30, 2025 at 06:21:07PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:44 +0000, Daniel P. Berrangé wrote:
Currently automatic VM managed save is only performed in session daemons, on desktop session close, or host OS shutdown request.
With this change it is possible to control shutdown behaviour for all daemons. A recommended setup might be:
auto_shutdown_try_save = "persistent" auto_shutdown_try_shutdown = "all" auto_shutdown_poweroff = "all"
Each setting accepts 'none', 'persistent', 'transient', and 'all' to control what types of guest it applies to.
For historical compatibility, for the system daemon, the settings currently default to:
auto_shutdown_try_save = "none" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
while for the session daemon they currently default to
auto_shutdown_try_save = "all" auto_shutdown_try_shutdown = "none" auto_shutdown_poweroff = "none"
The system daemon settings should NOT be enabled if the traditional libvirt-guests.service is already enabled.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 3 ++ src/qemu/qemu.conf.in | 37 ++++++++++++++++++++++ src/qemu/qemu_conf.c | 51 ++++++++++++++++++++++++++++++ src/qemu/qemu_conf.h | 3 ++ src/qemu/qemu_driver.c | 9 +++--- src/qemu/test_libvirtd_qemu.aug.in | 3 ++ 6 files changed, 101 insertions(+), 5 deletions(-)
diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 642093c40b..e465a231fc 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -98,6 +98,9 @@ module Libvirtd_qemu = | bool_entry "auto_dump_bypass_cache" | bool_entry "auto_start_bypass_cache" | int_entry "auto_start_delay" + | str_entry "auto_shutdown_try_save" + | str_entry "auto_shutdown_try_shutdown" + | str_entry "auto_shutdown_powerdown"
Don't forget to merge in the appropriate hunk from the next patch to make tests pass.
let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index a3e9bbfcf3..d8890c4c32 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -638,6 +638,43 @@ # #auto_start_delay = 0
+# Whether to perform managed save of running VMs if a host OS +# shutdown is requested (system/session daemons), or the desktop +# session terminates (session daemon only). Accepts +# +# * "none" - do not try to save any running VMs +# * "persistent" - only try to save persistent running VMs +# * "transient" - only try to save transient running VMs +# * "all" - try to save all running VMs +# +# Defaults to "all" for session daemons and "none" +# for system daemons +# +# If 'libvirt-guests.service' is enabled, then this must be +# set to 'none' for system daemons to avoid dueling actions +#auto_shutdown_try_save = "all"
As demonstrated in previous patch, the 'transient' and 'all' make no sense here, as all you'll get is an error per semantics of managed save.
The only way we could make saving of transient VMs work is to invoke the normal save API and dump them in a directory configured for this reason.
But then you get problem of what to do at startup as IIUC the native replacement to libvirt-guests will only support startup via 'autostart', thus that'd overcomplicate things a bit more.
I'd suggest to simply don't do save for transient VMs.
Yes, I'm going to block 'all' and 'transient' and rephase these inline config file docs.
+ if (virConfGetValueString(conf, "auto_shutdown_try_save", &autoShutdownScope) < 0) + return -1; + + if (autoShutdownScope != NULL && + (cfg->autoShutdownTrySave = + virDomainDriverAutoShutdownScopeTypeFromString(autoShutdownScope)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown auto_shutdown_try_save '%1$s'"), + autoShutdownScope); + return -1; + }
Here it'll need to reject the two unsupported options as well.
yep
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 61a2bdce51..5d9ace6dcc 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -201,6 +201,9 @@ struct _virQEMUDriverConfig { bool autoDumpBypassCache; bool autoStartBypassCache; int autoStartDelayMS; + int autoShutdownTrySave; + int autoShutdownTryShutdown; + int autoShutdownPoweroff;
Preferrably use the enum type, although that'll make the parser a bit more verbose due to absence of something like virXMLPropEnum.
It is pretty easy actually, so done that too With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Currently the session daemon will try a managed save on all VMs, leaving them running if that fails. This limits the managed save just to persistent VMs, as there will usually not be any way to restore transient VMs later. It also enables graceful shutdown and then forced poweroff, should save fail for some reason. These new defaults can be overridden in the config file if needed. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 2 +- src/qemu/qemu.conf.in | 14 ++++++++------ src/qemu/qemu_conf.c | 6 +++--- src/qemu/test_libvirtd_qemu.aug.in | 6 +++--- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index e465a231fc..605604a01a 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -100,7 +100,7 @@ module Libvirtd_qemu = | int_entry "auto_start_delay" | str_entry "auto_shutdown_try_save" | str_entry "auto_shutdown_try_shutdown" - | str_entry "auto_shutdown_powerdown" + | str_entry "auto_shutdown_poweroff" let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d8890c4c32..82eae2eecd 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -647,33 +647,35 @@ # * "transient" - only try to save transient running VMs # * "all" - try to save all running VMs # -# Defaults to "all" for session daemons and "none" +# Defaults to "persistent" for session daemons and "none" # for system daemons # # If 'libvirt-guests.service' is enabled, then this must be # set to 'none' for system daemons to avoid dueling actions -#auto_shutdown_try_save = "all" +#auto_shutdown_try_save = "persistent" # As above, but with a graceful shutdown action instead of # managed save. If managed save is enabled, shutdown will # be tried only on failure to perform managed save. # -# Defaults to "none" +# Defaults to "all" for session daemons and "none" for +# system daemons # # If 'libvirt-guests.service' is enabled, then this must be # set to 'none' for system daemons to avoid dueling actions -#auto_shutdown_try_shutdown = "none" +#auto_shutdown_try_shutdown = "all" # As above, but with a forced poweroff instead of managed # save. If managed save or graceful shutdown are enabled, # forced poweroff will be tried only on failure of the # other options. # -# Defaults to "none" +# Defaults to "all" for session daemons and "none" for +# system daemons. # # If 'libvirt-guests.service' is enabled, then this must be # set to 'none' for system daemons to avoid dueling actions -#auto_shutdown_powerdown = "none" +#auto_shutdown_poweroff = "all" # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index a57eccf569..76bb3bd888 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -313,9 +313,9 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; } else { - cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; - cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; - cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT; + cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; + cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; } return g_steal_pointer(&cfg); diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index e7137f686f..3bc8104d7a 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -76,9 +76,9 @@ module Test_libvirtd_qemu = { "auto_dump_bypass_cache" = "0" } { "auto_start_bypass_cache" = "0" } { "auto_start_delay" = "0" } -{ "auto_shutdown_try_save" = "all" } -{ "auto_shutdown_try_shutdown" = "none" } -{ "auto_shutdown_poweroff" = "none" } +{ "auto_shutdown_try_save" = "persistent" } +{ "auto_shutdown_try_shutdown" = "all" } +{ "auto_shutdown_poweroff" = "all" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } { "set_process_name" = "1" } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:45 +0000, Daniel P. Berrangé wrote:
Currently the session daemon will try a managed save on all VMs, leaving them running if that fails.
This limits the managed save just to persistent VMs, as there will usually not be any way to restore transient VMs later.
It also enables graceful shutdown and then forced poweroff, should save fail for some reason.
These new defaults can be overridden in the config file if needed.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 2 +- src/qemu/qemu.conf.in | 14 ++++++++------ src/qemu/qemu_conf.c | 6 +++--- src/qemu/test_libvirtd_qemu.aug.in | 6 +++--- 4 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index e465a231fc..605604a01a 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -100,7 +100,7 @@ module Libvirtd_qemu = | int_entry "auto_start_delay" | str_entry "auto_shutdown_try_save" | str_entry "auto_shutdown_try_shutdown" - | str_entry "auto_shutdown_powerdown" + | str_entry "auto_shutdown_poweroff"
let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper"
This hunk belongs to previous patch.
diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d8890c4c32..82eae2eecd 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -647,33 +647,35 @@ # * "transient" - only try to save transient running VMs # * "all" - try to save all running VMs # -# Defaults to "all" for session daemons and "none" +# Defaults to "persistent" for session daemons and "none" # for system daemons # # If 'libvirt-guests.service' is enabled, then this must be # set to 'none' for system daemons to avoid dueling actions -#auto_shutdown_try_save = "all" +#auto_shutdown_try_save = "persistent"
As noted 'all' doesn't make sense for 'save' operation so I'd call that a bugfix. Perhaps even worhty of a separate patch.
# As above, but with a graceful shutdown action instead of # managed save. If managed save is enabled, shutdown will # be tried only on failure to perform managed save. # -# Defaults to "none" +# Defaults to "all" for session daemons and "none" for +# system daemons # # If 'libvirt-guests.service' is enabled, then this must be # set to 'none' for system daemons to avoid dueling actions -#auto_shutdown_try_shutdown = "none" +#auto_shutdown_try_shutdown = "all"
# As above, but with a forced poweroff instead of managed # save. If managed save or graceful shutdown are enabled, # forced poweroff will be tried only on failure of the # other options. # -# Defaults to "none" +# Defaults to "all" for session daemons and "none" for +# system daemons. # # If 'libvirt-guests.service' is enabled, then this must be # set to 'none' for system daemons to avoid dueling actions -#auto_shutdown_powerdown = "none" +#auto_shutdown_poweroff = "all"
# If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index a57eccf569..76bb3bd888 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -313,9 +313,9 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; } else { - cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; - cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; - cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE; + cfg->autoShutdownTrySave = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_PERSISTENT; + cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; + cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; }
return g_steal_pointer(&cfg); diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index e7137f686f..3bc8104d7a 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -76,9 +76,9 @@ module Test_libvirtd_qemu = { "auto_dump_bypass_cache" = "0" } { "auto_start_bypass_cache" = "0" } { "auto_start_delay" = "0" } -{ "auto_shutdown_try_save" = "all" } -{ "auto_shutdown_try_shutdown" = "none" } -{ "auto_shutdown_poweroff" = "none" } +{ "auto_shutdown_try_save" = "persistent" } +{ "auto_shutdown_try_shutdown" = "all" } +{ "auto_shutdown_poweroff" = "all" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } { "set_process_name" = "1" }
For the rest: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Fri, Jan 31, 2025 at 10:20:23AM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:45 +0000, Daniel P. Berrangé wrote:
Currently the session daemon will try a managed save on all VMs, leaving them running if that fails.
This limits the managed save just to persistent VMs, as there will usually not be any way to restore transient VMs later.
It also enables graceful shutdown and then forced poweroff, should save fail for some reason.
These new defaults can be overridden in the config file if needed.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 2 +- src/qemu/qemu.conf.in | 14 ++++++++------ src/qemu/qemu_conf.c | 6 +++--- src/qemu/test_libvirtd_qemu.aug.in | 6 +++--- 4 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index e465a231fc..605604a01a 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -100,7 +100,7 @@ module Libvirtd_qemu = | int_entry "auto_start_delay" | str_entry "auto_shutdown_try_save" | str_entry "auto_shutdown_try_shutdown" - | str_entry "auto_shutdown_powerdown" + | str_entry "auto_shutdown_poweroff"
let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper"
This hunk belongs to previous patch.
diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d8890c4c32..82eae2eecd 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -647,33 +647,35 @@ # * "transient" - only try to save transient running VMs # * "all" - try to save all running VMs # -# Defaults to "all" for session daemons and "none" +# Defaults to "persistent" for session daemons and "none" # for system daemons # # If 'libvirt-guests.service' is enabled, then this must be # set to 'none' for system daemons to avoid dueling actions -#auto_shutdown_try_save = "all" +#auto_shutdown_try_save = "persistent"
As noted 'all' doesn't make sense for 'save' operation so I'd call that a bugfix. Perhaps even worhty of a separate patch.
This can be naturally fixed at the point where we make this configurable 2 patches earlier. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Allow users to control how many seconds libvirt waits for QEMU shutdown before force powering off a guest. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 4 ++++ src/qemu/qemu_conf.c | 4 ++++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 12 insertions(+) diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 605604a01a..8cb1b144b9 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -101,6 +101,7 @@ module Libvirtd_qemu = | str_entry "auto_shutdown_try_save" | str_entry "auto_shutdown_try_shutdown" | str_entry "auto_shutdown_poweroff" + | int_entry "auto_shutdown_wait" let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index 82eae2eecd..9287196c42 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -677,6 +677,10 @@ # set to 'none' for system daemons to avoid dueling actions #auto_shutdown_poweroff = "all" +# How may seconds to wait for running VMs to gracefully shutdown +# when 'auto_shutdown_try_shutdown' is enabled +#auto_shutdown_wait = 30 + # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 76bb3bd888..7ec682e533 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -692,6 +692,10 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, return -1; } + if (virConfGetValueInt(conf, "auto_shutdown_wait", + &cfg->autoShutdownWait) < 0) + return -1; + return 0; } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 5d9ace6dcc..b8f6be110d 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -204,6 +204,7 @@ struct _virQEMUDriverConfig { int autoShutdownTrySave; int autoShutdownTryShutdown; int autoShutdownPoweroff; + int autoShutdownWait; char *lockManagerName; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 641b45fd8f..baa0d51244 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -950,6 +950,7 @@ qemuStateStop(void) .trySave = cfg->autoShutdownTrySave, .tryShutdown = cfg->autoShutdownTryShutdown, .poweroff = cfg->autoShutdownPoweroff, + .waitShutdownSecs = cfg->autoShutdownWait, }; virDomainDriverAutoShutdown(&ascfg); diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index 3bc8104d7a..4c6de31199 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -79,6 +79,7 @@ module Test_libvirtd_qemu = { "auto_shutdown_try_save" = "persistent" } { "auto_shutdown_try_shutdown" = "all" } { "auto_shutdown_poweroff" = "all" } +{ "auto_shutdown_wait" = "30" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } { "set_process_name" = "1" } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:46 +0000, Daniel P. Berrangé wrote:
Allow users to control how many seconds libvirt waits for QEMU shutdown before force powering off a guest.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 4 ++++ src/qemu/qemu_conf.c | 4 ++++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 12 insertions(+)
diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 605604a01a..8cb1b144b9 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -101,6 +101,7 @@ module Libvirtd_qemu = | str_entry "auto_shutdown_try_save" | str_entry "auto_shutdown_try_shutdown" | str_entry "auto_shutdown_poweroff" + | int_entry "auto_shutdown_wait"
let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index 82eae2eecd..9287196c42 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -677,6 +677,10 @@ # set to 'none' for system daemons to avoid dueling actions #auto_shutdown_poweroff = "all"
+# How may seconds to wait for running VMs to gracefully shutdown +# when 'auto_shutdown_try_shutdown' is enabled +#auto_shutdown_wait = 30
This does say what the default is, but doesn't make it clear that 0 will be converted to 30, while 1 will not.
+ # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 76bb3bd888..7ec682e533 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -692,6 +692,10 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, return -1; }
+ if (virConfGetValueInt(conf, "auto_shutdown_wait", + &cfg->autoShutdownWait) < 0) + return -1; + return 0; }
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 5d9ace6dcc..b8f6be110d 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -204,6 +204,7 @@ struct _virQEMUDriverConfig { int autoShutdownTrySave; int autoShutdownTryShutdown; int autoShutdownPoweroff; + int autoShutdownWait;
Consider using unsigned as negative 'wait' doesn't make sense.
char *lockManagerName;
With the description clearly mentioning that 0 will not work: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Bypassing cache can make save performance more predictable and avoids trashing the OS cache with data that will not be read again. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 7 +++++-- src/hypervisor/domain_driver.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 4fecaf7e5c..867ee1ae2a 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -725,9 +725,9 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autofree bool *transient = NULL; VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" - "waitShutdownSecs=%d", + "waitShutdownSecs=%d saveBypassCache=%d", cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, - cfg->waitShutdownSecs); + cfg->waitShutdownSecs, cfg->saveBypassCache); /* * Ideally guests will shutdown in a few seconds, but it would @@ -784,6 +784,9 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) if (state == VIR_DOMAIN_PAUSED) flags[i] = VIR_DOMAIN_SAVE_PAUSED; } + if (cfg->saveBypassCache) + flags[i] |= VIR_DOMAIN_SAVE_BYPASS_CACHE; + virDomainSuspend(domains[i]); } diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index acb7a41b5d..16832f2449 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -108,6 +108,7 @@ typedef struct _virDomainDriverAutoShutdownConfig { virDomainDriverAutoShutdownScope tryShutdown; virDomainDriverAutoShutdownScope poweroff; int waitShutdownSecs; + bool saveBypassCache; } virDomainDriverAutoShutdownConfig; void virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:47 +0000, Daniel P. Berrangé wrote:
Bypassing cache can make save performance more predictable and avoids trashing the OS cache with data that will not be read again.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 7 +++++-- src/hypervisor/domain_driver.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

When doing managed save of VMs, triggered by OS shutdown, it may be desirable to control cache usage. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 8 ++++++++ src/qemu/qemu_conf.c | 3 +++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 15 insertions(+) diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 8cb1b144b9..9fa6398d8d 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -102,6 +102,7 @@ module Libvirtd_qemu = | str_entry "auto_shutdown_try_shutdown" | str_entry "auto_shutdown_poweroff" | int_entry "auto_shutdown_wait" + | bool_entry "auto_save_bypass_cache" let process_entry = str_entry "hugetlbfs_mount" | str_entry "bridge_helper" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index 9287196c42..d26ab4cb57 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -681,6 +681,14 @@ # when 'auto_shutdown_try_shutdown' is enabled #auto_shutdown_wait = 30 +# When a domain is configured to be auto-saved on shutdown, enabling +# this flag has the same effect as using the VIR_DOMAIN_SAVE_BYPASS_CACHE +# flag with the virDomainManagedSave API. That is, the system will +# avoid using the file system cache when writing any managed state +# file, but may cause slower operation. +# +#auto_save_bypass_cache = 0 + # If provided by the host and a hugetlbfs mount point is configured, # a guest may request huge page backing. When this mount point is # unspecified here, determination of a host mount point in /proc/mounts diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 7ec682e533..af09920869 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -695,6 +695,9 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, if (virConfGetValueInt(conf, "auto_shutdown_wait", &cfg->autoShutdownWait) < 0) return -1; + if (virConfGetValueBool(conf, "auto_save_bypass_cache", + &cfg->autoSaveBypassCache) < 0) + return -1; return 0; } diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index b8f6be110d..9818a8cbb5 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -205,6 +205,7 @@ struct _virQEMUDriverConfig { int autoShutdownTryShutdown; int autoShutdownPoweroff; int autoShutdownWait; + bool autoSaveBypassCache; char *lockManagerName; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index baa0d51244..8729b0fab0 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -951,6 +951,7 @@ qemuStateStop(void) .tryShutdown = cfg->autoShutdownTryShutdown, .poweroff = cfg->autoShutdownPoweroff, .waitShutdownSecs = cfg->autoShutdownWait, + .saveBypassCache = cfg->autoSaveBypassCache, }; virDomainDriverAutoShutdown(&ascfg); diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index 4c6de31199..65d0c20fe1 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -80,6 +80,7 @@ module Test_libvirtd_qemu = { "auto_shutdown_try_shutdown" = "all" } { "auto_shutdown_poweroff" = "all" } { "auto_shutdown_wait" = "30" } +{ "auto_save_bypass_cache" = "0" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } { "set_process_name" = "1" } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:48 +0000, Daniel P. Berrangé wrote:
When doing managed save of VMs, triggered by OS shutdown, it may be desirable to control cache usage.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 8 ++++++++ src/qemu/qemu_conf.c | 3 +++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 15 insertions(+)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

When a domain is marked for autostart, it will be started on every subsequent host OS boot. There may be times when it is desirable to mark a domain to be autostarted, on the next boot only. Thus we add virDomainSetAutostartOnce / virDomainGetAutostartOnce. An alternative would have been to overload the existing virDomainSetAutostart method, to accept values '1' or '2' for the autostart flag. This was not done because it is expected that language bindings will have mapped the current autostart flag to a boolean, and thus turning it into an enum would create a compatibility problem. A further alternative would have been to create a new method virDomainSetAutostartFlags, with a VIR_DOMAIN_AUTOSTART_ONCE flag defined. This was not done because it is felt desirable to clearly separate the two flags. Setting the "once" flag should not interfere with existing autostart setting, whether it is enabled or disabled currently. The 'virsh autostart' command, however, is still overloaded by just adding a --once flag, while current state is added to 'virsh dominfo'. No ability to filter by 'autostart once' status is added to the domain list APIs. The most common use of autostart once will be to automatically set it on host shutdown, and it be cleared on host startup. Thus there would rarely be scenarios in which a running app will need to filter on this new flag. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-domain.h | 4 ++ src/driver-hypervisor.h | 10 ++++ src/libvirt-domain.c | 87 ++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 6 +++ src/remote/remote_driver.c | 2 + src/remote/remote_protocol.x | 30 ++++++++++- src/remote_protocol-structs | 12 +++++ src/rpc/gendispatch.pl | 4 +- tools/virsh-domain-monitor.c | 5 ++ tools/virsh-domain.c | 39 ++++++++++---- 10 files changed, 187 insertions(+), 12 deletions(-) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 2a4b81f4df..6bd3200f37 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -2398,6 +2398,10 @@ int virDomainGetAutostart (virDomainPtr domain, int *autostart); int virDomainSetAutostart (virDomainPtr domain, int autostart); +int virDomainGetAutostartOnce(virDomainPtr domain, + int *autostart); +int virDomainSetAutostartOnce(virDomainPtr domain, + int autostart); /** * virVcpuState: diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index 4ce8da078d..c05c71b9fe 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -478,6 +478,14 @@ typedef int (*virDrvDomainSetAutostart)(virDomainPtr domain, int autostart); +typedef int +(*virDrvDomainGetAutostartOnce)(virDomainPtr domain, + int *autostart); + +typedef int +(*virDrvDomainSetAutostartOnce)(virDomainPtr domain, + int autostart); + typedef char * (*virDrvDomainGetSchedulerType)(virDomainPtr domain, int *nparams); @@ -1564,6 +1572,8 @@ struct _virHypervisorDriver { virDrvDomainDetachDeviceAlias domainDetachDeviceAlias; virDrvDomainGetAutostart domainGetAutostart; virDrvDomainSetAutostart domainSetAutostart; + virDrvDomainGetAutostartOnce domainGetAutostartOnce; + virDrvDomainSetAutostartOnce domainSetAutostartOnce; virDrvDomainGetSchedulerType domainGetSchedulerType; virDrvDomainGetSchedulerParameters domainGetSchedulerParameters; virDrvDomainGetSchedulerParametersFlags domainGetSchedulerParametersFlags; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index e8e5379672..7c730fc8de 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -7341,6 +7341,93 @@ virDomainSetAutostart(virDomainPtr domain, } +/** + * virDomainGetAutostartOnce: + * @domain: a domain object + * @autostart: the value returned + * + * Provides a boolean value indicating whether the domain + * is configured to be automatically started the next time + * the host machine boots only. + * + * Returns -1 in case of error, 0 in case of success + * + * Since: 11.0.0 + */ +int +virDomainGetAutostartOnce(virDomainPtr domain, + int *autostart) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "autostart=%p", autostart); + + virResetLastError(); + + virCheckDomainReturn(domain, -1); + virCheckNonNullArgGoto(autostart, error); + + conn = domain->conn; + + if (conn->driver->domainGetAutostartOnce) { + int ret; + ret = conn->driver->domainGetAutostartOnce(domain, autostart); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(domain->conn); + return -1; +} + + +/** + * virDomainSetAutostartOnce: + * @domain: a domain object + * @autostart: whether the domain should be automatically started 0 or 1 + * + * Configure the domain to be automatically started + * the next time the host machine boots only. + * + * Returns -1 in case of error, 0 in case of success + * + * Since: 11.0.0 + */ +int +virDomainSetAutostartOnce(virDomainPtr domain, + int autostart) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "autostart=%d", autostart); + + virResetLastError(); + + virCheckDomainReturn(domain, -1); + conn = domain->conn; + + virCheckReadOnlyGoto(conn->flags, error); + + if (conn->driver->domainSetAutostartOnce) { + int ret; + ret = conn->driver->domainSetAutostartOnce(domain, autostart); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + + error: + virDispatchError(domain->conn); + return -1; +} + + /** * virDomainInjectNMI: * @domain: pointer to domain object, or NULL for Domain0 diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 7a3492d9d7..d01ac418d2 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -948,4 +948,10 @@ LIBVIRT_10.2.0 { virDomainGraphicsReload; } LIBVIRT_10.1.0; +LIBVIRT_11.0.0 { + global: + virDomainGetAutostartOnce; + virDomainSetAutostartOnce; +} LIBVIRT_10.2.0; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 307f9ca945..a469209a9e 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7672,6 +7672,8 @@ static virHypervisorDriver hypervisor_driver = { .domainDetachDeviceAlias = remoteDomainDetachDeviceAlias, /* 4.4.0 */ .domainGetAutostart = remoteDomainGetAutostart, /* 0.3.0 */ .domainSetAutostart = remoteDomainSetAutostart, /* 0.3.0 */ + .domainGetAutostartOnce = remoteDomainGetAutostartOnce, /* 11.0.0 */ + .domainSetAutostartOnce = remoteDomainSetAutostartOnce, /* 11.0.0 */ .domainGetSchedulerType = remoteDomainGetSchedulerType, /* 0.3.0 */ .domainGetSchedulerParameters = remoteDomainGetSchedulerParameters, /* 0.3.0 */ .domainGetSchedulerParametersFlags = remoteDomainGetSchedulerParametersFlags, /* 0.9.2 */ diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 41c045ff78..4f873cb4cf 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -3973,6 +3973,20 @@ struct remote_domain_fd_associate_args { remote_nonnull_string name; unsigned int flags; }; + +struct remote_domain_get_autostart_once_args { + remote_nonnull_domain dom; +}; + +struct remote_domain_get_autostart_once_ret { + int autostart; +}; + +struct remote_domain_set_autostart_once_args { + remote_nonnull_domain dom; + int autostart; +}; + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -7048,5 +7062,19 @@ enum remote_procedure { * @generate: both * @acl: domain:write */ - REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448 + REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448, + + /** + * @generate: both + * @priority: high + * @acl: domain:read + */ + REMOTE_PROC_DOMAIN_GET_AUTOSTART_ONCE = 449, + + /** + * @generate: both + * @priority: high + * @acl: domain:write + */ + REMOTE_PROC_DOMAIN_SET_AUTOSTART_ONCE = 450 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 4d3dc2d249..6337a082ce 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -3306,6 +3306,16 @@ struct remote_domain_fd_associate_args { remote_nonnull_string name; u_int flags; }; +struct remote_domain_get_autostart_once_args { + remote_nonnull_domain dom; +}; +struct remote_domain_get_autostart_once_ret { + int autostart; +}; +struct remote_domain_set_autostart_once_args { + remote_nonnull_domain dom; + int autostart; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -3755,4 +3765,6 @@ enum remote_procedure { REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 446, REMOTE_PROC_NODE_DEVICE_UPDATE = 447, REMOTE_PROC_DOMAIN_GRAPHICS_RELOAD = 448, + REMOTE_PROC_DOMAIN_GET_AUTOSTART_ONCE = 449, + REMOTE_PROC_DOMAIN_SET_AUTOSTART_ONCE = 450, }; diff --git a/src/rpc/gendispatch.pl b/src/rpc/gendispatch.pl index 724a6aed6e..f9fae39fb1 100755 --- a/src/rpc/gendispatch.pl +++ b/src/rpc/gendispatch.pl @@ -839,7 +839,7 @@ elsif ($mode eq "server") { push(@ret_list, "ret->$1 = $1;"); $single_ret_var = $1; - if ($call->{ProcName} =~ m/GetAutostart$/) { + if ($call->{ProcName} =~ m/GetAutostart(Once)?$/) { $single_ret_by_ref = 1; } else { $single_ret_by_ref = 0; @@ -1650,7 +1650,7 @@ elsif ($mode eq "client") { } elsif ($ret_member =~ m/^int (\S+);/) { my $arg_name = $1; - if ($call->{ProcName} =~ m/GetAutostart$/) { + if ($call->{ProcName} =~ m/GetAutostart(Once)?$/) { push(@args_list, "int *$arg_name"); push(@ret_list, "if ($arg_name) *$arg_name = ret.$arg_name;"); push(@ret_list, "rv = 0;"); diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index 9ee9090c11..fd5cc80ec2 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -1236,6 +1236,11 @@ cmdDominfo(vshControl *ctl, const vshCmd *cmd) vshPrint(ctl, "%-15s %s\n", _("Autostart:"), autostart ? _("enable") : _("disable")); } + /* Check and display whether the domain autostarts next boot or not */ + if (!virDomainGetAutostartOnce(dom, &autostart)) { + vshPrint(ctl, "%-15s %s\n", _("Autostart Once:"), + autostart ? _("enable") : _("disable")); + } has_managed_save = virDomainHasManagedSaveImage(dom, 0); if (has_managed_save < 0) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 546db955a9..8a0111f0af 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1158,6 +1158,10 @@ static const vshCmdOptDef opts_autostart[] = { .type = VSH_OT_BOOL, .help = N_("disable autostarting") }, + {.name = "once", + .type = VSH_OT_BOOL, + .help = N_("control next boot state") + }, {.name = NULL} }; @@ -1167,24 +1171,41 @@ cmdAutostart(vshControl *ctl, const vshCmd *cmd) g_autoptr(virshDomain) dom = NULL; const char *name; int autostart; + int once; if (!(dom = virshCommandOptDomain(ctl, cmd, &name))) return false; autostart = !vshCommandOptBool(cmd, "disable"); + once = vshCommandOptBool(cmd, "once"); + + if (once) { + if (virDomainSetAutostartOnce(dom, autostart) < 0) { + if (autostart) + vshError(ctl, _("Failed to mark domain '%1$s' as autostarted on next boot"), name); + else + vshError(ctl, _("Failed to unmark domain '%1$s' as autostarted on next boot"), name); + return false; + } - if (virDomainSetAutostart(dom, autostart) < 0) { if (autostart) - vshError(ctl, _("Failed to mark domain '%1$s' as autostarted"), name); + vshPrintExtra(ctl, _("Domain '%1$s' marked as autostarted on next boot\n"), name); else - vshError(ctl, _("Failed to unmark domain '%1$s' as autostarted"), name); - return false; - } + vshPrintExtra(ctl, _("Domain '%1$s' unmarked as autostarted on next boot\n"), name); + } else { + if (virDomainSetAutostart(dom, autostart) < 0) { + if (autostart) + vshError(ctl, _("Failed to mark domain '%1$s' as autostarted"), name); + else + vshError(ctl, _("Failed to unmark domain '%1$s' as autostarted"), name); + return false; + } - if (autostart) - vshPrintExtra(ctl, _("Domain '%1$s' marked as autostarted\n"), name); - else - vshPrintExtra(ctl, _("Domain '%1$s' unmarked as autostarted\n"), name); + if (autostart) + vshPrintExtra(ctl, _("Domain '%1$s' marked as autostarted\n"), name); + else + vshPrintExtra(ctl, _("Domain '%1$s' unmarked as autostarted\n"), name); + } return true; } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:49 +0000, Daniel P. Berrangé wrote:
When a domain is marked for autostart, it will be started on every subsequent host OS boot. There may be times when it is desirable to mark a domain to be autostarted, on the next boot only.
Thus we add virDomainSetAutostartOnce / virDomainGetAutostartOnce.
An alternative would have been to overload the existing virDomainSetAutostart method, to accept values '1' or '2' for the autostart flag. This was not done because it is expected that language bindings will have mapped the current autostart flag to a boolean, and thus turning it into an enum would create a compatibility problem.
A further alternative would have been to create a new method virDomainSetAutostartFlags, with a VIR_DOMAIN_AUTOSTART_ONCE flag defined. This was not done because it is felt desirable to clearly separate the two flags. Setting the "once" flag should not interfere with existing autostart setting, whether it is enabled or disabled currently.
The 'virsh autostart' command, however, is still overloaded by just adding a --once flag, while current state is added to 'virsh dominfo'.
No ability to filter by 'autostart once' status is added to the domain list APIs. The most common use of autostart once will be to automatically set it on host shutdown, and it be cleared on host startup. Thus there would rarely be scenarios in which a running app will need to filter on this new flag.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-domain.h | 4 ++ src/driver-hypervisor.h | 10 ++++ src/libvirt-domain.c | 87 ++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 6 +++ src/remote/remote_driver.c | 2 + src/remote/remote_protocol.x | 30 ++++++++++- src/remote_protocol-structs | 12 +++++ src/rpc/gendispatch.pl | 4 +- tools/virsh-domain-monitor.c | 5 ++ tools/virsh-domain.c | 39 ++++++++++---- 10 files changed, 187 insertions(+), 12 deletions(-)
Don't forget to bump all the version tags to 11.1.0 ...
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 7a3492d9d7..d01ac418d2 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -948,4 +948,10 @@ LIBVIRT_10.2.0 { virDomainGraphicsReload; } LIBVIRT_10.1.0;
+LIBVIRT_11.0.0 {
Especially this one.
+ global: + virDomainGetAutostartOnce; + virDomainSetAutostartOnce; +} LIBVIRT_10.2.0; + # .... define new API here using predicted next version number ....
[...]
diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index 9ee9090c11..fd5cc80ec2 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -1236,6 +1236,11 @@ cmdDominfo(vshControl *ctl, const vshCmd *cmd) vshPrint(ctl, "%-15s %s\n", _("Autostart:"), autostart ? _("enable") : _("disable")); } + /* Check and display whether the domain autostarts next boot or not */ + if (!virDomainGetAutostartOnce(dom, &autostart)) { + vshPrint(ctl, "%-15s %s\n", _("Autostart Once:"), + autostart ? _("enable") : _("disable")); + }
I'd also suggest reseting the last stored error as spurious 'unsupported' errors can arise when communincating with old daemon.
has_managed_save = virDomainHasManagedSaveImage(dom, 0); if (has_managed_save < 0)

This is maintained in the same way as the autostart flag, using a symlink. The difference is that instead of '.xml', the symlink suffix is '.xml.once'. The link is also deleted immediately after it has been read. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/conf/domain_conf.c | 6 +++++- src/conf/domain_conf.h | 1 + src/conf/virdomainobjlist.c | 7 ++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index af88d0bcfd..4a794d1f71 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -29083,13 +29083,17 @@ virDomainDeleteConfig(const char *configDir, { g_autofree char *configFile = NULL; g_autofree char *autostartLink = NULL; + g_autofree char *autostartOnceLink = NULL; configFile = virDomainConfigFile(configDir, dom->def->name); autostartLink = virDomainConfigFile(autostartDir, dom->def->name); + autostartOnceLink = g_strdup_printf("%s.once", autostartLink); - /* Not fatal if this doesn't work */ + /* Not fatal if these don't work */ unlink(autostartLink); + unlink(autostartOnceLink); dom->autostart = 0; + dom->autostartOnce = 0; if (unlink(configFile) < 0 && errno != ENOENT) { diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 9f7c28343f..f30143e704 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3282,6 +3282,7 @@ struct _virDomainObj { virDomainStateReason state; unsigned int autostart : 1; + unsigned int autostartOnce : 1; unsigned int persistent : 1; unsigned int updated : 1; unsigned int removing : 1; diff --git a/src/conf/virdomainobjlist.c b/src/conf/virdomainobjlist.c index 72207450c5..ba2f9f544d 100644 --- a/src/conf/virdomainobjlist.c +++ b/src/conf/virdomainobjlist.c @@ -487,9 +487,10 @@ virDomainObjListLoadConfig(virDomainObjList *doms, { g_autofree char *configFile = NULL; g_autofree char *autostartLink = NULL; + g_autofree char *autostartOnceLink = NULL; g_autoptr(virDomainDef) def = NULL; virDomainObj *dom; - int autostart; + int autostart, autostartOnce; g_autoptr(virDomainDef) oldDef = NULL; configFile = virDomainConfigFile(configDir, name); @@ -500,13 +501,17 @@ virDomainObjListLoadConfig(virDomainObjList *doms, return NULL; autostartLink = virDomainConfigFile(autostartDir, name); + autostartOnceLink = g_strdup_printf("%s.once", autostartLink); autostart = virFileLinkPointsTo(autostartLink, configFile); + autostartOnce = virFileLinkPointsTo(autostartOnceLink, configFile); + unlink(autostartOnceLink); if (!(dom = virDomainObjListAddLocked(doms, &def, xmlopt, 0, &oldDef))) return NULL; dom->autostart = autostart; + dom->autostartOnce = autostartOnce; if (notify) (*notify)(dom, oldDef == NULL, opaque); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:50 +0000, Daniel P. Berrangé wrote:
This is maintained in the same way as the autostart flag, using a symlink. The difference is that instead of '.xml', the symlink suffix is '.xml.once'. The link is also deleted immediately after it has been read.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/conf/domain_conf.c | 6 +++++- src/conf/domain_conf.h | 1 + src/conf/virdomainobjlist.c | 7 ++++++- 3 files changed, 12 insertions(+), 2 deletions(-)
[...]
diff --git a/src/conf/virdomainobjlist.c b/src/conf/virdomainobjlist.c index 72207450c5..ba2f9f544d 100644 --- a/src/conf/virdomainobjlist.c +++ b/src/conf/virdomainobjlist.c @@ -487,9 +487,10 @@ virDomainObjListLoadConfig(virDomainObjList *doms, { g_autofree char *configFile = NULL; g_autofree char *autostartLink = NULL; + g_autofree char *autostartOnceLink = NULL; g_autoptr(virDomainDef) def = NULL; virDomainObj *dom; - int autostart; + int autostart, autostartOnce;
Preferrably one per line.
g_autoptr(virDomainDef) oldDef = NULL;
configFile = virDomainConfigFile(configDir, name);
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Wed, Jan 08, 2025 at 19:42:50 +0000, Daniel P. Berrangé wrote:
This is maintained in the same way as the autostart flag, using a symlink. The difference is that instead of '.xml', the symlink suffix is '.xml.once'. The link is also deleted immediately after it has been read.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/conf/domain_conf.c | 6 +++++- src/conf/domain_conf.h | 1 + src/conf/virdomainobjlist.c | 7 ++++++- 3 files changed, 12 insertions(+), 2 deletions(-)
[...]
diff --git a/src/conf/virdomainobjlist.c b/src/conf/virdomainobjlist.c index 72207450c5..ba2f9f544d 100644 --- a/src/conf/virdomainobjlist.c +++ b/src/conf/virdomainobjlist.c
[...]
@@ -500,13 +501,17 @@ virDomainObjListLoadConfig(virDomainObjList *doms, return NULL;
autostartLink = virDomainConfigFile(autostartDir, name); + autostartOnceLink = g_strdup_printf("%s.once", autostartLink);
autostart = virFileLinkPointsTo(autostartLink, configFile); + autostartOnce = virFileLinkPointsTo(autostartOnceLink, configFile); + unlink(autostartOnceLink);
Actually this will not work properly if you restart the daemon before rebooting the host as configs will be loaded, thus the file unlinked but then the only place this is recorded is in memory. The unlinking of the file needs to happen only after autostart was actually attempted, which should be reasonably doable now that's centralised.

When performing auto-shutdown of running domains, there is now the option to mark them as "autostart once", so that their state is restored on next boot. This applies on top of the traditional autostart flag. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 27 ++++++++++++++++++++++----- src/hypervisor/domain_driver.h | 1 + 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 867ee1ae2a..2f89b8c841 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -677,10 +677,11 @@ virDomainDriverAutoStartOne(virDomainObj *vm, virObjectLock(vm); virObjectRef(vm); - VIR_DEBUG("Autostart %s: autostart=%d", - vm->def->name, vm->autostart); + VIR_DEBUG("Autostart %s: autostart=%d autostartOnce=%d", + vm->def->name, vm->autostart, vm->autostartOnce); - if (vm->autostart && !virDomainObjIsActive(vm)) { + if ((vm->autostart || vm->autostartOnce) + && !virDomainObjIsActive(vm)) { virResetLastError(); if (state->cfg->delayMS) { if (!state->first) { @@ -691,6 +692,7 @@ virDomainDriverAutoStartOne(virDomainObj *vm, } state->cfg->callback(vm, state->cfg->opaque); + vm->autostartOnce = 0; } virDomainObjEndAPI(&vm); @@ -725,9 +727,9 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autofree bool *transient = NULL; VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" - "waitShutdownSecs=%d saveBypassCache=%d", + "waitShutdownSecs=%d saveBypassCache=%d autoRestore=%d", cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, - cfg->waitShutdownSecs, cfg->saveBypassCache); + cfg->waitShutdownSecs, cfg->saveBypassCache, cfg->autoRestore); /* * Ideally guests will shutdown in a few seconds, but it would @@ -763,6 +765,21 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) for (i = 0; i < numDomains; i++) { if (virDomainIsPersistent(domains[i]) == 0) transient[i] = true; + + if (cfg->autoRestore) { + if (transient[i]) { + VIR_DEBUG("Cannot auto-restore transient VM %s", + virDomainGetName(domains[i])); + } else { + VIR_DEBUG("Mark %s for autostart on next boot", + virDomainGetName(domains[i])); + if (virDomainSetAutostartOnce(domains[i], 1) < 0) { + VIR_WARN("Unable to mark domain '%s' for auto restore: %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + } + } + } } if (cfg->trySave != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index 16832f2449..72152f8054 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -109,6 +109,7 @@ typedef struct _virDomainDriverAutoShutdownConfig { virDomainDriverAutoShutdownScope poweroff; int waitShutdownSecs; bool saveBypassCache; + bool autoRestore; } virDomainDriverAutoShutdownConfig; void virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:51 +0000, Daniel P. Berrangé wrote:
When performing auto-shutdown of running domains, there is now the option to mark them as "autostart once", so that their state is restored on next boot. This applies on top of the traditional autostart flag.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 27 ++++++++++++++++++++++----- src/hypervisor/domain_driver.h | 1 + 2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 867ee1ae2a..2f89b8c841 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -677,10 +677,11 @@ virDomainDriverAutoStartOne(virDomainObj *vm, virObjectLock(vm); virObjectRef(vm);
- VIR_DEBUG("Autostart %s: autostart=%d", - vm->def->name, vm->autostart); + VIR_DEBUG("Autostart %s: autostart=%d autostartOnce=%d", + vm->def->name, vm->autostart, vm->autostartOnce);
- if (vm->autostart && !virDomainObjIsActive(vm)) { + if ((vm->autostart || vm->autostartOnce) + && !virDomainObjIsActive(vm)) { virResetLastError(); if (state->cfg->delayMS) { if (!state->first) { @@ -691,6 +692,7 @@ virDomainDriverAutoStartOne(virDomainObj *vm, }
state->cfg->callback(vm, state->cfg->opaque); + vm->autostartOnce = 0;
Per comment in previous patch the '.once' file needs to be unlinked here instead.
}
virDomainObjEndAPI(&vm);
Also both of the hunks above really look like they belong to the previous patch as they implement the auto-start-once feature while this commit is now using that feature to do 'restore' of the state at shutdown.
@@ -725,9 +727,9 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) g_autofree bool *transient = NULL;
VIR_DEBUG("Run autoshutdown uri=%s trySave=%d tryShutdown=%d poweroff=%d" - "waitShutdownSecs=%d saveBypassCache=%d", + "waitShutdownSecs=%d saveBypassCache=%d autoRestore=%d", cfg->uri, cfg->trySave, cfg->tryShutdown, cfg->poweroff, - cfg->waitShutdownSecs, cfg->saveBypassCache); + cfg->waitShutdownSecs, cfg->saveBypassCache, cfg->autoRestore);
/* * Ideally guests will shutdown in a few seconds, but it would @@ -763,6 +765,21 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) for (i = 0; i < numDomains; i++) { if (virDomainIsPersistent(domains[i]) == 0) transient[i] = true; + + if (cfg->autoRestore) { + if (transient[i]) { + VIR_DEBUG("Cannot auto-restore transient VM %s", + virDomainGetName(domains[i])); + } else { + VIR_DEBUG("Mark %s for autostart on next boot", + virDomainGetName(domains[i])); + if (virDomainSetAutostartOnce(domains[i], 1) < 0) { + VIR_WARN("Unable to mark domain '%s' for auto restore: %s", + virDomainGetName(domains[i]), + virGetLastErrorMessage()); + } + } + } }
if (cfg->trySave != VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_NONE) { diff --git a/src/hypervisor/domain_driver.h b/src/hypervisor/domain_driver.h index 16832f2449..72152f8054 100644 --- a/src/hypervisor/domain_driver.h +++ b/src/hypervisor/domain_driver.h @@ -109,6 +109,7 @@ typedef struct _virDomainDriverAutoShutdownConfig { virDomainDriverAutoShutdownScope poweroff; int waitShutdownSecs; bool saveBypassCache; + bool autoRestore; } virDomainDriverAutoShutdownConfig;
void virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg);
Wit the comments above addressed: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/qemu_driver.c | 95 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8729b0fab0..84bde503dd 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7778,6 +7778,99 @@ static int qemuDomainSetAutostart(virDomainPtr dom, } +static int qemuDomainGetAutostartOnce(virDomainPtr dom, + int *autostart) +{ + virDomainObj *vm; + int ret = -1; + + if (!(vm = qemuDomainObjFromDomain(dom))) + goto cleanup; + + if (virDomainGetAutostartOnceEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + *autostart = vm->autostartOnce; + ret = 0; + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + +static int qemuDomainSetAutostartOnce(virDomainPtr dom, + int autostart) +{ + virQEMUDriver *driver = dom->conn->privateData; + virDomainObj *vm; + g_autofree char *configFile = NULL; + g_autofree char *autostartLink = NULL; + g_autofree char *autostartOnceLink = NULL; + int ret = -1; + g_autoptr(virQEMUDriverConfig) cfg = NULL; + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainSetAutostartOnceEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (!vm->persistent) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cannot set autostart for transient domain")); + goto cleanup; + } + + autostart = (autostart != 0); + + if (vm->autostartOnce != autostart) { + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + configFile = virDomainConfigFile(cfg->configDir, vm->def->name); + autostartLink = virDomainConfigFile(cfg->autostartDir, vm->def->name); + autostartOnceLink = g_strdup_printf("%s.once", autostartLink); + + if (autostart) { + if (g_mkdir_with_parents(cfg->autostartDir, 0777) < 0) { + virReportSystemError(errno, + _("cannot create autostart directory %1$s"), + cfg->autostartDir); + goto endjob; + } + + if (symlink(configFile, autostartOnceLink) < 0) { + virReportSystemError(errno, + _("Failed to create symlink '%1$s' to '%2$s'"), + autostartOnceLink, configFile); + goto endjob; + } + } else { + if (unlink(autostartOnceLink) < 0 && + errno != ENOENT && + errno != ENOTDIR) { + virReportSystemError(errno, + _("Failed to delete symlink '%1$s'"), + autostartOnceLink); + goto endjob; + } + } + + vm->autostartOnce = autostart; + + endjob: + virDomainObjEndJob(vm); + } + ret = 0; + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + static char *qemuDomainGetSchedulerType(virDomainPtr dom, int *nparams) { @@ -20257,6 +20350,8 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainSetLaunchSecurityState = qemuDomainSetLaunchSecurityState, /* 8.0.0 */ .domainFDAssociate = qemuDomainFDAssociate, /* 9.0.0 */ .domainGraphicsReload = qemuDomainGraphicsReload, /* 10.2.0 */ + .domainGetAutostartOnce = qemuDomainGetAutostartOnce, /* 11.0.0 */ + .domainSetAutostartOnce = qemuDomainSetAutostartOnce, /* 11.0.0 */ }; -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:52 +0000, Daniel P. Berrangé wrote:
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/qemu_driver.c | 95 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8729b0fab0..84bde503dd 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7778,6 +7778,99 @@ static int qemuDomainSetAutostart(virDomainPtr dom, }
+static int qemuDomainGetAutostartOnce(virDomainPtr dom, + int *autostart)
Return type on separate line please.
+{ + virDomainObj *vm; + int ret = -1; + + if (!(vm = qemuDomainObjFromDomain(dom))) + goto cleanup; + + if (virDomainGetAutostartOnceEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + *autostart = vm->autostartOnce; + ret = 0; + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + +static int qemuDomainSetAutostartOnce(virDomainPtr dom, + int autostart)
Also here.
+{ + virQEMUDriver *driver = dom->conn->privateData; + virDomainObj *vm; + g_autofree char *configFile = NULL; + g_autofree char *autostartLink = NULL; + g_autofree char *autostartOnceLink = NULL; + int ret = -1; + g_autoptr(virQEMUDriverConfig) cfg = NULL; + + if (!(vm = qemuDomainObjFromDomain(dom))) + return -1; + + cfg = virQEMUDriverGetConfig(driver); + + if (virDomainSetAutostartOnceEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (!vm->persistent) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cannot set autostart for transient domain")); + goto cleanup; + } + + autostart = (autostart != 0); + + if (vm->autostartOnce != autostart) { + if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) + goto cleanup; + + configFile = virDomainConfigFile(cfg->configDir, vm->def->name); + autostartLink = virDomainConfigFile(cfg->autostartDir, vm->def->name); + autostartOnceLink = g_strdup_printf("%s.once", autostartLink); + + if (autostart) { + if (g_mkdir_with_parents(cfg->autostartDir, 0777) < 0) { + virReportSystemError(errno, + _("cannot create autostart directory %1$s"), + cfg->autostartDir); + goto endjob; + } + + if (symlink(configFile, autostartOnceLink) < 0) { + virReportSystemError(errno, + _("Failed to create symlink '%1$s' to '%2$s'"), + autostartOnceLink, configFile); + goto endjob; + } + } else { + if (unlink(autostartOnceLink) < 0 && + errno != ENOENT && + errno != ENOTDIR) { + virReportSystemError(errno, + _("Failed to delete symlink '%1$s'"), + autostartOnceLink); + goto endjob; + } + } + + vm->autostartOnce = autostart; + + endjob: + virDomainObjEndJob(vm); + } + ret = 0; + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + static char *qemuDomainGetSchedulerType(virDomainPtr dom, int *nparams) { @@ -20257,6 +20350,8 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainSetLaunchSecurityState = qemuDomainSetLaunchSecurityState, /* 8.0.0 */ .domainFDAssociate = qemuDomainFDAssociate, /* 9.0.0 */ .domainGraphicsReload = qemuDomainGraphicsReload, /* 10.2.0 */ + .domainGetAutostartOnce = qemuDomainGetAutostartOnce, /* 11.0.0 */ + .domainSetAutostartOnce = qemuDomainSetAutostartOnce, /* 11.0.0 */
11.1.0
};
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

If shutting down running VMs at host shutdown, it can be useful to automatically start them again on next boot. This adds a config parameter 'auto_shutdown_restore', which defaults to enabled, which leverages the autostart once feature. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 4 ++++ src/qemu/qemu_conf.c | 3 +++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 11 insertions(+) diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 9fa6398d8d..ee39da73b9 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -102,6 +102,7 @@ module Libvirtd_qemu = | str_entry "auto_shutdown_try_shutdown" | str_entry "auto_shutdown_poweroff" | int_entry "auto_shutdown_wait" + | bool_entry "auto_shutdown_restore" | bool_entry "auto_save_bypass_cache" let process_entry = str_entry "hugetlbfs_mount" diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d26ab4cb57..ace4b20a0c 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -681,6 +681,10 @@ # when 'auto_shutdown_try_shutdown' is enabled #auto_shutdown_wait = 30 +# Whether VMs that are automatically shutdown are set to +# automatically restore on next boot +#auto_shutdown_restore = 1 + # When a domain is configured to be auto-saved on shutdown, enabling # this flag has the same effect as using the VIR_DOMAIN_SAVE_BYPASS_CACHE # flag with the virDomainManagedSave API. That is, the system will diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index af09920869..458bcb43e6 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -317,6 +317,7 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged, cfg->autoShutdownTryShutdown = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; cfg->autoShutdownPoweroff = VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_ALL; } + cfg->autoShutdownRestore = true; return g_steal_pointer(&cfg); } @@ -695,6 +696,8 @@ virQEMUDriverConfigLoadSaveEntry(virQEMUDriverConfig *cfg, if (virConfGetValueInt(conf, "auto_shutdown_wait", &cfg->autoShutdownWait) < 0) return -1; + if (virConfGetValueBool(conf, "auto_shutdown_restore", &cfg->autoShutdownRestore) < 0) + return -1; if (virConfGetValueBool(conf, "auto_save_bypass_cache", &cfg->autoSaveBypassCache) < 0) return -1; diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 9818a8cbb5..24d7945efb 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -205,6 +205,7 @@ struct _virQEMUDriverConfig { int autoShutdownTryShutdown; int autoShutdownPoweroff; int autoShutdownWait; + bool autoShutdownRestore; bool autoSaveBypassCache; char *lockManagerName; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 84bde503dd..fde6f92761 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -952,6 +952,7 @@ qemuStateStop(void) .poweroff = cfg->autoShutdownPoweroff, .waitShutdownSecs = cfg->autoShutdownWait, .saveBypassCache = cfg->autoSaveBypassCache, + .autoRestore = cfg->autoShutdownRestore, }; virDomainDriverAutoShutdown(&ascfg); diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index 65d0c20fe1..922cb35db7 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -80,6 +80,7 @@ module Test_libvirtd_qemu = { "auto_shutdown_try_shutdown" = "all" } { "auto_shutdown_poweroff" = "all" } { "auto_shutdown_wait" = "30" } +{ "auto_shutdown_restore" = "1" } { "auto_save_bypass_cache" = "0" } { "hugetlbfs_mount" = "/dev/hugepages" } { "bridge_helper" = "qemu-bridge-helper" } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:53 +0000, Daniel P. Berrangé wrote:
If shutting down running VMs at host shutdown, it can be useful to automatically start them again on next boot. This adds a config parameter 'auto_shutdown_restore', which defaults to enabled, which leverages the autostart once feature.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf.in | 4 ++++ src/qemu/qemu_conf.c | 3 +++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/test_libvirtd_qemu.aug.in | 1 + 6 files changed, 11 insertions(+)
[...]
diff --git a/src/qemu/qemu.conf.in b/src/qemu/qemu.conf.in index d26ab4cb57..ace4b20a0c 100644 --- a/src/qemu/qemu.conf.in +++ b/src/qemu/qemu.conf.in @@ -681,6 +681,10 @@ # when 'auto_shutdown_try_shutdown' is enabled #auto_shutdown_wait = 30
+# Whether VMs that are automatically shutdown are set to
Consider: shutdown or saved
+# automatically restore on next boot +#auto_shutdown_restore = 1 + # When a domain is configured to be auto-saved on shutdown, enabling # this flag has the same effect as using the VIR_DOMAIN_SAVE_BYPASS_CACHE # flag with the virDomainManagedSave API. That is, the system will
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

Currently the remote daemon code is responsible for calling virStateStop in a background thread. The virNetDaemon code wants to synchronize with this during shutdown, however, so the virThreadPtr must be passed over. Even the limited synchronization done currently, however, is flawed and to fix this requires the virNetDaemon code to be responsible for calling virStateStop in a thread more directly. Thus the logic is moved over into virStateStop via a further callback to be registered by the remote daemon. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_remote.syms | 2 +- src/remote/remote_daemon.c | 40 ++------------------------ src/rpc/virnetdaemon.c | 58 ++++++++++++++++++++++++++++---------- src/rpc/virnetdaemon.h | 20 +++++++++++-- 4 files changed, 64 insertions(+), 56 deletions(-) diff --git a/src/libvirt_remote.syms b/src/libvirt_remote.syms index f0f90815cf..7e87b0bd2a 100644 --- a/src/libvirt_remote.syms +++ b/src/libvirt_remote.syms @@ -90,12 +90,12 @@ virNetDaemonIsPrivileged; virNetDaemonNew; virNetDaemonNewPostExecRestart; virNetDaemonPreExecRestart; +virNetDaemonPreserve; virNetDaemonQuit; virNetDaemonQuitExecRestart; virNetDaemonRemoveShutdownInhibition; virNetDaemonRun; virNetDaemonSetShutdownCallbacks; -virNetDaemonSetStateStopWorkerThread; virNetDaemonUpdateServices; diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index c4b930cb70..bf91ee5772 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -514,41 +514,6 @@ static void daemonInhibitCallback(bool inhibit, void *opaque) static GDBusConnection *sessionBus; static GDBusConnection *systemBus; -static void daemonStopWorker(void *opaque) -{ - virNetDaemon *dmn = opaque; - - VIR_DEBUG("Begin stop dmn=%p", dmn); - - ignore_value(virStateStop()); - - VIR_DEBUG("Completed stop dmn=%p", dmn); - - /* Exit daemon cleanly */ - virNetDaemonQuit(dmn); -} - - -/* We do this in a thread to not block the main loop */ -static void daemonStop(virNetDaemon *dmn) -{ - virThread *thr; - virObjectRef(dmn); - - thr = g_new0(virThread, 1); - - if (virThreadCreateFull(thr, true, - daemonStopWorker, - "daemon-stop", false, dmn) < 0) { - virObjectUnref(dmn); - g_free(thr); - return; - } - - virNetDaemonSetStateStopWorkerThread(dmn, &thr); -} - - static GDBusMessage * handleSessionMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, GDBusMessage *message, @@ -562,7 +527,7 @@ handleSessionMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, if (virGDBusMessageIsSignal(message, "org.freedesktop.DBus.Local", "Disconnected")) - daemonStop(dmn); + virNetDaemonPreserve(dmn); return message; } @@ -581,7 +546,7 @@ handleSystemMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, VIR_DEBUG("dmn=%p", dmn); - daemonStop(dmn); + virNetDaemonPreserve(dmn); } @@ -625,6 +590,7 @@ static void daemonRunStateInit(void *opaque) g_atomic_int_set(&driversInitialized, 1); virNetDaemonSetShutdownCallbacks(dmn, + virStateStop, virStateShutdownPrepare, virStateShutdownWait); diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index bb7d2ff9a0..19b19ff834 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -65,9 +65,10 @@ struct _virNetDaemon { GHashTable *servers; virJSONValue *srvObject; + virNetDaemonShutdownCallback shutdownPreserveCb; virNetDaemonShutdownCallback shutdownPrepareCb; virNetDaemonShutdownCallback shutdownWaitCb; - virThread *stateStopThread; + virThread *shutdownPreserveThread; int finishTimer; bool quit; bool finished; @@ -107,7 +108,7 @@ virNetDaemonDispose(void *obj) virEventRemoveHandle(dmn->sigwatch); #endif /* !WIN32 */ - g_free(dmn->stateStopThread); + g_free(dmn->shutdownPreserveThread); g_clear_pointer(&dmn->servers, g_hash_table_unref); @@ -705,8 +706,8 @@ daemonShutdownWait(void *opaque) virHashForEach(dmn->servers, daemonServerShutdownWait, NULL); if (!dmn->shutdownWaitCb || dmn->shutdownWaitCb() >= 0) { - if (dmn->stateStopThread) - virThreadJoin(dmn->stateStopThread); + if (dmn->shutdownPreserveThread) + virThreadJoin(dmn->shutdownPreserveThread); graceful = true; } @@ -801,17 +802,6 @@ virNetDaemonRun(virNetDaemon *dmn) } -void -virNetDaemonSetStateStopWorkerThread(virNetDaemon *dmn, - virThread **thr) -{ - VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); - - VIR_DEBUG("Setting state stop worker thread on dmn=%p to thr=%p", dmn, thr); - dmn->stateStopThread = g_steal_pointer(thr); -} - - void virNetDaemonQuit(virNetDaemon *dmn) { @@ -833,6 +823,42 @@ virNetDaemonQuitExecRestart(virNetDaemon *dmn) } +static void virNetDaemonPreserveWorker(void *opaque) +{ + virNetDaemon *dmn = opaque; + + VIR_DEBUG("Begin stop dmn=%p", dmn); + + dmn->shutdownPreserveCb(); + + VIR_DEBUG("Completed stop dmn=%p", dmn); + + virNetDaemonQuit(dmn); + virObjectUnref(dmn); +} + + +void virNetDaemonPreserve(virNetDaemon *dmn) +{ + VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + + if (!dmn->shutdownPreserveCb || + dmn->shutdownPreserveThread) + return; + + virObjectRef(dmn); + dmn->shutdownPreserveThread = g_new0(virThread, 1); + + if (virThreadCreateFull(dmn->shutdownPreserveThread, true, + virNetDaemonPreserveWorker, + "daemon-stop", false, dmn) < 0) { + virObjectUnref(dmn); + g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + return; + } +} + + static int daemonServerClose(void *payload, const char *key G_GNUC_UNUSED, @@ -870,11 +896,13 @@ virNetDaemonHasClients(virNetDaemon *dmn) void virNetDaemonSetShutdownCallbacks(virNetDaemon *dmn, + virNetDaemonShutdownCallback preserveCb, virNetDaemonShutdownCallback prepareCb, virNetDaemonShutdownCallback waitCb) { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + dmn->shutdownPreserveCb = preserveCb; dmn->shutdownPrepareCb = prepareCb; dmn->shutdownWaitCb = waitCb; } diff --git a/src/rpc/virnetdaemon.h b/src/rpc/virnetdaemon.h index 31a355adb4..c64f3bdeb1 100644 --- a/src/rpc/virnetdaemon.h +++ b/src/rpc/virnetdaemon.h @@ -66,13 +66,11 @@ int virNetDaemonAddSignalHandler(virNetDaemon *dmn, void virNetDaemonUpdateServices(virNetDaemon *dmn, bool enabled); -void virNetDaemonSetStateStopWorkerThread(virNetDaemon *dmn, - virThread **thr); - void virNetDaemonRun(virNetDaemon *dmn); void virNetDaemonQuit(virNetDaemon *dmn); void virNetDaemonQuitExecRestart(virNetDaemon *dmn); +void virNetDaemonPreserve(virNetDaemon *dmn); bool virNetDaemonHasClients(virNetDaemon *dmn); @@ -84,6 +82,22 @@ bool virNetDaemonHasServer(virNetDaemon *dmn, typedef int (*virNetDaemonShutdownCallback)(void); +/* + * @preserveCb: preserves any active state + * @prepareCb: start shutting down daemon + * @waitCb: wait for shutdown completion + * + * The sequence of operations during shutdown is as follows + * + * - Listener stops accepting new clients + * - Existing clients are closed + * - Delay until @preserveCb is complete (if running) + * - @prepareCb invoked + * - Server worker pool is drained in background + * - @waitCb is invoked in background + * - Main loop terminates + */ void virNetDaemonSetShutdownCallbacks(virNetDaemon *dmn, + virNetDaemonShutdownCallback preserveCb, virNetDaemonShutdownCallback prepareCb, virNetDaemonShutdownCallback waitCb); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:54 +0000, Daniel P. Berrangé wrote:
Currently the remote daemon code is responsible for calling virStateStop in a background thread. The virNetDaemon code wants to synchronize with this during shutdown, however, so the virThreadPtr must be passed over.
Even the limited synchronization done currently, however, is flawed and to fix this requires the virNetDaemon code to be responsible for calling virStateStop in a thread more directly.
Thus the logic is moved over into virStateStop via a further callback to be registered by the remote daemon.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_remote.syms | 2 +- src/remote/remote_daemon.c | 40 ++------------------------ src/rpc/virnetdaemon.c | 58 ++++++++++++++++++++++++++++---------- src/rpc/virnetdaemon.h | 20 +++++++++++-- 4 files changed, 64 insertions(+), 56 deletions(-)
diff --git a/src/libvirt_remote.syms b/src/libvirt_remote.syms index f0f90815cf..7e87b0bd2a 100644 --- a/src/libvirt_remote.syms +++ b/src/libvirt_remote.syms @@ -90,12 +90,12 @@ virNetDaemonIsPrivileged; virNetDaemonNew; virNetDaemonNewPostExecRestart; virNetDaemonPreExecRestart; +virNetDaemonPreserve; virNetDaemonQuit; virNetDaemonQuitExecRestart; virNetDaemonRemoveShutdownInhibition; virNetDaemonRun; virNetDaemonSetShutdownCallbacks; -virNetDaemonSetStateStopWorkerThread; virNetDaemonUpdateServices;
diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index c4b930cb70..bf91ee5772 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -514,41 +514,6 @@ static void daemonInhibitCallback(bool inhibit, void *opaque) static GDBusConnection *sessionBus; static GDBusConnection *systemBus;
-static void daemonStopWorker(void *opaque) -{ - virNetDaemon *dmn = opaque; - - VIR_DEBUG("Begin stop dmn=%p", dmn); - - ignore_value(virStateStop()); - - VIR_DEBUG("Completed stop dmn=%p", dmn); - - /* Exit daemon cleanly */ - virNetDaemonQuit(dmn); -} - - -/* We do this in a thread to not block the main loop */ -static void daemonStop(virNetDaemon *dmn) -{ - virThread *thr; - virObjectRef(dmn); - - thr = g_new0(virThread, 1); - - if (virThreadCreateFull(thr, true, - daemonStopWorker, - "daemon-stop", false, dmn) < 0) { - virObjectUnref(dmn); - g_free(thr); - return; - } - - virNetDaemonSetStateStopWorkerThread(dmn, &thr); -} - - static GDBusMessage * handleSessionMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, GDBusMessage *message, @@ -562,7 +527,7 @@ handleSessionMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, if (virGDBusMessageIsSignal(message, "org.freedesktop.DBus.Local", "Disconnected")) - daemonStop(dmn); + virNetDaemonPreserve(dmn);
return message; } @@ -581,7 +546,7 @@ handleSystemMessageFunc(GDBusConnection *connection G_GNUC_UNUSED,
VIR_DEBUG("dmn=%p", dmn);
- daemonStop(dmn); + virNetDaemonPreserve(dmn); }
@@ -625,6 +590,7 @@ static void daemonRunStateInit(void *opaque) g_atomic_int_set(&driversInitialized, 1);
virNetDaemonSetShutdownCallbacks(dmn, + virStateStop,
The name 'virStateStop' is starting to be confusing in the context it's being used now. I suggest you at least document what the expectations from the hypervisor drivers are? Or better when it's invoked.
virStateShutdownPrepare, virStateShutdownWait);
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index bb7d2ff9a0..19b19ff834 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -65,9 +65,10 @@ struct _virNetDaemon { GHashTable *servers; virJSONValue *srvObject;
+ virNetDaemonShutdownCallback shutdownPreserveCb; virNetDaemonShutdownCallback shutdownPrepareCb; virNetDaemonShutdownCallback shutdownWaitCb; - virThread *stateStopThread; + virThread *shutdownPreserveThread; int finishTimer; bool quit; bool finished; @@ -107,7 +108,7 @@ virNetDaemonDispose(void *obj) virEventRemoveHandle(dmn->sigwatch); #endif /* !WIN32 */
- g_free(dmn->stateStopThread); + g_free(dmn->shutdownPreserveThread);
g_clear_pointer(&dmn->servers, g_hash_table_unref);
@@ -705,8 +706,8 @@ daemonShutdownWait(void *opaque)
virHashForEach(dmn->servers, daemonServerShutdownWait, NULL); if (!dmn->shutdownWaitCb || dmn->shutdownWaitCb() >= 0) { - if (dmn->stateStopThread) - virThreadJoin(dmn->stateStopThread); + if (dmn->shutdownPreserveThread) + virThreadJoin(dmn->shutdownPreserveThread);
graceful = true; } @@ -801,17 +802,6 @@ virNetDaemonRun(virNetDaemon *dmn) }
-void -virNetDaemonSetStateStopWorkerThread(virNetDaemon *dmn, - virThread **thr) -{ - VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); - - VIR_DEBUG("Setting state stop worker thread on dmn=%p to thr=%p", dmn, thr); - dmn->stateStopThread = g_steal_pointer(thr); -} - - void virNetDaemonQuit(virNetDaemon *dmn) { @@ -833,6 +823,42 @@ virNetDaemonQuitExecRestart(virNetDaemon *dmn) }
+static void virNetDaemonPreserveWorker(void *opaque) +{ + virNetDaemon *dmn = opaque; + + VIR_DEBUG("Begin stop dmn=%p", dmn); + + dmn->shutdownPreserveCb(); + + VIR_DEBUG("Completed stop dmn=%p", dmn); + + virNetDaemonQuit(dmn); + virObjectUnref(dmn); +} + + +void virNetDaemonPreserve(virNetDaemon *dmn) +{ + VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + + if (!dmn->shutdownPreserveCb || + dmn->shutdownPreserveThread) + return; + + virObjectRef(dmn); + dmn->shutdownPreserveThread = g_new0(virThread, 1); + + if (virThreadCreateFull(dmn->shutdownPreserveThread, true, + virNetDaemonPreserveWorker, + "daemon-stop", false, dmn) < 0) { + virObjectUnref(dmn); + g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + return; + } +}
Please use the new function header formatting style.
+ + static int daemonServerClose(void *payload, const char *key G_GNUC_UNUSED, @@ -870,11 +896,13 @@ virNetDaemonHasClients(virNetDaemon *dmn)
void virNetDaemonSetShutdownCallbacks(virNetDaemon *dmn, + virNetDaemonShutdownCallback preserveCb, virNetDaemonShutdownCallback prepareCb, virNetDaemonShutdownCallback waitCb) { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
+ dmn->shutdownPreserveCb = preserveCb; dmn->shutdownPrepareCb = prepareCb; dmn->shutdownWaitCb = waitCb; }
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

On Wed, Jan 08, 2025 at 19:42:54 +0000, Daniel P. Berrangé wrote:
Currently the remote daemon code is responsible for calling virStateStop in a background thread. The virNetDaemon code wants to synchronize with this during shutdown, however, so the virThreadPtr must be passed over.
Even the limited synchronization done currently, however, is flawed and to fix this requires the virNetDaemon code to be responsible for calling virStateStop in a thread more directly.
Thus the logic is moved over into virStateStop via a further callback to be registered by the remote daemon.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_remote.syms | 2 +- src/remote/remote_daemon.c | 40 ++------------------------ src/rpc/virnetdaemon.c | 58 ++++++++++++++++++++++++++++---------- src/rpc/virnetdaemon.h | 20 +++++++++++-- 4 files changed, 64 insertions(+), 56 deletions(-)
diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index c4b930cb70..bf91ee5772 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -514,41 +514,6 @@ static void daemonInhibitCallback(bool inhibit, void *opaque) static GDBusConnection *sessionBus; static GDBusConnection *systemBus;
-static void daemonStopWorker(void *opaque) -{ - virNetDaemon *dmn = opaque;
So originally this was passed the 'dmn' pointer ...
- - VIR_DEBUG("Begin stop dmn=%p", dmn); - - ignore_value(virStateStop()); - - VIR_DEBUG("Completed stop dmn=%p", dmn); -
... but it was never touched except for logging ...
- /* Exit daemon cleanly */ - virNetDaemonQuit(dmn);
... and then using a self-locking API.
-} - - -/* We do this in a thread to not block the main loop */ -static void daemonStop(virNetDaemon *dmn) -{ - virThread *thr; - virObjectRef(dmn);
All of the above while having a reference.
- - thr = g_new0(virThread, 1); - - if (virThreadCreateFull(thr, true, - daemonStopWorker, - "daemon-stop", false, dmn) < 0) {
So this could make it async without worries ...
- virObjectUnref(dmn); - g_free(thr); - return; - } - - virNetDaemonSetStateStopWorkerThread(dmn, &thr);
... and without touching 'dmn' while unlocked.
-} - - static GDBusMessage * handleSessionMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, GDBusMessage *message, @@ -562,7 +527,7 @@ handleSessionMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, if (virGDBusMessageIsSignal(message, "org.freedesktop.DBus.Local", "Disconnected")) - daemonStop(dmn); + virNetDaemonPreserve(dmn);
return message; } @@ -581,7 +546,7 @@ handleSystemMessageFunc(GDBusConnection *connection G_GNUC_UNUSED,
VIR_DEBUG("dmn=%p", dmn);
- daemonStop(dmn); + virNetDaemonPreserve(dmn); }
@@ -625,6 +590,7 @@ static void daemonRunStateInit(void *opaque) g_atomic_int_set(&driversInitialized, 1);
virNetDaemonSetShutdownCallbacks(dmn, + virStateStop, virStateShutdownPrepare, virStateShutdownWait);
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index bb7d2ff9a0..19b19ff834 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -65,9 +65,10 @@ struct _virNetDaemon { GHashTable *servers; virJSONValue *srvObject;
+ virNetDaemonShutdownCallback shutdownPreserveCb; virNetDaemonShutdownCallback shutdownPrepareCb; virNetDaemonShutdownCallback shutdownWaitCb; - virThread *stateStopThread; + virThread *shutdownPreserveThread; int finishTimer; bool quit; bool finished; @@ -107,7 +108,7 @@ virNetDaemonDispose(void *obj) virEventRemoveHandle(dmn->sigwatch); #endif /* !WIN32 */
- g_free(dmn->stateStopThread); + g_free(dmn->shutdownPreserveThread);
g_clear_pointer(&dmn->servers, g_hash_table_unref);
@@ -705,8 +706,8 @@ daemonShutdownWait(void *opaque)
virHashForEach(dmn->servers, daemonServerShutdownWait, NULL); if (!dmn->shutdownWaitCb || dmn->shutdownWaitCb() >= 0) { - if (dmn->stateStopThread) - virThreadJoin(dmn->stateStopThread); + if (dmn->shutdownPreserveThread) + virThreadJoin(dmn->shutdownPreserveThread);
graceful = true; } @@ -801,17 +802,6 @@ virNetDaemonRun(virNetDaemon *dmn) }
-void -virNetDaemonSetStateStopWorkerThread(virNetDaemon *dmn, - virThread **thr) -{ - VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); - - VIR_DEBUG("Setting state stop worker thread on dmn=%p to thr=%p", dmn, thr); - dmn->stateStopThread = g_steal_pointer(thr); -} - - void virNetDaemonQuit(virNetDaemon *dmn) { @@ -833,6 +823,42 @@ virNetDaemonQuitExecRestart(virNetDaemon *dmn) }
[Re-organized hunks for explanatory reasons]
+ + +void virNetDaemonPreserve(virNetDaemon *dmn) +{ + VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
But in the new code, we lock 'dmn' ...
+ + if (!dmn->shutdownPreserveCb || + dmn->shutdownPreserveThread)
... so that we can safely access the properties of virNetDaemon ..
+ return; + + virObjectRef(dmn); + dmn->shutdownPreserveThread = g_new0(virThread, 1); + + if (virThreadCreateFull(dmn->shutdownPreserveThread, true, + virNetDaemonPreserveWorker, + "daemon-stop", false, dmn) < 0) {
.... but once you make it async here ...
+ virObjectUnref(dmn); + g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + return; + }
... and the context of this function finishes right away, 'dmn' will be unlocked ...
+} + +
+static void virNetDaemonPreserveWorker(void *opaque) +{ + virNetDaemon *dmn = opaque; + + VIR_DEBUG("Begin stop dmn=%p", dmn); + + dmn->shutdownPreserveCb();
... but here we access it's props.
+ + VIR_DEBUG("Completed stop dmn=%p", dmn); + + virNetDaemonQuit(dmn);
... now this uses self locking APIs once more.
+ virObjectUnref(dmn); +} static int daemonServerClose(void *payload, const char *key G_GNUC_UNUSED, @@ -870,11 +896,13 @@ virNetDaemonHasClients(virNetDaemon *dmn)
void virNetDaemonSetShutdownCallbacks(virNetDaemon *dmn, + virNetDaemonShutdownCallback preserveCb, virNetDaemonShutdownCallback prepareCb, virNetDaemonShutdownCallback waitCb) { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
+ dmn->shutdownPreserveCb = preserveCb;
And this setter technically makes it non-immutable. While it most likely will not be a problem I don't think we should access this function pointer outside of a lock. That is unless you document it as being/make it immutable. Upcoming patches also add code where virNetDaemonPreserveWorker will access dmn->quit which definitely isn't immutable so it's likely a more complete solution will be needed.

On Mon, Feb 03, 2025 at 10:52:23 +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:54 +0000, Daniel P. Berrangé wrote:
Currently the remote daemon code is responsible for calling virStateStop in a background thread. The virNetDaemon code wants to synchronize with this during shutdown, however, so the virThreadPtr must be passed over.
Even the limited synchronization done currently, however, is flawed and to fix this requires the virNetDaemon code to be responsible for calling virStateStop in a thread more directly.
Thus the logic is moved over into virStateStop via a further callback to be registered by the remote daemon.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/libvirt_remote.syms | 2 +- src/remote/remote_daemon.c | 40 ++------------------------ src/rpc/virnetdaemon.c | 58 ++++++++++++++++++++++++++++---------- src/rpc/virnetdaemon.h | 20 +++++++++++-- 4 files changed, 64 insertions(+), 56 deletions(-)
[...]
daemonServerClose(void *payload, const char *key G_GNUC_UNUSED, @@ -870,11 +896,13 @@ virNetDaemonHasClients(virNetDaemon *dmn)
void virNetDaemonSetShutdownCallbacks(virNetDaemon *dmn, + virNetDaemonShutdownCallback preserveCb, virNetDaemonShutdownCallback prepareCb, virNetDaemonShutdownCallback waitCb) { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
+ dmn->shutdownPreserveCb = preserveCb;
And this setter technically makes it non-immutable.
While it most likely will not be a problem I don't think we should access this function pointer outside of a lock. That is unless you document it as being/make it immutable.
Upcoming patches also add code where virNetDaemonPreserveWorker will access dmn->quit which definitely isn't immutable so it's likely a more complete solution will be needed.
Disregard this paragraph, I didn't notice the lock guard in the patch adding the access to dmn->quit. Thus declaring 'shutdownPreserveCb' to be immutable should be good enough.

The call to preserve state (ie running VMs) is triggered in response to the desktop session dbus terminating (session daemon), or logind sending a "PrepareForShutdown" signal. In the case of the latter, daemons should only save their state, not actually exit yet. Other things on the system may still expect the daemon to be running at this stage. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/remote_daemon.c | 4 +++- src/rpc/virnetdaemon.c | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index bf91ee5772..e03ee1de5a 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -526,8 +526,10 @@ handleSessionMessageFunc(GDBusConnection *connection G_GNUC_UNUSED, if (virGDBusMessageIsSignal(message, "org.freedesktop.DBus.Local", - "Disconnected")) + "Disconnected")) { virNetDaemonPreserve(dmn); + virNetDaemonQuit(dmn); + } return message; } diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index 19b19ff834..7f14c5420a 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -833,7 +833,6 @@ static void virNetDaemonPreserveWorker(void *opaque) VIR_DEBUG("Completed stop dmn=%p", dmn); - virNetDaemonQuit(dmn); virObjectUnref(dmn); } -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:55 +0000, Daniel P. Berrangé wrote:
The call to preserve state (ie running VMs) is triggered in response to the desktop session dbus terminating (session daemon), or logind sending a "PrepareForShutdown" signal. In the case of the latter, daemons should only save their state, not actually exit yet. Other things on the system may still expect the daemon to be running at this stage.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/remote_daemon.c | 4 +++- src/rpc/virnetdaemon.c | 1 - 2 files changed, 3 insertions(+), 2 deletions(-)
Reviewed-by: Peter Krempa <pkrempa@redhat.com>

The preserving of state (ie running VMs) requires a fully functional daemon and hypervisor driver. If any part has started shutting down then saving state may fail, or worse, hang. The current shutdown sequence does not guarantee safe ordering, as we synchronize with the state saving thread only after the hypervisor driver has had its 'shutdownPrepare' callback invoked. In the case of QEMU this means that worker threads processing monitor events may well have been stopped. This implements a full state machine that has a well defined ordering that an earlier commit documented as the desired semantics. With this change, nothing will start shutting down if the state saving thread is still running. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/remote_daemon.c | 4 +- src/rpc/virnetdaemon.c | 103 ++++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index e03ee1de5a..f64ecad7f5 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -617,8 +617,8 @@ static void daemonRunStateInit(void *opaque) NULL, G_DBUS_SIGNAL_FLAGS_NONE, handleSystemMessageFunc, - dmn, - NULL); + dmn, + NULL); /* Only now accept clients from network */ virNetDaemonUpdateServices(dmn, true); diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index 7f14c5420a..d1d7bae569 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -39,6 +39,15 @@ VIR_LOG_INIT("rpc.netdaemon"); +typedef enum { + VIR_NET_DAEMON_QUIT_NONE, + VIR_NET_DAEMON_QUIT_REQUESTED, + VIR_NET_DAEMON_QUIT_PRESERVING, + VIR_NET_DAEMON_QUIT_READY, + VIR_NET_DAEMON_QUIT_WAITING, + VIR_NET_DAEMON_QUIT_COMPLETED, +} virNetDaemonQuitPhase; + #ifndef WIN32 typedef struct _virNetDaemonSignal virNetDaemonSignal; struct _virNetDaemonSignal { @@ -69,9 +78,8 @@ struct _virNetDaemon { virNetDaemonShutdownCallback shutdownPrepareCb; virNetDaemonShutdownCallback shutdownWaitCb; virThread *shutdownPreserveThread; - int finishTimer; - bool quit; - bool finished; + int quitTimer; + virNetDaemonQuitPhase quit; bool graceful; bool execRestart; bool running; /* the daemon has reached the running phase */ @@ -414,7 +422,10 @@ virNetDaemonAutoShutdownTimer(int timerid G_GNUC_UNUSED, if (!dmn->autoShutdownInhibitions) { VIR_DEBUG("Automatic shutdown triggered"); - dmn->quit = true; + if (dmn->quit == VIR_NET_DAEMON_QUIT_NONE) { + VIR_DEBUG("Requesting daemon shutdown"); + dmn->quit = VIR_NET_DAEMON_QUIT_REQUESTED; + } } } @@ -705,27 +716,26 @@ daemonShutdownWait(void *opaque) bool graceful = false; virHashForEach(dmn->servers, daemonServerShutdownWait, NULL); - if (!dmn->shutdownWaitCb || dmn->shutdownWaitCb() >= 0) { - if (dmn->shutdownPreserveThread) - virThreadJoin(dmn->shutdownPreserveThread); - + if (!dmn->shutdownWaitCb || dmn->shutdownWaitCb() >= 0) graceful = true; - } VIR_WITH_OBJECT_LOCK_GUARD(dmn) { dmn->graceful = graceful; - virEventUpdateTimeout(dmn->finishTimer, 0); + dmn->quit = VIR_NET_DAEMON_QUIT_COMPLETED; + virEventUpdateTimeout(dmn->quitTimer, 0); + VIR_DEBUG("Shutdown wait completed graceful=%d", graceful); } } static void -virNetDaemonFinishTimer(int timerid G_GNUC_UNUSED, - void *opaque) +virNetDaemonQuitTimer(int timerid G_GNUC_UNUSED, + void *opaque) { virNetDaemon *dmn = opaque; VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); - dmn->finished = true; + dmn->quit = VIR_NET_DAEMON_QUIT_COMPLETED; + VIR_DEBUG("Shutdown wait timed out"); } @@ -742,9 +752,8 @@ virNetDaemonRun(virNetDaemon *dmn) goto cleanup; } - dmn->quit = false; - dmn->finishTimer = -1; - dmn->finished = false; + dmn->quit = VIR_NET_DAEMON_QUIT_NONE; + dmn->quitTimer = -1; dmn->graceful = false; dmn->running = true; @@ -753,7 +762,7 @@ virNetDaemonRun(virNetDaemon *dmn) virSystemdNotifyReady(); VIR_DEBUG("dmn=%p quit=%d", dmn, dmn->quit); - while (!dmn->finished) { + while (dmn->quit != VIR_NET_DAEMON_QUIT_COMPLETED) { virNetDaemonShutdownTimerUpdate(dmn); virObjectUnlock(dmn); @@ -767,17 +776,30 @@ virNetDaemonRun(virNetDaemon *dmn) virHashForEach(dmn->servers, daemonServerProcessClients, NULL); /* don't shutdown services when performing an exec-restart */ - if (dmn->quit && dmn->execRestart) + if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED && dmn->execRestart) goto cleanup; - if (dmn->quit && dmn->finishTimer == -1) { + if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED) { + VIR_DEBUG("Process quit request"); virHashForEach(dmn->servers, daemonServerClose, NULL); + + if (dmn->shutdownPreserveThread) { + VIR_DEBUG("Shutdown preserve thread running"); + dmn->quit = VIR_NET_DAEMON_QUIT_PRESERVING; + } else { + VIR_DEBUG("Ready to shutdown"); + dmn->quit = VIR_NET_DAEMON_QUIT_READY; + } + } + + if (dmn->quit == VIR_NET_DAEMON_QUIT_READY) { + VIR_DEBUG("Starting shutdown, running prepare"); if (dmn->shutdownPrepareCb && dmn->shutdownPrepareCb() < 0) break; - if ((dmn->finishTimer = virEventAddTimeout(30 * 1000, - virNetDaemonFinishTimer, - dmn, NULL)) < 0) { + if ((dmn->quitTimer = virEventAddTimeout(30 * 1000, + virNetDaemonQuitTimer, + dmn, NULL)) < 0) { VIR_WARN("Failed to register finish timer."); break; } @@ -787,6 +809,9 @@ virNetDaemonRun(virNetDaemon *dmn) VIR_WARN("Failed to register join thread."); break; } + + VIR_DEBUG("Waiting for shutdown completion"); + dmn->quit = VIR_NET_DAEMON_QUIT_WAITING; } } @@ -808,7 +833,7 @@ virNetDaemonQuit(virNetDaemon *dmn) VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); VIR_DEBUG("Quit requested %p", dmn); - dmn->quit = true; + dmn->quit = VIR_NET_DAEMON_QUIT_REQUESTED; } @@ -818,7 +843,7 @@ virNetDaemonQuitExecRestart(virNetDaemon *dmn) VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); VIR_DEBUG("Exec-restart requested %p", dmn); - dmn->quit = true; + dmn->quit = VIR_NET_DAEMON_QUIT_REQUESTED; dmn->execRestart = true; } @@ -827,12 +852,21 @@ static void virNetDaemonPreserveWorker(void *opaque) { virNetDaemon *dmn = opaque; - VIR_DEBUG("Begin stop dmn=%p", dmn); + VIR_DEBUG("Begin preserve dmn=%p", dmn); dmn->shutdownPreserveCb(); - VIR_DEBUG("Completed stop dmn=%p", dmn); + VIR_DEBUG("Completed preserve dmn=%p", dmn); + VIR_WITH_OBJECT_LOCK_GUARD(dmn) { + if (dmn->quit == VIR_NET_DAEMON_QUIT_PRESERVING) { + VIR_DEBUG("Marking shutdown as ready"); + dmn->quit = VIR_NET_DAEMON_QUIT_READY; + } + g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + } + + VIR_DEBUG("End preserve dmn=%p", dmn); virObjectUnref(dmn); } @@ -840,15 +874,26 @@ static void virNetDaemonPreserveWorker(void *opaque) void virNetDaemonPreserve(virNetDaemon *dmn) { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + VIR_DEBUG("Preserve state request"); - if (!dmn->shutdownPreserveCb || - dmn->shutdownPreserveThread) + if (!dmn->shutdownPreserveCb) { + VIR_DEBUG("No preserve callback registered"); return; + } + if (dmn->shutdownPreserveThread) { + VIR_DEBUG("Preserve state thread already running"); + return; + } + + if (dmn->quit != VIR_NET_DAEMON_QUIT_NONE) { + VIR_WARN("Already initiated shutdown sequence, unable to preserve state"); + return; + } virObjectRef(dmn); dmn->shutdownPreserveThread = g_new0(virThread, 1); - if (virThreadCreateFull(dmn->shutdownPreserveThread, true, + if (virThreadCreateFull(dmn->shutdownPreserveThread, false, virNetDaemonPreserveWorker, "daemon-stop", false, dmn) < 0) { virObjectUnref(dmn); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:56 +0000, Daniel P. Berrangé wrote:
The preserving of state (ie running VMs) requires a fully functional daemon and hypervisor driver. If any part has started shutting down then saving state may fail, or worse, hang.
The current shutdown sequence does not guarantee safe ordering, as we synchronize with the state saving thread only after the hypervisor driver has had its 'shutdownPrepare' callback invoked. In the case of QEMU this means that worker threads processing monitor events may well have been stopped.
This implements a full state machine that has a well defined ordering that an earlier commit documented as the desired semantics.
With this change, nothing will start shutting down if the state saving thread is still running.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/remote_daemon.c | 4 +- src/rpc/virnetdaemon.c | 103 ++++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 31 deletions(-)
diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index e03ee1de5a..f64ecad7f5 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -617,8 +617,8 @@ static void daemonRunStateInit(void *opaque) NULL, G_DBUS_SIGNAL_FLAGS_NONE, handleSystemMessageFunc, - dmn, - NULL); + dmn, + NULL);
/* Only now accept clients from network */ virNetDaemonUpdateServices(dmn, true);
This belongs to a different commit.
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index 7f14c5420a..d1d7bae569 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -39,6 +39,15 @@
VIR_LOG_INIT("rpc.netdaemon");
+typedef enum { + VIR_NET_DAEMON_QUIT_NONE, + VIR_NET_DAEMON_QUIT_REQUESTED, + VIR_NET_DAEMON_QUIT_PRESERVING, + VIR_NET_DAEMON_QUIT_READY, + VIR_NET_DAEMON_QUIT_WAITING, + VIR_NET_DAEMON_QUIT_COMPLETED, +} virNetDaemonQuitPhase;
[...]
@@ -753,7 +762,7 @@ virNetDaemonRun(virNetDaemon *dmn) virSystemdNotifyReady();
VIR_DEBUG("dmn=%p quit=%d", dmn, dmn->quit); - while (!dmn->finished) { + while (dmn->quit != VIR_NET_DAEMON_QUIT_COMPLETED) { virNetDaemonShutdownTimerUpdate(dmn);
virObjectUnlock(dmn); @@ -767,17 +776,30 @@ virNetDaemonRun(virNetDaemon *dmn) virHashForEach(dmn->servers, daemonServerProcessClients, NULL);
/* don't shutdown services when performing an exec-restart */ - if (dmn->quit && dmn->execRestart) + if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED && dmn->execRestart) goto cleanup;
- if (dmn->quit && dmn->finishTimer == -1) { + if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED) { + VIR_DEBUG("Process quit request"); virHashForEach(dmn->servers, daemonServerClose, NULL); + + if (dmn->shutdownPreserveThread) { + VIR_DEBUG("Shutdown preserve thread running"); + dmn->quit = VIR_NET_DAEMON_QUIT_PRESERVING; + } else { + VIR_DEBUG("Ready to shutdown"); + dmn->quit = VIR_NET_DAEMON_QUIT_READY; + } + } + + if (dmn->quit == VIR_NET_DAEMON_QUIT_READY) { + VIR_DEBUG("Starting shutdown, running prepare"); if (dmn->shutdownPrepareCb && dmn->shutdownPrepareCb() < 0) break;
- if ((dmn->finishTimer = virEventAddTimeout(30 * 1000, - virNetDaemonFinishTimer, - dmn, NULL)) < 0) { + if ((dmn->quitTimer = virEventAddTimeout(30 * 1000, + virNetDaemonQuitTimer, + dmn, NULL)) < 0) { VIR_WARN("Failed to register finish timer."); break; } @@ -787,6 +809,9 @@ virNetDaemonRun(virNetDaemon *dmn) VIR_WARN("Failed to register join thread."); break; } + + VIR_DEBUG("Waiting for shutdown completion"); + dmn->quit = VIR_NET_DAEMON_QUIT_WAITING;
[1]
@@ -808,7 +833,7 @@ virNetDaemonQuit(virNetDaemon *dmn) VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
VIR_DEBUG("Quit requested %p", dmn); - dmn->quit = true; + dmn->quit = VIR_NET_DAEMON_QUIT_REQUESTED; }
@@ -818,7 +843,7 @@ virNetDaemonQuitExecRestart(virNetDaemon *dmn) VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
VIR_DEBUG("Exec-restart requested %p", dmn); - dmn->quit = true; + dmn->quit = VIR_NET_DAEMON_QUIT_REQUESTED; dmn->execRestart = true; }
These two seem to be apart from other cases also be registered in various signal (SIGTERM/SIGINT/etc) handlers. As the signal handler code we use doesn't seem to interlock multiple deliveries of said signals it seems that these will be able to mess up the state of the state machine if a 'well-timed' signal hits. Specifically they could clear VIR_NET_DAEMON_QUIT_PRESERVING or VIR_NET_DAEMON_QUIT_WAITING. When VIR_NET_DAEMON_QUIT_PRESERVING will be overwritten it will get fixed in the next iteration of the worker loop in virNetDaemonRun(), but will re-notify systemd about the shutdown and re-stop the 'server's. What's most likely worse is that [1] VIR_NET_DAEMON_QUIT_WAITING can be lost as well, which would re-register the timer and start another shutdown thread.
@@ -827,12 +852,21 @@ static void virNetDaemonPreserveWorker(void *opaque) { virNetDaemon *dmn = opaque;
- VIR_DEBUG("Begin stop dmn=%p", dmn); + VIR_DEBUG("Begin preserve dmn=%p", dmn);
dmn->shutdownPreserveCb();
- VIR_DEBUG("Completed stop dmn=%p", dmn); + VIR_DEBUG("Completed preserve dmn=%p", dmn);
+ VIR_WITH_OBJECT_LOCK_GUARD(dmn) { + if (dmn->quit == VIR_NET_DAEMON_QUIT_PRESERVING) { + VIR_DEBUG("Marking shutdown as ready"); + dmn->quit = VIR_NET_DAEMON_QUIT_READY; + } + g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + } + + VIR_DEBUG("End preserve dmn=%p", dmn); virObjectUnref(dmn); }
@@ -840,15 +874,26 @@ static void virNetDaemonPreserveWorker(void *opaque) void virNetDaemonPreserve(virNetDaemon *dmn) { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + VIR_DEBUG("Preserve state request");
- if (!dmn->shutdownPreserveCb || - dmn->shutdownPreserveThread) + if (!dmn->shutdownPreserveCb) { + VIR_DEBUG("No preserve callback registered"); return; + } + if (dmn->shutdownPreserveThread) { + VIR_DEBUG("Preserve state thread already running"); + return; + } + + if (dmn->quit != VIR_NET_DAEMON_QUIT_NONE) { + VIR_WARN("Already initiated shutdown sequence, unable to preserve state"); + return; + }
virObjectRef(dmn); dmn->shutdownPreserveThread = g_new0(virThread, 1);
- if (virThreadCreateFull(dmn->shutdownPreserveThread, true, + if (virThreadCreateFull(dmn->shutdownPreserveThread, false,
This looks like a bugfix to a previous commit as the thread was never actually joined.
virNetDaemonPreserveWorker, "daemon-stop", false, dmn) < 0) { virObjectUnref(dmn);

On Mon, Feb 03, 2025 at 12:04:01PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:56 +0000, Daniel P. Berrangé wrote:
The preserving of state (ie running VMs) requires a fully functional daemon and hypervisor driver. If any part has started shutting down then saving state may fail, or worse, hang.
The current shutdown sequence does not guarantee safe ordering, as we synchronize with the state saving thread only after the hypervisor driver has had its 'shutdownPrepare' callback invoked. In the case of QEMU this means that worker threads processing monitor events may well have been stopped.
This implements a full state machine that has a well defined ordering that an earlier commit documented as the desired semantics.
With this change, nothing will start shutting down if the state saving thread is still running.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/remote_daemon.c | 4 +- src/rpc/virnetdaemon.c | 103 ++++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 31 deletions(-)
diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c index e03ee1de5a..f64ecad7f5 100644 --- a/src/remote/remote_daemon.c +++ b/src/remote/remote_daemon.c @@ -617,8 +617,8 @@ static void daemonRunStateInit(void *opaque) NULL, G_DBUS_SIGNAL_FLAGS_NONE, handleSystemMessageFunc, - dmn, - NULL); + dmn, + NULL);
/* Only now accept clients from network */ virNetDaemonUpdateServices(dmn, true);
This belongs to a different commit.
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index 7f14c5420a..d1d7bae569 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -39,6 +39,15 @@
VIR_LOG_INIT("rpc.netdaemon");
+typedef enum { + VIR_NET_DAEMON_QUIT_NONE, + VIR_NET_DAEMON_QUIT_REQUESTED, + VIR_NET_DAEMON_QUIT_PRESERVING, + VIR_NET_DAEMON_QUIT_READY, + VIR_NET_DAEMON_QUIT_WAITING, + VIR_NET_DAEMON_QUIT_COMPLETED, +} virNetDaemonQuitPhase;
[...]
@@ -753,7 +762,7 @@ virNetDaemonRun(virNetDaemon *dmn) virSystemdNotifyReady();
VIR_DEBUG("dmn=%p quit=%d", dmn, dmn->quit); - while (!dmn->finished) { + while (dmn->quit != VIR_NET_DAEMON_QUIT_COMPLETED) { virNetDaemonShutdownTimerUpdate(dmn);
virObjectUnlock(dmn); @@ -767,17 +776,30 @@ virNetDaemonRun(virNetDaemon *dmn) virHashForEach(dmn->servers, daemonServerProcessClients, NULL);
/* don't shutdown services when performing an exec-restart */ - if (dmn->quit && dmn->execRestart) + if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED && dmn->execRestart) goto cleanup;
- if (dmn->quit && dmn->finishTimer == -1) { + if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED) { + VIR_DEBUG("Process quit request"); virHashForEach(dmn->servers, daemonServerClose, NULL); + + if (dmn->shutdownPreserveThread) { + VIR_DEBUG("Shutdown preserve thread running"); + dmn->quit = VIR_NET_DAEMON_QUIT_PRESERVING; + } else { + VIR_DEBUG("Ready to shutdown"); + dmn->quit = VIR_NET_DAEMON_QUIT_READY; + } + } + + if (dmn->quit == VIR_NET_DAEMON_QUIT_READY) { + VIR_DEBUG("Starting shutdown, running prepare"); if (dmn->shutdownPrepareCb && dmn->shutdownPrepareCb() < 0) break;
- if ((dmn->finishTimer = virEventAddTimeout(30 * 1000, - virNetDaemonFinishTimer, - dmn, NULL)) < 0) { + if ((dmn->quitTimer = virEventAddTimeout(30 * 1000, + virNetDaemonQuitTimer, + dmn, NULL)) < 0) { VIR_WARN("Failed to register finish timer."); break; } @@ -787,6 +809,9 @@ virNetDaemonRun(virNetDaemon *dmn) VIR_WARN("Failed to register join thread."); break; } + + VIR_DEBUG("Waiting for shutdown completion"); + dmn->quit = VIR_NET_DAEMON_QUIT_WAITING;
[1]
@@ -808,7 +833,7 @@ virNetDaemonQuit(virNetDaemon *dmn) VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
VIR_DEBUG("Quit requested %p", dmn); - dmn->quit = true; + dmn->quit = VIR_NET_DAEMON_QUIT_REQUESTED; }
@@ -818,7 +843,7 @@ virNetDaemonQuitExecRestart(virNetDaemon *dmn) VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
VIR_DEBUG("Exec-restart requested %p", dmn); - dmn->quit = true; + dmn->quit = VIR_NET_DAEMON_QUIT_REQUESTED; dmn->execRestart = true; }
These two seem to be apart from other cases also be registered in various signal (SIGTERM/SIGINT/etc) handlers. As the signal handler code we use doesn't seem to interlock multiple deliveries of said signals it seems that these will be able to mess up the state of the state machine if a 'well-timed' signal hits. Specifically they could clear VIR_NET_DAEMON_QUIT_PRESERVING or VIR_NET_DAEMON_QUIT_WAITING.
When VIR_NET_DAEMON_QUIT_PRESERVING will be overwritten it will get fixed in the next iteration of the worker loop in virNetDaemonRun(), but will re-notify systemd about the shutdown and re-stop the 'server's.
What's most likely worse is that [1] VIR_NET_DAEMON_QUIT_WAITING can be lost as well, which would re-register the timer and start another shutdown thread.
We can't race because signals are queued and dispatched from the main event thread. We must, however, check that quit == VIR_NET_DAEMON_QUIT_NONE before setting it to VIR_NET_DAEMON_QUIT_REQUESTED.
@@ -827,12 +852,21 @@ static void virNetDaemonPreserveWorker(void *opaque) { virNetDaemon *dmn = opaque;
- VIR_DEBUG("Begin stop dmn=%p", dmn); + VIR_DEBUG("Begin preserve dmn=%p", dmn);
dmn->shutdownPreserveCb();
- VIR_DEBUG("Completed stop dmn=%p", dmn); + VIR_DEBUG("Completed preserve dmn=%p", dmn);
+ VIR_WITH_OBJECT_LOCK_GUARD(dmn) { + if (dmn->quit == VIR_NET_DAEMON_QUIT_PRESERVING) { + VIR_DEBUG("Marking shutdown as ready"); + dmn->quit = VIR_NET_DAEMON_QUIT_READY; + } + g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + } + + VIR_DEBUG("End preserve dmn=%p", dmn); virObjectUnref(dmn); }
@@ -840,15 +874,26 @@ static void virNetDaemonPreserveWorker(void *opaque) void virNetDaemonPreserve(virNetDaemon *dmn) { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + VIR_DEBUG("Preserve state request");
- if (!dmn->shutdownPreserveCb || - dmn->shutdownPreserveThread) + if (!dmn->shutdownPreserveCb) { + VIR_DEBUG("No preserve callback registered"); return; + } + if (dmn->shutdownPreserveThread) { + VIR_DEBUG("Preserve state thread already running"); + return; + } + + if (dmn->quit != VIR_NET_DAEMON_QUIT_NONE) { + VIR_WARN("Already initiated shutdown sequence, unable to preserve state"); + return; + }
virObjectRef(dmn); dmn->shutdownPreserveThread = g_new0(virThread, 1);
- if (virThreadCreateFull(dmn->shutdownPreserveThread, true, + if (virThreadCreateFull(dmn->shutdownPreserveThread, false,
This looks like a bugfix to a previous commit as the thread was never actually joined.
The previous code would join with the shutdownPreserveThread in order to synchronize the shutdown sequence. This logic was broken, and in this patch we're making the thread itself update 'dmn->quit' when it is done, so it can exit without anything needing to join to it. All synchronization is now via the 'dmn->quit' state machine. Thus we toggle to mark this thread as non-joinable in this patch
virNetDaemonPreserveWorker, "daemon-stop", false, dmn) < 0) { virObjectUnref(dmn);
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

The daemons are wired up to shutdown in responsible to UNIX process signals, as well as in response to login1 dbus signals, or loss of desktop session. The latter two options can optionally preserve state (ie running VMs). In non-systemd environments, as well as for testing, it would be useful to have a way to trigger shutdown with state preservation more directly. Thus a new admin protocol API is introduced virAdmConnectDaemonShutdown which will trigger a daemon shutdown, and preserve running VMs if the VIR_DAEMON_SHUTDOWN_PRESERVE flag is set. It has a corresponding 'virt-admin daemon-shutdown [--preserve]' command binding. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-admin.h | 13 +++++++++ src/admin/admin_protocol.x | 11 +++++++- src/admin/admin_server_dispatch.c | 13 +++++++++ src/admin/libvirt-admin.c | 33 +++++++++++++++++++++++ src/admin/libvirt_admin_public.syms | 5 ++++ src/rpc/virnetdaemon.c | 4 +++ tools/virt-admin.c | 41 +++++++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 1 deletion(-) diff --git a/include/libvirt/libvirt-admin.h b/include/libvirt/libvirt-admin.h index ae4703f89b..1926202e97 100644 --- a/include/libvirt/libvirt-admin.h +++ b/include/libvirt/libvirt-admin.h @@ -484,6 +484,19 @@ int virAdmConnectSetDaemonTimeout(virAdmConnectPtr conn, unsigned int timeout, unsigned int flags); +/** + * virAdmConnectDaemonShutdownFlags: + * + * Since: 11.0.0 + */ +typedef enum { + /* Preserve state before shutting down daemon (Since: 11.0.0) */ + VIR_DAEMON_SHUTDOWN_PRESERVE = (1 << 0), +} virAdmConnectDaemonShutdownFlags; + +int virAdmConnectDaemonShutdown(virAdmConnectPtr conn, + unsigned int flags); + # ifdef __cplusplus } # endif diff --git a/src/admin/admin_protocol.x b/src/admin/admin_protocol.x index f3130efd2d..cf5707e62e 100644 --- a/src/admin/admin_protocol.x +++ b/src/admin/admin_protocol.x @@ -219,6 +219,10 @@ struct admin_connect_set_daemon_timeout_args { unsigned int flags; }; +struct admin_connect_daemon_shutdown_args { + unsigned int flags; +}; + /* Define the program number, protocol version and procedure numbers here. */ const ADMIN_PROGRAM = 0x06900690; const ADMIN_PROTOCOL_VERSION = 1; @@ -334,5 +338,10 @@ enum admin_procedure { /** * @generate: both */ - ADMIN_PROC_CONNECT_SET_DAEMON_TIMEOUT = 19 + ADMIN_PROC_CONNECT_SET_DAEMON_TIMEOUT = 19, + + /** + * @generate: both + */ + ADMIN_PROC_CONNECT_DAEMON_SHUTDOWN = 20 }; diff --git a/src/admin/admin_server_dispatch.c b/src/admin/admin_server_dispatch.c index ae8a8d4fa6..092cc790e6 100644 --- a/src/admin/admin_server_dispatch.c +++ b/src/admin/admin_server_dispatch.c @@ -474,6 +474,19 @@ adminConnectSetDaemonTimeout(virNetDaemon *dmn, return virNetDaemonAutoShutdown(dmn, timeout); } +static int +adminConnectDaemonShutdown(virNetDaemon *dmn, + unsigned int flags) +{ + virCheckFlags(VIR_DAEMON_SHUTDOWN_PRESERVE, -1); + + if (flags & VIR_DAEMON_SHUTDOWN_PRESERVE) + virNetDaemonPreserve(dmn); + + virNetDaemonQuit(dmn); + + return 0; +} static int adminDispatchConnectGetLoggingOutputs(virNetServer *server G_GNUC_UNUSED, diff --git a/src/admin/libvirt-admin.c b/src/admin/libvirt-admin.c index 3c756eb376..1e2446135c 100644 --- a/src/admin/libvirt-admin.c +++ b/src/admin/libvirt-admin.c @@ -1357,3 +1357,36 @@ virAdmConnectSetDaemonTimeout(virAdmConnectPtr conn, return ret; } + + +/** + * virAdmConnectDaemonShutdown: + * @conn: pointer to an active admin connection + * @flags: optional extra falgs + * + * Trigger shutdown of the daemon, if @flags includes + * VIR_DAEMON_SHUTDOWN_PRESERVE then state will be + * preserved before shutting down + * + * Returns 0 on success, -1 on error. + * + * Since: 11.0.0 + */ +int +virAdmConnectDaemonShutdown(virAdmConnectPtr conn, + unsigned int flags) +{ + int ret; + + VIR_DEBUG("conn=%p, flags=0x%x", conn, flags); + + virResetLastError(); + virCheckAdmConnectReturn(conn, -1); + + if ((ret = remoteAdminConnectDaemonShutdown(conn, flags)) < 0) { + virDispatchError(NULL); + return -1; + } + + return ret; +} diff --git a/src/admin/libvirt_admin_public.syms b/src/admin/libvirt_admin_public.syms index 17930e4fac..d39656bc53 100644 --- a/src/admin/libvirt_admin_public.syms +++ b/src/admin/libvirt_admin_public.syms @@ -53,3 +53,8 @@ LIBVIRT_ADMIN_8.6.0 { global: virAdmConnectSetDaemonTimeout; } LIBVIRT_ADMIN_3.0.0; + +LIBVIRT_ADMIN_11.0.0 { + global: + virAdmConnectDaemonShutdown; +} LIBVIRT_ADMIN_8.6.0; diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index d1d7bae569..8cc7af1182 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -815,8 +815,10 @@ virNetDaemonRun(virNetDaemon *dmn) } } + VIR_DEBUG("Main loop exited"); if (dmn->graceful) { virThreadJoin(&shutdownThread); + VIR_DEBUG("Graceful shutdown complete"); } else { VIR_WARN("Make forcefull daemon shutdown"); exit(EXIT_FAILURE); @@ -946,6 +948,8 @@ virNetDaemonSetShutdownCallbacks(virNetDaemon *dmn, { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + VIR_DEBUG("Shutdown callbacks preserve=%p prepare=%p wait=%p", + preserveCb, prepareCb, waitCb); dmn->shutdownPreserveCb = preserveCb; dmn->shutdownPrepareCb = prepareCb; dmn->shutdownWaitCb = waitCb; diff --git a/tools/virt-admin.c b/tools/virt-admin.c index 325b7aa827..54dc36a564 100644 --- a/tools/virt-admin.c +++ b/tools/virt-admin.c @@ -1077,6 +1077,41 @@ cmdDaemonTimeout(vshControl *ctl, const vshCmd *cmd) } +/* -------------------------- + * Command daemon-shutdown + * -------------------------- + */ +static const vshCmdInfo info_daemon_shutdown = { + .help = N_("stop the daemon"), + .desc = N_("stop the daemon"), +}; + +static const vshCmdOptDef opts_daemon_shutdown[] = { + {.name = "preserve", + .type = VSH_OT_BOOL, + .required = false, + .positional = false, + .help = N_("preserve state before shutting down"), + }, + {.name = NULL} +}; + +static bool +cmdDaemonShutdown(vshControl *ctl, const vshCmd *cmd) +{ + vshAdmControl *priv = ctl->privData; + unsigned int flags = 0; + + if (vshCommandOptBool(cmd, "preserve")) + flags |= VIR_DAEMON_SHUTDOWN_PRESERVE; + + if (virAdmConnectDaemonShutdown(priv->conn, flags) < 0) + return false; + + return true; +} + + static void * vshAdmConnectionHandler(vshControl *ctl) { @@ -1469,6 +1504,12 @@ static const vshCmdDef managementCmds[] = { .info = &info_daemon_timeout, .flags = 0 }, + {.name = "daemon-shutdown", + .handler = cmdDaemonShutdown, + .opts = opts_daemon_shutdown, + .info = &info_daemon_shutdown, + .flags = 0 + }, {.name = NULL} }; -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:57 +0000, Daniel P. Berrangé wrote:
The daemons are wired up to shutdown in responsible to UNIX process signals, as well as in response to login1 dbus signals, or loss of desktop session. The latter two options can optionally preserve state (ie running VMs).
In non-systemd environments, as well as for testing, it would be useful to have a way to trigger shutdown with state preservation more directly.
Thus a new admin protocol API is introduced
virAdmConnectDaemonShutdown
which will trigger a daemon shutdown, and preserve running VMs if the VIR_DAEMON_SHUTDOWN_PRESERVE flag is set.
It has a corresponding 'virt-admin daemon-shutdown [--preserve]' command binding.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-admin.h | 13 +++++++++ src/admin/admin_protocol.x | 11 +++++++- src/admin/admin_server_dispatch.c | 13 +++++++++ src/admin/libvirt-admin.c | 33 +++++++++++++++++++++++ src/admin/libvirt_admin_public.syms | 5 ++++ src/rpc/virnetdaemon.c | 4 +++ tools/virt-admin.c | 41 +++++++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 1 deletion(-)
Test failure after this patch (and also present at the end of the series): ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 LC_CTYPE=en_US.UTF-8 UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 LC_ALL='' MSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 LANG=C MALLOC_PERTURB_=118 MESON_TEST_ITERATION=1 /bin/python3 /home/pipo/libvirt/scripts/check-remote-protocol.py admin_protocol virt_admin_driver /home/pipo/build/libvirt/gcc/src/admin/libvirt_admin_driver.a /bin/pdwtags /home/pipo/build/libvirt/gcc/../../../libvirt/src/admin_protocol-structs ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ✀ ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― --- /home/pipo/build/libvirt/gcc/../../../libvirt/src/admin_protocol-structs 2024-11-15 08:32:02.517209576 +0100 +++ - 2025-01-30 10:34:46.127808492 +0100 @@ -148,6 +148,9 @@ u_int timeout; u_int flags; }; +struct admin_connect_daemon_shutdown_args { + u_int flags; +}; enum admin_procedure { ADMIN_PROC_CONNECT_OPEN = 1, ADMIN_PROC_CONNECT_CLOSE = 2, @@ -168,4 +171,5 @@ ADMIN_PROC_CONNECT_SET_LOGGING_FILTERS = 17, ADMIN_PROC_SERVER_UPDATE_TLS_FILES = 18, ADMIN_PROC_CONNECT_SET_DAEMON_TIMEOUT = 19, + ADMIN_PROC_CONNECT_DAEMON_SHUTDOWN = 20, }; ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

On Wed, Jan 08, 2025 at 19:42:57 +0000, Daniel P. Berrangé wrote:
The daemons are wired up to shutdown in responsible to UNIX process signals, as well as in response to login1 dbus signals, or loss of desktop session. The latter two options can optionally preserve state (ie running VMs).
In non-systemd environments, as well as for testing, it would be useful to have a way to trigger shutdown with state preservation more directly.
Thus a new admin protocol API is introduced
virAdmConnectDaemonShutdown
which will trigger a daemon shutdown, and preserve running VMs if the VIR_DAEMON_SHUTDOWN_PRESERVE flag is set.
It has a corresponding 'virt-admin daemon-shutdown [--preserve]' command binding.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-admin.h | 13 +++++++++ src/admin/admin_protocol.x | 11 +++++++- src/admin/admin_server_dispatch.c | 13 +++++++++ src/admin/libvirt-admin.c | 33 +++++++++++++++++++++++ src/admin/libvirt_admin_public.syms | 5 ++++ src/rpc/virnetdaemon.c | 4 +++ tools/virt-admin.c | 41 +++++++++++++++++++++++++++++
Missing man-page addition for the 'virt-admin' tool.
7 files changed, 119 insertions(+), 1 deletion(-)
diff --git a/include/libvirt/libvirt-admin.h b/include/libvirt/libvirt-admin.h index ae4703f89b..1926202e97 100644 --- a/include/libvirt/libvirt-admin.h +++ b/include/libvirt/libvirt-admin.h @@ -484,6 +484,19 @@ int virAdmConnectSetDaemonTimeout(virAdmConnectPtr conn, unsigned int timeout, unsigned int flags);
+/** + * virAdmConnectDaemonShutdownFlags: + * + * Since: 11.0.0 + */ +typedef enum { + /* Preserve state before shutting down daemon (Since: 11.0.0) */ + VIR_DAEMON_SHUTDOWN_PRESERVE = (1 << 0), +} virAdmConnectDaemonShutdownFlags;
Don't forget to fix version numbers. Especially in the public.syms file.
diff --git a/src/admin/libvirt_admin_public.syms b/src/admin/libvirt_admin_public.syms index 17930e4fac..d39656bc53 100644 --- a/src/admin/libvirt_admin_public.syms +++ b/src/admin/libvirt_admin_public.syms @@ -53,3 +53,8 @@ LIBVIRT_ADMIN_8.6.0 { global: virAdmConnectSetDaemonTimeout; } LIBVIRT_ADMIN_3.0.0; + +LIBVIRT_ADMIN_11.0.0 {
^^^
+ global: + virAdmConnectDaemonShutdown; +} LIBVIRT_ADMIN_8.6.0;
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index d1d7bae569..8cc7af1182 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -815,8 +815,10 @@ virNetDaemonRun(virNetDaemon *dmn) } }
+ VIR_DEBUG("Main loop exited"); if (dmn->graceful) { virThreadJoin(&shutdownThread); + VIR_DEBUG("Graceful shutdown complete"); } else { VIR_WARN("Make forcefull daemon shutdown"); exit(EXIT_FAILURE); @@ -946,6 +948,8 @@ virNetDaemonSetShutdownCallbacks(virNetDaemon *dmn, { VIR_LOCK_GUARD lock = virObjectLockGuard(dmn);
+ VIR_DEBUG("Shutdown callbacks preserve=%p prepare=%p wait=%p", + preserveCb, prepareCb, waitCb); dmn->shutdownPreserveCb = preserveCb; dmn->shutdownPrepareCb = prepareCb; dmn->shutdownWaitCb = waitCb;
These debug statements don't seem to belong to this patch. Reviewed-by: Peter Krempa <pkrempa@redhat.com>

The service unit "TimeoutStopSec" setting controls how long systemd waits for a service to stop before aggressively killing it, defaulting to 30 seconds if not set. When we're processing shutdown of VMs in response to OS shutdown, we very likely need more than 30 seconds to complete this job, and can not stop the daemon during this time. To avoid being prematurely killed, setup a timer that repeatedly extends the "TimeoutStopSec" value while stop of running VMs is arranged. This does mean if libvirt hangs while stoppping VMs, systemd won't get to kill the libvirt daemon, but this is considered less harmful that forcefully killing running VMs. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/rpc/virnetdaemon.c | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index 8cc7af1182..3ddb9b5404 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -78,6 +78,9 @@ struct _virNetDaemon { virNetDaemonShutdownCallback shutdownPrepareCb; virNetDaemonShutdownCallback shutdownWaitCb; virThread *shutdownPreserveThread; + unsigned long long preserveStart; + unsigned int preserveExtended; + int preserveTimer; int quitTimer; virNetDaemonQuitPhase quit; bool graceful; @@ -93,6 +96,14 @@ struct _virNetDaemon { static virClass *virNetDaemonClass; +/* + * The minimum additional shutdown time (secs) we should ask + * systemd to allow, while state preservation operations + * are running. A timer will run every 5 seconds, and + * ensure at least this much extra time is requested + */ +#define VIR_NET_DAEMON_PRESERVE_MIN_TIME 30 + static int daemonServerClose(void *payload, const char *key G_GNUC_UNUSED, @@ -162,6 +173,7 @@ virNetDaemonNew(void) if (virEventRegisterDefaultImpl() < 0) goto error; + dmn->preserveTimer = -1; dmn->autoShutdownTimerID = -1; #ifndef WIN32 @@ -727,6 +739,42 @@ daemonShutdownWait(void *opaque) } } +static void +virNetDaemonPreserveTimer(int timerid G_GNUC_UNUSED, + void *opaque) +{ + virNetDaemon *dmn = opaque; + VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + unsigned long long now = g_get_monotonic_time(); + unsigned long long delta; + + if (dmn->quit != VIR_NET_DAEMON_QUIT_PRESERVING) + return; + + VIR_DEBUG("Started at %llu now %llu extended %u", + dmn->preserveStart, now, dmn->preserveExtended); + + /* Time since start of preserving state in usec */ + delta = now - dmn->preserveStart; + /* Converts to secs */ + delta /= (1000ull * 1000ull); + + /* Want extra seconds grace to ensure this timer fires + * again before system timeout expires, under high + * load conditions */ + delta += VIR_NET_DAEMON_PRESERVE_MIN_TIME; + + /* Deduct any extension we've previously asked for */ + delta -= dmn->preserveExtended; + + /* Tell systemd how much more we need to extend by */ + virSystemdNotifyExtendTimeout(delta); + dmn->preserveExtended += delta; + + VIR_DEBUG("Extended by %llu", delta); +} + + static void virNetDaemonQuitTimer(int timerid G_GNUC_UNUSED, void *opaque) @@ -781,11 +829,21 @@ virNetDaemonRun(virNetDaemon *dmn) if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED) { VIR_DEBUG("Process quit request"); + virSystemdNotifyStopping(); virHashForEach(dmn->servers, daemonServerClose, NULL); if (dmn->shutdownPreserveThread) { VIR_DEBUG("Shutdown preserve thread running"); dmn->quit = VIR_NET_DAEMON_QUIT_PRESERVING; + dmn->preserveStart = g_get_monotonic_time(); + dmn->preserveExtended = VIR_NET_DAEMON_PRESERVE_MIN_TIME; + virSystemdNotifyExtendTimeout(dmn->preserveExtended); + if ((dmn->preserveTimer = virEventAddTimeout(5 * 1000, + virNetDaemonPreserveTimer, + dmn, NULL)) < 0) { + VIR_WARN("Failed to register preservation timer"); + /* hope for the best */ + } } else { VIR_DEBUG("Ready to shutdown"); dmn->quit = VIR_NET_DAEMON_QUIT_READY; @@ -866,6 +924,10 @@ static void virNetDaemonPreserveWorker(void *opaque) dmn->quit = VIR_NET_DAEMON_QUIT_READY; } g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + if (dmn->preserveTimer != -1) { + virEventRemoveTimeout(dmn->preserveTimer); + dmn->preserveTimer = -1; + } } VIR_DEBUG("End preserve dmn=%p", dmn); -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:58 +0000, Daniel P. Berrangé wrote:
The service unit "TimeoutStopSec" setting controls how long systemd waits for a service to stop before aggressively killing it, defaulting to 30 seconds if not set.
When we're processing shutdown of VMs in response to OS shutdown, we very likely need more than 30 seconds to complete this job, and can not stop the daemon during this time.
To avoid being prematurely killed, setup a timer that repeatedly extends the "TimeoutStopSec" value while stop of running VMs is arranged.
This does mean if libvirt hangs while stoppping VMs, systemd won't get to kill the libvirt daemon, but this is considered less harmful that forcefully killing running VMs.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/rpc/virnetdaemon.c | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+)
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index 8cc7af1182..3ddb9b5404 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -78,6 +78,9 @@ struct _virNetDaemon { virNetDaemonShutdownCallback shutdownPrepareCb; virNetDaemonShutdownCallback shutdownWaitCb; virThread *shutdownPreserveThread; + unsigned long long preserveStart; + unsigned int preserveExtended; + int preserveTimer; int quitTimer; virNetDaemonQuitPhase quit; bool graceful; @@ -93,6 +96,14 @@ struct _virNetDaemon {
static virClass *virNetDaemonClass;
+/* + * The minimum additional shutdown time (secs) we should ask + * systemd to allow, while state preservation operations + * are running. A timer will run every 5 seconds, and + * ensure at least this much extra time is requested + */ +#define VIR_NET_DAEMON_PRESERVE_MIN_TIME 30 + static int daemonServerClose(void *payload, const char *key G_GNUC_UNUSED, @@ -162,6 +173,7 @@ virNetDaemonNew(void) if (virEventRegisterDefaultImpl() < 0) goto error;
+ dmn->preserveTimer = -1; dmn->autoShutdownTimerID = -1;
#ifndef WIN32 @@ -727,6 +739,42 @@ daemonShutdownWait(void *opaque) } }
+static void +virNetDaemonPreserveTimer(int timerid G_GNUC_UNUSED, + void *opaque) +{ + virNetDaemon *dmn = opaque; + VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + unsigned long long now = g_get_monotonic_time(); + unsigned long long delta; + + if (dmn->quit != VIR_NET_DAEMON_QUIT_PRESERVING) + return; + + VIR_DEBUG("Started at %llu now %llu extended %u", + dmn->preserveStart, now, dmn->preserveExtended); + + /* Time since start of preserving state in usec */ + delta = now - dmn->preserveStart; + /* Converts to secs */ + delta /= (1000ull * 1000ull); + + /* Want extra seconds grace to ensure this timer fires + * again before system timeout expires, under high + * load conditions */ + delta += VIR_NET_DAEMON_PRESERVE_MIN_TIME; + + /* Deduct any extension we've previously asked for */ + delta -= dmn->preserveExtended; + + /* Tell systemd how much more we need to extend by */ + virSystemdNotifyExtendTimeout(delta); + dmn->preserveExtended += delta; + + VIR_DEBUG("Extended by %llu", delta); +}
Assume: preserveStart = 0; preserveExtend = 30; /* from the initialization */ Calls: by assumption of starting time above above (all in seconds) the notify timeout simplifies to: notify(delta) = now + 30 - preserveExtend Iteration 0 is the direct call, all others are via timer iter now() notify(delta) preserveExtend 0 0 30 (hardcoded) 30 1 5 5 35 2 10 5 40 3 15 5 45 4 20 5 50 [...] 'man sd_notify' says for EXTEND_TIMEOUT_USEC=... The value specified is a time in microseconds during which the service must send a new message. So this means: - initial iteration indeed extends by 30 seconds - any subsequent iteration extends only by 5 - the timer is also 5 seconds, so it's likely to instantly timeout on second iteration - the complicated algorithm doesn't seem to be necessary, all you want is a constant which is 'timer_interval+tolerance'
static void virNetDaemonQuitTimer(int timerid G_GNUC_UNUSED, void *opaque) @@ -781,11 +829,21 @@ virNetDaemonRun(virNetDaemon *dmn)
if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED) { VIR_DEBUG("Process quit request"); + virSystemdNotifyStopping(); virHashForEach(dmn->servers, daemonServerClose, NULL);
if (dmn->shutdownPreserveThread) { VIR_DEBUG("Shutdown preserve thread running"); dmn->quit = VIR_NET_DAEMON_QUIT_PRESERVING; + dmn->preserveStart = g_get_monotonic_time(); + dmn->preserveExtended = VIR_NET_DAEMON_PRESERVE_MIN_TIME;
^^^ assumptions
+ virSystemdNotifyExtendTimeout(dmn->preserveExtended);
iter 0
+ if ((dmn->preserveTimer = virEventAddTimeout(5 * 1000, + virNetDaemonPreserveTimer, + dmn, NULL)) < 0) {
subsequent iters via timer
+ VIR_WARN("Failed to register preservation timer"); + /* hope for the best */ + } } else { VIR_DEBUG("Ready to shutdown"); dmn->quit = VIR_NET_DAEMON_QUIT_READY; @@ -866,6 +924,10 @@ static void virNetDaemonPreserveWorker(void *opaque) dmn->quit = VIR_NET_DAEMON_QUIT_READY; } g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + if (dmn->preserveTimer != -1) { + virEventRemoveTimeout(dmn->preserveTimer); + dmn->preserveTimer = -1; + } }
VIR_DEBUG("End preserve dmn=%p", dmn); -- 2.47.1

On Mon, Feb 03, 2025 at 02:17:37PM +0100, Peter Krempa wrote:
On Wed, Jan 08, 2025 at 19:42:58 +0000, Daniel P. Berrangé wrote:
The service unit "TimeoutStopSec" setting controls how long systemd waits for a service to stop before aggressively killing it, defaulting to 30 seconds if not set.
When we're processing shutdown of VMs in response to OS shutdown, we very likely need more than 30 seconds to complete this job, and can not stop the daemon during this time.
To avoid being prematurely killed, setup a timer that repeatedly extends the "TimeoutStopSec" value while stop of running VMs is arranged.
This does mean if libvirt hangs while stoppping VMs, systemd won't get to kill the libvirt daemon, but this is considered less harmful that forcefully killing running VMs.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/rpc/virnetdaemon.c | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+)
diff --git a/src/rpc/virnetdaemon.c b/src/rpc/virnetdaemon.c index 8cc7af1182..3ddb9b5404 100644 --- a/src/rpc/virnetdaemon.c +++ b/src/rpc/virnetdaemon.c @@ -78,6 +78,9 @@ struct _virNetDaemon { virNetDaemonShutdownCallback shutdownPrepareCb; virNetDaemonShutdownCallback shutdownWaitCb; virThread *shutdownPreserveThread; + unsigned long long preserveStart; + unsigned int preserveExtended; + int preserveTimer; int quitTimer; virNetDaemonQuitPhase quit; bool graceful; @@ -93,6 +96,14 @@ struct _virNetDaemon {
static virClass *virNetDaemonClass;
+/* + * The minimum additional shutdown time (secs) we should ask + * systemd to allow, while state preservation operations + * are running. A timer will run every 5 seconds, and + * ensure at least this much extra time is requested + */ +#define VIR_NET_DAEMON_PRESERVE_MIN_TIME 30 + static int daemonServerClose(void *payload, const char *key G_GNUC_UNUSED, @@ -162,6 +173,7 @@ virNetDaemonNew(void) if (virEventRegisterDefaultImpl() < 0) goto error;
+ dmn->preserveTimer = -1; dmn->autoShutdownTimerID = -1;
#ifndef WIN32 @@ -727,6 +739,42 @@ daemonShutdownWait(void *opaque) } }
+static void +virNetDaemonPreserveTimer(int timerid G_GNUC_UNUSED, + void *opaque) +{ + virNetDaemon *dmn = opaque; + VIR_LOCK_GUARD lock = virObjectLockGuard(dmn); + unsigned long long now = g_get_monotonic_time(); + unsigned long long delta; + + if (dmn->quit != VIR_NET_DAEMON_QUIT_PRESERVING) + return; + + VIR_DEBUG("Started at %llu now %llu extended %u", + dmn->preserveStart, now, dmn->preserveExtended); + + /* Time since start of preserving state in usec */ + delta = now - dmn->preserveStart; + /* Converts to secs */ + delta /= (1000ull * 1000ull); + + /* Want extra seconds grace to ensure this timer fires + * again before system timeout expires, under high + * load conditions */ + delta += VIR_NET_DAEMON_PRESERVE_MIN_TIME; + + /* Deduct any extension we've previously asked for */ + delta -= dmn->preserveExtended; + + /* Tell systemd how much more we need to extend by */ + virSystemdNotifyExtendTimeout(delta); + dmn->preserveExtended += delta; + + VIR_DEBUG("Extended by %llu", delta); +}
Assume:
preserveStart = 0; preserveExtend = 30; /* from the initialization */
Calls:
by assumption of starting time above above (all in seconds) the notify timeout simplifies to:
notify(delta) = now + 30 - preserveExtend
Iteration 0 is the direct call, all others are via timer
iter now() notify(delta) preserveExtend 0 0 30 (hardcoded) 30 1 5 5 35 2 10 5 40 3 15 5 45 4 20 5 50
[...]
'man sd_notify' says for EXTEND_TIMEOUT_USEC=...
The value specified is a time in microseconds during which the service must send a new message.
This is the bit I interpreted wrong. I was thinking that the EXTEND_TIMEOUT_USEC= is added to the default service stop timeout, and we must send another message before that overall timeout is hit. The actual semantics you point out are in fact much more useful and easier to deal with :-)
So this means: - initial iteration indeed extends by 30 seconds - any subsequent iteration extends only by 5 - the timer is also 5 seconds, so it's likely to instantly timeout on second iteration - the complicated algorithm doesn't seem to be necessary, all you want is a constant which is 'timer_interval+tolerance'
I'll just use EXTEND_TIMEOUT_USEC set to 10 seconds, and update it every 5 seconds.
static void virNetDaemonQuitTimer(int timerid G_GNUC_UNUSED, void *opaque) @@ -781,11 +829,21 @@ virNetDaemonRun(virNetDaemon *dmn)
if (dmn->quit == VIR_NET_DAEMON_QUIT_REQUESTED) { VIR_DEBUG("Process quit request"); + virSystemdNotifyStopping(); virHashForEach(dmn->servers, daemonServerClose, NULL);
if (dmn->shutdownPreserveThread) { VIR_DEBUG("Shutdown preserve thread running"); dmn->quit = VIR_NET_DAEMON_QUIT_PRESERVING; + dmn->preserveStart = g_get_monotonic_time(); + dmn->preserveExtended = VIR_NET_DAEMON_PRESERVE_MIN_TIME;
^^^ assumptions
+ virSystemdNotifyExtendTimeout(dmn->preserveExtended);
iter 0
+ if ((dmn->preserveTimer = virEventAddTimeout(5 * 1000, + virNetDaemonPreserveTimer, + dmn, NULL)) < 0) {
subsequent iters via timer
+ VIR_WARN("Failed to register preservation timer"); + /* hope for the best */ + } } else { VIR_DEBUG("Ready to shutdown"); dmn->quit = VIR_NET_DAEMON_QUIT_READY; @@ -866,6 +924,10 @@ static void virNetDaemonPreserveWorker(void *opaque) dmn->quit = VIR_NET_DAEMON_QUIT_READY; } g_clear_pointer(&dmn->shutdownPreserveThread, g_free); + if (dmn->preserveTimer != -1) { + virEventRemoveTimeout(dmn->preserveTimer); + dmn->preserveTimer = -1; + } }
VIR_DEBUG("End preserve dmn=%p", dmn); -- 2.47.1
With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Since processing running VMs on OS shutdown can take a while, it is beneficial to send systemd status messages about the progress. Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 2f89b8c841..421cc16574 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -30,6 +30,7 @@ #include "datatypes.h" #include "driver.h" #include "virlog.h" +#include "virsystemd.h" #define VIR_FROM_THIS VIR_FROM_DOMAIN @@ -791,6 +792,8 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) (!transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) continue; + virSystemdNotifyStatus("Suspending '%s' (%zu of %d)", + virDomainGetName(domains[i]), i + 1, numDomains); /* * Pause all VMs to make them stop dirtying pages, * so save is quicker. We remember if any VMs were @@ -808,6 +811,9 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) } for (i = 0; i < numDomains; i++) { + virSystemdNotifyStatus("Saving '%s' (%zu of %d)", + virDomainGetName(domains[i]), i + 1, numDomains); + if (virDomainManagedSave(domains[i], flags[i]) < 0) { VIR_WARN("Unable to perform managed save of '%s': %s", virDomainGetName(domains[i]), @@ -829,6 +835,9 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) (!transient[i] && cfg->tryShutdown == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) continue; + virSystemdNotifyStatus("Shutting down '%s' (%zu of %d)", + virDomainGetName(domains[i]), i + 1, numDomains); + if (virDomainShutdown(domains[i]) < 0) { VIR_WARN("Unable to perform graceful shutdown of '%s': %s", virDomainGetName(domains[i]), @@ -838,6 +847,8 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) } timer = g_timer_new(); + virSystemdNotifyStatus("Waiting %d secs for VM shutdown completion", + cfg->waitShutdownSecs); while (1) { bool anyRunning = false; for (i = 0; i < numDomains; i++) { @@ -874,10 +885,14 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) (!transient[i] && cfg->poweroff == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) continue; + virSystemdNotifyStatus("Destroying '%s' (%zu of %d)", + virDomainGetName(domains[i]), i + 1, numDomains); virDomainDestroy(domains[i]); } } + virSystemdNotifyStatus("Processed %d domains", numDomains); + cleanup: if (domains) { for (i = 0; i < numDomains; i++) { -- 2.47.1

On Wed, Jan 08, 2025 at 19:42:59 +0000, Daniel P. Berrangé wrote:
Since processing running VMs on OS shutdown can take a while, it is beneficial to send systemd status messages about the progress.
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- src/hypervisor/domain_driver.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/src/hypervisor/domain_driver.c b/src/hypervisor/domain_driver.c index 2f89b8c841..421cc16574 100644 --- a/src/hypervisor/domain_driver.c +++ b/src/hypervisor/domain_driver.c @@ -30,6 +30,7 @@ #include "datatypes.h" #include "driver.h" #include "virlog.h" +#include "virsystemd.h"
#define VIR_FROM_THIS VIR_FROM_DOMAIN
@@ -791,6 +792,8 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) (!transient[i] && cfg->trySave == VIR_DOMAIN_DRIVER_AUTO_SHUTDOWN_SCOPE_TRANSIENT)) continue;
+ virSystemdNotifyStatus("Suspending '%s' (%zu of %d)", + virDomainGetName(domains[i]), i + 1, numDomains);
As discussed in other sub-thread these messages are what I wanted, but you'll need to make sure that they end up in the system log, otherwise they won't help much. Also specifically for suspend I'm not sure if this message makes too much sense separately as it's just a sub-step of the 'save' operation.
/* * Pause all VMs to make them stop dirtying pages, * so save is quicker. We remember if any VMs were @@ -808,6 +811,9 @@ virDomainDriverAutoShutdown(virDomainDriverAutoShutdownConfig *cfg) }
for (i = 0; i < numDomains; i++) { + virSystemdNotifyStatus("Saving '%s' (%zu of %d)", + virDomainGetName(domains[i]), i + 1, numDomains); + if (virDomainManagedSave(domains[i], flags[i]) < 0) { VIR_WARN("Unable to perform managed save of '%s': %s",
Also these should IMO be delivered using exactly the same mechanism as the success messages as well (in addition to being a warning perhaps).

On Wed, Jan 08, 2025 at 19:42:33 +0000, Daniel P. Berrangé wrote: This series really deserves a NEWS entry where you can explain how to use this instead of libvirt-guests so that users are motivated to switch.
participants (2)
-
Daniel P. Berrangé
-
Peter Krempa