[PATCH libvirt v2 0/4] s390x CPU model compatibility check for incoming migration
Changelog: V2 - - patch 1 (new patch, split from v1 patch 1): - fix result string to enum mapping; move into monitor layer - update unit test for all four result strings - report unknown result strings as errors - patch 2: - squash v1 patch 2 (propagate responsibleProps through monitor layer) into this patch - drop GenericError interception block - fix virReportError overwriting error from virJSONValueArrayToStringList - update qemu_driver.c caller to pass NULL - patch 3 (split from v1 patch 1): - add block scopes per subtest - use STRNEQ_NULLABLE - drop "superset + type" test case - fix bullet list to only include cases added by this patch - patch 4: - replace qemuCPUModelCompatResult enum with virCPUCompareResult - move "type" property filtering into qemuProcessCheckS390CPU() - wire check into qemuProcessUpdateGuestCPU() instead of qemu_migration.c - simplify qemuConnectCPUModelComparison() to delegate to new helper - report unknown results as errors ------------------- 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: refactor GetCPUModelComparison to return virCPUCompareResult qemu: extend GetCPUModelComparison to return responsible-properties tests: extend qemuMonitorJSONGetCPUModelComparison test coverage qemu: introduce qemuProcessCheckS390CPU helper src/qemu/qemu_driver.c | 27 +----- src/qemu/qemu_monitor.c | 11 +-- src/qemu/qemu_monitor.h | 4 +- src/qemu/qemu_monitor_json.c | 45 +++++++--- src/qemu/qemu_monitor_json.h | 4 +- src/qemu/qemu_process.c | 75 ++++++++++++++++ src/qemu/qemu_process.h | 9 ++ tests/qemumonitorjsontest.c | 167 ++++++++++++++++++++++++++++++++--- 8 files changed, 284 insertions(+), 58 deletions(-) -- 2.48.1
Replace the char **result output parameter with a virCPUCompareResult return value in qemuMonitorJSONGetCPUModelComparison() and its public wrapper qemuMonitorGetCPUModelComparison(). Move the result string to enum mapping (identical/superset/subset/incompatible) into the monitor layer and remove it from qemuConnectCPUModelComparison() in qemu_driver.c. Unknown result strings are reported as errors. The existing unit test is updated to use the new return value and extended with cases for all four result strings (identical, superset, subset, incompatible). Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_driver.c | 16 ++++----- src/qemu/qemu_monitor.c | 9 +++-- src/qemu/qemu_monitor.h | 5 ++- src/qemu/qemu_monitor_json.c | 34 +++++++++++------- src/qemu/qemu_monitor_json.h | 5 ++- tests/qemumonitorjsontest.c | 67 +++++++++++++++++++++++++++++------- 6 files changed, 90 insertions(+), 46 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 5922065897..2500b6fb50 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12128,7 +12128,7 @@ qemuConnectCPUModelComparison(virQEMUCaps *qemuCaps, bool failIncompatible) { g_autoptr(qemuProcessQMP) proc = NULL; - g_autofree char *result = NULL; + virCPUCompareResult result; if (!(proc = qemuProcessQMPNew(virQEMUCapsGetBinary(qemuCaps), libDir, runUid, runGid, false))) @@ -12137,21 +12137,17 @@ qemuConnectCPUModelComparison(virQEMUCaps *qemuCaps, if (qemuProcessQMPStart(proc) < 0) return VIR_CPU_COMPARE_ERROR; - if (qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, &result) < 0) - return VIR_CPU_COMPARE_ERROR; - - if (STREQ(result, "identical")) - return VIR_CPU_COMPARE_IDENTICAL; + result = qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b); - if (STREQ(result, "superset")) - return VIR_CPU_COMPARE_SUPERSET; + if (result == VIR_CPU_COMPARE_ERROR) + return VIR_CPU_COMPARE_ERROR; - if (failIncompatible) { + if (failIncompatible && result == VIR_CPU_COMPARE_INCOMPATIBLE) { virReportError(VIR_ERR_CPU_INCOMPATIBLE, NULL); return VIR_CPU_COMPARE_ERROR; } - return VIR_CPU_COMPARE_INCOMPATIBLE; + return result; } diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 7d1b9a0bfd..265890922f 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -3326,17 +3326,16 @@ qemuMonitorGetCPUModelBaseline(qemuMonitor *mon, } -int +virCPUCompareResult qemuMonitorGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b, - char **result) + virCPUDef *cpu_b) { VIR_DEBUG("cpu_a=%p cpu_b=%p", cpu_a, cpu_b); - QEMU_CHECK_MONITOR(mon); + QEMU_CHECK_MONITOR_FULL(mon, return VIR_CPU_COMPARE_ERROR); - return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, result); + return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b); } diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index f711c001fb..c94cfc2b35 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1483,11 +1483,10 @@ qemuMonitorGetCPUModelBaseline(qemuMonitor *mon, virCPUDef *cpu_b, qemuMonitorCPUModelInfo **baseline); -int +virCPUCompareResult qemuMonitorGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b, - char **result); + virCPUDef *cpu_b); qemuMonitorCPUModelInfo * qemuMonitorCPUModelInfoCopy(const qemuMonitorCPUModelInfo *orig); diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 29c42f7dd5..a8932af43c 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -5686,45 +5686,55 @@ qemuMonitorJSONGetCPUModelBaseline(qemuMonitor *mon, } -int +virCPUCompareResult qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b, - char **result) + virCPUDef *cpu_b) { g_autoptr(virJSONValue) model_a = NULL; g_autoptr(virJSONValue) model_b = NULL; g_autoptr(virJSONValue) cmd = NULL; g_autoptr(virJSONValue) reply = NULL; - const char *data_result; + const char *result; virJSONValue *data; if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true, false)) || !(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true, false))) - return -1; + return VIR_CPU_COMPARE_ERROR; if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-model-comparison", "a:modela", &model_a, "a:modelb", &model_b, NULL))) - return -1; + return VIR_CPU_COMPARE_ERROR; if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) - return -1; + return VIR_CPU_COMPARE_ERROR; if (qemuMonitorJSONCheckError(cmd, reply) < 0) - return -1; + return VIR_CPU_COMPARE_ERROR; data = virJSONValueObjectGetObject(reply, "return"); - if (!(data_result = virJSONValueObjectGetString(data, "result"))) { + if (!(result = virJSONValueObjectGetString(data, "result"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("query-cpu-model-comparison reply data was missing 'result'")); - return -1; + return VIR_CPU_COMPARE_ERROR; } - *result = g_strdup(data_result); - return 0; + if (STREQ(result, "identical")) + return VIR_CPU_COMPARE_IDENTICAL; + + if (STREQ(result, "superset")) + return VIR_CPU_COMPARE_SUPERSET; + + if (STREQ(result, "subset") || STREQ(result, "incompatible")) + return VIR_CPU_COMPARE_INCOMPATIBLE; + + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown query-cpu-model-comparison result: '%1$s'"), + result); + return VIR_CPU_COMPARE_ERROR; } diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 63eded7ecb..e5d3d5d99e 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -439,11 +439,10 @@ qemuMonitorJSONGetCPUModelBaseline(qemuMonitor *mon, qemuMonitorCPUModelInfo **baseline) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4); -int +virCPUCompareResult qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b, - char **result) + virCPUDef *cpu_b) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); GHashTable * diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index a38d63c22c..b00d4512d1 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -2647,26 +2647,67 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) g_autoptr(qemuMonitorTest) test = NULL; g_autoptr(virCPUDef) cpu_a = virCPUDefNew(); g_autoptr(virCPUDef) cpu_b = virCPUDefNew(); - g_autofree char *result = NULL; + + cpu_a->model = g_strdup("cpu_a"); + cpu_b->model = g_strdup("cpu_b"); if (!(test = qemuMonitorTestNewSchema(data->xmlopt, data->schema))) return -1; - if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", - "{\"return\":{\"result\":\"test\"}}") < 0) - return -1; + /* identical */ + { + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"identical\"}}") < 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) != VIR_CPU_COMPARE_IDENTICAL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result for 'identical'"); + return -1; + } + } - if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), - cpu_a, cpu_b, &result) < 0) - return -1; + /* superset */ + { + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"superset\"}}") < 0) + return -1; - if (!result || STRNEQ(result, "test")) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - "Compare result not set"); - return -1; + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b) != VIR_CPU_COMPARE_SUPERSET) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result for 'superset'"); + return -1; + } + } + + /* subset */ + { + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"subset\"}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b) != VIR_CPU_COMPARE_INCOMPATIBLE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result for 'subset'"); + return -1; + } + } + + /* incompatible */ + { + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"incompatible\"}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b) != VIR_CPU_COMPARE_INCOMPATIBLE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result for 'incompatible'"); + return -1; + } } return 0; -- 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() and its public wrapper qemuMonitorGetCPUModelComparison(). When the caller passes a non-NULL pointer the array is populated from the QMP response; passing NULL preserves the existing behaviour. The existing caller in qemuConnectCPUModelComparison() is updated to pass NULL so the build continues to work. 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 ++- src/qemu/qemu_monitor_json.c | 15 ++++++++++++++- src/qemu/qemu_monitor_json.h | 3 ++- tests/qemumonitorjsontest.c | 8 ++++---- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 2500b6fb50..199eedca36 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; - result = qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b); + result = qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, NULL); if (result == VIR_CPU_COMPARE_ERROR) return VIR_CPU_COMPARE_ERROR; diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 265890922f..915ab7c10f 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -3329,13 +3329,15 @@ qemuMonitorGetCPUModelBaseline(qemuMonitor *mon, virCPUCompareResult qemuMonitorGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b) + virCPUDef *cpu_b, + 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_FULL(mon, return VIR_CPU_COMPARE_ERROR); - return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b); + return qemuMonitorJSONGetCPUModelComparison(mon, cpu_a, cpu_b, + responsibleProps); } diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index c94cfc2b35..f14bd9e04b 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1486,7 +1486,8 @@ qemuMonitorGetCPUModelBaseline(qemuMonitor *mon, virCPUCompareResult qemuMonitorGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b); + virCPUDef *cpu_b, + char ***responsibleProps); qemuMonitorCPUModelInfo * qemuMonitorCPUModelInfoCopy(const qemuMonitorCPUModelInfo *orig); diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index a8932af43c..5dec049479 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -5689,7 +5689,8 @@ qemuMonitorJSONGetCPUModelBaseline(qemuMonitor *mon, virCPUCompareResult qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b) + virCPUDef *cpu_b, + char ***responsibleProps) { g_autoptr(virJSONValue) model_a = NULL; g_autoptr(virJSONValue) model_b = NULL; @@ -5697,6 +5698,10 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, g_autoptr(virJSONValue) reply = NULL; const char *result; virJSONValue *data; + virJSONValue *propsArray; + + if (responsibleProps) + *responsibleProps = NULL; if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true, false)) || !(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true, false))) @@ -5722,6 +5727,14 @@ qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, return VIR_CPU_COMPARE_ERROR; } + propsArray = virJSONValueObjectGetArray(data, "responsible-properties"); + + if (responsibleProps && propsArray && + virJSONValueArraySize(propsArray) > 0 && + !(*responsibleProps = virJSONValueArrayToStringList(propsArray))) { + return VIR_CPU_COMPARE_ERROR; + } + if (STREQ(result, "identical")) return VIR_CPU_COMPARE_IDENTICAL; diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index e5d3d5d99e..ebee30036a 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -442,7 +442,8 @@ qemuMonitorJSONGetCPUModelBaseline(qemuMonitor *mon, virCPUCompareResult qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon, virCPUDef *cpu_a, - virCPUDef *cpu_b) + virCPUDef *cpu_b, + char ***responsibleProps) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); GHashTable * diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index b00d4512d1..db44a725ee 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -2661,7 +2661,7 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) return -1; if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), - cpu_a, cpu_b) != VIR_CPU_COMPARE_IDENTICAL) { + cpu_a, cpu_b, NULL) != VIR_CPU_COMPARE_IDENTICAL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "unexpected result for 'identical'"); return -1; @@ -2675,7 +2675,7 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) return -1; if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), - cpu_a, cpu_b) != VIR_CPU_COMPARE_SUPERSET) { + cpu_a, cpu_b, NULL) != VIR_CPU_COMPARE_SUPERSET) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "unexpected result for 'superset'"); return -1; @@ -2689,7 +2689,7 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) return -1; if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), - cpu_a, cpu_b) != VIR_CPU_COMPARE_INCOMPATIBLE) { + cpu_a, cpu_b, NULL) != VIR_CPU_COMPARE_INCOMPATIBLE) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "unexpected result for 'subset'"); return -1; @@ -2703,7 +2703,7 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) return -1; if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), - cpu_a, cpu_b) != VIR_CPU_COMPARE_INCOMPATIBLE) { + cpu_a, cpu_b, NULL) != VIR_CPU_COMPARE_INCOMPATIBLE) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", "unexpected result for 'incompatible'"); return -1; -- 2.48.1
Add test cases for the responsible-properties output parameter introduced in the previous patch: - responsible-properties populated correctly from QMP response - responsible-properties NULL when key is absent from response - responsible-properties NULL when array is empty - subset result with responsible-properties Each case is wrapped in its own block so responsibleProps is automatically freed after each subtest. Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- tests/qemumonitorjsontest.c | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index db44a725ee..5dc8ba67e6 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -2654,6 +2654,80 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) if (!(test = qemuMonitorTestNewSchema(data->xmlopt, data->schema))) return -1; + /* responsible-properties populated correctly */ + { + g_auto(GStrv) responsibleProps = NULL; + + 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, + &responsibleProps) != VIR_CPU_COMPARE_INCOMPATIBLE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result for incompatible with props"); + return -1; + } + + if (!responsibleProps || + STRNEQ_NULLABLE(responsibleProps[0], "prop_a") || + STRNEQ_NULLABLE(responsibleProps[1], "prop_b") || + responsibleProps[2] != NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected responsible-properties"); + return -1; + } + } + + /* responsible-properties absent from response — must be NULL */ + { + g_auto(GStrv) responsibleProps = NULL; + + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"incompatible\"}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, + &responsibleProps) != VIR_CPU_COMPARE_INCOMPATIBLE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result"); + return -1; + } + + if (responsibleProps) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "responsible-properties should be NULL when key absent"); + return -1; + } + } + + /* responsible-properties empty array — must be NULL */ + { + g_auto(GStrv) responsibleProps = NULL; + + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"incompatible\"," + "\"responsible-properties\":[]}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, + &responsibleProps) != VIR_CPU_COMPARE_INCOMPATIBLE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result"); + return -1; + } + + if (responsibleProps) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "responsible-properties should be NULL for empty array"); + return -1; + } + } + /* identical */ { if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", @@ -2710,6 +2784,32 @@ testQemuMonitorJSONqemuMonitorJSONGetCPUModelComparison(const void *opaque) } } + /* subset with responsible-properties */ + { + g_auto(GStrv) responsibleProps = NULL; + + if (qemuMonitorTestAddItem(test, "query-cpu-model-comparison", + "{\"return\":{\"result\":\"subset\"," + "\"responsible-properties\":[\"feat_x\"]}}") < 0) + return -1; + + if (qemuMonitorJSONGetCPUModelComparison(qemuMonitorTestGetMonitor(test), + cpu_a, cpu_b, + &responsibleProps) != VIR_CPU_COMPARE_INCOMPATIBLE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected result for subset with props"); + return -1; + } + + if (!responsibleProps || + STRNEQ_NULLABLE(responsibleProps[0], "feat_x") || + responsibleProps[1] != NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected responsible-properties"); + return -1; + } + } + return 0; } -- 2.48.1
Add a new helper qemuProcessCheckS390CPU() in qemu_process.c that wraps the QMP query-cpu-model-comparison probe for s390x and returns a virCPUCompareResult directly. The helper filters out the special "type" responsible-property (which signals a CPU generation mismatch rather than a missing feature) before reporting incompatibility to the caller. When reporting incompatibility, the differing properties are listed in the error message when available. If no responsible-properties are returned (or all are filtered out), a generic incompatibility message is reported instead. Wire the helper into qemuProcessUpdateGuestCPU() so that s390x CPU model compatibility is checked for real domain start paths (migration, snapshot restore) when the QEMU_CAPS_QUERY_CPU_MODEL_COMPARISON capability is present. The host CPU is passed as the first argument and the guest CPU as the second, matching qemuConnectCPUModelComparison(). The check is skipped in pretend mode (VIR_QEMU_PROCESS_START_PRETEND) since that path is used for command-line generation and does not involve a running QEMU instance. It is also skipped when the guest CPU definition has no model yet (for example host-model on a fresh start), since that model is resolved from the host CPU later and cannot be incompatible with it. Simplify qemuConnectCPUModelComparison() in qemu_driver.c to delegate directly to qemuProcessCheckS390CPU(). Signed-off-by: Rorie Reyes <rreyes@linux.ibm.com> --- src/qemu/qemu_driver.c | 23 ++----------- src/qemu/qemu_process.c | 75 +++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_process.h | 9 +++++ 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 199eedca36..ef549a2c02 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12127,27 +12127,8 @@ qemuConnectCPUModelComparison(virQEMUCaps *qemuCaps, virCPUDef *cpu_b, bool failIncompatible) { - g_autoptr(qemuProcessQMP) proc = NULL; - virCPUCompareResult result; - - if (!(proc = qemuProcessQMPNew(virQEMUCapsGetBinary(qemuCaps), - libDir, runUid, runGid, false))) - return VIR_CPU_COMPARE_ERROR; - - if (qemuProcessQMPStart(proc) < 0) - return VIR_CPU_COMPARE_ERROR; - - result = qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, NULL); - - if (result == VIR_CPU_COMPARE_ERROR) - return VIR_CPU_COMPARE_ERROR; - - if (failIncompatible && result == VIR_CPU_COMPARE_INCOMPATIBLE) { - virReportError(VIR_ERR_CPU_INCOMPATIBLE, NULL); - return VIR_CPU_COMPARE_ERROR; - } - - return result; + return qemuProcessCheckS390CPU(qemuCaps, libDir, runUid, runGid, + cpu_a, cpu_b, failIncompatible); } diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 3f1960f314..dfb9e0785a 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -6708,6 +6708,20 @@ qemuProcessUpdateGuestCPU(virDomainDef *def, VIR_QEMU_CAPS_HOST_CPU_FULL, true) < 0) return -1; + if (!(flags & VIR_QEMU_PROCESS_START_PRETEND) && + def->cpu->model && + ARCH_IS_S390(hostarch) && + virQEMUCapsGet(qemuCaps, QEMU_CAPS_QUERY_CPU_MODEL_COMPARISON)) { + virCPUDef *hvCPU = virQEMUCapsGetHostModel(qemuCaps, def->virtType, + VIR_QEMU_CAPS_HOST_CPU_REPORTED); + + if (hvCPU && hvCPU->fallback == VIR_CPU_FALLBACK_FORBID && + qemuProcessCheckS390CPU(qemuCaps, cfg->libDir, + cfg->user, cfg->group, + hvCPU, def->cpu, true) == VIR_CPU_COMPARE_ERROR) + return -1; + } + /* When starting a fresh domain we disable all features removed from * the specified CPU model to make sure they are only used if * explicitly requested. But when we are restoring a previously running @@ -10589,6 +10603,67 @@ qemuProcessQMPStart(qemuProcessQMP *proc) } +virCPUCompareResult +qemuProcessCheckS390CPU(virQEMUCaps *qemuCaps, + const char *libDir, + uid_t runUid, + gid_t runGid, + virCPUDef *cpu_a, + virCPUDef *cpu_b, + bool failIncompatible) +{ + g_autoptr(qemuProcessQMP) proc = NULL; + g_auto(GStrv) responsibleProps = NULL; + virCPUCompareResult result; + + if (!(proc = qemuProcessQMPNew(virQEMUCapsGetBinary(qemuCaps), + libDir, runUid, runGid, false))) + return VIR_CPU_COMPARE_ERROR; + + if (qemuProcessQMPStart(proc) < 0) + return VIR_CPU_COMPARE_ERROR; + + result = qemuMonitorGetCPUModelComparison(proc->mon, cpu_a, cpu_b, + &responsibleProps); + + if (result == VIR_CPU_COMPARE_ERROR) + return VIR_CPU_COMPARE_ERROR; + + if (result == VIR_CPU_COMPARE_IDENTICAL || result == VIR_CPU_COMPARE_SUPERSET) + return result; + + /* result is VIR_CPU_COMPARE_INCOMPATIBLE — filter out the special "type" + * property which signals a CPU generation mismatch rather than a missing + * feature and should not be reported as such to the user. */ + if (responsibleProps) { + size_t i, j; + for (i = 0, j = 0; responsibleProps[i]; i++) { + if (STRNEQ(responsibleProps[i], "type")) + responsibleProps[j++] = responsibleProps[i]; + else + g_free(responsibleProps[i]); + } + responsibleProps[j] = NULL; + } + + if (failIncompatible) { + if (responsibleProps && responsibleProps[0]) { + g_autofree char *props = g_strjoinv(", ", responsibleProps); + + virReportError(VIR_ERR_CPU_INCOMPATIBLE, + _("guest CPU model is not compatible with the destination host CPU model, differing properties: %1$s"), + props); + } else { + virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", + _("guest CPU model is not compatible with the destination host CPU model")); + } + return VIR_CPU_COMPARE_ERROR; + } + + return VIR_CPU_COMPARE_INCOMPATIBLE; +} + + void qemuProcessHandleNbdkitExit(qemuNbdkitProcess *nbdkit, virDomainObj *vm) diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index 5d8f1f89d5..d9dd5e185e 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -258,6 +258,15 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuProcessQMP, qemuProcessQMPFree); int qemuProcessQMPStart(qemuProcessQMP *proc); +virCPUCompareResult +qemuProcessCheckS390CPU(virQEMUCaps *qemuCaps, + const char *libDir, + uid_t runUid, + gid_t runGid, + virCPUDef *cpu_a, + virCPUDef *cpu_b, + bool failIncompatible); + void qemuProcessCleanupMigrationJob(virQEMUDriver *driver, virDomainObj *vm); -- 2.48.1
participants (1)
-
Rorie Reyes