On Sat, Mar 21, 2026 at 15:09:04 +0100, Guy Godfroy wrote:
> When outgoing migration fails after the "migrate-outgoing begin source"
> hook was executed, the source host has no way to be notified and cannot
> undo changes made in the begin hook (e.g., restoring exclusive storage
> locks after switching to shared mode).
> 
> Add a hook call on the source host when migration fails, using the
> "migrate-outgoing" operation with "end" as the sub-operation and
> "failed" as the extra argument. The hook output and return code are
> ignored since migration has already failed.
> 
> For qemu, the fail hook is called in the confirm phase when retcode != 0.
> For libxl, the fail hook is called in both the perform phase (when
> perform fails, since confirm is not called) and the confirm phase when
> cancelled is true.
> 
> Resolves: https://gitlab.com/libvirt/libvirt/-/issues/37
> Signed-off-by: Guy Godfroy <[email protected]>
> ---
>  docs/hooks.rst              | 27 ++++++++++++++++++++++++++-
>  src/libxl/libxl_migration.c | 22 ++++++++++++++++++++++
>  src/qemu/qemu_migration.c   | 12 ++++++++++++
>  3 files changed, 60 insertions(+), 1 deletion(-)
> 
> diff --git a/docs/hooks.rst b/docs/hooks.rst
> index cff1711536..4fe02b7c28 100644
> --- a/docs/hooks.rst
> +++ b/docs/hooks.rst
> @@ -269,6 +269,17 @@ operation. There is no specific operation to indicate a 
> "restart" is occurring.
>     script returns failure, the migration will be canceled. This hook may be 
> used,
>     e.g., to change storage lock mode from exclusive to shared before 
> migration.
>  
> +-  :since:`Since 12.3.0`, when migration fails on the source host, the qemu
> +   hook script is called to allow the source host to undo any changes made in
> +   the begin hook. It is called as:
> +
> +   ::
> +
> +      /etc/libvirt/hooks/qemu guest_name migrate-outgoing end failed
> +
> +   with domain XML sent to standard input of the script. Any output and the
> +   return code of the script are ignored.
> +
>  -  :since:`Since 1.2.9`, the qemu hook script is also called when restoring a
>     saved image either via the API or automatically when restoring a managed 
> save
>     machine. It is called as:
> @@ -454,6 +465,17 @@ operation. There is no specific operation to indicate a 
> "restart" is occurring.
>     script returns failure, the migration will be canceled. This hook may be 
> used,
>     e.g., to change storage lock mode from exclusive to shared before 
> migration.
>  
> +-  :since:`Since 12.3.0`, when migration fails on the source host, the libxl
> +   hook script is called to allow the source host to undo any changes made in
> +   the begin hook. It is called as:
> +
> +   ::
> +
> +      /etc/libvirt/hooks/libxl guest_name migrate-outgoing end failed
> +
> +   with domain XML sent to standard input of the script. Any output and the
> +   return code of the script are ignored.
> +
>  -  :since:`Since 6.5.0`, you can also place several hook scripts in the
>     directory ``/etc/libvirt/hooks/libxl.d/``. They are executed in 
> alphabetical
>     order after main script. In this case each script also acts as filter and 
> can
> @@ -599,7 +621,10 @@ destination hosts:
>     called for domain start are executed on **destination** host.
>  #. If all of these hook script executions exit successfully (exit status 0),
>     the migration continues. Any other exit code indicates failure, and the
> -   migration is aborted.
> +   migration is aborted. If migration is aborted, the *qemu* hook script on
> +   the **source** host is executed with the "migrate-outgoing" operation,
> +   "end" sub-operation, and "failed" as the extra argument
> +   (:since:`since 12.3.0`). The return code is ignored.
>  #. The QEMU guest is then migrated to the destination host.
>  #. Unless an error occurs during the migration process, the *qemu* hook 
> script
>     on the **source** host is then executed with the "stopped" and "release"
> diff --git a/src/libxl/libxl_migration.c b/src/libxl/libxl_migration.c
> index be51cfd316..4e7e2bdbe9 100644
> --- a/src/libxl/libxl_migration.c
> +++ b/src/libxl/libxl_migration.c
> @@ -1237,6 +1237,17 @@ libxlDomainMigrationSrcPerform(libxlDriverPrivate 
> *driver,
>              VIR_WARN("Unable to release lease on %s", vm->def->name);
>          }
>      } else {
> +        /* Call hook to notify source host that migration failed */
> +        if (virHookPresent(VIR_HOOK_DRIVER_LIBXL)) {
> +            g_autofree char *hookxml = NULL;
> +
> +            if ((hookxml = virDomainDefFormat(vm->def, driver->xmlopt,
> +                                              VIR_DOMAIN_DEF_FORMAT_SECURE)))
> +                virHookCall(VIR_HOOK_DRIVER_LIBXL, vm->def->name,
> +                            VIR_HOOK_LIBXL_OP_MIGRATE_OUTGOING, 
> VIR_HOOK_SUBOP_END,
> +                            "failed", hookxml, NULL);
> +        }
> +
>          /*
>           * Confirm phase will not be executed if perform fails. End the
>           * job started in begin phase.
> @@ -1358,6 +1369,17 @@ libxlDomainMigrationSrcConfirm(libxlDriverPrivate 
> *driver,
>      virObjectEvent *event = NULL;
>  
>      if (cancelled) {
> +        /* Call hook to notify source host that migration failed */
> +        if (virHookPresent(VIR_HOOK_DRIVER_LIBXL)) {
> +            g_autofree char *hookxml = NULL;
> +
> +            if ((hookxml = virDomainDefFormat(vm->def, driver->xmlopt,
> +                                              VIR_DOMAIN_DEF_FORMAT_SECURE)))
> +                virHookCall(VIR_HOOK_DRIVER_LIBXL, vm->def->name,
> +                            VIR_HOOK_LIBXL_OP_MIGRATE_OUTGOING, 
> VIR_HOOK_SUBOP_END,
> +                            "failed", hookxml, NULL);
> +        }
> +
>          /* Resume lock process that was paused in MigrationSrcPerform */
>          virDomainLockProcessResume(driver->lockManager,
>                                     "xen:///system",
> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
> index e9ce2d8b8b..afb5161ea3 100644
> --- a/src/qemu/qemu_migration.c
> +++ b/src/qemu/qemu_migration.c
> @@ -4218,6 +4218,18 @@ qemuMigrationSrcConfirmPhase(virQEMUDriver *driver,
>              qemuDomainSetMaxMemLock(vm, 0, &priv->preMigrationMemlock);
>          }
>  
> +        /* Call hook to notify source host that migration failed */
> +        if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {
> +            g_autofree char *xml = NULL;
> +
> +            if ((xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
> +                                               vm->def, priv->origCPU,
> +                                               false, true)))
> +                virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name,
> +                            VIR_HOOK_QEMU_OP_MIGRATE_OUTGOING, 
> VIR_HOOK_SUBOP_END,
> +                            "failed", xml, NULL);
> +        }
> +


There are surely more failure points in qemu migration. There's at
least:

 -  qemuMigrationSrcPerformJob
 - qemuMigrationSrcPerformPhase
 - qemuProcessRecoverMigration

Maybe:
 - qemuMigrationAnyConnectionClosed


You might want to see if qemuMigrationSrcRestoreDomainState isn't a
better place to call it.

Reply via email to