[PATCH libvirt v1 0/4] s390x CPU model compatibility check for incoming migration
This series extends the qemu-cpu-model-comparison QMP interface in libvirt to surface the "responsible-properties" array returned by QEMU, and uses that information to perform a pre-flight CPU compatibility check on s390x before an incoming migration is accepted. Background ---------- On x86, QEMU rejects an incompatible CPU model at domain start time and produces a meaningful error. On s390x the guest starts successfully but may fail later in an opaque way if the destination host lacks required CPU features. This series brings s390x in line with x86 by catching the incompatibility early, aborting the migration cleanly, and reporting exactly which CPU properties are responsible. Functional Test Instructions ----------------------------- Prerequisites: - Two s390x hosts (source and destination) with different CPU feature levels from the same generation lineage (e.g. z16 and z17). - libvirt and QEMU built from this series on both hosts. - QEMU must support the query-cpu-model-comparison QMP command (QEMU >= 5.1). Test case 1 — Incompatible CPU model is rejected cleanly: 1. Define a guest on the source host using a custom s390x CPU model that includes features not present on the destination host. 2. Initiate a live migration to the destination host: virsh migrate --live <domain> qemu+ssh://dest-host/system 3. Expected result: migration fails immediately with a clear libvirt error before QEMU starts on the destination, e.g.: error: operation failed: guest CPU model is a newer generation than the destination host CPU model or, for a feature-level mismatch within the same generation: error: operation failed: destination host CPU model is missing features required by the guest CPU model: <prop1>, <prop2> 4. Verify the guest is still running cleanly on the source host: virsh domstate <domain> # should report: running Test case 2 — Compatible CPU model migrates successfully: 1. Define a guest using a CPU model fully supported by the destination (e.g. gen16a-base guest migrating to a gen17 host). 2. Initiate the same live migration. 3. Expected result: migration completes successfully with no errors. Rorie Reyes (4): qemu: extend GetCPUModelComparison to return responsible-properties qemu: propagate responsibleProps parameter through monitor layer qemu: introduce qemuProcessCPUModelComparison helper qemu: check CPU model compatibility on s390x before incoming migration src/qemu/qemu_driver.c | 20 ++--- src/qemu/qemu_migration.c | 114 +++++++++++++++++++++++++ src/qemu/qemu_monitor.c | 8 +- src/qemu/qemu_monitor.h | 3 +- src/qemu/qemu_monitor_json.c | 26 +++++- src/qemu/qemu_monitor_json.h | 5 +- src/qemu/qemu_process.c | 50 +++++++++++ src/qemu/qemu_process.h | 21 +++++ tests/qemumonitorjsontest.c | 159 +++++++++++++++++++++++++++++++++-- 9 files changed, 381 insertions(+), 25 deletions(-) -- 2.48.1
The qemu-cpu-model-comparison QMP command can return a "responsible-properties" array identifying which CPU properties caused an incompatibility result. Expose this through the JSON monitor layer by adding an optional `responsibleProps` output parameter (char ***) to qemuMonitorJSONGetCPUModelComparison(). When the caller passes a non-NULL pointer the array is populated from the QMP response; passing NULL preserves the existing behaviour so all current callers are unaffected until they opt in. The existing caller in qemu_monitor.c is updated to pass NULL so the build continues to work. A GenericError response from the QMP command is intercepted and re-reported as VIR_ERR_CPU_INCOMPATIBLE with the QEMU error description. Assuming the JSON query was properly constructed, all GenericErrors should indicate an incompatible CPU model due to QEMU binary limitations (e.g. old binary does not recognize newer features or models). The existing unit test is updated to pass the new argument and extended with additional calls: one that verifies the responsible-properties array is correctly populated from the QMP response, one that verifies it is NULL when the key is absent from the response, one that verifies it is NULL when the array is empty, and cases for "identical", "subset", and "superset" results (including a "type" responsible-property) to cover all result-string mappings. Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_monitor.c | 2 +- src/qemu/qemu_monitor_json.c | 26 +++++- src/qemu/qemu_monitor_json.h | 5 +- tests/qemumonitorjsontest.c | 159 +++++++++++++++++++++++++++++++++-- 4 files changed, 182 insertions(+), 10 deletions(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 2c86cd1971..e3edc88814 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -3335,7 +3335,7 @@ qemuMonitorGetCPUModelComparison(qemuMonitor *mon, QEMU_CHECK_MONITOR(mon); - return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, result); + return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, result, NULL); } diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 8e88dc9442..a895341b67 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -5689,7 +5689,8 @@ int qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, virCPUDef *cpu_b, - char **result) + char **result, + char ***responsibleProps) { g_autoptr(virJSONValue) model_a = NULL; g_autoptr(virJSONValue) model_b = NULL; @@ -5697,6 +5698,11 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, g_autoptr(virJSONValue) reply = NULL; const char *data_result; virJSONValue *data; + virJSONValue *propsArray; + + *result = NULL; + if (responsibleProps) + *responsibleProps = NULL; if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true, false)) || !(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true, false))) @@ -5711,6 +5717,13 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) return -1; + if (qemuMonitorJSONHasError(reply, "GenericError")) { + virJSONValue *error = virJSONValueObjectGet(reply, "error"); + virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", + qemuMonitorJSONStringifyError(error)); + return -1; + } + if (qemuMonitorJSONCheckError(cmd, reply) < 0) return -1; @@ -5723,6 +5736,17 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, } *result = g_strdup(data_result); + + propsArray = virJSONValueObjectGetArray(data, "responsible-properties"); + + if (responsibleProps && propsArray && + virJSONValueArraySize(propsArray) > 0 && + !(*responsibleProps = virJSONValueArrayToStringList(propsArray))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Invalid 'responsible-properties' list in query-cpu-model-comparison response")); + return -1; + } + return 0; } diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 63eded7ecb..9a3695d8e1 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -443,8 +443,9 @@ int qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, virCPUDef *cpu_b, - char **result) - ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + char **result, + char ***responsibleProps) + ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4); GHashTable * qemuMonitorJSONGetCommandLineOptions(qemuMonitor *mon); diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index a963029c69..ee7123d4c8 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -2623,27 +2623,174 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) g_autoptr(virCPUDef) cpu_a = virCPUDefNew(); g_autoptr(virCPUDef) cpu_b = virCPUDefNew(); g_autofree char *result = NULL; + g_auto(GStrv) responsibleProps = NULL; if (!(test = qemuMonitorTestNewSchema(data->xmlopt, data->schema))) return -1; + cpu_a->model = g_strdup("cpu_a"); + cpu_b->model = g_strdup("cpu_b"); + + /* incompatible, NULL responsibleProps — must still work */ if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", - "{\"return\":{\"result\":\"test\"}}") < 0) + "{\"return\":{\"result\":\"incompatible\"," + "\"responsible-properties\":[\"prop_a\",\"prop_b\"]}}") < 0) return -1; - cpu_a->model = g_strdup("cpu_a"); - cpu_b->model = g_strdup("cpu_b"); + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, NULL) < 0) + return -1; + + if (!result || STRNEQ(result, "incompatible")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Expected result 'incompatible'"); + return -1; + } + + g_clear_pointer(&result, g_free); + + /* incompatible, responsibleProps requested — missing-features path */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"incompatible\"," + "\"responsible-properties\":[\"prop_a\",\"prop_b\"]}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, + &responsibleProps) < 0) + return -1; + + if (!result || STRNEQ(result, "incompatible") || + !responsibleProps || + !responsibleProps[0] || STRNEQ(responsibleProps[0], "prop_a") || + !responsibleProps[1] || STRNEQ(responsibleProps[1], "prop_b") || + responsibleProps[2] != NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "responsible-properties not populated correctly"); + return -1; + } + + g_clear_pointer(&result, g_free); + g_clear_pointer(&responsibleProps, g_strfreev); + + /* responsibleProps requested but key absent in response */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"identical\"}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, + &responsibleProps) < 0) + return -1; + + if (responsibleProps != NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "responsible-properties should be NULL when key absent"); + return -1; + } + + g_clear_pointer(&result, g_free); + + /* responsibleProps requested but array is empty */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"incompatible\"," + "\"responsible-properties\":[]}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, + &responsibleProps) < 0) + return -1; + + if (responsibleProps != NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "responsible-properties should be NULL for empty array"); + return -1; + } + + g_clear_pointer(&result, g_free); + + /* identical — maps to QEMU_CPU_COMPAT_IDENTICAL, migration allowed */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"identical\"," + "\"responsible-properties\":[]}}") < 0) + return -1; if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), - cpu_a, cpu_b, &result) < 0) + cpu_a, cpu_b, &result, NULL) < 0) return -1; - if (!result || STRNEQ(result, "test")) { + if (!result || STRNEQ(result, "identical")) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - "Compare result not set"); + "Expected result 'identical'"); return -1; } + g_clear_pointer(&result, g_free); + + /* subset — maps to QEMU_CPU_COMPAT_SUBSET, migration allowed */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"subset\"," + "\"responsible-properties\":[]}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, NULL) < 0) + return -1; + + if (!result || STRNEQ(result, "subset")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Expected result 'subset'"); + return -1; + } + + g_clear_pointer(&result, g_free); + + /* superset with "type" prop — generation-mismatch error path */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"superset\"," + "\"responsible-properties\":[\"type\"]}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, + &responsibleProps) < 0) + return -1; + + if (!result || STRNEQ(result, "superset") || + !responsibleProps || + !responsibleProps[0] || STRNEQ(responsibleProps[0], "type") || + responsibleProps[1] != NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Expected result 'superset' with props=[\"type\"]"); + return -1; + } + + g_clear_pointer(&result, g_free); + g_clear_pointer(&responsibleProps, g_strfreev); + + /* GenericError from QEMU must be converted to VIR_ERR_CPU_INCOMPATIBLE */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"error\":{\"class\":\"GenericError\"," + "\"desc\":\"CPU model not found\"}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, NULL) >= 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Expected query-cpu-model-comparison to fail on GenericError"); + return -1; + } else { + virErrorPtr err = virGetLastError(); + if (!err || err->code != VIR_ERR_CPU_INCOMPATIBLE || + !strstr(err->message, "CPU model not found")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Expected VIR_ERR_CPU_INCOMPATIBLE with QEMU error desc"); + return -1; + } + virResetLastError(); + } + return 0; } -- 2.48.1
On 10/2/26 04:36, Rorie Reyes wrote:
The qemu-cpu-model-comparison QMP command can return a "responsible-properties" array identifying which CPU properties caused an incompatibility result. Expose this through the JSON monitor layer by adding an optional `responsibleProps` output parameter (char ***) to qemuMonitorJSONGetCPUModelComparison().
When the caller passes a non-NULL pointer the array is populated from the QMP response; passing NULL preserves the existing behaviour so all current callers are unaffected until they opt in.
The existing caller in qemu_monitor.c is updated to pass NULL so the build continues to work.
A GenericError response from the QMP command is intercepted and re-reported as VIR_ERR_CPU_INCOMPATIBLE with the QEMU error description. Assuming the JSON query was properly constructed, all GenericErrors should indicate an incompatible CPU model due to QEMU binary limitations (e.g. old binary does not recognize newer features or models).
The existing unit test is updated to pass the new argument and extended with additional calls: one that verifies the responsible-properties array is correctly populated from the QMP response, one that verifies it is NULL when the key is absent from the response, one that verifies it is NULL when the array is empty, and cases for "identical", "subset", and "superset" results (including a "type" responsible-property) to cover all result-string mappings.
Signed-off-by: Rorie Reyes<rreyes@linux.ibm.com> --- src/qemu/qemu_monitor.c | 2 +- src/qemu/qemu_monitor_json.c | 26 +++++- src/qemu/qemu_monitor_json.h | 5 +- tests/qemumonitorjsontest.c | 159 +++++++++++++++++++++++++++++++++-- 4 files changed, 182 insertions(+), 10 deletions(-)
Reviewed-by: Boris Fiuczynski <fiuczy@linux.ibm.com> -- Mit freundlichen Grüßen/Kind regards Boris Fiuczynski IBM Deutschland Research & Development GmbH Vorsitzender des Aufsichtsrats: Wolfgang Wendt Geschäftsführung: David Faller Sitz der Gesellschaft: Ehningen Registergericht: Amtsgericht Stuttgart, HRB 243294
Hi, The commit message suggests this patch is doing several things at once... On Thu, Oct 01, 2026 at 22:36:23 -0400, Rorie Reyes wrote:
The qemu-cpu-model-comparison QMP command can return a "responsible-properties" array identifying which CPU properties caused an incompatibility result. Expose this through the JSON monitor layer by adding an optional `responsibleProps` output parameter (char ***) to qemuMonitorJSONGetCPUModelComparison().
When the caller passes a non-NULL pointer the array is populated from the QMP response; passing NULL preserves the existing behaviour so all current callers are unaffected until they opt in.
This is the main thing.
The existing caller in qemu_monitor.c is updated to pass NULL so the build continues to work.
You don't really need to explicitly say this, it's the minimal change you have to do to make the code compile.
A GenericError response from the QMP command is intercepted and re-reported as VIR_ERR_CPU_INCOMPATIBLE with the QEMU error description. Assuming the JSON query was properly constructed, all GenericErrors should indicate an incompatible CPU model due to QEMU binary limitations (e.g. old binary does not recognize newer features or models).
This sounds completely unrelated and should go to a separate patch. Se below for more comments on this part.
The existing unit test is updated to pass the new argument and extended with additional calls: one that verifies the responsible-properties array is correctly populated from the QMP response, one that verifies it is NULL when the key is absent from the response, one that verifies it is NULL when the array is empty, and cases for "identical", "subset", and "superset" results (including a "type" responsible-property) to cover all result-string mappings.
I would only do the minimal change here and add new tests in a separate patch.
Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_monitor.c | 2 +- src/qemu/qemu_monitor_json.c | 26 +++++- src/qemu/qemu_monitor_json.h | 5 +- tests/qemumonitorjsontest.c | 159 +++++++++++++++++++++++++++++++++-- 4 files changed, 182 insertions(+), 10 deletions(-)
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 2c86cd1971..e3edc88814 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -3335,7 +3335,7 @@ qemuMonitorGetCPUModelComparison(qemuMonitor *mon,
QEMU_CHECK_MONITOR(mon);
- return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, result); + return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, result, NULL);
qemuMonitorGetCPUModelComparison is just a tiny wrapper around qemuMonitorJSONGetCPUModelComparison, you should propagate the new parameter one layer up right away. That is, squash the second patch into this one.
}
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 8e88dc9442..a895341b67 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -5689,7 +5689,8 @@ int qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, virCPUDef *cpu_b, - char **result) + char **result, + char ***responsibleProps) { g_autoptr(virJSONValue) model_a = NULL; g_autoptr(virJSONValue) model_b = NULL; @@ -5697,6 +5698,11 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, g_autoptr(virJSONValue) reply = NULL; const char *data_result; virJSONValue *data; + virJSONValue *propsArray; + + *result = NULL; + if (responsibleProps) + *responsibleProps = NULL;
if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true, false)) || !(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true, false))) @@ -5711,6 +5717,13 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) return -1;
+ if (qemuMonitorJSONHasError(reply, "GenericError")) { + virJSONValue *error = virJSONValueObjectGet(reply, "error"); + virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", + qemuMonitorJSONStringifyError(error)); + return -1; + }
If this really means the CPUs are incompatible rather then an error, you should propagate the info to the caller rather then reporting it as VIR_ERR_CPU_INCOMPATIBLE error here. The caller decides whether CPU incompatibility should be reported as an error or just as a specific return code. But we normally report unknown CPU models and features as errors so I don't think we should handle that differently in s390. That said, leaving just qemuMonitorJSONCheckError should be enough. And if not, the changes should definitely go into a separate patch.
+ if (qemuMonitorJSONCheckError(cmd, reply) < 0) return -1;
@@ -5723,6 +5736,17 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, }
*result = g_strdup(data_result);
If you want to gain some bonus points, you can add a new patch before this one that would refactor qemuMonitor*GetCPUModelComparison so that it doesn't reports the result as a string :-) In other words, move if (STREQ(result, "identical")) return VIR_CPU_COMPARE_IDENTICAL; if (STREQ(result, "superset")) return VIR_CPU_COMPARE_SUPERSET; from qemu_driver.c to the monitor code and get rid of the char **result parameter completely.
+ + propsArray = virJSONValueObjectGetArray(data, "responsible-properties"); + + if (responsibleProps && propsArray && + virJSONValueArraySize(propsArray) > 0 && + !(*responsibleProps = virJSONValueArrayToStringList(propsArray))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Invalid 'responsible-properties' list in query-cpu-model-comparison response"));
This would overwrite the error already reported by virJSONValueArrayToStringList. Just return -1 here.
+ return -1; + }
QEMU reports a special "type" property in the responsible-properties list if the incompatibility reason is not only a set of features that might be allowed to be disabled to get compatible CPUs, but if the models themselves are incompatible. Should we filter it out somehow to avoid reporting "type" as a missing feature?
+ return 0; }
diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 63eded7ecb..9a3695d8e1 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -443,8 +443,9 @@ int qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, virCPUDef *cpu_b, - char **result) - ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + char **result, + char ***responsibleProps) + ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4);
GHashTable * qemuMonitorJSONGetCommandLineOptions(qemuMonitor *mon); diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index a963029c69..ee7123d4c8 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c
Move the new tests into a separate patch, please.
@@ -2623,27 +2623,174 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) g_autoptr(virCPUDef) cpu_a = virCPUDefNew(); g_autoptr(virCPUDef) cpu_b = virCPUDefNew(); g_autofree char *result = NULL; + g_auto(GStrv) responsibleProps = NULL;
I would suggest defining a code block around each test ({ ... }) and move result and responsibleProps inside it so that they are automatically freed after each test.
if (!(test = qemuMonitorTestNewSchema(data->xmlopt, data->schema))) return -1;
+ cpu_a->model = g_strdup("cpu_a"); + cpu_b->model = g_strdup("cpu_b"); + + /* incompatible, NULL responsibleProps — must still work */ if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", - "{\"return\":{\"result\":\"test\"}}") < 0) + "{\"return\":{\"result\":\"incompatible\"," + "\"responsible-properties\":[\"prop_a\",\"prop_b\"]}}") < 0) return -1;
- cpu_a->model = g_strdup("cpu_a"); - cpu_b->model = g_strdup("cpu_b"); + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, &result, NULL) < 0) + return -1; + + if (!result || STRNEQ(result, "incompatible")) {
Use STRNEQ_NULLABLE throughout all this code instead (although ideally the string comparison would not be here at all if qemuMonitorJSONGetCPUModelComparison is refactored as I suggested earlier). ...
+ /* superset with "type" prop — generation-mismatch error path */ + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"superset\"," + "\"responsible-properties\":[\"type\"]}}") < 0) + return -1;
Is this even possible? From qapi/machine.json I would expect the "type" property to be only present for incompatible CPUs. ... Jirka
Add a new internal qemuCPUModelCompatResult enum to qemu_process.h with values for IDENTICAL, SUBSET, SUPERSET, INCOMPATIBLE, and ERROR, and a new shared helper qemuProcessCPUModelComparison() in qemu_process.c that wraps the QMP probe and maps the result string to the enum. Refactor qemuConnectCPUModelComparison() in qemu_driver.c to delegate to the new helper, eliminating the duplicated QMP probe boilerplate. qemuConnectCPUModelComparison() maps the internal enum back to virCPUCompareResult, treating SUBSET as VIR_CPU_COMPARE_INCOMPATIBLE to preserve existing public API behaviour. Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_driver.c | 20 +++++++---------- src/qemu/qemu_process.c | 50 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_process.h | 21 +++++++++++++++++ 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 7332ec0256..56a254f0ec 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12127,25 +12127,21 @@ qemuConnectCPUModelComparison(virQEMUCaps *qemuCaps, virCPUDef *cpu_b, bool failIncompatible) { - g_autoptr(qemuProcessQMP) proc = NULL; - g_autofree char *result = NULL; - - if (!(proc = qemuProcessQMPNew(virQEMUCapsGetBinary(qemuCaps), - libDir, runUid, runGid, false))) - return VIR_CPU_COMPARE_ERROR; - - if (qemuProcessQMPStart(proc) < 0) - return VIR_CPU_COMPARE_ERROR; + qemuCPUModelCompatResult cmp = + qemuProcessCPUModelComparison(qemuCaps, libDir, runUid, runGid, + cpu_a, cpu_b, NULL); - if (qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, &result, NULL) < 0) + if (cmp == QEMU_CPU_COMPAT_ERROR) return VIR_CPU_COMPARE_ERROR; - if (STREQ(result, "identical")) + if (cmp == QEMU_CPU_COMPAT_IDENTICAL) return VIR_CPU_COMPARE_IDENTICAL; - if (STREQ(result, "superset")) + if (cmp == QEMU_CPU_COMPAT_SUPERSET) return VIR_CPU_COMPARE_SUPERSET; + /* cpu_a is the host. SUBSET means the host lacks features the guest + * needs — treat as incompatible, same as INCOMPATIBLE. */ if (failIncompatible) { virReportError(VIR_ERR_CPU_INCOMPATIBLE, NULL); return VIR_CPU_COMPARE_ERROR; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 57a3ed3720..90b8acc6c2 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -113,6 +113,14 @@ VIR_LOG_INIT("qemu.qemu_process"); +VIR_ENUM_IMPL(qemuCPUModelCompatResult, + QEMU_CPU_COMPAT_LAST, + "incompatible", + "identical", + "superset", + "subset", +); + /** * qemuProcessRemoveDomainStatus * @@ -10583,6 +10591,48 @@ qemuProcessQMPStart(qemuProcessQMP *proc) } +qemuCPUModelCompatResult +qemuProcessCPUModelComparison(virQEMUCaps *qemuCaps, + const char *libDir, + uid_t runUid, + gid_t runGid, + virCPUDef *cpu_a, + virCPUDef *cpu_b, + char ***responsibleProps) +{ + g_autoptr(qemuProcessQMP) proc = NULL; + g_autofree char *result = NULL; + qemuCPUModelCompatResult compatResult; + + if (!(proc = qemuProcessQMPNew(virQEMUCapsGetBinary(qemuCaps), + libDir, runUid, runGid, false))) + return QEMU_CPU_COMPAT_ERROR; + + if (qemuProcessQMPStart(proc) < 0) + return QEMU_CPU_COMPAT_ERROR; + + if (qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, &result, + responsibleProps) < 0) + return QEMU_CPU_COMPAT_ERROR; + + compatResult = qemuCPUModelCompatResultTypeFromString(result); + + switch (compatResult) { + case QEMU_CPU_COMPAT_INCOMPATIBLE: + case QEMU_CPU_COMPAT_IDENTICAL: + case QEMU_CPU_COMPAT_SUPERSET: + case QEMU_CPU_COMPAT_SUBSET: + return compatResult; + case QEMU_CPU_COMPAT_ERROR: + case QEMU_CPU_COMPAT_LAST: + default: + VIR_WARN("unknown query-cpu-model-comparison result '%s', treating as incompatible", + result); + return QEMU_CPU_COMPAT_INCOMPATIBLE; + } +} + + void qemuProcessHandleNbdkitExit(qemuNbdkitProcess *nbdkit, virDomainObj *vm) diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index 5d8f1f89d5..059966e0a7 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -258,6 +258,27 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuProcessQMP, qemuProcessQMPFree); int qemuProcessQMPStart(qemuProcessQMP *proc); +typedef enum { + QEMU_CPU_COMPAT_ERROR = -1, + QEMU_CPU_COMPAT_INCOMPATIBLE = 0, + QEMU_CPU_COMPAT_IDENTICAL = 1, + QEMU_CPU_COMPAT_SUPERSET = 2, + QEMU_CPU_COMPAT_SUBSET = 3, + + QEMU_CPU_COMPAT_LAST, +} qemuCPUModelCompatResult; + +VIR_ENUM_DECL(qemuCPUModelCompatResult); + +qemuCPUModelCompatResult +qemuProcessCPUModelComparison(virQEMUCaps *qemuCaps, + const char *libDir, + uid_t runUid, + gid_t runGid, + virCPUDef *cpu_a, + virCPUDef *cpu_b, + char ***responsibleProps); + void qemuProcessCleanupMigrationJob(virQEMUDriver *driver, virDomainObj *vm); -- 2.48.1
On Thu, Oct 01, 2026 at 22:36:25 -0400, Rorie Reyes wrote:
Add a new internal qemuCPUModelCompatResult enum to qemu_process.h with values for IDENTICAL, SUBSET, SUPERSET, INCOMPATIBLE, and ERROR, and a new shared helper qemuProcessCPUModelComparison() in qemu_process.c that wraps the QMP probe and maps the result string to the enum.
Refactor qemuConnectCPUModelComparison() in qemu_driver.c to delegate to the new helper, eliminating the duplicated QMP probe boilerplate.
Instead of reusing internals from qemuConnectCPUModelComparison in other places, I think we should incorporate the function inside qemuDomainCheckCPU. Or rather rename qemuDomainCheckCPU to emphasise it is x86 specific, create similar function for s390 and call them both from qemuDomainCheckCPU and adapt qemuConnectCompareHypervisorCPU to use only this new function. See my reply to the last patch for details.
qemuConnectCPUModelComparison() maps the internal enum back to virCPUCompareResult, treating SUBSET as VIR_CPU_COMPARE_INCOMPATIBLE to preserve existing public API behaviour.
Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_driver.c | 20 +++++++---------- src/qemu/qemu_process.c | 50 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_process.h | 21 +++++++++++++++++ 3 files changed, 79 insertions(+), 12 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 7332ec0256..56a254f0ec 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12127,25 +12127,21 @@ qemuConnectCPUModelComparison(virQEMUCaps *qemuCaps, virCPUDef *cpu_b, bool failIncompatible) { - g_autoptr(qemuProcessQMP) proc = NULL; - g_autofree char *result = NULL; - - if (!(proc = qemuProcessQMPNew(virQEMUCapsGetBinary(qemuCaps), - libDir, runUid, runGid, false))) - return VIR_CPU_COMPARE_ERROR; - - if (qemuProcessQMPStart(proc) < 0) - return VIR_CPU_COMPARE_ERROR; + qemuCPUModelCompatResult cmp = + qemuProcessCPUModelComparison(qemuCaps, libDir, runUid, runGid, + cpu_a, cpu_b, NULL);
- if (qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, &result, NULL) < 0) + if (cmp == QEMU_CPU_COMPAT_ERROR) return VIR_CPU_COMPARE_ERROR;
- if (STREQ(result, "identical")) + if (cmp == QEMU_CPU_COMPAT_IDENTICAL) return VIR_CPU_COMPARE_IDENTICAL;
- if (STREQ(result, "superset")) + if (cmp == QEMU_CPU_COMPAT_SUPERSET) return VIR_CPU_COMPARE_SUPERSET;
Hmm, this is the kind of refactoring I suggested in my reply to the first patch. I'd just push it a few layers down directly to the monitor code.
+ /* cpu_a is the host. SUBSET means the host lacks features the guest + * needs — treat as incompatible, same as INCOMPATIBLE. */ if (failIncompatible) { virReportError(VIR_ERR_CPU_INCOMPATIBLE, NULL); return VIR_CPU_COMPARE_ERROR; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 57a3ed3720..90b8acc6c2 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c ... @@ -10583,6 +10591,48 @@ qemuProcessQMPStart(qemuProcessQMP *proc) }
+qemuCPUModelCompatResult +qemuProcessCPUModelComparison(virQEMUCaps *qemuCaps, + const char *libDir, + uid_t runUid, + gid_t runGid, + virCPUDef *cpu_a, + virCPUDef *cpu_b, + char ***responsibleProps) +{
...
+ switch (compatResult) { + case QEMU_CPU_COMPAT_INCOMPATIBLE: + case QEMU_CPU_COMPAT_IDENTICAL: + case QEMU_CPU_COMPAT_SUPERSET: + case QEMU_CPU_COMPAT_SUBSET: + return compatResult; + case QEMU_CPU_COMPAT_ERROR: + case QEMU_CPU_COMPAT_LAST: + default: + VIR_WARN("unknown query-cpu-model-comparison result '%s', treating as incompatible", + result); + return QEMU_CPU_COMPAT_INCOMPATIBLE; + }
No matter how you finally end up decoding the strings to the enum, unknown results from query-cpu-model-comparison should be properly reported as errors. Jirka
Add the optional `responsibleProps` (char ***) parameter to qemuMonitorGetCPUModelComparison() in the public monitor API and wire it through to the JSON implementation. The parameter is optional — callers that pass NULL preserve existing behaviour. The existing caller in qemu_driver.c is updated to pass NULL so the build continues to work until the caller is refactored in the next patch. Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_monitor.c | 8 +++++--- src/qemu/qemu_monitor.h | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ebf19d7c0f..7332ec0256 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12137,7 +12137,7 @@ qemuConnectCPUModelComparison(virQEMUCaps *qemuCaps, if (qemuProcessQMPStart(proc) < 0) return VIR_CPU_COMPARE_ERROR; - if (qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, &result) < 0) + if (qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, &result, NULL) < 0) return VIR_CPU_COMPARE_ERROR; if (STREQ(result, "identical")) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index e3edc88814..88127ca27d 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -3329,13 +3329,15 @@ int qemuMonitorGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, virCPUDef *cpu_b, - char **result) + char **result, + char ***responsibleProps) { - VIR_DEBUG("cpu_a=%p cpu_b=%p", cpu_a, cpu_b); + VIR_DEBUG("cpu_a=%p cpu_b=%p responsibleProps=%p", cpu_a, cpu_b, responsibleProps); QEMU_CHECK_MONITOR(mon); - return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, result, NULL); + return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, result, + responsibleProps); } diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index e9b6a25f0c..ddea8796c4 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1485,7 +1485,8 @@ int qemuMonitorGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, virCPUDef *cpu_b, - char **result); + char **result, + char ***responsibleProps); qemuMonitorCPUModelInfo * qemuMonitorCPUModelInfoCopy(const qemuMonitorCPUModelInfo *orig); -- 2.48.1
On Thu, Oct 01, 2026 at 22:36:24 -0400, Rorie Reyes wrote:
Add the optional `responsibleProps` (char ***) parameter to qemuMonitorGetCPUModelComparison() in the public monitor API and wire it through to the JSON implementation. The parameter is optional — callers that pass NULL preserve existing behaviour.
The existing caller in qemu_driver.c is updated to pass NULL so the build continues to work until the caller is refactored in the next patch.
Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_monitor.c | 8 +++++--- src/qemu/qemu_monitor.h | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-)
This should be squashed in the patch that introduced the new parameter to qemuMonitorJSONGetCPUModelComparison. Jirka
When migrating an s390x guest to a target s390x host, it is possible the migration will fail ungracefully due to incompatible CPU models. Add a pre-migration check for the s390x architecture: when the guest uses a custom or host-model CPU, look up the QEMU capabilities from the driver cache and, if the QEMU_CAPS_QUERY_CPU_MODEL_COMPARISON capability is present, invoke a CPU model comparison between the guest and target hypervisor CPU models. The check runs before qemuProcessInit() so that the guest CPU from the migration cookie is compared before it can be resolved to the destination host model. The comparison result is distinguished by four cases: - IDENTICAL or SUBSET: guest CPU model is presumed to be compatible with target host's model. - SUPERSET: guest CPU model is newer than target host's model, or target host does not support features guest requires. - INCOMPATIBLE: guest CPU model cannot run on target host. Report the list of features the target is missing (if any). - Probe error: propagated as VIR_ERR_CPU_INCOMPATIBLE from the monitor layer. Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_migration.c | 114 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 4a43ab83b0..b77bdd4370 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -3308,6 +3308,90 @@ qemuMigrationDstPrepareAnyBlockDirtyBitmaps(virDomainObj *vm, } +/** + * qemuMigrationDstCheckCPUModelCompat: + * + * Run a short-lived QMP probe to compare @guestCPU against @hvCPU on the + * destination host. Returns 0 if the guest CPU is compatible (identical + * or a subset of what the host provides), -1 if incompatible. + * + * On incompatibility an error is reported via virReportError before returning + * -1. Probe errors (e.g. the destination QEMU does not recognise a CPU + * property) are reported as VIR_ERR_CPU_INCOMPATIBLE by the monitor layer and + * are propagated as-is. + */ +static int +qemuMigrationDstCheckCPUModelCompat(virQEMUCaps *qemuCaps, + const char *libDir, + uid_t runUid, + gid_t runGid, + virCPUDef *hvCPU, + virCPUDef *guestCPU) +{ + g_autofree char **responsibleProps = NULL; + qemuCPUModelCompatResult cmp = qemuProcessCPUModelComparison(qemuCaps, libDir, + runUid, runGid, + guestCPU, hvCPU, + &responsibleProps); + + switch (cmp) { + case QEMU_CPU_COMPAT_IDENTICAL: + case QEMU_CPU_COMPAT_SUBSET: + /* Guest CPU model is presumed to be compatible with target host's model. */ + return 0; + + case QEMU_CPU_COMPAT_SUPERSET: + /* Either guest CPU model is newer than target host's model, or target + * host does not support features required by guest. */ + virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", + _("guest CPU model is a newer generation than the destination host CPU model")); + return -1; + + case QEMU_CPU_COMPAT_INCOMPATIBLE: + /* Guest CPU model cannot run on target host. Report the list of + * features the target is missing (if any). + * + * s390x will report the property 'type' when CPU models are + * incompatible. Remove it from the responsibleProps list so it + * is not reported as a missing feature. */ + if (responsibleProps) { + size_t i = 0; + size_t j = 0; + + while (responsibleProps[i]) { + if (STREQ(responsibleProps[i], "type")) { + g_free(responsibleProps[i++]); + } else { + responsibleProps[j++] = responsibleProps[i++]; + } + } + responsibleProps[j] = NULL; + + if (responsibleProps[0]) { + g_autofree char *missing_str = g_strjoinv(", ", responsibleProps); + virReportError(VIR_ERR_CPU_INCOMPATIBLE, + _("destination host CPU model is missing features required by the guest CPU model: %1$s"), + missing_str); + return -1; + } + } + + virReportError(VIR_ERR_CPU_INCOMPATIBLE, + _("guest CPU model '%1$s' is not compatible with host CPU model '%2$s'"), + guestCPU->model ? guestCPU->model : _("unknown"), + hvCPU->model ? hvCPU->model : _("unknown")); + return -1; + + case QEMU_CPU_COMPAT_ERROR: + case QEMU_CPU_COMPAT_LAST: + default: + break; + } + + return -1; +} + + static int qemuMigrationDstPrepareActive(virQEMUDriver *driver, virDomainObj *vm, @@ -3354,6 +3438,36 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver, startFlags = VIR_QEMU_PROCESS_START_AUTODESTROY; + /* Check CPU model compatibility before starting the guest. + * Snapshot the guest CPU from the migration cookie before qemuProcessInit + * can resolve and replace it with the destination host CPU model. + * This allows libvirt to provide a clear error message and perform + * proper cleanup if the destination host doesn't support the required + * CPU features. + */ + if (ARCH_IS_S390(vm->def->os.arch) && + mig->cpu && + (mig->cpu->mode == VIR_CPU_MODE_CUSTOM || + mig->cpu->mode == VIR_CPU_MODE_HOST_MODEL) && + mig->cpu->model) { + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); + g_autoptr(virQEMUCaps) qemuCaps = virQEMUCapsCacheLookup(driver->qemuCapsCache, + vm->def->emulator); + + if (qemuCaps && + virQEMUCapsGet(qemuCaps, QEMU_CAPS_QUERY_CPU_MODEL_COMPARISON)) { + virCPUDef *hvCPU = virQEMUCapsGetHostModel(qemuCaps, + vm->def->virtType, + VIR_QEMU_CAPS_HOST_CPU_REPORTED); + + if (hvCPU && + qemuMigrationDstCheckCPUModelCompat(qemuCaps, + cfg->libDir, cfg->user, cfg->group, + hvCPU, mig->cpu) < 0) + goto error; + } + } + if (qemuProcessInit(driver, vm, mig->cpu, VIR_ASYNC_JOB_MIGRATION_IN, true, startFlags) < 0) goto error; -- 2.48.1
On Thu, Oct 01, 2026 at 22:36:26 -0400, Rorie Reyes wrote:
When migrating an s390x guest to a target s390x host, it is possible the migration will fail ungracefully due to incompatible CPU models.
Add a pre-migration check for the s390x architecture: when the guest uses a custom or host-model CPU, look up the QEMU capabilities from the driver cache and, if the QEMU_CAPS_QUERY_CPU_MODEL_COMPARISON capability is present, invoke a CPU model comparison between the guest and target hypervisor CPU models.
The check runs before qemuProcessInit() so that the guest CPU from the migration cookie is compared before it can be resolved to the destination host model. The comparison result is distinguished by four cases:
- IDENTICAL or SUBSET: guest CPU model is presumed to be compatible with target host's model. - SUPERSET: guest CPU model is newer than target host's model, or target host does not support features guest requires. - INCOMPATIBLE: guest CPU model cannot run on target host. Report the list of features the target is missing (if any). - Probe error: propagated as VIR_ERR_CPU_INCOMPATIBLE from the monitor layer.
Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_migration.c | 114 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+)
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
qemu_migration.c is a wrong place to handle this. We have a code that already checks CPU compatibility for any QEMU start (fresh start, migration, snapshot, restore) and there's no reason for s390 to come up with its own place to deal with this. Also this path would only address migration, while I believe we want similar behavior for snapshots and restore as well. The CPU compatibility code is in qemuProcessUpdateGuestCPU which calls to qemuDomainCheckCPU. So if we refactor qemuDomainCheckCPU in a way I described in my previous reply, we should get migration checks for free. The flags parameter in qemuProcessUpdateGuestCPU says whether it was called during a fresh start or not and can be used to tune the behavior if needed.
index 4a43ab83b0..b77bdd4370 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -3308,6 +3308,90 @@ qemuMigrationDstPrepareAnyBlockDirtyBitmaps(virDomainObj *vm, }
+/** + * qemuMigrationDstCheckCPUModelCompat: + * + * Run a short-lived QMP probe to compare @guestCPU against @hvCPU on the + * destination host. Returns 0 if the guest CPU is compatible (identical + * or a subset of what the host provides), -1 if incompatible. + * + * On incompatibility an error is reported via virReportError before returning + * -1. Probe errors (e.g. the destination QEMU does not recognise a CPU + * property) are reported as VIR_ERR_CPU_INCOMPATIBLE by the monitor layer and + * are propagated as-is. + */ +static int +qemuMigrationDstCheckCPUModelCompat(virQEMUCaps *qemuCaps, + const char *libDir, + uid_t runUid, + gid_t runGid, + virCPUDef *hvCPU, + virCPUDef *guestCPU) +{ + g_autofree char **responsibleProps = NULL; + qemuCPUModelCompatResult cmp = qemuProcessCPUModelComparison(qemuCaps, libDir, + runUid, runGid, + guestCPU, hvCPU, + &responsibleProps); + + switch (cmp) { + case QEMU_CPU_COMPAT_IDENTICAL: + case QEMU_CPU_COMPAT_SUBSET: + /* Guest CPU model is presumed to be compatible with target host's model. */ + return 0; + + case QEMU_CPU_COMPAT_SUPERSET: + /* Either guest CPU model is newer than target host's model, or target + * host does not support features required by guest. */ + virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", + _("guest CPU model is a newer generation than the destination host CPU model")); + return -1; + + case QEMU_CPU_COMPAT_INCOMPATIBLE: + /* Guest CPU model cannot run on target host. Report the list of + * features the target is missing (if any). + * + * s390x will report the property 'type' when CPU models are + * incompatible. Remove it from the responsibleProps list so it + * is not reported as a missing feature. */ + if (responsibleProps) { + size_t i = 0; + size_t j = 0; + + while (responsibleProps[i]) { + if (STREQ(responsibleProps[i], "type")) { + g_free(responsibleProps[i++]); + } else { + responsibleProps[j++] = responsibleProps[i++]; + } + } + responsibleProps[j] = NULL;
OK, this is the filtering I talked about in patch 1 and I think we should do it in the json monitor unless we need to see that info somewhere else, which I believe is not the case.
+ + if (responsibleProps[0]) { + g_autofree char *missing_str = g_strjoinv(", ", responsibleProps); + virReportError(VIR_ERR_CPU_INCOMPATIBLE, + _("destination host CPU model is missing features required by the guest CPU model: %1$s"), + missing_str); + return -1; + } + } ...
Jirka
Just sending a friendly "ping" to have this series reviewed please. Thank you! On 10/1/26 10:36 PM, Rorie Reyes wrote:
This series extends the qemu-cpu-model-comparison QMP interface in libvirt to surface the "responsible-properties" array returned by QEMU, and uses that information to perform a pre-flight CPU compatibility check on s390x before an incoming migration is accepted.
Background ---------- On x86, QEMU rejects an incompatible CPU model at domain start time and produces a meaningful error. On s390x the guest starts successfully but may fail later in an opaque way if the destination host lacks required CPU features. This series brings s390x in line with x86 by catching the incompatibility early, aborting the migration cleanly, and reporting exactly which CPU properties are responsible.
Functional Test Instructions ----------------------------- Prerequisites: - Two s390x hosts (source and destination) with different CPU feature levels from the same generation lineage (e.g. z16 and z17). - libvirt and QEMU built from this series on both hosts. - QEMU must support the query-cpu-model-comparison QMP command (QEMU >= 5.1).
Test case 1 — Incompatible CPU model is rejected cleanly: 1. Define a guest on the source host using a custom s390x CPU model that includes features not present on the destination host. 2. Initiate a live migration to the destination host:
virsh migrate --live <domain> qemu+ssh://dest-host/system
3. Expected result: migration fails immediately with a clear libvirt error before QEMU starts on the destination, e.g.:
error: operation failed: guest CPU model is a newer generation than the destination host CPU model
or, for a feature-level mismatch within the same generation:
error: operation failed: destination host CPU model is missing features required by the guest CPU model: <prop1>, <prop2>
4. Verify the guest is still running cleanly on the source host:
virsh domstate <domain> # should report: running
Test case 2 — Compatible CPU model migrates successfully: 1. Define a guest using a CPU model fully supported by the destination (e.g. gen16a-base guest migrating to a gen17 host). 2. Initiate the same live migration. 3. Expected result: migration completes successfully with no errors.
Rorie Reyes (4): qemu: extend GetCPUModelComparison to return responsible-properties qemu: propagate responsibleProps parameter through monitor layer qemu: introduce qemuProcessCPUModelComparison helper qemu: check CPU model compatibility on s390x before incoming migration
src/qemu/qemu_driver.c | 20 ++--- src/qemu/qemu_migration.c | 114 +++++++++++++++++++++++++ src/qemu/qemu_monitor.c | 8 +- src/qemu/qemu_monitor.h | 3 +- src/qemu/qemu_monitor_json.c | 26 +++++- src/qemu/qemu_monitor_json.h | 5 +- src/qemu/qemu_process.c | 50 +++++++++++ src/qemu/qemu_process.h | 21 +++++ tests/qemumonitorjsontest.c | 159 +++++++++++++++++++++++++++++++++-- 9 files changed, 381 insertions(+), 25 deletions(-)
participants (3)
-
Boris Fiuczynski -
Jiri Denemark -
Rorie Reyes