render / rpms / libvirt

Forked from rpms/libvirt 9 months ago
Clone
6ae9ed
From 5fde4ecb99989b91940f1b24a509adb47e3e0894 Mon Sep 17 00:00:00 2001
6ae9ed
Message-Id: <5fde4ecb99989b91940f1b24a509adb47e3e0894@dist-git>
6ae9ed
From: Peter Krempa <pkrempa@redhat.com>
6ae9ed
Date: Wed, 24 Aug 2016 16:11:37 -0400
6ae9ed
Subject: [PATCH] qemu: command: Add support for sparse vcpu topologies
6ae9ed
6ae9ed
https://bugzilla.redhat.com/show_bug.cgi?id=1097930
6ae9ed
https://bugzilla.redhat.com/show_bug.cgi?id=1224341
6ae9ed
6ae9ed
Add support for using the new approach to hotplug vcpus using device_add
6ae9ed
during startup of qemu to allow sparse vcpu topologies.
6ae9ed
6ae9ed
There are a few limitations imposed by qemu on the supported
6ae9ed
configuration:
6ae9ed
- vcpu0 needs to be always present and not hotpluggable
6ae9ed
- non-hotpluggable cpus need to be ordered at the beginning
6ae9ed
- order of the vcpus needs to be unique for every single hotpluggable
6ae9ed
  entity
6ae9ed
6ae9ed
Qemu also doesn't really allow to query the information necessary to
6ae9ed
start a VM with the vcpus directly on the commandline. Fortunately they
6ae9ed
can be hotplugged during startup.
6ae9ed
6ae9ed
The new hotplug code uses the following approach:
6ae9ed
- non-hotpluggable vcpus are counted and put to the -smp option
6ae9ed
- qemu is started
6ae9ed
- qemu is queried for the necessary information
6ae9ed
- the configuration is checked
6ae9ed
- the hotpluggable vcpus are hotplugged
6ae9ed
- vcpus are started
6ae9ed
6ae9ed
This patch adds a lot of checking code and enables the support to
6ae9ed
specify the individual vcpu element with qemu.
6ae9ed
6ae9ed
(cherry picked from commit 9eb9106ea51b43102ee51132f69780b2c86ccbca)
6ae9ed
---
6ae9ed
 docs/formatdomain.html.in                          |   5 +
6ae9ed
 src/qemu/qemu_command.c                            |  20 ++-
6ae9ed
 src/qemu/qemu_domain.c                             |  76 ++++++++-
6ae9ed
 src/qemu/qemu_process.c                            | 178 +++++++++++++++++++++
6ae9ed
 .../qemuxml2argv-cpu-hotplug-startup.args          |  20 +++
6ae9ed
 .../qemuxml2argv-cpu-hotplug-startup.xml           |  29 ++++
6ae9ed
 tests/qemuxml2argvtest.c                           |   2 +
6ae9ed
 7 files changed, 325 insertions(+), 5 deletions(-)
6ae9ed
 create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.args
6ae9ed
 create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.xml
6ae9ed
6ae9ed
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
6ae9ed
index 252f91c..a838bff 100644
6ae9ed
--- a/docs/formatdomain.html.in
6ae9ed
+++ b/docs/formatdomain.html.in
6ae9ed
@@ -580,6 +580,11 @@
6ae9ed
         Note that providing state for individual cpus may be necessary to enable
6ae9ed
         support of addressable vCPU hotplug and this feature may not be
6ae9ed
         supported by all hypervisors.
6ae9ed
+
6ae9ed
+        For QEMU the following conditions are required. Vcpu 0 needs to be
6ae9ed
+        enabled and non-hotpluggable. On PPC64 along with it vcpus that are in
6ae9ed
+        the same core need to be enabled as well. All non-hotpluggable cpus
6ae9ed
+        present at boot need to be grouped after vcpu 0.
6ae9ed
         Since 2.2.0 (QEMU only)
6ae9ed
       
6ae9ed
     
