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 <[email protected]>
---
 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

Reply via email to