[PATCH 13/32] qemu: Canonicalize CPU models

Jiri Denemark posted 32 patches 2 weeks ago
There is a newer version of this series
[PATCH 13/32] qemu: Canonicalize CPU models
Posted by Jiri Denemark 2 weeks ago
When parsing a domain XML which uses a non-versioned CPU model we want
to replace it with the appropriate version variant similarly to what we
do with machine types. Theoretically QEMU supports per machine type
specification of a version with which a non-versioned CPU model is
replaced, but this is always 1 for all machine types and the
query-machines QMP command does not even report the value.

Luckily after talking to Igor, having a single number per machine type
does not really allow for setting it to anything but 1 as CPU models
have different number of versions. Each machine type would need to
define a specific version for each CPU model, which would be a
maintenance nightmare. For this reason there's no desire to ever resolve
non-versioned CPU models to anything but v1 in QEMU and the per machine
type setting will most likely even be removed completely. Thus it is
safe for us to always use v1 as the canonical CPU model.

Some non-versioned CPU models, however, are actually aliases to specific
versions of a base model rather than being base models themselves. These
are the old CPU model variants before model versions were introduced,
e.g., -noTSX, -IBRS, etc. The mapping of these names to versions is
hardcoded and will never change. We do not translate such CPU models to
the corresponding versioned names. This allows us to introduce the
corresponding -v* variants that match the QEMU models rather than the
existing definitions in our CPU map. The guest CPU will be the same
either way, but the way libvirt checks the CPU model compatibility with
the host will be different. The old "partial" check done by libvirt
using the definition from CPU map will still be used for the old names
(we can't change this for compatibility reasons), but the corresponding
versioned variants (as well as all other versions that do not have a
non-versioned alias) will benefit from the recently introduced new
"partial" check which uses only the information we get from QEMU to
check whether a specific CPU definition is usable on the host.

Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
---
 src/qemu/qemu_capabilities.c | 53 ++++++++++++++++++++++++++++++++++++
 src/qemu/qemu_capabilities.h |  3 ++
 src/qemu/qemu_domain.c       |  6 ++++
 src/qemu/qemu_postparse.c    | 19 +++++++++++++
 4 files changed, 81 insertions(+)

diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
index 5ac9f306f5..455fb5acbe 100644
--- a/src/qemu/qemu_capabilities.c
+++ b/src/qemu/qemu_capabilities.c
@@ -2379,6 +2379,59 @@ virQEMUCapsGetCanonicalMachine(virQEMUCaps *qemuCaps,
 }
 
 
+/**
+ * virQEMUCapsGetCanonicalCPU:
+ * @qemuCaps: QEMU capabilities
+ * @type: virtualization type
+ * @model: CPU model name
+ *
+ * Resolve a CPU model without explicit version to its versioned twin. We only
+ * replace a base CPU model with its -v1 variant as that's what QEMU is doing.
+ * Theoretically they have a per machine type configuration of the default CPU
+ * version to be used if no explicit version is specified, but it's always 1
+ * and they will not change it. And they do not report the default CPU version
+ * via QMP anyway.
+ *
+ * The old named variants of other CPU models (-noTSX, -IBRS, etc.) are ignored
+ * as they are hardcoded aliases to specific versions and not "dynamically"
+ * resolved.
+ *
+ * Returns the canonical versioned name of the CPU model or NULL if the CPU
+ * model should stay unchanged. The pointer (which must never be freed by the
+ * caller) is valid as long as the caller holds a reference to the qemuCaps
+ * object.
+ */
+const char *
+virQEMUCapsGetCanonicalCPU(virQEMUCaps *qemuCaps,
+                           virDomainVirtType type,
+                           const char *model)
+{
+    qemuMonitorCPUDefs *models;
+    size_t i;
+
+    if (!ARCH_IS_X86(qemuCaps->arch) || !model)
+        return NULL;
+
+    models = virQEMUCapsGetAccel(qemuCaps, type)->cpuModels;
+    if (!models)
+        return NULL;
+
+    for (i = 0; i < models->ncpus; i++) {
+        qemuMonitorCPUDefInfo *cpu = models->cpus + i;
+        const char *p = STRSKIP(cpu->name, model);
+
+        if (p && STREQ(p, "-v1")) {
+            if (virCPUCheckModel(qemuCaps->arch, cpu->name))
+                return cpu->name;
+
+            break;
+        }
+    }
+
+    return NULL;
+}
+
+
 int
 virQEMUCapsGetMachineMaxCpus(virQEMUCaps *qemuCaps,
                              virDomainVirtType virtType,
diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
index 48e4530c95..7decf31b97 100644
--- a/src/qemu/qemu_capabilities.h
+++ b/src/qemu/qemu_capabilities.h
@@ -778,6 +778,9 @@ bool virQEMUCapsIsCPUModeSupported(virQEMUCaps *qemuCaps,
 const char *virQEMUCapsGetCanonicalMachine(virQEMUCaps *qemuCaps,
                                            virDomainVirtType virtType,
                                            const char *name);
+const char *virQEMUCapsGetCanonicalCPU(virQEMUCaps *qemuCaps,
+                                       virDomainVirtType type,
+                                       const char *model);
 bool virQEMUCapsIsMachineSupported(virQEMUCaps *qemuCaps,
                                    virDomainVirtType virtType,
                                    const char *canonical_machine)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 153bd56e86..d922f482c2 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -5057,6 +5057,12 @@ qemuDomainMakeCPUMigratable(virArch arch,
         !ARCH_IS_X86(arch))
         return 0;
 
+    /* Strip -v1 suffix from the CPU model for backward compatibility with
+     * libvirt releases that do not support versioned CPU models.
+     */
+    if (g_str_has_suffix(cpu->model, "-v1"))
+        cpu->model[strlen(cpu->model) - 3] = '\0';
+
     if (STREQ(cpu->model, "Icelake-Server")) {
         /* Originally Icelake-Server CPU model contained pconfig CPU feature.
          * It was never actually enabled and thus it was removed. To enable
diff --git a/src/qemu/qemu_postparse.c b/src/qemu/qemu_postparse.c
index 03b5ef825a..d3d85de11c 100644
--- a/src/qemu/qemu_postparse.c
+++ b/src/qemu/qemu_postparse.c
@@ -1656,6 +1656,23 @@ qemuDomainDefVcpusPostParse(virDomainDef *def)
 }
 
 
+static void
+qemuDomainCanonicalizeCPU(virDomainDef *def,
+                          virQEMUCaps *qemuCaps)
+{
+    const char *model;
+
+    model = virQEMUCapsGetCanonicalCPU(qemuCaps, def->virtType, def->cpu->model);
+    if (!model)
+        return;
+
+    VIR_DEBUG("Replacing CPU model '%1$s' with '%2$s'", def->cpu->model, model);
+
+    g_free(def->cpu->model);
+    def->cpu->model = g_strdup(model);
+}
+
+
 static int
 qemuDomainDefCPUPostParse(virDomainDef *def,
                           virQEMUCaps *qemuCaps)
@@ -1667,6 +1684,8 @@ qemuDomainDefCPUPostParse(virDomainDef *def,
     if (!def->cpu)
         return 0;
 
+    qemuDomainCanonicalizeCPU(def, qemuCaps);
+
     for (i = 0; i < def->cpu->nfeatures; i++) {
         virCPUFeatureDef *feature = &def->cpu->features[i];
 
-- 
2.47.0