On Sat, Mar 21, 2026 at 15:09:03 +0100, Guy Godfroy wrote:
> Currently, the hook script with the "migrate" operation is only called
> on the destination host at the beginning of incoming migration. This
> makes it impossible for the source host to prepare for migration, for
> example to change storage locks from exclusive to shared mode when using
> shared LVM storage with lvmlockd.
> 
> Add a hook call on the source host at the beginning of outgoing
> migration using the new "migrate-outgoing" operation. The hook is called
> with "source" as the extra argument and "begin" as the sub-operation.

I don't think having the 'source' extra argument makes sense once we
have the whole separate 'migrate-outgoing' operation.

> The script is not used as a filter; its output is ignored. If the script
> returns failure, the migration is canceled.
> 
> The same change is also applied to the libxl driver for consistency.
> 
> Signed-off-by: Guy Godfroy <[email protected]>
> ---
>  docs/hooks.rst              | 32 ++++++++++++++++++++++++++++++--
>  src/libxl/libxl_migration.c | 17 +++++++++++++++++
>  src/qemu/qemu_migration.c   | 17 +++++++++++++++++
>  3 files changed, 64 insertions(+), 2 deletions(-)
> 
> diff --git a/docs/hooks.rst b/docs/hooks.rst
> index 0310e0d82c..cff1711536 100644
> --- a/docs/hooks.rst
> +++ b/docs/hooks.rst
> @@ -257,6 +257,18 @@ operation. There is no specific operation to indicate a 
> "restart" is occurring.
>     is not valid, incoming migration will be canceled. This hook may be used,
>     e.g., to change location of disk images for incoming domains.
>  
> +-  :since:`Since 12.3.0`, the qemu hook script is also called at the 
> beginning
> +   of outgoing migration on the **source** host. It is called as:
> +
> +   ::
> +
> +      /etc/libvirt/hooks/qemu guest_name migrate-outgoing begin source
> +
> +   with domain XML sent to standard input of the script. Unlike the 
> destination
> +   hook, this script does not act as a filter and any output is ignored. If 
> the
> +   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.

I'd refrain from giving any suggestions how to use the script. It's not
the only way to do things nor the suggested way to do thihgs.

> +
>  -  :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:
> @@ -430,6 +442,18 @@ operation. There is no specific operation to indicate a 
> "restart" is occurring.
>     is not valid, incoming migration will be canceled. This hook may be used,
>     e.g., to change location of disk images for incoming domains.
>  
> +-  :since:`Since 12.3.0`, the libxl hook script is also called at the 
> beginning
> +   of outgoing migration on the **source** host. It is called as:
> +
> +   ::
> +
> +      /etc/libvirt/hooks/libxl guest_name migrate-outgoing begin source
> +
> +   with domain XML sent to standard input of the script. Unlike the 
> destination
> +   hook, this script does not act as a filter and any output is ignored. If 
> the
> +   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 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
> @@ -566,10 +590,14 @@ Migration of a QEMU guest involves running hook scripts 
> on both the source and
>  destination hosts:
>  
>  #. At the beginning of the migration, the *qemu* hook script on the
> -   **destination** host is executed with the "migrate" operation.
> +   **source** host is executed with the "migrate-outgoing" operation and
> +   "source" as the extra argument (:since:`since 12.3.0`). This allows the
> +   source host to prepare for migration, e.g., changing storage locks.
> +#. Then, the *qemu* hook script on the **destination** host is executed
> +   with the "migrate" operation.
>  #. Before QEMU process is spawned, the two operations ("prepare" and "start")
>     called for domain start are executed on **destination** host.
> -#. If both of these hook script executions exit successfully (exit status 0),
> +#. 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.
>  #. The QEMU guest is then migrated to the destination host.
> diff --git a/src/libxl/libxl_migration.c b/src/libxl/libxl_migration.c
> index f5dee7627b..be51cfd316 100644
> --- a/src/libxl/libxl_migration.c
> +++ b/src/libxl/libxl_migration.c
> @@ -411,6 +411,23 @@ libxlDomainMigrationSrcBegin(virConnectPtr conn,
>      if (!libxlDomainMigrationIsAllowed(def))
>          goto endjob;
>  
> +    /* Call hook to allow source host to prepare for migration */
> +    if (virHookPresent(VIR_HOOK_DRIVER_LIBXL)) {
> +        g_autofree char *hookxml = NULL;
> +        int hookret;
> +
> +        if (!(hookxml = virDomainDefFormat(def, driver->xmlopt,
> +                                           VIR_DOMAIN_DEF_FORMAT_SECURE)))
> +            goto endjob;
> +
> +        hookret = virHookCall(VIR_HOOK_DRIVER_LIBXL, def->name,
> +                              VIR_HOOK_LIBXL_OP_MIGRATE_OUTGOING, 
> VIR_HOOK_SUBOP_BEGIN,
> +                              "source", hookxml, NULL);

the libxl driver has 'libxlDomainHookRun'. Any reason you've open-coded
this?

Additionally VIR_DOMAIN_XML_SECURE | VIR_DOMAIN_XML_MIGRATABLE, are used
to format the XML and you picked only _SECURE.


> +
> +        if (hookret < 0)
> +            goto endjob;
> +    }
> +
>      xml = virDomainDefFormat(def, driver->xmlopt, 
> VIR_DOMAIN_DEF_FORMAT_SECURE);
>      /* Valid xml means success! EndJob in the confirm phase */
>      if (xml)
> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
> index fec808ccfb..e9ce2d8b8b 100644
> --- a/src/qemu/qemu_migration.c
> +++ b/src/qemu/qemu_migration.c
> @@ -2870,6 +2870,23 @@ qemuMigrationSrcBeginPhase(virQEMUDriver *driver,
>           vm->newDef && !qemuDomainVcpuHotplugIsInOrder(vm->newDef)))
>          cookieFlags |= QEMU_MIGRATION_COOKIE_CPU_HOTPLUG;
>  
> +    /* Call hook to allow source host to prepare for migration */
> +    if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {
> +        g_autofree char *xml = NULL;
> +        int hookret;
> +
> +        if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps, vm->def,
> +                                            priv->origCPU, false, true)))
> +            return NULL;

This looks okay.


> +
> +        hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name,
> +                              VIR_HOOK_QEMU_OP_MIGRATE_OUTGOING, 
> VIR_HOOK_SUBOP_BEGIN,
> +                              "source", xml, NULL);
> +
> +        if (hookret < 0)
> +            return NULL;
> +    }
> +
>      if (qemuBlockNodesEnsureActive(vm, vm->job->asyncJob) < 0)

There's a trivial merge conflict with patches I've posted.


>          return NULL;

With the changes:

Reviewed-by: Peter Krempa <[email protected]>

Reply via email to