[PATCH 18/20] qemu: Add support for parallel save and restore

Jim Fehlig via Devel posted 20 patches 3 months, 2 weeks ago
[PATCH 18/20] qemu: Add support for parallel save and restore
Posted by Jim Fehlig via Devel 3 months, 2 weeks ago
Add support for parallel save and restore by mapping libvirt's
"parallel-connections" parameter to QEMU's "multifd-channels"
migration parameter.

Signed-off-by: Jim Fehlig <jfehlig@suse.com>
---
 src/qemu/qemu_driver.c           | 31 ++++++++++++++++++++-----------
 src/qemu/qemu_migration_params.c | 25 +++++++++++++++++++++++--
 src/qemu/qemu_migration_params.h |  5 ++++-
 3 files changed, 47 insertions(+), 14 deletions(-)

diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 0025bad6e7..fce599b321 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -2608,6 +2608,8 @@ qemuDomainSaveInternal(virQEMUDriver *driver,
                        int compressed,
                        virCommand *compressor,
                        const char *xmlin,
+                       virTypedParameterPtr params,
+                       int nparams,
                        unsigned int flags)
 {
     g_autofree char *xml = NULL;
@@ -2691,7 +2693,7 @@ qemuDomainSaveInternal(virQEMUDriver *driver,
     xml = NULL;
 
     mappedRam = data->header.features & QEMU_SAVE_FEATURE_MAPPED_RAM;
-    if (!(saveParams = qemuMigrationParamsForSave(mappedRam, flags)))
+    if (!(saveParams = qemuMigrationParamsForSave(params, nparams, mappedRam, flags)))
         goto endjob;
 
     ret = qemuSaveImageCreate(driver, vm, path, data, compressor,
@@ -2777,7 +2779,7 @@ qemuDomainManagedSaveHelper(virQEMUDriver *driver,
     VIR_INFO("Saving state of domain '%s' to '%s'", vm->def->name, path);
 
     if (qemuDomainSaveInternal(driver, vm, path, compressed,
-                               compressor, dxml, flags) < 0)
+                               compressor, dxml, NULL, 0, flags) < 0)
         return -1;
 
     vm->hasManagedSave = true;
@@ -2816,7 +2818,7 @@ qemuDomainSaveFlags(virDomainPtr dom, const char *path, const char *dxml,
         goto cleanup;
 
     ret = qemuDomainSaveInternal(driver, vm, path, compressed,
-                                 compressor, dxml, flags);
+                                 compressor, dxml, NULL, 0, flags);
 
  cleanup:
     virDomainObjEndAPI(&vm);
@@ -2846,13 +2848,16 @@ qemuDomainSaveParams(virDomainPtr dom,
 
     virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE |
                   VIR_DOMAIN_SAVE_RUNNING |
-                  VIR_DOMAIN_SAVE_PAUSED, -1);
+                  VIR_DOMAIN_SAVE_PAUSED |
+                  VIR_DOMAIN_SAVE_PARALLEL, -1);
 
     if (virTypedParamsValidate(params, nparams,
                                VIR_DOMAIN_SAVE_PARAM_FILE,
                                VIR_TYPED_PARAM_STRING,
                                VIR_DOMAIN_SAVE_PARAM_DXML,
                                VIR_TYPED_PARAM_STRING,
+                               VIR_DOMAIN_SAVE_PARAM_PARALLEL_CONNECTIONS,
+                               VIR_TYPED_PARAM_INT,
                                NULL) < 0)
         return -1;
 
@@ -2884,7 +2889,7 @@ qemuDomainSaveParams(virDomainPtr dom,
         goto cleanup;
 
     ret = qemuDomainSaveInternal(driver, vm, to, compressed,
-                                 compressor, dxml, flags);
+                                 compressor, dxml, params, nparams, flags);
 
  cleanup:
     virDomainObjEndAPI(&vm);
@@ -5759,6 +5764,8 @@ static int
 qemuDomainRestoreInternal(virConnectPtr conn,
                           const char *path,
                           const char *dxml,
+                          virTypedParameterPtr params,
+                          int nparams,
                           unsigned int flags,
                           int (*ensureACL)(virConnectPtr, virDomainDef *))
 {
@@ -5780,7 +5787,8 @@ qemuDomainRestoreInternal(virConnectPtr conn,
     virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE |
                   VIR_DOMAIN_SAVE_RUNNING |
                   VIR_DOMAIN_SAVE_PAUSED |
-                  VIR_DOMAIN_SAVE_RESET_NVRAM, -1);
+                  VIR_DOMAIN_SAVE_RESET_NVRAM |
+                  VIR_DOMAIN_SAVE_PARALLEL, -1);
 
     if (flags & VIR_DOMAIN_SAVE_RESET_NVRAM)
         reset_nvram = true;
@@ -5789,7 +5797,7 @@ qemuDomainRestoreInternal(virConnectPtr conn,
         goto cleanup;
 
     mapped_ram = data->header.features & QEMU_SAVE_FEATURE_MAPPED_RAM;
-    if (!(restoreParams = qemuMigrationParamsForSave(mapped_ram, flags)))
+    if (!(restoreParams = qemuMigrationParamsForSave(params, nparams, mapped_ram, flags)))
         return -1;
 
     fd = qemuSaveImageOpen(driver, path,
@@ -5871,7 +5879,7 @@ qemuDomainRestoreFlags(virConnectPtr conn,
                        const char *dxml,
                        unsigned int flags)
 {
-    return qemuDomainRestoreInternal(conn, path, dxml, flags,
+    return qemuDomainRestoreInternal(conn, path, dxml, NULL, 0, flags,
                                      virDomainRestoreFlagsEnsureACL);
 }
 
@@ -5879,7 +5887,7 @@ static int
 qemuDomainRestore(virConnectPtr conn,
                   const char *path)
 {
-    return qemuDomainRestoreInternal(conn, path, NULL, 0,
+    return qemuDomainRestoreInternal(conn, path, NULL, NULL, 0, 0,
                                      virDomainRestoreEnsureACL);
 }
 
@@ -5895,6 +5903,7 @@ qemuDomainRestoreParams(virConnectPtr conn,
     if (virTypedParamsValidate(params, nparams,
                                VIR_DOMAIN_SAVE_PARAM_FILE, VIR_TYPED_PARAM_STRING,
                                VIR_DOMAIN_SAVE_PARAM_DXML, VIR_TYPED_PARAM_STRING,
+                               VIR_DOMAIN_SAVE_PARAM_PARALLEL_CONNECTIONS, VIR_TYPED_PARAM_INT,
                                NULL) < 0)
         return -1;
 
@@ -5911,7 +5920,7 @@ qemuDomainRestoreParams(virConnectPtr conn,
         return -1;
     }
 
-    ret = qemuDomainRestoreInternal(conn, path, dxml, flags,
+    ret = qemuDomainRestoreInternal(conn, path, dxml, params, nparams, flags,
                                     virDomainRestoreParamsEnsureACL);
     return ret;
 }
@@ -6115,7 +6124,7 @@ qemuDomainObjRestore(virConnectPtr conn,
     }
 
     mapped_ram = data->header.features & QEMU_SAVE_FEATURE_MAPPED_RAM;
-    if (!(restoreParams = qemuMigrationParamsForSave(mapped_ram,
+    if (!(restoreParams = qemuMigrationParamsForSave(NULL, 0, mapped_ram,
                                                      bypass_cache ? VIR_DOMAIN_SAVE_BYPASS_CACHE : 0)))
         return -1;
 
diff --git a/src/qemu/qemu_migration_params.c b/src/qemu/qemu_migration_params.c
index 8f6003005c..8c656af163 100644
--- a/src/qemu/qemu_migration_params.c
+++ b/src/qemu/qemu_migration_params.c
@@ -788,7 +788,10 @@ qemuMigrationParamsFromFlags(virTypedParameterPtr params,
 
 
 qemuMigrationParams *
-qemuMigrationParamsForSave(bool mappedRam, unsigned int flags)
+qemuMigrationParamsForSave(virTypedParameterPtr params,
+                           int nparams,
+                           bool mappedRam,
+                           unsigned int flags)
 {
     g_autoptr(qemuMigrationParams) saveParams = NULL;
 
@@ -800,7 +803,25 @@ qemuMigrationParamsForSave(bool mappedRam, unsigned int flags)
             return NULL;
         if (virBitmapSetBit(saveParams->caps, QEMU_MIGRATION_CAP_MULTIFD) < 0)
             return NULL;
-        saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = 1;
+
+        if (flags & VIR_DOMAIN_SAVE_PARALLEL) {
+            int nchannels;
+
+            if (params && virTypedParamsGetInt(params, nparams,
+                                               VIR_DOMAIN_SAVE_PARAM_PARALLEL_CONNECTIONS,
+                                               &nchannels) < 0)
+                return NULL;
+
+            if (nchannels < 1) {
+                virReportError(VIR_ERR_INVALID_ARG, "%s",
+                               _("number of parallel save channels cannot be less than 1"));
+                return NULL;
+            }
+
+            saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = nchannels;
+        } else {
+            saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = 1;
+        }
         saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].set = true;
 
         if (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) {
diff --git a/src/qemu/qemu_migration_params.h b/src/qemu/qemu_migration_params.h
index 9700469b5e..a672b3d875 100644
--- a/src/qemu/qemu_migration_params.h
+++ b/src/qemu/qemu_migration_params.h
@@ -88,7 +88,10 @@ qemuMigrationParamsFromFlags(virTypedParameterPtr params,
                              qemuMigrationParty party);
 
 qemuMigrationParams *
-qemuMigrationParamsForSave(bool mappedRam, unsigned int flags);
+qemuMigrationParamsForSave(virTypedParameterPtr params,
+                           int nparams,
+                           bool mappedRam,
+                           unsigned int flags);
 
 int
 qemuMigrationParamsDump(qemuMigrationParams *migParams,
-- 
2.35.3
Re: [PATCH 18/20] qemu: Add support for parallel save and restore
Posted by Daniel P. Berrangé 1 month, 2 weeks ago
On Thu, Aug 08, 2024 at 05:38:11PM -0600, Jim Fehlig via Devel wrote:
> Add support for parallel save and restore by mapping libvirt's
> "parallel-connections" parameter to QEMU's "multifd-channels"
> migration parameter.
> 
> Signed-off-by: Jim Fehlig <jfehlig@suse.com>
> ---
>  src/qemu/qemu_driver.c           | 31 ++++++++++++++++++++-----------
>  src/qemu/qemu_migration_params.c | 25 +++++++++++++++++++++++--
>  src/qemu/qemu_migration_params.h |  5 ++++-
>  3 files changed, 47 insertions(+), 14 deletions(-)

Unless I'm missing something, nothing in this patch is validating
the the image is about to be written in v3 format with MAPPED_RAM
set. We must reject VIR_DOMAIN_SAVE_PARALLEL if we're not using
MAPPED_RAM. Likewise when restoring we must reject PARALLEL if
the loaded image doesnt have MAPPED_RAM feature set.

> 
> diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
> index 0025bad6e7..fce599b321 100644
> --- a/src/qemu/qemu_driver.c
> +++ b/src/qemu/qemu_driver.c
> @@ -2608,6 +2608,8 @@ qemuDomainSaveInternal(virQEMUDriver *driver,
>                         int compressed,
>                         virCommand *compressor,
>                         const char *xmlin,
> +                       virTypedParameterPtr params,
> +                       int nparams,
>                         unsigned int flags)
>  {
>      g_autofree char *xml = NULL;
> @@ -2691,7 +2693,7 @@ qemuDomainSaveInternal(virQEMUDriver *driver,
>      xml = NULL;
>  
>      mappedRam = data->header.features & QEMU_SAVE_FEATURE_MAPPED_RAM;
> -    if (!(saveParams = qemuMigrationParamsForSave(mappedRam, flags)))
> +    if (!(saveParams = qemuMigrationParamsForSave(params, nparams, mappedRam, flags)))
>          goto endjob;
>  
>      ret = qemuSaveImageCreate(driver, vm, path, data, compressor,
> @@ -2777,7 +2779,7 @@ qemuDomainManagedSaveHelper(virQEMUDriver *driver,
>      VIR_INFO("Saving state of domain '%s' to '%s'", vm->def->name, path);
>  
>      if (qemuDomainSaveInternal(driver, vm, path, compressed,
> -                               compressor, dxml, flags) < 0)
> +                               compressor, dxml, NULL, 0, flags) < 0)
>          return -1;
>  
>      vm->hasManagedSave = true;
> @@ -2816,7 +2818,7 @@ qemuDomainSaveFlags(virDomainPtr dom, const char *path, const char *dxml,
>          goto cleanup;
>  
>      ret = qemuDomainSaveInternal(driver, vm, path, compressed,
> -                                 compressor, dxml, flags);
> +                                 compressor, dxml, NULL, 0, flags);
>  
>   cleanup:
>      virDomainObjEndAPI(&vm);
> @@ -2846,13 +2848,16 @@ qemuDomainSaveParams(virDomainPtr dom,
>  
>      virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE |
>                    VIR_DOMAIN_SAVE_RUNNING |
> -                  VIR_DOMAIN_SAVE_PAUSED, -1);
> +                  VIR_DOMAIN_SAVE_PAUSED |
> +                  VIR_DOMAIN_SAVE_PARALLEL, -1);
>  
>      if (virTypedParamsValidate(params, nparams,
>                                 VIR_DOMAIN_SAVE_PARAM_FILE,
>                                 VIR_TYPED_PARAM_STRING,
>                                 VIR_DOMAIN_SAVE_PARAM_DXML,
>                                 VIR_TYPED_PARAM_STRING,
> +                               VIR_DOMAIN_SAVE_PARAM_PARALLEL_CONNECTIONS,
> +                               VIR_TYPED_PARAM_INT,
>                                 NULL) < 0)
>          return -1;
>  
> @@ -2884,7 +2889,7 @@ qemuDomainSaveParams(virDomainPtr dom,
>          goto cleanup;
>  
>      ret = qemuDomainSaveInternal(driver, vm, to, compressed,
> -                                 compressor, dxml, flags);
> +                                 compressor, dxml, params, nparams, flags);
>  
>   cleanup:
>      virDomainObjEndAPI(&vm);
> @@ -5759,6 +5764,8 @@ static int
>  qemuDomainRestoreInternal(virConnectPtr conn,
>                            const char *path,
>                            const char *dxml,
> +                          virTypedParameterPtr params,
> +                          int nparams,
>                            unsigned int flags,
>                            int (*ensureACL)(virConnectPtr, virDomainDef *))
>  {
> @@ -5780,7 +5787,8 @@ qemuDomainRestoreInternal(virConnectPtr conn,
>      virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE |
>                    VIR_DOMAIN_SAVE_RUNNING |
>                    VIR_DOMAIN_SAVE_PAUSED |
> -                  VIR_DOMAIN_SAVE_RESET_NVRAM, -1);
> +                  VIR_DOMAIN_SAVE_RESET_NVRAM |
> +                  VIR_DOMAIN_SAVE_PARALLEL, -1);
>  
>      if (flags & VIR_DOMAIN_SAVE_RESET_NVRAM)
>          reset_nvram = true;
> @@ -5789,7 +5797,7 @@ qemuDomainRestoreInternal(virConnectPtr conn,
>          goto cleanup;
>  
>      mapped_ram = data->header.features & QEMU_SAVE_FEATURE_MAPPED_RAM;
> -    if (!(restoreParams = qemuMigrationParamsForSave(mapped_ram, flags)))
> +    if (!(restoreParams = qemuMigrationParamsForSave(params, nparams, mapped_ram, flags)))
>          return -1;
>  
>      fd = qemuSaveImageOpen(driver, path,
> @@ -5871,7 +5879,7 @@ qemuDomainRestoreFlags(virConnectPtr conn,
>                         const char *dxml,
>                         unsigned int flags)
>  {
> -    return qemuDomainRestoreInternal(conn, path, dxml, flags,
> +    return qemuDomainRestoreInternal(conn, path, dxml, NULL, 0, flags,
>                                       virDomainRestoreFlagsEnsureACL);
>  }
>  
> @@ -5879,7 +5887,7 @@ static int
>  qemuDomainRestore(virConnectPtr conn,
>                    const char *path)
>  {
> -    return qemuDomainRestoreInternal(conn, path, NULL, 0,
> +    return qemuDomainRestoreInternal(conn, path, NULL, NULL, 0, 0,
>                                       virDomainRestoreEnsureACL);
>  }
>  
> @@ -5895,6 +5903,7 @@ qemuDomainRestoreParams(virConnectPtr conn,
>      if (virTypedParamsValidate(params, nparams,
>                                 VIR_DOMAIN_SAVE_PARAM_FILE, VIR_TYPED_PARAM_STRING,
>                                 VIR_DOMAIN_SAVE_PARAM_DXML, VIR_TYPED_PARAM_STRING,
> +                               VIR_DOMAIN_SAVE_PARAM_PARALLEL_CONNECTIONS, VIR_TYPED_PARAM_INT,
>                                 NULL) < 0)
>          return -1;
>  
> @@ -5911,7 +5920,7 @@ qemuDomainRestoreParams(virConnectPtr conn,
>          return -1;
>      }
>  
> -    ret = qemuDomainRestoreInternal(conn, path, dxml, flags,
> +    ret = qemuDomainRestoreInternal(conn, path, dxml, params, nparams, flags,
>                                      virDomainRestoreParamsEnsureACL);
>      return ret;
>  }
> @@ -6115,7 +6124,7 @@ qemuDomainObjRestore(virConnectPtr conn,
>      }
>  
>      mapped_ram = data->header.features & QEMU_SAVE_FEATURE_MAPPED_RAM;
> -    if (!(restoreParams = qemuMigrationParamsForSave(mapped_ram,
> +    if (!(restoreParams = qemuMigrationParamsForSave(NULL, 0, mapped_ram,
>                                                       bypass_cache ? VIR_DOMAIN_SAVE_BYPASS_CACHE : 0)))
>          return -1;
>  
> diff --git a/src/qemu/qemu_migration_params.c b/src/qemu/qemu_migration_params.c
> index 8f6003005c..8c656af163 100644
> --- a/src/qemu/qemu_migration_params.c
> +++ b/src/qemu/qemu_migration_params.c
> @@ -788,7 +788,10 @@ qemuMigrationParamsFromFlags(virTypedParameterPtr params,
>  
>  
>  qemuMigrationParams *
> -qemuMigrationParamsForSave(bool mappedRam, unsigned int flags)
> +qemuMigrationParamsForSave(virTypedParameterPtr params,
> +                           int nparams,
> +                           bool mappedRam,
> +                           unsigned int flags)
>  {
>      g_autoptr(qemuMigrationParams) saveParams = NULL;
>  
> @@ -800,7 +803,25 @@ qemuMigrationParamsForSave(bool mappedRam, unsigned int flags)
>              return NULL;
>          if (virBitmapSetBit(saveParams->caps, QEMU_MIGRATION_CAP_MULTIFD) < 0)
>              return NULL;
> -        saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = 1;
> +
> +        if (flags & VIR_DOMAIN_SAVE_PARALLEL) {
> +            int nchannels;
> +
> +            if (params && virTypedParamsGetInt(params, nparams,
> +                                               VIR_DOMAIN_SAVE_PARAM_PARALLEL_CONNECTIONS,
> +                                               &nchannels) < 0)
> +                return NULL;
> +
> +            if (nchannels < 1) {
> +                virReportError(VIR_ERR_INVALID_ARG, "%s",
> +                               _("number of parallel save channels cannot be less than 1"));
> +                return NULL;
> +            }
> +
> +            saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = nchannels;
> +        } else {
> +            saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = 1;
> +        }
>          saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].set = true;
>  
>          if (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) {
> diff --git a/src/qemu/qemu_migration_params.h b/src/qemu/qemu_migration_params.h
> index 9700469b5e..a672b3d875 100644
> --- a/src/qemu/qemu_migration_params.h
> +++ b/src/qemu/qemu_migration_params.h
> @@ -88,7 +88,10 @@ qemuMigrationParamsFromFlags(virTypedParameterPtr params,
>                               qemuMigrationParty party);
>  
>  qemuMigrationParams *
> -qemuMigrationParamsForSave(bool mappedRam, unsigned int flags);
> +qemuMigrationParamsForSave(virTypedParameterPtr params,
> +                           int nparams,
> +                           bool mappedRam,
> +                           unsigned int flags);
>  
>  int
>  qemuMigrationParamsDump(qemuMigrationParams *migParams,
> -- 
> 2.35.3
> 

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 :|
Re: [PATCH 18/20] qemu: Add support for parallel save and restore
Posted by Jim Fehlig via Devel 1 month, 1 week ago
On 10/10/24 07:43, Daniel P. Berrangé wrote:
> On Thu, Aug 08, 2024 at 05:38:11PM -0600, Jim Fehlig via Devel wrote:
>> Add support for parallel save and restore by mapping libvirt's
>> "parallel-connections" parameter to QEMU's "multifd-channels"
>> migration parameter.
>>
>> Signed-off-by: Jim Fehlig <jfehlig@suse.com>
>> ---
>>   src/qemu/qemu_driver.c           | 31 ++++++++++++++++++++-----------
>>   src/qemu/qemu_migration_params.c | 25 +++++++++++++++++++++++--
>>   src/qemu/qemu_migration_params.h |  5 ++++-
>>   3 files changed, 47 insertions(+), 14 deletions(-)
> 
> Unless I'm missing something, nothing in this patch is validating
> the the image is about to be written in v3 format with MAPPED_RAM
> set. We must reject VIR_DOMAIN_SAVE_PARALLEL if we're not using
> MAPPED_RAM. Likewise when restoring we must reject PARALLEL if
> the loaded image doesnt have MAPPED_RAM feature set.

Yeah, good catch. The existing logic essentially ignores PARALLEL if mapped-ram 
is not supported.

[snip]

>> diff --git a/src/qemu/qemu_migration_params.c b/src/qemu/qemu_migration_params.c
>> index 8f6003005c..8c656af163 100644
>> --- a/src/qemu/qemu_migration_params.c
>> +++ b/src/qemu/qemu_migration_params.c
>> @@ -788,7 +788,10 @@ qemuMigrationParamsFromFlags(virTypedParameterPtr params,
>>   
>>   
>>   qemuMigrationParams *
>> -qemuMigrationParamsForSave(bool mappedRam, unsigned int flags)
>> +qemuMigrationParamsForSave(virTypedParameterPtr params,
>> +                           int nparams,
>> +                           bool mappedRam,
>> +                           unsigned int flags)
>>   {
>>       g_autoptr(qemuMigrationParams) saveParams = NULL;
>>   
>> @@ -800,7 +803,25 @@ qemuMigrationParamsForSave(bool mappedRam, unsigned int flags)
>>               return NULL;
>>           if (virBitmapSetBit(saveParams->caps, QEMU_MIGRATION_CAP_MULTIFD) < 0)
>>               return NULL;
>> -        saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = 1;
>> +
>> +        if (flags & VIR_DOMAIN_SAVE_PARALLEL) {
>> +            int nchannels;
>> +
>> +            if (params && virTypedParamsGetInt(params, nparams,
>> +                                               VIR_DOMAIN_SAVE_PARAM_PARALLEL_CONNECTIONS,
>> +                                               &nchannels) < 0)
>> +                return NULL;
>> +
>> +            if (nchannels < 1) {
>> +                virReportError(VIR_ERR_INVALID_ARG, "%s",
>> +                               _("number of parallel save channels cannot be less than 1"));
>> +                return NULL;
>> +            }
>> +
>> +            saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = nchannels;
>> +        } else {
>> +            saveParams->params[QEMU_MIGRATION_PARAM_MULTIFD_CHANNELS].value.i = 1;
>> +        }

It's not shown in the diff, but this block is nestled within a 'if (mappedRam)' 
block. I've squashed the blow hunk into this patch in my local branch.

Regards,
Jim

diff --git a/src/qemu/qemu_migration_params.c b/src/qemu/qemu_migration_params.c
index 8c656af163..6fa09c272e 100644
--- a/src/qemu/qemu_migration_params.c
+++ b/src/qemu/qemu_migration_params.c
@@ -795,6 +795,12 @@ qemuMigrationParamsForSave(virTypedParameterPtr params,
  {
      g_autoptr(qemuMigrationParams) saveParams = NULL;

+    if (flags & VIR_DOMAIN_SAVE_PARALLEL && !mapped_ram) {
+        virReportError(VIR_ERR_INVALID_ARG, "%s",
+                       _("Parallel save is not available with this QEMU binary"));
+        return NULL;
+    }
+
      if (!(saveParams = qemuMigrationParamsNew()))
          return NULL;