[libvirt] [PATCH v4 10/11] Implement CPU selection in QEMU driver

Jiri Denemark jdenemar at redhat.com
Thu Dec 17 20:21:44 UTC 2009


Signed-off-by: Jiri Denemark <jdenemar at redhat.com>
---
 src/qemu/qemu_conf.c   |  398 +++++++++++++++++++++++++++++++++++++++++++++---
 src/qemu/qemu_conf.h   |    7 +
 src/qemu/qemu_driver.c |   44 ++++--
 3 files changed, 419 insertions(+), 30 deletions(-)

diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index 2ee2042..36bf9a2 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -52,6 +52,7 @@
 #include "nodeinfo.h"
 #include "logging.h"
 #include "network.h"
+#include "cpu/cpu.h"
 
 #define VIR_FROM_THIS VIR_FROM_QEMU
 
@@ -608,6 +609,151 @@ qemudGetOldMachines(const char *ostype,
     return 0;
 }
 
+
+typedef int
+(*qemudParseCPUModels)(const char *output,
+                       unsigned int *retcount,
+                       const char ***retcpus);
+
+/* Format:
+ * <arch> <model>
+ */
+static int
+qemudParseX86Models(const char *output,
+                    unsigned int *retcount,
+                    const char ***retcpus)
+{
+    const char *p = output;
+    const char *next;
+    unsigned int count = 0;
+    const char **cpus = NULL;
+    int i;
+
+    do {
+        const char *t;
+
+        if ((next = strchr(p, '\n')))
+            next++;
+
+        if (!(t = strchr(p, ' ')) || (next && t >= next))
+            continue;
+
+        if (!STRPREFIX(p, "x86"))
+            continue;
+
+        p = t;
+        while (*p == ' ')
+            p++;
+
+        if (*p == '\0' || *p == '\n')
+            continue;
+
+        if (retcpus) {
+            if (VIR_REALLOC_N(cpus, count + 1) < 0)
+                goto error;
+
+            if (next)
+                cpus[count] = strndup(p, next - p - 1);
+            else
+                cpus[count] = strdup(p);
+
+            if (!cpus[count])
+                goto error;
+        }
+        count++;
+    } while ((p = next));
+
+    if (retcount)
+        *retcount = count;
+    if (retcpus)
+        *retcpus = cpus;
+
+    return 0;
+
+error:
+    if (cpus) {
+        for (i = 0; i < count; i++)
+            VIR_FREE(cpus[i]);
+    }
+    VIR_FREE(cpus);
+
+    return -1;
+}
+
+
+int
+qemudProbeCPUModels(const char *qemu,
+                    const char *arch,
+                    unsigned int *count,
+                    const char ***cpus)
+{
+    const char *const qemuarg[] = { qemu, "-cpu", "?", NULL };
+    const char *const qemuenv[] = { "LC_ALL=C", NULL };
+    enum { MAX_MACHINES_OUTPUT_SIZE = 1024*4 };
+    char *output = NULL;
+    int newstdout = -1;
+    int ret = -1;
+    pid_t child;
+    int status;
+    int len;
+    qemudParseCPUModels parse;
+
+    if (count)
+        *count = 0;
+    if (cpus)
+        *cpus = NULL;
+
+    if (STREQ(arch, "i686") || STREQ(arch, "x86_64"))
+        parse = qemudParseX86Models;
+    else {
+        VIR_DEBUG(_("don't know how to parse %s CPU models"), arch);
+        return 0;
+    }
+
+    if (virExec(NULL, qemuarg, qemuenv, NULL,
+                &child, -1, &newstdout, NULL, VIR_EXEC_CLEAR_CAPS) < 0)
+        return -1;
+
+    len = virFileReadLimFD(newstdout, MAX_MACHINES_OUTPUT_SIZE, &output);
+    if (len < 0) {
+        virReportSystemError(NULL, errno, "%s",
+                             _("Unable to read QEMU supported CPU models"));
+        goto cleanup;
+    }
+
+    if (parse(output, count, cpus) < 0) {
+        virReportOOMError(NULL);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(output);
+    if (close(newstdout) < 0)
+        ret = -1;
+
+rewait:
+    if (waitpid(child, &status, 0) != child) {
+        if (errno == EINTR)
+            goto rewait;
+
+        VIR_ERROR(_("Unexpected exit status from qemu %d pid %lu"),
+                  WEXITSTATUS(status), (unsigned long)child);
+        ret = -1;
+    }
+    /* Check & log unexpected exit status, but don't fail,
+     * as there's really no need to throw an error if we did
+     * actually read a valid version number above */
+    if (WEXITSTATUS(status) != 0) {
+        VIR_WARN(_("Unexpected exit status '%d', qemu probably failed"),
+                 WEXITSTATUS(status));
+    }
+
+    return ret;
+}
+
+
 static int
 qemudCapsInitGuest(virCapsPtr caps,
                    virCapsPtr old_caps,
@@ -624,6 +770,7 @@ qemudCapsInitGuest(virCapsPtr caps,
     virCapsGuestMachinePtr *machines = NULL;
     int nmachines = 0;
     struct stat st;
+    unsigned int ncpus;
 
     /* Check for existance of base emulator, or alternate base
      * which can be used with magic cpu choice
@@ -725,6 +872,11 @@ qemudCapsInitGuest(virCapsPtr caps,
 
     guest->arch.defaultInfo.emulator_mtime = binary_mtime;
 
+    if (qemudProbeCPUModels(binary, info->arch, &ncpus, NULL) == 0
+        && ncpus > 0
+        && !virCapabilitiesAddGuestFeature(guest, "cpuselection", 1, 0))
+        return -1;
+
     if (hvm) {
         if (virCapabilitiesAddGuestDomain(guest,
                                           "qemu",
@@ -808,6 +960,49 @@ qemudCapsInitGuest(virCapsPtr caps,
     return 0;
 }
 
+
+static int
+qemudCapsInitCPU(virCapsPtr caps,
+                 const char *arch)
+{
+    virCPUDefPtr cpu = NULL;
+    union cpuData *data = NULL;
+    virNodeInfo nodeinfo;
+    int ret = -1;
+
+    if (VIR_ALLOC(cpu) < 0
+        || !(cpu->arch = strdup(arch))) {
+        virReportOOMError(NULL);
+        goto error;
+    }
+
+    if (nodeGetInfo(NULL, &nodeinfo))
+        goto error;
+
+    cpu->type = VIR_CPU_TYPE_HOST;
+    cpu->sockets = nodeinfo.sockets;
+    cpu->cores = nodeinfo.cores;
+    cpu->threads = nodeinfo.threads;
+
+    if (!(data = cpuNodeData(NULL, arch))
+        || cpuDecode(NULL, cpu, data, 0, NULL) < 0)
+        goto error;
+
+    caps->host.cpu = cpu;
+
+    ret = 0;
+
+cleanup:
+    cpuDataFree(NULL, arch, data);
+
+    return ret;
+
+error:
+    virCPUDefFree(cpu);
+    goto cleanup;
+}
+
+
 virCapsPtr qemudCapsInit(virCapsPtr old_caps) {
     struct utsname utsname;
     virCapsPtr caps;
@@ -832,6 +1027,15 @@ virCapsPtr qemudCapsInit(virCapsPtr old_caps) {
         VIR_WARN0("Failed to query host NUMA topology, disabling NUMA capabilities");
     }
 
+    if (old_caps == NULL || old_caps->host.cpu == NULL) {
+        if (qemudCapsInitCPU(caps, utsname.machine) < 0)
+            VIR_WARN0("Failed to get host CPU");
+    }
+    else {
+        caps->host.cpu = old_caps->host.cpu;
+        old_caps->host.cpu = NULL;
+    }
+
     virCapabilitiesAddHostMigrateTransport(caps,
                                            "tcp");
 
@@ -1563,6 +1767,98 @@ static void qemudBuildCommandLineChrDevStr(virDomainChrDefPtr dev,
     }
 }
 
+
+static int
+qemudBuildCommandLineCPU(virConnectPtr conn,
+                         const struct qemud_driver *driver,
+                         const virDomainDefPtr def,
+                         const char *emulator,
+                         const struct utsname *ut,
+                         char **opt)
+{
+    const virCPUDefPtr host = driver->caps->host.cpu;
+    virCPUDefPtr guest = NULL;
+    unsigned int ncpus;
+    const char **cpus = NULL;
+    union cpuData *data = NULL;
+    int ret = -1;
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    int i;
+
+    if (qemudProbeCPUModels(emulator, ut->machine, &ncpus, &cpus) < 0)
+        goto cleanup;
+
+    if (ncpus > 0 && host && def->cpu && def->cpu->model) {
+        virCPUCompareResult cmp;
+
+        cmp = cpuGuestData(conn, host, def->cpu, &data);
+        switch (cmp) {
+        case VIR_CPU_COMPARE_INCOMPATIBLE:
+            qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+                    "%s", _("guest CPU is not compatible with host CPU"));
+            /* fall through */
+        case VIR_CPU_COMPARE_ERROR:
+            goto cleanup;
+
+        default:
+            break;
+        }
+
+        if (VIR_ALLOC(guest) < 0 || !(guest->arch = strdup(ut->machine)))
+            goto no_memory;
+
+        if (cpuDecode(conn, guest, data, ncpus, cpus) < 0)
+            goto cleanup;
+
+        virBufferVSprintf(&buf, "%s", guest->model);
+        for (i = 0; i < guest->nfeatures; i++)
+            virBufferVSprintf(&buf, ",+%s", guest->features[i].name);
+    }
+    else {
+        /*
+         * Need to force a 32-bit guest CPU type if
+         *
+         *  1. guest OS is i686
+         *  2. host OS is x86_64
+         *  3. emulator is qemu-kvm or kvm
+         *
+         * Or
+         *
+         *  1. guest OS is i686
+         *  2. emulator is qemu-system-x86_64
+         */
+        if (STREQ(def->os.arch, "i686") &&
+            ((STREQ(ut->machine, "x86_64") &&
+              strstr(emulator, "kvm")) ||
+             strstr(emulator, "x86_64")))
+            virBufferAddLit(&buf, "qemu32");
+    }
+
+    if (virBufferError(&buf))
+        goto no_memory;
+
+    *opt = virBufferContentAndReset(&buf);
+
+    ret = 0;
+
+cleanup:
+    virCPUDefFree(guest);
+    cpuDataFree(conn, ut->machine, data);
+
+    if (cpus) {
+        for (i = 0; i < ncpus; i++)
+            VIR_FREE(cpus[i]);
+        VIR_FREE(cpus);
+    }
+
+    return ret;
+
+no_memory:
+    virReportOOMError(conn);
+    goto cleanup;
+}
+
+
 #define QEMU_SERIAL_PARAM_ACCEPTED_CHARS \
   "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
 
