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

Reply via email to