qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 00/13] migration: Unify capabilities and parameters
@ 2025-04-11 19:14 Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 01/13] migration: Fix latent bug in migrate_params_test_apply() Fabiano Rosas
                   ` (13 more replies)
  0 siblings, 14 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Hi everyone, I did a cleanup (if it can be called that) of the user
input validation for capabilities and parameters and turned the two
concepts into a single 'options' to be stored in a MigrationConfig
object.

RFC mostly because this idea exposes (pre-existing) issues around how
to validate capabilities that are mutually excludent and options that
need to be enabled together.

I'd also like some feedback on what approach to take regarding
compatibility.

Let me know what you think. I know it's a lot to look at, any comments
are welcomed and don't worry on trying to cover everything. This is
long-term work. Thank you.

The reasons for this work are:
------------------------------

Capabilities are just boolean parameters that can only be set before
migration. For the majority of the code there's no distinction between
the two.

Having a single structure allows the qmp_migrate command to receive
the migration configuration all at once:

{ 'command': 'migrate',
  'data': {'*uri': 'str',
           '*channels': [ 'MigrationChannel' ],
+	   '*config': 'MigrationConfig',
           '*detach': 'bool', '*resume': 'bool' } }

+{ 'struct': 'MigrationConfig',
+  'data': { '*announce-initial': 'size',
+            '*announce-max': 'size',
+	     ...   <-- all parameters and capabilities as optional
+} }

(optionally fold 'detach' and 'resume' into MigrationConfig)

Other benefits of a single configuration struct:

- allows the removal of two commands:
  migrate-set-capabilities/query-capabilities which simplifies the
  user interface (migrate-set-parameters, or similar, is still
  required for options that need to be adjusted during migration);

- removes (some of) the triplication in migration.json;

- simplifies the (future) work of implementing a handshake feature for
  migration: only one structure to negotiate over and less reliance on
  commands other than 'migrate'.

The major changes in this series are:
-------------------------------------

- Add a (QAPI) type hierarchy:

                               MigrationConfigBase
                              (most config options)
                                       |
             +-------------------------|-------------------------+
             |                         |                         |
    MigrationConfig          MigrationParameters        MigrateSetParameters
 (internal use, s->config,   (compat with               (compat with
 new query/set-config)       query-migrate-parameters)  migrate-set-parameters)

- Remove migrate_params_test_apply. This function duplicates a lot of code;

- Add compatibility routines to convert from the existing QMP user
  input into the new MigrationConfig for internal use;

- Merge capabilities and parameters validation into one function;

- Convert qmp_migrate and qmp_migrate_incoming to use the new structure.

Open questions:
---------------

- Deprecations/compat?

I think we should deprecate migrate-set/query-capabilities and everything to do
with capabilities (specifically the validation in the JSON at the end of the
stream).

For migrate-set/query-parameters, we could probably keep it around indefinitely,
but it'd be convenient to introduce new commands so we can give them new
semantics.

- How to restrict the options that should not be set when the migration is in
progress?

i.e.:
  all options can be set before migration (initial config)
  some options can be set during migration (runtime)

I thought of adding another type at the top of the hierarchy, with
just the options allowed to change at runtime, but that doesn't really
stop the others being also set at runtime. I'd need a way to have a
set of options that are rejected 'if migration_is_running()', without
adding more duplication all around.

- What about savevm?

None of this solves the issue of random caps/params being set before
calling savevm. We still need to special-case savevm and reject
everything. Unless we entirely deprecate setting initial options via
set-parameters (or set-config) and require all options to be set as
savevm (and migrate) arguments.

- HMP?

Can we convert the strings passed via hmp_set_parameters without
having an enum of parameters? Duplication problem again.

- incoming defer?

It seems we cannot do the final step of removing
migrate-set-capabilites before we have a form of handshake
implemented. That would take the config from qmp_migrate on source and
send it to the destination for negotiation.

- last but definitely not least:

Changing caps from a list of bools into struct members complicates the
validation because some caps must be checked against every other cap
already set. And the user could be playing games with switching caps
on and off, so there's a lot of redundant checking the must be
made. The current code already has a bunch of gaps in that regard.

For this series I opted to not check has_* fields for capabilities,
i.e. to validate them all every time migrate_config_check() is called.

Fabiano Rosas (12):
  migration: Normalize tls arguments
  migration: Run a post update routine after setting parameters
  migration: Fix parameter validation
  migration: Reduce a bit of duplication in migration.json
  migration: Remove the parameters copy during validation
  migration: Introduce new MigrationConfig structure
  migration: Replace s->parameters with s->config
  migration: Do away with usage of QERR_INVALID_PARAMETER_VALUE
  migration: Replace s->capabilities with s->config
  migration: Merge parameters and capability checks
  [PoC] migration: Add query/set commands for MigrationConfig
  [PoC] migration: Allow migrate commands to provide the migration
    config

Markus Armbruster (1):
  migration: Fix latent bug in migrate_params_test_apply()

 migration/migration-hmp-cmds.c |    5 +-
 migration/migration.c          |   52 +-
 migration/migration.h          |    5 +-
 migration/options.c            | 1386 ++++++++++++++++----------------
 migration/options.h            |   25 +-
 migration/page_cache.c         |    6 +-
 migration/ram.c                |    9 +-
 migration/savevm.c             |    8 +-
 migration/tls.c                |    2 +-
 qapi/migration.json            |  571 +++++++------
 system/vl.c                    |    3 +-
 11 files changed, 1079 insertions(+), 993 deletions(-)

-- 
2.35.3



^ permalink raw reply	[flat|nested] 44+ messages in thread

* [RFC PATCH 01/13] migration: Fix latent bug in migrate_params_test_apply()
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 02/13] migration: Normalize tls arguments Fabiano Rosas
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

From: Markus Armbruster <armbru@redhat.com>

migrate_params_test_apply() neglects to apply tls_authz.  Currently
harmless, because migrate_params_check() doesn't care.  Fix it anyway.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Message-ID: <20250407072833.2118928-1-armbru@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/options.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/migration/options.c b/migration/options.c
index b0ac2ea408..cb8eec218f 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -1193,6 +1193,11 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
         dest->tls_hostname = params->tls_hostname->u.s;
     }
 
+    if (params->tls_authz) {
+        assert(params->tls_authz->type == QTYPE_QSTRING);
+        dest->tls_authz = params->tls_authz->u.s;
+    }
+
     if (params->has_max_bandwidth) {
         dest->max_bandwidth = params->max_bandwidth;
     }
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 02/13] migration: Normalize tls arguments
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 01/13] migration: Fix latent bug in migrate_params_test_apply() Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-14 16:30   ` Daniel P. Berrangé
  2025-04-11 19:14 ` [RFC PATCH 03/13] migration: Run a post update routine after setting parameters Fabiano Rosas
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

The tls_creds, tls_authz and tls_hostname arguments are strings that
can be set by the user. They are allowed to be either a valid string,
an empty string or NULL. The values "" and NULL are effectively
treated the same by the code, but this is not entirely clear because
the handling is not uniform.

Make the 3 variables be handled the same and at the same place in
options.c. Note that this affects only the internal usage of the
variables.

(migrate_tls() had to be moved to be able to use migrate_tls_creds())

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/options.c | 81 ++++++++++++++++++++++++---------------------
 migration/tls.c     |  2 +-
 2 files changed, 44 insertions(+), 39 deletions(-)

diff --git a/migration/options.c b/migration/options.c
index cb8eec218f..7cd465ca94 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -379,13 +379,6 @@ bool migrate_rdma(void)
     return s->rdma_migration;
 }
 
-bool migrate_tls(void)
-{
-    MigrationState *s = migrate_get_current();
-
-    return s->parameters.tls_creds && *s->parameters.tls_creds;
-}
-
 typedef enum WriteTrackingSupport {
     WT_SUPPORT_UNKNOWN = 0,
     WT_SUPPORT_ABSENT,
@@ -814,21 +807,41 @@ const char *migrate_tls_authz(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.tls_authz;
+    if (s->parameters.tls_authz &&
+        *s->parameters.tls_authz) {
+        return s->parameters.tls_authz;
+    }
+
+    return NULL;
 }
 
 const char *migrate_tls_creds(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.tls_creds;
+    if (s->parameters.tls_creds &&
+        *s->parameters.tls_creds) {
+        return s->parameters.tls_creds;
+    }
+
+    return NULL;
 }
 
 const char *migrate_tls_hostname(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.tls_hostname;
+    if (s->parameters.tls_hostname &&
+        *s->parameters.tls_hostname) {
+        return s->parameters.tls_hostname;
+    }
+
+    return NULL;
+}
+
+bool migrate_tls(void)
+{
+    return !!migrate_tls_creds();
 }
 
 uint64_t migrate_vcpu_dirty_limit_period(void)
@@ -883,8 +896,10 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp)
     params->cpu_throttle_increment = s->parameters.cpu_throttle_increment;
     params->has_cpu_throttle_tailslow = true;
     params->cpu_throttle_tailslow = s->parameters.cpu_throttle_tailslow;
-    params->tls_creds = g_strdup(s->parameters.tls_creds);
-    params->tls_hostname = g_strdup(s->parameters.tls_hostname);
+    params->tls_creds = g_strdup(s->parameters.tls_creds ?
+                                 s->parameters.tls_creds : "");
+    params->tls_hostname = g_strdup(s->parameters.tls_hostname ?
+                                    s->parameters.tls_hostname : "");
     params->tls_authz = g_strdup(s->parameters.tls_authz ?
                                  s->parameters.tls_authz : "");
     params->has_max_bandwidth = true;
@@ -945,6 +960,7 @@ void migrate_params_init(MigrationParameters *params)
 {
     params->tls_hostname = g_strdup("");
     params->tls_creds = g_strdup("");
+    params->tls_authz = g_strdup("");
 
     /* Set has_* up only for parameter checks */
     params->has_throttle_trigger_threshold = true;
@@ -1184,18 +1200,27 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
     }
 
     if (params->tls_creds) {
-        assert(params->tls_creds->type == QTYPE_QSTRING);
-        dest->tls_creds = params->tls_creds->u.s;
+        if (params->tls_creds->type == QTYPE_QNULL) {
+            dest->tls_creds = NULL;
+        } else {
+            dest->tls_creds = params->tls_creds->u.s;
+        }
     }
 
     if (params->tls_hostname) {
-        assert(params->tls_hostname->type == QTYPE_QSTRING);
-        dest->tls_hostname = params->tls_hostname->u.s;
+        if (params->tls_hostname->type == QTYPE_QNULL) {
+            dest->tls_hostname = NULL;
+        } else {
+            dest->tls_hostname = params->tls_hostname->u.s;
+        }
     }
 
     if (params->tls_authz) {
-        assert(params->tls_authz->type == QTYPE_QSTRING);
-        dest->tls_authz = params->tls_authz->u.s;
+        if (params->tls_authz->type == QTYPE_QNULL) {
+            dest->tls_authz = NULL;
+        } else {
+            dest->tls_authz = params->tls_authz->u.s;
+        }
     }
 
     if (params->has_max_bandwidth) {
@@ -1413,26 +1438,6 @@ void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
 {
     MigrationParameters tmp;
 
-    /* TODO Rewrite "" to null instead for all three tls_* parameters */
-    if (params->tls_creds
-        && params->tls_creds->type == QTYPE_QNULL) {
-        qobject_unref(params->tls_creds->u.n);
-        params->tls_creds->type = QTYPE_QSTRING;
-        params->tls_creds->u.s = strdup("");
-    }
-    if (params->tls_hostname
-        && params->tls_hostname->type == QTYPE_QNULL) {
-        qobject_unref(params->tls_hostname->u.n);
-        params->tls_hostname->type = QTYPE_QSTRING;
-        params->tls_hostname->u.s = strdup("");
-    }
-    if (params->tls_authz
-        && params->tls_authz->type == QTYPE_QNULL) {
-        qobject_unref(params->tls_authz->u.n);
-        params->tls_authz->type = QTYPE_QSTRING;
-        params->tls_authz->u.s = strdup("");
-    }
-
     migrate_params_test_apply(params, &tmp);
 
     if (!migrate_params_check(&tmp, errp)) {
diff --git a/migration/tls.c b/migration/tls.c
index 5cbf952383..8a89d3f767 100644
--- a/migration/tls.c
+++ b/migration/tls.c
@@ -126,7 +126,7 @@ QIOChannelTLS *migration_tls_client_create(QIOChannel *ioc,
     }
 
     const char *tls_hostname = migrate_tls_hostname();
-    if (tls_hostname && *tls_hostname) {
+    if (tls_hostname) {
         hostname = tls_hostname;
     }
 
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 03/13] migration: Run a post update routine after setting parameters
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 01/13] migration: Fix latent bug in migrate_params_test_apply() Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 02/13] migration: Normalize tls arguments Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-05-15 20:42   ` Peter Xu
  2025-04-11 19:14 ` [RFC PATCH 04/13] migration: Fix parameter validation Fabiano Rosas
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Some migration parameters are updated immediately once they are set
via migrate-set-parameters. Move that work outside of
migrate_params_apply() and leave that function with the single
responsibility of setting s->parameters and not doing any
side-effects.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/options.c | 39 +++++++++++++++++++++++++++++----------
 migration/ram.c     |  2 +-
 2 files changed, 30 insertions(+), 11 deletions(-)

diff --git a/migration/options.c b/migration/options.c
index 7cd465ca94..cac28540dd 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -1302,7 +1302,7 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
     }
 }
 
-static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
+static void migrate_params_apply(MigrateSetParameters *params)
 {
     MigrationState *s = migrate_get_current();
 
@@ -1344,9 +1344,6 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
 
     if (params->has_max_bandwidth) {
         s->parameters.max_bandwidth = params->max_bandwidth;
-        if (s->to_dst_file && !migration_in_postcopy()) {
-            migration_rate_set(s->parameters.max_bandwidth);
-        }
     }
 
     if (params->has_avail_switchover_bandwidth) {
@@ -1359,7 +1356,6 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
 
     if (params->has_x_checkpoint_delay) {
         s->parameters.x_checkpoint_delay = params->x_checkpoint_delay;
-        colo_checkpoint_delay_set();
     }
 
     if (params->has_multifd_channels) {
@@ -1379,13 +1375,9 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
     }
     if (params->has_xbzrle_cache_size) {
         s->parameters.xbzrle_cache_size = params->xbzrle_cache_size;
-        xbzrle_cache_resize(params->xbzrle_cache_size, errp);
     }
     if (params->has_max_postcopy_bandwidth) {
         s->parameters.max_postcopy_bandwidth = params->max_postcopy_bandwidth;
-        if (s->to_dst_file && migration_in_postcopy()) {
-            migration_rate_set(s->parameters.max_postcopy_bandwidth);
-        }
     }
     if (params->has_max_cpu_throttle) {
         s->parameters.max_cpu_throttle = params->max_cpu_throttle;
@@ -1434,6 +1426,32 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
     }
 }
 
+static void migrate_post_update_params(MigrateSetParameters *new, Error **errp)
+{
+    MigrationState *s = migrate_get_current();
+
+    if (new->has_max_bandwidth) {
+        if (s->to_dst_file && !migration_in_postcopy()) {
+            migration_rate_set(new->max_bandwidth);
+        }
+    }
+
+    if (new->has_x_checkpoint_delay) {
+        colo_checkpoint_delay_set();
+    }
+
+    if (new->has_xbzrle_cache_size) {
+        xbzrle_cache_resize(new->xbzrle_cache_size, errp);
+    }
+
+    if (new->has_max_postcopy_bandwidth) {
+        if (s->to_dst_file && migration_in_postcopy()) {
+            migration_rate_set(new->max_postcopy_bandwidth);
+        }
+    }
+
+}
+
 void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
 {
     MigrationParameters tmp;
@@ -1445,5 +1463,6 @@ void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
         return;
     }
 
-    migrate_params_apply(params, errp);
+    migrate_params_apply(params);
+    migrate_post_update_params(params, errp);
 }
diff --git a/migration/ram.c b/migration/ram.c
index 424df6d9f1..e0ba8e0d48 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -144,7 +144,7 @@ static void XBZRLE_cache_unlock(void)
 /**
  * xbzrle_cache_resize: resize the xbzrle cache
  *
- * This function is called from migrate_params_apply in main
+ * This function is called from migrate_post_update_config in main
  * thread, possibly while a migration is in progress.  A running
  * migration may be using the cache and might finish during this call,
  * hence changes to the cache are protected by XBZRLE.lock().
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 04/13] migration: Fix parameter validation
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (2 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 03/13] migration: Run a post update routine after setting parameters Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-05-15 20:59   ` Peter Xu
  2025-04-11 19:14 ` [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json Fabiano Rosas
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

The migration parameters validation involves producing a temporary
structure which merges the current parameter values with the new
parameters set by the user.

The has_ boolean fields of MigrateSetParameter are taken into
consideration when writing the temporary structure, however the copy
of the current parameters also copies all the has_ fields of
s->parameters and those are (almost) all true due to being initialized
by migrate_params_init().

Since the temporary structure copy does not carry over the has_ fields
from MigrateSetParameters, only the values which were initialized in
migrate_params_init() will end up being validated. This causes
(almost) all of the migration parameters to be validated again every
time a parameter is set, which could be considered a bug. But it also
skips validation of those values which are not set in
migrate_params_init(), which is a worse issue.

Fix by initializing the missing values in migrate_params_init().
Currently 'avail_switchover_bandwidth' and 'block_bitmap_mapping' are
affected.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/options.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/migration/options.c b/migration/options.c
index cac28540dd..625d597a85 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -987,6 +987,8 @@ void migrate_params_init(MigrationParameters *params)
     params->has_mode = true;
     params->has_zero_page_detection = true;
     params->has_direct_io = true;
+    params->has_avail_switchover_bandwidth = true;
+    params->has_block_bitmap_mapping = true;
 }
 
 /*
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (3 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 04/13] migration: Fix parameter validation Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-14 16:38   ` Daniel P. Berrangé
  2025-04-17 18:45   ` Markus Armbruster
  2025-04-11 19:14 ` [RFC PATCH 06/13] migration: Remove the parameters copy during validation Fabiano Rosas
                   ` (8 subsequent siblings)
  13 siblings, 2 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Introduce a new MigrationConfigBase, to allow most of the duplication
in migration.json to be eliminated.

The reason we need MigrationParameters and MigrationSetParameters is
that the internal parameter representation in the migration code, as
well as the user-facing return of query-migrate-parameters use one
type for the TLS options (tls-creds, tls-hostname, tls-authz), while
the user-facing input from migrate-set-parameters uses another.

The difference is in whether the NULL values is accepted. The former
considers NULL as invalid, while the latter doesn't.

Move all other (non-TLS) options into the new type and make it a base
type for the two diverging types so that each child type can declare
the TLS options in its own way.

Nothing changes in the user API, nothing changes in the internal
representation, but we save several lines of duplication in
migration.json.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 qapi/migration.json | 358 +++++++++++++-------------------------------
 1 file changed, 108 insertions(+), 250 deletions(-)

diff --git a/qapi/migration.json b/qapi/migration.json
index 8b9c53595c..5a4d5a2d3e 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -914,202 +914,6 @@
            'zero-page-detection',
            'direct-io'] }
 
-##
-# @MigrateSetParameters:
-#
-# @announce-initial: Initial delay (in milliseconds) before sending
-#     the first announce (Since 4.0)
-#
-# @announce-max: Maximum delay (in milliseconds) between packets in
-#     the announcement (Since 4.0)
-#
-# @announce-rounds: Number of self-announce packets sent after
-#     migration (Since 4.0)
-#
-# @announce-step: Increase in delay (in milliseconds) between
-#     subsequent packets in the announcement (Since 4.0)
-#
-# @throttle-trigger-threshold: The ratio of bytes_dirty_period and
-#     bytes_xfer_period to trigger throttling.  It is expressed as
-#     percentage.  The default value is 50.  (Since 5.0)
-#
-# @cpu-throttle-initial: Initial percentage of time guest cpus are
-#     throttled when migration auto-converge is activated.  The
-#     default value is 20.  (Since 2.7)
-#
-# @cpu-throttle-increment: throttle percentage increase each time
-#     auto-converge detects that migration is not making progress.
-#     The default value is 10.  (Since 2.7)
-#
-# @cpu-throttle-tailslow: Make CPU throttling slower at tail stage At
-#     the tail stage of throttling, the Guest is very sensitive to CPU
-#     percentage while the @cpu-throttle -increment is excessive
-#     usually at tail stage.  If this parameter is true, we will
-#     compute the ideal CPU percentage used by the Guest, which may
-#     exactly make the dirty rate match the dirty rate threshold.
-#     Then we will choose a smaller throttle increment between the one
-#     specified by @cpu-throttle-increment and the one generated by
-#     ideal CPU percentage.  Therefore, it is compatible to
-#     traditional throttling, meanwhile the throttle increment won't
-#     be excessive at tail stage.  The default value is false.  (Since
-#     5.1)
-#
-# @tls-creds: ID of the 'tls-creds' object that provides credentials
-#     for establishing a TLS connection over the migration data
-#     channel.  On the outgoing side of the migration, the credentials
-#     must be for a 'client' endpoint, while for the incoming side the
-#     credentials must be for a 'server' endpoint.  Setting this to a
-#     non-empty string enables TLS for all migrations.  An empty
-#     string means that QEMU will use plain text mode for migration,
-#     rather than TLS.  This is the default.  (Since 2.7)
-#
-# @tls-hostname: migration target's hostname for validating the
-#     server's x509 certificate identity.  If empty, QEMU will use the
-#     hostname from the migration URI, if any.  A non-empty value is
-#     required when using x509 based TLS credentials and the migration
-#     URI does not include a hostname, such as fd: or exec: based
-#     migration.  (Since 2.7)
-#
-#     Note: empty value works only since 2.9.
-#
-# @tls-authz: ID of the 'authz' object subclass that provides access
-#     control checking of the TLS x509 certificate distinguished name.
-#     This object is only resolved at time of use, so can be deleted
-#     and recreated on the fly while the migration server is active.
-#     If missing, it will default to denying access (Since 4.0)
-#
-# @max-bandwidth: maximum speed for migration, in bytes per second.
-#     (Since 2.8)
-#
-# @avail-switchover-bandwidth: to set the available bandwidth that
-#     migration can use during switchover phase.  NOTE!  This does not
-#     limit the bandwidth during switchover, but only for calculations
-#     when making decisions to switchover.  By default, this value is
-#     zero, which means QEMU will estimate the bandwidth
-#     automatically.  This can be set when the estimated value is not
-#     accurate, while the user is able to guarantee such bandwidth is
-#     available when switching over.  When specified correctly, this
-#     can make the switchover decision much more accurate.
-#     (Since 8.2)
-#
-# @downtime-limit: set maximum tolerated downtime for migration.
-#     maximum downtime in milliseconds (Since 2.8)
-#
-# @x-checkpoint-delay: The delay time (in ms) between two COLO
-#     checkpoints in periodic mode.  (Since 2.8)
-#
-# @multifd-channels: Number of channels used to migrate data in
-#     parallel.  This is the same number that the number of sockets
-#     used for migration.  The default value is 2 (since 4.0)
-#
-# @xbzrle-cache-size: cache size to be used by XBZRLE migration.  It
-#     needs to be a multiple of the target page size and a power of 2
-#     (Since 2.11)
-#
-# @max-postcopy-bandwidth: Background transfer bandwidth during
-#     postcopy.  Defaults to 0 (unlimited).  In bytes per second.
-#     (Since 3.0)
-#
-# @max-cpu-throttle: maximum cpu throttle percentage.  Defaults to 99.
-#     (Since 3.1)
-#
-# @multifd-compression: Which compression method to use.  Defaults to
-#     none.  (Since 5.0)
-#
-# @multifd-zlib-level: Set the compression level to be used in live
-#     migration, the compression level is an integer between 0 and 9,
-#     where 0 means no compression, 1 means the best compression
-#     speed, and 9 means best compression ratio which will consume
-#     more CPU.  Defaults to 1.  (Since 5.0)
-#
-# @multifd-qatzip-level: Set the compression level to be used in live
-#     migration. The level is an integer between 1 and 9, where 1 means
-#     the best compression speed, and 9 means the best compression
-#     ratio which will consume more CPU. Defaults to 1.  (Since 9.2)
-#
-# @multifd-zstd-level: Set the compression level to be used in live
-#     migration, the compression level is an integer between 0 and 20,
-#     where 0 means no compression, 1 means the best compression
-#     speed, and 20 means best compression ratio which will consume
-#     more CPU.  Defaults to 1.  (Since 5.0)
-#
-# @block-bitmap-mapping: Maps block nodes and bitmaps on them to
-#     aliases for the purpose of dirty bitmap migration.  Such aliases
-#     may for example be the corresponding names on the opposite site.
-#     The mapping must be one-to-one, but not necessarily complete: On
-#     the source, unmapped bitmaps and all bitmaps on unmapped nodes
-#     will be ignored.  On the destination, encountering an unmapped
-#     alias in the incoming migration stream will result in a report,
-#     and all further bitmap migration data will then be discarded.
-#     Note that the destination does not know about bitmaps it does
-#     not receive, so there is no limitation or requirement regarding
-#     the number of bitmaps received, or how they are named, or on
-#     which nodes they are placed.  By default (when this parameter
-#     has never been set), bitmap names are mapped to themselves.
-#     Nodes are mapped to their block device name if there is one, and
-#     to their node name otherwise.  (Since 5.2)
-#
-# @x-vcpu-dirty-limit-period: Periodic time (in milliseconds) of dirty
-#     limit during live migration.  Should be in the range 1 to
-#     1000ms.  Defaults to 1000ms.  (Since 8.1)
-#
-# @vcpu-dirty-limit: Dirtyrate limit (MB/s) during live migration.
-#     Defaults to 1.  (Since 8.1)
-#
-# @mode: Migration mode.  See description in @MigMode.  Default is
-#     'normal'.  (Since 8.2)
-#
-# @zero-page-detection: Whether and how to detect zero pages.
-#     See description in @ZeroPageDetection.  Default is 'multifd'.
-#     (since 9.0)
-#
-# @direct-io: Open migration files with O_DIRECT when possible.  This
-#     only has effect if the @mapped-ram capability is enabled.
-#     (Since 9.1)
-#
-# Features:
-#
-# @unstable: Members @x-checkpoint-delay and
-#     @x-vcpu-dirty-limit-period are experimental.
-#
-# TODO: either fuse back into MigrationParameters, or make
-#     MigrationParameters members mandatory
-#
-# Since: 2.4
-##
-{ 'struct': 'MigrateSetParameters',
-  'data': { '*announce-initial': 'size',
-            '*announce-max': 'size',
-            '*announce-rounds': 'size',
-            '*announce-step': 'size',
-            '*throttle-trigger-threshold': 'uint8',
-            '*cpu-throttle-initial': 'uint8',
-            '*cpu-throttle-increment': 'uint8',
-            '*cpu-throttle-tailslow': 'bool',
-            '*tls-creds': 'StrOrNull',
-            '*tls-hostname': 'StrOrNull',
-            '*tls-authz': 'StrOrNull',
-            '*max-bandwidth': 'size',
-            '*avail-switchover-bandwidth': 'size',
-            '*downtime-limit': 'uint64',
-            '*x-checkpoint-delay': { 'type': 'uint32',
-                                     'features': [ 'unstable' ] },
-            '*multifd-channels': 'uint8',
-            '*xbzrle-cache-size': 'size',
-            '*max-postcopy-bandwidth': 'size',
-            '*max-cpu-throttle': 'uint8',
-            '*multifd-compression': 'MultiFDCompression',
-            '*multifd-zlib-level': 'uint8',
-            '*multifd-qatzip-level': 'uint8',
-            '*multifd-zstd-level': 'uint8',
-            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
-            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
-                                            'features': [ 'unstable' ] },
-            '*vcpu-dirty-limit': 'uint64',
-            '*mode': 'MigMode',
-            '*zero-page-detection': 'ZeroPageDetection',
-            '*direct-io': 'bool' } }
-
 ##
 # @migrate-set-parameters:
 #
@@ -1127,9 +931,7 @@
   'data': 'MigrateSetParameters' }
 
 ##
-# @MigrationParameters:
-#
-# The optional members aren't actually optional.
+# @MigrationConfigBase:
 #
 # @announce-initial: Initial delay (in milliseconds) before sending
 #     the first announce (Since 4.0)
@@ -1168,26 +970,6 @@
 #     be excessive at tail stage.  The default value is false.  (Since
 #     5.1)
 #
-# @tls-creds: ID of the 'tls-creds' object that provides credentials
-#     for establishing a TLS connection over the migration data
-#     channel.  On the outgoing side of the migration, the credentials
-#     must be for a 'client' endpoint, while for the incoming side the
-#     credentials must be for a 'server' endpoint.  An empty string
-#     means that QEMU will use plain text mode for migration, rather
-#     than TLS.  (Since 2.7)
-#
-#     Note: 2.8 omits empty @tls-creds instead.
-#
-# @tls-hostname: migration target's hostname for validating the
-#     server's x509 certificate identity.  If empty, QEMU will use the
-#     hostname from the migration URI, if any.  (Since 2.7)
-#
-#     Note: 2.8 omits empty @tls-hostname instead.
-#
-# @tls-authz: ID of the 'authz' object subclass that provides access
-#     control checking of the TLS x509 certificate distinguished name.
-#     (Since 4.0)
-#
 # @max-bandwidth: maximum speed for migration, in bytes per second.
 #     (Since 2.8)
 #
@@ -1277,45 +1059,121 @@
 #     only has effect if the @mapped-ram capability is enabled.
 #     (Since 9.1)
 #
+# @tls: Whether to use TLS. If this is set the options @tls-authz,
+#     @tls-creds, @tls-hostname are mandatory and a valid string is
+#     expected. (Since 10.1)
+#
 # Features:
 #
 # @unstable: Members @x-checkpoint-delay and
 #     @x-vcpu-dirty-limit-period are experimental.
 #
+# Since: 10.1
+##
+{ 'struct': 'MigrationConfigBase',
+  'data': { '*announce-initial': 'size',
+            '*announce-max': 'size',
+            '*announce-rounds': 'size',
+            '*announce-step': 'size',
+            '*throttle-trigger-threshold': 'uint8',
+            '*cpu-throttle-initial': 'uint8',
+            '*cpu-throttle-increment': 'uint8',
+            '*cpu-throttle-tailslow': 'bool',
+            '*max-bandwidth': 'size',
+            '*avail-switchover-bandwidth': 'size',
+            '*downtime-limit': 'uint64',
+            '*x-checkpoint-delay': { 'type': 'uint32',
+                                     'features': [ 'unstable' ] },
+            '*multifd-channels': 'uint8',
+            '*xbzrle-cache-size': 'size',
+            '*max-postcopy-bandwidth': 'size',
+            '*max-cpu-throttle': 'uint8',
+            '*multifd-compression': 'MultiFDCompression',
+            '*multifd-zlib-level': 'uint8',
+            '*multifd-qatzip-level': 'uint8',
+            '*multifd-zstd-level': 'uint8',
+            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
+            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
+                                            'features': [ 'unstable' ] },
+            '*vcpu-dirty-limit': 'uint64',
+            '*mode': 'MigMode',
+            '*zero-page-detection': 'ZeroPageDetection',
+            '*direct-io': 'bool',
+            '*tls': 'bool' } }
+
+##
+# @MigrationParameters:
+#
+# The optional members of the base type aren't actually optional.
+#
+# @tls-creds: ID of the 'tls-creds' object that provides credentials
+#     for establishing a TLS connection over the migration data
+#     channel.  On the outgoing side of the migration, the credentials
+#     must be for a 'client' endpoint, while for the incoming side the
+#     credentials must be for a 'server' endpoint.  Setting this to a
+#     non-empty string enables TLS for all migrations.  An empty
+#     string means that QEMU will use plain text mode for migration,
+#     rather than TLS.  (Since 2.7)
+#
+# @tls-hostname: migration target's hostname for validating the
+#     server's x509 certificate identity.  If empty, QEMU will use the
+#     hostname from the migration URI, if any.  A non-empty value is
+#     required when using x509 based TLS credentials and the migration
+#     URI does not include a hostname, such as fd: or exec: based
+#     migration.  (Since 2.7)
+#
+#     Note: empty value works only since 2.9.
+#
+# @tls-authz: ID of the 'authz' object subclass that provides access
+#     control checking of the TLS x509 certificate distinguished name.
+#     This object is only resolved at time of use, so can be deleted
+#     and recreated on the fly while the migration server is active.
+#     If missing, it will default to denying access (Since 4.0)
+#
 # Since: 2.4
 ##
 { 'struct': 'MigrationParameters',
-  'data': { '*announce-initial': 'size',
-            '*announce-max': 'size',
-            '*announce-rounds': 'size',
-            '*announce-step': 'size',
-            '*throttle-trigger-threshold': 'uint8',
-            '*cpu-throttle-initial': 'uint8',
-            '*cpu-throttle-increment': 'uint8',
-            '*cpu-throttle-tailslow': 'bool',
-            '*tls-creds': 'str',
-            '*tls-hostname': 'str',
-            '*tls-authz': 'str',
-            '*max-bandwidth': 'size',
-            '*avail-switchover-bandwidth': 'size',
-            '*downtime-limit': 'uint64',
-            '*x-checkpoint-delay': { 'type': 'uint32',
-                                     'features': [ 'unstable' ] },
-            '*multifd-channels': 'uint8',
-            '*xbzrle-cache-size': 'size',
-            '*max-postcopy-bandwidth': 'size',
-            '*max-cpu-throttle': 'uint8',
-            '*multifd-compression': 'MultiFDCompression',
-            '*multifd-zlib-level': 'uint8',
-            '*multifd-qatzip-level': 'uint8',
-            '*multifd-zstd-level': 'uint8',
-            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
-            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
-                                            'features': [ 'unstable' ] },
-            '*vcpu-dirty-limit': 'uint64',
-            '*mode': 'MigMode',
-            '*zero-page-detection': 'ZeroPageDetection',
-            '*direct-io': 'bool' } }
+  'base': 'MigrationConfigBase',
+  'data': { 'tls-creds': 'str',
+            'tls-hostname': 'str',
+            'tls-authz': 'str' } }
+
+##
+# @MigrateSetParameters:
+#
+# Compatibility layer to accept null values for the TLS options.
+#
+# @tls-creds: ID of the 'tls-creds' object that provides credentials
+#     for establishing a TLS connection over the migration data
+#     channel.  On the outgoing side of the migration, the credentials
+#     must be for a 'client' endpoint, while for the incoming side the
+#     credentials must be for a 'server' endpoint.  Setting this to a
+#     non-empty string enables TLS for all migrations.  An empty
+#     string means that QEMU will use plain text mode for migration,
+#     rather than TLS.  This is the default.  (Since 2.7)
+#
+# @tls-hostname: migration target's hostname for validating the
+#     server's x509 certificate identity.  If empty, QEMU will use the
+#     hostname from the migration URI, if any.  A non-empty value is
+#     required when using x509 based TLS credentials and the migration
+#     URI does not include a hostname, such as fd: or exec: based
+#     migration.  (Since 2.7)
+#
+#     Note: empty value works only since 2.9.
+#
+# @tls-authz: ID of the 'authz' object subclass that provides access
+#     control checking of the TLS x509 certificate distinguished name.
+#     This object is only resolved at time of use, so can be deleted
+#     and recreated on the fly while the migration server is active.
+#     If missing, it will default to denying access (Since 4.0)
+#
+# Since: 2.4
+##
+{ 'struct': 'MigrateSetParameters',
+  'base': 'MigrationConfigBase',
+  'data': { '*tls-creds': 'StrOrNull',
+            '*tls-hostname': 'StrOrNull',
+            '*tls-authz': 'StrOrNull' } }
 
 ##
 # @query-migrate-parameters:
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 06/13] migration: Remove the parameters copy during validation
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (4 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure Fabiano Rosas
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

While validating migration parameters, a temporary structure is used
to apply the values to be validated. Only when validation succeeds on
the temporary values, they are actually copied into the internal
s->parameters.

There is however an extra step, which is to copy s->parameters into
the temporary structure prior to merging the new values in. This is
not documented.

The validation function is intended to only validate new values by
checking the QAPI generated has_* boolean fields for each struct
member. However, nothing ever sets the has_* fields on the temporary
structure, so the reason that copy is there is to take advantage of
the has_* fields of s->parameters, which _have_ been set by
migrate_params_init() for the same reason of allowing
migrate_params_check() to interpret the values as newly added.

This is convoluted and has led to the current state of validating
_every_ parameter, even the ones who were not set by the user.

migrate_params_init() sets _all_ has_* fields because there are global
qdev properties defined for every parameter and those need to be
validated at migration_object_check().

Detangle the validation done at object creation from the one done
after user input, remove the s->parameters copy and move the
migrate_params_init() function close to the
migrate_params_check(). Add comments.

With the removal of the copy at migrate_set_parameters, there's still
the need to convert MigrateSetParameters into MigrationParameters to
satisfy the signature of migrate_params_check(). This is fine. Keep
it, but replace the inline copy of every member (done at
migrate_params_test_apply) with a QAPI_CLONE call as long prophecized
by the code comments.

This last step cannot be in a separate patch because with the removal
of the s->parameters copy, something needs to be done to set the has_*
fields of the temporary structure so it can be validated. That
something is already embedded in the MigrateSetParameters ->
MigrationParameters conversion due to the former having the booleans
properly set by QAPI.

MigrateSetParameters and MigrationParameters are obviously not of the
same type, so the QAPI_CLONE call will use the common base
MigrationConfigBase. The only remaining fields will be the TLS options
which will be set inline.

For the validation done at migration_check_object(), introduce a new
copy, to make sure we're not operating on the s->parameters and
leaving it dirty with the has_* fields set.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/migration.c |  22 +++++-
 migration/options.c   | 175 ++++++++++++------------------------------
 2 files changed, 67 insertions(+), 130 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index d46e776e24..2c3bb98df8 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -4051,8 +4051,6 @@ static void migration_instance_init(Object *obj)
     qemu_sem_init(&ms->pause_sem, 0);
     qemu_mutex_init(&ms->error_mutex);
 
-    migrate_params_init(&ms->parameters);
-
     qemu_sem_init(&ms->postcopy_pause_sem, 0);
     qemu_sem_init(&ms->rp_state.rp_sem, 0);
     qemu_sem_init(&ms->rp_state.rp_pong_acks, 0);
@@ -4070,11 +4068,29 @@ static bool migration_object_check(MigrationState *ms, Error **errp)
 {
     /* Assuming all off */
     bool old_caps[MIGRATION_CAPABILITY__MAX] = { 0 };
+    g_autoptr(MigrationParameters) globals = NULL;
 
-    if (!migrate_params_check(&ms->parameters, errp)) {
+    /*
+     * Copy the values that were already set via qdev properties
+     * (-global).
+     */
+    globals = QAPI_CLONE(MigrationParameters, &ms->parameters);
+
+    /*
+     * Set the has_* fields because migrate_params_check() only
+     * validates new fields.
+     */
+    migrate_params_init(globals);
+
+    if (!migrate_params_check(globals, errp)) {
         return false;
     }
 
+    /*
+     * After the validation succeeds, there's no need to apply the
+     * 'globals' because the values are already in s->config.
+     */
+
     return migrate_caps_check(old_caps, ms->capabilities, errp);
 }
 
diff --git a/migration/options.c b/migration/options.c
index 625d597a85..87599e4fdd 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -958,11 +958,7 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp)
 
 void migrate_params_init(MigrationParameters *params)
 {
-    params->tls_hostname = g_strdup("");
-    params->tls_creds = g_strdup("");
-    params->tls_authz = g_strdup("");
-
-    /* Set has_* up only for parameter checks */
+    /* these should match the parameters in migration_properties */
     params->has_throttle_trigger_threshold = true;
     params->has_cpu_throttle_initial = true;
     params->has_cpu_throttle_increment = true;
@@ -1178,138 +1174,51 @@ bool migrate_params_check(MigrationParameters *params, Error **errp)
     return true;
 }
 
-static void migrate_params_test_apply(MigrateSetParameters *params,
-                                      MigrationParameters *dest)
+/*
+ * Compatibility layer to convert MigrateSetParameters to
+ * MigrationParameters. In the existing QMP user interface, the
+ * migrate-set-parameters command takes the TLS options as 'StrOrNull'
+ * while the query-migrate-parameters command returns the TLS strings
+ * as 'str'.
+ */
+static void migrate_params_copy_compat(MigrationParameters *dst,
+                                       MigrateSetParameters *src)
 {
-    *dest = migrate_get_current()->parameters;
-
-    /* TODO use QAPI_CLONE() instead of duplicating it inline */
-
-    if (params->has_throttle_trigger_threshold) {
-        dest->throttle_trigger_threshold = params->throttle_trigger_threshold;
-    }
-
-    if (params->has_cpu_throttle_initial) {
-        dest->cpu_throttle_initial = params->cpu_throttle_initial;
-    }
-
-    if (params->has_cpu_throttle_increment) {
-        dest->cpu_throttle_increment = params->cpu_throttle_increment;
-    }
-
-    if (params->has_cpu_throttle_tailslow) {
-        dest->cpu_throttle_tailslow = params->cpu_throttle_tailslow;
-    }
-
-    if (params->tls_creds) {
-        if (params->tls_creds->type == QTYPE_QNULL) {
-            dest->tls_creds = NULL;
+    /* copy the common elements between the two */
+    QAPI_CLONE_MEMBERS(MigrationConfigBase,
+                       (MigrationConfigBase *)dst,
+                       (MigrationConfigBase *)src);
+
+     /* now copy the elements of different type */
+    if (src->tls_creds) {
+        if (src->tls_creds->type == QTYPE_QNULL) {
+            dst->tls_creds = NULL;
         } else {
-            dest->tls_creds = params->tls_creds->u.s;
+            dst->tls_creds = src->tls_creds->u.s;
         }
     }
 
-    if (params->tls_hostname) {
-        if (params->tls_hostname->type == QTYPE_QNULL) {
-            dest->tls_hostname = NULL;
+    if (src->tls_hostname) {
+        if (src->tls_hostname->type == QTYPE_QNULL) {
+            dst->tls_hostname = NULL;
         } else {
-            dest->tls_hostname = params->tls_hostname->u.s;
+            dst->tls_hostname = src->tls_hostname->u.s;
         }
     }
 
-    if (params->tls_authz) {
-        if (params->tls_authz->type == QTYPE_QNULL) {
-            dest->tls_authz = NULL;
+    if (src->tls_authz) {
+        if (src->tls_authz->type == QTYPE_QNULL) {
+            dst->tls_authz = NULL;
         } else {
-            dest->tls_authz = params->tls_authz->u.s;
+            dst->tls_authz = src->tls_authz->u.s;
         }
     }
-
-    if (params->has_max_bandwidth) {
-        dest->max_bandwidth = params->max_bandwidth;
-    }
-
-    if (params->has_avail_switchover_bandwidth) {
-        dest->avail_switchover_bandwidth = params->avail_switchover_bandwidth;
-    }
-
-    if (params->has_downtime_limit) {
-        dest->downtime_limit = params->downtime_limit;
-    }
-
-    if (params->has_x_checkpoint_delay) {
-        dest->x_checkpoint_delay = params->x_checkpoint_delay;
-    }
-
-    if (params->has_multifd_channels) {
-        dest->multifd_channels = params->multifd_channels;
-    }
-    if (params->has_multifd_compression) {
-        dest->multifd_compression = params->multifd_compression;
-    }
-    if (params->has_multifd_qatzip_level) {
-        dest->multifd_qatzip_level = params->multifd_qatzip_level;
-    }
-    if (params->has_multifd_zlib_level) {
-        dest->multifd_zlib_level = params->multifd_zlib_level;
-    }
-    if (params->has_multifd_zstd_level) {
-        dest->multifd_zstd_level = params->multifd_zstd_level;
-    }
-    if (params->has_xbzrle_cache_size) {
-        dest->xbzrle_cache_size = params->xbzrle_cache_size;
-    }
-    if (params->has_max_postcopy_bandwidth) {
-        dest->max_postcopy_bandwidth = params->max_postcopy_bandwidth;
-    }
-    if (params->has_max_cpu_throttle) {
-        dest->max_cpu_throttle = params->max_cpu_throttle;
-    }
-    if (params->has_announce_initial) {
-        dest->announce_initial = params->announce_initial;
-    }
-    if (params->has_announce_max) {
-        dest->announce_max = params->announce_max;
-    }
-    if (params->has_announce_rounds) {
-        dest->announce_rounds = params->announce_rounds;
-    }
-    if (params->has_announce_step) {
-        dest->announce_step = params->announce_step;
-    }
-
-    if (params->has_block_bitmap_mapping) {
-        dest->has_block_bitmap_mapping = true;
-        dest->block_bitmap_mapping = params->block_bitmap_mapping;
-    }
-
-    if (params->has_x_vcpu_dirty_limit_period) {
-        dest->x_vcpu_dirty_limit_period =
-            params->x_vcpu_dirty_limit_period;
-    }
-    if (params->has_vcpu_dirty_limit) {
-        dest->vcpu_dirty_limit = params->vcpu_dirty_limit;
-    }
-
-    if (params->has_mode) {
-        dest->mode = params->mode;
-    }
-
-    if (params->has_zero_page_detection) {
-        dest->zero_page_detection = params->zero_page_detection;
-    }
-
-    if (params->has_direct_io) {
-        dest->direct_io = params->direct_io;
-    }
 }
 
 static void migrate_params_apply(MigrateSetParameters *params)
 {
     MigrationState *s = migrate_get_current();
 
-    /* TODO use QAPI_CLONE() instead of duplicating it inline */
-
     if (params->has_throttle_trigger_threshold) {
         s->parameters.throttle_trigger_threshold = params->throttle_trigger_threshold;
     }
@@ -1328,20 +1237,32 @@ static void migrate_params_apply(MigrateSetParameters *params)
 
     if (params->tls_creds) {
         g_free(s->parameters.tls_creds);
-        assert(params->tls_creds->type == QTYPE_QSTRING);
-        s->parameters.tls_creds = g_strdup(params->tls_creds->u.s);
+
+        if (params->tls_creds->type == QTYPE_QNULL) {
+            s->parameters.tls_creds = NULL;
+        } else {
+            s->parameters.tls_creds = g_strdup(params->tls_creds->u.s);
+        }
     }
 
     if (params->tls_hostname) {
         g_free(s->parameters.tls_hostname);
-        assert(params->tls_hostname->type == QTYPE_QSTRING);
-        s->parameters.tls_hostname = g_strdup(params->tls_hostname->u.s);
+
+        if (params->tls_hostname->type == QTYPE_QNULL) {
+            s->parameters.tls_hostname = NULL;
+        } else {
+            s->parameters.tls_hostname = g_strdup(params->tls_hostname->u.s);
+        }
     }
 
     if (params->tls_authz) {
         g_free(s->parameters.tls_authz);
-        assert(params->tls_authz->type == QTYPE_QSTRING);
-        s->parameters.tls_authz = g_strdup(params->tls_authz->u.s);
+
+        if (params->tls_authz->type == QTYPE_QNULL) {
+            s->parameters.tls_authz = NULL;
+        } else {
+            s->parameters.tls_authz = g_strdup(params->tls_authz->u.s);
+        }
     }
 
     if (params->has_max_bandwidth) {
@@ -1456,9 +1377,9 @@ static void migrate_post_update_params(MigrateSetParameters *new, Error **errp)
 
 void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
 {
-    MigrationParameters tmp;
+    MigrationParameters tmp = {};
 
-    migrate_params_test_apply(params, &tmp);
+    migrate_params_copy_compat(&tmp, params);
 
     if (!migrate_params_check(&tmp, errp)) {
         /* Invalid parameter */
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (5 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 06/13] migration: Remove the parameters copy during validation Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-18  7:03   ` Markus Armbruster
  2025-04-11 19:14 ` [RFC PATCH 08/13] migration: Replace s->parameters with s->config Fabiano Rosas
                   ` (6 subsequent siblings)
  13 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Add a new migration structure to consolidate the capabilities and
parameters. This structure will be used in place of the s->parameters
and s->capabilities data structures in the next few patches.

The QAPI migration types now look like this:

/* options previously known as parameters */
{ 'struct': 'MigrationConfigBase',
  'data': {
      <parameters>
} }

/* for compat with query-migrate-parameters */
{ 'struct': 'MigrationParameters',
  'base': 'MigrationConfigBase',
  'data': {
      <TLS options in 'str' format>
} }

/* for compat with migrate-set-parameters */
{ 'struct': 'MigrateSetParameters',
  'base': 'MigrationConfigBase',
  'data': {
      <TLS options in 'StrOrNull' format>
} }

/* to replace MigrationParameters in the MigrationState */
{ 'struct': 'MigrationConfig',
  'base': 'MigrationConfigBase'
  'data': {
    <TLS options in 'str' format>
} }

The above keeps the query/set-parameters commands stable. For the
capabilities as well as the options added in the future, we have a
choice of where to put them:

1) In MigrationConfigBase, this means that the existing
   query/set-parameters commands will be updated to deal with
   capabilities/new options.

  { 'struct': 'MigrationConfigBase',
    'data': {
      <parameters>
      <capabilities>
      <new opts>
  } }

  { 'struct': 'MigrationConfig',
    'base': 'MigrationConfigBase'
    'data': {
      <TLS options in 'str' format>
  } }

2) In MigrationConfig, this means that the existing commands will be
   frozen in time.

  { 'struct': 'MigrationConfigBase',
    'data': {
      <parameters>
  } }

  { 'struct': 'MigrationConfig',
    'base': 'MigrationConfigBase'
    'data': {
      <TLS options in 'str' format>
      <capabilities>
      <new opts>
  } }

For now, I've chosen the option 1, all capabilities and new options go
into MigrationConfigBase. This gives the option to keep the existing
commands for as long as we'd like.

Note that the query/set capabilities commands will have to go, we can
treat parameters as generic configuration options, but capabilities
are just too different.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 qapi/migration.json | 163 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 160 insertions(+), 3 deletions(-)

diff --git a/qapi/migration.json b/qapi/migration.json
index 5a4d5a2d3e..5e39f21adc 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -1063,10 +1063,108 @@
 #     @tls-creds, @tls-hostname are mandatory and a valid string is
 #     expected. (Since 10.1)
 #
+# @xbzrle: Migration supports xbzrle (Xor Based Zero Run Length
+#     Encoding).  This feature allows us to minimize migration traffic
+#     for certain work loads, by sending compressed difference of the
+#     pages
+#
+# @rdma-pin-all: Controls whether or not the entire VM memory
+#     footprint is mlock()'d on demand or all at once.  Refer to
+#     docs/rdma.txt for usage.  Disabled by default.  (since 2.0)
+#
+# @zero-blocks: During storage migration encode blocks of zeroes
+#     efficiently.  This essentially saves 1MB of zeroes per block on
+#     the wire.  Enabling requires source and target VM to support
+#     this feature.  To enable it is sufficient to enable the
+#     capability on the source VM.  The feature is disabled by
+#     default.  (since 1.6)
+#
+# @events: generate events for each migration state change (since 2.4)
+#
+# @auto-converge: If enabled, QEMU will automatically throttle down
+#     the guest to speed up convergence of RAM migration.  (since 1.6)
+#
+# @postcopy-ram: Start executing on the migration target before all of
+#     RAM has been migrated, pulling the remaining pages along as
+#     needed.  The capacity must have the same setting on both source
+#     and target or migration will not even start.  NOTE: If the
+#     migration fails during postcopy the VM will fail.  (since 2.6)
+#
+# @x-colo: If enabled, migration will never end, and the state of the
+#     VM on the primary side will be migrated continuously to the VM
+#     on secondary side, this process is called COarse-Grain LOck
+#     Stepping (COLO) for Non-stop Service.  (since 2.8)
+#
+# @release-ram: if enabled, qemu will free the migrated ram pages on
+#     the source during postcopy-ram migration.  (since 2.9)
+#
+# @return-path: If enabled, migration will use the return path even
+#     for precopy.  (since 2.10)
+#
+# @pause-before-switchover: Pause outgoing migration before
+#     serialising device state and before disabling block IO (since
+#     2.11)
+#
+# @multifd: Use more than one fd for migration (since 4.0)
+#
+# @dirty-bitmaps: If enabled, QEMU will migrate named dirty bitmaps.
+#     (since 2.12)
+#
+# @postcopy-blocktime: Calculate downtime for postcopy live migration
+#     (since 3.0)
+#
+# @late-block-activate: If enabled, the destination will not activate
+#     block devices (and thus take locks) immediately at the end of
+#     migration.  (since 3.0)
+#
+# @x-ignore-shared: If enabled, QEMU will not migrate shared memory
+#     that is accessible on the destination machine.  (since 4.0)
+#
+# @validate-uuid: Send the UUID of the source to allow the destination
+#     to ensure it is the same.  (since 4.2)
+#
+# @background-snapshot: If enabled, the migration stream will be a
+#     snapshot of the VM exactly at the point when the migration
+#     procedure starts.  The VM RAM is saved with running VM.
+#     (since 6.0)
+#
+# @zero-copy-send: Controls behavior on sending memory pages on
+#     migration.  When true, enables a zero-copy mechanism for sending
+#     memory pages, if host supports it.  Requires that QEMU be
+#     permitted to use locked memory for guest RAM pages.  (since 7.1)
+#
+# @postcopy-preempt: If enabled, the migration process will allow
+#     postcopy requests to preempt precopy stream, so postcopy
+#     requests will be handled faster.  This is a performance feature
+#     and should not affect the correctness of postcopy migration.
+#     (since 7.1)
+#
+# @switchover-ack: If enabled, migration will not stop the source VM
+#     and complete the migration until an ACK is received from the
+#     destination that it's OK to do so.  Exactly when this ACK is
+#     sent depends on the migrated devices that use this feature.  For
+#     example, a device can use it to make sure some of its data is
+#     sent and loaded in the destination before doing switchover.
+#     This can reduce downtime if devices that support this capability
+#     are present.  'return-path' capability must be enabled to use
+#     it.  (since 8.1)
+#
+# @dirty-limit: If enabled, migration will throttle vCPUs as needed to
+#     keep their dirty page rate within @vcpu-dirty-limit.  This can
+#     improve responsiveness of large guests during live migration,
+#     and can result in more stable read performance.  Requires KVM
+#     with accelerator property "dirty-ring-size" set.  (Since 8.1)
+#
+# @mapped-ram: Migrate using fixed offsets in the migration file for
+#     each RAM page.  Requires a migration URI that supports seeking,
+#     such as a file.  (since 9.0)
+#
 # Features:
 #
-# @unstable: Members @x-checkpoint-delay and
-#     @x-vcpu-dirty-limit-period are experimental.
+# @unstable: Members @x-checkpoint-delay, @x-vcpu-dirty-limit-period,
+#     @x-colo and @x-ignore-shared are experimental.
+# @deprecated: Member @zero-blocks is deprecated as being part of
+#     block migration which was already removed.
 #
 # Since: 10.1
 ##
@@ -1099,7 +1197,29 @@
             '*mode': 'MigMode',
             '*zero-page-detection': 'ZeroPageDetection',
             '*direct-io': 'bool',
-            '*tls': 'bool' } }
+            '*tls': 'bool',
+            '*xbzrle': 'bool',
+            '*rdma-pin-all': 'bool',
+            '*auto-converge': 'bool',
+            '*zero-blocks': { 'type': 'bool', 'features': [ 'deprecated' ] },
+            '*events': 'bool',
+            '*postcopy-ram': 'bool',
+            '*x-colo': { 'type': 'bool', 'features': [ 'unstable' ] },
+            '*release-ram': 'bool',
+            '*return-path': 'bool',
+            '*pause-before-switchover': 'bool',
+            '*multifd': 'bool',
+            '*dirty-bitmaps': 'bool',
+            '*postcopy-blocktime': 'bool',
+            '*late-block-activate': 'bool',
+            '*x-ignore-shared': { 'type': 'bool', 'features': [ 'unstable' ] },
+            '*validate-uuid': 'bool',
+            '*background-snapshot': 'bool',
+            '*zero-copy-send': 'bool',
+            '*postcopy-preempt': 'bool',
+            '*switchover-ack': 'bool',
+            '*dirty-limit': 'bool',
+            '*mapped-ram': 'bool' } }
 
 ##
 # @MigrationParameters:
@@ -2395,3 +2515,40 @@
   'data': { 'job-id': 'str',
             'tag': 'str',
             'devices': ['str'] } }
+
+##
+# @MigrationConfig:
+#
+# Migration configuration options
+#
+# @tls-creds: ID of the 'tls-creds' object that provides credentials
+#     for establishing a TLS connection over the migration data
+#     channel.  On the outgoing side of the migration, the credentials
+#     must be for a 'client' endpoint, while for the incoming side the
+#     credentials must be for a 'server' endpoint.  Setting this to a
+#     non-empty string enables TLS for all migrations.  An empty
+#     string means that QEMU will use plain text mode for migration,
+#     rather than TLS.  (Since 2.7)
+#
+# @tls-hostname: migration target's hostname for validating the
+#     server's x509 certificate identity.  If empty, QEMU will use the
+#     hostname from the migration URI, if any.  A non-empty value is
+#     required when using x509 based TLS credentials and the migration
+#     URI does not include a hostname, such as fd: or exec: based
+#     migration.  (Since 2.7)
+#
+#     Note: empty value works only since 2.9.
+#
+# @tls-authz: ID of the 'authz' object subclass that provides access
+#     control checking of the TLS x509 certificate distinguished name.
+#     This object is only resolved at time of use, so can be deleted
+#     and recreated on the fly while the migration server is active.
+#     If missing, it will default to denying access (Since 4.0)
+#
+# Since: 10.1
+##
+{ 'struct': 'MigrationConfig',
+  'base': 'MigrationConfigBase',
+  'data': { '*tls-creds': 'str',
+            '*tls-hostname': 'str',
+            '*tls-authz': 'str' } }
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 08/13] migration: Replace s->parameters with s->config
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (6 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 09/13] migration: Do away with usage of QERR_INVALID_PARAMETER_VALUE Fabiano Rosas
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

The concepts of 'parameters' and 'capabilities' are almost
functionally identical and there is a consensus that their usage is
confusing, particularly when it comes to determine to which category a
new option should belong.

The new MigrationConfig type was introduced to eliminate the
distinction between parameters and capabilities and contains the union
of both groups.

Replace internal usages of MigrationParameters with MigrationConfig.

For the user-facing migrate-set-parameters and
query-migrate-parameters, the old types MigrateSetParameters and
MigrationParameters are still used.

Note that migrate_params_check (now migrate_config_check) will be used
in the future also for capabilities validation and for any future
command that uses MigrationConfig.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/migration.c |  25 ++-
 migration/migration.h |   4 +-
 migration/options.c   | 371 ++++++++++++++++++++----------------------
 migration/options.h   |   8 +-
 migration/ram.c       |   4 +-
 5 files changed, 192 insertions(+), 220 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index 2c3bb98df8..9324d4f44e 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -119,7 +119,7 @@ static void migration_downtime_start(MigrationState *s)
 /*
  * This is unfortunate: incoming migration actually needs the outgoing
  * migration state (MigrationState) to be there too, e.g. to query
- * capabilities, parameters, using locks, setup errors, etc.
+ * options, using locks, setup errors, etc.
  *
  * NOTE: when calling this, making sure current_migration exists and not
  * been freed yet!  Otherwise trying to access the refcount is already
@@ -1664,7 +1664,7 @@ void migration_remove_notifier(NotifierWithReturn *notify)
 int migration_call_notifiers(MigrationState *s, MigrationEventType type,
                              Error **errp)
 {
-    MigMode mode = s->parameters.mode;
+    MigMode mode = s->config.mode;
     MigrationEvent e;
     int ret;
 
@@ -1735,7 +1735,7 @@ bool migration_thread_is_self(void)
 
 bool migrate_mode_is_cpr(MigrationState *s)
 {
-    MigMode mode = s->parameters.mode;
+    MigMode mode = s->config.mode;
     return mode == MIG_MODE_CPR_REBOOT ||
            mode == MIG_MODE_CPR_TRANSFER;
 }
@@ -1750,9 +1750,8 @@ int migrate_init(MigrationState *s, Error **errp)
     }
 
     /*
-     * Reinitialise all migration state, except
-     * parameters/capabilities that the user set, and
-     * locks.
+     * Reinitialise all migration state, except options that the user
+     * set, and locks.
      */
     s->to_dst_file = NULL;
     s->state = MIGRATION_STATUS_NONE;
@@ -2212,7 +2211,7 @@ void qmp_migrate(const char *uri, bool has_channels,
         return;
     }
 
-    if (s->parameters.mode == MIG_MODE_CPR_TRANSFER && !cpr_channel) {
+    if (s->config.mode == MIG_MODE_CPR_TRANSFER && !cpr_channel) {
         error_setg(errp, "missing 'cpr' migration channel");
         return;
     }
@@ -2237,7 +2236,7 @@ void qmp_migrate(const char *uri, bool has_channels,
      * in which case the target will not listen for the incoming migration
      * connection, so qmp_migrate_finish will fail to connect, and then recover.
      */
-    if (s->parameters.mode == MIG_MODE_CPR_TRANSFER) {
+    if (s->config.mode == MIG_MODE_CPR_TRANSFER) {
         migrate_hup_add(s, cpr_state_ioc(), (GSourceFunc)qmp_migrate_finish_cb,
                         QAPI_CLONE(MigrationAddress, addr));
 
@@ -4068,21 +4067,21 @@ static bool migration_object_check(MigrationState *ms, Error **errp)
 {
     /* Assuming all off */
     bool old_caps[MIGRATION_CAPABILITY__MAX] = { 0 };
-    g_autoptr(MigrationParameters) globals = NULL;
+    g_autoptr(MigrationConfig) globals = NULL;
 
     /*
      * Copy the values that were already set via qdev properties
      * (-global).
      */
-    globals = QAPI_CLONE(MigrationParameters, &ms->parameters);
+    globals = QAPI_CLONE(MigrationConfig, &ms->config);
 
     /*
-     * Set the has_* fields because migrate_params_check() only
+     * Set the has_* fields because migrate_config_check() only
      * validates new fields.
      */
-    migrate_params_init(globals);
+    migrate_config_init(globals);
 
-    if (!migrate_params_check(globals, errp)) {
+    if (!migrate_config_check(globals, errp)) {
         return false;
     }
 
diff --git a/migration/migration.h b/migration/migration.h
index d53f7cad84..8e6aa595b6 100644
--- a/migration/migration.h
+++ b/migration/migration.h
@@ -317,9 +317,7 @@ struct MigrationState {
      */
     uint64_t threshold_size;
 
-    /* params from 'migrate-set-parameters' */
-    MigrationParameters parameters;
-
+    MigrationConfig config;
     MigrationStatus state;
 
     /* State related to return path */
diff --git a/migration/options.c b/migration/options.c
index 87599e4fdd..7c41fbbce6 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -41,7 +41,7 @@
  * for sending the last part */
 #define DEFAULT_MIGRATE_SET_DOWNTIME 300
 
-/* Define default autoconverge cpu throttle migration parameters */
+/* Define default autoconverge cpu throttle migration options */
 #define DEFAULT_MIGRATE_THROTTLE_TRIGGER_THRESHOLD 50
 #define DEFAULT_MIGRATE_CPU_THROTTLE_INITIAL 20
 #define DEFAULT_MIGRATE_CPU_THROTTLE_INCREMENT 10
@@ -72,7 +72,7 @@
 #define DEFAULT_MIGRATE_MAX_POSTCOPY_BANDWIDTH 0
 
 /*
- * Parameters for self_announce_delay giving a stream of RARP/ARP
+ * Defaults for self_announce_delay giving a stream of RARP/ARP
  * packets after migration.
  */
 #define DEFAULT_MIGRATE_ANNOUNCE_INITIAL  50
@@ -104,78 +104,77 @@ const Property migration_properties[] = {
     DEFINE_PROP_BOOL("multifd-clean-tls-termination", MigrationState,
                      multifd_clean_tls_termination, true),
 
-    /* Migration parameters */
     DEFINE_PROP_UINT8("x-throttle-trigger-threshold", MigrationState,
-                      parameters.throttle_trigger_threshold,
+                      config.throttle_trigger_threshold,
                       DEFAULT_MIGRATE_THROTTLE_TRIGGER_THRESHOLD),
     DEFINE_PROP_UINT8("x-cpu-throttle-initial", MigrationState,
-                      parameters.cpu_throttle_initial,
+                      config.cpu_throttle_initial,
                       DEFAULT_MIGRATE_CPU_THROTTLE_INITIAL),
     DEFINE_PROP_UINT8("x-cpu-throttle-increment", MigrationState,
-                      parameters.cpu_throttle_increment,
+                      config.cpu_throttle_increment,
                       DEFAULT_MIGRATE_CPU_THROTTLE_INCREMENT),
     DEFINE_PROP_BOOL("x-cpu-throttle-tailslow", MigrationState,
-                      parameters.cpu_throttle_tailslow, false),
+                      config.cpu_throttle_tailslow, false),
     DEFINE_PROP_SIZE("x-max-bandwidth", MigrationState,
-                      parameters.max_bandwidth, MAX_THROTTLE),
+                      config.max_bandwidth, MAX_THROTTLE),
     DEFINE_PROP_SIZE("avail-switchover-bandwidth", MigrationState,
-                      parameters.avail_switchover_bandwidth, 0),
+                      config.avail_switchover_bandwidth, 0),
     DEFINE_PROP_UINT64("x-downtime-limit", MigrationState,
-                      parameters.downtime_limit,
+                      config.downtime_limit,
                       DEFAULT_MIGRATE_SET_DOWNTIME),
     DEFINE_PROP_UINT32("x-checkpoint-delay", MigrationState,
-                      parameters.x_checkpoint_delay,
+                      config.x_checkpoint_delay,
                       DEFAULT_MIGRATE_X_CHECKPOINT_DELAY),
     DEFINE_PROP_UINT8("multifd-channels", MigrationState,
-                      parameters.multifd_channels,
+                      config.multifd_channels,
                       DEFAULT_MIGRATE_MULTIFD_CHANNELS),
     DEFINE_PROP_MULTIFD_COMPRESSION("multifd-compression", MigrationState,
-                      parameters.multifd_compression,
+                      config.multifd_compression,
                       DEFAULT_MIGRATE_MULTIFD_COMPRESSION),
     DEFINE_PROP_UINT8("multifd-zlib-level", MigrationState,
-                      parameters.multifd_zlib_level,
+                      config.multifd_zlib_level,
                       DEFAULT_MIGRATE_MULTIFD_ZLIB_LEVEL),
     DEFINE_PROP_UINT8("multifd-qatzip-level", MigrationState,
-                      parameters.multifd_qatzip_level,
+                      config.multifd_qatzip_level,
                       DEFAULT_MIGRATE_MULTIFD_QATZIP_LEVEL),
     DEFINE_PROP_UINT8("multifd-zstd-level", MigrationState,
-                      parameters.multifd_zstd_level,
+                      config.multifd_zstd_level,
                       DEFAULT_MIGRATE_MULTIFD_ZSTD_LEVEL),
     DEFINE_PROP_SIZE("xbzrle-cache-size", MigrationState,
-                      parameters.xbzrle_cache_size,
+                      config.xbzrle_cache_size,
                       DEFAULT_MIGRATE_XBZRLE_CACHE_SIZE),
     DEFINE_PROP_SIZE("max-postcopy-bandwidth", MigrationState,
-                      parameters.max_postcopy_bandwidth,
+                      config.max_postcopy_bandwidth,
                       DEFAULT_MIGRATE_MAX_POSTCOPY_BANDWIDTH),
     DEFINE_PROP_UINT8("max-cpu-throttle", MigrationState,
-                      parameters.max_cpu_throttle,
+                      config.max_cpu_throttle,
                       DEFAULT_MIGRATE_MAX_CPU_THROTTLE),
     DEFINE_PROP_SIZE("announce-initial", MigrationState,
-                      parameters.announce_initial,
+                      config.announce_initial,
                       DEFAULT_MIGRATE_ANNOUNCE_INITIAL),
     DEFINE_PROP_SIZE("announce-max", MigrationState,
-                      parameters.announce_max,
+                      config.announce_max,
                       DEFAULT_MIGRATE_ANNOUNCE_MAX),
     DEFINE_PROP_SIZE("announce-rounds", MigrationState,
-                      parameters.announce_rounds,
+                      config.announce_rounds,
                       DEFAULT_MIGRATE_ANNOUNCE_ROUNDS),
     DEFINE_PROP_SIZE("announce-step", MigrationState,
-                      parameters.announce_step,
+                      config.announce_step,
                       DEFAULT_MIGRATE_ANNOUNCE_STEP),
-    DEFINE_PROP_STRING("tls-creds", MigrationState, parameters.tls_creds),
-    DEFINE_PROP_STRING("tls-hostname", MigrationState, parameters.tls_hostname),
-    DEFINE_PROP_STRING("tls-authz", MigrationState, parameters.tls_authz),
+    DEFINE_PROP_STRING("tls-creds", MigrationState, config.tls_creds),
+    DEFINE_PROP_STRING("tls-hostname", MigrationState, config.tls_hostname),
+    DEFINE_PROP_STRING("tls-authz", MigrationState, config.tls_authz),
     DEFINE_PROP_UINT64("x-vcpu-dirty-limit-period", MigrationState,
-                       parameters.x_vcpu_dirty_limit_period,
+                       config.x_vcpu_dirty_limit_period,
                        DEFAULT_MIGRATE_VCPU_DIRTY_LIMIT_PERIOD),
     DEFINE_PROP_UINT64("vcpu-dirty-limit", MigrationState,
-                       parameters.vcpu_dirty_limit,
+                       config.vcpu_dirty_limit,
                        DEFAULT_MIGRATE_VCPU_DIRTY_LIMIT),
     DEFINE_PROP_MIG_MODE("mode", MigrationState,
-                      parameters.mode,
+                      config.mode,
                       MIG_MODE_NORMAL),
     DEFINE_PROP_ZERO_PAGE_DETECTION("zero-page-detection", MigrationState,
-                       parameters.zero_page_detection,
+                       config.zero_page_detection,
                        ZERO_PAGE_DETECTION_MULTIFD),
 
     /* Migration capabilities */
@@ -650,48 +649,46 @@ void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params,
     }
 }
 
-/* parameters */
-
 const BitmapMigrationNodeAliasList *migrate_block_bitmap_mapping(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.block_bitmap_mapping;
+    return s->config.block_bitmap_mapping;
 }
 
 bool migrate_has_block_bitmap_mapping(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.has_block_bitmap_mapping;
+    return s->config.has_block_bitmap_mapping;
 }
 
 uint32_t migrate_checkpoint_delay(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.x_checkpoint_delay;
+    return s->config.x_checkpoint_delay;
 }
 
 uint8_t migrate_cpu_throttle_increment(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.cpu_throttle_increment;
+    return s->config.cpu_throttle_increment;
 }
 
 uint8_t migrate_cpu_throttle_initial(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.cpu_throttle_initial;
+    return s->config.cpu_throttle_initial;
 }
 
 bool migrate_cpu_throttle_tailslow(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.cpu_throttle_tailslow;
+    return s->config.cpu_throttle_tailslow;
 }
 
 bool migrate_direct_io(void)
@@ -708,7 +705,7 @@ bool migrate_direct_io(void)
      * isolated to the main migration thread while multifd channels
      * process the aligned data with O_DIRECT enabled.
      */
-    return s->parameters.direct_io &&
+    return s->config.direct_io &&
         s->capabilities[MIGRATION_CAPABILITY_MAPPED_RAM] &&
         s->capabilities[MIGRATION_CAPABILITY_MULTIFD];
 }
@@ -717,35 +714,35 @@ uint64_t migrate_downtime_limit(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.downtime_limit;
+    return s->config.downtime_limit;
 }
 
 uint8_t migrate_max_cpu_throttle(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.max_cpu_throttle;
+    return s->config.max_cpu_throttle;
 }
 
 uint64_t migrate_max_bandwidth(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.max_bandwidth;
+    return s->config.max_bandwidth;
 }
 
 uint64_t migrate_avail_switchover_bandwidth(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.avail_switchover_bandwidth;
+    return s->config.avail_switchover_bandwidth;
 }
 
 uint64_t migrate_max_postcopy_bandwidth(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.max_postcopy_bandwidth;
+    return s->config.max_postcopy_bandwidth;
 }
 
 MigMode migrate_mode(void)
@@ -753,7 +750,7 @@ MigMode migrate_mode(void)
     MigMode mode = cpr_get_incoming_mode();
 
     if (mode == MIG_MODE_NONE) {
-        mode = migrate_get_current()->parameters.mode;
+        mode = migrate_get_current()->config.mode;
     }
 
     assert(mode >= 0 && mode < MIG_MODE__MAX);
@@ -764,52 +761,52 @@ int migrate_multifd_channels(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.multifd_channels;
+    return s->config.multifd_channels;
 }
 
 MultiFDCompression migrate_multifd_compression(void)
 {
     MigrationState *s = migrate_get_current();
 
-    assert(s->parameters.multifd_compression < MULTIFD_COMPRESSION__MAX);
-    return s->parameters.multifd_compression;
+    assert(s->config.multifd_compression < MULTIFD_COMPRESSION__MAX);
+    return s->config.multifd_compression;
 }
 
 int migrate_multifd_zlib_level(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.multifd_zlib_level;
+    return s->config.multifd_zlib_level;
 }
 
 int migrate_multifd_qatzip_level(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.multifd_qatzip_level;
+    return s->config.multifd_qatzip_level;
 }
 
 int migrate_multifd_zstd_level(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.multifd_zstd_level;
+    return s->config.multifd_zstd_level;
 }
 
 uint8_t migrate_throttle_trigger_threshold(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.throttle_trigger_threshold;
+    return s->config.throttle_trigger_threshold;
 }
 
 const char *migrate_tls_authz(void)
 {
     MigrationState *s = migrate_get_current();
 
-    if (s->parameters.tls_authz &&
-        *s->parameters.tls_authz) {
-        return s->parameters.tls_authz;
+    if (s->config.tls_authz &&
+        *s->config.tls_authz) {
+        return s->config.tls_authz;
     }
 
     return NULL;
@@ -819,9 +816,9 @@ const char *migrate_tls_creds(void)
 {
     MigrationState *s = migrate_get_current();
 
-    if (s->parameters.tls_creds &&
-        *s->parameters.tls_creds) {
-        return s->parameters.tls_creds;
+    if (s->config.tls_creds &&
+        *s->config.tls_creds) {
+        return s->config.tls_creds;
     }
 
     return NULL;
@@ -831,9 +828,9 @@ const char *migrate_tls_hostname(void)
 {
     MigrationState *s = migrate_get_current();
 
-    if (s->parameters.tls_hostname &&
-        *s->parameters.tls_hostname) {
-        return s->parameters.tls_hostname;
+    if (s->config.tls_hostname &&
+        *s->config.tls_hostname) {
+        return s->config.tls_hostname;
     }
 
     return NULL;
@@ -848,35 +845,33 @@ uint64_t migrate_vcpu_dirty_limit_period(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.x_vcpu_dirty_limit_period;
+    return s->config.x_vcpu_dirty_limit_period;
 }
 
 uint64_t migrate_xbzrle_cache_size(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.xbzrle_cache_size;
+    return s->config.xbzrle_cache_size;
 }
 
 ZeroPageDetection migrate_zero_page_detection(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->parameters.zero_page_detection;
+    return s->config.zero_page_detection;
 }
 
-/* parameters helpers */
-
 AnnounceParameters *migrate_announce_params(void)
 {
     static AnnounceParameters ap;
 
     MigrationState *s = migrate_get_current();
 
-    ap.initial = s->parameters.announce_initial;
-    ap.max = s->parameters.announce_max;
-    ap.rounds = s->parameters.announce_rounds;
-    ap.step = s->parameters.announce_step;
+    ap.initial = s->config.announce_initial;
+    ap.max = s->config.announce_max;
+    ap.rounds = s->config.announce_rounds;
+    ap.step = s->config.announce_step;
 
     return &ap;
 }
@@ -889,74 +884,74 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp)
     /* TODO use QAPI_CLONE() instead of duplicating it inline */
     params = g_malloc0(sizeof(*params));
     params->has_throttle_trigger_threshold = true;
-    params->throttle_trigger_threshold = s->parameters.throttle_trigger_threshold;
+    params->throttle_trigger_threshold = s->config.throttle_trigger_threshold;
     params->has_cpu_throttle_initial = true;
-    params->cpu_throttle_initial = s->parameters.cpu_throttle_initial;
+    params->cpu_throttle_initial = s->config.cpu_throttle_initial;
     params->has_cpu_throttle_increment = true;
-    params->cpu_throttle_increment = s->parameters.cpu_throttle_increment;
+    params->cpu_throttle_increment = s->config.cpu_throttle_increment;
     params->has_cpu_throttle_tailslow = true;
-    params->cpu_throttle_tailslow = s->parameters.cpu_throttle_tailslow;
-    params->tls_creds = g_strdup(s->parameters.tls_creds ?
-                                 s->parameters.tls_creds : "");
-    params->tls_hostname = g_strdup(s->parameters.tls_hostname ?
-                                    s->parameters.tls_hostname : "");
-    params->tls_authz = g_strdup(s->parameters.tls_authz ?
-                                 s->parameters.tls_authz : "");
+    params->cpu_throttle_tailslow = s->config.cpu_throttle_tailslow;
+    params->tls_creds = g_strdup(s->config.tls_creds ?
+                                 s->config.tls_creds : "");
+    params->tls_hostname = g_strdup(s->config.tls_hostname ?
+                                    s->config.tls_hostname : "");
+    params->tls_authz = g_strdup(s->config.tls_authz ?
+                                 s->config.tls_authz : "");
     params->has_max_bandwidth = true;
-    params->max_bandwidth = s->parameters.max_bandwidth;
+    params->max_bandwidth = s->config.max_bandwidth;
     params->has_avail_switchover_bandwidth = true;
-    params->avail_switchover_bandwidth = s->parameters.avail_switchover_bandwidth;
+    params->avail_switchover_bandwidth = s->config.avail_switchover_bandwidth;
     params->has_downtime_limit = true;
-    params->downtime_limit = s->parameters.downtime_limit;
+    params->downtime_limit = s->config.downtime_limit;
     params->has_x_checkpoint_delay = true;
-    params->x_checkpoint_delay = s->parameters.x_checkpoint_delay;
+    params->x_checkpoint_delay = s->config.x_checkpoint_delay;
     params->has_multifd_channels = true;
-    params->multifd_channels = s->parameters.multifd_channels;
+    params->multifd_channels = s->config.multifd_channels;
     params->has_multifd_compression = true;
-    params->multifd_compression = s->parameters.multifd_compression;
+    params->multifd_compression = s->config.multifd_compression;
     params->has_multifd_zlib_level = true;
-    params->multifd_zlib_level = s->parameters.multifd_zlib_level;
+    params->multifd_zlib_level = s->config.multifd_zlib_level;
     params->has_multifd_qatzip_level = true;
-    params->multifd_qatzip_level = s->parameters.multifd_qatzip_level;
+    params->multifd_qatzip_level = s->config.multifd_qatzip_level;
     params->has_multifd_zstd_level = true;
-    params->multifd_zstd_level = s->parameters.multifd_zstd_level;
+    params->multifd_zstd_level = s->config.multifd_zstd_level;
     params->has_xbzrle_cache_size = true;
-    params->xbzrle_cache_size = s->parameters.xbzrle_cache_size;
+    params->xbzrle_cache_size = s->config.xbzrle_cache_size;
     params->has_max_postcopy_bandwidth = true;
-    params->max_postcopy_bandwidth = s->parameters.max_postcopy_bandwidth;
+    params->max_postcopy_bandwidth = s->config.max_postcopy_bandwidth;
     params->has_max_cpu_throttle = true;
-    params->max_cpu_throttle = s->parameters.max_cpu_throttle;
+    params->max_cpu_throttle = s->config.max_cpu_throttle;
     params->has_announce_initial = true;
-    params->announce_initial = s->parameters.announce_initial;
+    params->announce_initial = s->config.announce_initial;
     params->has_announce_max = true;
-    params->announce_max = s->parameters.announce_max;
+    params->announce_max = s->config.announce_max;
     params->has_announce_rounds = true;
-    params->announce_rounds = s->parameters.announce_rounds;
+    params->announce_rounds = s->config.announce_rounds;
     params->has_announce_step = true;
-    params->announce_step = s->parameters.announce_step;
+    params->announce_step = s->config.announce_step;
 
-    if (s->parameters.has_block_bitmap_mapping) {
+    if (s->config.has_block_bitmap_mapping) {
         params->has_block_bitmap_mapping = true;
         params->block_bitmap_mapping =
             QAPI_CLONE(BitmapMigrationNodeAliasList,
-                       s->parameters.block_bitmap_mapping);
+                       s->config.block_bitmap_mapping);
     }
 
     params->has_x_vcpu_dirty_limit_period = true;
-    params->x_vcpu_dirty_limit_period = s->parameters.x_vcpu_dirty_limit_period;
+    params->x_vcpu_dirty_limit_period = s->config.x_vcpu_dirty_limit_period;
     params->has_vcpu_dirty_limit = true;
-    params->vcpu_dirty_limit = s->parameters.vcpu_dirty_limit;
+    params->vcpu_dirty_limit = s->config.vcpu_dirty_limit;
     params->has_mode = true;
-    params->mode = s->parameters.mode;
+    params->mode = s->config.mode;
     params->has_zero_page_detection = true;
-    params->zero_page_detection = s->parameters.zero_page_detection;
+    params->zero_page_detection = s->config.zero_page_detection;
     params->has_direct_io = true;
-    params->direct_io = s->parameters.direct_io;
+    params->direct_io = s->config.direct_io;
 
     return params;
 }
 
-void migrate_params_init(MigrationParameters *params)
+void migrate_config_init(MigrationConfig *params)
 {
     /* these should match the parameters in migration_properties */
     params->has_throttle_trigger_threshold = true;
@@ -988,10 +983,10 @@ void migrate_params_init(MigrationParameters *params)
 }
 
 /*
- * Check whether the parameters are valid. Error will be put into errp
+ * Check whether the options are valid. Error will be put into errp
  * (if provided). Return true if valid, otherwise false.
  */
-bool migrate_params_check(MigrationParameters *params, Error **errp)
+bool migrate_config_check(MigrationConfig *params, Error **errp)
 {
     ERRP_GUARD();
 
@@ -1162,7 +1157,7 @@ bool migrate_params_check(MigrationParameters *params, Error **errp)
     if (params->has_vcpu_dirty_limit &&
         (params->vcpu_dirty_limit < 1)) {
         error_setg(errp,
-                   "Parameter 'vcpu_dirty_limit' must be greater than 1 MB/s");
+                   "Option 'vcpu_dirty_limit' must be greater than 1 MB/s");
         return false;
     }
 
@@ -1176,12 +1171,12 @@ bool migrate_params_check(MigrationParameters *params, Error **errp)
 
 /*
  * Compatibility layer to convert MigrateSetParameters to
- * MigrationParameters. In the existing QMP user interface, the
+ * MigrationConfig. In the existing QMP user interface, the
  * migrate-set-parameters command takes the TLS options as 'StrOrNull'
  * while the query-migrate-parameters command returns the TLS strings
  * as 'str'.
  */
-static void migrate_params_copy_compat(MigrationParameters *dst,
+static void migrate_params_copy_compat(MigrationConfig *dst,
                                        MigrateSetParameters *src)
 {
     /* copy the common elements between the two */
@@ -1215,141 +1210,126 @@ static void migrate_params_copy_compat(MigrationParameters *dst,
     }
 }
 
-static void migrate_params_apply(MigrateSetParameters *params)
+static void migrate_config_apply(MigrationConfig *new)
 {
     MigrationState *s = migrate_get_current();
 
-    if (params->has_throttle_trigger_threshold) {
-        s->parameters.throttle_trigger_threshold = params->throttle_trigger_threshold;
+    if (new->has_throttle_trigger_threshold) {
+        s->config.throttle_trigger_threshold = new->throttle_trigger_threshold;
     }
 
-    if (params->has_cpu_throttle_initial) {
-        s->parameters.cpu_throttle_initial = params->cpu_throttle_initial;
+    if (new->has_cpu_throttle_initial) {
+        s->config.cpu_throttle_initial = new->cpu_throttle_initial;
     }
 
-    if (params->has_cpu_throttle_increment) {
-        s->parameters.cpu_throttle_increment = params->cpu_throttle_increment;
+    if (new->has_cpu_throttle_increment) {
+        s->config.cpu_throttle_increment = new->cpu_throttle_increment;
     }
 
-    if (params->has_cpu_throttle_tailslow) {
-        s->parameters.cpu_throttle_tailslow = params->cpu_throttle_tailslow;
+    if (new->has_cpu_throttle_tailslow) {
+        s->config.cpu_throttle_tailslow = new->cpu_throttle_tailslow;
     }
 
-    if (params->tls_creds) {
-        g_free(s->parameters.tls_creds);
-
-        if (params->tls_creds->type == QTYPE_QNULL) {
-            s->parameters.tls_creds = NULL;
-        } else {
-            s->parameters.tls_creds = g_strdup(params->tls_creds->u.s);
-        }
+    if (new->tls_creds) {
+        g_free(s->config.tls_creds);
+        s->config.tls_creds = g_strdup(new->tls_creds);
     }
 
-    if (params->tls_hostname) {
-        g_free(s->parameters.tls_hostname);
-
-        if (params->tls_hostname->type == QTYPE_QNULL) {
-            s->parameters.tls_hostname = NULL;
-        } else {
-            s->parameters.tls_hostname = g_strdup(params->tls_hostname->u.s);
-        }
+    if (new->tls_hostname) {
+        g_free(s->config.tls_hostname);
+        s->config.tls_hostname = g_strdup(new->tls_hostname);
     }
 
-    if (params->tls_authz) {
-        g_free(s->parameters.tls_authz);
-
-        if (params->tls_authz->type == QTYPE_QNULL) {
-            s->parameters.tls_authz = NULL;
-        } else {
-            s->parameters.tls_authz = g_strdup(params->tls_authz->u.s);
-        }
+    if (new->tls_authz) {
+        g_free(s->config.tls_authz);
+        s->config.tls_authz = g_strdup(new->tls_authz);
     }
 
-    if (params->has_max_bandwidth) {
-        s->parameters.max_bandwidth = params->max_bandwidth;
+    if (new->has_max_bandwidth) {
+        s->config.max_bandwidth = new->max_bandwidth;
     }
 
-    if (params->has_avail_switchover_bandwidth) {
-        s->parameters.avail_switchover_bandwidth = params->avail_switchover_bandwidth;
+    if (new->has_avail_switchover_bandwidth) {
+        s->config.avail_switchover_bandwidth = new->avail_switchover_bandwidth;
     }
 
-    if (params->has_downtime_limit) {
-        s->parameters.downtime_limit = params->downtime_limit;
+    if (new->has_downtime_limit) {
+        s->config.downtime_limit = new->downtime_limit;
     }
 
-    if (params->has_x_checkpoint_delay) {
-        s->parameters.x_checkpoint_delay = params->x_checkpoint_delay;
+    if (new->has_x_checkpoint_delay) {
+        s->config.x_checkpoint_delay = new->x_checkpoint_delay;
     }
 
-    if (params->has_multifd_channels) {
-        s->parameters.multifd_channels = params->multifd_channels;
+    if (new->has_multifd_channels) {
+        s->config.multifd_channels = new->multifd_channels;
     }
-    if (params->has_multifd_compression) {
-        s->parameters.multifd_compression = params->multifd_compression;
+    if (new->has_multifd_compression) {
+        s->config.multifd_compression = new->multifd_compression;
     }
-    if (params->has_multifd_qatzip_level) {
-        s->parameters.multifd_qatzip_level = params->multifd_qatzip_level;
+    if (new->has_multifd_qatzip_level) {
+        s->config.multifd_qatzip_level = new->multifd_qatzip_level;
     }
-    if (params->has_multifd_zlib_level) {
-        s->parameters.multifd_zlib_level = params->multifd_zlib_level;
+    if (new->has_multifd_zlib_level) {
+        s->config.multifd_zlib_level = new->multifd_zlib_level;
     }
-    if (params->has_multifd_zstd_level) {
-        s->parameters.multifd_zstd_level = params->multifd_zstd_level;
+    if (new->has_multifd_zstd_level) {
+        s->config.multifd_zstd_level = new->multifd_zstd_level;
     }
-    if (params->has_xbzrle_cache_size) {
-        s->parameters.xbzrle_cache_size = params->xbzrle_cache_size;
+    if (new->has_xbzrle_cache_size) {
+        s->config.xbzrle_cache_size = new->xbzrle_cache_size;
     }
-    if (params->has_max_postcopy_bandwidth) {
-        s->parameters.max_postcopy_bandwidth = params->max_postcopy_bandwidth;
+    if (new->has_max_postcopy_bandwidth) {
+        s->config.max_postcopy_bandwidth = new->max_postcopy_bandwidth;
     }
-    if (params->has_max_cpu_throttle) {
-        s->parameters.max_cpu_throttle = params->max_cpu_throttle;
+    if (new->has_max_cpu_throttle) {
+        s->config.max_cpu_throttle = new->max_cpu_throttle;
     }
-    if (params->has_announce_initial) {
-        s->parameters.announce_initial = params->announce_initial;
+    if (new->has_announce_initial) {
+        s->config.announce_initial = new->announce_initial;
     }
-    if (params->has_announce_max) {
-        s->parameters.announce_max = params->announce_max;
+    if (new->has_announce_max) {
+        s->config.announce_max = new->announce_max;
     }
-    if (params->has_announce_rounds) {
-        s->parameters.announce_rounds = params->announce_rounds;
+    if (new->has_announce_rounds) {
+        s->config.announce_rounds = new->announce_rounds;
     }
-    if (params->has_announce_step) {
-        s->parameters.announce_step = params->announce_step;
+    if (new->has_announce_step) {
+        s->config.announce_step = new->announce_step;
     }
 
-    if (params->has_block_bitmap_mapping) {
+    if (new->has_block_bitmap_mapping) {
         qapi_free_BitmapMigrationNodeAliasList(
-            s->parameters.block_bitmap_mapping);
+            s->config.block_bitmap_mapping);
 
-        s->parameters.has_block_bitmap_mapping = true;
-        s->parameters.block_bitmap_mapping =
+        s->config.has_block_bitmap_mapping = true;
+        s->config.block_bitmap_mapping =
             QAPI_CLONE(BitmapMigrationNodeAliasList,
-                       params->block_bitmap_mapping);
+                       new->block_bitmap_mapping);
     }
 
-    if (params->has_x_vcpu_dirty_limit_period) {
-        s->parameters.x_vcpu_dirty_limit_period =
-            params->x_vcpu_dirty_limit_period;
+    if (new->has_x_vcpu_dirty_limit_period) {
+        s->config.x_vcpu_dirty_limit_period =
+            new->x_vcpu_dirty_limit_period;
     }
-    if (params->has_vcpu_dirty_limit) {
-        s->parameters.vcpu_dirty_limit = params->vcpu_dirty_limit;
+    if (new->has_vcpu_dirty_limit) {
+        s->config.vcpu_dirty_limit = new->vcpu_dirty_limit;
     }
 
-    if (params->has_mode) {
-        s->parameters.mode = params->mode;
+    if (new->has_mode) {
+        s->config.mode = new->mode;
     }
 
-    if (params->has_zero_page_detection) {
-        s->parameters.zero_page_detection = params->zero_page_detection;
+    if (new->has_zero_page_detection) {
+        s->config.zero_page_detection = new->zero_page_detection;
     }
 
-    if (params->has_direct_io) {
-        s->parameters.direct_io = params->direct_io;
+    if (new->has_direct_io) {
+        s->config.direct_io = new->direct_io;
     }
 }
 
-static void migrate_post_update_params(MigrateSetParameters *new, Error **errp)
+static void migrate_post_update_config(MigrationConfig *new, Error **errp)
 {
     MigrationState *s = migrate_get_current();
 
@@ -1372,20 +1352,19 @@ static void migrate_post_update_params(MigrateSetParameters *new, Error **errp)
             migration_rate_set(new->max_postcopy_bandwidth);
         }
     }
-
 }
 
 void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
 {
-    MigrationParameters tmp = {};
+    MigrationConfig tmp = {};
 
     migrate_params_copy_compat(&tmp, params);
 
-    if (!migrate_params_check(&tmp, errp)) {
+    if (!migrate_config_check(&tmp, errp)) {
         /* Invalid parameter */
         return;
     }
 
-    migrate_params_apply(params);
-    migrate_post_update_params(params, errp);
+    migrate_config_apply(&tmp);
+    migrate_post_update_config(&tmp, errp);
 }
diff --git a/migration/options.h b/migration/options.h
index 762be4e641..20fdba4237 100644
--- a/migration/options.h
+++ b/migration/options.h
@@ -59,8 +59,6 @@ bool migrate_tls(void);
 
 bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp);
 
-/* parameters */
-
 const BitmapMigrationNodeAliasList *migrate_block_bitmap_mapping(void);
 bool migrate_has_block_bitmap_mapping(void);
 
@@ -86,8 +84,6 @@ const char *migrate_tls_hostname(void);
 uint64_t migrate_xbzrle_cache_size(void);
 ZeroPageDetection migrate_zero_page_detection(void);
 
-/* parameters helpers */
-
-bool migrate_params_check(MigrationParameters *params, Error **errp);
-void migrate_params_init(MigrationParameters *params);
+bool migrate_config_check(MigrationConfig *params, Error **errp);
+void migrate_config_init(MigrationConfig *params);
 #endif
diff --git a/migration/ram.c b/migration/ram.c
index e0ba8e0d48..b938bd621e 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -973,11 +973,11 @@ static void migration_dirty_limit_guest(void)
      * vcpu-dirty-limit untouched.
      */
     if (dirtylimit_in_service() &&
-        quota_dirtyrate == s->parameters.vcpu_dirty_limit) {
+        quota_dirtyrate == s->config.vcpu_dirty_limit) {
         return;
     }
 
-    quota_dirtyrate = s->parameters.vcpu_dirty_limit;
+    quota_dirtyrate = s->config.vcpu_dirty_limit;
 
     /*
      * Set all vCPU a quota dirtyrate, note that the second
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 09/13] migration: Do away with usage of QERR_INVALID_PARAMETER_VALUE
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (7 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 08/13] migration: Replace s->parameters with s->config Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 10/13] migration: Replace s->capabilities with s->config Fabiano Rosas
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Now that we're removing mentions of 'parameters' from migration code,
the "Parameter X expects..." message is a little confusing, although
the "parameter" there refers to the QMP command parameter. There's
also some instances which don't make sense grammatically.

Remove all the usage of QERR_INVALID_PARAMETER_VALUE.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/migration.c  |  3 +--
 migration/options.c    | 56 +++++++++++++++---------------------------
 migration/page_cache.c |  6 ++---
 migration/ram.c        |  3 +--
 4 files changed, 24 insertions(+), 44 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index 9324d4f44e..8ca1383b46 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -2281,8 +2281,7 @@ static void qmp_migrate_finish(MigrationAddress *addr, bool resume_requested,
     } else if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) {
         file_start_outgoing_migration(s, &addr->u.file, &local_err);
     } else {
-        error_setg(&local_err, QERR_INVALID_PARAMETER_VALUE, "uri",
-                   "a valid migration protocol");
+        error_setg(&local_err, "uri is not a valid migration protocol");
         migrate_set_state(&s->state, MIGRATION_STATUS_SETUP,
                           MIGRATION_STATUS_FAILED);
     }
diff --git a/migration/options.c b/migration/options.c
index 7c41fbbce6..0bdd9e23f6 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -993,8 +993,7 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
     if (params->has_throttle_trigger_threshold &&
         (params->throttle_trigger_threshold < 1 ||
          params->throttle_trigger_threshold > 100)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "throttle_trigger_threshold",
+        error_setg(errp, "Option throttle_trigger_threshold expects "
                    "an integer in the range of 1 to 100");
         return false;
     }
@@ -1002,8 +1001,7 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
     if (params->has_cpu_throttle_initial &&
         (params->cpu_throttle_initial < 1 ||
          params->cpu_throttle_initial > 99)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "cpu_throttle_initial",
+        error_setg(errp, "Option cpu_throttle_initial expects "
                    "an integer in the range of 1 to 99");
         return false;
     }
@@ -1011,15 +1009,13 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
     if (params->has_cpu_throttle_increment &&
         (params->cpu_throttle_increment < 1 ||
          params->cpu_throttle_increment > 99)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "cpu_throttle_increment",
+        error_setg(errp, "Option cpu_throttle_increment expects "
                    "an integer in the range of 1 to 99");
         return false;
     }
 
     if (params->has_max_bandwidth && (params->max_bandwidth > SIZE_MAX)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "max_bandwidth",
+        error_setg(errp, "Option max_bandwidth expects "
                    "an integer in the range of 0 to "stringify(SIZE_MAX)
                    " bytes/second");
         return false;
@@ -1027,8 +1023,7 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
 
     if (params->has_avail_switchover_bandwidth &&
         (params->avail_switchover_bandwidth > SIZE_MAX)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "avail_switchover_bandwidth",
+        error_setg(errp, "Option avail_switchover_bandwidth expects "
                    "an integer in the range of 0 to "stringify(SIZE_MAX)
                    " bytes/second");
         return false;
@@ -1036,25 +1031,21 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
 
     if (params->has_downtime_limit &&
         (params->downtime_limit > MAX_MIGRATE_DOWNTIME)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "downtime_limit",
+        error_setg(errp, "Option downtime_limit expects "
                    "an integer in the range of 0 to "
                     stringify(MAX_MIGRATE_DOWNTIME)" ms");
         return false;
     }
 
-    /* x_checkpoint_delay is now always positive */
-
     if (params->has_multifd_channels && (params->multifd_channels < 1)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "multifd_channels",
+        error_setg(errp, "Option multifd_channels expects "
                    "a value between 1 and 255");
         return false;
     }
 
     if (params->has_multifd_zlib_level &&
         (params->multifd_zlib_level > 9)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "multifd_zlib_level",
+        error_setg(errp, "Option multifd_zlib_level expects "
                    "a value between 0 and 9");
         return false;
     }
@@ -1062,14 +1053,14 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
     if (params->has_multifd_qatzip_level &&
         ((params->multifd_qatzip_level > 9) ||
         (params->multifd_qatzip_level < 1))) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "multifd_qatzip_level",
+        error_setg(errp, "Option multifd_qatzip_level expects "
                    "a value between 1 and 9");
         return false;
     }
 
     if (params->has_multifd_zstd_level &&
         (params->multifd_zstd_level > 20)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "multifd_zstd_level",
+        error_setg(errp, "Option multifd_zstd_level expects "
                    "a value between 0 and 20");
         return false;
     }
@@ -1077,8 +1068,7 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
     if (params->has_xbzrle_cache_size &&
         (params->xbzrle_cache_size < qemu_target_page_size() ||
          !is_power_of_2(params->xbzrle_cache_size))) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "xbzrle_cache_size",
+        error_setg(errp, "Option xbzrle_cache_size expects "
                    "a power of two no less than the target page size");
         return false;
     }
@@ -1086,40 +1076,35 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
     if (params->has_max_cpu_throttle &&
         (params->max_cpu_throttle < params->cpu_throttle_initial ||
          params->max_cpu_throttle > 99)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "max_cpu_throttle",
+        error_setg(errp, "max_Option cpu_throttle expects "
                    "an integer in the range of cpu_throttle_initial to 99");
         return false;
     }
 
     if (params->has_announce_initial &&
         params->announce_initial > 100000) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "announce_initial",
+        error_setg(errp, "Option announce_initial expects "
                    "a value between 0 and 100000");
         return false;
     }
     if (params->has_announce_max &&
         params->announce_max > 100000) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "announce_max",
+        error_setg(errp, "Option announce_max expects "
                    "a value between 0 and 100000");
-       return false;
+        return false;
     }
     if (params->has_announce_rounds &&
         params->announce_rounds > 1000) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "announce_rounds",
+        error_setg(errp, "Option announce_rounds expects "
                    "a value between 0 and 1000");
-       return false;
+        return false;
     }
     if (params->has_announce_step &&
         (params->announce_step < 1 ||
         params->announce_step > 10000)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "announce_step",
+        error_setg(errp, "Option announce_step expects "
                    "a value between 0 and 10000");
-       return false;
+        return false;
     }
 
     if (params->has_block_bitmap_mapping &&
@@ -1148,8 +1133,7 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
     if (params->has_x_vcpu_dirty_limit_period &&
         (params->x_vcpu_dirty_limit_period < 1 ||
          params->x_vcpu_dirty_limit_period > 1000)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
-                   "x-vcpu-dirty-limit-period",
+        error_setg(errp, "Option x-vcpu-dirty-limit-period expects "
                    "a value between 1 and 1000");
         return false;
     }
diff --git a/migration/page_cache.c b/migration/page_cache.c
index 6d4f7a9bbc..650b15e48c 100644
--- a/migration/page_cache.c
+++ b/migration/page_cache.c
@@ -45,15 +45,13 @@ PageCache *cache_init(uint64_t new_size, size_t page_size, Error **errp)
     PageCache *cache;
 
     if (new_size < page_size) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cache size",
-                   "is smaller than one target page size");
+        error_setg(errp, "cache size is smaller than target page size");
         return NULL;
     }
 
     /* round down to the nearest power of 2 */
     if (!is_power_of_2(num_pages)) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cache size",
-                   "is not a power of two number of pages");
+        error_setg(errp, "number of pages is not a power of two");
         return NULL;
     }
 
diff --git a/migration/ram.c b/migration/ram.c
index b938bd621e..c7d122c4a0 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -161,8 +161,7 @@ int xbzrle_cache_resize(uint64_t new_size, Error **errp)
 
     /* Check for truncation */
     if (new_size != (size_t)new_size) {
-        error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cache size",
-                   "exceeding address space");
+        error_setg(errp, "xbzrle cache size integer overflow");
         return -1;
     }
 
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 10/13] migration: Replace s->capabilities with s->config
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (8 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 09/13] migration: Do away with usage of QERR_INVALID_PARAMETER_VALUE Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 11/13] migration: Merge parameters and capability checks Fabiano Rosas
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Continuing with the removal of parameters and capabilities, replace
the internal usage of s->capabilities with MigrationConfig.

To keep compatibility with the special format of the user-facing
migrate-set-capabilities and query-migrate-capabilities, introduce a
few compatibility routines to convert between
MigrationCapabilityStatusList and MigrationConfig.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/migration.c |   4 +-
 migration/migration.h |   1 -
 migration/options.c   | 361 +++++++++++++++++++++++++-----------------
 migration/options.h   |  16 +-
 migration/savevm.c    |   8 +-
 5 files changed, 227 insertions(+), 163 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index 8ca1383b46..031e0ade16 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -4064,8 +4064,6 @@ static void migration_instance_init(Object *obj)
  */
 static bool migration_object_check(MigrationState *ms, Error **errp)
 {
-    /* Assuming all off */
-    bool old_caps[MIGRATION_CAPABILITY__MAX] = { 0 };
     g_autoptr(MigrationConfig) globals = NULL;
 
     /*
@@ -4089,7 +4087,7 @@ static bool migration_object_check(MigrationState *ms, Error **errp)
      * 'globals' because the values are already in s->config.
      */
 
-    return migrate_caps_check(old_caps, ms->capabilities, errp);
+    return migrate_caps_check(&ms->config, errp);
 }
 
 static const TypeInfo migration_type = {
diff --git a/migration/migration.h b/migration/migration.h
index 8e6aa595b6..c5a7d356e8 100644
--- a/migration/migration.h
+++ b/migration/migration.h
@@ -356,7 +356,6 @@ struct MigrationState {
     int64_t downtime_start;
     int64_t downtime;
     int64_t expected_downtime;
-    bool capabilities[MIGRATION_CAPABILITY__MAX];
     int64_t setup_time;
 
     /*
diff --git a/migration/options.c b/migration/options.c
index 0bdd9e23f6..562f58263f 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -80,9 +80,6 @@
 #define DEFAULT_MIGRATE_ANNOUNCE_ROUNDS    5
 #define DEFAULT_MIGRATE_ANNOUNCE_STEP    100
 
-#define DEFINE_PROP_MIG_CAP(name, x)             \
-    DEFINE_PROP_BOOL(name, MigrationState, capabilities[x], false)
-
 #define DEFAULT_MIGRATE_VCPU_DIRTY_LIMIT_PERIOD     1000    /* milliseconds */
 #define DEFAULT_MIGRATE_VCPU_DIRTY_LIMIT            1       /* MB/s */
 
@@ -176,30 +173,40 @@ const Property migration_properties[] = {
     DEFINE_PROP_ZERO_PAGE_DETECTION("zero-page-detection", MigrationState,
                        config.zero_page_detection,
                        ZERO_PAGE_DETECTION_MULTIFD),
-
-    /* Migration capabilities */
-    DEFINE_PROP_MIG_CAP("x-xbzrle", MIGRATION_CAPABILITY_XBZRLE),
-    DEFINE_PROP_MIG_CAP("x-rdma-pin-all", MIGRATION_CAPABILITY_RDMA_PIN_ALL),
-    DEFINE_PROP_MIG_CAP("x-auto-converge", MIGRATION_CAPABILITY_AUTO_CONVERGE),
-    DEFINE_PROP_MIG_CAP("x-zero-blocks", MIGRATION_CAPABILITY_ZERO_BLOCKS),
-    DEFINE_PROP_MIG_CAP("x-events", MIGRATION_CAPABILITY_EVENTS),
-    DEFINE_PROP_MIG_CAP("x-postcopy-ram", MIGRATION_CAPABILITY_POSTCOPY_RAM),
-    DEFINE_PROP_MIG_CAP("x-postcopy-preempt",
-                        MIGRATION_CAPABILITY_POSTCOPY_PREEMPT),
-    DEFINE_PROP_MIG_CAP("x-colo", MIGRATION_CAPABILITY_X_COLO),
-    DEFINE_PROP_MIG_CAP("x-release-ram", MIGRATION_CAPABILITY_RELEASE_RAM),
-    DEFINE_PROP_MIG_CAP("x-return-path", MIGRATION_CAPABILITY_RETURN_PATH),
-    DEFINE_PROP_MIG_CAP("x-multifd", MIGRATION_CAPABILITY_MULTIFD),
-    DEFINE_PROP_MIG_CAP("x-background-snapshot",
-            MIGRATION_CAPABILITY_BACKGROUND_SNAPSHOT),
+    DEFINE_PROP_BOOL("x-xbzrle",
+                     MigrationState, config.xbzrle, false),
+    DEFINE_PROP_BOOL("x-rdma-pin-all",
+                     MigrationState, config.rdma_pin_all, false),
+    DEFINE_PROP_BOOL("x-auto-converge",
+                     MigrationState, config.auto_converge, false),
+    DEFINE_PROP_BOOL("x-zero-blocks",
+                     MigrationState, config.zero_blocks, false),
+    DEFINE_PROP_BOOL("x-events",
+                     MigrationState, config.events, false),
+    DEFINE_PROP_BOOL("x-postcopy-ram",
+                     MigrationState, config.postcopy_ram, false),
+    DEFINE_PROP_BOOL("x-postcopy-preempt",
+                     MigrationState, config.postcopy_preempt, false),
+    DEFINE_PROP_BOOL("x-colo",
+                     MigrationState, config.x_colo, false),
+    DEFINE_PROP_BOOL("x-release-ram",
+                     MigrationState, config.release_ram, false),
+    DEFINE_PROP_BOOL("x-return-path",
+                     MigrationState, config.return_path, false),
+    DEFINE_PROP_BOOL("x-multifd",
+                     MigrationState, config.multifd, false),
+    DEFINE_PROP_BOOL("x-background-snapshot",
+                     MigrationState, config.background_snapshot, false),
 #ifdef CONFIG_LINUX
-    DEFINE_PROP_MIG_CAP("x-zero-copy-send",
-            MIGRATION_CAPABILITY_ZERO_COPY_SEND),
+    DEFINE_PROP_BOOL("x-zero-copy-send",
+                     MigrationState, config.zero_copy_send, false),
 #endif
-    DEFINE_PROP_MIG_CAP("x-switchover-ack",
-                        MIGRATION_CAPABILITY_SWITCHOVER_ACK),
-    DEFINE_PROP_MIG_CAP("x-dirty-limit", MIGRATION_CAPABILITY_DIRTY_LIMIT),
-    DEFINE_PROP_MIG_CAP("mapped-ram", MIGRATION_CAPABILITY_MAPPED_RAM),
+    DEFINE_PROP_BOOL("x-switchover-ack",
+                     MigrationState, config.switchover_ack, false),
+    DEFINE_PROP_BOOL("x-dirty-limit",
+                     MigrationState, config.dirty_limit, false),
+    DEFINE_PROP_BOOL("mapped-ram",
+                     MigrationState, config.mapped_ram, false),
 };
 const size_t migration_properties_count = ARRAY_SIZE(migration_properties);
 
@@ -207,7 +214,7 @@ bool migrate_auto_converge(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_AUTO_CONVERGE];
+    return s->config.auto_converge;
 }
 
 bool migrate_send_switchover_start(void)
@@ -221,144 +228,142 @@ bool migrate_background_snapshot(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_BACKGROUND_SNAPSHOT];
+    return s->config.background_snapshot;
 }
 
 bool migrate_colo(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_X_COLO];
+    return s->config.x_colo;
 }
 
 bool migrate_dirty_bitmaps(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_DIRTY_BITMAPS];
+    return s->config.dirty_bitmaps;
 }
 
 bool migrate_dirty_limit(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_DIRTY_LIMIT];
+    return s->config.dirty_limit;
 }
 
 bool migrate_events(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_EVENTS];
+    return s->config.events;
 }
 
 bool migrate_mapped_ram(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_MAPPED_RAM];
+    return s->config.mapped_ram;
 }
 
 bool migrate_ignore_shared(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_X_IGNORE_SHARED];
+    return s->config.x_ignore_shared;
 }
 
 bool migrate_late_block_activate(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_LATE_BLOCK_ACTIVATE];
+    return s->config.late_block_activate;
 }
 
 bool migrate_multifd(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_MULTIFD];
+    return s->config.multifd;
 }
 
 bool migrate_pause_before_switchover(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_PAUSE_BEFORE_SWITCHOVER];
+    return s->config.pause_before_switchover;
 }
 
 bool migrate_postcopy_blocktime(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_POSTCOPY_BLOCKTIME];
+    return s->config.postcopy_blocktime;
 }
 
 bool migrate_postcopy_preempt(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_POSTCOPY_PREEMPT];
+    return s->config.postcopy_preempt;
 }
 
 bool migrate_postcopy_ram(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_POSTCOPY_RAM];
+    return s->config.postcopy_ram;
 }
 
 bool migrate_rdma_pin_all(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_RDMA_PIN_ALL];
+    return s->config.rdma_pin_all;
 }
 
 bool migrate_release_ram(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_RELEASE_RAM];
+    return s->config.release_ram;
 }
 
 bool migrate_return_path(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_RETURN_PATH];
+    return s->config.return_path;
 }
 
 bool migrate_switchover_ack(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_SWITCHOVER_ACK];
+    return s->config.switchover_ack;
 }
 
 bool migrate_validate_uuid(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_VALIDATE_UUID];
+    return s->config.validate_uuid;
 }
 
 bool migrate_xbzrle(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_XBZRLE];
+    return s->config.xbzrle;
 }
 
 bool migrate_zero_copy_send(void)
 {
     MigrationState *s = migrate_get_current();
 
-    return s->capabilities[MIGRATION_CAPABILITY_ZERO_COPY_SEND];
+    return s->config.zero_copy_send;
 }
 
-/* pseudo capabilities */
-
 bool migrate_multifd_flush_after_each_section(void)
 {
     MigrationState *s = migrate_get_current();
@@ -403,63 +408,25 @@ WriteTrackingSupport migrate_query_write_tracking(void)
     return WT_SUPPORT_COMPATIBLE;
 }
 
-/* Migration capabilities set */
-struct MigrateCapsSet {
-    int size;                       /* Capability set size */
-    MigrationCapability caps[];     /* Variadic array of capabilities */
-};
-typedef struct MigrateCapsSet MigrateCapsSet;
-
-/* Define and initialize MigrateCapsSet */
-#define INITIALIZE_MIGRATE_CAPS_SET(_name, ...)   \
-    MigrateCapsSet _name = {    \
-        .size = sizeof((int []) { __VA_ARGS__ }) / sizeof(int), \
-        .caps = { __VA_ARGS__ } \
-    }
-
-/* Background-snapshot compatibility check list */
-static const
-INITIALIZE_MIGRATE_CAPS_SET(check_caps_background_snapshot,
-    MIGRATION_CAPABILITY_POSTCOPY_RAM,
-    MIGRATION_CAPABILITY_DIRTY_BITMAPS,
-    MIGRATION_CAPABILITY_POSTCOPY_BLOCKTIME,
-    MIGRATION_CAPABILITY_LATE_BLOCK_ACTIVATE,
-    MIGRATION_CAPABILITY_RETURN_PATH,
-    MIGRATION_CAPABILITY_MULTIFD,
-    MIGRATION_CAPABILITY_PAUSE_BEFORE_SWITCHOVER,
-    MIGRATION_CAPABILITY_AUTO_CONVERGE,
-    MIGRATION_CAPABILITY_RELEASE_RAM,
-    MIGRATION_CAPABILITY_RDMA_PIN_ALL,
-    MIGRATION_CAPABILITY_XBZRLE,
-    MIGRATION_CAPABILITY_X_COLO,
-    MIGRATION_CAPABILITY_VALIDATE_UUID,
-    MIGRATION_CAPABILITY_ZERO_COPY_SEND);
-
 static bool migrate_incoming_started(void)
 {
     return !!migration_incoming_get_current()->transport_data;
 }
 
-/**
- * @migration_caps_check - check capability compatibility
- *
- * @old_caps: old capability list
- * @new_caps: new capability list
- * @errp: set *errp if the check failed, with reason
- *
- * Returns true if check passed, otherwise false.
- */
-bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
+bool migrate_caps_check(MigrationConfig *new, Error **errp)
 {
-    ERRP_GUARD();
+    MigrationState *s = migrate_get_current();
     MigrationIncomingState *mis = migration_incoming_get_current();
+    bool postcopy_already_on = s->config.postcopy_ram;
+    ERRP_GUARD();
 
-    if (new_caps[MIGRATION_CAPABILITY_ZERO_BLOCKS]) {
+
+    if (new->zero_blocks) {
         warn_report("zero-blocks capability is deprecated");
     }
 
 #ifndef CONFIG_REPLICATION
-    if (new_caps[MIGRATION_CAPABILITY_X_COLO]) {
+    if (new->x_colo) {
         error_setg(errp, "QEMU compiled without replication module"
                    " can't enable COLO");
         error_append_hint(errp, "Please enable replication before COLO.\n");
@@ -467,32 +434,32 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
     }
 #endif
 
-    if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
+    if (new->postcopy_ram) {
         /* This check is reasonably expensive, so only when it's being
          * set the first time, also it's only the destination that needs
          * special support.
          */
-        if (!old_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM] &&
+        if (!postcopy_already_on &&
             runstate_check(RUN_STATE_INMIGRATE) &&
             !postcopy_ram_supported_by_host(mis, errp)) {
             error_prepend(errp, "Postcopy is not supported: ");
             return false;
         }
 
-        if (new_caps[MIGRATION_CAPABILITY_X_IGNORE_SHARED]) {
+        if (new->x_ignore_shared) {
             error_setg(errp, "Postcopy is not compatible with ignore-shared");
             return false;
         }
 
-        if (new_caps[MIGRATION_CAPABILITY_MULTIFD]) {
+        if (new->multifd) {
             error_setg(errp, "Postcopy is not yet compatible with multifd");
             return false;
         }
     }
 
-    if (new_caps[MIGRATION_CAPABILITY_BACKGROUND_SNAPSHOT]) {
+    if (new->background_snapshot) {
         WriteTrackingSupport wt_support;
-        int idx;
+
         /*
          * Check if 'background-snapshot' capability is supported by
          * host kernel and compatible with guest memory configuration.
@@ -508,41 +475,45 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
             return false;
         }
 
-        /*
-         * Check if there are any migration capabilities
-         * incompatible with 'background-snapshot'.
-         */
-        for (idx = 0; idx < check_caps_background_snapshot.size; idx++) {
-            int incomp_cap = check_caps_background_snapshot.caps[idx];
-            if (new_caps[incomp_cap]) {
-                error_setg(errp,
-                        "Background-snapshot is not compatible with %s",
-                        MigrationCapability_str(incomp_cap));
-                return false;
-            }
+        if (new->postcopy_ram ||
+            new->dirty_bitmaps ||
+            new->postcopy_blocktime ||
+            new->late_block_activate ||
+            new->return_path ||
+            new->multifd ||
+            new->pause_before_switchover ||
+            new->auto_converge ||
+            new->release_ram ||
+            new->rdma_pin_all ||
+            new->xbzrle ||
+            new->x_colo ||
+            new->validate_uuid ||
+            new->zero_copy_send) {
+            error_setg(errp,
+                       "Background-snapshot is not compatible with "
+                       "currently set capabilities");
+            return false;
         }
     }
 
 #ifdef CONFIG_LINUX
-    if (new_caps[MIGRATION_CAPABILITY_ZERO_COPY_SEND] &&
-        (!new_caps[MIGRATION_CAPABILITY_MULTIFD] ||
-         new_caps[MIGRATION_CAPABILITY_XBZRLE] ||
-         migrate_multifd_compression() ||
-         migrate_tls())) {
+    if (new->zero_copy_send &&
+        (!new->multifd || new->xbzrle ||
+         migrate_multifd_compression() || migrate_tls())) {
         error_setg(errp,
                    "Zero copy only available for non-compressed non-TLS multifd migration");
         return false;
     }
 #else
-    if (new_caps[MIGRATION_CAPABILITY_ZERO_COPY_SEND]) {
+    if (new->zero_copy_send) {
         error_setg(errp,
                    "Zero copy currently only available on Linux");
         return false;
     }
 #endif
 
-    if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_PREEMPT]) {
-        if (!new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
+    if (new->postcopy_preempt) {
+        if (!new->postcopy_ram) {
             error_setg(errp, "Postcopy preempt requires postcopy-ram");
             return false;
         }
@@ -554,22 +525,22 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
         }
     }
 
-    if (new_caps[MIGRATION_CAPABILITY_MULTIFD]) {
+    if (new->multifd) {
         if (migrate_incoming_started()) {
             error_setg(errp, "Multifd must be set before incoming starts");
             return false;
         }
     }
 
-    if (new_caps[MIGRATION_CAPABILITY_SWITCHOVER_ACK]) {
-        if (!new_caps[MIGRATION_CAPABILITY_RETURN_PATH]) {
+    if (new->switchover_ack) {
+        if (!new->return_path) {
             error_setg(errp, "Capability 'switchover-ack' requires capability "
                              "'return-path'");
             return false;
         }
     }
-    if (new_caps[MIGRATION_CAPABILITY_DIRTY_LIMIT]) {
-        if (new_caps[MIGRATION_CAPABILITY_AUTO_CONVERGE]) {
+    if (new->dirty_limit) {
+        if (new->auto_converge) {
             error_setg(errp, "dirty-limit conflicts with auto-converge"
                        " either of then available currently");
             return false;
@@ -582,21 +553,21 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
         }
     }
 
-    if (new_caps[MIGRATION_CAPABILITY_MULTIFD]) {
-        if (new_caps[MIGRATION_CAPABILITY_XBZRLE]) {
+    if (new->multifd) {
+        if (new->xbzrle) {
             error_setg(errp, "Multifd is not compatible with xbzrle");
             return false;
         }
     }
 
-    if (new_caps[MIGRATION_CAPABILITY_MAPPED_RAM]) {
-        if (new_caps[MIGRATION_CAPABILITY_XBZRLE]) {
+    if (new->mapped_ram) {
+        if (new->xbzrle) {
             error_setg(errp,
                        "Mapped-ram migration is incompatible with xbzrle");
             return false;
         }
 
-        if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
+        if (new->postcopy_ram) {
             error_setg(errp,
                        "Mapped-ram migration is incompatible with postcopy");
             return false;
@@ -616,37 +587,143 @@ MigrationCapabilityStatusList *qmp_query_migrate_capabilities(Error **errp)
     for (i = 0; i < MIGRATION_CAPABILITY__MAX; i++) {
         caps = g_malloc0(sizeof(*caps));
         caps->capability = i;
-        caps->state = s->capabilities[i];
+        caps->state = migrate_config_get_cap_compat(&s->config, i);
         QAPI_LIST_APPEND(tail, caps);
     }
 
     return head;
 }
 
+static bool *migrate_config_get_cap_addr(MigrationConfig *config, int i)
+{
+    bool *cap_addr = NULL;
+
+    switch (i) {
+    case MIGRATION_CAPABILITY_XBZRLE:
+        cap_addr = &config->xbzrle;
+        break;
+    case MIGRATION_CAPABILITY_RDMA_PIN_ALL:
+        cap_addr = &config->rdma_pin_all;
+        break;
+    case MIGRATION_CAPABILITY_AUTO_CONVERGE:
+        cap_addr = &config->auto_converge;
+        break;
+    case MIGRATION_CAPABILITY_ZERO_BLOCKS:
+        cap_addr = &config->zero_blocks;
+        break;
+    case MIGRATION_CAPABILITY_EVENTS:
+        cap_addr = &config->events;
+        break;
+    case MIGRATION_CAPABILITY_POSTCOPY_RAM:
+        cap_addr = &config->postcopy_ram;
+        break;
+    case MIGRATION_CAPABILITY_X_COLO:
+        cap_addr = &config->x_colo;
+        break;
+    case MIGRATION_CAPABILITY_RELEASE_RAM:
+        cap_addr = &config->release_ram;
+        break;
+    case MIGRATION_CAPABILITY_RETURN_PATH:
+        cap_addr = &config->return_path;
+        break;
+    case MIGRATION_CAPABILITY_PAUSE_BEFORE_SWITCHOVER:
+        cap_addr = &config->pause_before_switchover;
+        break;
+    case MIGRATION_CAPABILITY_MULTIFD:
+        cap_addr = &config->multifd;
+        break;
+    case MIGRATION_CAPABILITY_DIRTY_BITMAPS:
+        cap_addr = &config->dirty_bitmaps;
+        break;
+    case MIGRATION_CAPABILITY_POSTCOPY_BLOCKTIME:
+        cap_addr = &config->postcopy_blocktime;
+        break;
+    case MIGRATION_CAPABILITY_LATE_BLOCK_ACTIVATE:
+        cap_addr = &config->late_block_activate;
+        break;
+    case MIGRATION_CAPABILITY_X_IGNORE_SHARED:
+        cap_addr = &config->x_ignore_shared;
+        break;
+    case MIGRATION_CAPABILITY_VALIDATE_UUID:
+        cap_addr = &config->validate_uuid;
+        break;
+    case MIGRATION_CAPABILITY_BACKGROUND_SNAPSHOT:
+        cap_addr = &config->background_snapshot;
+        break;
+    case MIGRATION_CAPABILITY_ZERO_COPY_SEND:
+        cap_addr = &config->zero_copy_send;
+        break;
+    case MIGRATION_CAPABILITY_POSTCOPY_PREEMPT:
+        cap_addr = &config->postcopy_preempt;
+        break;
+    case MIGRATION_CAPABILITY_SWITCHOVER_ACK:
+        cap_addr = &config->switchover_ack;
+        break;
+    case MIGRATION_CAPABILITY_DIRTY_LIMIT:
+        cap_addr = &config->dirty_limit;
+        break;
+    case MIGRATION_CAPABILITY_MAPPED_RAM:
+        cap_addr = &config->mapped_ram;
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
+    return cap_addr;
+}
+
+/* Compatibility for code that reads capabilities in a loop */
+bool migrate_config_get_cap_compat(MigrationConfig *config, int i)
+{
+    return *(migrate_config_get_cap_addr(config, i));
+}
+
+/* Compatibility for code that writes capabilities in a loop */
+static void migrate_config_set_cap_compat(MigrationConfig *config, int i,
+                                          bool val)
+{
+    *(migrate_config_get_cap_addr(config, i)) = val;
+}
+
+/*
+ * Set capabilities for compatibility with the old
+ * migrate-set-capabilities command.
+ */
+static void migrate_config_set_caps_compat(MigrationConfig *config,
+                                           MigrationCapabilityStatusList *caps)
+{
+    MigrationCapabilityStatusList *cap;
+
+    for (cap = caps; cap; cap = cap->next) {
+        migrate_config_set_cap_compat(config, cap->value->capability,
+                                      cap->value->state);
+    }
+}
+
 void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params,
                                   Error **errp)
 {
     MigrationState *s = migrate_get_current();
-    MigrationCapabilityStatusList *cap;
-    bool new_caps[MIGRATION_CAPABILITY__MAX];
+    g_autoptr(MigrationConfig) new = NULL;
 
     if (migration_is_running() || migration_in_colo_state()) {
         error_setg(errp, "There's a migration process in progress");
         return;
     }
 
-    memcpy(new_caps, s->capabilities, sizeof(new_caps));
-    for (cap = params; cap; cap = cap->next) {
-        new_caps[cap->value->capability] = cap->value->state;
-    }
+    /*
+     * Capabilities validation needs to first copy from s->config in
+     * case the incoming config has a capability that conflicts with
+     * another that's already set.
+     */
+    new = QAPI_CLONE(MigrationConfig, &s->config);
+    migrate_config_set_caps_compat(new, params);
 
-    if (!migrate_caps_check(s->capabilities, new_caps, errp)) {
+    if (!migrate_caps_check(new, errp)) {
         return;
     }
 
-    for (cap = params; cap; cap = cap->next) {
-        s->capabilities[cap->value->capability] = cap->value->state;
-    }
+    migrate_config_set_caps_compat(&s->config, params);
 }
 
 const BitmapMigrationNodeAliasList *migrate_block_bitmap_mapping(void)
@@ -705,9 +782,7 @@ bool migrate_direct_io(void)
      * isolated to the main migration thread while multifd channels
      * process the aligned data with O_DIRECT enabled.
      */
-    return s->config.direct_io &&
-        s->capabilities[MIGRATION_CAPABILITY_MAPPED_RAM] &&
-        s->capabilities[MIGRATION_CAPABILITY_MULTIFD];
+    return s->config.direct_io && s->config.mapped_ram && s->config.multifd;
 }
 
 uint64_t migrate_downtime_limit(void)
diff --git a/migration/options.h b/migration/options.h
index 20fdba4237..6455bce985 100644
--- a/migration/options.h
+++ b/migration/options.h
@@ -1,5 +1,5 @@
 /*
- * QEMU migration capabilities
+ * QEMU migration options
  *
  * Copyright (c) 2012-2023 Red Hat Inc
  *
@@ -23,8 +23,6 @@
 extern const Property migration_properties[];
 extern const size_t migration_properties_count;
 
-/* capabilities */
-
 bool migrate_auto_converge(void);
 bool migrate_colo(void);
 bool migrate_dirty_bitmaps(void);
@@ -43,21 +41,11 @@ bool migrate_validate_uuid(void);
 bool migrate_xbzrle(void);
 bool migrate_zero_copy_send(void);
 
-/*
- * pseudo capabilities
- *
- * These are functions that are used in a similar way to capabilities
- * check, but they are not a capability.
- */
-
 bool migrate_multifd_flush_after_each_section(void);
 bool migrate_postcopy(void);
 bool migrate_rdma(void);
 bool migrate_tls(void);
 
-/* capabilities helpers */
-
-bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp);
 
 const BitmapMigrationNodeAliasList *migrate_block_bitmap_mapping(void);
 bool migrate_has_block_bitmap_mapping(void);
@@ -86,4 +74,6 @@ ZeroPageDetection migrate_zero_page_detection(void);
 
 bool migrate_config_check(MigrationConfig *params, Error **errp);
 void migrate_config_init(MigrationConfig *params);
+bool migrate_config_get_cap_compat(MigrationConfig *config, int i);
+bool migrate_caps_check(MigrationConfig *new, Error **errp);
 #endif
diff --git a/migration/savevm.c b/migration/savevm.c
index ce158c3512..c81cbc3ee7 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -290,7 +290,8 @@ static uint32_t get_validatable_capabilities_count(void)
     uint32_t result = 0;
     int i;
     for (i = 0; i < MIGRATION_CAPABILITY__MAX; i++) {
-        if (should_validate_capability(i) && s->capabilities[i]) {
+        if (should_validate_capability(i) &&
+            migrate_config_get_cap_compat(&s->config, i)) {
             result++;
         }
     }
@@ -312,7 +313,8 @@ static int configuration_pre_save(void *opaque)
     state->capabilities = g_renew(MigrationCapability, state->capabilities,
                                   state->caps_count);
     for (i = j = 0; i < MIGRATION_CAPABILITY__MAX; i++) {
-        if (should_validate_capability(i) && s->capabilities[i]) {
+        if (should_validate_capability(i) &&
+            migrate_config_get_cap_compat(&s->config, i)) {
             state->capabilities[j++] = i;
         }
     }
@@ -362,7 +364,7 @@ static bool configuration_validate_capabilities(SaveState *state)
             continue;
         }
         source_state = test_bit(i, source_caps_bm);
-        target_state = s->capabilities[i];
+        target_state = migrate_config_get_cap_compat(&s->config, i);
         if (source_state != target_state) {
             error_report("Capability %s is %s, but received capability is %s",
                          MigrationCapability_str(i),
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 11/13] migration: Merge parameters and capability checks
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (9 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 10/13] migration: Replace s->capabilities with s->config Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-04-11 19:14 ` [RFC PATCH 12/13] [PoC] migration: Add query/set commands for MigrationConfig Fabiano Rosas
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Since parameters and capabilities are now stored in the same structure
and both use the same method to validate the input (i.e. copy, set a
temporary, validate the temporary and overwrite), it's possible to use
the same routine for both.

With this all distinction from capabilities and parameters is removed,
except for the user-facing pieces that have to maintain compatibility.

Note that capabilities need to be validated against conflicting
capabilities that are already set and also against capabilities that
the user might have set on the same command. Therefore they cannot
check the has_* fields, otherwise the amount of combinations would be
too large.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/migration.c |   6 +-
 migration/options.c   | 528 ++++++++++++++++++++++--------------------
 migration/options.h   |   1 -
 3 files changed, 272 insertions(+), 263 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index 031e0ade16..55d839abd0 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -4078,16 +4078,12 @@ static bool migration_object_check(MigrationState *ms, Error **errp)
      */
     migrate_config_init(globals);
 
-    if (!migrate_config_check(globals, errp)) {
-        return false;
-    }
-
     /*
      * After the validation succeeds, there's no need to apply the
      * 'globals' because the values are already in s->config.
      */
 
-    return migrate_caps_check(&ms->config, errp);
+    return migrate_config_check(globals, errp);
 }
 
 static const TypeInfo migration_type = {
diff --git a/migration/options.c b/migration/options.c
index 562f58263f..4e3792dec3 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -413,170 +413,6 @@ static bool migrate_incoming_started(void)
     return !!migration_incoming_get_current()->transport_data;
 }
 
-bool migrate_caps_check(MigrationConfig *new, Error **errp)
-{
-    MigrationState *s = migrate_get_current();
-    MigrationIncomingState *mis = migration_incoming_get_current();
-    bool postcopy_already_on = s->config.postcopy_ram;
-    ERRP_GUARD();
-
-
-    if (new->zero_blocks) {
-        warn_report("zero-blocks capability is deprecated");
-    }
-
-#ifndef CONFIG_REPLICATION
-    if (new->x_colo) {
-        error_setg(errp, "QEMU compiled without replication module"
-                   " can't enable COLO");
-        error_append_hint(errp, "Please enable replication before COLO.\n");
-        return false;
-    }
-#endif
-
-    if (new->postcopy_ram) {
-        /* This check is reasonably expensive, so only when it's being
-         * set the first time, also it's only the destination that needs
-         * special support.
-         */
-        if (!postcopy_already_on &&
-            runstate_check(RUN_STATE_INMIGRATE) &&
-            !postcopy_ram_supported_by_host(mis, errp)) {
-            error_prepend(errp, "Postcopy is not supported: ");
-            return false;
-        }
-
-        if (new->x_ignore_shared) {
-            error_setg(errp, "Postcopy is not compatible with ignore-shared");
-            return false;
-        }
-
-        if (new->multifd) {
-            error_setg(errp, "Postcopy is not yet compatible with multifd");
-            return false;
-        }
-    }
-
-    if (new->background_snapshot) {
-        WriteTrackingSupport wt_support;
-
-        /*
-         * Check if 'background-snapshot' capability is supported by
-         * host kernel and compatible with guest memory configuration.
-         */
-        wt_support = migrate_query_write_tracking();
-        if (wt_support < WT_SUPPORT_AVAILABLE) {
-            error_setg(errp, "Background-snapshot is not supported by host kernel");
-            return false;
-        }
-        if (wt_support < WT_SUPPORT_COMPATIBLE) {
-            error_setg(errp, "Background-snapshot is not compatible "
-                    "with guest memory configuration");
-            return false;
-        }
-
-        if (new->postcopy_ram ||
-            new->dirty_bitmaps ||
-            new->postcopy_blocktime ||
-            new->late_block_activate ||
-            new->return_path ||
-            new->multifd ||
-            new->pause_before_switchover ||
-            new->auto_converge ||
-            new->release_ram ||
-            new->rdma_pin_all ||
-            new->xbzrle ||
-            new->x_colo ||
-            new->validate_uuid ||
-            new->zero_copy_send) {
-            error_setg(errp,
-                       "Background-snapshot is not compatible with "
-                       "currently set capabilities");
-            return false;
-        }
-    }
-
-#ifdef CONFIG_LINUX
-    if (new->zero_copy_send &&
-        (!new->multifd || new->xbzrle ||
-         migrate_multifd_compression() || migrate_tls())) {
-        error_setg(errp,
-                   "Zero copy only available for non-compressed non-TLS multifd migration");
-        return false;
-    }
-#else
-    if (new->zero_copy_send) {
-        error_setg(errp,
-                   "Zero copy currently only available on Linux");
-        return false;
-    }
-#endif
-
-    if (new->postcopy_preempt) {
-        if (!new->postcopy_ram) {
-            error_setg(errp, "Postcopy preempt requires postcopy-ram");
-            return false;
-        }
-
-        if (migrate_incoming_started()) {
-            error_setg(errp,
-                       "Postcopy preempt must be set before incoming starts");
-            return false;
-        }
-    }
-
-    if (new->multifd) {
-        if (migrate_incoming_started()) {
-            error_setg(errp, "Multifd must be set before incoming starts");
-            return false;
-        }
-    }
-
-    if (new->switchover_ack) {
-        if (!new->return_path) {
-            error_setg(errp, "Capability 'switchover-ack' requires capability "
-                             "'return-path'");
-            return false;
-        }
-    }
-    if (new->dirty_limit) {
-        if (new->auto_converge) {
-            error_setg(errp, "dirty-limit conflicts with auto-converge"
-                       " either of then available currently");
-            return false;
-        }
-
-        if (!kvm_enabled() || !kvm_dirty_ring_enabled()) {
-            error_setg(errp, "dirty-limit requires KVM with accelerator"
-                   " property 'dirty-ring-size' set");
-            return false;
-        }
-    }
-
-    if (new->multifd) {
-        if (new->xbzrle) {
-            error_setg(errp, "Multifd is not compatible with xbzrle");
-            return false;
-        }
-    }
-
-    if (new->mapped_ram) {
-        if (new->xbzrle) {
-            error_setg(errp,
-                       "Mapped-ram migration is incompatible with xbzrle");
-            return false;
-        }
-
-        if (new->postcopy_ram) {
-            error_setg(errp,
-                       "Mapped-ram migration is incompatible with postcopy");
-            return false;
-        }
-    }
-
-    return true;
-}
-
 MigrationCapabilityStatusList *qmp_query_migrate_capabilities(Error **errp)
 {
     MigrationCapabilityStatusList *head = NULL, **tail = &head;
@@ -594,6 +430,10 @@ MigrationCapabilityStatusList *qmp_query_migrate_capabilities(Error **errp)
     return head;
 }
 
+/*
+ * Compatibility for code that accesses capability values by index
+ * (deprecated) instead of accessing the fields by name.
+ */
 static bool *migrate_config_get_cap_addr(MigrationConfig *config, int i)
 {
     bool *cap_addr = NULL;
@@ -672,31 +512,37 @@ static bool *migrate_config_get_cap_addr(MigrationConfig *config, int i)
     return cap_addr;
 }
 
-/* Compatibility for code that reads capabilities in a loop */
 bool migrate_config_get_cap_compat(MigrationConfig *config, int i)
 {
     return *(migrate_config_get_cap_addr(config, i));
 }
 
-/* Compatibility for code that writes capabilities in a loop */
-static void migrate_config_set_cap_compat(MigrationConfig *config, int i,
-                                          bool val)
-{
-    *(migrate_config_get_cap_addr(config, i)) = val;
-}
-
 /*
- * Set capabilities for compatibility with the old
- * migrate-set-capabilities command.
+ * For compatibility with the old migrate-set-capabilities
+ * command. Convert the user-facing MigrationCapabilityStatusList into
+ * the internal representation MigrationConfig.
  */
 static void migrate_config_set_caps_compat(MigrationConfig *config,
-                                           MigrationCapabilityStatusList *caps)
+                                           MigrationCapabilityStatusList *caps,
+                                           bool tmp_config)
 {
     MigrationCapabilityStatusList *cap;
 
+    /*
+     * The value that'll be written to the has_* fields. It should be
+     * 'true' for a temporary config so migrate_config_check() can see
+     * that the value is new. For the permanent config, it must be
+     * 'false' because the has_* fields of s->config are never used.
+     */
+    bool has_field_value = !tmp_config;
+
     for (cap = caps; cap; cap = cap->next) {
-        migrate_config_set_cap_compat(config, cap->value->capability,
-                                      cap->value->state);
+        int i = cap->value->capability;
+        bool val = cap->value->state;
+        bool *cap_addr = migrate_config_get_cap_addr(config, i);
+
+        *cap_addr = val;
+        *(cap_addr - sizeof(bool)) = has_field_value;
     }
 }
 
@@ -717,13 +563,12 @@ void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params,
      * another that's already set.
      */
     new = QAPI_CLONE(MigrationConfig, &s->config);
-    migrate_config_set_caps_compat(new, params);
+    migrate_config_set_caps_compat(new, params, true);
 
-    if (!migrate_caps_check(new, errp)) {
+    if (!migrate_config_check(new, errp)) {
         return;
     }
-
-    migrate_config_set_caps_compat(&s->config, params);
+    migrate_config_set_caps_compat(&s->config, params, false);
 }
 
 const BitmapMigrationNodeAliasList *migrate_block_bitmap_mapping(void)
@@ -1026,172 +871,192 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp)
     return params;
 }
 
-void migrate_config_init(MigrationConfig *params)
+void migrate_config_init(MigrationConfig *config)
 {
-    /* these should match the parameters in migration_properties */
-    params->has_throttle_trigger_threshold = true;
-    params->has_cpu_throttle_initial = true;
-    params->has_cpu_throttle_increment = true;
-    params->has_cpu_throttle_tailslow = true;
-    params->has_max_bandwidth = true;
-    params->has_downtime_limit = true;
-    params->has_x_checkpoint_delay = true;
-    params->has_multifd_channels = true;
-    params->has_multifd_compression = true;
-    params->has_multifd_zlib_level = true;
-    params->has_multifd_qatzip_level = true;
-    params->has_multifd_zstd_level = true;
-    params->has_xbzrle_cache_size = true;
-    params->has_max_postcopy_bandwidth = true;
-    params->has_max_cpu_throttle = true;
-    params->has_announce_initial = true;
-    params->has_announce_max = true;
-    params->has_announce_rounds = true;
-    params->has_announce_step = true;
-    params->has_x_vcpu_dirty_limit_period = true;
-    params->has_vcpu_dirty_limit = true;
-    params->has_mode = true;
-    params->has_zero_page_detection = true;
-    params->has_direct_io = true;
-    params->has_avail_switchover_bandwidth = true;
-    params->has_block_bitmap_mapping = true;
+    config->has_throttle_trigger_threshold = true;
+    config->has_cpu_throttle_initial = true;
+    config->has_cpu_throttle_increment = true;
+    config->has_cpu_throttle_tailslow = true;
+    config->has_max_bandwidth = true;
+    config->has_downtime_limit = true;
+    config->has_x_checkpoint_delay = true;
+    config->has_multifd_channels = true;
+    config->has_multifd_compression = true;
+    config->has_multifd_zlib_level = true;
+    config->has_multifd_qatzip_level = true;
+    config->has_multifd_zstd_level = true;
+    config->has_xbzrle_cache_size = true;
+    config->has_max_postcopy_bandwidth = true;
+    config->has_max_cpu_throttle = true;
+    config->has_announce_initial = true;
+    config->has_announce_max = true;
+    config->has_announce_rounds = true;
+    config->has_announce_step = true;
+    config->has_x_vcpu_dirty_limit_period = true;
+    config->has_vcpu_dirty_limit = true;
+    config->has_mode = true;
+    config->has_zero_page_detection = true;
+    config->has_direct_io = true;
+    config->has_avail_switchover_bandwidth = true;
+    config->has_block_bitmap_mapping = true;
+
+    config->has_xbzrle = true;
+    config->has_rdma_pin_all = true;
+    config->has_auto_converge = true;
+    config->has_zero_blocks = true;
+    config->has_events = true;
+    config->has_postcopy_ram = true;
+    config->has_postcopy_preempt = true;
+    config->has_x_colo = true;
+    config->has_release_ram = true;
+    config->has_return_path = true;
+    config->has_multifd = true;
+    config->has_background_snapshot = true;
+#ifdef CONFIG_LINUX
+    config->has_zero_copy_send = true;
+#endif
+    config->has_switchover_ack = true;
+    config->has_dirty_limit = true;
+    config->has_mapped_ram = true;
 }
 
 /*
  * Check whether the options are valid. Error will be put into errp
  * (if provided). Return true if valid, otherwise false.
  */
-bool migrate_config_check(MigrationConfig *params, Error **errp)
+bool migrate_config_check(MigrationConfig *new, Error **errp)
 {
+    MigrationIncomingState *mis = migration_incoming_get_current();
+    bool old_postcopy_ram = migrate_get_current()->config.postcopy_ram;
     ERRP_GUARD();
 
-    if (params->has_throttle_trigger_threshold &&
-        (params->throttle_trigger_threshold < 1 ||
-         params->throttle_trigger_threshold > 100)) {
+    if (new->has_throttle_trigger_threshold &&
+        (new->throttle_trigger_threshold < 1 ||
+         new->throttle_trigger_threshold > 100)) {
         error_setg(errp, "Option throttle_trigger_threshold expects "
                    "an integer in the range of 1 to 100");
         return false;
     }
 
-    if (params->has_cpu_throttle_initial &&
-        (params->cpu_throttle_initial < 1 ||
-         params->cpu_throttle_initial > 99)) {
+    if (new->has_cpu_throttle_initial &&
+        (new->cpu_throttle_initial < 1 ||
+         new->cpu_throttle_initial > 99)) {
         error_setg(errp, "Option cpu_throttle_initial expects "
                    "an integer in the range of 1 to 99");
         return false;
     }
 
-    if (params->has_cpu_throttle_increment &&
-        (params->cpu_throttle_increment < 1 ||
-         params->cpu_throttle_increment > 99)) {
+    if (new->has_cpu_throttle_increment &&
+        (new->cpu_throttle_increment < 1 ||
+         new->cpu_throttle_increment > 99)) {
         error_setg(errp, "Option cpu_throttle_increment expects "
                    "an integer in the range of 1 to 99");
         return false;
     }
 
-    if (params->has_max_bandwidth && (params->max_bandwidth > SIZE_MAX)) {
+    if (new->has_max_bandwidth && (new->max_bandwidth > SIZE_MAX)) {
         error_setg(errp, "Option max_bandwidth expects "
                    "an integer in the range of 0 to "stringify(SIZE_MAX)
                    " bytes/second");
         return false;
     }
 
-    if (params->has_avail_switchover_bandwidth &&
-        (params->avail_switchover_bandwidth > SIZE_MAX)) {
+    if (new->has_avail_switchover_bandwidth &&
+        (new->avail_switchover_bandwidth > SIZE_MAX)) {
         error_setg(errp, "Option avail_switchover_bandwidth expects "
                    "an integer in the range of 0 to "stringify(SIZE_MAX)
                    " bytes/second");
         return false;
     }
 
-    if (params->has_downtime_limit &&
-        (params->downtime_limit > MAX_MIGRATE_DOWNTIME)) {
+    if (new->has_downtime_limit &&
+        (new->downtime_limit > MAX_MIGRATE_DOWNTIME)) {
         error_setg(errp, "Option downtime_limit expects "
                    "an integer in the range of 0 to "
                     stringify(MAX_MIGRATE_DOWNTIME)" ms");
         return false;
     }
 
-    if (params->has_multifd_channels && (params->multifd_channels < 1)) {
+    if (new->has_multifd_channels && (new->multifd_channels < 1)) {
         error_setg(errp, "Option multifd_channels expects "
                    "a value between 1 and 255");
         return false;
     }
 
-    if (params->has_multifd_zlib_level &&
-        (params->multifd_zlib_level > 9)) {
+    if (new->has_multifd_zlib_level &&
+        (new->multifd_zlib_level > 9)) {
         error_setg(errp, "Option multifd_zlib_level expects "
                    "a value between 0 and 9");
         return false;
     }
 
-    if (params->has_multifd_qatzip_level &&
-        ((params->multifd_qatzip_level > 9) ||
-        (params->multifd_qatzip_level < 1))) {
+    if (new->has_multifd_qatzip_level &&
+        ((new->multifd_qatzip_level > 9) ||
+        (new->multifd_qatzip_level < 1))) {
         error_setg(errp, "Option multifd_qatzip_level expects "
                    "a value between 1 and 9");
         return false;
     }
 
-    if (params->has_multifd_zstd_level &&
-        (params->multifd_zstd_level > 20)) {
+    if (new->has_multifd_zstd_level &&
+        (new->multifd_zstd_level > 20)) {
         error_setg(errp, "Option multifd_zstd_level expects "
                    "a value between 0 and 20");
         return false;
     }
 
-    if (params->has_xbzrle_cache_size &&
-        (params->xbzrle_cache_size < qemu_target_page_size() ||
-         !is_power_of_2(params->xbzrle_cache_size))) {
+    if (new->has_xbzrle_cache_size &&
+        (new->xbzrle_cache_size < qemu_target_page_size() ||
+         !is_power_of_2(new->xbzrle_cache_size))) {
         error_setg(errp, "Option xbzrle_cache_size expects "
                    "a power of two no less than the target page size");
         return false;
     }
 
-    if (params->has_max_cpu_throttle &&
-        (params->max_cpu_throttle < params->cpu_throttle_initial ||
-         params->max_cpu_throttle > 99)) {
+    if (new->has_max_cpu_throttle &&
+        (new->max_cpu_throttle < new->cpu_throttle_initial ||
+         new->max_cpu_throttle > 99)) {
         error_setg(errp, "max_Option cpu_throttle expects "
                    "an integer in the range of cpu_throttle_initial to 99");
         return false;
     }
 
-    if (params->has_announce_initial &&
-        params->announce_initial > 100000) {
+    if (new->has_announce_initial &&
+        new->announce_initial > 100000) {
         error_setg(errp, "Option announce_initial expects "
                    "a value between 0 and 100000");
         return false;
     }
-    if (params->has_announce_max &&
-        params->announce_max > 100000) {
+    if (new->has_announce_max &&
+        new->announce_max > 100000) {
         error_setg(errp, "Option announce_max expects "
                    "a value between 0 and 100000");
         return false;
     }
-    if (params->has_announce_rounds &&
-        params->announce_rounds > 1000) {
+    if (new->has_announce_rounds &&
+        new->announce_rounds > 1000) {
         error_setg(errp, "Option announce_rounds expects "
                    "a value between 0 and 1000");
         return false;
     }
-    if (params->has_announce_step &&
-        (params->announce_step < 1 ||
-        params->announce_step > 10000)) {
+    if (new->has_announce_step &&
+        (new->announce_step < 1 ||
+        new->announce_step > 10000)) {
         error_setg(errp, "Option announce_step expects "
                    "a value between 0 and 10000");
         return false;
     }
 
-    if (params->has_block_bitmap_mapping &&
-        !check_dirty_bitmap_mig_alias_map(params->block_bitmap_mapping, errp)) {
+    if (new->has_block_bitmap_mapping &&
+        !check_dirty_bitmap_mig_alias_map(new->block_bitmap_mapping, errp)) {
         error_prepend(errp, "Invalid mapping given for block-bitmap-mapping: ");
         return false;
     }
 
 #ifdef CONFIG_LINUX
     if (migrate_zero_copy_send() &&
-        ((params->has_multifd_compression && params->multifd_compression) ||
-         (params->tls_creds && *params->tls_creds))) {
+        ((new->has_multifd_compression && new->multifd_compression) ||
+         (new->tls_creds && *new->tls_creds))) {
         error_setg(errp,
                    "Zero copy only available for non-compressed non-TLS multifd migration");
         return false;
@@ -1205,26 +1070,175 @@ bool migrate_config_check(MigrationConfig *params, Error **errp)
         return false;
     }
 
-    if (params->has_x_vcpu_dirty_limit_period &&
-        (params->x_vcpu_dirty_limit_period < 1 ||
-         params->x_vcpu_dirty_limit_period > 1000)) {
+    if (new->has_x_vcpu_dirty_limit_period &&
+        (new->x_vcpu_dirty_limit_period < 1 ||
+         new->x_vcpu_dirty_limit_period > 1000)) {
         error_setg(errp, "Option x-vcpu-dirty-limit-period expects "
                    "a value between 1 and 1000");
         return false;
     }
 
-    if (params->has_vcpu_dirty_limit &&
-        (params->vcpu_dirty_limit < 1)) {
+    if (new->has_vcpu_dirty_limit &&
+        (new->vcpu_dirty_limit < 1)) {
         error_setg(errp,
                    "Option 'vcpu_dirty_limit' must be greater than 1 MB/s");
         return false;
     }
 
-    if (params->has_direct_io && params->direct_io && !qemu_has_direct_io()) {
+    if (new->has_direct_io && new->direct_io && !qemu_has_direct_io()) {
         error_setg(errp, "No build-time support for direct-io");
         return false;
     }
 
+    if (new->zero_blocks) {
+        warn_report("zero-blocks capability is deprecated");
+    }
+
+#ifndef CONFIG_REPLICATION
+    if (new->x_colo) {
+        error_setg(errp, "QEMU compiled without replication module"
+                   " can't enable COLO");
+        error_append_hint(errp, "Please enable replication before COLO.\n");
+        return false;
+    }
+#endif
+
+    if (new->postcopy_ram) {
+        /* This check is reasonably expensive, so only when it's being
+         * set the first time, also it's only the destination that needs
+         * special support.
+         */
+        if (!old_postcopy_ram && runstate_check(RUN_STATE_INMIGRATE) &&
+            !postcopy_ram_supported_by_host(mis, errp)) {
+            error_prepend(errp, "Postcopy is not supported: ");
+            return false;
+        }
+
+        if (new->x_ignore_shared) {
+            error_setg(errp, "Postcopy is not compatible with ignore-shared");
+            return false;
+        }
+
+        if (new->multifd) {
+            error_setg(errp, "Postcopy is not yet compatible with multifd");
+            return false;
+        }
+    }
+
+    if (new->background_snapshot) {
+        WriteTrackingSupport wt_support;
+
+        /*
+         * Check if 'background-snapshot' capability is supported by
+         * host kernel and compatible with guest memory configuration.
+         */
+        wt_support = migrate_query_write_tracking();
+        if (wt_support < WT_SUPPORT_AVAILABLE) {
+            error_setg(errp, "Background-snapshot is not supported by host kernel");
+            return false;
+        }
+        if (wt_support < WT_SUPPORT_COMPATIBLE) {
+            error_setg(errp, "Background-snapshot is not compatible "
+                    "with guest memory configuration");
+            return false;
+        }
+
+        if (new->postcopy_ram ||
+            new->dirty_bitmaps ||
+            new->postcopy_blocktime ||
+            new->late_block_activate ||
+            new->return_path ||
+            new->multifd ||
+            new->pause_before_switchover ||
+            new->auto_converge ||
+            new->release_ram ||
+            new->rdma_pin_all ||
+            new->xbzrle ||
+            new->x_colo ||
+            new->validate_uuid ||
+            new->zero_copy_send) {
+            error_setg(errp,
+                       "Background-snapshot is not compatible with "
+                       "currently set capabilities");
+            return false;
+        }
+    }
+
+    if (new->zero_copy_send) {
+#ifdef CONFIG_LINUX
+        if (!new->multifd || new->xbzrle ||
+            migrate_multifd_compression() || migrate_tls()) {
+            error_setg(errp,
+                       "Zero copy only available for non-compressed non-TLS multifd migration");
+            return false;
+        }
+#else
+        error_setg(errp,
+                   "Zero copy currently only available on Linux");
+        return false;
+#endif
+    }
+
+    if (new->postcopy_preempt) {
+        if (!new->postcopy_ram) {
+            error_setg(errp, "Postcopy preempt requires postcopy-ram");
+            return false;
+        }
+
+        if (migrate_incoming_started()) {
+            error_setg(errp,
+                       "Postcopy preempt must be set before incoming starts");
+            return false;
+        }
+    }
+
+    if (new->multifd) {
+        if (migrate_incoming_started()) {
+            error_setg(errp, "Multifd must be set before incoming starts");
+            return false;
+        }
+
+        if (new->xbzrle) {
+            error_setg(errp, "Multifd is not compatible with xbzrle");
+            return false;
+        }
+    }
+
+    if (new->switchover_ack) {
+        if (!new->return_path) {
+            error_setg(errp, "Capability 'switchover-ack' requires capability "
+                             "'return-path'");
+            return false;
+        }
+    }
+    if (new->dirty_limit) {
+        if (new->auto_converge) {
+            error_setg(errp, "dirty-limit conflicts with auto-converge"
+                       " either of then available currently");
+            return false;
+        }
+
+        if (!kvm_enabled() || !kvm_dirty_ring_enabled()) {
+            error_setg(errp, "dirty-limit requires KVM with accelerator"
+                   " property 'dirty-ring-size' set");
+            return false;
+        }
+    }
+
+    if (new->mapped_ram) {
+        if (new->xbzrle) {
+            error_setg(errp,
+                       "Mapped-ram migration is incompatible with xbzrle");
+            return false;
+        }
+
+        if (new->postcopy_ram) {
+            error_setg(errp,
+                       "Mapped-ram migration is incompatible with postcopy");
+            return false;
+        }
+    }
+
     return true;
 }
 
diff --git a/migration/options.h b/migration/options.h
index 6455bce985..61ee854bb0 100644
--- a/migration/options.h
+++ b/migration/options.h
@@ -46,7 +46,6 @@ bool migrate_postcopy(void);
 bool migrate_rdma(void);
 bool migrate_tls(void);
 
-
 const BitmapMigrationNodeAliasList *migrate_block_bitmap_mapping(void);
 bool migrate_has_block_bitmap_mapping(void);
 
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 12/13] [PoC] migration: Add query/set commands for MigrationConfig
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (10 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 11/13] migration: Merge parameters and capability checks Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-05-26  7:51   ` Markus Armbruster
  2025-04-11 19:14 ` [RFC PATCH 13/13] [PoC] migration: Allow migrate commands to provide the migration config Fabiano Rosas
  2025-04-14 16:44 ` [RFC PATCH 00/13] migration: Unify capabilities and parameters Daniel P. Berrangé
  13 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Add the QMP commands query-migrate-config and migrate-set-config to
read and write the migration configuration options.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/options.c | 24 ++++++++++++++++++++++++
 migration/options.h |  2 +-
 qapi/migration.json | 42 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/migration/options.c b/migration/options.c
index 4e3792dec3..c85ee2e506 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -1441,3 +1441,27 @@ void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
     migrate_config_apply(&tmp);
     migrate_post_update_config(&tmp, errp);
 }
+
+void qmp_migrate_set_config(MigrationConfig *config, Error **errp)
+{
+    if (!migrate_config_check(config, errp)) {
+        /* Invalid parameter */
+        return;
+    }
+
+    migrate_config_apply(config);
+    migrate_post_update_config(config, errp);
+}
+
+MigrationConfig *qmp_query_migrate_config(Error **errp)
+{
+    MigrationState *s = migrate_get_current();
+    MigrationConfig *config = g_new0(MigrationConfig, 1);
+
+    QAPI_CLONE_MEMBERS(MigrationConfig, config, &s->config);
+
+    /* set the has_* fields for every option */
+    migrate_config_init(config);
+
+    return config;
+}
diff --git a/migration/options.h b/migration/options.h
index 61ee854bb0..0e36dafe80 100644
--- a/migration/options.h
+++ b/migration/options.h
@@ -72,7 +72,7 @@ uint64_t migrate_xbzrle_cache_size(void);
 ZeroPageDetection migrate_zero_page_detection(void);
 
 bool migrate_config_check(MigrationConfig *params, Error **errp);
-void migrate_config_init(MigrationConfig *params);
+void migrate_config_init(MigrationConfig *config);
 bool migrate_config_get_cap_compat(MigrationConfig *config, int i);
 bool migrate_caps_check(MigrationConfig *new, Error **errp);
 #endif
diff --git a/qapi/migration.json b/qapi/migration.json
index 5e39f21adc..bb2487dbc6 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -2552,3 +2552,45 @@
   'data': { '*tls-creds': 'str',
             '*tls-hostname': 'str',
             '*tls-authz': 'str' } }
+
+##
+# @query-migrate-config:
+#
+# Returns information about the current migration configuration
+# options
+#
+# Returns: @MigrationConfig
+#
+# Since: 10.1
+#
+# .. qmp-example::
+#
+#     -> { "execute": "query-migrate-config" }
+#     <- { "return": {
+#              "multifd-channels": 2,
+#              "cpu-throttle-increment": 10,
+#              "cpu-throttle-initial": 20,
+#              "max-bandwidth": 33554432,
+#              "downtime-limit": 300
+#           }
+#        }
+##
+{ 'command': 'query-migrate-config',
+  'returns': 'MigrationConfig' }
+
+##
+# @migrate-set-config:
+#
+# Set various migration configuration options.
+#
+# Since: 10.1
+#
+# .. qmp-example::
+#
+#     -> { "execute": "migrate-set-config" ,
+#          "arguments": { "max-bandwidth": 33554432,
+#                         "downtime-limit": 300 } }
+#     <- { "return": {} }
+##
+{ 'command': 'migrate-set-config', 'boxed': true,
+  'data': 'MigrationConfig' }
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [RFC PATCH 13/13] [PoC] migration: Allow migrate commands to provide the migration config
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (11 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 12/13] [PoC] migration: Add query/set commands for MigrationConfig Fabiano Rosas
@ 2025-04-11 19:14 ` Fabiano Rosas
  2025-05-26  8:03   ` Markus Armbruster
  2025-04-14 16:44 ` [RFC PATCH 00/13] migration: Unify capabilities and parameters Daniel P. Berrangé
  13 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-11 19:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Daniel P . Berrangé

Allow the migrate and migrate_incoming commands to pass the migration
configuration options all at once, dispensing the use of
migrate-set-parameters and migrate-set-capabilities.

The motivation of this is to simplify the interface with the
management layer and avoid the usage of several command invocations to
configure a migration. It also avoids stale parameters from a previous
migration to influence the current migration.

The options that are changed during the migration can still be set
with the existing commands.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 migration/migration-hmp-cmds.c |  5 +++--
 migration/migration.c          |  8 ++++----
 qapi/migration.json            | 10 ++++++++++
 system/vl.c                    |  3 ++-
 4 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c
index 49c26daed3..44d2265002 100644
--- a/migration/migration-hmp-cmds.c
+++ b/migration/migration-hmp-cmds.c
@@ -429,7 +429,7 @@ void hmp_migrate_incoming(Monitor *mon, const QDict *qdict)
     }
     QAPI_LIST_PREPEND(caps, g_steal_pointer(&channel));
 
-    qmp_migrate_incoming(NULL, true, caps, true, false, &err);
+    qmp_migrate_incoming(NULL, true, caps, NULL, true, false, &err);
     qapi_free_MigrationChannelList(caps);
 
 end:
@@ -715,7 +715,8 @@ void hmp_migrate(Monitor *mon, const QDict *qdict)
     }
     QAPI_LIST_PREPEND(caps, g_steal_pointer(&channel));
 
-    qmp_migrate(NULL, true, caps, false, false, true, resume, &err);
+    qmp_migrate(NULL, true, caps, NULL, false, false, true, resume,
+                &err);
     if (hmp_handle_error(mon, err)) {
         return;
     }
diff --git a/migration/migration.c b/migration/migration.c
index 55d839abd0..a1f04cef32 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -1894,8 +1894,8 @@ void migrate_del_blocker(Error **reasonp)
 
 void qmp_migrate_incoming(const char *uri, bool has_channels,
                           MigrationChannelList *channels,
-                          bool has_exit_on_error, bool exit_on_error,
-                          Error **errp)
+                          MigrationConfig *config, bool has_exit_on_error,
+                          bool exit_on_error, Error **errp)
 {
     Error *local_err = NULL;
     static bool once = true;
@@ -2159,8 +2159,8 @@ static gboolean qmp_migrate_finish_cb(QIOChannel *channel,
     return G_SOURCE_REMOVE;
 }
 
-void qmp_migrate(const char *uri, bool has_channels,
-                 MigrationChannelList *channels, bool has_detach, bool detach,
+void qmp_migrate(const char *uri, bool has_channels, MigrationChannelList *channels,
+                 MigrationConfig *config, bool has_detach, bool detach,
                  bool has_resume, bool resume, Error **errp)
 {
     bool resume_requested;
diff --git a/qapi/migration.json b/qapi/migration.json
index bb2487dbc6..5bd8f0f1b2 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -1638,6 +1638,10 @@
 #
 # @resume: resume one paused migration, default "off".  (since 3.0)
 #
+# @config: migration configuration options, previously set via
+#     @migrate-set-parameters and @migrate-set-capabilities.  (since
+#     10.1)
+#
 # Since: 0.14
 #
 # .. admonition:: Notes
@@ -1702,6 +1706,7 @@
 { 'command': 'migrate',
   'data': {'*uri': 'str',
            '*channels': [ 'MigrationChannel' ],
+           '*config': 'MigrationConfig',
            '*detach': 'bool', '*resume': 'bool' } }
 
 ##
@@ -1721,6 +1726,10 @@
 #     error details could be retrieved with query-migrate.
 #     (since 9.1)
 #
+# @config: migration configuration options, previously set via
+#     @migrate-set-parameters and @migrate-set-capabilities.  (since
+#     10.1)
+#
 # Since: 2.3
 #
 # .. admonition:: Notes
@@ -1774,6 +1783,7 @@
 { 'command': 'migrate-incoming',
              'data': {'*uri': 'str',
                       '*channels': [ 'MigrationChannel' ],
+                      '*config': 'MigrationConfig',
                       '*exit-on-error': 'bool' } }
 
 ##
diff --git a/system/vl.c b/system/vl.c
index ec93988a03..ea7040ef8d 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -2826,7 +2826,8 @@ void qmp_x_exit_preconfig(Error **errp)
                 g_new0(MigrationChannelList, 1);
 
             channels->value = incoming_channels[MIGRATION_CHANNEL_TYPE_MAIN];
-            qmp_migrate_incoming(NULL, true, channels, true, true, &local_err);
+            qmp_migrate_incoming(NULL, true, channels, NULL, true, true,
+                                 &local_err);
             if (local_err) {
                 error_reportf_err(local_err, "-incoming %s: ", incoming);
                 exit(1);
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 02/13] migration: Normalize tls arguments
  2025-04-11 19:14 ` [RFC PATCH 02/13] migration: Normalize tls arguments Fabiano Rosas
@ 2025-04-14 16:30   ` Daniel P. Berrangé
  0 siblings, 0 replies; 44+ messages in thread
From: Daniel P. Berrangé @ 2025-04-14 16:30 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Markus Armbruster

On Fri, Apr 11, 2025 at 04:14:32PM -0300, Fabiano Rosas wrote:
> The tls_creds, tls_authz and tls_hostname arguments are strings that
> can be set by the user. They are allowed to be either a valid string,
> an empty string or NULL. The values "" and NULL are effectively
> treated the same by the code, but this is not entirely clear because
> the handling is not uniform.
> 
> Make the 3 variables be handled the same and at the same place in
> options.c. Note that this affects only the internal usage of the
> variables.
> 
> (migrate_tls() had to be moved to be able to use migrate_tls_creds())
> 
> Signed-off-by: Fabiano Rosas <farosas@suse.de>
> ---
>  migration/options.c | 81 ++++++++++++++++++++++++---------------------
>  migration/tls.c     |  2 +-
>  2 files changed, 44 insertions(+), 39 deletions(-)
> 
> diff --git a/migration/options.c b/migration/options.c
> index cb8eec218f..7cd465ca94 100644
> --- a/migration/options.c
> +++ b/migration/options.c


> @@ -1184,18 +1200,27 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
>      }
>  
>      if (params->tls_creds) {
> -        assert(params->tls_creds->type == QTYPE_QSTRING);
> -        dest->tls_creds = params->tls_creds->u.s;
> +        if (params->tls_creds->type == QTYPE_QNULL) {
> +            dest->tls_creds = NULL;
> +        } else {
> +            dest->tls_creds = params->tls_creds->u.s;
> +        }

Feels like it is still worth having the assert(QTYPE_QSTRING)
in the else branch before we blindly reference the string pointer

>      }
>  
>      if (params->tls_hostname) {
> -        assert(params->tls_hostname->type == QTYPE_QSTRING);
> -        dest->tls_hostname = params->tls_hostname->u.s;
> +        if (params->tls_hostname->type == QTYPE_QNULL) {
> +            dest->tls_hostname = NULL;
> +        } else {
> +            dest->tls_hostname = params->tls_hostname->u.s;
> +        }
>      }
>  
>      if (params->tls_authz) {
> -        assert(params->tls_authz->type == QTYPE_QSTRING);
> -        dest->tls_authz = params->tls_authz->u.s;
> +        if (params->tls_authz->type == QTYPE_QNULL) {
> +            dest->tls_authz = NULL;
> +        } else {
> +            dest->tls_authz = params->tls_authz->u.s;
> +        }
>      }
>  
>      if (params->has_max_bandwidth) {

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 :|



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-11 19:14 ` [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json Fabiano Rosas
@ 2025-04-14 16:38   ` Daniel P. Berrangé
  2025-04-14 17:02     ` Fabiano Rosas
  2025-04-17 18:45   ` Markus Armbruster
  1 sibling, 1 reply; 44+ messages in thread
From: Daniel P. Berrangé @ 2025-04-14 16:38 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Markus Armbruster

On Fri, Apr 11, 2025 at 04:14:35PM -0300, Fabiano Rosas wrote:
> Introduce a new MigrationConfigBase, to allow most of the duplication
> in migration.json to be eliminated.
> 
> The reason we need MigrationParameters and MigrationSetParameters is
> that the internal parameter representation in the migration code, as
> well as the user-facing return of query-migrate-parameters use one
> type for the TLS options (tls-creds, tls-hostname, tls-authz), while
> the user-facing input from migrate-set-parameters uses another.
> 
> The difference is in whether the NULL values is accepted. The former
> considers NULL as invalid, while the latter doesn't.
> 
> Move all other (non-TLS) options into the new type and make it a base
> type for the two diverging types so that each child type can declare
> the TLS options in its own way.
> 
> Nothing changes in the user API, nothing changes in the internal
> representation, but we save several lines of duplication in
> migration.json.
> 
> Signed-off-by: Fabiano Rosas <farosas@suse.de>
> ---
>  qapi/migration.json | 358 +++++++++++++-------------------------------
>  1 file changed, 108 insertions(+), 250 deletions(-)
> 
> diff --git a/qapi/migration.json b/qapi/migration.json
> index 8b9c53595c..5a4d5a2d3e 100644
> --- a/qapi/migration.json
> +++ b/qapi/migration.json
> @@ -914,202 +914,6 @@

> @@ -1277,45 +1059,121 @@
>  #     only has effect if the @mapped-ram capability is enabled.
>  #     (Since 9.1)
>  #
> +# @tls: Whether to use TLS. If this is set the options @tls-authz,
> +#     @tls-creds, @tls-hostname are mandatory and a valid string is
> +#     expected. (Since 10.1)
> +#

I'm not really finding it compelling to add a bool @tls as it
is just a denormalization of  !!@tls-creds.

Incidentally the docs here are wrong - TLS can be used by
only setting @tls-creds. The @tls-authz & @tls-hostname
params are always optional.

>  # Features:
>  #
>  # @unstable: Members @x-checkpoint-delay and
>  #     @x-vcpu-dirty-limit-period are experimental.
>  #
> +# Since: 10.1
> +##
> +{ 'struct': 'MigrationConfigBase',
> +  'data': { '*announce-initial': 'size',
> +            '*announce-max': 'size',
> +            '*announce-rounds': 'size',
> +            '*announce-step': 'size',
> +            '*throttle-trigger-threshold': 'uint8',
> +            '*cpu-throttle-initial': 'uint8',
> +            '*cpu-throttle-increment': 'uint8',
> +            '*cpu-throttle-tailslow': 'bool',
> +            '*max-bandwidth': 'size',
> +            '*avail-switchover-bandwidth': 'size',
> +            '*downtime-limit': 'uint64',
> +            '*x-checkpoint-delay': { 'type': 'uint32',
> +                                     'features': [ 'unstable' ] },
> +            '*multifd-channels': 'uint8',
> +            '*xbzrle-cache-size': 'size',
> +            '*max-postcopy-bandwidth': 'size',
> +            '*max-cpu-throttle': 'uint8',
> +            '*multifd-compression': 'MultiFDCompression',
> +            '*multifd-zlib-level': 'uint8',
> +            '*multifd-qatzip-level': 'uint8',
> +            '*multifd-zstd-level': 'uint8',
> +            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
> +            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
> +                                            'features': [ 'unstable' ] },
> +            '*vcpu-dirty-limit': 'uint64',
> +            '*mode': 'MigMode',
> +            '*zero-page-detection': 'ZeroPageDetection',
> +            '*direct-io': 'bool',
> +            '*tls': 'bool' } }


>  { 'struct': 'MigrationParameters',
> +  'base': 'MigrationConfigBase',
> +  'data': { 'tls-creds': 'str',
> +            'tls-hostname': 'str',
> +            'tls-authz': 'str' } }

snip

> +{ 'struct': 'MigrateSetParameters',
> +  'base': 'MigrationConfigBase',
> +  'data': { '*tls-creds': 'StrOrNull',
> +            '*tls-hostname': 'StrOrNull',
> +            '*tls-authz': 'StrOrNull' } }

I recall we discussed this difference a year or two ago, but can't
recall what the outcome was.

Making the TLS params optional is a back compatible change for
MigrationParameters. I would think replacing 'str' with 'StrOrNull'
is also back compatible. So I'm wondering if we can't just unify
the sttructs fully for TLS, even if one usage scenario never actually
needs the "OrNull" bit nor needs the optionality 


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 :|



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
                   ` (12 preceding siblings ...)
  2025-04-11 19:14 ` [RFC PATCH 13/13] [PoC] migration: Allow migrate commands to provide the migration config Fabiano Rosas
@ 2025-04-14 16:44 ` Daniel P. Berrangé
  2025-04-14 17:12   ` Fabiano Rosas
  2025-04-16 13:44   ` Markus Armbruster
  13 siblings, 2 replies; 44+ messages in thread
From: Daniel P. Berrangé @ 2025-04-14 16:44 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Markus Armbruster

On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
> Open questions:
> ---------------
> 
> - Deprecations/compat?
> 
> I think we should deprecate migrate-set/query-capabilities and everything to do
> with capabilities (specifically the validation in the JSON at the end of the
> stream).
> 
> For migrate-set/query-parameters, we could probably keep it around indefinitely,
> but it'd be convenient to introduce new commands so we can give them new
> semantics.
> 
> - How to restrict the options that should not be set when the migration is in
> progress?
> 
> i.e.:
>   all options can be set before migration (initial config)
>   some options can be set during migration (runtime)
> 
> I thought of adding another type at the top of the hierarchy, with
> just the options allowed to change at runtime, but that doesn't really
> stop the others being also set at runtime. I'd need a way to have a
> set of options that are rejected 'if migration_is_running()', without
> adding more duplication all around.
> 
> - What about savevm?
> 
> None of this solves the issue of random caps/params being set before
> calling savevm. We still need to special-case savevm and reject
> everything. Unless we entirely deprecate setting initial options via
> set-parameters (or set-config) and require all options to be set as
> savevm (and migrate) arguments.

I'd suggest we aim for a world where the commands take all options
as direct args and try to remove the global state eventually.

For savevm/loadvm in particular it is very much a foot-gun that
'migrate-set-*' will affect them, because savevm/loadvm aren't
obviously connected to 'migrate-*' commands unless you're aware
of how QEMU implements savevm internally.

> - HMP?
> 
> Can we convert the strings passed via hmp_set_parameters without
> having an enum of parameters? Duplication problem again.

> 
> - incoming defer?
> 
> It seems we cannot do the final step of removing
> migrate-set-capabilites before we have a form of handshake
> implemented. That would take the config from qmp_migrate on source and
> send it to the destination for negotiation.

I'm not sure I understand why the QAPI design changes are tied
to the new protocol handshake ? I guess you're wanting to avoid
updating 'migrate_incoming' to accept the new parameters directly ?


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 :|



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-14 16:38   ` Daniel P. Berrangé
@ 2025-04-14 17:02     ` Fabiano Rosas
  2025-04-16 13:38       ` Markus Armbruster
  0 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-14 17:02 UTC (permalink / raw)
  To: Daniel P. Berrangé; +Cc: qemu-devel, Peter Xu, Markus Armbruster

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Fri, Apr 11, 2025 at 04:14:35PM -0300, Fabiano Rosas wrote:
>> Introduce a new MigrationConfigBase, to allow most of the duplication
>> in migration.json to be eliminated.
>> 
>> The reason we need MigrationParameters and MigrationSetParameters is
>> that the internal parameter representation in the migration code, as
>> well as the user-facing return of query-migrate-parameters use one
>> type for the TLS options (tls-creds, tls-hostname, tls-authz), while
>> the user-facing input from migrate-set-parameters uses another.
>> 
>> The difference is in whether the NULL values is accepted. The former
>> considers NULL as invalid, while the latter doesn't.
>> 
>> Move all other (non-TLS) options into the new type and make it a base
>> type for the two diverging types so that each child type can declare
>> the TLS options in its own way.
>> 
>> Nothing changes in the user API, nothing changes in the internal
>> representation, but we save several lines of duplication in
>> migration.json.
>> 
>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>> ---
>>  qapi/migration.json | 358 +++++++++++++-------------------------------
>>  1 file changed, 108 insertions(+), 250 deletions(-)
>> 
>> diff --git a/qapi/migration.json b/qapi/migration.json
>> index 8b9c53595c..5a4d5a2d3e 100644
>> --- a/qapi/migration.json
>> +++ b/qapi/migration.json
>> @@ -914,202 +914,6 @@
>
>> @@ -1277,45 +1059,121 @@
>>  #     only has effect if the @mapped-ram capability is enabled.
>>  #     (Since 9.1)
>>  #
>> +# @tls: Whether to use TLS. If this is set the options @tls-authz,
>> +#     @tls-creds, @tls-hostname are mandatory and a valid string is
>> +#     expected. (Since 10.1)
>> +#
>
> I'm not really finding it compelling to add a bool @tls as it
> is just a denormalization of  !!@tls-creds.
>

This is here by mistake.

I remember Markus mentioning that implying TLS usage from tls-creds was
undesirable. I was prototyping a way of requiring an explicit opt-in.

> Incidentally the docs here are wrong - TLS can be used by
> only setting @tls-creds. The @tls-authz & @tls-hostname
> params are always optional.
>
>>  # Features:
>>  #
>>  # @unstable: Members @x-checkpoint-delay and
>>  #     @x-vcpu-dirty-limit-period are experimental.
>>  #
>> +# Since: 10.1
>> +##
>> +{ 'struct': 'MigrationConfigBase',
>> +  'data': { '*announce-initial': 'size',
>> +            '*announce-max': 'size',
>> +            '*announce-rounds': 'size',
>> +            '*announce-step': 'size',
>> +            '*throttle-trigger-threshold': 'uint8',
>> +            '*cpu-throttle-initial': 'uint8',
>> +            '*cpu-throttle-increment': 'uint8',
>> +            '*cpu-throttle-tailslow': 'bool',
>> +            '*max-bandwidth': 'size',
>> +            '*avail-switchover-bandwidth': 'size',
>> +            '*downtime-limit': 'uint64',
>> +            '*x-checkpoint-delay': { 'type': 'uint32',
>> +                                     'features': [ 'unstable' ] },
>> +            '*multifd-channels': 'uint8',
>> +            '*xbzrle-cache-size': 'size',
>> +            '*max-postcopy-bandwidth': 'size',
>> +            '*max-cpu-throttle': 'uint8',
>> +            '*multifd-compression': 'MultiFDCompression',
>> +            '*multifd-zlib-level': 'uint8',
>> +            '*multifd-qatzip-level': 'uint8',
>> +            '*multifd-zstd-level': 'uint8',
>> +            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
>> +            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
>> +                                            'features': [ 'unstable' ] },
>> +            '*vcpu-dirty-limit': 'uint64',
>> +            '*mode': 'MigMode',
>> +            '*zero-page-detection': 'ZeroPageDetection',
>> +            '*direct-io': 'bool',
>> +            '*tls': 'bool' } }
>
>
>>  { 'struct': 'MigrationParameters',
>> +  'base': 'MigrationConfigBase',
>> +  'data': { 'tls-creds': 'str',
>> +            'tls-hostname': 'str',
>> +            'tls-authz': 'str' } }
>
> snip
>
>> +{ 'struct': 'MigrateSetParameters',
>> +  'base': 'MigrationConfigBase',
>> +  'data': { '*tls-creds': 'StrOrNull',
>> +            '*tls-hostname': 'StrOrNull',
>> +            '*tls-authz': 'StrOrNull' } }
>
> I recall we discussed this difference a year or two ago, but can't
> recall what the outcome was.
>
> Making the TLS params optional is a back compatible change for
> MigrationParameters. I would think replacing 'str' with 'StrOrNull'
> is also back compatible. So I'm wondering if we can't just unify
> the sttructs fully for TLS, even if one usage scenario never actually
> needs the "OrNull" bit nor needs the optionality 
>

MigrationParameters is the output type for query-migrate-parameters. I
belive it must be all non-optional to keep compatibility. The docs on
master say: "The optional members aren't really optional"

For MigrateSetParameters they've always been optional and continue to
be. There we need to keep StrOrNull for compat.

>
> With regards,
> Daniel


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-14 16:44 ` [RFC PATCH 00/13] migration: Unify capabilities and parameters Daniel P. Berrangé
@ 2025-04-14 17:12   ` Fabiano Rosas
  2025-04-14 17:20     ` Daniel P. Berrangé
  2025-04-16 13:44   ` Markus Armbruster
  1 sibling, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-14 17:12 UTC (permalink / raw)
  To: Daniel P. Berrangé; +Cc: qemu-devel, Peter Xu, Markus Armbruster

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
>> Open questions:
>> ---------------
>> 
>> - Deprecations/compat?
>> 
>> I think we should deprecate migrate-set/query-capabilities and everything to do
>> with capabilities (specifically the validation in the JSON at the end of the
>> stream).
>> 
>> For migrate-set/query-parameters, we could probably keep it around indefinitely,
>> but it'd be convenient to introduce new commands so we can give them new
>> semantics.
>> 
>> - How to restrict the options that should not be set when the migration is in
>> progress?
>> 
>> i.e.:
>>   all options can be set before migration (initial config)
>>   some options can be set during migration (runtime)
>> 
>> I thought of adding another type at the top of the hierarchy, with
>> just the options allowed to change at runtime, but that doesn't really
>> stop the others being also set at runtime. I'd need a way to have a
>> set of options that are rejected 'if migration_is_running()', without
>> adding more duplication all around.
>> 
>> - What about savevm?
>> 
>> None of this solves the issue of random caps/params being set before
>> calling savevm. We still need to special-case savevm and reject
>> everything. Unless we entirely deprecate setting initial options via
>> set-parameters (or set-config) and require all options to be set as
>> savevm (and migrate) arguments.
>
> I'd suggest we aim for a world where the commands take all options
> as direct args and try to remove the global state eventually.
>

Well, except the options that are adjusted during migration. But yes, I
agree. It all depends on how we proceed with keeping the old commands
around and for how long. If they're still around we can't stop people
from using them and later invoking "savevm" for instance.

> For savevm/loadvm in particular it is very much a foot-gun that
> 'migrate-set-*' will affect them, because savevm/loadvm aren't
> obviously connected to 'migrate-*' commands unless you're aware
> of how QEMU implements savevm internally.
>

Yes, I could perhaps reset all options once savevm is called, maybe that
would be acceptable, then we don't need to check and block every single
one. Once we add support to migration options to savevm, then they'd be
set in the savevm command-line from day 1 and those wouldn't be
reset. We could also keep HMP restricted to savevm without any migration
options. That's be easy to enforce. If the user wants fancy savevm, they
can invoke via QMP.

>> - HMP?
>> 
>> Can we convert the strings passed via hmp_set_parameters without
>> having an enum of parameters? Duplication problem again.
>
>> 
>> - incoming defer?
>> 
>> It seems we cannot do the final step of removing
>> migrate-set-capabilites before we have a form of handshake
>> implemented. That would take the config from qmp_migrate on source and
>> send it to the destination for negotiation.
>
> I'm not sure I understand why the QAPI design changes are tied
> to the new protocol handshake ? I guess you're wanting to avoid
> updating 'migrate_incoming' to accept the new parameters directly ?
>

Yes, without migrate-set-capabilities, we'd need to pass an enormous
command line to -incoming defer to be able to enable capabilities on the
destination. With the handshake, we could transfer them over the wire
somehow. Does that make sense?

>
> With regards,
> Daniel


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-14 17:12   ` Fabiano Rosas
@ 2025-04-14 17:20     ` Daniel P. Berrangé
  2025-04-14 17:40       ` Fabiano Rosas
  0 siblings, 1 reply; 44+ messages in thread
From: Daniel P. Berrangé @ 2025-04-14 17:20 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Markus Armbruster

On Mon, Apr 14, 2025 at 02:12:35PM -0300, Fabiano Rosas wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
> >> Open questions:
> >> ---------------
> >> 
> >> - Deprecations/compat?
> >> 
> >> I think we should deprecate migrate-set/query-capabilities and everything to do
> >> with capabilities (specifically the validation in the JSON at the end of the
> >> stream).
> >> 
> >> For migrate-set/query-parameters, we could probably keep it around indefinitely,
> >> but it'd be convenient to introduce new commands so we can give them new
> >> semantics.
> >> 
> >> - How to restrict the options that should not be set when the migration is in
> >> progress?
> >> 
> >> i.e.:
> >>   all options can be set before migration (initial config)
> >>   some options can be set during migration (runtime)
> >> 
> >> I thought of adding another type at the top of the hierarchy, with
> >> just the options allowed to change at runtime, but that doesn't really
> >> stop the others being also set at runtime. I'd need a way to have a
> >> set of options that are rejected 'if migration_is_running()', without
> >> adding more duplication all around.
> >> 
> >> - What about savevm?
> >> 
> >> None of this solves the issue of random caps/params being set before
> >> calling savevm. We still need to special-case savevm and reject
> >> everything. Unless we entirely deprecate setting initial options via
> >> set-parameters (or set-config) and require all options to be set as
> >> savevm (and migrate) arguments.
> >
> > I'd suggest we aim for a world where the commands take all options
> > as direct args and try to remove the global state eventually.
> >
> 
> Well, except the options that are adjusted during migration. But yes, I
> agree. It all depends on how we proceed with keeping the old commands
> around and for how long. If they're still around we can't stop people
> from using them and later invoking "savevm" for instance.
> 
> > For savevm/loadvm in particular it is very much a foot-gun that
> > 'migrate-set-*' will affect them, because savevm/loadvm aren't
> > obviously connected to 'migrate-*' commands unless you're aware
> > of how QEMU implements savevm internally.
> >
> 
> Yes, I could perhaps reset all options once savevm is called, maybe that
> would be acceptable, then we don't need to check and block every single
> one. Once we add support to migration options to savevm, then they'd be
> set in the savevm command-line from day 1 and those wouldn't be
> reset. We could also keep HMP restricted to savevm without any migration
> options. That's be easy to enforce. If the user wants fancy savevm, they
> can invoke via QMP.

Can we make the two approaches mutually exclusive ? Taking your
'migrate' command example addition:

  { 'command': 'migrate',
    'data': {'*uri': 'str',
             '*channels': [ 'MigrationChannel' ],
  +          '*config': 'MigrationConfig',
             '*detach': 'bool', '*resume': 'bool' } }

if 'migrate' is invoked with the '*config' data being non-nil,
then we should ignore *all* global state previously set with
migrate-set-XXXX, and exclusively use '*config'.

That gives a clean semantic break between old and new approaches,
without us having to worry about removing the existing commands
quickly.


> >> - incoming defer?
> >> 
> >> It seems we cannot do the final step of removing
> >> migrate-set-capabilites before we have a form of handshake
> >> implemented. That would take the config from qmp_migrate on source and
> >> send it to the destination for negotiation.
> >
> > I'm not sure I understand why the QAPI design changes are tied
> > to the new protocol handshake ? I guess you're wanting to avoid
> > updating 'migrate_incoming' to accept the new parameters directly ?
> >
> 
> Yes, without migrate-set-capabilities, we'd need to pass an enormous
> command line to -incoming defer to be able to enable capabilities on the
> destination. With the handshake, we could transfer them over the wire
> somehow. Does that make sense?

'-incoming defer' still gets paired with 'migrate-incoming' on the
target, so no matter what, there's no reason to ever pass parameters
on the CLI with '-incoming defer'.


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 :|



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-14 17:20     ` Daniel P. Berrangé
@ 2025-04-14 17:40       ` Fabiano Rosas
  2025-04-14 19:06         ` Daniel P. Berrangé
  2025-05-15 20:21         ` Peter Xu
  0 siblings, 2 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-14 17:40 UTC (permalink / raw)
  To: Daniel P. Berrangé; +Cc: qemu-devel, Peter Xu, Markus Armbruster

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Mon, Apr 14, 2025 at 02:12:35PM -0300, Fabiano Rosas wrote:
>> Daniel P. Berrangé <berrange@redhat.com> writes:
>> 
>> > On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
>> >> Open questions:
>> >> ---------------
>> >> 
>> >> - Deprecations/compat?
>> >> 
>> >> I think we should deprecate migrate-set/query-capabilities and everything to do
>> >> with capabilities (specifically the validation in the JSON at the end of the
>> >> stream).
>> >> 
>> >> For migrate-set/query-parameters, we could probably keep it around indefinitely,
>> >> but it'd be convenient to introduce new commands so we can give them new
>> >> semantics.
>> >> 
>> >> - How to restrict the options that should not be set when the migration is in
>> >> progress?
>> >> 
>> >> i.e.:
>> >>   all options can be set before migration (initial config)
>> >>   some options can be set during migration (runtime)
>> >> 
>> >> I thought of adding another type at the top of the hierarchy, with
>> >> just the options allowed to change at runtime, but that doesn't really
>> >> stop the others being also set at runtime. I'd need a way to have a
>> >> set of options that are rejected 'if migration_is_running()', without
>> >> adding more duplication all around.
>> >> 
>> >> - What about savevm?
>> >> 
>> >> None of this solves the issue of random caps/params being set before
>> >> calling savevm. We still need to special-case savevm and reject
>> >> everything. Unless we entirely deprecate setting initial options via
>> >> set-parameters (or set-config) and require all options to be set as
>> >> savevm (and migrate) arguments.
>> >
>> > I'd suggest we aim for a world where the commands take all options
>> > as direct args and try to remove the global state eventually.
>> >
>> 
>> Well, except the options that are adjusted during migration. But yes, I
>> agree. It all depends on how we proceed with keeping the old commands
>> around and for how long. If they're still around we can't stop people
>> from using them and later invoking "savevm" for instance.
>> 
>> > For savevm/loadvm in particular it is very much a foot-gun that
>> > 'migrate-set-*' will affect them, because savevm/loadvm aren't
>> > obviously connected to 'migrate-*' commands unless you're aware
>> > of how QEMU implements savevm internally.
>> >
>> 
>> Yes, I could perhaps reset all options once savevm is called, maybe that
>> would be acceptable, then we don't need to check and block every single
>> one. Once we add support to migration options to savevm, then they'd be
>> set in the savevm command-line from day 1 and those wouldn't be
>> reset. We could also keep HMP restricted to savevm without any migration
>> options. That's be easy to enforce. If the user wants fancy savevm, they
>> can invoke via QMP.
>
> Can we make the two approaches mutually exclusive ? Taking your
> 'migrate' command example addition:
>
>   { 'command': 'migrate',
>     'data': {'*uri': 'str',
>              '*channels': [ 'MigrationChannel' ],
>   +          '*config': 'MigrationConfig',
>              '*detach': 'bool', '*resume': 'bool' } }
>
> if 'migrate' is invoked with the '*config' data being non-nil,
> then we should ignore *all* global state previously set with
> migrate-set-XXXX, and exclusively use '*config'.
>
> That gives a clean semantic break between old and new approaches,
> without us having to worry about removing the existing commands
> quickly.
>

Good idea. I will need to do something about the -global options because
they also set the defaults for the various options. But we should be
able to decouple setting defaults from -global. Or I could just apply
-global again on top of what came in '*config'.

>
>> >> - incoming defer?
>> >> 
>> >> It seems we cannot do the final step of removing
>> >> migrate-set-capabilites before we have a form of handshake
>> >> implemented. That would take the config from qmp_migrate on source and
>> >> send it to the destination for negotiation.
>> >
>> > I'm not sure I understand why the QAPI design changes are tied
>> > to the new protocol handshake ? I guess you're wanting to avoid
>> > updating 'migrate_incoming' to accept the new parameters directly ?
>> >
>> 
>> Yes, without migrate-set-capabilities, we'd need to pass an enormous
>> command line to -incoming defer to be able to enable capabilities on the
>> destination. With the handshake, we could transfer them over the wire
>> somehow. Does that make sense?
>
> '-incoming defer' still gets paired with 'migrate-incoming' on the
> target, so no matter what, there's no reason to ever pass parameters
> on the CLI with '-incoming defer'.
>

Oops, I misread the strcmp in vl.c. I mean -incoming uri is the one
that'll need a huge cmdline.

But if we follow your suggestion above we could just tie -incoming URI
to the existing commands and make the new format require defer.

>
> With regards,
> Daniel


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-14 17:40       ` Fabiano Rosas
@ 2025-04-14 19:06         ` Daniel P. Berrangé
  2025-05-15 20:21         ` Peter Xu
  1 sibling, 0 replies; 44+ messages in thread
From: Daniel P. Berrangé @ 2025-04-14 19:06 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Markus Armbruster

On Mon, Apr 14, 2025 at 02:40:25PM -0300, Fabiano Rosas wrote:
> Daniel P. Berrangé <berrange@redhat.com> writes:
> 
> > On Mon, Apr 14, 2025 at 02:12:35PM -0300, Fabiano Rosas wrote:
> >> Daniel P. Berrangé <berrange@redhat.com> writes:
> >> 
> >> > On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
> >> >> Open questions:
> >> >> ---------------
> >> >> 
> >> >> - Deprecations/compat?
> >> >> 
> >> >> I think we should deprecate migrate-set/query-capabilities and everything to do
> >> >> with capabilities (specifically the validation in the JSON at the end of the
> >> >> stream).
> >> >> 
> >> >> For migrate-set/query-parameters, we could probably keep it around indefinitely,
> >> >> but it'd be convenient to introduce new commands so we can give them new
> >> >> semantics.
> >> >> 
> >> >> - How to restrict the options that should not be set when the migration is in
> >> >> progress?
> >> >> 
> >> >> i.e.:
> >> >>   all options can be set before migration (initial config)
> >> >>   some options can be set during migration (runtime)
> >> >> 
> >> >> I thought of adding another type at the top of the hierarchy, with
> >> >> just the options allowed to change at runtime, but that doesn't really
> >> >> stop the others being also set at runtime. I'd need a way to have a
> >> >> set of options that are rejected 'if migration_is_running()', without
> >> >> adding more duplication all around.
> >> >> 
> >> >> - What about savevm?
> >> >> 
> >> >> None of this solves the issue of random caps/params being set before
> >> >> calling savevm. We still need to special-case savevm and reject
> >> >> everything. Unless we entirely deprecate setting initial options via
> >> >> set-parameters (or set-config) and require all options to be set as
> >> >> savevm (and migrate) arguments.
> >> >
> >> > I'd suggest we aim for a world where the commands take all options
> >> > as direct args and try to remove the global state eventually.
> >> >
> >> 
> >> Well, except the options that are adjusted during migration. But yes, I
> >> agree. It all depends on how we proceed with keeping the old commands
> >> around and for how long. If they're still around we can't stop people
> >> from using them and later invoking "savevm" for instance.
> >> 
> >> > For savevm/loadvm in particular it is very much a foot-gun that
> >> > 'migrate-set-*' will affect them, because savevm/loadvm aren't
> >> > obviously connected to 'migrate-*' commands unless you're aware
> >> > of how QEMU implements savevm internally.
> >> >
> >> 
> >> Yes, I could perhaps reset all options once savevm is called, maybe that
> >> would be acceptable, then we don't need to check and block every single
> >> one. Once we add support to migration options to savevm, then they'd be
> >> set in the savevm command-line from day 1 and those wouldn't be
> >> reset. We could also keep HMP restricted to savevm without any migration
> >> options. That's be easy to enforce. If the user wants fancy savevm, they
> >> can invoke via QMP.
> >
> > Can we make the two approaches mutually exclusive ? Taking your
> > 'migrate' command example addition:
> >
> >   { 'command': 'migrate',
> >     'data': {'*uri': 'str',
> >              '*channels': [ 'MigrationChannel' ],
> >   +          '*config': 'MigrationConfig',
> >              '*detach': 'bool', '*resume': 'bool' } }
> >
> > if 'migrate' is invoked with the '*config' data being non-nil,
> > then we should ignore *all* global state previously set with
> > migrate-set-XXXX, and exclusively use '*config'.
> >
> > That gives a clean semantic break between old and new approaches,
> > without us having to worry about removing the existing commands
> > quickly.
> >
> 
> Good idea. I will need to do something about the -global options because
> they also set the defaults for the various options. But we should be
> able to decouple setting defaults from -global. Or I could just apply
> -global again on top of what came in '*config'.
> 
> >
> >> >> - incoming defer?
> >> >> 
> >> >> It seems we cannot do the final step of removing
> >> >> migrate-set-capabilites before we have a form of handshake
> >> >> implemented. That would take the config from qmp_migrate on source and
> >> >> send it to the destination for negotiation.
> >> >
> >> > I'm not sure I understand why the QAPI design changes are tied
> >> > to the new protocol handshake ? I guess you're wanting to avoid
> >> > updating 'migrate_incoming' to accept the new parameters directly ?
> >> >
> >> 
> >> Yes, without migrate-set-capabilities, we'd need to pass an enormous
> >> command line to -incoming defer to be able to enable capabilities on the
> >> destination. With the handshake, we could transfer them over the wire
> >> somehow. Does that make sense?
> >
> > '-incoming defer' still gets paired with 'migrate-incoming' on the
> > target, so no matter what, there's no reason to ever pass parameters
> > on the CLI with '-incoming defer'.
> >
> 
> Oops, I misread the strcmp in vl.c. I mean -incoming uri is the one
> that'll need a huge cmdline.
> 
> But if we follow your suggestion above we could just tie -incoming URI
> to the existing commands and make the new format require defer.

Yes,  modern approach should require 'defer'.


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 :|



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-14 17:02     ` Fabiano Rosas
@ 2025-04-16 13:38       ` Markus Armbruster
  2025-04-16 14:41         ` Fabiano Rosas
  0 siblings, 1 reply; 44+ messages in thread
From: Markus Armbruster @ 2025-04-16 13:38 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: Daniel P. Berrangé, qemu-devel, Peter Xu

Fabiano Rosas <farosas@suse.de> writes:

> Daniel P. Berrangé <berrange@redhat.com> writes:
>
>> On Fri, Apr 11, 2025 at 04:14:35PM -0300, Fabiano Rosas wrote:
>>> Introduce a new MigrationConfigBase, to allow most of the duplication
>>> in migration.json to be eliminated.
>>> 
>>> The reason we need MigrationParameters and MigrationSetParameters is
>>> that the internal parameter representation in the migration code, as
>>> well as the user-facing return of query-migrate-parameters use one
>>> type for the TLS options (tls-creds, tls-hostname, tls-authz), while
>>> the user-facing input from migrate-set-parameters uses another.
>>> 
>>> The difference is in whether the NULL values is accepted. The former
>>> considers NULL as invalid, while the latter doesn't.
>>> 
>>> Move all other (non-TLS) options into the new type and make it a base
>>> type for the two diverging types so that each child type can declare
>>> the TLS options in its own way.
>>> 
>>> Nothing changes in the user API, nothing changes in the internal
>>> representation, but we save several lines of duplication in
>>> migration.json.
>>> 
>>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>>> ---
>>>  qapi/migration.json | 358 +++++++++++++-------------------------------
>>>  1 file changed, 108 insertions(+), 250 deletions(-)
>>> 
>>> diff --git a/qapi/migration.json b/qapi/migration.json
>>> index 8b9c53595c..5a4d5a2d3e 100644
>>> --- a/qapi/migration.json
>>> +++ b/qapi/migration.json
>>> @@ -914,202 +914,6 @@
>>
>>> @@ -1277,45 +1059,121 @@
>>>  #     only has effect if the @mapped-ram capability is enabled.
>>>  #     (Since 9.1)
>>>  #
>>> +# @tls: Whether to use TLS. If this is set the options @tls-authz,
>>> +#     @tls-creds, @tls-hostname are mandatory and a valid string is
>>> +#     expected. (Since 10.1)
>>> +#
>>
>> I'm not really finding it compelling to add a bool @tls as it
>> is just a denormalization of  !!@tls-creds.
>>
>
> This is here by mistake.
>
> I remember Markus mentioning that implying TLS usage from tls-creds was
> undesirable. I was prototyping a way of requiring an explicit opt-in.

I don't remember a thing :)

>> Incidentally the docs here are wrong - TLS can be used by
>> only setting @tls-creds. The @tls-authz & @tls-hostname
>> params are always optional.
>>
>>>  # Features:
>>>  #
>>>  # @unstable: Members @x-checkpoint-delay and
>>>  #     @x-vcpu-dirty-limit-period are experimental.
>>>  #
>>> +# Since: 10.1
>>> +##
>>> +{ 'struct': 'MigrationConfigBase',
>>> +  'data': { '*announce-initial': 'size',
>>> +            '*announce-max': 'size',
>>> +            '*announce-rounds': 'size',
>>> +            '*announce-step': 'size',
>>> +            '*throttle-trigger-threshold': 'uint8',
>>> +            '*cpu-throttle-initial': 'uint8',
>>> +            '*cpu-throttle-increment': 'uint8',
>>> +            '*cpu-throttle-tailslow': 'bool',
>>> +            '*max-bandwidth': 'size',
>>> +            '*avail-switchover-bandwidth': 'size',
>>> +            '*downtime-limit': 'uint64',
>>> +            '*x-checkpoint-delay': { 'type': 'uint32',
>>> +                                     'features': [ 'unstable' ] },
>>> +            '*multifd-channels': 'uint8',
>>> +            '*xbzrle-cache-size': 'size',
>>> +            '*max-postcopy-bandwidth': 'size',
>>> +            '*max-cpu-throttle': 'uint8',
>>> +            '*multifd-compression': 'MultiFDCompression',
>>> +            '*multifd-zlib-level': 'uint8',
>>> +            '*multifd-qatzip-level': 'uint8',
>>> +            '*multifd-zstd-level': 'uint8',
>>> +            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
>>> +            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
>>> +                                            'features': [ 'unstable' ] },
>>> +            '*vcpu-dirty-limit': 'uint64',
>>> +            '*mode': 'MigMode',
>>> +            '*zero-page-detection': 'ZeroPageDetection',
>>> +            '*direct-io': 'bool',
>>> +            '*tls': 'bool' } }
>>
>>
>>>  { 'struct': 'MigrationParameters',
>>> +  'base': 'MigrationConfigBase',
>>> +  'data': { 'tls-creds': 'str',
>>> +            'tls-hostname': 'str',
>>> +            'tls-authz': 'str' } }
>>
>> snip
>>
>>> +{ 'struct': 'MigrateSetParameters',
>>> +  'base': 'MigrationConfigBase',
>>> +  'data': { '*tls-creds': 'StrOrNull',
>>> +            '*tls-hostname': 'StrOrNull',
>>> +            '*tls-authz': 'StrOrNull' } }
>>
>> I recall we discussed this difference a year or two ago, but can't
>> recall what the outcome was.
>>
>> Making the TLS params optional is a back compatible change for
>> MigrationParameters. I would think replacing 'str' with 'StrOrNull'
>> is also back compatible. So I'm wondering if we can't just unify
>> the sttructs fully for TLS, even if one usage scenario never actually
>> needs the "OrNull" bit nor needs the optionality 
>>
>
> MigrationParameters is the output type for query-migrate-parameters. I
> belive it must be all non-optional to keep compatibility. The docs on
> master say: "The optional members aren't really optional"

To be precise: even though the members are declared optional, they must
always be present.

The members became optional when commit de63ab61241 (migrate: Share
common MigrationParameters struct) reused MigrationParameters as
migrate-set-parameters argument type.

Making mandatory members of some type optional is compatible as long as
the type occurs only in command arguments.  But MigrationParameters is a
command return type.  Making its members optional was not kosher.
Because the optional members are always present, QMP didn't actually
change, except for introspection.

> For MigrateSetParameters they've always been optional and continue to
> be. There we need to keep StrOrNull for compat.

StrOrNull goes back to commit 01fa5598269 (migration: Use JSON null
instead of "" to reset parameter to default).

Similar argument: adding values to a member is compatible as long as the
type occurs only in command arguments.  But MigrationParameters is a
command return type.  Adding value null is won't be kosher.  However, as
long as as the value is never actually used there, QMP won't actually
change, except for introspection.

If this was a clean and obvious interface, I'd argue against such
trickery and for keeping it clean and obvious.  But the migration
configuration interface isn't.



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-14 16:44 ` [RFC PATCH 00/13] migration: Unify capabilities and parameters Daniel P. Berrangé
  2025-04-14 17:12   ` Fabiano Rosas
@ 2025-04-16 13:44   ` Markus Armbruster
  2025-04-16 15:00     ` Fabiano Rosas
  1 sibling, 1 reply; 44+ messages in thread
From: Markus Armbruster @ 2025-04-16 13:44 UTC (permalink / raw)
  To: Daniel P. Berrangé; +Cc: Fabiano Rosas, qemu-devel, Peter Xu

Daniel P. Berrangé <berrange@redhat.com> writes:

> On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
>> Open questions:
>> ---------------
>> 
>> - Deprecations/compat?
>> 
>> I think we should deprecate migrate-set/query-capabilities and everything to do
>> with capabilities (specifically the validation in the JSON at the end of the
>> stream).
>> 
>> For migrate-set/query-parameters, we could probably keep it around indefinitely,
>> but it'd be convenient to introduce new commands so we can give them new
>> semantics.
>> 
>> - How to restrict the options that should not be set when the migration is in
>> progress?
>> 
>> i.e.:
>>   all options can be set before migration (initial config)
>>   some options can be set during migration (runtime)
>> 
>> I thought of adding another type at the top of the hierarchy, with
>> just the options allowed to change at runtime, but that doesn't really
>> stop the others being also set at runtime. I'd need a way to have a
>> set of options that are rejected 'if migration_is_running()', without
>> adding more duplication all around.
>> 
>> - What about savevm?
>> 
>> None of this solves the issue of random caps/params being set before
>> calling savevm. We still need to special-case savevm and reject
>> everything. Unless we entirely deprecate setting initial options via
>> set-parameters (or set-config) and require all options to be set as
>> savevm (and migrate) arguments.
>
> I'd suggest we aim for a world where the commands take all options
> as direct args and try to remove the global state eventually.

Yes.

Even better: make it a job.

[...]



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-16 13:38       ` Markus Armbruster
@ 2025-04-16 14:41         ` Fabiano Rosas
  2025-04-17  5:56           ` Markus Armbruster
  0 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-16 14:41 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Daniel P. Berrangé, qemu-devel, Peter Xu

Markus Armbruster <armbru@redhat.com> writes:

> Fabiano Rosas <farosas@suse.de> writes:
>
>> Daniel P. Berrangé <berrange@redhat.com> writes:
>>
>>> On Fri, Apr 11, 2025 at 04:14:35PM -0300, Fabiano Rosas wrote:
>>>> Introduce a new MigrationConfigBase, to allow most of the duplication
>>>> in migration.json to be eliminated.
>>>> 
>>>> The reason we need MigrationParameters and MigrationSetParameters is
>>>> that the internal parameter representation in the migration code, as
>>>> well as the user-facing return of query-migrate-parameters use one
>>>> type for the TLS options (tls-creds, tls-hostname, tls-authz), while
>>>> the user-facing input from migrate-set-parameters uses another.
>>>> 
>>>> The difference is in whether the NULL values is accepted. The former
>>>> considers NULL as invalid, while the latter doesn't.
>>>> 
>>>> Move all other (non-TLS) options into the new type and make it a base
>>>> type for the two diverging types so that each child type can declare
>>>> the TLS options in its own way.
>>>> 
>>>> Nothing changes in the user API, nothing changes in the internal
>>>> representation, but we save several lines of duplication in
>>>> migration.json.
>>>> 
>>>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>>>> ---
>>>>  qapi/migration.json | 358 +++++++++++++-------------------------------
>>>>  1 file changed, 108 insertions(+), 250 deletions(-)
>>>> 
>>>> diff --git a/qapi/migration.json b/qapi/migration.json
>>>> index 8b9c53595c..5a4d5a2d3e 100644
>>>> --- a/qapi/migration.json
>>>> +++ b/qapi/migration.json
>>>> @@ -914,202 +914,6 @@
>>>
>>>> @@ -1277,45 +1059,121 @@
>>>>  #     only has effect if the @mapped-ram capability is enabled.
>>>>  #     (Since 9.1)
>>>>  #
>>>> +# @tls: Whether to use TLS. If this is set the options @tls-authz,
>>>> +#     @tls-creds, @tls-hostname are mandatory and a valid string is
>>>> +#     expected. (Since 10.1)
>>>> +#
>>>
>>> I'm not really finding it compelling to add a bool @tls as it
>>> is just a denormalization of  !!@tls-creds.
>>>
>>
>> This is here by mistake.
>>
>> I remember Markus mentioning that implying TLS usage from tls-creds was
>> undesirable. I was prototyping a way of requiring an explicit opt-in.
>
> I don't remember a thing :)
>

I hope I interpreted you correctly:

  "We have three syntactically independent parameters: @tls-creds,
  @tls-hostname, @tls-authz.  The latter two are meaningless (and silently
  ignored) unless the first one is given.  I hate that.
  
  TLS is off by default.  To enable it, set @tls-creds.  Except setting it
  to the otherwise invalid value "" disables it.  That's because all we
  have for configuration is the "set migration parameter to value"
  interface.  Only works because we have an invalid value we can abuse.  I
  hate that." -- https://lore.kernel.org/all/877cnrjd71.fsf@pond.sub.org/

I still think it might be an improvement to define a new interface that
requires explicitly enabling TLS. IOW, make tls a capability. But I
really don't want to get caught on that right now, one mess at a time.

>>> Incidentally the docs here are wrong - TLS can be used by
>>> only setting @tls-creds. The @tls-authz & @tls-hostname
>>> params are always optional.
>>>
>>>>  # Features:
>>>>  #
>>>>  # @unstable: Members @x-checkpoint-delay and
>>>>  #     @x-vcpu-dirty-limit-period are experimental.
>>>>  #
>>>> +# Since: 10.1
>>>> +##
>>>> +{ 'struct': 'MigrationConfigBase',
>>>> +  'data': { '*announce-initial': 'size',
>>>> +            '*announce-max': 'size',
>>>> +            '*announce-rounds': 'size',
>>>> +            '*announce-step': 'size',
>>>> +            '*throttle-trigger-threshold': 'uint8',
>>>> +            '*cpu-throttle-initial': 'uint8',
>>>> +            '*cpu-throttle-increment': 'uint8',
>>>> +            '*cpu-throttle-tailslow': 'bool',
>>>> +            '*max-bandwidth': 'size',
>>>> +            '*avail-switchover-bandwidth': 'size',
>>>> +            '*downtime-limit': 'uint64',
>>>> +            '*x-checkpoint-delay': { 'type': 'uint32',
>>>> +                                     'features': [ 'unstable' ] },
>>>> +            '*multifd-channels': 'uint8',
>>>> +            '*xbzrle-cache-size': 'size',
>>>> +            '*max-postcopy-bandwidth': 'size',
>>>> +            '*max-cpu-throttle': 'uint8',
>>>> +            '*multifd-compression': 'MultiFDCompression',
>>>> +            '*multifd-zlib-level': 'uint8',
>>>> +            '*multifd-qatzip-level': 'uint8',
>>>> +            '*multifd-zstd-level': 'uint8',
>>>> +            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
>>>> +            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
>>>> +                                            'features': [ 'unstable' ] },
>>>> +            '*vcpu-dirty-limit': 'uint64',
>>>> +            '*mode': 'MigMode',
>>>> +            '*zero-page-detection': 'ZeroPageDetection',
>>>> +            '*direct-io': 'bool',
>>>> +            '*tls': 'bool' } }
>>>
>>>
>>>>  { 'struct': 'MigrationParameters',
>>>> +  'base': 'MigrationConfigBase',
>>>> +  'data': { 'tls-creds': 'str',
>>>> +            'tls-hostname': 'str',
>>>> +            'tls-authz': 'str' } }
>>>
>>> snip
>>>
>>>> +{ 'struct': 'MigrateSetParameters',
>>>> +  'base': 'MigrationConfigBase',
>>>> +  'data': { '*tls-creds': 'StrOrNull',
>>>> +            '*tls-hostname': 'StrOrNull',
>>>> +            '*tls-authz': 'StrOrNull' } }
>>>
>>> I recall we discussed this difference a year or two ago, but can't
>>> recall what the outcome was.
>>>
>>> Making the TLS params optional is a back compatible change for
>>> MigrationParameters. I would think replacing 'str' with 'StrOrNull'
>>> is also back compatible. So I'm wondering if we can't just unify
>>> the sttructs fully for TLS, even if one usage scenario never actually
>>> needs the "OrNull" bit nor needs the optionality 
>>>
>>
>> MigrationParameters is the output type for query-migrate-parameters. I
>> belive it must be all non-optional to keep compatibility. The docs on
>> master say: "The optional members aren't really optional"
>
> To be precise: even though the members are declared optional, they must
> always be present.
>
> The members became optional when commit de63ab61241 (migrate: Share
> common MigrationParameters struct) reused MigrationParameters as
> migrate-set-parameters argument type.
>

Then, commit 1bda8b3c69 (migration: Unshare MigrationParameters struct
for now, 2017-07-18) partially undid that change and introduced
MigrateSetParameters.

At that point we might have been able to make the MigrationParameters'
members mandatory, but it was chosen not too, probably with good
reason. I can't tell from the commit message.

Strictly speaking, we cannot make them mandatory now because commit
31e4c354b3 (migration: Add block-bitmap-mapping parameter, 2020-08-20)
added the block-bitmap parameter, but made its output in
query-migrate-parameters truly optional:

    if (s->parameters.has_block_bitmap_mapping) {
        params->has_block_bitmap_mapping = true;
        params->block_bitmap_mapping =
            QAPI_CLONE(BitmapMigrationNodeAliasList,
                       s->parameters.block_bitmap_mapping);
    }

> Making mandatory members of some type optional is compatible as long as
> the type occurs only in command arguments.  But MigrationParameters is a
> command return type.  Making its members optional was not kosher.
> Because the optional members are always present, QMP didn't actually
> change, except for introspection.
>
>> For MigrateSetParameters they've always been optional and continue to
>> be. There we need to keep StrOrNull for compat.
>
> StrOrNull goes back to commit 01fa5598269 (migration: Use JSON null
> instead of "" to reset parameter to default).
>
> Similar argument: adding values to a member is compatible as long as the
> type occurs only in command arguments.  But MigrationParameters is a
> command return type.  Adding value null is won't be kosher.  However, as
> long as as the value is never actually used there, QMP won't actually
> change, except for introspection.
>
> If this was a clean and obvious interface, I'd argue against such
> trickery and for keeping it clean and obvious.  But the migration
> configuration interface isn't.


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-16 13:44   ` Markus Armbruster
@ 2025-04-16 15:00     ` Fabiano Rosas
  2025-04-24  9:35       ` Markus Armbruster
  0 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-04-16 15:00 UTC (permalink / raw)
  To: Markus Armbruster, Daniel P. Berrangé; +Cc: qemu-devel, Peter Xu

Markus Armbruster <armbru@redhat.com> writes:

> Daniel P. Berrangé <berrange@redhat.com> writes:
>
>> On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
>>> Open questions:
>>> ---------------
>>> 
>>> - Deprecations/compat?
>>> 
>>> I think we should deprecate migrate-set/query-capabilities and everything to do
>>> with capabilities (specifically the validation in the JSON at the end of the
>>> stream).
>>> 
>>> For migrate-set/query-parameters, we could probably keep it around indefinitely,
>>> but it'd be convenient to introduce new commands so we can give them new
>>> semantics.
>>> 
>>> - How to restrict the options that should not be set when the migration is in
>>> progress?
>>> 
>>> i.e.:
>>>   all options can be set before migration (initial config)
>>>   some options can be set during migration (runtime)
>>> 
>>> I thought of adding another type at the top of the hierarchy, with
>>> just the options allowed to change at runtime, but that doesn't really
>>> stop the others being also set at runtime. I'd need a way to have a
>>> set of options that are rejected 'if migration_is_running()', without
>>> adding more duplication all around.
>>> 
>>> - What about savevm?
>>> 
>>> None of this solves the issue of random caps/params being set before
>>> calling savevm. We still need to special-case savevm and reject
>>> everything. Unless we entirely deprecate setting initial options via
>>> set-parameters (or set-config) and require all options to be set as
>>> savevm (and migrate) arguments.
>>
>> I'd suggest we aim for a world where the commands take all options
>> as direct args and try to remove the global state eventually.
>
> Yes.
>
> Even better: make it a job.
>

What do we gain from that in relation to being able to pass ~50
parameters to a command? I don't see it. I think that's the actual crux
here, too many options and how to get them from the QAPI into the
migration core for consumption.

The current usage of MigrationParameters as both the return type for
query-set-parameters and the global parameter store for the migration
state is really dissonant. What do the has_* fields even mean when
accessed via MigrationState::parameters? This series is not doing any
better in that regard, mind you. I'm almost tempted to ask that we
expose the QDict from the marshaling function directly to the migration
code, at least that's a data type that makes sense internally.


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-16 14:41         ` Fabiano Rosas
@ 2025-04-17  5:56           ` Markus Armbruster
  0 siblings, 0 replies; 44+ messages in thread
From: Markus Armbruster @ 2025-04-17  5:56 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: Daniel P. Berrangé, qemu-devel, Peter Xu

Fabiano Rosas <farosas@suse.de> writes:

> Markus Armbruster <armbru@redhat.com> writes:
>
>> Fabiano Rosas <farosas@suse.de> writes:
>>
>>> Daniel P. Berrangé <berrange@redhat.com> writes:
>>>
>>>> On Fri, Apr 11, 2025 at 04:14:35PM -0300, Fabiano Rosas wrote:
>>>>> Introduce a new MigrationConfigBase, to allow most of the duplication
>>>>> in migration.json to be eliminated.
>>>>> 
>>>>> The reason we need MigrationParameters and MigrationSetParameters is
>>>>> that the internal parameter representation in the migration code, as
>>>>> well as the user-facing return of query-migrate-parameters use one
>>>>> type for the TLS options (tls-creds, tls-hostname, tls-authz), while
>>>>> the user-facing input from migrate-set-parameters uses another.
>>>>> 
>>>>> The difference is in whether the NULL values is accepted. The former
>>>>> considers NULL as invalid, while the latter doesn't.
>>>>> 
>>>>> Move all other (non-TLS) options into the new type and make it a base
>>>>> type for the two diverging types so that each child type can declare
>>>>> the TLS options in its own way.
>>>>> 
>>>>> Nothing changes in the user API, nothing changes in the internal
>>>>> representation, but we save several lines of duplication in
>>>>> migration.json.
>>>>> 
>>>>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>>>>> ---
>>>>>  qapi/migration.json | 358 +++++++++++++-------------------------------
>>>>>  1 file changed, 108 insertions(+), 250 deletions(-)
>>>>> 
>>>>> diff --git a/qapi/migration.json b/qapi/migration.json
>>>>> index 8b9c53595c..5a4d5a2d3e 100644
>>>>> --- a/qapi/migration.json
>>>>> +++ b/qapi/migration.json
>>>>> @@ -914,202 +914,6 @@
>>>>
>>>>> @@ -1277,45 +1059,121 @@
>>>>>  #     only has effect if the @mapped-ram capability is enabled.
>>>>>  #     (Since 9.1)
>>>>>  #
>>>>> +# @tls: Whether to use TLS. If this is set the options @tls-authz,
>>>>> +#     @tls-creds, @tls-hostname are mandatory and a valid string is
>>>>> +#     expected. (Since 10.1)
>>>>> +#
>>>>
>>>> I'm not really finding it compelling to add a bool @tls as it
>>>> is just a denormalization of  !!@tls-creds.
>>>>
>>>
>>> This is here by mistake.
>>>
>>> I remember Markus mentioning that implying TLS usage from tls-creds was
>>> undesirable. I was prototyping a way of requiring an explicit opt-in.
>>
>> I don't remember a thing :)
>>
>
> I hope I interpreted you correctly:
>
>   "We have three syntactically independent parameters: @tls-creds,
>   @tls-hostname, @tls-authz.  The latter two are meaningless (and silently
>   ignored) unless the first one is given.  I hate that.
>   
>   TLS is off by default.  To enable it, set @tls-creds.  Except setting it
>   to the otherwise invalid value "" disables it.  That's because all we
>   have for configuration is the "set migration parameter to value"
>   interface.  Only works because we have an invalid value we can abuse.  I
>   hate that." -- https://lore.kernel.org/all/877cnrjd71.fsf@pond.sub.org/

Ah, now I remember!

Possible minimally invasive improvement here:

* Reject @tls-hostname and @tls-authz when the value of @tls-creds means
  "TLS is off".

* When @tls-set-creds get set to a value that means "TLS is off",
  @tls-hostname and @tls-authz get unset.

This could break applications.  I'm not at all sure the slight reduction
in ugliness would be worth the risk.

I do not mean to discourage pursuing your RFC!  The TLS configuration is
ugly and confusing to use internally.  Getting that streamlined would be
nice.

> I still think it might be an improvement to define a new interface that
> requires explicitly enabling TLS. IOW, make tls a capability. But I
> really don't want to get caught on that right now, one mess at a time.

First: one mess at a time is commonly the sensible approach.

Second: I'm not sure attempts to make the existing migration
configuration interfaces somewhat less ugly and confusing are
worthwhile.

I rarely advocate starting over just because something is ugly.
However, migration configuration is not only ugly; its use of global
state feels fundamentally misguided to me.  Evidence: the surprising
side effects on savevm Daniel mentioned.  I'll add: when a management
application connects to an existing QEMU, it needs to laboriously set
the entire global migration configuration state, or else rely on unsound
assumptions.  Complete non-problem when the entire configuration is
passed to the command, like we do elsewhere.

>>>> Incidentally the docs here are wrong - TLS can be used by
>>>> only setting @tls-creds. The @tls-authz & @tls-hostname
>>>> params are always optional.
>>>>
>>>>>  # Features:
>>>>>  #
>>>>>  # @unstable: Members @x-checkpoint-delay and
>>>>>  #     @x-vcpu-dirty-limit-period are experimental.
>>>>>  #
>>>>> +# Since: 10.1
>>>>> +##
>>>>> +{ 'struct': 'MigrationConfigBase',
>>>>> +  'data': { '*announce-initial': 'size',
>>>>> +            '*announce-max': 'size',
>>>>> +            '*announce-rounds': 'size',
>>>>> +            '*announce-step': 'size',
>>>>> +            '*throttle-trigger-threshold': 'uint8',
>>>>> +            '*cpu-throttle-initial': 'uint8',
>>>>> +            '*cpu-throttle-increment': 'uint8',
>>>>> +            '*cpu-throttle-tailslow': 'bool',
>>>>> +            '*max-bandwidth': 'size',
>>>>> +            '*avail-switchover-bandwidth': 'size',
>>>>> +            '*downtime-limit': 'uint64',
>>>>> +            '*x-checkpoint-delay': { 'type': 'uint32',
>>>>> +                                     'features': [ 'unstable' ] },
>>>>> +            '*multifd-channels': 'uint8',
>>>>> +            '*xbzrle-cache-size': 'size',
>>>>> +            '*max-postcopy-bandwidth': 'size',
>>>>> +            '*max-cpu-throttle': 'uint8',
>>>>> +            '*multifd-compression': 'MultiFDCompression',
>>>>> +            '*multifd-zlib-level': 'uint8',
>>>>> +            '*multifd-qatzip-level': 'uint8',
>>>>> +            '*multifd-zstd-level': 'uint8',
>>>>> +            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
>>>>> +            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
>>>>> +                                            'features': [ 'unstable' ] },
>>>>> +            '*vcpu-dirty-limit': 'uint64',
>>>>> +            '*mode': 'MigMode',
>>>>> +            '*zero-page-detection': 'ZeroPageDetection',
>>>>> +            '*direct-io': 'bool',
>>>>> +            '*tls': 'bool' } }
>>>>
>>>>
>>>>>  { 'struct': 'MigrationParameters',
>>>>> +  'base': 'MigrationConfigBase',
>>>>> +  'data': { 'tls-creds': 'str',
>>>>> +            'tls-hostname': 'str',
>>>>> +            'tls-authz': 'str' } }
>>>>
>>>> snip
>>>>
>>>>> +{ 'struct': 'MigrateSetParameters',
>>>>> +  'base': 'MigrationConfigBase',
>>>>> +  'data': { '*tls-creds': 'StrOrNull',
>>>>> +            '*tls-hostname': 'StrOrNull',
>>>>> +            '*tls-authz': 'StrOrNull' } }
>>>>
>>>> I recall we discussed this difference a year or two ago, but can't
>>>> recall what the outcome was.
>>>>
>>>> Making the TLS params optional is a back compatible change for
>>>> MigrationParameters. I would think replacing 'str' with 'StrOrNull'
>>>> is also back compatible. So I'm wondering if we can't just unify
>>>> the sttructs fully for TLS, even if one usage scenario never actually
>>>> needs the "OrNull" bit nor needs the optionality 
>>>>
>>>
>>> MigrationParameters is the output type for query-migrate-parameters. I
>>> belive it must be all non-optional to keep compatibility. The docs on
>>> master say: "The optional members aren't really optional"
>>
>> To be precise: even though the members are declared optional, they must
>> always be present.
>>
>> The members became optional when commit de63ab61241 (migrate: Share
>> common MigrationParameters struct) reused MigrationParameters as
>> migrate-set-parameters argument type.
>>
>
> Then, commit 1bda8b3c69 (migration: Unshare MigrationParameters struct
> for now, 2017-07-18) partially undid that change and introduced
> MigrateSetParameters.
>
> At that point we might have been able to make the MigrationParameters'
> members mandatory, but it was chosen not too, probably with good
> reason. I can't tell from the commit message.

As far as I remember: to minimize last minute churn, and to not
complicate a future re-sharing.

> Strictly speaking, we cannot make them mandatory now because commit
> 31e4c354b3 (migration: Add block-bitmap-mapping parameter, 2020-08-20)
> added the block-bitmap parameter, but made its output in
> query-migrate-parameters truly optional:
>
>     if (s->parameters.has_block_bitmap_mapping) {
>         params->has_block_bitmap_mapping = true;
>         params->block_bitmap_mapping =
>             QAPI_CLONE(BitmapMigrationNodeAliasList,
>                        s->parameters.block_bitmap_mapping);
>     }

We're consistently inconsistent ;)

>> Making mandatory members of some type optional is compatible as long as
>> the type occurs only in command arguments.  But MigrationParameters is a
>> command return type.  Making its members optional was not kosher.
>> Because the optional members are always present, QMP didn't actually
>> change, except for introspection.
>>
>>> For MigrateSetParameters they've always been optional and continue to
>>> be. There we need to keep StrOrNull for compat.
>>
>> StrOrNull goes back to commit 01fa5598269 (migration: Use JSON null
>> instead of "" to reset parameter to default).
>>
>> Similar argument: adding values to a member is compatible as long as the
>> type occurs only in command arguments.  But MigrationParameters is a
>> command return type.  Adding value null is won't be kosher.  However, as
>> long as as the value is never actually used there, QMP won't actually
>> change, except for introspection.
>>
>> If this was a clean and obvious interface, I'd argue against such
>> trickery and for keeping it clean and obvious.  But the migration
>> configuration interface isn't.



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-11 19:14 ` [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json Fabiano Rosas
  2025-04-14 16:38   ` Daniel P. Berrangé
@ 2025-04-17 18:45   ` Markus Armbruster
  2025-04-18  6:40     ` Markus Armbruster
  1 sibling, 1 reply; 44+ messages in thread
From: Markus Armbruster @ 2025-04-17 18:45 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Daniel P . Berrangé

Fabiano Rosas <farosas@suse.de> writes:

> Introduce a new MigrationConfigBase, to allow most of the duplication
> in migration.json to be eliminated.
>
> The reason we need MigrationParameters and MigrationSetParameters is
> that the internal parameter representation in the migration code, as
> well as the user-facing return of query-migrate-parameters use one
> type for the TLS options (tls-creds, tls-hostname, tls-authz), while
> the user-facing input from migrate-set-parameters uses another.
>
> The difference is in whether the NULL values is accepted. The former
> considers NULL as invalid, while the latter doesn't.
>
> Move all other (non-TLS) options into the new type and make it a base
> type for the two diverging types so that each child type can declare
> the TLS options in its own way.
>
> Nothing changes in the user API, nothing changes in the internal
> representation, but we save several lines of duplication in
> migration.json.

I double-checked the external interface.  There's a new member @tls, but
you already told us it's an accident.

Documentation does change.  More on that below.

> Signed-off-by: Fabiano Rosas <farosas@suse.de>
> ---
>  qapi/migration.json | 358 +++++++++++++-------------------------------
>  1 file changed, 108 insertions(+), 250 deletions(-)
>
> diff --git a/qapi/migration.json b/qapi/migration.json
> index 8b9c53595c..5a4d5a2d3e 100644
> --- a/qapi/migration.json
> +++ b/qapi/migration.json
> @@ -914,202 +914,6 @@
>             'zero-page-detection',
>             'direct-io'] }
>  
> -##
> -# @MigrateSetParameters:
> -#
> -# @announce-initial: Initial delay (in milliseconds) before sending
> -#     the first announce (Since 4.0)
> -#
> -# @announce-max: Maximum delay (in milliseconds) between packets in
> -#     the announcement (Since 4.0)
> -#
> -# @announce-rounds: Number of self-announce packets sent after
> -#     migration (Since 4.0)
> -#
> -# @announce-step: Increase in delay (in milliseconds) between
> -#     subsequent packets in the announcement (Since 4.0)
> -#
> -# @throttle-trigger-threshold: The ratio of bytes_dirty_period and
> -#     bytes_xfer_period to trigger throttling.  It is expressed as
> -#     percentage.  The default value is 50.  (Since 5.0)
> -#
> -# @cpu-throttle-initial: Initial percentage of time guest cpus are
> -#     throttled when migration auto-converge is activated.  The
> -#     default value is 20.  (Since 2.7)
> -#
> -# @cpu-throttle-increment: throttle percentage increase each time
> -#     auto-converge detects that migration is not making progress.
> -#     The default value is 10.  (Since 2.7)
> -#
> -# @cpu-throttle-tailslow: Make CPU throttling slower at tail stage At
> -#     the tail stage of throttling, the Guest is very sensitive to CPU
> -#     percentage while the @cpu-throttle -increment is excessive
> -#     usually at tail stage.  If this parameter is true, we will
> -#     compute the ideal CPU percentage used by the Guest, which may
> -#     exactly make the dirty rate match the dirty rate threshold.
> -#     Then we will choose a smaller throttle increment between the one
> -#     specified by @cpu-throttle-increment and the one generated by
> -#     ideal CPU percentage.  Therefore, it is compatible to
> -#     traditional throttling, meanwhile the throttle increment won't
> -#     be excessive at tail stage.  The default value is false.  (Since
> -#     5.1)
> -#
> -# @tls-creds: ID of the 'tls-creds' object that provides credentials
> -#     for establishing a TLS connection over the migration data
> -#     channel.  On the outgoing side of the migration, the credentials
> -#     must be for a 'client' endpoint, while for the incoming side the
> -#     credentials must be for a 'server' endpoint.  Setting this to a
> -#     non-empty string enables TLS for all migrations.  An empty
> -#     string means that QEMU will use plain text mode for migration,
> -#     rather than TLS.  This is the default.  (Since 2.7)
> -#
> -# @tls-hostname: migration target's hostname for validating the
> -#     server's x509 certificate identity.  If empty, QEMU will use the
> -#     hostname from the migration URI, if any.  A non-empty value is
> -#     required when using x509 based TLS credentials and the migration
> -#     URI does not include a hostname, such as fd: or exec: based
> -#     migration.  (Since 2.7)
> -#
> -#     Note: empty value works only since 2.9.
> -#
> -# @tls-authz: ID of the 'authz' object subclass that provides access
> -#     control checking of the TLS x509 certificate distinguished name.
> -#     This object is only resolved at time of use, so can be deleted
> -#     and recreated on the fly while the migration server is active.
> -#     If missing, it will default to denying access (Since 4.0)
> -#
> -# @max-bandwidth: maximum speed for migration, in bytes per second.
> -#     (Since 2.8)
> -#
> -# @avail-switchover-bandwidth: to set the available bandwidth that
> -#     migration can use during switchover phase.  NOTE!  This does not
> -#     limit the bandwidth during switchover, but only for calculations
> -#     when making decisions to switchover.  By default, this value is
> -#     zero, which means QEMU will estimate the bandwidth
> -#     automatically.  This can be set when the estimated value is not
> -#     accurate, while the user is able to guarantee such bandwidth is
> -#     available when switching over.  When specified correctly, this
> -#     can make the switchover decision much more accurate.
> -#     (Since 8.2)
> -#
> -# @downtime-limit: set maximum tolerated downtime for migration.
> -#     maximum downtime in milliseconds (Since 2.8)
> -#
> -# @x-checkpoint-delay: The delay time (in ms) between two COLO
> -#     checkpoints in periodic mode.  (Since 2.8)
> -#
> -# @multifd-channels: Number of channels used to migrate data in
> -#     parallel.  This is the same number that the number of sockets
> -#     used for migration.  The default value is 2 (since 4.0)
> -#
> -# @xbzrle-cache-size: cache size to be used by XBZRLE migration.  It
> -#     needs to be a multiple of the target page size and a power of 2
> -#     (Since 2.11)
> -#
> -# @max-postcopy-bandwidth: Background transfer bandwidth during
> -#     postcopy.  Defaults to 0 (unlimited).  In bytes per second.
> -#     (Since 3.0)
> -#
> -# @max-cpu-throttle: maximum cpu throttle percentage.  Defaults to 99.
> -#     (Since 3.1)
> -#
> -# @multifd-compression: Which compression method to use.  Defaults to
> -#     none.  (Since 5.0)
> -#
> -# @multifd-zlib-level: Set the compression level to be used in live
> -#     migration, the compression level is an integer between 0 and 9,
> -#     where 0 means no compression, 1 means the best compression
> -#     speed, and 9 means best compression ratio which will consume
> -#     more CPU.  Defaults to 1.  (Since 5.0)
> -#
> -# @multifd-qatzip-level: Set the compression level to be used in live
> -#     migration. The level is an integer between 1 and 9, where 1 means
> -#     the best compression speed, and 9 means the best compression
> -#     ratio which will consume more CPU. Defaults to 1.  (Since 9.2)
> -#
> -# @multifd-zstd-level: Set the compression level to be used in live
> -#     migration, the compression level is an integer between 0 and 20,
> -#     where 0 means no compression, 1 means the best compression
> -#     speed, and 20 means best compression ratio which will consume
> -#     more CPU.  Defaults to 1.  (Since 5.0)
> -#
> -# @block-bitmap-mapping: Maps block nodes and bitmaps on them to
> -#     aliases for the purpose of dirty bitmap migration.  Such aliases
> -#     may for example be the corresponding names on the opposite site.
> -#     The mapping must be one-to-one, but not necessarily complete: On
> -#     the source, unmapped bitmaps and all bitmaps on unmapped nodes
> -#     will be ignored.  On the destination, encountering an unmapped
> -#     alias in the incoming migration stream will result in a report,
> -#     and all further bitmap migration data will then be discarded.
> -#     Note that the destination does not know about bitmaps it does
> -#     not receive, so there is no limitation or requirement regarding
> -#     the number of bitmaps received, or how they are named, or on
> -#     which nodes they are placed.  By default (when this parameter
> -#     has never been set), bitmap names are mapped to themselves.
> -#     Nodes are mapped to their block device name if there is one, and
> -#     to their node name otherwise.  (Since 5.2)
> -#
> -# @x-vcpu-dirty-limit-period: Periodic time (in milliseconds) of dirty
> -#     limit during live migration.  Should be in the range 1 to
> -#     1000ms.  Defaults to 1000ms.  (Since 8.1)
> -#
> -# @vcpu-dirty-limit: Dirtyrate limit (MB/s) during live migration.
> -#     Defaults to 1.  (Since 8.1)
> -#
> -# @mode: Migration mode.  See description in @MigMode.  Default is
> -#     'normal'.  (Since 8.2)
> -#
> -# @zero-page-detection: Whether and how to detect zero pages.
> -#     See description in @ZeroPageDetection.  Default is 'multifd'.
> -#     (since 9.0)
> -#
> -# @direct-io: Open migration files with O_DIRECT when possible.  This
> -#     only has effect if the @mapped-ram capability is enabled.
> -#     (Since 9.1)
> -#
> -# Features:
> -#
> -# @unstable: Members @x-checkpoint-delay and
> -#     @x-vcpu-dirty-limit-period are experimental.
> -#
> -# TODO: either fuse back into MigrationParameters, or make
> -#     MigrationParameters members mandatory
> -#
> -# Since: 2.4
> -##
> -{ 'struct': 'MigrateSetParameters',
> -  'data': { '*announce-initial': 'size',
> -            '*announce-max': 'size',
> -            '*announce-rounds': 'size',
> -            '*announce-step': 'size',
> -            '*throttle-trigger-threshold': 'uint8',
> -            '*cpu-throttle-initial': 'uint8',
> -            '*cpu-throttle-increment': 'uint8',
> -            '*cpu-throttle-tailslow': 'bool',
> -            '*tls-creds': 'StrOrNull',
> -            '*tls-hostname': 'StrOrNull',
> -            '*tls-authz': 'StrOrNull',
> -            '*max-bandwidth': 'size',
> -            '*avail-switchover-bandwidth': 'size',
> -            '*downtime-limit': 'uint64',
> -            '*x-checkpoint-delay': { 'type': 'uint32',
> -                                     'features': [ 'unstable' ] },
> -            '*multifd-channels': 'uint8',
> -            '*xbzrle-cache-size': 'size',
> -            '*max-postcopy-bandwidth': 'size',
> -            '*max-cpu-throttle': 'uint8',
> -            '*multifd-compression': 'MultiFDCompression',
> -            '*multifd-zlib-level': 'uint8',
> -            '*multifd-qatzip-level': 'uint8',
> -            '*multifd-zstd-level': 'uint8',
> -            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
> -            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
> -                                            'features': [ 'unstable' ] },
> -            '*vcpu-dirty-limit': 'uint64',
> -            '*mode': 'MigMode',
> -            '*zero-page-detection': 'ZeroPageDetection',
> -            '*direct-io': 'bool' } }
> -
>  ##
>  # @migrate-set-parameters:
>  #
> @@ -1127,9 +931,7 @@
>    'data': 'MigrateSetParameters' }
>  
>  ##
> -# @MigrationParameters:
> -#
> -# The optional members aren't actually optional.
> +# @MigrationConfigBase:
>  #
>  # @announce-initial: Initial delay (in milliseconds) before sending
>  #     the first announce (Since 4.0)
> @@ -1168,26 +970,6 @@
>  #     be excessive at tail stage.  The default value is false.  (Since
>  #     5.1)
>  #
> -# @tls-creds: ID of the 'tls-creds' object that provides credentials
> -#     for establishing a TLS connection over the migration data
> -#     channel.  On the outgoing side of the migration, the credentials
> -#     must be for a 'client' endpoint, while for the incoming side the
> -#     credentials must be for a 'server' endpoint.  An empty string
> -#     means that QEMU will use plain text mode for migration, rather
> -#     than TLS.  (Since 2.7)
> -#
> -#     Note: 2.8 omits empty @tls-creds instead.
> -#
> -# @tls-hostname: migration target's hostname for validating the
> -#     server's x509 certificate identity.  If empty, QEMU will use the
> -#     hostname from the migration URI, if any.  (Since 2.7)
> -#
> -#     Note: 2.8 omits empty @tls-hostname instead.
> -#
> -# @tls-authz: ID of the 'authz' object subclass that provides access
> -#     control checking of the TLS x509 certificate distinguished name.
> -#     (Since 4.0)
> -#
>  # @max-bandwidth: maximum speed for migration, in bytes per second.
>  #     (Since 2.8)
>  #
> @@ -1277,45 +1059,121 @@
>  #     only has effect if the @mapped-ram capability is enabled.
>  #     (Since 9.1)
>  #
> +# @tls: Whether to use TLS. If this is set the options @tls-authz,
> +#     @tls-creds, @tls-hostname are mandatory and a valid string is
> +#     expected. (Since 10.1)
> +#
>  # Features:
>  #
>  # @unstable: Members @x-checkpoint-delay and
>  #     @x-vcpu-dirty-limit-period are experimental.
>  #
> +# Since: 10.1
> +##
> +{ 'struct': 'MigrationConfigBase',
> +  'data': { '*announce-initial': 'size',
> +            '*announce-max': 'size',
> +            '*announce-rounds': 'size',
> +            '*announce-step': 'size',
> +            '*throttle-trigger-threshold': 'uint8',
> +            '*cpu-throttle-initial': 'uint8',
> +            '*cpu-throttle-increment': 'uint8',
> +            '*cpu-throttle-tailslow': 'bool',
> +            '*max-bandwidth': 'size',
> +            '*avail-switchover-bandwidth': 'size',
> +            '*downtime-limit': 'uint64',
> +            '*x-checkpoint-delay': { 'type': 'uint32',
> +                                     'features': [ 'unstable' ] },
> +            '*multifd-channels': 'uint8',
> +            '*xbzrle-cache-size': 'size',
> +            '*max-postcopy-bandwidth': 'size',
> +            '*max-cpu-throttle': 'uint8',
> +            '*multifd-compression': 'MultiFDCompression',
> +            '*multifd-zlib-level': 'uint8',
> +            '*multifd-qatzip-level': 'uint8',
> +            '*multifd-zstd-level': 'uint8',
> +            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
> +            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
> +                                            'features': [ 'unstable' ] },
> +            '*vcpu-dirty-limit': 'uint64',
> +            '*mode': 'MigMode',
> +            '*zero-page-detection': 'ZeroPageDetection',
> +            '*direct-io': 'bool',
> +            '*tls': 'bool' } }
> +
> +##
> +# @MigrationParameters:
> +#
> +# The optional members of the base type aren't actually optional.
> +#
> +# @tls-creds: ID of the 'tls-creds' object that provides credentials
> +#     for establishing a TLS connection over the migration data
> +#     channel.  On the outgoing side of the migration, the credentials
> +#     must be for a 'client' endpoint, while for the incoming side the
> +#     credentials must be for a 'server' endpoint.  Setting this to a
> +#     non-empty string enables TLS for all migrations.  An empty
> +#     string means that QEMU will use plain text mode for migration,
> +#     rather than TLS.  (Since 2.7)
> +#
> +# @tls-hostname: migration target's hostname for validating the
> +#     server's x509 certificate identity.  If empty, QEMU will use the
> +#     hostname from the migration URI, if any.  A non-empty value is
> +#     required when using x509 based TLS credentials and the migration
> +#     URI does not include a hostname, such as fd: or exec: based
> +#     migration.  (Since 2.7)
> +#
> +#     Note: empty value works only since 2.9.
> +#
> +# @tls-authz: ID of the 'authz' object subclass that provides access
> +#     control checking of the TLS x509 certificate distinguished name.
> +#     This object is only resolved at time of use, so can be deleted
> +#     and recreated on the fly while the migration server is active.
> +#     If missing, it will default to denying access (Since 4.0)
> +#
>  # Since: 2.4
>  ##
>  { 'struct': 'MigrationParameters',
> -  'data': { '*announce-initial': 'size',
> -            '*announce-max': 'size',
> -            '*announce-rounds': 'size',
> -            '*announce-step': 'size',
> -            '*throttle-trigger-threshold': 'uint8',
> -            '*cpu-throttle-initial': 'uint8',
> -            '*cpu-throttle-increment': 'uint8',
> -            '*cpu-throttle-tailslow': 'bool',
> -            '*tls-creds': 'str',
> -            '*tls-hostname': 'str',
> -            '*tls-authz': 'str',
> -            '*max-bandwidth': 'size',
> -            '*avail-switchover-bandwidth': 'size',
> -            '*downtime-limit': 'uint64',
> -            '*x-checkpoint-delay': { 'type': 'uint32',
> -                                     'features': [ 'unstable' ] },
> -            '*multifd-channels': 'uint8',
> -            '*xbzrle-cache-size': 'size',
> -            '*max-postcopy-bandwidth': 'size',
> -            '*max-cpu-throttle': 'uint8',
> -            '*multifd-compression': 'MultiFDCompression',
> -            '*multifd-zlib-level': 'uint8',
> -            '*multifd-qatzip-level': 'uint8',
> -            '*multifd-zstd-level': 'uint8',
> -            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
> -            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
> -                                            'features': [ 'unstable' ] },
> -            '*vcpu-dirty-limit': 'uint64',
> -            '*mode': 'MigMode',
> -            '*zero-page-detection': 'ZeroPageDetection',
> -            '*direct-io': 'bool' } }
> +  'base': 'MigrationConfigBase',
> +  'data': { 'tls-creds': 'str',
> +            'tls-hostname': 'str',
> +            'tls-authz': 'str' } }
> +
> +##
> +# @MigrateSetParameters:
> +#
> +# Compatibility layer to accept null values for the TLS options.
> +#
> +# @tls-creds: ID of the 'tls-creds' object that provides credentials
> +#     for establishing a TLS connection over the migration data
> +#     channel.  On the outgoing side of the migration, the credentials
> +#     must be for a 'client' endpoint, while for the incoming side the
> +#     credentials must be for a 'server' endpoint.  Setting this to a
> +#     non-empty string enables TLS for all migrations.  An empty
> +#     string means that QEMU will use plain text mode for migration,
> +#     rather than TLS.  This is the default.  (Since 2.7)
> +#
> +# @tls-hostname: migration target's hostname for validating the
> +#     server's x509 certificate identity.  If empty, QEMU will use the
> +#     hostname from the migration URI, if any.  A non-empty value is
> +#     required when using x509 based TLS credentials and the migration
> +#     URI does not include a hostname, such as fd: or exec: based
> +#     migration.  (Since 2.7)
> +#
> +#     Note: empty value works only since 2.9.
> +#
> +# @tls-authz: ID of the 'authz' object subclass that provides access
> +#     control checking of the TLS x509 certificate distinguished name.
> +#     This object is only resolved at time of use, so can be deleted
> +#     and recreated on the fly while the migration server is active.
> +#     If missing, it will default to denying access (Since 4.0)
> +#
> +# Since: 2.4
> +##
> +{ 'struct': 'MigrateSetParameters',
> +  'base': 'MigrationConfigBase',
> +  'data': { '*tls-creds': 'StrOrNull',
> +            '*tls-hostname': 'StrOrNull',
> +            '*tls-authz': 'StrOrNull' } }
>  
>  ##
>  # @query-migrate-parameters:

Your change is fairly simple, but that's not obvious from the diff.

1. Move everything but the TLS stuff from MigrationParameters to its new
   base type MigrationConfigBase.

   No change to the interface, obviously.

   Introspection hides the base type, and shows members of
   MigrationParameters in a different order, but order doesn't matter.

   The doc generator isn't smart enough (yet) to hide the base type.
   Apart from that, documentation is unchanged.

2. Replace everything but the TLS stuff from MigrateSetParameters by
   base type MigrateSetParameters.

   Because the base type's members are identical to the members it
   replaces, no change to the interface.

   Introspection hides the base type, and shows members of
   MigrateSetParameters in a different order, but order doesn't matter.

   The base type's member documentation is *not* identical to the member
   documentation it replaces.  To see the differences, I ran
   sphinx-build with -b text before and after, and fed its output to
   diff:

       * **cpu-throttle-initial** ("int", *optional*) -- Initial
         percentage of time guest cpus are throttled when migration
-        auto-converge is activated.  The default value is 20.  (Since
-        2.7)
+        auto-converge is activated.  (Since 2.7)

   We no longer document the default value.  This is wrong.

   The difference before the patch makes some sense.  The members of
   MigrateSetParameters are actually optional command arguments, and
   their defaults must be documented.  The members of
   MigrationParameters aren't actually optional, and talking about
   defaults is misleading there.  We do it anyway in a few places.

   Factoring out MigrationConfigBase leads to a conflict: in its role as
   base of MigrationParameters, it shouldn't talk about defaults, and in
   its role as base of MigrateSetParameters, it should document the
   defaults supplied by migrate-set-parameters.

   There is no perfectly satisfactory solution with our current
   infrastructure.

   We can elect to ignore the problem for now.  Point it out in a TODO
   comment.

   We can try to explain in the doc text that default values apply only
   to migrate-set-parameters.  I expect this to be awkward.  More
   awkward once the doc generator inlines stuff.

       * **cpu-throttle-increment** ("int", *optional*) -- throttle
         percentage increase each time auto-converge detects that
-        migration is not making progress. The default value is 10.
-        (Since 2.7)
+        migration is not making progress. (Since 2.7)
 
    Likewise.
 
-      * **x-checkpoint-delay** ("int", *optional*) -- The delay time
-        (in ms) between two COLO checkpoints in periodic mode.  (Since
-        2.8)
+      * **x-checkpoint-delay** ("int", *optional*) -- the delay time
+        between two COLO checkpoints. (Since 2.8)
 
    Inconsistent before the patch.  The deleted version is better.  Easy
    enough to fix.
 
+      * **tls** ("boolean", *optional*) -- Whether to use TLS. If this
+        is set the options "tls-authz", "tls-creds", "tls-hostname"
+        are mandatory and a valid string is expected. (Since 10.1)
+

    Accident.



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json
  2025-04-17 18:45   ` Markus Armbruster
@ 2025-04-18  6:40     ` Markus Armbruster
  0 siblings, 0 replies; 44+ messages in thread
From: Markus Armbruster @ 2025-04-18  6:40 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Fabiano Rosas, qemu-devel, Peter Xu, Daniel P . Berrangé

Markus Armbruster <armbru@redhat.com> writes:

> Fabiano Rosas <farosas@suse.de> writes:
>
>> Introduce a new MigrationConfigBase, to allow most of the duplication
>> in migration.json to be eliminated.
>>
>> The reason we need MigrationParameters and MigrationSetParameters is
>> that the internal parameter representation in the migration code, as
>> well as the user-facing return of query-migrate-parameters use one
>> type for the TLS options (tls-creds, tls-hostname, tls-authz), while
>> the user-facing input from migrate-set-parameters uses another.
>>
>> The difference is in whether the NULL values is accepted. The former
>> considers NULL as invalid, while the latter doesn't.
>>
>> Move all other (non-TLS) options into the new type and make it a base
>> type for the two diverging types so that each child type can declare
>> the TLS options in its own way.
>>
>> Nothing changes in the user API, nothing changes in the internal
>> representation, but we save several lines of duplication in
>> migration.json.
>
> I double-checked the external interface.  There's a new member @tls, but
> you already told us it's an accident.
>
> Documentation does change.  More on that below.
>
>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>> ---
>>  qapi/migration.json | 358 +++++++++++++-------------------------------
>>  1 file changed, 108 insertions(+), 250 deletions(-)
>>
>> diff --git a/qapi/migration.json b/qapi/migration.json
>> index 8b9c53595c..5a4d5a2d3e 100644
>> --- a/qapi/migration.json
>> +++ b/qapi/migration.json
>> @@ -914,202 +914,6 @@
>>             'zero-page-detection',
>>             'direct-io'] }
>>  
>> -##
>> -# @MigrateSetParameters:
>> -#
>> -# @announce-initial: Initial delay (in milliseconds) before sending
>> -#     the first announce (Since 4.0)
>> -#
>> -# @announce-max: Maximum delay (in milliseconds) between packets in
>> -#     the announcement (Since 4.0)
>> -#
>> -# @announce-rounds: Number of self-announce packets sent after
>> -#     migration (Since 4.0)
>> -#
>> -# @announce-step: Increase in delay (in milliseconds) between
>> -#     subsequent packets in the announcement (Since 4.0)
>> -#
>> -# @throttle-trigger-threshold: The ratio of bytes_dirty_period and
>> -#     bytes_xfer_period to trigger throttling.  It is expressed as
>> -#     percentage.  The default value is 50.  (Since 5.0)
>> -#
>> -# @cpu-throttle-initial: Initial percentage of time guest cpus are
>> -#     throttled when migration auto-converge is activated.  The
>> -#     default value is 20.  (Since 2.7)
>> -#
>> -# @cpu-throttle-increment: throttle percentage increase each time
>> -#     auto-converge detects that migration is not making progress.
>> -#     The default value is 10.  (Since 2.7)
>> -#
>> -# @cpu-throttle-tailslow: Make CPU throttling slower at tail stage At
>> -#     the tail stage of throttling, the Guest is very sensitive to CPU
>> -#     percentage while the @cpu-throttle -increment is excessive
>> -#     usually at tail stage.  If this parameter is true, we will
>> -#     compute the ideal CPU percentage used by the Guest, which may
>> -#     exactly make the dirty rate match the dirty rate threshold.
>> -#     Then we will choose a smaller throttle increment between the one
>> -#     specified by @cpu-throttle-increment and the one generated by
>> -#     ideal CPU percentage.  Therefore, it is compatible to
>> -#     traditional throttling, meanwhile the throttle increment won't
>> -#     be excessive at tail stage.  The default value is false.  (Since
>> -#     5.1)
>> -#
>> -# @tls-creds: ID of the 'tls-creds' object that provides credentials
>> -#     for establishing a TLS connection over the migration data
>> -#     channel.  On the outgoing side of the migration, the credentials
>> -#     must be for a 'client' endpoint, while for the incoming side the
>> -#     credentials must be for a 'server' endpoint.  Setting this to a
>> -#     non-empty string enables TLS for all migrations.  An empty
>> -#     string means that QEMU will use plain text mode for migration,
>> -#     rather than TLS.  This is the default.  (Since 2.7)
>> -#
>> -# @tls-hostname: migration target's hostname for validating the
>> -#     server's x509 certificate identity.  If empty, QEMU will use the
>> -#     hostname from the migration URI, if any.  A non-empty value is
>> -#     required when using x509 based TLS credentials and the migration
>> -#     URI does not include a hostname, such as fd: or exec: based
>> -#     migration.  (Since 2.7)
>> -#
>> -#     Note: empty value works only since 2.9.
>> -#
>> -# @tls-authz: ID of the 'authz' object subclass that provides access
>> -#     control checking of the TLS x509 certificate distinguished name.
>> -#     This object is only resolved at time of use, so can be deleted
>> -#     and recreated on the fly while the migration server is active.
>> -#     If missing, it will default to denying access (Since 4.0)
>> -#
>> -# @max-bandwidth: maximum speed for migration, in bytes per second.
>> -#     (Since 2.8)
>> -#
>> -# @avail-switchover-bandwidth: to set the available bandwidth that
>> -#     migration can use during switchover phase.  NOTE!  This does not
>> -#     limit the bandwidth during switchover, but only for calculations
>> -#     when making decisions to switchover.  By default, this value is
>> -#     zero, which means QEMU will estimate the bandwidth
>> -#     automatically.  This can be set when the estimated value is not
>> -#     accurate, while the user is able to guarantee such bandwidth is
>> -#     available when switching over.  When specified correctly, this
>> -#     can make the switchover decision much more accurate.
>> -#     (Since 8.2)
>> -#
>> -# @downtime-limit: set maximum tolerated downtime for migration.
>> -#     maximum downtime in milliseconds (Since 2.8)
>> -#
>> -# @x-checkpoint-delay: The delay time (in ms) between two COLO
>> -#     checkpoints in periodic mode.  (Since 2.8)
>> -#
>> -# @multifd-channels: Number of channels used to migrate data in
>> -#     parallel.  This is the same number that the number of sockets
>> -#     used for migration.  The default value is 2 (since 4.0)
>> -#
>> -# @xbzrle-cache-size: cache size to be used by XBZRLE migration.  It
>> -#     needs to be a multiple of the target page size and a power of 2
>> -#     (Since 2.11)
>> -#
>> -# @max-postcopy-bandwidth: Background transfer bandwidth during
>> -#     postcopy.  Defaults to 0 (unlimited).  In bytes per second.
>> -#     (Since 3.0)
>> -#
>> -# @max-cpu-throttle: maximum cpu throttle percentage.  Defaults to 99.
>> -#     (Since 3.1)
>> -#
>> -# @multifd-compression: Which compression method to use.  Defaults to
>> -#     none.  (Since 5.0)
>> -#
>> -# @multifd-zlib-level: Set the compression level to be used in live
>> -#     migration, the compression level is an integer between 0 and 9,
>> -#     where 0 means no compression, 1 means the best compression
>> -#     speed, and 9 means best compression ratio which will consume
>> -#     more CPU.  Defaults to 1.  (Since 5.0)
>> -#
>> -# @multifd-qatzip-level: Set the compression level to be used in live
>> -#     migration. The level is an integer between 1 and 9, where 1 means
>> -#     the best compression speed, and 9 means the best compression
>> -#     ratio which will consume more CPU. Defaults to 1.  (Since 9.2)
>> -#
>> -# @multifd-zstd-level: Set the compression level to be used in live
>> -#     migration, the compression level is an integer between 0 and 20,
>> -#     where 0 means no compression, 1 means the best compression
>> -#     speed, and 20 means best compression ratio which will consume
>> -#     more CPU.  Defaults to 1.  (Since 5.0)
>> -#
>> -# @block-bitmap-mapping: Maps block nodes and bitmaps on them to
>> -#     aliases for the purpose of dirty bitmap migration.  Such aliases
>> -#     may for example be the corresponding names on the opposite site.
>> -#     The mapping must be one-to-one, but not necessarily complete: On
>> -#     the source, unmapped bitmaps and all bitmaps on unmapped nodes
>> -#     will be ignored.  On the destination, encountering an unmapped
>> -#     alias in the incoming migration stream will result in a report,
>> -#     and all further bitmap migration data will then be discarded.
>> -#     Note that the destination does not know about bitmaps it does
>> -#     not receive, so there is no limitation or requirement regarding
>> -#     the number of bitmaps received, or how they are named, or on
>> -#     which nodes they are placed.  By default (when this parameter
>> -#     has never been set), bitmap names are mapped to themselves.
>> -#     Nodes are mapped to their block device name if there is one, and
>> -#     to their node name otherwise.  (Since 5.2)
>> -#
>> -# @x-vcpu-dirty-limit-period: Periodic time (in milliseconds) of dirty
>> -#     limit during live migration.  Should be in the range 1 to
>> -#     1000ms.  Defaults to 1000ms.  (Since 8.1)
>> -#
>> -# @vcpu-dirty-limit: Dirtyrate limit (MB/s) during live migration.
>> -#     Defaults to 1.  (Since 8.1)
>> -#
>> -# @mode: Migration mode.  See description in @MigMode.  Default is
>> -#     'normal'.  (Since 8.2)
>> -#
>> -# @zero-page-detection: Whether and how to detect zero pages.
>> -#     See description in @ZeroPageDetection.  Default is 'multifd'.
>> -#     (since 9.0)
>> -#
>> -# @direct-io: Open migration files with O_DIRECT when possible.  This
>> -#     only has effect if the @mapped-ram capability is enabled.
>> -#     (Since 9.1)
>> -#
>> -# Features:
>> -#
>> -# @unstable: Members @x-checkpoint-delay and
>> -#     @x-vcpu-dirty-limit-period are experimental.
>> -#
>> -# TODO: either fuse back into MigrationParameters, or make
>> -#     MigrationParameters members mandatory
>> -#
>> -# Since: 2.4
>> -##
>> -{ 'struct': 'MigrateSetParameters',
>> -  'data': { '*announce-initial': 'size',
>> -            '*announce-max': 'size',
>> -            '*announce-rounds': 'size',
>> -            '*announce-step': 'size',
>> -            '*throttle-trigger-threshold': 'uint8',
>> -            '*cpu-throttle-initial': 'uint8',
>> -            '*cpu-throttle-increment': 'uint8',
>> -            '*cpu-throttle-tailslow': 'bool',
>> -            '*tls-creds': 'StrOrNull',
>> -            '*tls-hostname': 'StrOrNull',
>> -            '*tls-authz': 'StrOrNull',
>> -            '*max-bandwidth': 'size',
>> -            '*avail-switchover-bandwidth': 'size',
>> -            '*downtime-limit': 'uint64',
>> -            '*x-checkpoint-delay': { 'type': 'uint32',
>> -                                     'features': [ 'unstable' ] },
>> -            '*multifd-channels': 'uint8',
>> -            '*xbzrle-cache-size': 'size',
>> -            '*max-postcopy-bandwidth': 'size',
>> -            '*max-cpu-throttle': 'uint8',
>> -            '*multifd-compression': 'MultiFDCompression',
>> -            '*multifd-zlib-level': 'uint8',
>> -            '*multifd-qatzip-level': 'uint8',
>> -            '*multifd-zstd-level': 'uint8',
>> -            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
>> -            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
>> -                                            'features': [ 'unstable' ] },
>> -            '*vcpu-dirty-limit': 'uint64',
>> -            '*mode': 'MigMode',
>> -            '*zero-page-detection': 'ZeroPageDetection',
>> -            '*direct-io': 'bool' } }
>> -
>>  ##
>>  # @migrate-set-parameters:
>>  #
>> @@ -1127,9 +931,7 @@
>>    'data': 'MigrateSetParameters' }
>>  
>>  ##
>> -# @MigrationParameters:
>> -#
>> -# The optional members aren't actually optional.
>> +# @MigrationConfigBase:
>>  #
>>  # @announce-initial: Initial delay (in milliseconds) before sending
>>  #     the first announce (Since 4.0)
>> @@ -1168,26 +970,6 @@
>>  #     be excessive at tail stage.  The default value is false.  (Since
>>  #     5.1)
>>  #
>> -# @tls-creds: ID of the 'tls-creds' object that provides credentials
>> -#     for establishing a TLS connection over the migration data
>> -#     channel.  On the outgoing side of the migration, the credentials
>> -#     must be for a 'client' endpoint, while for the incoming side the
>> -#     credentials must be for a 'server' endpoint.  An empty string
>> -#     means that QEMU will use plain text mode for migration, rather
>> -#     than TLS.  (Since 2.7)
>> -#
>> -#     Note: 2.8 omits empty @tls-creds instead.
>> -#
>> -# @tls-hostname: migration target's hostname for validating the
>> -#     server's x509 certificate identity.  If empty, QEMU will use the
>> -#     hostname from the migration URI, if any.  (Since 2.7)
>> -#
>> -#     Note: 2.8 omits empty @tls-hostname instead.
>> -#
>> -# @tls-authz: ID of the 'authz' object subclass that provides access
>> -#     control checking of the TLS x509 certificate distinguished name.
>> -#     (Since 4.0)
>> -#
>>  # @max-bandwidth: maximum speed for migration, in bytes per second.
>>  #     (Since 2.8)
>>  #
>> @@ -1277,45 +1059,121 @@
>>  #     only has effect if the @mapped-ram capability is enabled.
>>  #     (Since 9.1)
>>  #
>> +# @tls: Whether to use TLS. If this is set the options @tls-authz,
>> +#     @tls-creds, @tls-hostname are mandatory and a valid string is
>> +#     expected. (Since 10.1)
>> +#
>>  # Features:
>>  #
>>  # @unstable: Members @x-checkpoint-delay and
>>  #     @x-vcpu-dirty-limit-period are experimental.
>>  #
>> +# Since: 10.1
>> +##
>> +{ 'struct': 'MigrationConfigBase',
>> +  'data': { '*announce-initial': 'size',
>> +            '*announce-max': 'size',
>> +            '*announce-rounds': 'size',
>> +            '*announce-step': 'size',
>> +            '*throttle-trigger-threshold': 'uint8',
>> +            '*cpu-throttle-initial': 'uint8',
>> +            '*cpu-throttle-increment': 'uint8',
>> +            '*cpu-throttle-tailslow': 'bool',
>> +            '*max-bandwidth': 'size',
>> +            '*avail-switchover-bandwidth': 'size',
>> +            '*downtime-limit': 'uint64',
>> +            '*x-checkpoint-delay': { 'type': 'uint32',
>> +                                     'features': [ 'unstable' ] },
>> +            '*multifd-channels': 'uint8',
>> +            '*xbzrle-cache-size': 'size',
>> +            '*max-postcopy-bandwidth': 'size',
>> +            '*max-cpu-throttle': 'uint8',
>> +            '*multifd-compression': 'MultiFDCompression',
>> +            '*multifd-zlib-level': 'uint8',
>> +            '*multifd-qatzip-level': 'uint8',
>> +            '*multifd-zstd-level': 'uint8',
>> +            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
>> +            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
>> +                                            'features': [ 'unstable' ] },
>> +            '*vcpu-dirty-limit': 'uint64',
>> +            '*mode': 'MigMode',
>> +            '*zero-page-detection': 'ZeroPageDetection',
>> +            '*direct-io': 'bool',
>> +            '*tls': 'bool' } }
>> +
>> +##
>> +# @MigrationParameters:
>> +#
>> +# The optional members of the base type aren't actually optional.
>> +#
>> +# @tls-creds: ID of the 'tls-creds' object that provides credentials
>> +#     for establishing a TLS connection over the migration data
>> +#     channel.  On the outgoing side of the migration, the credentials
>> +#     must be for a 'client' endpoint, while for the incoming side the
>> +#     credentials must be for a 'server' endpoint.  Setting this to a
>> +#     non-empty string enables TLS for all migrations.  An empty
>> +#     string means that QEMU will use plain text mode for migration,
>> +#     rather than TLS.  (Since 2.7)
>> +#
>> +# @tls-hostname: migration target's hostname for validating the
>> +#     server's x509 certificate identity.  If empty, QEMU will use the
>> +#     hostname from the migration URI, if any.  A non-empty value is
>> +#     required when using x509 based TLS credentials and the migration
>> +#     URI does not include a hostname, such as fd: or exec: based
>> +#     migration.  (Since 2.7)
>> +#
>> +#     Note: empty value works only since 2.9.
>> +#
>> +# @tls-authz: ID of the 'authz' object subclass that provides access
>> +#     control checking of the TLS x509 certificate distinguished name.
>> +#     This object is only resolved at time of use, so can be deleted
>> +#     and recreated on the fly while the migration server is active.
>> +#     If missing, it will default to denying access (Since 4.0)
>> +#
>>  # Since: 2.4
>>  ##
>>  { 'struct': 'MigrationParameters',
>> -  'data': { '*announce-initial': 'size',
>> -            '*announce-max': 'size',
>> -            '*announce-rounds': 'size',
>> -            '*announce-step': 'size',
>> -            '*throttle-trigger-threshold': 'uint8',
>> -            '*cpu-throttle-initial': 'uint8',
>> -            '*cpu-throttle-increment': 'uint8',
>> -            '*cpu-throttle-tailslow': 'bool',
>> -            '*tls-creds': 'str',
>> -            '*tls-hostname': 'str',
>> -            '*tls-authz': 'str',
>> -            '*max-bandwidth': 'size',
>> -            '*avail-switchover-bandwidth': 'size',
>> -            '*downtime-limit': 'uint64',
>> -            '*x-checkpoint-delay': { 'type': 'uint32',
>> -                                     'features': [ 'unstable' ] },
>> -            '*multifd-channels': 'uint8',
>> -            '*xbzrle-cache-size': 'size',
>> -            '*max-postcopy-bandwidth': 'size',
>> -            '*max-cpu-throttle': 'uint8',
>> -            '*multifd-compression': 'MultiFDCompression',
>> -            '*multifd-zlib-level': 'uint8',
>> -            '*multifd-qatzip-level': 'uint8',
>> -            '*multifd-zstd-level': 'uint8',
>> -            '*block-bitmap-mapping': [ 'BitmapMigrationNodeAlias' ],
>> -            '*x-vcpu-dirty-limit-period': { 'type': 'uint64',
>> -                                            'features': [ 'unstable' ] },
>> -            '*vcpu-dirty-limit': 'uint64',
>> -            '*mode': 'MigMode',
>> -            '*zero-page-detection': 'ZeroPageDetection',
>> -            '*direct-io': 'bool' } }
>> +  'base': 'MigrationConfigBase',
>> +  'data': { 'tls-creds': 'str',
>> +            'tls-hostname': 'str',
>> +            'tls-authz': 'str' } }
>> +
>> +##
>> +# @MigrateSetParameters:
>> +#
>> +# Compatibility layer to accept null values for the TLS options.
>> +#
>> +# @tls-creds: ID of the 'tls-creds' object that provides credentials
>> +#     for establishing a TLS connection over the migration data
>> +#     channel.  On the outgoing side of the migration, the credentials
>> +#     must be for a 'client' endpoint, while for the incoming side the
>> +#     credentials must be for a 'server' endpoint.  Setting this to a
>> +#     non-empty string enables TLS for all migrations.  An empty
>> +#     string means that QEMU will use plain text mode for migration,
>> +#     rather than TLS.  This is the default.  (Since 2.7)
>> +#
>> +# @tls-hostname: migration target's hostname for validating the
>> +#     server's x509 certificate identity.  If empty, QEMU will use the
>> +#     hostname from the migration URI, if any.  A non-empty value is
>> +#     required when using x509 based TLS credentials and the migration
>> +#     URI does not include a hostname, such as fd: or exec: based
>> +#     migration.  (Since 2.7)
>> +#
>> +#     Note: empty value works only since 2.9.
>> +#
>> +# @tls-authz: ID of the 'authz' object subclass that provides access
>> +#     control checking of the TLS x509 certificate distinguished name.
>> +#     This object is only resolved at time of use, so can be deleted
>> +#     and recreated on the fly while the migration server is active.
>> +#     If missing, it will default to denying access (Since 4.0)
>> +#
>> +# Since: 2.4
>> +##
>> +{ 'struct': 'MigrateSetParameters',
>> +  'base': 'MigrationConfigBase',
>> +  'data': { '*tls-creds': 'StrOrNull',
>> +            '*tls-hostname': 'StrOrNull',
>> +            '*tls-authz': 'StrOrNull' } }
>>  
>>  ##
>>  # @query-migrate-parameters:
>
> Your change is fairly simple, but that's not obvious from the diff.
>
> 1. Move everything but the TLS stuff from MigrationParameters to its new
>    base type MigrationConfigBase.
>
>    No change to the interface, obviously.
>
>    Introspection hides the base type, and shows members of
>    MigrationParameters in a different order, but order doesn't matter.
>
>    The doc generator isn't smart enough (yet) to hide the base type.
>    Apart from that, documentation is unchanged.
>
> 2. Replace everything but the TLS stuff from MigrateSetParameters by
>    base type MigrateSetParameters.
>
>    Because the base type's members are identical to the members it
>    replaces, no change to the interface.
>
>    Introspection hides the base type, and shows members of
>    MigrateSetParameters in a different order, but order doesn't matter.
>
>    The base type's member documentation is *not* identical to the member
>    documentation it replaces.  To see the differences, I ran
>    sphinx-build with -b text before and after, and fed its output to
>    diff:
>
>        * **cpu-throttle-initial** ("int", *optional*) -- Initial
>          percentage of time guest cpus are throttled when migration
> -        auto-converge is activated.  The default value is 20.  (Since
> -        2.7)
> +        auto-converge is activated.  (Since 2.7)
>
>    We no longer document the default value.  This is wrong.
>
>    The difference before the patch makes some sense.  The members of
>    MigrateSetParameters are actually optional command arguments, and
>    their defaults must be documented.  The members of
>    MigrationParameters aren't actually optional, and talking about
>    defaults is misleading there.  We do it anyway in a few places.
>
>    Factoring out MigrationConfigBase leads to a conflict: in its role as
>    base of MigrationParameters, it shouldn't talk about defaults, and in
>    its role as base of MigrateSetParameters, it should document the
>    defaults supplied by migrate-set-parameters.
>
>    There is no perfectly satisfactory solution with our current
>    infrastructure.
>
>    We can elect to ignore the problem for now.  Point it out in a TODO
>    comment.
>
>    We can try to explain in the doc text that default values apply only
>    to migrate-set-parameters.  I expect this to be awkward.  More
>    awkward once the doc generator inlines stuff.
>
>        * **cpu-throttle-increment** ("int", *optional*) -- throttle
>          percentage increase each time auto-converge detects that
> -        migration is not making progress. The default value is 10.
> -        (Since 2.7)
> +        migration is not making progress. (Since 2.7)
>  
>     Likewise.
>  
> -      * **x-checkpoint-delay** ("int", *optional*) -- The delay time
> -        (in ms) between two COLO checkpoints in periodic mode.  (Since
> -        2.8)
> +      * **x-checkpoint-delay** ("int", *optional*) -- the delay time
> +        between two COLO checkpoints. (Since 2.8)
>  
>     Inconsistent before the patch.  The deleted version is better.  Easy
>     enough to fix.
>  
> +      * **tls** ("boolean", *optional*) -- Whether to use TLS. If this
> +        is set the options "tls-authz", "tls-creds", "tls-hostname"
> +        are mandatory and a valid string is expected. (Since 10.1)
> +
>
>     Accident.

I missed

>    base type MigrateSetParameters.
>
>    Because the base type's members are identical to the members it
>    replaces, no change to the interface.
>
>    Introspection hides the base type, and shows members of
>    MigrateSetParameters in a different order, but order doesn't matter.
>
>    The base type's member documentation is *not* identical to the member
>    documentation it replaces.  To see the differences, I ran
>    sphinx-build with -b text before and after, and fed its output to
>    diff:
>
>        * **cpu-throttle-initial** ("int", *optional*) -- Initial
>          percentage of time guest cpus are throttled when migration
> -        auto-converge is activated.  The default value is 20.  (Since
> -        2.7)
> +        auto-converge is activated.  (Since 2.7)
>
>    We no longer document the default value.  This is wrong.
>
>    The difference before the patch makes some sense.  The members of
>    MigrateSetParameters are actually optional command arguments, and
>    their defaults must be documented.  The members of
>    MigrationParameters aren't actually optional, and talking about
>    defaults is misleading there.  We do it anyway in a few places.
>
>    Factoring out MigrationConfigBase leads to a conflict: in its role as
>    base of MigrationParameters, it shouldn't talk about defaults, and in
>    its role as base of MigrateSetParameters, it should document the
>    defaults supplied by migrate-set-parameters.
>
>    There is no perfectly satisfactory solution with our current
>    infrastructure.
>
>    We can elect to ignore the problem for now.  Point it out in a TODO
>    comment.
>
>    We can try to explain in the doc text that default values apply only
>    to migrate-set-parameters.  I expect this to be awkward.  More
>    awkward once the doc generator inlines stuff.
>
>        * **cpu-throttle-increment** ("int", *optional*) -- throttle
>          percentage increase each time auto-converge detects that
> -        migration is not making progress. The default value is 10.
> -        (Since 2.7)
> +        migration is not making progress. (Since 2.7)
>  
>     Likewise.
>  
> -      * **x-checkpoint-delay** ("int", *optional*) -- The delay time
> -        (in ms) between two COLO checkpoints in periodic mode.  (Since
> -        2.8)
> +      * **x-checkpoint-delay** ("int", *optional*) -- the delay time
> +        between two COLO checkpoints. (Since 2.8)
>  
>     Inconsistent before the patch.  The deleted version is better.  Easy
>     enough to fix.
>  
> +      * **tls** ("boolean", *optional*) -- Whether to use TLS. If this
> +        is set the options "tls-authz", "tls-creds", "tls-hostname"
> +        are mandatory and a valid string is expected. (Since 10.1)
> +
>
>     Accident.

I missed

  3. Make @tls-creds, @tls-hostname, @tls-authz non-optional in
     MigrationParameters.
  
     Compatible change to the interface.

When my description of what a patch does goes 1. 2. 3., then splitting
it up could be in order.  Especially when I miss 3. initially :)



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure
  2025-04-11 19:14 ` [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure Fabiano Rosas
@ 2025-04-18  7:03   ` Markus Armbruster
  2025-05-23 13:38     ` Fabiano Rosas
  0 siblings, 1 reply; 44+ messages in thread
From: Markus Armbruster @ 2025-04-18  7:03 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Daniel P . Berrangé

Fabiano Rosas <farosas@suse.de> writes:

> Add a new migration structure to consolidate the capabilities and
> parameters. This structure will be used in place of the s->parameters
> and s->capabilities data structures in the next few patches.
>
> The QAPI migration types now look like this:
>
> /* options previously known as parameters */

Configuration previously known as parameters less the TLS stuff.

> { 'struct': 'MigrationConfigBase',
>   'data': {
>       <parameters>
> } }
>
>
> /* for compat with query-migrate-parameters */
> { 'struct': 'MigrationParameters',
>   'base': 'MigrationConfigBase',
>   'data': {
>       <TLS options in 'str' format>
> } }
>
> /* for compat with migrate-set-parameters */
> { 'struct': 'MigrateSetParameters',
>   'base': 'MigrationConfigBase',
>   'data': {
>       <TLS options in 'StrOrNull' format>
> } }

Yes, this is the state since PATCH 05.

> /* to replace MigrationParameters in the MigrationState */
> { 'struct': 'MigrationConfig',
>   'base': 'MigrationConfigBase'
>   'data': {
>     <TLS options in 'str' format>
> } }

This is new in this patch.

Your description doesn't cover optionalness.  Here's my understanding:

* MigrationSetParameters has optional members, because
  migrate-set-parameters needs that.

* MigrationParameters would ideally have these members non-optional,
  because query-migrate-parameters wants that.

* But to enable sharing via common base type MigrationConfigBase, we
  accept unwanted optional in MigrationParameters and thus
  query-migrate-parameters.

* This doesn't apply to the non-shared members of MigrationParameters,
  so you made them non-optional.  These are @tls-creds, @tls-hostname,
  @tls-authz.

* But in MigrationConfig they're optional again, because "empty string
  means absent" is silly; we want "NULL means absent".

Correct?

Up to here, this enables cleanup of some "empty string means absent"
silliness in later patches.

The remainder is about unifying capabilities into parameters.  I'd split
the patch (but I'm a compulsive patch splitter).

> The above keeps the query/set-parameters commands stable. For the
> capabilities as well as the options added in the future, we have a
> choice of where to put them:
>
> 1) In MigrationConfigBase, this means that the existing
>    query/set-parameters commands will be updated to deal with
>    capabilities/new options.
>
>   { 'struct': 'MigrationConfigBase',
>     'data': {
>       <parameters>
>       <capabilities>
>       <new opts>
>   } }
>
>   { 'struct': 'MigrationConfig',
>     'base': 'MigrationConfigBase'
>     'data': {
>       <TLS options in 'str' format>
>   } }
>
> 2) In MigrationConfig, this means that the existing commands will be
>    frozen in time.
>
>   { 'struct': 'MigrationConfigBase',
>     'data': {
>       <parameters>
>   } }
>
>   { 'struct': 'MigrationConfig',
>     'base': 'MigrationConfigBase'
>     'data': {
>       <TLS options in 'str' format>
>       <capabilities>
>       <new opts>
>   } }
>
> For now, I've chosen the option 1, all capabilities and new options go
> into MigrationConfigBase. This gives the option to keep the existing
> commands for as long as we'd like.

Perhaps this would be slightly easier to digest for the reader if you
talked just about capabilities at first.  Once that's understood,
mention we have the same choice for new configuration bits.

> Note that the query/set capabilities commands will have to go, we can
> treat parameters as generic configuration options, but capabilities
> are just too different.

I think the argument is that migration capabilities are a pointless
interface complication.  One mechanism (parameters) is better than two
(parameters and capabilities).

> Signed-off-by: Fabiano Rosas <farosas@suse.de>
> ---
>  qapi/migration.json | 163 +++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 160 insertions(+), 3 deletions(-)
>
> diff --git a/qapi/migration.json b/qapi/migration.json
> index 5a4d5a2d3e..5e39f21adc 100644
> --- a/qapi/migration.json
> +++ b/qapi/migration.json
> @@ -1063,10 +1063,108 @@
>  #     @tls-creds, @tls-hostname are mandatory and a valid string is
>  #     expected. (Since 10.1)
>  #
> +# @xbzrle: Migration supports xbzrle (Xor Based Zero Run Length
> +#     Encoding).  This feature allows us to minimize migration traffic
> +#     for certain work loads, by sending compressed difference of the
> +#     pages
> +#
> +# @rdma-pin-all: Controls whether or not the entire VM memory
> +#     footprint is mlock()'d on demand or all at once.  Refer to
> +#     docs/rdma.txt for usage.  Disabled by default.  (since 2.0)
> +#
> +# @zero-blocks: During storage migration encode blocks of zeroes
> +#     efficiently.  This essentially saves 1MB of zeroes per block on
> +#     the wire.  Enabling requires source and target VM to support
> +#     this feature.  To enable it is sufficient to enable the
> +#     capability on the source VM.  The feature is disabled by
> +#     default.  (since 1.6)
> +#
> +# @events: generate events for each migration state change (since 2.4)
> +#
> +# @auto-converge: If enabled, QEMU will automatically throttle down
> +#     the guest to speed up convergence of RAM migration.  (since 1.6)
> +#
> +# @postcopy-ram: Start executing on the migration target before all of
> +#     RAM has been migrated, pulling the remaining pages along as
> +#     needed.  The capacity must have the same setting on both source
> +#     and target or migration will not even start.  NOTE: If the
> +#     migration fails during postcopy the VM will fail.  (since 2.6)
> +#
> +# @x-colo: If enabled, migration will never end, and the state of the
> +#     VM on the primary side will be migrated continuously to the VM
> +#     on secondary side, this process is called COarse-Grain LOck
> +#     Stepping (COLO) for Non-stop Service.  (since 2.8)
> +#
> +# @release-ram: if enabled, qemu will free the migrated ram pages on
> +#     the source during postcopy-ram migration.  (since 2.9)
> +#
> +# @return-path: If enabled, migration will use the return path even
> +#     for precopy.  (since 2.10)
> +#
> +# @pause-before-switchover: Pause outgoing migration before
> +#     serialising device state and before disabling block IO (since
> +#     2.11)
> +#
> +# @multifd: Use more than one fd for migration (since 4.0)
> +#
> +# @dirty-bitmaps: If enabled, QEMU will migrate named dirty bitmaps.
> +#     (since 2.12)
> +#
> +# @postcopy-blocktime: Calculate downtime for postcopy live migration
> +#     (since 3.0)
> +#
> +# @late-block-activate: If enabled, the destination will not activate
> +#     block devices (and thus take locks) immediately at the end of
> +#     migration.  (since 3.0)
> +#
> +# @x-ignore-shared: If enabled, QEMU will not migrate shared memory
> +#     that is accessible on the destination machine.  (since 4.0)
> +#
> +# @validate-uuid: Send the UUID of the source to allow the destination
> +#     to ensure it is the same.  (since 4.2)
> +#
> +# @background-snapshot: If enabled, the migration stream will be a
> +#     snapshot of the VM exactly at the point when the migration
> +#     procedure starts.  The VM RAM is saved with running VM.
> +#     (since 6.0)
> +#
> +# @zero-copy-send: Controls behavior on sending memory pages on
> +#     migration.  When true, enables a zero-copy mechanism for sending
> +#     memory pages, if host supports it.  Requires that QEMU be
> +#     permitted to use locked memory for guest RAM pages.  (since 7.1)
> +#
> +# @postcopy-preempt: If enabled, the migration process will allow
> +#     postcopy requests to preempt precopy stream, so postcopy
> +#     requests will be handled faster.  This is a performance feature
> +#     and should not affect the correctness of postcopy migration.
> +#     (since 7.1)
> +#
> +# @switchover-ack: If enabled, migration will not stop the source VM
> +#     and complete the migration until an ACK is received from the
> +#     destination that it's OK to do so.  Exactly when this ACK is
> +#     sent depends on the migrated devices that use this feature.  For
> +#     example, a device can use it to make sure some of its data is
> +#     sent and loaded in the destination before doing switchover.
> +#     This can reduce downtime if devices that support this capability
> +#     are present.  'return-path' capability must be enabled to use
> +#     it.  (since 8.1)
> +#
> +# @dirty-limit: If enabled, migration will throttle vCPUs as needed to
> +#     keep their dirty page rate within @vcpu-dirty-limit.  This can
> +#     improve responsiveness of large guests during live migration,
> +#     and can result in more stable read performance.  Requires KVM
> +#     with accelerator property "dirty-ring-size" set.  (Since 8.1)
> +#
> +# @mapped-ram: Migrate using fixed offsets in the migration file for
> +#     each RAM page.  Requires a migration URI that supports seeking,
> +#     such as a file.  (since 9.0)
> +#
>  # Features:
>  #
> -# @unstable: Members @x-checkpoint-delay and
> -#     @x-vcpu-dirty-limit-period are experimental.
> +# @unstable: Members @x-checkpoint-delay, @x-vcpu-dirty-limit-period,
> +#     @x-colo and @x-ignore-shared are experimental.
> +# @deprecated: Member @zero-blocks is deprecated as being part of
> +#     block migration which was already removed.
>  #
>  # Since: 10.1
>  ##
> @@ -1099,7 +1197,29 @@
>              '*mode': 'MigMode',
>              '*zero-page-detection': 'ZeroPageDetection',
>              '*direct-io': 'bool',
> -            '*tls': 'bool' } }
> +            '*tls': 'bool',
> +            '*xbzrle': 'bool',
> +            '*rdma-pin-all': 'bool',
> +            '*auto-converge': 'bool',
> +            '*zero-blocks': { 'type': 'bool', 'features': [ 'deprecated' ] },
> +            '*events': 'bool',
> +            '*postcopy-ram': 'bool',
> +            '*x-colo': { 'type': 'bool', 'features': [ 'unstable' ] },
> +            '*release-ram': 'bool',
> +            '*return-path': 'bool',
> +            '*pause-before-switchover': 'bool',
> +            '*multifd': 'bool',
> +            '*dirty-bitmaps': 'bool',
> +            '*postcopy-blocktime': 'bool',
> +            '*late-block-activate': 'bool',
> +            '*x-ignore-shared': { 'type': 'bool', 'features': [ 'unstable' ] },
> +            '*validate-uuid': 'bool',
> +            '*background-snapshot': 'bool',
> +            '*zero-copy-send': 'bool',
> +            '*postcopy-preempt': 'bool',
> +            '*switchover-ack': 'bool',
> +            '*dirty-limit': 'bool',
> +            '*mapped-ram': 'bool' } }

This is part 2 "unify capabilities into parameters".

Missing in your series: deprecate migrate-set-capabilities and
query-migrate-capabilities.

>  
>  ##
>  # @MigrationParameters:
> @@ -2395,3 +2515,40 @@
>    'data': { 'job-id': 'str',
>              'tag': 'str',
>              'devices': ['str'] } }
> +
> +##
> +# @MigrationConfig:
> +#
> +# Migration configuration options
> +#
> +# @tls-creds: ID of the 'tls-creds' object that provides credentials
> +#     for establishing a TLS connection over the migration data
> +#     channel.  On the outgoing side of the migration, the credentials
> +#     must be for a 'client' endpoint, while for the incoming side the
> +#     credentials must be for a 'server' endpoint.  Setting this to a
> +#     non-empty string enables TLS for all migrations.  An empty
> +#     string means that QEMU will use plain text mode for migration,
> +#     rather than TLS.  (Since 2.7)

Is "empty string" expected to occur here?

> +#
> +# @tls-hostname: migration target's hostname for validating the
> +#     server's x509 certificate identity.  If empty, QEMU will use the
> +#     hostname from the migration URI, if any.  A non-empty value is
> +#     required when using x509 based TLS credentials and the migration
> +#     URI does not include a hostname, such as fd: or exec: based
> +#     migration.  (Since 2.7)
> +#
> +#     Note: empty value works only since 2.9.

Likewise.

> +#
> +# @tls-authz: ID of the 'authz' object subclass that provides access
> +#     control checking of the TLS x509 certificate distinguished name.
> +#     This object is only resolved at time of use, so can be deleted
> +#     and recreated on the fly while the migration server is active.
> +#     If missing, it will default to denying access (Since 4.0)
> +#
> +# Since: 10.1
> +##
> +{ 'struct': 'MigrationConfig',
> +  'base': 'MigrationConfigBase',
> +  'data': { '*tls-creds': 'str',
> +            '*tls-hostname': 'str',
> +            '*tls-authz': 'str' } }

This is part 1 "enable cleanup".



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-16 15:00     ` Fabiano Rosas
@ 2025-04-24  9:35       ` Markus Armbruster
  0 siblings, 0 replies; 44+ messages in thread
From: Markus Armbruster @ 2025-04-24  9:35 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: Daniel P. Berrangé, qemu-devel, Peter Xu

Fabiano Rosas <farosas@suse.de> writes:

> Markus Armbruster <armbru@redhat.com> writes:
>
>> Daniel P. Berrangé <berrange@redhat.com> writes:
>>
>>> On Fri, Apr 11, 2025 at 04:14:30PM -0300, Fabiano Rosas wrote:
>>>> Open questions:
>>>> ---------------
>>>> 
>>>> - Deprecations/compat?
>>>> 
>>>> I think we should deprecate migrate-set/query-capabilities and everything to do
>>>> with capabilities (specifically the validation in the JSON at the end of the
>>>> stream).
>>>> 
>>>> For migrate-set/query-parameters, we could probably keep it around indefinitely,
>>>> but it'd be convenient to introduce new commands so we can give them new
>>>> semantics.
>>>> 
>>>> - How to restrict the options that should not be set when the migration is in
>>>> progress?
>>>> 
>>>> i.e.:
>>>>   all options can be set before migration (initial config)
>>>>   some options can be set during migration (runtime)
>>>> 
>>>> I thought of adding another type at the top of the hierarchy, with
>>>> just the options allowed to change at runtime, but that doesn't really
>>>> stop the others being also set at runtime. I'd need a way to have a
>>>> set of options that are rejected 'if migration_is_running()', without
>>>> adding more duplication all around.
>>>> 
>>>> - What about savevm?
>>>> 
>>>> None of this solves the issue of random caps/params being set before
>>>> calling savevm. We still need to special-case savevm and reject
>>>> everything. Unless we entirely deprecate setting initial options via
>>>> set-parameters (or set-config) and require all options to be set as
>>>> savevm (and migrate) arguments.
>>>
>>> I'd suggest we aim for a world where the commands take all options
>>> as direct args and try to remove the global state eventually.
>>
>> Yes.
>>
>> Even better: make it a job.
>
> What do we gain from that in relation to being able to pass ~50
> parameters to a command? I don't see it. I think that's the actual crux
> here, too many options and how to get them from the QAPI into the
> migration core for consumption.

Two separate interface design problems:

1. Query and manipulate complex configuration

2. Control and monitor long-running jobs

Let's start with 1.

Migration is not the only instance of complex configuration.  Block
devices are arguably even more complex.  A difference: we can have many
block devices, but just one migration.

Here's the usual way to configure things: commands creating a thing take
the thing's configuration as arguments, other commands query and
manipulate a thing's configuration.  Works fine both for simple and
complex configuration.  Block devices work this way.

Migration doesn't.  Instead, we have global configuration state, and
commands to query and manipulate it.  Also works.

Instead of passing the entire configuration to the "create" command, you
can pass it piecemeal to "manipulate global configuration" commands.
You pay for the convenience by having to reset the entire global
configuration to a known state in certain situations.  Since use of the
global configuration state is implicit, it can be surprising, as Daniel
pointed out for savevm.

Overall, this isn't simpler or more convenient than the usual way, just
different.

Both ways use QAPI objects to hold configuration.  Query commands can
return such an object.  Commands that deal with partial configuration
can only take the same object when its members are optional.

In places where configuration is complete (internal configuration state,
the query command that returns it), optional makes no sense, and is
annoying.  We either accept that, or duplicate the configuration object.
Duplicating violates DRY.  It does result in better command
documentation, though.

On to 2.

Migration is not the only kind of long-running job.  There's also
block-commit, block-stream, drive-mirror, drive-backup, blockdev-create,
x-blockdev-amend, snapshot-load, snapshot-save, snapshot-delete.

These all create a "job" object that represents the long-running job.
We have commands and events to control and monitor such jobs: job-pause,
job-resume, job-cancel, job-complete, job-dismiss, job-finalize,
query-jobs, JOB_STATUS_CHANGE.

Migration does its own thing instead.

> The current usage of MigrationParameters as both the return type for
> query-set-parameters and the global parameter store for the migration
> state is really dissonant. What do the has_* fields even mean when
> accessed via MigrationState::parameters?

Yes, it's ugly, and the alternative is differently ugly, as discussed
above.

>                                          This series is not doing any
> better in that regard, mind you. I'm almost tempted to ask that we
> expose the QDict from the marshaling function directly to the migration
> code, at least that's a data type that makes sense internally.

Based on experience in the block layer, I predict you'd regret this :)

Use of native C data types is just so much easier on the eyes than
messing with QObjects.  Moreover, the compiler can't help you as much
with QObjects.



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 00/13] migration: Unify capabilities and parameters
  2025-04-14 17:40       ` Fabiano Rosas
  2025-04-14 19:06         ` Daniel P. Berrangé
@ 2025-05-15 20:21         ` Peter Xu
  1 sibling, 0 replies; 44+ messages in thread
From: Peter Xu @ 2025-05-15 20:21 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: Daniel P. Berrangé, qemu-devel, Markus Armbruster

On Mon, Apr 14, 2025 at 02:40:25PM -0300, Fabiano Rosas wrote:
> > Can we make the two approaches mutually exclusive ? Taking your
> > 'migrate' command example addition:
> >
> >   { 'command': 'migrate',
> >     'data': {'*uri': 'str',
> >              '*channels': [ 'MigrationChannel' ],
> >   +          '*config': 'MigrationConfig',
> >              '*detach': 'bool', '*resume': 'bool' } }
> >
> > if 'migrate' is invoked with the '*config' data being non-nil,
> > then we should ignore *all* global state previously set with
> > migrate-set-XXXX, and exclusively use '*config'.
> >
> > That gives a clean semantic break between old and new approaches,
> > without us having to worry about removing the existing commands
> > quickly.
> >
> 
> Good idea. I will need to do something about the -global options because
> they also set the defaults for the various options. But we should be
> able to decouple setting defaults from -global. Or I could just apply
> -global again on top of what came in '*config'.

Would it still be possible that we allow whatever options attached to the
"migrate" command to only overwrite the globals (either set via -global or
migrate-set-* QMP commands), rather than ignoring them completely?

If so, we don't need to do anything with current -global and it'll keep
working.  It's the same to most existing way to set the migration options
(e.g., otherwise do we plan to disable HMP "migrate" usage?).

Making the above '*config' to only overwrite also do not stand against the
mgmt using it to ignore the default options, after all the mgmt can decide
to always set everything in the '*config' then it's the same as ignoring
the globals?

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 03/13] migration: Run a post update routine after setting parameters
  2025-04-11 19:14 ` [RFC PATCH 03/13] migration: Run a post update routine after setting parameters Fabiano Rosas
@ 2025-05-15 20:42   ` Peter Xu
  0 siblings, 0 replies; 44+ messages in thread
From: Peter Xu @ 2025-05-15 20:42 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Markus Armbruster, Daniel P . Berrangé

On Fri, Apr 11, 2025 at 04:14:33PM -0300, Fabiano Rosas wrote:
> Some migration parameters are updated immediately once they are set
> via migrate-set-parameters. Move that work outside of
> migrate_params_apply() and leave that function with the single
> responsibility of setting s->parameters and not doing any
> side-effects.
> 
> Signed-off-by: Fabiano Rosas <farosas@suse.de>

Reviewed-by: Peter Xu <peterx@redhat.com>

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 04/13] migration: Fix parameter validation
  2025-04-11 19:14 ` [RFC PATCH 04/13] migration: Fix parameter validation Fabiano Rosas
@ 2025-05-15 20:59   ` Peter Xu
  2025-05-22 16:39     ` Fabiano Rosas
  0 siblings, 1 reply; 44+ messages in thread
From: Peter Xu @ 2025-05-15 20:59 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Markus Armbruster, Daniel P . Berrangé

On Fri, Apr 11, 2025 at 04:14:34PM -0300, Fabiano Rosas wrote:
> The migration parameters validation involves producing a temporary
> structure which merges the current parameter values with the new
> parameters set by the user.
> 
> The has_ boolean fields of MigrateSetParameter are taken into
> consideration when writing the temporary structure, however the copy
> of the current parameters also copies all the has_ fields of
> s->parameters and those are (almost) all true due to being initialized
> by migrate_params_init().
> 
> Since the temporary structure copy does not carry over the has_ fields
> from MigrateSetParameters, only the values which were initialized in
> migrate_params_init() will end up being validated. This causes
> (almost) all of the migration parameters to be validated again every
> time a parameter is set, which could be considered a bug. But it also
> skips validation of those values which are not set in
> migrate_params_init(), which is a worse issue.

IMHO it's ok to double check all parameters in slow path.  Definitely not
ok to skip them.. So now the question is, if migrate_params_test_apply() so
far should check all params anyway...

Shall we drop the checking for all has_ there, then IIUC we also don't need
any initializations for has_* in migrate_params_init() here?

So, admittedly s->parameters.has_* is still ugly to be present.. we declare
all of them not used and ignore them always at least in s->parameters.

> 
> Fix by initializing the missing values in migrate_params_init().
> Currently 'avail_switchover_bandwidth' and 'block_bitmap_mapping' are
> affected.
> 
> Signed-off-by: Fabiano Rosas <farosas@suse.de>
> ---
>  migration/options.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/migration/options.c b/migration/options.c
> index cac28540dd..625d597a85 100644
> --- a/migration/options.c
> +++ b/migration/options.c
> @@ -987,6 +987,8 @@ void migrate_params_init(MigrationParameters *params)
>      params->has_mode = true;
>      params->has_zero_page_detection = true;
>      params->has_direct_io = true;
> +    params->has_avail_switchover_bandwidth = true;
> +    params->has_block_bitmap_mapping = true;
>  }
>  
>  /*
> -- 
> 2.35.3
> 

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 04/13] migration: Fix parameter validation
  2025-05-15 20:59   ` Peter Xu
@ 2025-05-22 16:39     ` Fabiano Rosas
  2025-05-22 17:39       ` Fabiano Rosas
  0 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-05-22 16:39 UTC (permalink / raw)
  To: Peter Xu; +Cc: qemu-devel, Markus Armbruster, Daniel P . Berrangé

Peter Xu <peterx@redhat.com> writes:

> On Fri, Apr 11, 2025 at 04:14:34PM -0300, Fabiano Rosas wrote:
>> The migration parameters validation involves producing a temporary
>> structure which merges the current parameter values with the new
>> parameters set by the user.
>> 
>> The has_ boolean fields of MigrateSetParameter are taken into
>> consideration when writing the temporary structure, however the copy
>> of the current parameters also copies all the has_ fields of
>> s->parameters and those are (almost) all true due to being initialized
>> by migrate_params_init().
>> 
>> Since the temporary structure copy does not carry over the has_ fields
>> from MigrateSetParameters, only the values which were initialized in
>> migrate_params_init() will end up being validated. This causes
>> (almost) all of the migration parameters to be validated again every
>> time a parameter is set, which could be considered a bug. But it also
>> skips validation of those values which are not set in
>> migrate_params_init(), which is a worse issue.
>
> IMHO it's ok to double check all parameters in slow path.  Definitely not
> ok to skip them.. So now the question is, if migrate_params_test_apply() so
> far should check all params anyway...
>

Well, either way is fine by me. In the current code, I can't tell what
the intention was, unfortunately.

We could check all params, but then we need to make sure they never
change in between calls to migrate-set-params. Looking at the code I
don't see any place where we allow s->parameters to change.

But then I worry about checks that:
- might be too costly
- only make sense the first time
- depend on the order of setting (a param/cap that should only be set
before/after some other param/cap)

> Shall we drop the checking for all has_ there, then IIUC we also don't need
> any initializations for has_* in migrate_params_init() here?
>

It'd be nice to not have to touch the has_ fields, yes. I'll experiment
with it.

> So, admittedly s->parameters.has_* is still ugly to be present.. we declare
> all of them not used and ignore them always at least in s->parameters.
>
>> 
>> Fix by initializing the missing values in migrate_params_init().
>> Currently 'avail_switchover_bandwidth' and 'block_bitmap_mapping' are
>> affected.
>> 
>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>> ---
>>  migration/options.c | 2 ++
>>  1 file changed, 2 insertions(+)
>> 
>> diff --git a/migration/options.c b/migration/options.c
>> index cac28540dd..625d597a85 100644
>> --- a/migration/options.c
>> +++ b/migration/options.c
>> @@ -987,6 +987,8 @@ void migrate_params_init(MigrationParameters *params)
>>      params->has_mode = true;
>>      params->has_zero_page_detection = true;
>>      params->has_direct_io = true;
>> +    params->has_avail_switchover_bandwidth = true;
>> +    params->has_block_bitmap_mapping = true;
>>  }
>>  
>>  /*
>> -- 
>> 2.35.3
>> 


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 04/13] migration: Fix parameter validation
  2025-05-22 16:39     ` Fabiano Rosas
@ 2025-05-22 17:39       ` Fabiano Rosas
  2025-05-26 13:09         ` Peter Xu
  0 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-05-22 17:39 UTC (permalink / raw)
  To: Peter Xu; +Cc: qemu-devel, Markus Armbruster, Daniel P . Berrangé

Fabiano Rosas <farosas@suse.de> writes:

> Peter Xu <peterx@redhat.com> writes:
>
>> On Fri, Apr 11, 2025 at 04:14:34PM -0300, Fabiano Rosas wrote:
>>> The migration parameters validation involves producing a temporary
>>> structure which merges the current parameter values with the new
>>> parameters set by the user.
>>> 
>>> The has_ boolean fields of MigrateSetParameter are taken into
>>> consideration when writing the temporary structure, however the copy
>>> of the current parameters also copies all the has_ fields of
>>> s->parameters and those are (almost) all true due to being initialized
>>> by migrate_params_init().
>>> 
>>> Since the temporary structure copy does not carry over the has_ fields
>>> from MigrateSetParameters, only the values which were initialized in
>>> migrate_params_init() will end up being validated. This causes
>>> (almost) all of the migration parameters to be validated again every
>>> time a parameter is set, which could be considered a bug. But it also
>>> skips validation of those values which are not set in
>>> migrate_params_init(), which is a worse issue.
>>
>> IMHO it's ok to double check all parameters in slow path.  Definitely not
>> ok to skip them.. So now the question is, if migrate_params_test_apply() so
>> far should check all params anyway...

Actually, this doesn't work...

The migrate-set-* commands have optional fields, so we need some form of
checking has_* to know which fields the user is setting. Otherwise
MigrationSetParameters will have zeros all over that will trip the
check.

Then, we need some form of checking has_* to be able to enventually get
the values into s->config (former s->parameters/capabilities), otherwise
we'll overwrite the already-set values with the potentially empty ones
coming from QAPI.

Then there's also the issue of knowing whether a value is 0 because the
user set it 0 or because it was never set.

We also can't apply an invalid value to s->config and validate it after
because some parameters are allowed to be changed during migration.



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure
  2025-04-18  7:03   ` Markus Armbruster
@ 2025-05-23 13:38     ` Fabiano Rosas
  2025-05-26  7:37       ` Markus Armbruster
  0 siblings, 1 reply; 44+ messages in thread
From: Fabiano Rosas @ 2025-05-23 13:38 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Peter Xu, Daniel P . Berrangé

Markus Armbruster <armbru@redhat.com> writes:

Markus, sorry for the delay here. I had vacations and holidays, plus a
pile of patches to review.

> Fabiano Rosas <farosas@suse.de> writes:
>
>> Add a new migration structure to consolidate the capabilities and
>> parameters. This structure will be used in place of the s->parameters
>> and s->capabilities data structures in the next few patches.
>>
>> The QAPI migration types now look like this:
>>
>> /* options previously known as parameters */
>
> Configuration previously known as parameters less the TLS stuff.
>
>> { 'struct': 'MigrationConfigBase',
>>   'data': {
>>       <parameters>
>> } }
>>
>>
>> /* for compat with query-migrate-parameters */
>> { 'struct': 'MigrationParameters',
>>   'base': 'MigrationConfigBase',
>>   'data': {
>>       <TLS options in 'str' format>
>> } }
>>
>> /* for compat with migrate-set-parameters */
>> { 'struct': 'MigrateSetParameters',
>>   'base': 'MigrationConfigBase',
>>   'data': {
>>       <TLS options in 'StrOrNull' format>
>> } }
>
> Yes, this is the state since PATCH 05.
>
>> /* to replace MigrationParameters in the MigrationState */
>> { 'struct': 'MigrationConfig',
>>   'base': 'MigrationConfigBase'
>>   'data': {
>>     <TLS options in 'str' format>
>> } }
>
> This is new in this patch.
>
> Your description doesn't cover optionalness.  Here's my understanding:
>
> * MigrationSetParameters has optional members, because
>   migrate-set-parameters needs that.
>

Yes.

> * MigrationParameters would ideally have these members non-optional,
>   because query-migrate-parameters wants that.
>

Yes.

> * But to enable sharing via common base type MigrationConfigBase, we
>   accept unwanted optional in MigrationParameters and thus
>   query-migrate-parameters.
>

Yes.

> * This doesn't apply to the non-shared members of MigrationParameters,
>   so you made them non-optional.  These are @tls-creds, @tls-hostname,
>   @tls-authz.
>

Yes.

> * But in MigrationConfig they're optional again, because "empty string
>   means absent" is silly; we want "NULL means absent".
>

Yes. But mostly because MigrationConfig will become the type for the new
'*config' argument to migrate/migrate_incoming (patches 12 & 13) and we
want to keep all members optional. Otherwise the user would have to pass
all ~50 migration options in every migrate command, which is bad IMO.

> Correct?
>
> Up to here, this enables cleanup of some "empty string means absent"
> silliness in later patches.
>
> The remainder is about unifying capabilities into parameters.  I'd split
> the patch (but I'm a compulsive patch splitter).
>
>> The above keeps the query/set-parameters commands stable. For the
>> capabilities as well as the options added in the future, we have a
>> choice of where to put them:
>>
>> 1) In MigrationConfigBase, this means that the existing
>>    query/set-parameters commands will be updated to deal with
>>    capabilities/new options.
>>
>>   { 'struct': 'MigrationConfigBase',
>>     'data': {
>>       <parameters>
>>       <capabilities>
>>       <new opts>
>>   } }
>>
>>   { 'struct': 'MigrationConfig',
>>     'base': 'MigrationConfigBase'
>>     'data': {
>>       <TLS options in 'str' format>
>>   } }
>>
>> 2) In MigrationConfig, this means that the existing commands will be
>>    frozen in time.
>>
>>   { 'struct': 'MigrationConfigBase',
>>     'data': {
>>       <parameters>
>>   } }
>>
>>   { 'struct': 'MigrationConfig',
>>     'base': 'MigrationConfigBase'
>>     'data': {
>>       <TLS options in 'str' format>
>>       <capabilities>
>>       <new opts>
>>   } }
>>
>> For now, I've chosen the option 1, all capabilities and new options go
>> into MigrationConfigBase. This gives the option to keep the existing
>> commands for as long as we'd like.
>
> Perhaps this would be slightly easier to digest for the reader if you
> talked just about capabilities at first.  Once that's understood,
> mention we have the same choice for new configuration bits.
>

Ok, I'll reorganize, along with the other comments you've made.

>> Note that the query/set capabilities commands will have to go, we can
>> treat parameters as generic configuration options, but capabilities
>> are just too different.
>
> I think the argument is that migration capabilities are a pointless
> interface complication.  One mechanism (parameters) is better than two
> (parameters and capabilities).
>

Yes, that's the main point indeed.

>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>> ---
>>  qapi/migration.json | 163 +++++++++++++++++++++++++++++++++++++++++++-
>>  1 file changed, 160 insertions(+), 3 deletions(-)
>>
>> diff --git a/qapi/migration.json b/qapi/migration.json
>> index 5a4d5a2d3e..5e39f21adc 100644
>> --- a/qapi/migration.json
>> +++ b/qapi/migration.json
>> @@ -1063,10 +1063,108 @@
>>  #     @tls-creds, @tls-hostname are mandatory and a valid string is
>>  #     expected. (Since 10.1)
>>  #
>> +# @xbzrle: Migration supports xbzrle (Xor Based Zero Run Length
>> +#     Encoding).  This feature allows us to minimize migration traffic
>> +#     for certain work loads, by sending compressed difference of the
>> +#     pages
>> +#
>> +# @rdma-pin-all: Controls whether or not the entire VM memory
>> +#     footprint is mlock()'d on demand or all at once.  Refer to
>> +#     docs/rdma.txt for usage.  Disabled by default.  (since 2.0)
>> +#
>> +# @zero-blocks: During storage migration encode blocks of zeroes
>> +#     efficiently.  This essentially saves 1MB of zeroes per block on
>> +#     the wire.  Enabling requires source and target VM to support
>> +#     this feature.  To enable it is sufficient to enable the
>> +#     capability on the source VM.  The feature is disabled by
>> +#     default.  (since 1.6)
>> +#
>> +# @events: generate events for each migration state change (since 2.4)
>> +#
>> +# @auto-converge: If enabled, QEMU will automatically throttle down
>> +#     the guest to speed up convergence of RAM migration.  (since 1.6)
>> +#
>> +# @postcopy-ram: Start executing on the migration target before all of
>> +#     RAM has been migrated, pulling the remaining pages along as
>> +#     needed.  The capacity must have the same setting on both source
>> +#     and target or migration will not even start.  NOTE: If the
>> +#     migration fails during postcopy the VM will fail.  (since 2.6)
>> +#
>> +# @x-colo: If enabled, migration will never end, and the state of the
>> +#     VM on the primary side will be migrated continuously to the VM
>> +#     on secondary side, this process is called COarse-Grain LOck
>> +#     Stepping (COLO) for Non-stop Service.  (since 2.8)
>> +#
>> +# @release-ram: if enabled, qemu will free the migrated ram pages on
>> +#     the source during postcopy-ram migration.  (since 2.9)
>> +#
>> +# @return-path: If enabled, migration will use the return path even
>> +#     for precopy.  (since 2.10)
>> +#
>> +# @pause-before-switchover: Pause outgoing migration before
>> +#     serialising device state and before disabling block IO (since
>> +#     2.11)
>> +#
>> +# @multifd: Use more than one fd for migration (since 4.0)
>> +#
>> +# @dirty-bitmaps: If enabled, QEMU will migrate named dirty bitmaps.
>> +#     (since 2.12)
>> +#
>> +# @postcopy-blocktime: Calculate downtime for postcopy live migration
>> +#     (since 3.0)
>> +#
>> +# @late-block-activate: If enabled, the destination will not activate
>> +#     block devices (and thus take locks) immediately at the end of
>> +#     migration.  (since 3.0)
>> +#
>> +# @x-ignore-shared: If enabled, QEMU will not migrate shared memory
>> +#     that is accessible on the destination machine.  (since 4.0)
>> +#
>> +# @validate-uuid: Send the UUID of the source to allow the destination
>> +#     to ensure it is the same.  (since 4.2)
>> +#
>> +# @background-snapshot: If enabled, the migration stream will be a
>> +#     snapshot of the VM exactly at the point when the migration
>> +#     procedure starts.  The VM RAM is saved with running VM.
>> +#     (since 6.0)
>> +#
>> +# @zero-copy-send: Controls behavior on sending memory pages on
>> +#     migration.  When true, enables a zero-copy mechanism for sending
>> +#     memory pages, if host supports it.  Requires that QEMU be
>> +#     permitted to use locked memory for guest RAM pages.  (since 7.1)
>> +#
>> +# @postcopy-preempt: If enabled, the migration process will allow
>> +#     postcopy requests to preempt precopy stream, so postcopy
>> +#     requests will be handled faster.  This is a performance feature
>> +#     and should not affect the correctness of postcopy migration.
>> +#     (since 7.1)
>> +#
>> +# @switchover-ack: If enabled, migration will not stop the source VM
>> +#     and complete the migration until an ACK is received from the
>> +#     destination that it's OK to do so.  Exactly when this ACK is
>> +#     sent depends on the migrated devices that use this feature.  For
>> +#     example, a device can use it to make sure some of its data is
>> +#     sent and loaded in the destination before doing switchover.
>> +#     This can reduce downtime if devices that support this capability
>> +#     are present.  'return-path' capability must be enabled to use
>> +#     it.  (since 8.1)
>> +#
>> +# @dirty-limit: If enabled, migration will throttle vCPUs as needed to
>> +#     keep their dirty page rate within @vcpu-dirty-limit.  This can
>> +#     improve responsiveness of large guests during live migration,
>> +#     and can result in more stable read performance.  Requires KVM
>> +#     with accelerator property "dirty-ring-size" set.  (Since 8.1)
>> +#
>> +# @mapped-ram: Migrate using fixed offsets in the migration file for
>> +#     each RAM page.  Requires a migration URI that supports seeking,
>> +#     such as a file.  (since 9.0)
>> +#
>>  # Features:
>>  #
>> -# @unstable: Members @x-checkpoint-delay and
>> -#     @x-vcpu-dirty-limit-period are experimental.
>> +# @unstable: Members @x-checkpoint-delay, @x-vcpu-dirty-limit-period,
>> +#     @x-colo and @x-ignore-shared are experimental.
>> +# @deprecated: Member @zero-blocks is deprecated as being part of
>> +#     block migration which was already removed.
>>  #
>>  # Since: 10.1
>>  ##
>> @@ -1099,7 +1197,29 @@
>>              '*mode': 'MigMode',
>>              '*zero-page-detection': 'ZeroPageDetection',
>>              '*direct-io': 'bool',
>> -            '*tls': 'bool' } }
>> +            '*tls': 'bool',
>> +            '*xbzrle': 'bool',
>> +            '*rdma-pin-all': 'bool',
>> +            '*auto-converge': 'bool',
>> +            '*zero-blocks': { 'type': 'bool', 'features': [ 'deprecated' ] },
>> +            '*events': 'bool',
>> +            '*postcopy-ram': 'bool',
>> +            '*x-colo': { 'type': 'bool', 'features': [ 'unstable' ] },
>> +            '*release-ram': 'bool',
>> +            '*return-path': 'bool',
>> +            '*pause-before-switchover': 'bool',
>> +            '*multifd': 'bool',
>> +            '*dirty-bitmaps': 'bool',
>> +            '*postcopy-blocktime': 'bool',
>> +            '*late-block-activate': 'bool',
>> +            '*x-ignore-shared': { 'type': 'bool', 'features': [ 'unstable' ] },
>> +            '*validate-uuid': 'bool',
>> +            '*background-snapshot': 'bool',
>> +            '*zero-copy-send': 'bool',
>> +            '*postcopy-preempt': 'bool',
>> +            '*switchover-ack': 'bool',
>> +            '*dirty-limit': 'bool',
>> +            '*mapped-ram': 'bool' } }
>
> This is part 2 "unify capabilities into parameters".
>
> Missing in your series: deprecate migrate-set-capabilities and
> query-migrate-capabilities.
>

I'll add when repost.

>>  
>>  ##
>>  # @MigrationParameters:
>> @@ -2395,3 +2515,40 @@
>>    'data': { 'job-id': 'str',
>>              'tag': 'str',
>>              'devices': ['str'] } }
>> +
>> +##
>> +# @MigrationConfig:
>> +#
>> +# Migration configuration options
>> +#
>> +# @tls-creds: ID of the 'tls-creds' object that provides credentials
>> +#     for establishing a TLS connection over the migration data
>> +#     channel.  On the outgoing side of the migration, the credentials
>> +#     must be for a 'client' endpoint, while for the incoming side the
>> +#     credentials must be for a 'server' endpoint.  Setting this to a
>> +#     non-empty string enables TLS for all migrations.  An empty
>> +#     string means that QEMU will use plain text mode for migration,
>> +#     rather than TLS.  (Since 2.7)
>
> Is "empty string" expected to occur here?
>

Yes. Up until this point I'm keeping everything compatible, only
switching to use MigrationConfig internally. Come patch 12, when we
expose this type externally, it will be time to decide whether to
declare that the new way of setting migration configuration options
treats the empty string as invalid. I haven't made the call yet because
I wanted some feedback on the new commands, backward compatibility, etc.

>> +#
>> +# @tls-hostname: migration target's hostname for validating the
>> +#     server's x509 certificate identity.  If empty, QEMU will use the
>> +#     hostname from the migration URI, if any.  A non-empty value is
>> +#     required when using x509 based TLS credentials and the migration
>> +#     URI does not include a hostname, such as fd: or exec: based
>> +#     migration.  (Since 2.7)
>> +#
>> +#     Note: empty value works only since 2.9.
>
> Likewise.
>

Same here.

>> +#
>> +# @tls-authz: ID of the 'authz' object subclass that provides access
>> +#     control checking of the TLS x509 certificate distinguished name.
>> +#     This object is only resolved at time of use, so can be deleted
>> +#     and recreated on the fly while the migration server is active.
>> +#     If missing, it will default to denying access (Since 4.0)
>> +#
>> +# Since: 10.1
>> +##
>> +{ 'struct': 'MigrationConfig',
>> +  'base': 'MigrationConfigBase',
>> +  'data': { '*tls-creds': 'str',
>> +            '*tls-hostname': 'str',
>> +            '*tls-authz': 'str' } }
>
> This is part 1 "enable cleanup".

Ok.

Thanks for the review!


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure
  2025-05-23 13:38     ` Fabiano Rosas
@ 2025-05-26  7:37       ` Markus Armbruster
  0 siblings, 0 replies; 44+ messages in thread
From: Markus Armbruster @ 2025-05-26  7:37 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Daniel P . Berrangé

Fabiano Rosas <farosas@suse.de> writes:

> Markus Armbruster <armbru@redhat.com> writes:
>
> Markus, sorry for the delay here. I had vacations and holidays, plus a
> pile of patches to review.

No problem.  Hope you enjoyed your time off!

>> Fabiano Rosas <farosas@suse.de> writes:
>>
>>> Add a new migration structure to consolidate the capabilities and
>>> parameters. This structure will be used in place of the s->parameters
>>> and s->capabilities data structures in the next few patches.
>>>
>>> The QAPI migration types now look like this:
>>>
>>> /* options previously known as parameters */
>>
>> Configuration previously known as parameters less the TLS stuff.
>>
>>> { 'struct': 'MigrationConfigBase',
>>>   'data': {
>>>       <parameters>
>>> } }
>>>
>>>
>>> /* for compat with query-migrate-parameters */
>>> { 'struct': 'MigrationParameters',
>>>   'base': 'MigrationConfigBase',
>>>   'data': {
>>>       <TLS options in 'str' format>
>>> } }
>>>
>>> /* for compat with migrate-set-parameters */
>>> { 'struct': 'MigrateSetParameters',
>>>   'base': 'MigrationConfigBase',
>>>   'data': {
>>>       <TLS options in 'StrOrNull' format>
>>> } }
>>
>> Yes, this is the state since PATCH 05.
>>
>>> /* to replace MigrationParameters in the MigrationState */
>>> { 'struct': 'MigrationConfig',
>>>   'base': 'MigrationConfigBase'
>>>   'data': {
>>>     <TLS options in 'str' format>
>>> } }
>>
>> This is new in this patch.
>>
>> Your description doesn't cover optionalness.  Here's my understanding:
>>
>> * MigrationSetParameters has optional members, because
>>   migrate-set-parameters needs that.
>>
>
> Yes.
>
>> * MigrationParameters would ideally have these members non-optional,
>>   because query-migrate-parameters wants that.
>>
>
> Yes.
>
>> * But to enable sharing via common base type MigrationConfigBase, we
>>   accept unwanted optional in MigrationParameters and thus
>>   query-migrate-parameters.
>>
>
> Yes.
>
>> * This doesn't apply to the non-shared members of MigrationParameters,
>>   so you made them non-optional.  These are @tls-creds, @tls-hostname,
>>   @tls-authz.
>>
>
> Yes.
>
>> * But in MigrationConfig they're optional again, because "empty string
>>   means absent" is silly; we want "NULL means absent".
>>
>
> Yes. But mostly because MigrationConfig will become the type for the new
> '*config' argument to migrate/migrate_incoming (patches 12 & 13) and we
> want to keep all members optional. Otherwise the user would have to pass
> all ~50 migration options in every migrate command, which is bad IMO.

Got it.

>> Correct?
>>
>> Up to here, this enables cleanup of some "empty string means absent"
>> silliness in later patches.
>>
>> The remainder is about unifying capabilities into parameters.  I'd split
>> the patch (but I'm a compulsive patch splitter).
>>
>>> The above keeps the query/set-parameters commands stable. For the
>>> capabilities as well as the options added in the future, we have a
>>> choice of where to put them:
>>>
>>> 1) In MigrationConfigBase, this means that the existing
>>>    query/set-parameters commands will be updated to deal with
>>>    capabilities/new options.
>>>
>>>   { 'struct': 'MigrationConfigBase',
>>>     'data': {
>>>       <parameters>
>>>       <capabilities>
>>>       <new opts>
>>>   } }
>>>
>>>   { 'struct': 'MigrationConfig',
>>>     'base': 'MigrationConfigBase'
>>>     'data': {
>>>       <TLS options in 'str' format>
>>>   } }
>>>
>>> 2) In MigrationConfig, this means that the existing commands will be
>>>    frozen in time.
>>>
>>>   { 'struct': 'MigrationConfigBase',
>>>     'data': {
>>>       <parameters>
>>>   } }
>>>
>>>   { 'struct': 'MigrationConfig',
>>>     'base': 'MigrationConfigBase'
>>>     'data': {
>>>       <TLS options in 'str' format>
>>>       <capabilities>
>>>       <new opts>
>>>   } }
>>>
>>> For now, I've chosen the option 1, all capabilities and new options go
>>> into MigrationConfigBase. This gives the option to keep the existing
>>> commands for as long as we'd like.
>>
>> Perhaps this would be slightly easier to digest for the reader if you
>> talked just about capabilities at first.  Once that's understood,
>> mention we have the same choice for new configuration bits.
>>
>
> Ok, I'll reorganize, along with the other comments you've made.
>
>>> Note that the query/set capabilities commands will have to go, we can
>>> treat parameters as generic configuration options, but capabilities
>>> are just too different.
>>
>> I think the argument is that migration capabilities are a pointless
>> interface complication.  One mechanism (parameters) is better than two
>> (parameters and capabilities).
>>
>
> Yes, that's the main point indeed.

Perhaps you can make this point more prominently.

>>> Signed-off-by: Fabiano Rosas <farosas@suse.de>

[...]



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 12/13] [PoC] migration: Add query/set commands for MigrationConfig
  2025-04-11 19:14 ` [RFC PATCH 12/13] [PoC] migration: Add query/set commands for MigrationConfig Fabiano Rosas
@ 2025-05-26  7:51   ` Markus Armbruster
  2025-05-27 22:14     ` Fabiano Rosas
  0 siblings, 1 reply; 44+ messages in thread
From: Markus Armbruster @ 2025-05-26  7:51 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Daniel P . Berrangé

Fabiano Rosas <farosas@suse.de> writes:

> Add the QMP commands query-migrate-config and migrate-set-config to
> read and write the migration configuration options.

These supersede query-migrate-capabilities, query-migrate-parameters,
migrate-set-capabilities, and migrate-set-parameters, right?

> Signed-off-by: Fabiano Rosas <farosas@suse.de>
> ---
>  migration/options.c | 24 ++++++++++++++++++++++++
>  migration/options.h |  2 +-
>  qapi/migration.json | 42 ++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 67 insertions(+), 1 deletion(-)
>
> diff --git a/migration/options.c b/migration/options.c
> index 4e3792dec3..c85ee2e506 100644
> --- a/migration/options.c
> +++ b/migration/options.c
> @@ -1441,3 +1441,27 @@ void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
>      migrate_config_apply(&tmp);
>      migrate_post_update_config(&tmp, errp);
>  }
> +
> +void qmp_migrate_set_config(MigrationConfig *config, Error **errp)
> +{
> +    if (!migrate_config_check(config, errp)) {
> +        /* Invalid parameter */
> +        return;
> +    }
> +
> +    migrate_config_apply(config);
> +    migrate_post_update_config(config, errp);
> +}
> +
> +MigrationConfig *qmp_query_migrate_config(Error **errp)
> +{
> +    MigrationState *s = migrate_get_current();
> +    MigrationConfig *config = g_new0(MigrationConfig, 1);
> +
> +    QAPI_CLONE_MEMBERS(MigrationConfig, config, &s->config);
> +
> +    /* set the has_* fields for every option */
> +    migrate_config_init(config);
> +
> +    return config;
> +}
> diff --git a/migration/options.h b/migration/options.h
> index 61ee854bb0..0e36dafe80 100644
> --- a/migration/options.h
> +++ b/migration/options.h
> @@ -72,7 +72,7 @@ uint64_t migrate_xbzrle_cache_size(void);
>  ZeroPageDetection migrate_zero_page_detection(void);
>  
>  bool migrate_config_check(MigrationConfig *params, Error **errp);
> -void migrate_config_init(MigrationConfig *params);
> +void migrate_config_init(MigrationConfig *config);

Have you considered renaming the declaration's parameter when you change
its type in PATCH 08, or when you rename the definition's parameter in
PATCH 11?

>  bool migrate_config_get_cap_compat(MigrationConfig *config, int i);
>  bool migrate_caps_check(MigrationConfig *new, Error **errp);
>  #endif
> diff --git a/qapi/migration.json b/qapi/migration.json
> index 5e39f21adc..bb2487dbc6 100644
> --- a/qapi/migration.json
> +++ b/qapi/migration.json
> @@ -2552,3 +2552,45 @@
>    'data': { '*tls-creds': 'str',
>              '*tls-hostname': 'str',
>              '*tls-authz': 'str' } }
> +
> +##
> +# @query-migrate-config:
> +#
> +# Returns information about the current migration configuration
> +# options
> +#
> +# Returns: @MigrationConfig
> +#
> +# Since: 10.1
> +#
> +# .. qmp-example::
> +#
> +#     -> { "execute": "query-migrate-config" }
> +#     <- { "return": {
> +#              "multifd-channels": 2,
> +#              "cpu-throttle-increment": 10,
> +#              "cpu-throttle-initial": 20,
> +#              "max-bandwidth": 33554432,
> +#              "downtime-limit": 300
> +#           }
> +#        }
> +##
> +{ 'command': 'query-migrate-config',
> +  'returns': 'MigrationConfig' }
> +
> +##
> +# @migrate-set-config:
> +#
> +# Set various migration configuration options.
> +#
> +# Since: 10.1
> +#
> +# .. qmp-example::
> +#
> +#     -> { "execute": "migrate-set-config" ,
> +#          "arguments": { "max-bandwidth": 33554432,
> +#                         "downtime-limit": 300 } }
> +#     <- { "return": {} }
> +##
> +{ 'command': 'migrate-set-config', 'boxed': true,
> +  'data': 'MigrationConfig' }

This patch exposes MigrationConfig externally.  We should double-check
its documentation to make sure it's what we want there.  Known issue:
how to reset @tls-creds & friends.  Touched in my review of PATCH 07.



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 13/13] [PoC] migration: Allow migrate commands to provide the migration config
  2025-04-11 19:14 ` [RFC PATCH 13/13] [PoC] migration: Allow migrate commands to provide the migration config Fabiano Rosas
@ 2025-05-26  8:03   ` Markus Armbruster
  2025-05-26 15:10     ` Peter Xu
  0 siblings, 1 reply; 44+ messages in thread
From: Markus Armbruster @ 2025-05-26  8:03 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu, Daniel P . Berrangé

Fabiano Rosas <farosas@suse.de> writes:

> Allow the migrate and migrate_incoming commands to pass the migration
> configuration options all at once, dispensing the use of
> migrate-set-parameters and migrate-set-capabilities.
>
> The motivation of this is to simplify the interface with the
> management layer and avoid the usage of several command invocations to
> configure a migration. It also avoids stale parameters from a previous
> migration to influence the current migration.
>
> The options that are changed during the migration can still be set
> with the existing commands.
>
> Signed-off-by: Fabiano Rosas <farosas@suse.de>
> ---
>  migration/migration-hmp-cmds.c |  5 +++--
>  migration/migration.c          |  8 ++++----
>  qapi/migration.json            | 10 ++++++++++
>  system/vl.c                    |  3 ++-
>  4 files changed, 19 insertions(+), 7 deletions(-)
>
> diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c
> index 49c26daed3..44d2265002 100644
> --- a/migration/migration-hmp-cmds.c
> +++ b/migration/migration-hmp-cmds.c
> @@ -429,7 +429,7 @@ void hmp_migrate_incoming(Monitor *mon, const QDict *qdict)
>      }
>      QAPI_LIST_PREPEND(caps, g_steal_pointer(&channel));
>  
> -    qmp_migrate_incoming(NULL, true, caps, true, false, &err);
> +    qmp_migrate_incoming(NULL, true, caps, NULL, true, false, &err);
>      qapi_free_MigrationChannelList(caps);
>  
>  end:
> @@ -715,7 +715,8 @@ void hmp_migrate(Monitor *mon, const QDict *qdict)
>      }
>      QAPI_LIST_PREPEND(caps, g_steal_pointer(&channel));
>  
> -    qmp_migrate(NULL, true, caps, false, false, true, resume, &err);
> +    qmp_migrate(NULL, true, caps, NULL, false, false, true, resume,
> +                &err);
>      if (hmp_handle_error(mon, err)) {
>          return;
>      }
> diff --git a/migration/migration.c b/migration/migration.c
> index 55d839abd0..a1f04cef32 100644
> --- a/migration/migration.c
> +++ b/migration/migration.c
> @@ -1894,8 +1894,8 @@ void migrate_del_blocker(Error **reasonp)
>  
>  void qmp_migrate_incoming(const char *uri, bool has_channels,
>                            MigrationChannelList *channels,
> -                          bool has_exit_on_error, bool exit_on_error,
> -                          Error **errp)
> +                          MigrationConfig *config, bool has_exit_on_error,
> +                          bool exit_on_error, Error **errp)
>  {
>      Error *local_err = NULL;
>      static bool once = true;
> @@ -2159,8 +2159,8 @@ static gboolean qmp_migrate_finish_cb(QIOChannel *channel,
>      return G_SOURCE_REMOVE;
>  }
>  
> -void qmp_migrate(const char *uri, bool has_channels,
> -                 MigrationChannelList *channels, bool has_detach, bool detach,
> +void qmp_migrate(const char *uri, bool has_channels, MigrationChannelList *channels,
> +                 MigrationConfig *config, bool has_detach, bool detach,
>                   bool has_resume, bool resume, Error **errp)
>  {
>      bool resume_requested;
> diff --git a/qapi/migration.json b/qapi/migration.json
> index bb2487dbc6..5bd8f0f1b2 100644
> --- a/qapi/migration.json
> +++ b/qapi/migration.json
> @@ -1638,6 +1638,10 @@
   ##
   # @migrate:
   #
   # Migrates the current running guest to another Virtual Machine.
   #
   # @uri: the Uniform Resource Identifier of the destination VM
   #
   # @channels: list of migration stream channels with each stream in the
   #     list connected to a destination interface endpoint.
   #
   # @detach: this argument exists only for compatibility reasons and is
   #     ignored by QEMU
>  #
>  # @resume: resume one paused migration, default "off".  (since 3.0)

Unrelated to this patch, but here goes anyway.  What happens if I pass
@uri and @channels with I "resume": true, and they differ from the ones
passed originally?

>  #
> +# @config: migration configuration options, previously set via
> +#     @migrate-set-parameters and @migrate-set-capabilities.  (since
> +#     10.1)
> +#
>  # Since: 0.14
>  #
>  # .. admonition:: Notes
> @@ -1702,6 +1706,7 @@
>  { 'command': 'migrate',
>    'data': {'*uri': 'str',
>             '*channels': [ 'MigrationChannel' ],
> +           '*config': 'MigrationConfig',
>             '*detach': 'bool', '*resume': 'bool' } }
>  

Some configuration is passed in individual arguments, the remainder
wrapped in a struct.

Here's how we could avoid this.  'data': 'MigrateArguments' where
MigrateArguments is a struct with base MigrationConfig that adds the
other arguments.  Add 'boxed': true if you like.

Not sure it's wortwhile, but I wanted to show you for your
consideration.

>  ##
> @@ -1721,6 +1726,10 @@
>  #     error details could be retrieved with query-migrate.
>  #     (since 9.1)
>  #
> +# @config: migration configuration options, previously set via
> +#     @migrate-set-parameters and @migrate-set-capabilities.  (since
> +#     10.1)
> +#
>  # Since: 2.3
>  #
>  # .. admonition:: Notes
> @@ -1774,6 +1783,7 @@
>  { 'command': 'migrate-incoming',
>               'data': {'*uri': 'str',
>                        '*channels': [ 'MigrationChannel' ],
> +                      '*config': 'MigrationConfig',
>                        '*exit-on-error': 'bool' } }
>  
>  ##

Likewise.

> diff --git a/system/vl.c b/system/vl.c
> index ec93988a03..ea7040ef8d 100644
> --- a/system/vl.c
> +++ b/system/vl.c
> @@ -2826,7 +2826,8 @@ void qmp_x_exit_preconfig(Error **errp)
>                  g_new0(MigrationChannelList, 1);
>  
>              channels->value = incoming_channels[MIGRATION_CHANNEL_TYPE_MAIN];
> -            qmp_migrate_incoming(NULL, true, channels, true, true, &local_err);
> +            qmp_migrate_incoming(NULL, true, channels, NULL, true, true,
> +                                 &local_err);
>              if (local_err) {
>                  error_reportf_err(local_err, "-incoming %s: ", incoming);
>                  exit(1);



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 04/13] migration: Fix parameter validation
  2025-05-22 17:39       ` Fabiano Rosas
@ 2025-05-26 13:09         ` Peter Xu
  2025-05-26 15:41           ` Fabiano Rosas
  0 siblings, 1 reply; 44+ messages in thread
From: Peter Xu @ 2025-05-26 13:09 UTC (permalink / raw)
  To: Fabiano Rosas; +Cc: qemu-devel, Markus Armbruster, Daniel P . Berrangé

On Thu, May 22, 2025 at 02:39:48PM -0300, Fabiano Rosas wrote:
> Actually, this doesn't work...
> 
> The migrate-set-* commands have optional fields, so we need some form of
> checking has_* to know which fields the user is setting. Otherwise
> MigrationSetParameters will have zeros all over that will trip the
> check.
> 
> Then, we need some form of checking has_* to be able to enventually get
> the values into s->config (former s->parameters/capabilities), otherwise
> we'll overwrite the already-set values with the potentially empty ones
> coming from QAPI.
> 
> Then there's also the issue of knowing whether a value is 0 because the
> user set it 0 or because it was never set.
> 
> We also can't apply an invalid value to s->config and validate it after
> because some parameters are allowed to be changed during migration.

What I meant was we only conditionally ignore the has_* fields in below:

  (1) migrate_params_check(), so that QEMU always checks all parameters in
      the MigrationParameters* specified when invoking the function.

  (2) MigrationState.parameters, so that as long as the parameters are
      applied (it should only happen after sanity check all pass..) then we
      ignore these has_* fields (until MigrationState.parameters can have a
      better struct to not include these has_* fields).

We can keep the has_* checks in migrate_params_test_apply() and
migrate_params_apply(), so that we won't touch the ones the user didn't
specify in the QMP commands as you said.

The benefits of having above 1/2 ignoring has_* is some code removal where
we assume has_* always are TRUEs.

This can be still a bit confusing, but at least we don't need to init has_*
fields in migrate_params_init() anymore as they'll be all ignored, then
there's no chance we forget set TRUEs to any new params either.

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 13/13] [PoC] migration: Allow migrate commands to provide the migration config
  2025-05-26  8:03   ` Markus Armbruster
@ 2025-05-26 15:10     ` Peter Xu
  0 siblings, 0 replies; 44+ messages in thread
From: Peter Xu @ 2025-05-26 15:10 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Fabiano Rosas, qemu-devel, Daniel P . Berrangé

On Mon, May 26, 2025 at 10:03:55AM +0200, Markus Armbruster wrote:
> > diff --git a/qapi/migration.json b/qapi/migration.json
> > index bb2487dbc6..5bd8f0f1b2 100644
> > --- a/qapi/migration.json
> > +++ b/qapi/migration.json
> > @@ -1638,6 +1638,10 @@
>    ##
>    # @migrate:
>    #
>    # Migrates the current running guest to another Virtual Machine.
>    #
>    # @uri: the Uniform Resource Identifier of the destination VM
>    #
>    # @channels: list of migration stream channels with each stream in the
>    #     list connected to a destination interface endpoint.
>    #
>    # @detach: this argument exists only for compatibility reasons and is
>    #     ignored by QEMU
> >  #
> >  # @resume: resume one paused migration, default "off".  (since 3.0)
> 
> Unrelated to this patch, but here goes anyway.  What happens if I pass
> @uri and @channels with I "resume": true, and they differ from the ones
> passed originally?

I can answer this one - "resume" was designed to work always with new
channels/URI passed in.  It was currently only used for postcopy to resume
a paused postcopy migration where the old URI/channels stopped working
already.

The doc is indeed not obvious to show that, and can be confusing.  If
anyone thinks worthwhile, I can send a patch to touch it up:

diff --git a/qapi/migration.json b/qapi/migration.json
index 8b9c53595c..a4c9272e8b 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -1658,7 +1658,10 @@
 # @detach: this argument exists only for compatibility reasons and is
 #     ignored by QEMU
 #
-# @resume: resume one paused migration, default "off".  (since 3.0)
+# @resume: when set, resume one paused postcopy migration, using the new
+#     URI/channels specified to replace the old/broken channels.  The user
+#     should make sure the migration is in "postcopy-paused" state before
+#     the resume request.  Default "off".  (since 3.0)
 #
 # Since: 0.14
 #

Thanks,

-- 
Peter Xu



^ permalink raw reply related	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 04/13] migration: Fix parameter validation
  2025-05-26 13:09         ` Peter Xu
@ 2025-05-26 15:41           ` Fabiano Rosas
  0 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-05-26 15:41 UTC (permalink / raw)
  To: Peter Xu; +Cc: qemu-devel, Markus Armbruster, Daniel P . Berrangé

Peter Xu <peterx@redhat.com> writes:

> On Thu, May 22, 2025 at 02:39:48PM -0300, Fabiano Rosas wrote:
>> Actually, this doesn't work...
>> 
>> The migrate-set-* commands have optional fields, so we need some form of
>> checking has_* to know which fields the user is setting. Otherwise
>> MigrationSetParameters will have zeros all over that will trip the
>> check.
>> 
>> Then, we need some form of checking has_* to be able to enventually get
>> the values into s->config (former s->parameters/capabilities), otherwise
>> we'll overwrite the already-set values with the potentially empty ones
>> coming from QAPI.
>> 
>> Then there's also the issue of knowing whether a value is 0 because the
>> user set it 0 or because it was never set.
>> 
>> We also can't apply an invalid value to s->config and validate it after
>> because some parameters are allowed to be changed during migration.
>
> What I meant was we only conditionally ignore the has_* fields in below:
>
>   (1) migrate_params_check(), so that QEMU always checks all parameters in
>       the MigrationParameters* specified when invoking the function.
>

Yes, I realised later that's what you meant. I'm looking into
it. Hitting some issues with the block_bitmap_mapping option, which
seems to be able to become NULL even if has_block_bitmap_mapping is
true. According to commit 3cba22c9ad ("migration: Fix
block_bitmap_mapping migration").

>   (2) MigrationState.parameters, so that as long as the parameters are
>       applied (it should only happen after sanity check all pass..) then we
>       ignore these has_* fields (until MigrationState.parameters can have a
>       better struct to not include these has_* fields).
>
> We can keep the has_* checks in migrate_params_test_apply() and
> migrate_params_apply(), so that we won't touch the ones the user didn't
> specify in the QMP commands as you said.
>
> The benefits of having above 1/2 ignoring has_* is some code removal where
> we assume has_* always are TRUEs.
>
> This can be still a bit confusing, but at least we don't need to init has_*
> fields in migrate_params_init() anymore as they'll be all ignored, then
> there's no chance we forget set TRUEs to any new params either.



^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [RFC PATCH 12/13] [PoC] migration: Add query/set commands for MigrationConfig
  2025-05-26  7:51   ` Markus Armbruster
@ 2025-05-27 22:14     ` Fabiano Rosas
  0 siblings, 0 replies; 44+ messages in thread
From: Fabiano Rosas @ 2025-05-27 22:14 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel, Peter Xu, Daniel P . Berrangé

Markus Armbruster <armbru@redhat.com> writes:

> Fabiano Rosas <farosas@suse.de> writes:
>
>> Add the QMP commands query-migrate-config and migrate-set-config to
>> read and write the migration configuration options.
>
> These supersede query-migrate-capabilities, query-migrate-parameters,
> migrate-set-capabilities, and migrate-set-parameters, right?
>

They could. We haven't made a decision at this moment.

>> Signed-off-by: Fabiano Rosas <farosas@suse.de>
>> ---
>>  migration/options.c | 24 ++++++++++++++++++++++++
>>  migration/options.h |  2 +-
>>  qapi/migration.json | 42 ++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 67 insertions(+), 1 deletion(-)
>>
>> diff --git a/migration/options.c b/migration/options.c
>> index 4e3792dec3..c85ee2e506 100644
>> --- a/migration/options.c
>> +++ b/migration/options.c
>> @@ -1441,3 +1441,27 @@ void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
>>      migrate_config_apply(&tmp);
>>      migrate_post_update_config(&tmp, errp);
>>  }
>> +
>> +void qmp_migrate_set_config(MigrationConfig *config, Error **errp)
>> +{
>> +    if (!migrate_config_check(config, errp)) {
>> +        /* Invalid parameter */
>> +        return;
>> +    }
>> +
>> +    migrate_config_apply(config);
>> +    migrate_post_update_config(config, errp);
>> +}
>> +
>> +MigrationConfig *qmp_query_migrate_config(Error **errp)
>> +{
>> +    MigrationState *s = migrate_get_current();
>> +    MigrationConfig *config = g_new0(MigrationConfig, 1);
>> +
>> +    QAPI_CLONE_MEMBERS(MigrationConfig, config, &s->config);
>> +
>> +    /* set the has_* fields for every option */
>> +    migrate_config_init(config);
>> +
>> +    return config;
>> +}
>> diff --git a/migration/options.h b/migration/options.h
>> index 61ee854bb0..0e36dafe80 100644
>> --- a/migration/options.h
>> +++ b/migration/options.h
>> @@ -72,7 +72,7 @@ uint64_t migrate_xbzrle_cache_size(void);
>>  ZeroPageDetection migrate_zero_page_detection(void);
>>  
>>  bool migrate_config_check(MigrationConfig *params, Error **errp);
>> -void migrate_config_init(MigrationConfig *params);
>> +void migrate_config_init(MigrationConfig *config);
>
> Have you considered renaming the declaration's parameter when you change
> its type in PATCH 08, or when you rename the definition's parameter in
> PATCH 11?
>

Yes, that looks like a mistake during rebase.

>>  bool migrate_config_get_cap_compat(MigrationConfig *config, int i);
>>  bool migrate_caps_check(MigrationConfig *new, Error **errp);
>>  #endif
>> diff --git a/qapi/migration.json b/qapi/migration.json
>> index 5e39f21adc..bb2487dbc6 100644
>> --- a/qapi/migration.json
>> +++ b/qapi/migration.json
>> @@ -2552,3 +2552,45 @@
>>    'data': { '*tls-creds': 'str',
>>              '*tls-hostname': 'str',
>>              '*tls-authz': 'str' } }
>> +
>> +##
>> +# @query-migrate-config:
>> +#
>> +# Returns information about the current migration configuration
>> +# options
>> +#
>> +# Returns: @MigrationConfig
>> +#
>> +# Since: 10.1
>> +#
>> +# .. qmp-example::
>> +#
>> +#     -> { "execute": "query-migrate-config" }
>> +#     <- { "return": {
>> +#              "multifd-channels": 2,
>> +#              "cpu-throttle-increment": 10,
>> +#              "cpu-throttle-initial": 20,
>> +#              "max-bandwidth": 33554432,
>> +#              "downtime-limit": 300
>> +#           }
>> +#        }
>> +##
>> +{ 'command': 'query-migrate-config',
>> +  'returns': 'MigrationConfig' }
>> +
>> +##
>> +# @migrate-set-config:
>> +#
>> +# Set various migration configuration options.
>> +#
>> +# Since: 10.1
>> +#
>> +# .. qmp-example::
>> +#
>> +#     -> { "execute": "migrate-set-config" ,
>> +#          "arguments": { "max-bandwidth": 33554432,
>> +#                         "downtime-limit": 300 } }
>> +#     <- { "return": {} }
>> +##
>> +{ 'command': 'migrate-set-config', 'boxed': true,
>> +  'data': 'MigrationConfig' }
>
> This patch exposes MigrationConfig externally.  We should double-check
> its documentation to make sure it's what we want there.  Known issue:
> how to reset @tls-creds & friends.  Touched in my review of PATCH 07.

I'll take a closer look at tls options after Daniel suggested changing
MigrationParameters' TLS options type from str to StrOrNull. That allows
us to drop MigrateSetParameters entirely.


^ permalink raw reply	[flat|nested] 44+ messages in thread

end of thread, other threads:[~2025-05-27 22:15 UTC | newest]

Thread overview: 44+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-11 19:14 [RFC PATCH 00/13] migration: Unify capabilities and parameters Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 01/13] migration: Fix latent bug in migrate_params_test_apply() Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 02/13] migration: Normalize tls arguments Fabiano Rosas
2025-04-14 16:30   ` Daniel P. Berrangé
2025-04-11 19:14 ` [RFC PATCH 03/13] migration: Run a post update routine after setting parameters Fabiano Rosas
2025-05-15 20:42   ` Peter Xu
2025-04-11 19:14 ` [RFC PATCH 04/13] migration: Fix parameter validation Fabiano Rosas
2025-05-15 20:59   ` Peter Xu
2025-05-22 16:39     ` Fabiano Rosas
2025-05-22 17:39       ` Fabiano Rosas
2025-05-26 13:09         ` Peter Xu
2025-05-26 15:41           ` Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 05/13] migration: Reduce a bit of duplication in migration.json Fabiano Rosas
2025-04-14 16:38   ` Daniel P. Berrangé
2025-04-14 17:02     ` Fabiano Rosas
2025-04-16 13:38       ` Markus Armbruster
2025-04-16 14:41         ` Fabiano Rosas
2025-04-17  5:56           ` Markus Armbruster
2025-04-17 18:45   ` Markus Armbruster
2025-04-18  6:40     ` Markus Armbruster
2025-04-11 19:14 ` [RFC PATCH 06/13] migration: Remove the parameters copy during validation Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 07/13] migration: Introduce new MigrationConfig structure Fabiano Rosas
2025-04-18  7:03   ` Markus Armbruster
2025-05-23 13:38     ` Fabiano Rosas
2025-05-26  7:37       ` Markus Armbruster
2025-04-11 19:14 ` [RFC PATCH 08/13] migration: Replace s->parameters with s->config Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 09/13] migration: Do away with usage of QERR_INVALID_PARAMETER_VALUE Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 10/13] migration: Replace s->capabilities with s->config Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 11/13] migration: Merge parameters and capability checks Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 12/13] [PoC] migration: Add query/set commands for MigrationConfig Fabiano Rosas
2025-05-26  7:51   ` Markus Armbruster
2025-05-27 22:14     ` Fabiano Rosas
2025-04-11 19:14 ` [RFC PATCH 13/13] [PoC] migration: Allow migrate commands to provide the migration config Fabiano Rosas
2025-05-26  8:03   ` Markus Armbruster
2025-05-26 15:10     ` Peter Xu
2025-04-14 16:44 ` [RFC PATCH 00/13] migration: Unify capabilities and parameters Daniel P. Berrangé
2025-04-14 17:12   ` Fabiano Rosas
2025-04-14 17:20     ` Daniel P. Berrangé
2025-04-14 17:40       ` Fabiano Rosas
2025-04-14 19:06         ` Daniel P. Berrangé
2025-05-15 20:21         ` Peter Xu
2025-04-16 13:44   ` Markus Armbruster
2025-04-16 15:00     ` Fabiano Rosas
2025-04-24  9:35       ` Markus Armbruster

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).