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 <[email protected]> --- 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
