mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-22 03:12:22 +00:00
Implement CPU selection in QEMU driver
* src/qemu/qemu_conf.c src/qemu/qemu_conf.h src/qemu/qemu_driver.c: add the new entry point, extend capabilities and code to interract with qemu
This commit is contained in:
parent
d5ef0a69e9
commit
ffb13b1115
@ -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 */
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,28 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
qemuCPUCompare(virConnectPtr conn,
|
||||
const char *xmlDesc,
|
||||
unsigned int flags ATTRIBUTE_UNUSED)
|
||||
{
|
||||
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 +7946,7 @@ static virDriver qemuDriver = {
|
||||
qemuIsSecure,
|
||||
qemuDomainIsActive,
|
||||
qemuDomainIsPersistent,
|
||||
NULL, /* cpuCompare */
|
||||
qemuCPUCompare, /* cpuCompare */
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user