From e8d6c289550bee3ecc9c7c818b85e5dd1228fb52 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Mon, 8 Feb 2010 16:37:17 +0000 Subject: [PATCH] Support VCPU hotplug in QEMU guests QEMU has a monitor command 'set_cpu' which allows a specific CPU to be toggled between online& offline state. libvirt CPU hotplug does not work in terms of individual indexes CPUs. Thus to support this, we iteratively toggle the online state when the total number of vCPUs is adjusted via libvirt NB, currently untested since QEMU segvs when running this! * src/qemu/qemu_driver.c: Toggle online state for CPUs when doing hotplug * src/qemu/qemu_monitor.c, src/qemu/qemu_monitor.h, src/qemu/qemu_monitor_json.c, src/qemu/qemu_monitor_json.h, src/qemu/qemu_monitor_text.c, src/qemu/qemu_monitor_text.h: Add monitor API for toggling a CPU's online status via 'set_cpu --- src/qemu/qemu_driver.c | 77 ++++++++++++++++++++++++++++++++++-- src/qemu/qemu_monitor.c | 14 +++++++ src/qemu/qemu_monitor.h | 2 + src/qemu/qemu_monitor_json.c | 44 +++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 1 + src/qemu/qemu_monitor_text.c | 38 ++++++++++++++++++ src/qemu/qemu_monitor_text.h | 1 + 7 files changed, 173 insertions(+), 4 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 2c048395b2..b8b7916e51 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4494,10 +4494,56 @@ cleanup: } -static int qemudDomainSetVcpus(virDomainPtr dom, - ATTRIBUTE_UNUSED unsigned int nvcpus) { +static int qemudDomainHotplugVcpus(virDomainObjPtr vm, unsigned int nvcpus) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + int i, rc; + int ret = -1; + + /* We need different branches here, because we want to offline + * in reverse order to onlining, so any partial fail leaves us in a + * reasonably sensible state */ + if (nvcpus > vm->def->vcpus) { + for (i = vm->def->vcpus ; i < nvcpus ; i++) { + /* Online new CPU */ + rc = qemuMonitorSetCPU(priv->mon, i, 1); + if (rc == 0) + goto unsupported; + if (rc < 0) + goto cleanup; + + vm->def->vcpus++; + } + } else { + for (i = vm->def->vcpus - 1 ; i >= nvcpus ; i--) { + /* Offline old CPU */ + rc = qemuMonitorSetCPU(priv->mon, i, 0); + if (rc == 0) + goto unsupported; + if (rc < 0) + goto cleanup; + + vm->def->vcpus--; + } + } + + ret = 0; + +cleanup: + return ret; + +unsupported: + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot change vcpu count of this domain")); + goto cleanup; +} + + +static int qemudDomainSetVcpus(virDomainPtr dom, unsigned int nvcpus) { struct qemud_driver *driver = dom->conn->privateData; virDomainObjPtr vm; + const char * type; + int max; int ret = -1; qemuDriverLock(driver); @@ -4518,8 +4564,31 @@ static int qemudDomainSetVcpus(virDomainPtr dom, goto cleanup; } - qemuReportError(VIR_ERR_NO_SUPPORT, - "%s", _("cpu hotplug not yet supported")); + if (!(type = virDomainVirtTypeToString(vm->def->virtType))) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown virt type in domain definition '%d'"), + vm->def->virtType); + goto endjob; + } + + if ((max = qemudGetMaxVCPUs(NULL, type)) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("could not determine max vcpus for the domain")); + goto endjob; + } + + if (nvcpus > max) { + qemuReportError(VIR_ERR_INVALID_ARG, + _("requested vcpus is greater than max allowable" + " vcpus for the domain: %d > %d"), nvcpus, max); + goto endjob; + } + + ret = qemudDomainHotplugVcpus(vm, nvcpus); + +endjob: + if (qemuDomainObjEndJob(vm) == 0) + vm = NULL; cleanup: if (vm) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index a4d2b890e2..c252bce584 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -925,6 +925,20 @@ int qemuMonitorSetBalloon(qemuMonitorPtr mon, return ret; } + +int qemuMonitorSetCPU(qemuMonitorPtr mon, int cpu, int online) +{ + int ret; + DEBUG("mon=%p, fd=%d cpu=%d online=%d", mon, mon->fd, cpu, online); + + if (mon->json) + ret = qemuMonitorJSONSetCPU(mon, cpu, online); + else + ret = qemuMonitorTextSetCPU(mon, cpu, online); + return ret; +} + + int qemuMonitorEjectMedia(qemuMonitorPtr mon, const char *devname) { diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 3e552360e2..15a2ff03d4 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -146,6 +146,8 @@ int qemuMonitorSetVNCPassword(qemuMonitorPtr mon, const char *password); int qemuMonitorSetBalloon(qemuMonitorPtr mon, unsigned long newmem); +int qemuMonitorSetCPU(qemuMonitorPtr mon, int cpu, int online); + /* XXX should we pass the virDomainDiskDefPtr instead * and hide devname details inside monitor. Reconsider diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 9c0245c9b9..f04fd2eb5a 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -914,6 +914,50 @@ cleanup: } +/* + * Returns: 0 if CPU hotplug not supported, +1 if CPU hotplug worked + * or -1 on failure + */ +int qemuMonitorJSONSetCPU(qemuMonitorPtr mon, + int cpu, int online) +{ + int ret; + virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("balloon", + "U:cpu", (unsigned long long)cpu, + "s:state", online ? "online" : "offline", + NULL); + virJSONValuePtr reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) { + /* XXX See if CPU soft-failed due to lack of ACPI */ +#if 0 + if (qemuMonitorJSONHasError(reply, "DeviceNotActive") || + qemuMonitorJSONHasError(reply, "KVMMissingCap")) + goto cleanup; +#endif + + /* See if any other fatal error occurred */ + ret = qemuMonitorJSONCheckError(cmd, reply); + + /* Real success */ + if (ret == 0) + ret = 1; + } + +#if 0 +cleanup: +#endif + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + + int qemuMonitorJSONEjectMedia(qemuMonitorPtr mon, const char *devname) { diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 70a8dae6cb..f18c7991b7 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -59,6 +59,7 @@ int qemuMonitorJSONSetVNCPassword(qemuMonitorPtr mon, const char *password); int qemuMonitorJSONSetBalloon(qemuMonitorPtr mon, unsigned long newmem); +int qemuMonitorJSONSetCPU(qemuMonitorPtr mon, int cpu, int online); int qemuMonitorJSONEjectMedia(qemuMonitorPtr mon, const char *devname); diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index 82f7a9b9cb..7f0e7f65dc 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -797,6 +797,44 @@ int qemuMonitorTextSetBalloon(qemuMonitorPtr mon, return ret; } + +/* + * Returns: 0 if balloon not supported, +1 if balloon adjust worked + * or -1 on failure + */ +int qemuMonitorTextSetCPU(qemuMonitorPtr mon, int cpu, int online) +{ + char *cmd; + char *reply = NULL; + int ret = -1; + + if (virAsprintf(&cmd, "set_cpu %d %s", cpu, online ? "online" : "offline") < 0) { + virReportOOMError(); + return -1; + } + + if (qemuMonitorCommand(mon, cmd, &reply) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("could nt change CPU online status")); + VIR_FREE(cmd); + return -1; + } + VIR_FREE(cmd); + + /* If the command failed qemu prints: 'unknown command' + * No message is printed on success it seems */ + if (strstr(reply, "\nunknown command:")) { + /* Don't set error - it is expected CPU onlining fails on many qemu - caller will handle */ + ret = 0; + } else { + ret = 1; + } + + VIR_FREE(reply); + return ret; +} + + int qemuMonitorTextEjectMedia(qemuMonitorPtr mon, const char *devname) { diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index 9dcb0c2e6c..ddacb42d6d 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -61,6 +61,7 @@ int qemuMonitorTextSetVNCPassword(qemuMonitorPtr mon, const char *password); int qemuMonitorTextSetBalloon(qemuMonitorPtr mon, unsigned long newmem); +int qemuMonitorTextSetCPU(qemuMonitorPtr mon, int cpu, int online); int qemuMonitorTextEjectMedia(qemuMonitorPtr mon, const char *devname);