@@ -1610,7 +1906,7 @@ int qemudBuildCommandLine(virConnectPtr conn,
     const char *emulator;
     char uuid[VIR_UUID_STRING_BUFLEN];
     char domid[50];
-    const char *cpu = NULL;
+    char *cpu;
 
     uname_normalize(&ut);
 
@@ -1670,24 +1966,6 @@ int qemudBuildCommandLine(virConnectPtr conn,
         def->virtType == VIR_DOMAIN_VIRT_KVM)
         enableKVM = 1;
 
-    /*
-     * Need to force a 32-bit guest CPU type if
-     *
-     *  1. guest OS is i686
-     *  2. host OS is x86_64
-     *  3. emulator is qemu-kvm or kvm
-     *
-     * Or
-     *
-     *  1. guest OS is i686
-     *  2. emulator is qemu-system-x86_64
-     */
-    if (STREQ(def->os.arch, "i686") &&
-        ((STREQ(ut.machine, "x86_64") &&
-          strstr(emulator, "kvm")) ||
-         strstr(emulator, "x86_64")))
-        cpu = "qemu32";
-
 #define ADD_ARG_SPACE                                                   \
     do { \
         if (qargc == qarga) {                                           \
@@ -1788,9 +2066,14 @@ int qemudBuildCommandLine(virConnectPtr conn,
         ADD_ARG_LIT("-M");
         ADD_ARG_LIT(def->os.machine);
     }
+
+    if (qemudBuildCommandLineCPU(conn, driver, def, emulator, &ut, &cpu) < 0)
+        goto error;
+
     if (cpu) {
         ADD_ARG_LIT("-cpu");
         ADD_ARG_LIT(cpu);
+        VIR_FREE(cpu);
     }
 
     if (disableKQEMU)
@@ -3429,6 +3712,79 @@ error:
     return NULL;
 }
 
+
+static int
+qemuParseCommandLineCPU(virConnectPtr conn,
+                        virDomainDefPtr dom,
+                        const char *val)
+{
+    virCPUDefPtr cpu;
+    const char *p = val;
+    const char *next;
+
+    if (VIR_ALLOC(cpu) < 0)
+        goto no_memory;
+
+    cpu->type = VIR_CPU_TYPE_GUEST;
+
+    do {
+        if (*p == '\0' || *p == ',')
+            goto syntax;
+
+        if ((next = strchr(p, ',')))
+            next++;
+
+        if (!cpu->model) {
+            if (next)
+                cpu->model = strndup(p, next - p - 1);
+            else
+                cpu->model = strdup(p);
+
+            if (!cpu->model)
+                goto no_memory;
+        }
+        else if (*p == '+' || *p == '-') {
+            char *feature;
+            int policy;
+            int ret;
+
+            if (*p == '+')
+                policy = VIR_CPU_FEATURE_REQUIRE;
+            else
+                policy = VIR_CPU_FEATURE_DISABLE;
+
+            p++;
+            if (*p == '\0' || *p == ',')
+                goto syntax;
+
+            if (next)
+                feature = strndup(p, next - p - 1);
+            else
+                feature = strdup(p);
+
+            ret = virCPUDefAddFeature(conn, cpu, feature, policy);
+            VIR_FREE(feature);
+            if (ret < 0)
+                goto error;
+        }
+    } while ((p = next));
+
+    dom->cpu = cpu;
+    return 0;
+
+syntax:
+    qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+            _("unknown CPU syntax '%s'"), val);
+    goto error;
+
+no_memory:
+    virReportOOMError(conn);
+error:
+    virCPUDefFree(cpu);
+    return -1;
+}
+
+
 /*
  * Analyse the env and argv settings and reconstruct a
  * virDomainDefPtr representing these settings as closely
@@ -3856,6 +4212,10 @@ virDomainDefPtr qemuParseCommandLine(virConnectPtr conn,
                                  _("unknown video adapter type '%s'"), val);
                 goto error;
             }
+        } else if (STREQ(arg, "-cpu")) {
+            WANT_VALUE();
+            if (qemuParseCommandLineCPU(conn, def, val) < 0)
+                goto error;
         } else if (STREQ(arg, "-domid")) {
             WANT_VALUE();
             /* ignore, generted on the fly */
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index 248677a..e958850 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -37,6 +37,8 @@
 #include "security/security_driver.h"
 #include "cgroup.h"
 #include "pci.h"
+#include "cpu_conf.h"
+#include "driver.h"
 
 #define qemudDebug(fmt, ...) do {} while(0)
 
@@ -203,6 +205,11 @@ int         qemudProbeMachineTypes      (const char *binary,
                                          virCapsGuestMachinePtr **machines,
                                          int *nmachines);
 
+int         qemudProbeCPUModels         (const char *qemu,
+                                         const char *arch,
+                                         unsigned int *count,
+                                         const char ***cpus);
+
 int         qemudCanonicalizeMachine    (struct qemud_driver *driver,
                                          virDomainDefPtr def);
 
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 80ffce6..ceabff2 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -73,6 +73,7 @@
 #include "cgroup.h"
 #include "libvirt_internal.h"
 #include "xml.h"
+#include "cpu/cpu.h"
 
 
 #define VIR_FROM_THIS VIR_FROM_QEMU
@@ -2844,24 +2845,19 @@ static int qemudGetMaxVCPUs(virConnectPtr conn, const char *type) {
 
 static char *qemudGetCapabilities(virConnectPtr conn) {
     struct qemud_driver *driver = conn->privateData;
-    virCapsPtr caps;
+    virCapsPtr caps = NULL;
     char *xml = NULL;
 
     qemuDriverLock(driver);
-    if ((caps = qemudCapsInit(qemu_driver->caps)) == NULL) {
-        virReportOOMError(conn);
-        goto cleanup;
-    }
+    if ((caps = qemudCapsInit(qemu_driver->caps)) == NULL)
+        goto no_memory;
 
     caps->privateDataAllocFunc = qemuDomainObjPrivateAlloc;
     caps->privateDataFreeFunc = qemuDomainObjPrivateFree;
 
     if (qemu_driver->securityDriver &&
-        qemudSecurityCapsInit(qemu_driver->securityDriver, caps) < 0) {
-        virCapabilitiesFree(caps);
-        virReportOOMError(conn);
-        goto cleanup;
-    }
+        qemudSecurityCapsInit(qemu_driver->securityDriver, caps) < 0)
+        goto no_memory;
 
     virCapabilitiesFree(qemu_driver->caps);
     qemu_driver->caps = caps;
@@ -2873,6 +2869,11 @@ cleanup:
     qemuDriverUnlock(driver);
 
     return xml;
+
+no_memory:
+    virCapabilitiesFree(caps);
+    virReportOOMError(conn);
+    goto cleanup;
 }
 
 
@@ -7849,6 +7850,27 @@ out:
     return ret;
 }
 
+static int
+qemuCPUCompare(virConnectPtr conn,
+               const char *xmlDesc)
+{
+    struct qemud_driver *driver = conn->privateData;
+    int ret = VIR_CPU_COMPARE_ERROR;
+
+    qemuDriverLock(driver);
+
+    if (!driver->caps || !driver->caps->host.cpu) {
+        qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT,
+                         "%s", _("cannot get host CPU capabilities"));
+    }
+    else
+        ret = cpuCompareXML(conn, driver->caps->host.cpu, xmlDesc);
+
+    qemuDriverUnlock(driver);
+
+    return ret;
+}
+
 static virDriver qemuDriver = {
     VIR_DRV_QEMU,
     "QEMU",
@@ -7923,7 +7945,7 @@ static virDriver qemuDriver = {
     qemuIsSecure,
     qemuDomainIsActive,
     qemuDomainIsPersistent,
-    NULL, /* cpuCompare */
+    qemuCPUCompare, /* cpuCompare */
 };
 
 
-- 
1.6.5.7




More information about the libvir-list mailing list