Add VIR_DOMAIN_VCPU_ASYNC to the vCPU management APIs and introduce VIR_DOMAIN_EVENT_ID_VCPU_REMOVED, carrying the libvirt XML vCPU id. For live vCPU unplug, async mode returns after successfully submitting the unplug request instead of doing the short wait used by the non-async path. The live XML is left unchanged until completion is confirmed, and the final outcome is reported through domain events: successful completion emits VCPU_REMOVED, while guest-rejected unplug requests continue to emit DEVICE_REMOVAL_FAILED. This closes the current gap where successful vCPU hot-unplug emits no domain event for virsh event or other libvirt event consumers to observe. Thread the new event through the remote protocol, add --async to virsh setvcpus and virsh setvcpu, and teach virsh event and event-test about vcpu-removed. Async mode is supported only for live vCPU unplug. Signed-off-by: Akash Kulhalli <akash.kulhalli@oracle.com> --- examples/c/misc/event-test.c | 12 +++++ include/libvirt/libvirt-domain.h | 23 +++++++++ src/conf/domain_event.c | 66 +++++++++++++++++++++++++ src/conf/domain_event.h | 6 +++ src/libvirt-domain.c | 40 +++++++++++++++- src/libvirt_private.syms | 2 + src/qemu/qemu_driver.c | 33 +++++++++++-- src/qemu/qemu_hotplug.c | 74 +++++++++++++++++++++++++---- src/qemu/qemu_hotplug.h | 8 ++-- src/remote/remote_daemon_dispatch.c | 26 ++++++++++ src/remote/remote_driver.c | 29 +++++++++++ src/remote/remote_protocol.x | 14 +++++- src/remote_protocol-structs | 6 +++ tests/qemuhotplugtest.c | 6 ++- tools/virsh-domain-event.c | 16 +++++++ tools/virsh-domain.c | 34 +++++++++++++ 16 files changed, 373 insertions(+), 22 deletions(-) diff --git a/examples/c/misc/event-test.c b/examples/c/misc/event-test.c index 2ce82ca9e088..f9e65c55f064 100644 --- a/examples/c/misc/event-test.c +++ b/examples/c/misc/event-test.c @@ -1039,6 +1039,17 @@ myDomainEventDeviceRemovalFailedCallback(virConnectPtr conn G_GNUC_UNUSED, return 0; } +static int +myDomainEventVcpuRemovedCallback(virConnectPtr conn G_GNUC_UNUSED, + virDomainPtr dom, + unsigned int vcpuid, + void *opaque G_GNUC_UNUSED) +{ + printf("%s EVENT: Domain %s(%d) vcpu removed: %u\n", + __func__, virDomainGetName(dom), virDomainGetID(dom), vcpuid); + return 0; +} + static const char * metadataTypeToStr(int status) @@ -1183,6 +1194,7 @@ struct domainEventData domainEvents[] = { DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_MEMORY_FAILURE, myDomainEventMemoryFailureCallback), DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_MEMORY_DEVICE_SIZE_CHANGE, myDomainEventMemoryDeviceSizeChangeCallback), DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_NIC_MAC_CHANGE, myDomainEventNICMACChangeCallback), + DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_VCPU_REMOVED, myDomainEventVcpuRemovedCallback), }; struct storagePoolEventData { diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 4a8e3114b35d..a326d133ee41 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -2605,6 +2605,7 @@ typedef enum { VIR_DOMAIN_VCPU_MAXIMUM = (1 << 2), /* Max rather than current count (Since: 0.8.5) */ VIR_DOMAIN_VCPU_GUEST = (1 << 3), /* Modify state of the cpu in the guest (Since: 1.1.0) */ VIR_DOMAIN_VCPU_HOTPLUGGABLE = (1 << 4), /* Make vcpus added hot(un)pluggable (Since: 2.4.0) */ + VIR_DOMAIN_VCPU_ASYNC = (1 << 5), /* Return after firing live unplug request(s) (Since: 12.3.0) */ } virDomainVcpuFlags; int virDomainSetVcpus (virDomainPtr domain, @@ -6965,6 +6966,27 @@ typedef void (*virConnectDomainEventDeviceRemovalFailedCallback)(virConnectPtr c const char *devAlias, void *opaque); +/** + * virConnectDomainEventVcpuRemovedCallback: + * @conn: connection object + * @dom: domain on which the event occurred + * @vcpuid: libvirt XML vCPU id + * @opaque: application specified data + * + * This callback occurs when a vCPU is removed from the domain. + * + * The @vcpuid value matches the ``<vcpu id='...'>`` value from the domain XML. + * + * The callback signature to use when registering for an event of type + * VIR_DOMAIN_EVENT_ID_VCPU_REMOVED with virConnectDomainEventRegisterAny(). + * + * Since: 12.3.0 + */ +typedef void (*virConnectDomainEventVcpuRemovedCallback)(virConnectPtr conn, + virDomainPtr dom, + unsigned int vcpuid, + void *opaque); + /** * virConnectDomainEventMetadataChangeCallback: * @conn: connection object @@ -7617,6 +7639,7 @@ typedef enum { VIR_DOMAIN_EVENT_ID_MEMORY_FAILURE = 25, /* virConnectDomainEventMemoryFailureCallback (Since: 6.9.0) */ VIR_DOMAIN_EVENT_ID_MEMORY_DEVICE_SIZE_CHANGE = 26, /* virConnectDomainEventMemoryDeviceSizeChangeCallback (Since: 7.9.0) */ VIR_DOMAIN_EVENT_ID_NIC_MAC_CHANGE = 27, /* virConnectDomainEventNICMACChangeCallback (Since: 11.2.0) */ + VIR_DOMAIN_EVENT_ID_VCPU_REMOVED = 28, /* virConnectDomainEventVcpuRemovedCallback (Since: 12.3.0) */ # ifdef VIR_ENUM_SENTINELS VIR_DOMAIN_EVENT_ID_LAST diff --git a/src/conf/domain_event.c b/src/conf/domain_event.c index 88087bad4f21..17ad4a0d2c91 100644 --- a/src/conf/domain_event.c +++ b/src/conf/domain_event.c @@ -53,6 +53,7 @@ static virClass *virDomainEventDeviceAddedClass; static virClass *virDomainEventMigrationIterationClass; static virClass *virDomainEventJobCompletedClass; static virClass *virDomainEventDeviceRemovalFailedClass; +static virClass *virDomainEventVcpuRemovedClass; static virClass *virDomainEventMetadataChangeClass; static virClass *virDomainEventBlockThresholdClass; static virClass *virDomainEventMemoryFailureClass; @@ -78,6 +79,7 @@ static void virDomainEventDeviceAddedDispose(void *obj); static void virDomainEventMigrationIterationDispose(void *obj); static void virDomainEventJobCompletedDispose(void *obj); static void virDomainEventDeviceRemovalFailedDispose(void *obj); +static void virDomainEventVcpuRemovedDispose(void *obj); static void virDomainEventMetadataChangeDispose(void *obj); static void virDomainEventBlockThresholdDispose(void *obj); static void virDomainEventMemoryFailureDispose(void *obj); @@ -251,6 +253,13 @@ struct _virDomainEventDeviceRemovalFailed { }; typedef struct _virDomainEventDeviceRemovalFailed virDomainEventDeviceRemovalFailed; +struct _virDomainEventVcpuRemoved { + virDomainEvent parent; + + unsigned int vcpuid; +}; +typedef struct _virDomainEventVcpuRemoved virDomainEventVcpuRemoved; + struct _virDomainEventMetadataChange { virDomainEvent parent; @@ -337,6 +346,8 @@ virDomainEventsOnceInit(void) return -1; if (!VIR_CLASS_NEW(virDomainEventDeviceRemovalFailed, virDomainEventClass)) return -1; + if (!VIR_CLASS_NEW(virDomainEventVcpuRemoved, virDomainEventClass)) + return -1; if (!VIR_CLASS_NEW(virDomainEventMetadataChange, virDomainEventClass)) return -1; if (!VIR_CLASS_NEW(virDomainEventBlockThreshold, virDomainEventClass)) @@ -484,6 +495,13 @@ virDomainEventDeviceRemovalFailedDispose(void *obj) g_free(event->devAlias); } +static void +virDomainEventVcpuRemovedDispose(void *obj) +{ + virDomainEventVcpuRemoved *event = obj; + VIR_DEBUG("obj=%p", event); +} + static void virDomainEventPMDispose(void *obj) @@ -1382,6 +1400,43 @@ virDomainEventDeviceRemovalFailedNewFromDom(virDomainPtr dom, devAlias); } +static virObjectEvent * +virDomainEventVcpuRemovedNew(int id, + const char *name, + unsigned char *uuid, + unsigned int vcpuid) +{ + virDomainEventVcpuRemoved *ev; + + if (virDomainEventsInitialize() < 0) + return NULL; + + if (!(ev = virDomainEventNew(virDomainEventVcpuRemovedClass, + VIR_DOMAIN_EVENT_ID_VCPU_REMOVED, + id, name, uuid))) + return NULL; + + ev->vcpuid = vcpuid; + + return (virObjectEvent *)ev; +} + +virObjectEvent * +virDomainEventVcpuRemovedNewFromObj(virDomainObj *obj, + unsigned int vcpuid) +{ + return virDomainEventVcpuRemovedNew(obj->def->id, obj->def->name, + obj->def->uuid, vcpuid); +} + +virObjectEvent * +virDomainEventVcpuRemovedNewFromDom(virDomainPtr dom, + unsigned int vcpuid) +{ + return virDomainEventVcpuRemovedNew(dom->id, dom->name, dom->uuid, + vcpuid); +} + static virObjectEvent * virDomainEventAgentLifecycleNew(int id, @@ -2134,6 +2189,17 @@ virDomainEventDispatchDefaultFunc(virConnectPtr conn, goto cleanup; } + case VIR_DOMAIN_EVENT_ID_VCPU_REMOVED: + { + virDomainEventVcpuRemoved *vcpuRemovedEvent; + + vcpuRemovedEvent = (virDomainEventVcpuRemoved *)event; + ((virConnectDomainEventVcpuRemovedCallback)cb)(conn, dom, + vcpuRemovedEvent->vcpuid, + cbopaque); + goto cleanup; + } + case VIR_DOMAIN_EVENT_ID_LAST: break; } diff --git a/src/conf/domain_event.h b/src/conf/domain_event.h index f31cfb9e42ad..1b1b16095a77 100644 --- a/src/conf/domain_event.h +++ b/src/conf/domain_event.h @@ -192,6 +192,12 @@ virDomainEventDeviceRemovalFailedNewFromObj(virDomainObj *obj, virObjectEvent * virDomainEventDeviceRemovalFailedNewFromDom(virDomainPtr dom, const char *devAlias); +virObjectEvent * +virDomainEventVcpuRemovedNewFromObj(virDomainObj *obj, + unsigned int vcpuid); +virObjectEvent * +virDomainEventVcpuRemovedNewFromDom(virDomainPtr dom, + unsigned int vcpuid); virObjectEvent * virDomainEventTunableNewFromObj(virDomainObj *obj, diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index db9eea57745c..c290dc6efeca 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -7702,6 +7702,14 @@ virDomainSendProcessSignal(virDomainPtr domain, * whether it also affects persistent configuration; for more control, * use virDomainSetVcpusFlags(). * + * When this API decreases the live vCPU count by hot-unplugging vCPUs in + * the hypervisor, completion may be asynchronous. Successful unplug + * completion is reported by VIR_DOMAIN_EVENT_ID_VCPU_REMOVED, carrying the + * XML ``<vcpu id='...'>`` value. Rejected unplug requests continue to be + * reported by VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED. The success event + * may be delivered before this API call returns. A timeout is reported only + * through the returned error, not through a domain event. + * * Returns 0 in case of success, -1 in case of failure. * * Since: 0.1.4 @@ -7773,8 +7781,23 @@ virDomainSetVcpus(virDomainPtr domain, unsigned int nvcpus) * be used with live guests and is incompatible with VIR_DOMAIN_VCPU_MAXIMUM. * The usage of this flag may require a guest agent configured. * + * If @flags includes VIR_DOMAIN_VCPU_ASYNC, only vCPU hot-unplug is requested + * asynchronously. In this mode, success means that all required unplug + * request(s) were successfully fired; final completion is reported by + * the VIR_DOMAIN_EVENT_ID_VCPU_REMOVED event, and rejection is reported by a + * `VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED` event type. + * * Not all hypervisors can support all flag combinations. * + * When this API decreases the live vCPU count by hot-unplugging vCPUs, + * completion may be asynchronous. Successful unplug completion is reported by + * VIR_DOMAIN_EVENT_ID_VCPU_REMOVED, carrying the XML ``<vcpu id='...'>`` + * value within the event data. Rejected unplug requests continue to be + * reported by VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED. The success event + * may be delivered before this API call returns. In non-async mode, a + * timeout is reported only through the returned error, not through a domain + * event. + * * Returns 0 in case of success, -1 in case of failure. * * Since: 0.8.5 @@ -13125,7 +13148,8 @@ virDomainSetGuestVcpus(virDomainPtr domain, * @domain: pointer to domain object * @vcpumap: text representation of a bitmap of vcpus to set * @state: 0 to disable/1 to enable cpus described by @vcpumap - * @flags: bitwise-OR of virDomainModificationImpact + * @flags: bitwise-OR of virDomainModificationImpact with optional + * VIR_DOMAIN_VCPU_ASYNC * * Enables/disables individual vcpus described by @vcpumap in the hypervisor. * @@ -13134,6 +13158,20 @@ virDomainSetGuestVcpus(virDomainPtr domain, * * Note that OSes and hypervisors may require vCPU 0 to stay online. * + * If @flags includes VIR_DOMAIN_VCPU_ASYNC, only live vCPU disable + * (hot-unplug) is requested asynchronously. In this mode, success means the + * unplug request was successfully fired; final completion is reported by + * VIR_DOMAIN_EVENT_ID_VCPU_REMOVED, while rejection is reported by + * VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED. + * + * When this API disables live vCPUs by hot-unplugging them, the operation + * completion may be asynchronous. Successful unplug completion is reported by + * VIR_DOMAIN_EVENT_ID_VCPU_REMOVED, carrying the XML ``<vcpu id='...'>`` + * value. Rejected unplug requests continue to be reported by + * VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED. The success event may be + * delivered before this API call returns. In non-async mode, a timeout is + * reported only through the returned error, not through a domain event. + * * Returns 0 on success, -1 on error. * * Since: 3.1.0 diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index cf0e71cc6af1..f22b5895db12 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -811,6 +811,8 @@ virDomainEventTrayChangeNewFromDom; virDomainEventTrayChangeNewFromObj; virDomainEventTunableNewFromDom; virDomainEventTunableNewFromObj; +virDomainEventVcpuRemovedNewFromDom; +virDomainEventVcpuRemovedNewFromObj; virDomainEventWatchdogNewFromDom; virDomainEventWatchdogNewFromObj; virDomainQemuMonitorEventNew; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d227ac58cdb4..ad4dc11c970f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -3620,7 +3620,14 @@ processDeviceDeletedEvent(virQEMUDriver *driver, } if (STRPREFIX(devAlias, "vcpu")) { - qemuDomainRemoveVcpuAlias(vm, devAlias); + int vcpuid; + virObjectEvent *event; + if ((vcpuid = qemuDomainRemoveVcpuAlias(vm, devAlias)) == -1) + goto endjob; + + event = virDomainEventVcpuRemovedNewFromObj(vm, vcpuid); + virObjectEventStateQueue(driver->domainEventState, event); + } else { if (virDomainDefFindDevice(vm->def, devAlias, &dev, true) < 0) goto endjob; @@ -4269,6 +4276,7 @@ qemuDomainSetVcpusFlags(virDomainPtr dom, virDomainObj *vm = NULL; virDomainDef *def; virDomainDef *persistentDef; + bool async = !!(flags & VIR_DOMAIN_VCPU_ASYNC); bool hotpluggable = !!(flags & VIR_DOMAIN_VCPU_HOTPLUGGABLE); bool useAgent = !!(flags & VIR_DOMAIN_VCPU_GUEST); int ret = -1; @@ -4277,7 +4285,8 @@ qemuDomainSetVcpusFlags(virDomainPtr dom, VIR_DOMAIN_AFFECT_CONFIG | VIR_DOMAIN_VCPU_MAXIMUM | VIR_DOMAIN_VCPU_GUEST | - VIR_DOMAIN_VCPU_HOTPLUGGABLE, -1); + VIR_DOMAIN_VCPU_HOTPLUGGABLE | + VIR_DOMAIN_VCPU_ASYNC, -1); if (!(vm = qemuDomainObjFromDomain(dom))) goto cleanup; @@ -4297,13 +4306,19 @@ qemuDomainSetVcpusFlags(virDomainPtr dom, if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0) goto endjob; + if (async && (useAgent || persistentDef || !def)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("asynchronous mode is supported only for live vcpu unplug")); + goto endjob; + } + if (useAgent) ret = qemuDomainSetVcpusAgent(vm, nvcpus); else if (flags & VIR_DOMAIN_VCPU_MAXIMUM) ret = qemuDomainSetVcpusMax(driver, vm, def, persistentDef, nvcpus); else ret = qemuDomainSetVcpusInternal(driver, vm, def, persistentDef, - nvcpus, hotpluggable); + nvcpus, hotpluggable, async); endjob: if (useAgent) @@ -19169,12 +19184,14 @@ qemuDomainSetVcpu(virDomainPtr dom, virDomainObj *vm = NULL; virDomainDef *def = NULL; virDomainDef *persistentDef = NULL; + bool async = !!(flags & VIR_DOMAIN_VCPU_ASYNC); g_autoptr(virBitmap) map = NULL; ssize_t lastvcpu; int ret = -1; virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | - VIR_DOMAIN_AFFECT_CONFIG, -1); + VIR_DOMAIN_AFFECT_CONFIG | + VIR_DOMAIN_VCPU_ASYNC, -1); if (state != 0 && state != 1) { virReportInvalidArg(state, "%s", _("unsupported state value")); @@ -19220,7 +19237,13 @@ qemuDomainSetVcpu(virDomainPtr dom, } } - ret = qemuDomainSetVcpuInternal(driver, vm, def, persistentDef, map, !!state); + if (async && (persistentDef || !def)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("asynchronous mode is supported only for live vcpu unplug")); + goto endjob; + } + + ret = qemuDomainSetVcpuInternal(driver, vm, def, persistentDef, map, !!state, async); endjob: virDomainObjEndJob(vm); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index b7a282b96e52..0b3a781cea19 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -5723,6 +5723,12 @@ qemuDomainRemoveDevice(virQEMUDriver *driver, return 0; } +static bool +qemuDomainDeviceRemoved(virDomainObj *vm) +{ + qemuDomainObjPrivate *priv = vm->privateData; + return priv->unplug.status == QEMU_DOMAIN_UNPLUGGING_DEVICE_STATUS_OK; +} static void qemuDomainMarkDeviceAliasForRemoval(virDomainObj *vm, @@ -6761,7 +6767,7 @@ qemuDomainRemoveVcpu(virDomainObj *vm, } -void +int qemuDomainRemoveVcpuAlias(virDomainObj *vm, const char *alias) { @@ -6774,13 +6780,16 @@ qemuDomainRemoveVcpuAlias(virDomainObj *vm, vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu); if (STREQ_NULLABLE(alias, vcpupriv->alias)) { - qemuDomainRemoveVcpu(vm, i); - return; + if (qemuDomainRemoveVcpu(vm, i) < 0) + return -1; + + return i; } } VIR_DEBUG("vcpu '%s' not found in vcpulist of domain '%s'", alias, vm->def->name); + return -1; } @@ -6788,7 +6797,8 @@ static int qemuDomainHotplugDelVcpu(virQEMUDriver *driver, virQEMUDriverConfig *cfg, virDomainObj *vm, - unsigned int vcpu) + unsigned int vcpu, + bool async) { virDomainVcpuDef *vcpuinfo = virDomainDefGetVcpu(vm->def, vcpu); qemuDomainVcpuPrivate *vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpuinfo); @@ -6796,6 +6806,7 @@ qemuDomainHotplugDelVcpu(virQEMUDriver *driver, unsigned int nvcpus = vcpupriv->vcpus; int rc; int ret = -1; + virObjectEvent *event = NULL; if (!vcpupriv->alias) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, @@ -6813,6 +6824,22 @@ qemuDomainHotplugDelVcpu(virQEMUDriver *driver, goto cleanup; } } else { + if (async) { + /* rc = 0 is implied in this branch */ + if (qemuDomainDeviceRemoved(vm)) { + /* event has already arrived, handle it now */ + goto success; + } + /* + * event has not arrived yet, but the monitor operation was + * successful; there will not be a waiter anymore when this thread + * exits. Reset removal state now so that the event handling path + * can be properly triggered if and when the event does arrive + */ + ret = 0; + goto cleanup; + } + if ((rc = qemuDomainWaitForDeviceRemoval(vm)) <= 0) { if (rc == 0) virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s", @@ -6821,6 +6848,7 @@ qemuDomainHotplugDelVcpu(virQEMUDriver *driver, } } + success: if (qemuDomainRemoveVcpu(vm, vcpu) < 0) goto cleanup; @@ -6831,6 +6859,10 @@ qemuDomainHotplugDelVcpu(virQEMUDriver *driver, ret = 0; + /* emit event now to close the async caller loop */ + event = virDomainEventVcpuRemovedNewFromObj(vm, vcpu); + virObjectEventStateQueue(driver->domainEventState, event); + cleanup: qemuDomainResetDeviceRemoval(vm); return ret; @@ -7003,7 +7035,8 @@ qemuDomainSetVcpusLive(virQEMUDriver *driver, virQEMUDriverConfig *cfg, virDomainObj *vm, virBitmap *vcpumap, - bool enable) + bool enable, + bool async) { qemuDomainObjPrivate *priv = vm->privateData; virCgroupEmulatorAllNodesData *emulatorCgroup = NULL; @@ -7023,7 +7056,7 @@ qemuDomainSetVcpusLive(virQEMUDriver *driver, if (!virBitmapIsBitSet(vcpumap, nextvcpu)) continue; - if (qemuDomainHotplugDelVcpu(driver, cfg, vm, nextvcpu) < 0) + if (qemuDomainHotplugDelVcpu(driver, cfg, vm, nextvcpu, async) < 0) goto cleanup; } } @@ -7114,12 +7147,20 @@ qemuDomainSetVcpusInternal(virQEMUDriver *driver, virDomainDef *def, virDomainDef *persistentDef, unsigned int nvcpus, - bool hotpluggable) + bool hotpluggable, + bool async) { g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); g_autoptr(virBitmap) vcpumap = NULL; bool enable; + if (async && persistentDef) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("asynchronous mode is supported only for live vcpu unplug")); + + return -1; + } + if (def && nvcpus > virDomainDefGetVcpusMax(def)) { virReportError(VIR_ERR_INVALID_ARG, _("requested vcpus is greater than max allowable vcpus for the live domain: %1$u > %2$u"), @@ -7139,7 +7180,13 @@ qemuDomainSetVcpusInternal(virQEMUDriver *driver, &enable))) return -1; - if (qemuDomainSetVcpusLive(driver, cfg, vm, vcpumap, enable) < 0) + if (async && enable) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("asynchronous mode is supported only for vcpu unplug")); + return -1; + } + + if (qemuDomainSetVcpusLive(driver, cfg, vm, vcpumap, enable, async) < 0) return -1; } @@ -7289,7 +7336,8 @@ qemuDomainSetVcpuInternal(virQEMUDriver *driver, virDomainDef *def, virDomainDef *persistentDef, virBitmap *map, - bool state) + bool state, + bool async) { g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); g_autoptr(virBitmap) livevcpus = NULL; @@ -7320,8 +7368,14 @@ qemuDomainSetVcpuInternal(virQEMUDriver *driver, return -1; } + if (async && state) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("asynchronous mode is supported only for vcpu unplug")); + return -1; + } + if (livevcpus && - qemuDomainSetVcpusLive(driver, cfg, vm, livevcpus, state) < 0) + qemuDomainSetVcpusLive(driver, cfg, vm, livevcpus, state, async) < 0) return -1; if (persistentDef) { diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h index e6c90253e416..aa2a9ea6eb75 100644 --- a/src/qemu/qemu_hotplug.h +++ b/src/qemu/qemu_hotplug.h @@ -79,7 +79,7 @@ qemuDomainUpdateDeviceLive(virDomainObj *vm, virQEMUDriver *driver, bool force); -void +int qemuDomainRemoveVcpuAlias(virDomainObj *vm, const char *alias); @@ -106,7 +106,8 @@ qemuDomainSetVcpusInternal(virQEMUDriver *driver, virDomainDef *def, virDomainDef *persistentDef, unsigned int nvcpus, - bool hotpluggable); + bool hotpluggable, + bool async); int qemuDomainSetVcpuInternal(virQEMUDriver *driver, @@ -114,7 +115,8 @@ qemuDomainSetVcpuInternal(virQEMUDriver *driver, virDomainDef *def, virDomainDef *persistentDef, virBitmap *vcpus, - bool state); + bool state, + bool async); unsigned long long qemuDomainGetUnplugTimeout(virDomainObj *vm) ATTRIBUTE_MOCKABLE; diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c index 7e74ff063f5b..81b0ed00da1a 100644 --- a/src/remote/remote_daemon_dispatch.c +++ b/src/remote/remote_daemon_dispatch.c @@ -1322,6 +1322,31 @@ remoteRelayDomainEventMemoryDeviceSizeChange(virConnectPtr conn, return 0; } +static int +remoteRelayDomainEventVcpuRemoved(virConnectPtr conn, + virDomainPtr dom, + unsigned int vcpuid, + void *opaque) +{ + daemonClientEventCallback *callback = opaque; + remote_domain_event_vcpu_removed_msg data; + + if (callback->callbackID < 0 || + !remoteRelayDomainEventCheckACL(callback->client, conn, dom)) + return -1; + + memset(&data, 0, sizeof(data)); + data.callbackID = callback->callbackID; + data.vcpuid = vcpuid; + make_nonnull_domain(&data.dom, dom); + + remoteDispatchObjectEventSend(callback->client, remoteProgram, + REMOTE_PROC_DOMAIN_EVENT_VCPU_REMOVED, + (xdrproc_t)xdr_remote_domain_event_vcpu_removed_msg, + &data); + return 0; +} + static int remoteRelayDomainEventNICMACChange(virConnectPtr conn, @@ -1383,6 +1408,7 @@ static virConnectDomainEventGenericCallback domainEventCallbacks[] = { VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventMemoryFailure), VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventMemoryDeviceSizeChange), VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventNICMACChange), + VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventVcpuRemoved), }; G_STATIC_ASSERT(G_N_ELEMENTS(domainEventCallbacks) == VIR_DOMAIN_EVENT_ID_LAST); diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index ec71eaed8762..c8a4e3f6da98 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -428,6 +428,10 @@ remoteDomainBuildEventMemoryDeviceSizeChange(virNetClientProgram *prog, virNetClient *client, void *evdata, void *opaque); static void +remoteDomainBuildEventVcpuRemoved(virNetClientProgram *prog, + virNetClient *client, + void *evdata, void *opaque); +static void remoteConnectNotifyEventConnectionClosed(virNetClientProgram *prog G_GNUC_UNUSED, virNetClient *client G_GNUC_UNUSED, void *evdata, void *opaque); @@ -659,6 +663,10 @@ static virNetClientProgramEvent remoteEvents[] = { remoteDomainBuildEventNICMACChange, sizeof(remote_domain_event_nic_mac_change_msg), (xdrproc_t)xdr_remote_domain_event_nic_mac_change_msg }, + { REMOTE_PROC_DOMAIN_EVENT_VCPU_REMOVED, + remoteDomainBuildEventVcpuRemoved, + sizeof(remote_domain_event_vcpu_removed_msg), + (xdrproc_t)xdr_remote_domain_event_vcpu_removed_msg }, }; static void @@ -5138,6 +5146,27 @@ remoteDomainBuildEventMemoryDeviceSizeChange(virNetClientProgram *prog G_GNUC_UN virObjectEventStateQueueRemote(priv->eventState, event, msg->callbackID); } +static void +remoteDomainBuildEventVcpuRemoved(virNetClientProgram *prog G_GNUC_UNUSED, + virNetClient *client G_GNUC_UNUSED, + void *evdata, void *opaque) +{ + virConnectPtr conn = opaque; + remote_domain_event_vcpu_removed_msg *msg = evdata; + struct private_data *priv = conn->privateData; + virDomainPtr dom; + virObjectEvent *event = NULL; + + if (!(dom = get_nonnull_domain(conn, msg->dom))) + return; + + event = virDomainEventVcpuRemovedNewFromDom(dom, msg->vcpuid); + + virObjectUnref(dom); + + virObjectEventStateQueueRemote(priv->eventState, event, msg->callbackID); +} + static void remoteDomainBuildEventNICMACChange(virNetClientProgram *prog G_GNUC_UNUSED, diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 38a83c64eadb..23699e99a60a 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -3981,6 +3981,12 @@ struct remote_domain_event_memory_device_size_change_msg { unsigned hyper size; }; +struct remote_domain_event_vcpu_removed_msg { + int callbackID; + remote_nonnull_domain dom; + unsigned int vcpuid; +}; + struct remote_domain_fd_associate_args { remote_nonnull_domain dom; @@ -7120,5 +7126,11 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453 + REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453, + + /** + * @generate: both + * @acl: none + */ + REMOTE_PROC_DOMAIN_EVENT_VCPU_REMOVED = 454 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 0f87d13a5ae1..75c3d0cb2e06 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -3315,6 +3315,11 @@ struct remote_domain_event_memory_device_size_change_msg { remote_nonnull_string alias; uint64_t size; }; +struct remote_domain_event_vcpu_removed_msg { + int callbackID; + remote_nonnull_domain dom; + u_int vcpuid; +}; struct remote_domain_fd_associate_args { remote_nonnull_domain dom; remote_nonnull_string name; @@ -3791,4 +3796,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SET_THROTTLE_GROUP = 451, REMOTE_PROC_DOMAIN_DEL_THROTTLE_GROUP = 452, REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453, + REMOTE_PROC_DOMAIN_EVENT_VCPU_REMOVED = 454, }; diff --git a/tests/qemuhotplugtest.c b/tests/qemuhotplugtest.c index ea9d3243f8b1..36bc4d913826 100644 --- a/tests/qemuhotplugtest.c +++ b/tests/qemuhotplugtest.c @@ -322,6 +322,7 @@ struct testQemuHotplugCpuParams { GHashTable *capsLatestFiles; GHashTable *capsCache; GHashTable *schemaCache; + bool async; }; @@ -420,7 +421,7 @@ testQemuHotplugCpuGroup(const void *opaque) rc = qemuDomainSetVcpusInternal(&driver, data->vm, data->vm->def, data->vm->newDef, params->newcpus, - true); + true, params->async); if (params->fail) { if (rc == 0) @@ -458,7 +459,8 @@ testQemuHotplugCpuIndividual(const void *opaque) goto cleanup; rc = qemuDomainSetVcpuInternal(&driver, data->vm, data->vm->def, - data->vm->newDef, map, params->state); + data->vm->newDef, map, params->state, + params->async); if (params->fail) { if (rc == 0) diff --git a/tools/virsh-domain-event.c b/tools/virsh-domain-event.c index b9d1cdf019ca..fdd7bf611605 100644 --- a/tools/virsh-domain-event.c +++ b/tools/virsh-domain-event.c @@ -705,6 +705,20 @@ virshEventDeviceRemovalFailedPrint(virConnectPtr conn G_GNUC_UNUSED, virshEventPrint(opaque, &buf); } +static void +virshEventVcpuRemovedPrint(virConnectPtr conn G_GNUC_UNUSED, + virDomainPtr dom, + unsigned int vcpuid, + void *opaque) +{ + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + + virBufferAsprintf(&buf, + _("event 'vcpu-removed' for domain '%1$s': vcpu: %2$u\n"), + virDomainGetName(dom), vcpuid); + virshEventPrint(opaque, &buf); +} + VIR_ENUM_DECL(virshEventMetadataChangeType); VIR_ENUM_IMPL(virshEventMetadataChangeType, VIR_DOMAIN_METADATA_LAST, @@ -873,6 +887,8 @@ virshDomainEventCallback virshDomainEventCallbacks[] = { VIR_DOMAIN_EVENT_CALLBACK(virshEventMemoryDeviceSizeChangePrint), }, { "nic-mac-change", VIR_DOMAIN_EVENT_CALLBACK(virshEventNICMACChangePrint), }, + { "vcpu-removed", + VIR_DOMAIN_EVENT_CALLBACK(virshEventVcpuRemovedPrint), }, }; G_STATIC_ASSERT(VIR_DOMAIN_EVENT_ID_LAST == G_N_ELEMENTS(virshDomainEventCallbacks)); diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 08a1ce395378..350a3b6cd8b2 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -7667,6 +7667,10 @@ static const vshCmdOptDef opts_setvcpus[] = { .type = VSH_OT_BOOL, .help = N_("make added vcpus hot(un)pluggable") }, + {.name = "async", + .type = VSH_OT_BOOL, + .help = N_("return after firing live vcpu unplug request(s)") + }, {.name = NULL} }; @@ -7681,6 +7685,7 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) bool current = vshCommandOptBool(cmd, "current"); bool guest = vshCommandOptBool(cmd, "guest"); bool hotpluggable = vshCommandOptBool(cmd, "hotpluggable"); + bool async = vshCommandOptBool(cmd, "async"); unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; VSH_EXCLUSIVE_OPTIONS_VAR(current, live); @@ -7699,6 +7704,8 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) flags |= VIR_DOMAIN_VCPU_MAXIMUM; if (hotpluggable) flags |= VIR_DOMAIN_VCPU_HOTPLUGGABLE; + if (async) + flags |= VIR_DOMAIN_VCPU_ASYNC; if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) return false; @@ -7711,6 +7718,12 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) return false; } + if (async && (config || maximum || guest || hotpluggable)) { + vshError(ctl, "%s", + _("--async can be used only for live hypervisor vcpu unplug")); + return false; + } + /* none of the options were specified */ if (!current && flags == 0) { if (virDomainSetVcpus(dom, count) != 0) @@ -7720,6 +7733,10 @@ cmdSetvcpus(vshControl *ctl, const vshCmd *cmd) return false; } + if (async) + vshPrintExtra(ctl, "%s", + _("vCPU unplug requests sent successfully\n")); + return true; } @@ -7829,6 +7846,10 @@ static const vshCmdOptDef opts_setvcpu[] = { .type = VSH_OT_BOOL, .help = N_("disable cpus specified by cpumap") }, + {.name = "async", + .type = VSH_OT_BOOL, + .help = N_("return after firing live vcpu unplug request") + }, VIRSH_COMMON_OPT_DOMAIN_CONFIG, VIRSH_COMMON_OPT_DOMAIN_LIVE, VIRSH_COMMON_OPT_DOMAIN_CURRENT, @@ -7843,6 +7864,7 @@ cmdSetvcpu(vshControl *ctl, const vshCmd *cmd) bool disable = vshCommandOptBool(cmd, "disable"); bool config = vshCommandOptBool(cmd, "config"); bool live = vshCommandOptBool(cmd, "live"); + bool async = vshCommandOptBool(cmd, "async"); const char *vcpulist = NULL; int state = 0; unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; @@ -7856,12 +7878,20 @@ cmdSetvcpu(vshControl *ctl, const vshCmd *cmd) flags |= VIR_DOMAIN_AFFECT_CONFIG; if (live) flags |= VIR_DOMAIN_AFFECT_LIVE; + if (async) + flags |= VIR_DOMAIN_VCPU_ASYNC; if (!(enable || disable)) { vshError(ctl, "%s", _("one of --enable, --disable is required")); return false; } + if (async && (config || enable)) { + vshError(ctl, "%s", + _("--async can be used only for live vcpu disable")); + return false; + } + if (vshCommandOptString(ctl, cmd, "vcpulist", &vcpulist)) return false; @@ -7874,6 +7904,10 @@ cmdSetvcpu(vshControl *ctl, const vshCmd *cmd) if (virDomainSetVcpu(dom, vcpulist, state, flags) < 0) return false; + if (async) + vshPrintExtra(ctl, "%s", + _("vCPU unplug request sent successfully\n")); + return true; } -- 2.47.3