[libvirt] [PATCH 0/2] qemu: Add pre-migration hook

Current dxml parameter of virDomainMigrate{,ToURI}2 requires the caller to have deep knowlege of the environment on the target machine. In some cases, this may be impractical or even impossible to achieve. By adding per-migration hook which may filter incoming domain XML and change it appropriately called during the Prepare phase the destination host may filter incoming domain XMLs and change them to fit local environment. For example, such hook may relocate disk images. The hook is not allowed to make guest-visible changes to a domain XML. Jiri Denemark (2): hooks: Add support for capturing hook output qemu: Add pre-migration hook daemon/libvirtd.c | 6 +++--- docs/hooks.html.in | 35 +++++++++++++++++++++++------------ src/lxc/lxc_driver.c | 6 ++++-- src/qemu/qemu_migration.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_process.c | 12 ++++++++---- src/util/hooks.c | 25 ++++++++++++++++++++----- src/util/hooks.h | 3 ++- 7 files changed, 100 insertions(+), 27 deletions(-) -- 1.7.8.5

Hooks may now be used as filters. --- daemon/libvirtd.c | 6 +++--- src/lxc/lxc_driver.c | 6 ++++-- src/qemu/qemu_process.c | 12 ++++++++---- src/util/hooks.c | 22 ++++++++++++++++++---- src/util/hooks.h | 2 +- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index b1b542b..52e80fa 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -1148,7 +1148,7 @@ static void daemonReloadHandler(virNetServerPtr srv ATTRIBUTE_UNUSED, { VIR_INFO("Reloading configuration on SIGHUP"); virHookCall(VIR_HOOK_DRIVER_DAEMON, "-", - VIR_HOOK_DAEMON_OP_RELOAD, SIGHUP, "SIGHUP", NULL); + VIR_HOOK_DAEMON_OP_RELOAD, SIGHUP, "SIGHUP", NULL, NULL); if (virStateReload() < 0) VIR_WARN("Error while reloading drivers"); } @@ -1571,7 +1571,7 @@ int main(int argc, char **argv) { * an error ? */ virHookCall(VIR_HOOK_DRIVER_DAEMON, "-", VIR_HOOK_DAEMON_OP_START, - 0, "start", NULL); + 0, "start", NULL, NULL); if (daemonSetupNetworking(srv, config, sock_file, sock_file_ro, @@ -1604,7 +1604,7 @@ int main(int argc, char **argv) { ret = 0; virHookCall(VIR_HOOK_DRIVER_DAEMON, "-", VIR_HOOK_DAEMON_OP_SHUTDOWN, - 0, "shutdown", NULL); + 0, "shutdown", NULL, NULL); cleanup: virNetServerProgramFree(remoteProgram); diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index d77afcc..d9cbd9e 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -1115,7 +1115,8 @@ static void lxcVmCleanup(lxc_driver_t *driver, /* we can't stop the operation even if the script raised an error */ virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name, - VIR_HOOK_LXC_OP_STOPPED, VIR_HOOK_SUBOP_END, NULL, xml); + VIR_HOOK_LXC_OP_STOPPED, VIR_HOOK_SUBOP_END, + NULL, xml, NULL); VIR_FREE(xml); } @@ -1632,7 +1633,8 @@ lxcBuildControllerCmd(lxc_driver_t *driver, int hookret; hookret = virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name, - VIR_HOOK_LXC_OP_START, VIR_HOOK_SUBOP_BEGIN, NULL, xml); + VIR_HOOK_LXC_OP_START, VIR_HOOK_SUBOP_BEGIN, + NULL, xml, NULL); VIR_FREE(xml); /* diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 41218de..36e5ce6 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -3120,7 +3120,8 @@ int qemuProcessStart(virConnectPtr conn, int hookret; hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, - VIR_HOOK_QEMU_OP_PREPARE, VIR_HOOK_SUBOP_BEGIN, NULL, xml); + VIR_HOOK_QEMU_OP_PREPARE, VIR_HOOK_SUBOP_BEGIN, + NULL, xml, NULL); VIR_FREE(xml); /* @@ -3305,7 +3306,8 @@ int qemuProcessStart(virConnectPtr conn, int hookret; hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, - VIR_HOOK_QEMU_OP_START, VIR_HOOK_SUBOP_BEGIN, NULL, xml); + VIR_HOOK_QEMU_OP_START, VIR_HOOK_SUBOP_BEGIN, + NULL, xml, NULL); VIR_FREE(xml); /* @@ -3726,7 +3728,8 @@ void qemuProcessStop(struct qemud_driver *driver, /* we can't stop the operation even if the script raised an error */ virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, - VIR_HOOK_QEMU_OP_STOPPED, VIR_HOOK_SUBOP_END, NULL, xml); + VIR_HOOK_QEMU_OP_STOPPED, VIR_HOOK_SUBOP_END, + NULL, xml, NULL); VIR_FREE(xml); } @@ -3819,7 +3822,8 @@ retry: /* we can't stop the operation even if the script raised an error */ virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, - VIR_HOOK_QEMU_OP_RELEASE, VIR_HOOK_SUBOP_END, NULL, xml); + VIR_HOOK_QEMU_OP_RELEASE, VIR_HOOK_SUBOP_END, + NULL, xml, NULL); VIR_FREE(xml); } diff --git a/src/util/hooks.c b/src/util/hooks.c index 110a94b..8c16a3a 100644 --- a/src/util/hooks.c +++ b/src/util/hooks.c @@ -173,7 +173,7 @@ virHookPresent(int driver) { return(1); } -/* +/** * virHookCall: * @driver: the driver number (from virHookDriver enum) * @id: an id for the object '-' if non available for example on daemon hooks @@ -181,17 +181,26 @@ virHookPresent(int driver) { * @sub_op: a sub_operation, currently unused * @extra: optional string information * @input: extra input given to the script on stdin + * @output: optional address of variable to store malloced result buffer * * Implement a hook call, where the external script for the driver is * called with the given information. This is a synchronous call, we wait for - * execution completion + * execution completion. If @output is non-NULL, *output is guaranteed to be + * allocated after successful virHookCall, and is best-effort allocated after + * failed virHookCall; the caller is responsible for freeing *output. * * Returns: 0 if the execution succeeded, 1 if the script was not found or * invalid parameters, and -1 if script returned an error */ int -virHookCall(int driver, const char *id, int op, int sub_op, const char *extra, - const char *input) { +virHookCall(int driver, + const char *id, + int op, + int sub_op, + const char *extra, + const char *input, + char **output) +{ int ret; int exitstatus; char *path; @@ -200,6 +209,9 @@ virHookCall(int driver, const char *id, int op, int sub_op, const char *extra, const char *opstr; const char *subopstr; + if (output) + *output = NULL; + if ((driver < VIR_HOOK_DRIVER_DAEMON) || (driver >= VIR_HOOK_DRIVER_LAST)) return(1); @@ -257,6 +269,8 @@ virHookCall(int driver, const char *id, int op, int sub_op, const char *extra, if (input) virCommandSetInputBuffer(cmd, input); + if (output) + virCommandSetOutputBuffer(cmd, output); ret = virCommandRun(cmd, &exitstatus); if (ret == 0 && exitstatus != 0) { diff --git a/src/util/hooks.h b/src/util/hooks.h index fd7411c..a53ac2c 100644 --- a/src/util/hooks.h +++ b/src/util/hooks.h @@ -72,6 +72,6 @@ int virHookInitialize(void); int virHookPresent(int driver); int virHookCall(int driver, const char *id, int op, int sub_op, - const char *extra, const char *input); + const char *extra, const char *input, char **output); #endif /* __VIR_HOOKS_H__ */ -- 1.7.8.5

On 02/28/2012 02:49 PM, Jiri Denemark wrote:
Hooks may now be used as filters. --- daemon/libvirtd.c | 6 +++--- src/lxc/lxc_driver.c | 6 ++++-- src/qemu/qemu_process.c | 12 ++++++++---- src/util/hooks.c | 22 ++++++++++++++++++---- src/util/hooks.h | 2 +- 5 files changed, 34 insertions(+), 14 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

This hook is called during the Prepare phase on destination host and may be used for changing domain XML. --- docs/hooks.html.in | 35 +++++++++++++++++++++++------------ src/qemu/qemu_migration.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/util/hooks.c | 3 ++- src/util/hooks.h | 1 + 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/docs/hooks.html.in b/docs/hooks.html.in index 890359e..6c82c6d 100644 --- a/docs/hooks.html.in +++ b/docs/hooks.html.in @@ -120,6 +120,16 @@ called again, <span class="since">since 0.9.0</span>, to allow any additional resource cleanup:<br/> <pre>/etc/libvirt/hooks/qemu guest_name release end -</pre></li> + <li><span class="since">Since 0.9.11</span>, the qemu hook script + is also called at the beginning of incoming migration. It is called + as: <pre>/etc/libvirt/hooks/qemu guest_name migrate begin -</pre> + with domain XML sent to standard input of the script. In this case, + the script acts as a filter and is supposed to modify the domain + XML and print it out on its standard output. Empty output is + identical to copying the input XML without changing it. In case the + script returns failure or the output XML is not valid, incoming + migration will be canceled. This hook may be used to, e.g., change + location of disk images for incoming domains.</li> </ul> <h5><a name="lxc">/etc/libvirt/hooks/lxc</a></h5> @@ -161,19 +171,20 @@ source and destination hosts:</p> <ol> <li>At the beginning of the migration, the <i>qemu</i> hook script on - the <b>destination</b> host is executed with the "start" - operation.<br/><br/></li> - <li>If this hook script returns indicating success (error code 0), the - migration continues. Any other return code indicates failure, and - the migration is aborted.<br/><br/></li> - <li>The QEMU guest is then migrated to the destination host.<br/> - <br/></li> + the <b>destination</b> host is executed with the "migrate" + operation.</li> + <li>Before QEMU process is spawned, the two operations ("prepare" and + "start") called for domain start are executed on + <b>destination</b> host.</li> + <li>If any of these hook script executions returns indicating success + (error code 0), the migration continues. Any other return code + indicates failure, and the migration is aborted.</li> + <li>The QEMU guest is then migrated to the destination host.</li> <li>Unless an error occurs during the migration process, the <i>qemu</i> - hook script on the <b>source</b> host is then executed with the "stopped" - operation, to indicate it is no longer running on this - host.<br/><br/> - Regardless of the return code from this hook script, the migration - is not aborted as it has already been performed.</li> + hook script on the <b>source</b> host is then executed with the + "stopped" and "release" operations to indicate it is no longer + running on this host. Regardless of the return codes, the + migration is not aborted as it has already been performed.</li> </ol> <br/> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 7df2d4f..d00bd61 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -47,6 +47,7 @@ #include "rpc/virnetsocket.h" #include "storage_file.h" #include "viruri.h" +#include "hooks.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -1130,6 +1131,7 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, qemuMigrationCookiePtr mig = NULL; bool tunnel = !!st; char *origname = NULL; + char *xmlout = NULL; if (virTimeMillisNow(&now) < 0) return -1; @@ -1150,6 +1152,43 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, goto cleanup; } + /* Let migration hook filter domain XML */ + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + char *xml = virDomainDefFormat(def, VIR_DOMAIN_XML_SECURE); + int hookret; + + hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, def->name, + VIR_HOOK_QEMU_OP_MIGRATE, VIR_HOOK_SUBOP_BEGIN, + NULL, xml, &xmlout); + VIR_FREE(xml); + + if (hookret < 0) { + goto cleanup; + } else if (hookret == 0) { + if (!*xmlout) { + VIR_DEBUG("Migrate hook filter returned nothing; using the" + " original XML"); + } else { + virDomainDefPtr newdef; + + VIR_DEBUG("Using hook-filtered domain XML: %s", xmlout); + newdef = virDomainDefParseString(driver->caps, xmlout, + QEMU_EXPECTED_VIRT_TYPES, + VIR_DOMAIN_XML_INACTIVE); + if (!newdef) + goto cleanup; + + if (!virDomainDefCheckABIStability(def, newdef)) { + virDomainDefFree(newdef); + goto cleanup; + } + + virDomainDefFree(def); + def = newdef; + } + } + } + if (virDomainObjIsDuplicate(&driver->domains, def, 1) < 0) goto cleanup; @@ -1244,6 +1283,7 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, cleanup: VIR_FREE(origname); + VIR_FREE(xmlout); virDomainDefFree(def); VIR_FORCE_CLOSE(dataFD[0]); VIR_FORCE_CLOSE(dataFD[1]); diff --git a/src/util/hooks.c b/src/util/hooks.c index 8c16a3a..b0c15fd 100644 --- a/src/util/hooks.c +++ b/src/util/hooks.c @@ -73,7 +73,8 @@ VIR_ENUM_IMPL(virHookQemuOp, VIR_HOOK_QEMU_OP_LAST, "start", "stopped", "prepare", - "release") + "release", + "migrate") VIR_ENUM_IMPL(virHookLxcOp, VIR_HOOK_LXC_OP_LAST, "start", diff --git a/src/util/hooks.h b/src/util/hooks.h index a53ac2c..7fd29f6 100644 --- a/src/util/hooks.h +++ b/src/util/hooks.h @@ -56,6 +56,7 @@ enum virHookQemuOpType { VIR_HOOK_QEMU_OP_STOPPED, /* domain has stopped */ VIR_HOOK_QEMU_OP_PREPARE, /* domain startup initiated */ VIR_HOOK_QEMU_OP_RELEASE, /* domain destruction is over */ + VIR_HOOK_QEMU_OP_MIGRATE, /* domain is being migrated */ VIR_HOOK_QEMU_OP_LAST, }; -- 1.7.8.5

On 02/28/2012 02:49 PM, Jiri Denemark wrote:
This hook is called during the Prepare phase on destination host and may be used for changing domain XML. --- docs/hooks.html.in | 35 +++++++++++++++++++++++------------ src/qemu/qemu_migration.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/util/hooks.c | 3 ++- src/util/hooks.h | 1 + 4 files changed, 66 insertions(+), 13 deletions(-)
diff --git a/docs/hooks.html.in b/docs/hooks.html.in index 890359e..6c82c6d 100644 --- a/docs/hooks.html.in +++ b/docs/hooks.html.in @@ -120,6 +120,16 @@ called again, <span class="since">since 0.9.0</span>, to allow any additional resource cleanup:<br/> <pre>/etc/libvirt/hooks/qemu guest_name release end -</pre></li> + <li><span class="since">Since 0.9.11</span>, the qemu hook script + is also called at the beginning of incoming migration. It is called + as: <pre>/etc/libvirt/hooks/qemu guest_name migrate begin -</pre> + with domain XML sent to standard input of the script. In this case, + the script acts as a filter and is supposed to modify the domain + XML and print it out on its standard output. Empty output is + identical to copying the input XML without changing it. In case the + script returns failure or the output XML is not valid, incoming + migration will be canceled. This hook may be used to, e.g., change
I think this reads better as: s/used to, e.g., change/used, e.g., to change/
+ location of disk images for incoming domains.</li> </ul>
<h5><a name="lxc">/etc/libvirt/hooks/lxc</a></h5> @@ -161,19 +171,20 @@ source and destination hosts:</p> <ol> <li>At the beginning of the migration, the <i>qemu</i> hook script on - the <b>destination</b> host is executed with the "start" - operation.<br/><br/></li> - <li>If this hook script returns indicating success (error code 0), the - migration continues. Any other return code indicates failure, and - the migration is aborted.<br/><br/></li> - <li>The QEMU guest is then migrated to the destination host.<br/> - <br/></li> + the <b>destination</b> host is executed with the "migrate" + operation.</li> + <li>Before QEMU process is spawned, the two operations ("prepare" and + "start") called for domain start are executed on + <b>destination</b> host.</li> + <li>If any of these hook script executions returns indicating success + (error code 0), the migration continues. Any other return code + indicates failure, and the migration is aborted.</li>
This reads awkwardly - it makes it sound like 'prepare' exiting with 0 makes the overall operation succeed even if 'start' exits with non-zero. I'd change it to: If both of these hook script executions exit successfully (exit status 0), the migration continues. Any other exit code indicates failure, and the migration is aborted.
@@ -1150,6 +1152,43 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, goto cleanup; }
+ /* Let migration hook filter domain XML */ + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + char *xml = virDomainDefFormat(def, VIR_DOMAIN_XML_SECURE); + int hookret; + + hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, def->name, + VIR_HOOK_QEMU_OP_MIGRATE, VIR_HOOK_SUBOP_BEGIN, + NULL, xml, &xmlout);
Needs to check for xml being NULL on OOM before virHookCall. ACK with those issues fixed. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Tue, Feb 28, 2012 at 17:04:29 -0700, Eric Blake wrote:
On 02/28/2012 02:49 PM, Jiri Denemark wrote:
This hook is called during the Prepare phase on destination host and may be used for changing domain XML. --- docs/hooks.html.in | 35 +++++++++++++++++++++++------------ src/qemu/qemu_migration.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/util/hooks.c | 3 ++- src/util/hooks.h | 1 + 4 files changed, 66 insertions(+), 13 deletions(-) ... @@ -1150,6 +1152,43 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, goto cleanup; }
+ /* Let migration hook filter domain XML */ + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + char *xml = virDomainDefFormat(def, VIR_DOMAIN_XML_SECURE); + int hookret; + + hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, def->name, + VIR_HOOK_QEMU_OP_MIGRATE, VIR_HOOK_SUBOP_BEGIN, + NULL, xml, &xmlout);
Needs to check for xml being NULL on OOM before virHookCall.
Oops, I just copy&pasted the code from other places, where domain XML is probably not so important so we don't care if the hook gets it or not.
ACK with those issues fixed.
I fixed the issues and pushed this small series. Thanks. Jirka
participants (2)
-
Eric Blake
-
Jiri Denemark