6ae9ed
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
6ae9ed
index 95055c4..e292f48 100644
6ae9ed
--- a/src/qemu/qemu_command.c
6ae9ed
+++ b/src/qemu/qemu_command.c
6ae9ed
@@ -7109,17 +7109,29 @@ qemuBuildMachineCommandLine(virCommandPtr cmd,
6ae9ed
 
6ae9ed
 static int
6ae9ed
 qemuBuildSmpCommandLine(virCommandPtr cmd,
6ae9ed
-                        const virDomainDef *def)
6ae9ed
+                        virDomainDefPtr def)
6ae9ed
 {
6ae9ed
     char *smp;
6ae9ed
     virBuffer buf = VIR_BUFFER_INITIALIZER;
6ae9ed
+    unsigned int maxvcpus = virDomainDefGetVcpusMax(def);
6ae9ed
+    unsigned int nvcpus = 0;
6ae9ed
+    virDomainVcpuDefPtr vcpu;
6ae9ed
+    size_t i;
6ae9ed
+
6ae9ed
+    /* count non-hotpluggable enabled vcpus. Hotpluggable ones will be added
6ae9ed
+     * in a different way */
6ae9ed
+    for (i = 0; i < maxvcpus; i++) {
6ae9ed
+        vcpu = virDomainDefGetVcpu(def, i);
6ae9ed
+        if (vcpu->online && vcpu->hotpluggable == VIR_TRISTATE_BOOL_NO)
6ae9ed
+            nvcpus++;
6ae9ed
+    }
6ae9ed
 
6ae9ed
     virCommandAddArg(cmd, "-smp");
6ae9ed
 
6ae9ed
-    virBufferAsprintf(&buf, "%u", virDomainDefGetVcpus(def));
6ae9ed
+    virBufferAsprintf(&buf, "%u", nvcpus);
6ae9ed
 
6ae9ed
-    if (virDomainDefHasVcpusOffline(def))
6ae9ed
-        virBufferAsprintf(&buf, ",maxcpus=%u", virDomainDefGetVcpusMax(def));
6ae9ed
+    if (nvcpus != maxvcpus)
6ae9ed
+        virBufferAsprintf(&buf, ",maxcpus=%u", maxvcpus);
6ae9ed
     /* sockets, cores, and threads are either all zero
6ae9ed
      * or all non-zero, thus checking one of them is enough */
6ae9ed
     if (def->cpu && def->cpu->sockets) {
6ae9ed
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
6ae9ed
index 959b551..8eb6e51 100644
6ae9ed
--- a/src/qemu/qemu_domain.c
6ae9ed
+++ b/src/qemu/qemu_domain.c
6ae9ed
@@ -2247,6 +2247,76 @@ qemuDomainRecheckInternalPaths(virDomainDefPtr def,
6ae9ed
 
6ae9ed
 
6ae9ed
 static int
6ae9ed
+qemuDomainDefVcpusPostParse(virDomainDefPtr def)
6ae9ed
+{
6ae9ed
+    unsigned int maxvcpus = virDomainDefGetVcpusMax(def);
6ae9ed
+    virDomainVcpuDefPtr vcpu;
6ae9ed
+    virDomainVcpuDefPtr prevvcpu;
6ae9ed
+    size_t i;
6ae9ed
+    bool has_order = false;
6ae9ed
+
6ae9ed
+    /* vcpu 0 needs to be present, first, and non-hotpluggable */
6ae9ed
+    vcpu = virDomainDefGetVcpu(def, 0);
6ae9ed
+    if (!vcpu->online) {
6ae9ed
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
6ae9ed
+                       _("vcpu 0 can't be offline"));
6ae9ed
+        return -1;
6ae9ed
+    }
6ae9ed
+    if (vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES) {
6ae9ed
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
6ae9ed
+                       _("vcpu0 can't be hotpluggable"));
6ae9ed
+        return -1;
6ae9ed
+    }
6ae9ed
+    if (vcpu->order != 0 && vcpu->order != 1) {
6ae9ed
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
6ae9ed
+                       _("vcpu0 must be enabled first"));
6ae9ed
+        return -1;
6ae9ed
+    }
6ae9ed
+
6ae9ed
+    if (vcpu->order != 0)
6ae9ed
+        has_order = true;
6ae9ed
+
6ae9ed
+    prevvcpu = vcpu;
6ae9ed
+
6ae9ed
+    /* all online vcpus or non online vcpu need to have order set */
6ae9ed
+    for (i = 1; i < maxvcpus; i++) {
6ae9ed
+        vcpu = virDomainDefGetVcpu(def, i);
6ae9ed
+
6ae9ed
+        if (vcpu->online &&
6ae9ed
+            (vcpu->order != 0) != has_order) {
6ae9ed
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
6ae9ed
+                           _("all vcpus must have either set or unset order"));
6ae9ed
+            return -1;
6ae9ed
+        }
6ae9ed
+
6ae9ed
+        /* few conditions for non-hotpluggable (thus online) vcpus */
6ae9ed
+        if (vcpu->hotpluggable == VIR_TRISTATE_BOOL_NO) {
6ae9ed
+            /* they can be ordered only at the beginning */
6ae9ed
+            if (prevvcpu->hotpluggable == VIR_TRISTATE_BOOL_YES) {
6ae9ed
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
6ae9ed
+                               _("online non-hotpluggable vcpus need to be "
6ae9ed
+                                 "ordered prior to hotplugable vcpus"));
6ae9ed
+                return -1;
6ae9ed
+            }
6ae9ed
+
6ae9ed
+            /* they need to be in order (qemu doesn't support any order yet).
6ae9ed
+             * Also note that multiple vcpus may share order on some platforms */
6ae9ed
+            if (prevvcpu->order > vcpu->order) {
6ae9ed
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
6ae9ed
+                               _("online non-hotpluggable vcpus must be ordered "
6ae9ed
+                                 "in ascending order"));
6ae9ed
+                return -1;
6ae9ed
+            }
6ae9ed
+        }
6ae9ed
+
6ae9ed
+        prevvcpu = vcpu;
6ae9ed
+    }
6ae9ed
+
6ae9ed
+    return 0;
6ae9ed
+}
6ae9ed
+
6ae9ed
+
6ae9ed
+static int
6ae9ed
 qemuDomainDefPostParse(virDomainDefPtr def,
6ae9ed
                        virCapsPtr caps,
6ae9ed
                        unsigned int parseFlags,
6ae9ed
@@ -2301,6 +2371,9 @@ qemuDomainDefPostParse(virDomainDefPtr def,
6ae9ed
     if (virSecurityManagerVerify(driver->securityManager, def) < 0)
6ae9ed
         goto cleanup;
6ae9ed
 
6ae9ed
+    if (qemuDomainDefVcpusPostParse(def) < 0)
6ae9ed
+        goto cleanup;
6ae9ed
+
6ae9ed
     ret = 0;
6ae9ed
  cleanup:
6ae9ed
     virObjectUnref(qemuCaps);
6ae9ed
@@ -2670,7 +2743,8 @@ virDomainDefParserConfig virQEMUDriverDomainDefParserConfig = {
6ae9ed
     .deviceValidateCallback = qemuDomainDeviceDefValidate,
6ae9ed
 
6ae9ed
     .features = VIR_DOMAIN_DEF_FEATURE_MEMORY_HOTPLUG |
6ae9ed
-                VIR_DOMAIN_DEF_FEATURE_OFFLINE_VCPUPIN
6ae9ed
+                VIR_DOMAIN_DEF_FEATURE_OFFLINE_VCPUPIN |
6ae9ed
+                VIR_DOMAIN_DEF_FEATURE_INDIVIDUAL_VCPUS,
6ae9ed
 };
6ae9ed
 
6ae9ed
 
6ae9ed
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
6ae9ed
index c94eed4..2eac422 100644
6ae9ed
--- a/src/qemu/qemu_process.c
6ae9ed
+++ b/src/qemu/qemu_process.c
6ae9ed
@@ -4719,6 +4719,172 @@ qemuProcessSetupIOThreads(virDomainObjPtr vm)
6ae9ed
 }
6ae9ed
 
6ae9ed
 
6ae9ed
+static int
6ae9ed
+qemuProcessValidateHotpluggableVcpus(virDomainDefPtr def)
6ae9ed
+{
6ae9ed
+    virDomainVcpuDefPtr vcpu;
6ae9ed
+    virDomainVcpuDefPtr subvcpu;
6ae9ed
+    qemuDomainVcpuPrivatePtr vcpupriv;
6ae9ed
+    unsigned int maxvcpus = virDomainDefGetVcpusMax(def);
6ae9ed
+    size_t i = 0;
6ae9ed
+    size_t j;
6ae9ed
+    virBitmapPtr ordermap = NULL;
6ae9ed
+    int ret = -1;
6ae9ed
+
6ae9ed
+    if (!(ordermap = virBitmapNew(maxvcpus)))
6ae9ed
+        goto cleanup;
6ae9ed
+
6ae9ed
+    /* validate:
6ae9ed
+     * - all hotpluggable entities to be hotplugged have the correct data
6ae9ed
+     * - vcpus belonging to a hotpluggable entity share configuration
6ae9ed
+     * - order of the hotpluggable entities is unique
6ae9ed
+     */
6ae9ed
+    for (i = 0; i < maxvcpus; i++) {
6ae9ed
+        vcpu = virDomainDefGetVcpu(def, i);
6ae9ed
+        vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu);
6ae9ed
+
6ae9ed
+        /* skip over hotpluggable entities  */
6ae9ed
+        if (vcpupriv->vcpus == 0)
6ae9ed
+            continue;
6ae9ed
+
6ae9ed
+        if (vcpu->order != 0) {
6ae9ed
+            if (virBitmapIsBitSet(ordermap, vcpu->order - 1)) {
6ae9ed
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
6ae9ed
+                               _("duplicate vcpu order '%u'"), vcpu->order - 1);
6ae9ed
+                goto cleanup;
6ae9ed
+            }
6ae9ed
+
6ae9ed
+            ignore_value(virBitmapSetBit(ordermap, vcpu->order - 1));
6ae9ed
+        }
6ae9ed
+
6ae9ed
+
6ae9ed
+        for (j = i + 1; j < (i + vcpupriv->vcpus); j++) {
6ae9ed
+            subvcpu = virDomainDefGetVcpu(def, j);
6ae9ed
+            if (subvcpu->hotpluggable != vcpu->hotpluggable ||
6ae9ed
+                subvcpu->online != vcpu->online ||
6ae9ed
+                subvcpu->order != vcpu->order) {
6ae9ed
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
6ae9ed
+                               _("vcpus '%zu' and '%zu' are in the same hotplug "
6ae9ed
+                                 "group but differ in configuration"), i, j);
6ae9ed
+                goto cleanup;
6ae9ed
+            }
6ae9ed
+        }
6ae9ed
+
6ae9ed
+        if (vcpu->online && vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES) {
6ae9ed
+            if ((vcpupriv->socket_id == -1 && vcpupriv->core_id == -1 &&
6ae9ed
+                 vcpupriv->thread_id == -1) ||
6ae9ed
+                !vcpupriv->type) {
6ae9ed
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
6ae9ed
+                               _("vcpu '%zu' is missing hotplug data"), i);
6ae9ed
+                goto cleanup;
6ae9ed
+            }
6ae9ed
+        }
6ae9ed
+    }
6ae9ed
+
6ae9ed
+    ret = 0;
6ae9ed
+ cleanup:
6ae9ed
+    virBitmapFree(ordermap);
6ae9ed
+    return ret;
6ae9ed
+}
6ae9ed
+
6ae9ed
+
6ae9ed
+static int
6ae9ed
+qemuDomainHasHotpluggableStartupVcpus(virDomainDefPtr def)
6ae9ed
+{
6ae9ed
+    size_t maxvcpus = virDomainDefGetVcpusMax(def);
6ae9ed
+    virDomainVcpuDefPtr vcpu;
6ae9ed
+    size_t i;
6ae9ed
+
6ae9ed
+    for (i = 0; i < maxvcpus; i++) {
6ae9ed
+        vcpu = virDomainDefGetVcpu(def, i);
6ae9ed
+
6ae9ed
+        if (vcpu->online && vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES)
6ae9ed
+            return true;
6ae9ed
+    }
6ae9ed
+
6ae9ed
+    return false;
6ae9ed
+}
6ae9ed
+
6ae9ed
+
6ae9ed
+static int
6ae9ed
+qemuProcessVcpusSortOrder(const void *a,
6ae9ed
+                          const void *b)
6ae9ed
+{
6ae9ed
+    virDomainVcpuDefPtr vcpua = *((virDomainVcpuDefPtr *)a);
6ae9ed
+    virDomainVcpuDefPtr vcpub = *((virDomainVcpuDefPtr *)b);
6ae9ed
+
6ae9ed
+    return vcpua->order - vcpub->order;
6ae9ed
+}
6ae9ed
+
6ae9ed
+
6ae9ed
+static int
6ae9ed
+qemuProcessSetupHotpluggableVcpus(virQEMUDriverPtr driver,
6ae9ed
+                                  virDomainObjPtr vm,
6ae9ed
+                                  qemuDomainAsyncJob asyncJob)
6ae9ed
+{
6ae9ed
+    unsigned int maxvcpus = virDomainDefGetVcpusMax(vm->def);
6ae9ed
+    virDomainVcpuDefPtr vcpu;
6ae9ed
+    qemuDomainVcpuPrivatePtr vcpupriv;
6ae9ed
+    virJSONValuePtr vcpuprops = NULL;
6ae9ed
+    size_t i;
6ae9ed
+    int ret = -1;
6ae9ed
+    int rc;
6ae9ed
+
6ae9ed
+    virDomainVcpuDefPtr *bootHotplug = NULL;
6ae9ed
+    size_t nbootHotplug = 0;
6ae9ed
+
6ae9ed
+    for (i = 0; i < maxvcpus; i++) {
6ae9ed
+        vcpu = virDomainDefGetVcpu(vm->def, i);
6ae9ed
+        vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu);
6ae9ed
+
6ae9ed
+        if (vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES && vcpu->online &&
6ae9ed
+            vcpupriv->vcpus != 0) {
6ae9ed
+            if (virAsprintf(&vcpupriv->alias, "vcpu%zu", i) < 0)
6ae9ed
+                goto cleanup;
6ae9ed
+
6ae9ed
+            if (VIR_APPEND_ELEMENT(bootHotplug, nbootHotplug, vcpu) < 0)
6ae9ed
+                goto cleanup;
6ae9ed
+        }
6ae9ed
+    }
6ae9ed
+
6ae9ed
+    if (nbootHotplug == 0) {
6ae9ed
+        ret = 0;
6ae9ed
+        goto cleanup;
6ae9ed
+    }
6ae9ed
+
6ae9ed
+    qsort(bootHotplug, nbootHotplug, sizeof(*bootHotplug),
6ae9ed
+          qemuProcessVcpusSortOrder);
6ae9ed
+
6ae9ed
+    for (i = 0; i < nbootHotplug; i++) {
6ae9ed
+        vcpu = bootHotplug[i];
6ae9ed
+
6ae9ed
+        if (!(vcpuprops = qemuBuildHotpluggableCPUProps(vcpu)))
6ae9ed
+            goto cleanup;
6ae9ed
+
6ae9ed
+        if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
6ae9ed
+            goto cleanup;
6ae9ed
+
6ae9ed
+        rc = qemuMonitorAddDeviceArgs(qemuDomainGetMonitor(vm), vcpuprops);
6ae9ed
+        vcpuprops = NULL;
6ae9ed
+
6ae9ed
+        if (qemuDomainObjExitMonitor(driver, vm) < 0)
6ae9ed
+            goto cleanup;
6ae9ed
+
6ae9ed
+        if (rc < 0)
6ae9ed
+            goto cleanup;
6ae9ed
+
6ae9ed
+        virJSONValueFree(vcpuprops);
6ae9ed
+    }
6ae9ed
+
6ae9ed
+    ret = 0;
6ae9ed
+
6ae9ed
+ cleanup:
6ae9ed
+    VIR_FREE(bootHotplug);
6ae9ed
+    virJSONValueFree(vcpuprops);
6ae9ed
+    return ret;
6ae9ed
+}
6ae9ed
+
6ae9ed
+
6ae9ed
 /**
6ae9ed
  * qemuProcessPrepareDomain
6ae9ed
  *
6ae9ed
@@ -5195,6 +5361,18 @@ qemuProcessLaunch(virConnectPtr conn,
6ae9ed
     if (qemuSetupCpusetMems(vm) < 0)
6ae9ed
         goto cleanup;
6ae9ed
 
6ae9ed
+    VIR_DEBUG("setting up hotpluggable cpus");
6ae9ed
+    if (qemuDomainHasHotpluggableStartupVcpus(vm->def)) {
6ae9ed
+        if (qemuDomainRefreshVcpuInfo(driver, vm, asyncJob, false) < 0)
6ae9ed
+            goto cleanup;
6ae9ed
+
6ae9ed
+        if (qemuProcessValidateHotpluggableVcpus(vm->def) < 0)
6ae9ed
+            goto cleanup;
6ae9ed
+
6ae9ed
+        if (qemuProcessSetupHotpluggableVcpus(driver, vm, asyncJob) < 0)
6ae9ed
+            goto cleanup;
6ae9ed
+    }
6ae9ed
+
6ae9ed
     VIR_DEBUG("Refreshing VCPU info");
6ae9ed
     if (qemuDomainRefreshVcpuInfo(driver, vm, asyncJob, false) < 0)
6ae9ed
         goto cleanup;
6ae9ed
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.args
6ae9ed
new file mode 100644
6ae9ed
index 0000000..035f250
6ae9ed
--- /dev/null
6ae9ed
+++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.args
6ae9ed
@@ -0,0 +1,20 @@
6ae9ed
+LC_ALL=C \
6ae9ed
+PATH=/bin \
6ae9ed
+HOME=/home/test \
6ae9ed
+USER=test \
6ae9ed
+LOGNAME=test \
6ae9ed
+QEMU_AUDIO_DRV=none \
6ae9ed
+/usr/bin/qemu \
6ae9ed
+-name QEMUGuest1 \
6ae9ed
+-S \
6ae9ed
+-M pc \
6ae9ed
+-m 214 \
6ae9ed
+-smp 1,maxcpus=6,sockets=3,cores=2,threads=1 \
6ae9ed
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
6ae9ed
+-nographic \
6ae9ed
+-nodefaults \
6ae9ed
+-monitor unix:/tmp/lib/domain--1-QEMUGuest1/monitor.sock,server,nowait \
6ae9ed
+-no-acpi \
6ae9ed
+-boot n \
6ae9ed
+-usb \
6ae9ed
+-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3
6ae9ed
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.xml b/tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.xml
6ae9ed
new file mode 100644
6ae9ed
index 0000000..58718aa
6ae9ed
--- /dev/null
6ae9ed
+++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-hotplug-startup.xml
6ae9ed
@@ -0,0 +1,29 @@
6ae9ed
+<domain type='qemu'>
6ae9ed
+  <name>QEMUGuest1</name>
6ae9ed
+  <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
6ae9ed
+  <memory unit='KiB'>219100</memory>
6ae9ed
+  <currentMemory unit='KiB'>219100</currentMemory>
6ae9ed
+  <vcpu placement='static' current='3'>6</vcpu>
6ae9ed
+  <vcpus>
6ae9ed
+    <vcpu id='0' enabled='yes' hotpluggable='no' order='1'/>
6ae9ed
+    <vcpu id='1' enabled='no' hotpluggable='yes'/>
6ae9ed
+    <vcpu id='2' enabled='no' hotpluggable='yes'/>
6ae9ed
+    <vcpu id='3' enabled='no' hotpluggable='yes'/>
6ae9ed
+    <vcpu id='4' enabled='yes' hotpluggable='yes' order='2'/>
6ae9ed
+    <vcpu id='5' enabled='yes' hotpluggable='yes' order='3'/>
6ae9ed
+  </vcpus>
6ae9ed
+  <os>
6ae9ed
+    <type arch='x86_64' machine='pc'>hvm</type>
6ae9ed
+    <boot dev='network'/>
6ae9ed
+  </os>
6ae9ed
+  <cpu>
6ae9ed
+    <topology sockets="3" cores="2" threads="1"/>
6ae9ed
+  </cpu>
6ae9ed
+  <clock offset='utc'/>
6ae9ed
+  <on_poweroff>destroy</on_poweroff>
6ae9ed
+  <on_reboot>restart</on_reboot>
6ae9ed
+  <on_crash>destroy</on_crash>
6ae9ed
+  <devices>
6ae9ed
+      <emulator>/usr/bin/qemu</emulator>
6ae9ed
+  </devices>
6ae9ed
+</domain>
6ae9ed
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
6ae9ed
index a9a3e6b..9de2f94 100644
6ae9ed
--- a/tests/qemuxml2argvtest.c
6ae9ed
+++ b/tests/qemuxml2argvtest.c
6ae9ed
@@ -2102,6 +2102,8 @@ mymain(void)
6ae9ed
     DO_TEST("intel-iommu", QEMU_CAPS_DEVICE_PCI_BRIDGE,
6ae9ed
             QEMU_CAPS_DEVICE_DMI_TO_PCI_BRIDGE, QEMU_CAPS_DEVICE_INTEL_IOMMU);
6ae9ed
 
6ae9ed
+    DO_TEST("cpu-hotplug-startup", QEMU_CAPS_QUERY_HOTPLUGGABLE_CPUS);
6ae9ed
+
6ae9ed
     qemuTestDriverFree(&driver);
6ae9ed
 
6ae9ed
     return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
6ae9ed
-- 
6ae9ed
2.10.0
6ae9ed