7548c0
From 5f6723e71e3765d1d43bfa9ba1c66e0e05e11a48 Mon Sep 17 00:00:00 2001
7548c0
Message-Id: <5f6723e71e3765d1d43bfa9ba1c66e0e05e11a48@dist-git>
7548c0
From: Michal Privoznik <mprivozn@redhat.com>
7548c0
Date: Mon, 9 Nov 2020 17:22:32 +0100
7548c0
Subject: [PATCH] Allow NUMA nodes without vCPUs
7548c0
MIME-Version: 1.0
7548c0
Content-Type: text/plain; charset=UTF-8
7548c0
Content-Transfer-Encoding: 8bit
7548c0
7548c0
QEMU allows creating NUMA nodes that have memory only.
7548c0
These are somehow important for HMAT.
7548c0
7548c0
With check done in qemuValidateDomainDef() for QEMU 2.7 or newer
7548c0
(checked via QEMU_CAPS_NUMA), we can be sure that the vCPUs are
7548c0
fully assigned to NUMA nodes in domain XML.
7548c0
7548c0
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
7548c0
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
7548c0
(cherry picked from commit a26f61ee0cffa421b87ef568002b684dd8025432)
7548c0
7548c0
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1749518
7548c0
7548c0
Conflicts:
7548c0
- src/qemu/qemu_validate.c: This file doesn't exist in downstream
7548c0
yet, so I've moved the change that original patch would do to
7548c0
qemu_domain.c where the validator lives.
7548c0
7548c0
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
7548c0
Message-Id: <365508c75e579e9037ad555d6c372068ccd50c95.1604938867.git.mprivozn@redhat.com>
7548c0
Reviewed-by: Ján Tomko <jtomko@redhat.com>
7548c0
---
7548c0
 docs/formatdomain.html.in                     |  2 +
7548c0
 docs/schemas/cputypes.rng                     |  8 ++-
7548c0
 src/conf/numa_conf.c                          | 59 ++++++++++---------
7548c0
 src/libxl/xen_xl.c                            | 10 ++--
7548c0
 src/qemu/qemu_command.c                       | 26 ++++----
7548c0
 src/qemu/qemu_domain.c                        | 22 +++----
7548c0
 tests/qemuxml2argvdata/numatune-no-vcpu.args  | 33 +++++++++++
7548c0
 tests/qemuxml2argvdata/numatune-no-vcpu.xml   | 42 +++++++++++++
7548c0
 tests/qemuxml2argvtest.c                      |  1 +
7548c0
 tests/qemuxml2xmloutdata/numatune-no-vcpu.xml |  1 +
7548c0
 tests/qemuxml2xmltest.c                       |  1 +
7548c0
 11 files changed, 149 insertions(+), 56 deletions(-)
7548c0
 create mode 100644 tests/qemuxml2argvdata/numatune-no-vcpu.args
7548c0
 create mode 100644 tests/qemuxml2argvdata/numatune-no-vcpu.xml
7548c0
 create mode 120000 tests/qemuxml2xmloutdata/numatune-no-vcpu.xml
7548c0
7548c0
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
7548c0
index 76799f5ffc..4b8d312596 100644
7548c0
--- a/docs/formatdomain.html.in
7548c0
+++ b/docs/formatdomain.html.in
7548c0
@@ -1783,6 +1783,8 @@
7548c0
       cpus specifies the CPU or range of CPUs that are
7548c0
       part of the node. memory specifies the node memory
7548c0
       in kibibytes (i.e. blocks of 1024 bytes).
7548c0
+      Since 6.6.0 the cpus attribute
7548c0
+      is optional and if omitted a CPU-less NUMA node is created.
7548c0
       Since 1.2.11 one can use an additional 
7548c0
           href="#elementsMemoryAllocation">unit attribute to
7548c0
       define units in which memory is specified.
7548c0
diff --git a/docs/schemas/cputypes.rng b/docs/schemas/cputypes.rng
7548c0
index e2744acad3..a1682a1003 100644
7548c0
--- a/docs/schemas/cputypes.rng
7548c0
+++ b/docs/schemas/cputypes.rng
7548c0
@@ -115,9 +115,11 @@
7548c0
           <ref name="unsignedInt"/>
7548c0
         </attribute>
7548c0
       </optional>
7548c0
-      <attribute name="cpus">
7548c0
-        <ref name="cpuset"/>
7548c0
-      </attribute>
7548c0
+      <optional>
7548c0
+        <attribute name="cpus">
7548c0
+          <ref name="cpuset"/>
7548c0
+        </attribute>
7548c0
+      </optional>
7548c0
       <attribute name="memory">
7548c0
         <ref name="memoryKB"/>
7548c0
       </attribute>
7548c0
diff --git a/src/conf/numa_conf.c b/src/conf/numa_conf.c
7548c0
index c9cc8ac22e..a805336d16 100644
7548c0
--- a/src/conf/numa_conf.c
7548c0
+++ b/src/conf/numa_conf.c
7548c0
@@ -889,32 +889,28 @@ virDomainNumaDefParseXML(virDomainNumaPtr def,
7548c0
         }
7548c0
         VIR_FREE(tmp);
7548c0
 
7548c0
-        if (def->mem_nodes[cur_cell].cpumask) {
7548c0
+        if (def->mem_nodes[cur_cell].mem) {
7548c0
             virReportError(VIR_ERR_XML_ERROR,
7548c0
                            _("Duplicate NUMA cell info for cell id '%u'"),
7548c0
                            cur_cell);
7548c0
             goto cleanup;
7548c0
         }
7548c0
 
7548c0
-        if (!(tmp = virXMLPropString(nodes[i], "cpus"))) {
7548c0
-            virReportError(VIR_ERR_XML_ERROR, "%s",
7548c0
-                           _("Missing 'cpus' attribute in NUMA cell"));
7548c0
-            goto cleanup;
7548c0
-        }
7548c0
+        if ((tmp = virXMLPropString(nodes[i], "cpus"))) {
7548c0
+            g_autoptr(virBitmap) cpumask = NULL;
7548c0
 
7548c0
-        if (virBitmapParse(tmp, &def->mem_nodes[cur_cell].cpumask,
7548c0
-                           VIR_DOMAIN_CPUMASK_LEN) < 0)
7548c0
-            goto cleanup;
7548c0
+            if (virBitmapParse(tmp, &cpumask, VIR_DOMAIN_CPUMASK_LEN) < 0)
7548c0
+                goto cleanup;
7548c0
 
7548c0
-        if (virBitmapIsAllClear(def->mem_nodes[cur_cell].cpumask)) {
7548c0
-            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
7548c0
-                          _("NUMA cell %d has no vCPUs assigned"), cur_cell);
7548c0
-            goto cleanup;
7548c0
+            if (!virBitmapIsAllClear(cpumask))
7548c0
+                def->mem_nodes[cur_cell].cpumask = g_steal_pointer(&cpumask);
7548c0
+            VIR_FREE(tmp);
7548c0
         }
7548c0
-        VIR_FREE(tmp);
7548c0
 
7548c0
         for (j = 0; j < n; j++) {
7548c0
-            if (j == cur_cell || !def->mem_nodes[j].cpumask)
7548c0
+            if (j == cur_cell ||
7548c0
+                !def->mem_nodes[j].cpumask ||
7548c0
+                !def->mem_nodes[cur_cell].cpumask)
7548c0
                 continue;
7548c0
 
7548c0
             if (virBitmapOverlaps(def->mem_nodes[j].cpumask,
7548c0
@@ -976,7 +972,6 @@ virDomainNumaDefFormatXML(virBufferPtr buf,
7548c0
 {
7548c0
     virDomainMemoryAccess memAccess;
7548c0
     virTristateBool discard;
7548c0
-    char *cpustr;
7548c0
     size_t ncells = virDomainNumaGetNodeCount(def);
7548c0
     size_t i;
7548c0
 
7548c0
@@ -986,17 +981,22 @@ virDomainNumaDefFormatXML(virBufferPtr buf,
7548c0
     virBufferAddLit(buf, "<numa>\n");
7548c0
     virBufferAdjustIndent(buf, 2);
7548c0
     for (i = 0; i < ncells; i++) {
7548c0
+        virBitmapPtr cpumask = virDomainNumaGetNodeCpumask(def, i);
7548c0
         int ndistances;
7548c0
 
7548c0
         memAccess = virDomainNumaGetNodeMemoryAccessMode(def, i);
7548c0
         discard = virDomainNumaGetNodeDiscard(def, i);
7548c0
 
7548c0
-        if (!(cpustr = virBitmapFormat(virDomainNumaGetNodeCpumask(def, i))))
7548c0
-            return -1;
7548c0
-
7548c0
         virBufferAddLit(buf, "
7548c0
         virBufferAsprintf(buf, " id='%zu'", i);
7548c0
-        virBufferAsprintf(buf, " cpus='%s'", cpustr);
7548c0
+
7548c0
+        if (cpumask) {
7548c0
+            g_autofree char *cpustr = virBitmapFormat(cpumask);
7548c0
+
7548c0
+            if (!cpustr)
7548c0
+                return -1;
7548c0
+            virBufferAsprintf(buf, " cpus='%s'", cpustr);
7548c0
+        }
7548c0
         virBufferAsprintf(buf, " memory='%llu'",
7548c0
                           virDomainNumaGetNodeMemorySize(def, i));
7548c0
         virBufferAddLit(buf, " unit='KiB'");
7548c0
@@ -1032,8 +1032,6 @@ virDomainNumaDefFormatXML(virBufferPtr buf,
7548c0
             virBufferAdjustIndent(buf, -2);
7548c0
             virBufferAddLit(buf, "</cell>\n");
7548c0
         }
7548c0
-
7548c0
-        VIR_FREE(cpustr);
7548c0
     }
7548c0
     virBufferAdjustIndent(buf, -2);
7548c0
     virBufferAddLit(buf, "</numa>\n");
7548c0
@@ -1048,8 +1046,12 @@ virDomainNumaGetCPUCountTotal(virDomainNumaPtr numa)
7548c0
     size_t i;
7548c0
     unsigned int ret = 0;
7548c0
 
7548c0
-    for (i = 0; i < numa->nmem_nodes; i++)
7548c0
-        ret += virBitmapCountBits(virDomainNumaGetNodeCpumask(numa, i));
7548c0
+    for (i = 0; i < numa->nmem_nodes; i++) {
7548c0
+        virBitmapPtr cpumask = virDomainNumaGetNodeCpumask(numa, i);
7548c0
+
7548c0
+        if (cpumask)
7548c0
+            ret += virBitmapCountBits(cpumask);
7548c0
+    }
7548c0
 
7548c0
     return ret;
7548c0
 }
7548c0
@@ -1061,11 +1063,14 @@ virDomainNumaGetMaxCPUID(virDomainNumaPtr numa)
7548c0
     unsigned int ret = 0;
7548c0
 
7548c0
     for (i = 0; i < numa->nmem_nodes; i++) {
7548c0
+        virBitmapPtr cpumask = virDomainNumaGetNodeCpumask(numa, i);
7548c0
         int bit;
7548c0
 
7548c0
-        bit = virBitmapLastSetBit(virDomainNumaGetNodeCpumask(numa, i));
7548c0
-        if (bit > ret)
7548c0
-            ret = bit;
7548c0
+        if (cpumask) {
7548c0
+            bit = virBitmapLastSetBit(cpumask);
7548c0
+            if (bit > ret)
7548c0
+                ret = bit;
7548c0
+        }
7548c0
     }
7548c0
 
7548c0
     return ret;
7548c0
diff --git a/src/libxl/xen_xl.c b/src/libxl/xen_xl.c
7548c0
index edea30a86a..752fa925ec 100644
7548c0
--- a/src/libxl/xen_xl.c
7548c0
+++ b/src/libxl/xen_xl.c
7548c0
@@ -1443,19 +1443,21 @@ xenFormatXLVnuma(virConfValuePtr list,
7548c0
 {
7548c0
     int ret = -1;
7548c0
     size_t i;
7548c0
-
7548c0
     virBuffer buf = VIR_BUFFER_INITIALIZER;
7548c0
     virConfValuePtr numaVnode, tmp;
7548c0
-
7548c0
+    virBitmapPtr cpumask = virDomainNumaGetNodeCpumask(numa, node);
7548c0
     size_t nodeSize = virDomainNumaGetNodeMemorySize(numa, node) / 1024;
7548c0
-    char *nodeVcpus = virBitmapFormat(virDomainNumaGetNodeCpumask(numa, node));
7548c0
+    g_autofree char *nodeVcpus = NULL;
7548c0
 
7548c0
-    if (VIR_ALLOC(numaVnode) < 0)
7548c0
+    if (!cpumask ||
7548c0
+        VIR_ALLOC(numaVnode) < 0)
7548c0
         goto cleanup;
7548c0
 
7548c0
     numaVnode->type = VIR_CONF_LIST;
7548c0
     numaVnode->list = NULL;
7548c0
 
7548c0
+    nodeVcpus = virBitmapFormat(cpumask);
7548c0
+
7548c0
     /* pnode */
7548c0
     virBufferAsprintf(&buf, "pnode=%zu", node);
7548c0
     xenFormatXLVnode(numaVnode, &buf;;
7548c0
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
7548c0
index 1a573c2817..ac63d18a42 100644
7548c0
--- a/src/qemu/qemu_command.c
7548c0
+++ b/src/qemu/qemu_command.c
7548c0
@@ -7364,8 +7364,6 @@ qemuBuildNumaCommandLine(virQEMUDriverConfigPtr cfg,
7548c0
     size_t i, j;
7548c0
     virQEMUCapsPtr qemuCaps = priv->qemuCaps;
7548c0
     g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
7548c0
-    char *cpumask = NULL;
7548c0
-    char *tmpmask = NULL;
7548c0
     char *next = NULL;
7548c0
     virBufferPtr nodeBackends = NULL;
7548c0
     bool needBackend = false;
7548c0
@@ -7400,9 +7398,7 @@ qemuBuildNumaCommandLine(virQEMUDriverConfigPtr cfg,
7548c0
         goto cleanup;
7548c0
 
7548c0
     for (i = 0; i < ncells; i++) {
7548c0
-        VIR_FREE(cpumask);
7548c0
-        if (!(cpumask = virBitmapFormat(virDomainNumaGetNodeCpumask(def->numa, i))))
7548c0
-            goto cleanup;
7548c0
+        virBitmapPtr cpumask = virDomainNumaGetNodeCpumask(def->numa, i);
7548c0
 
7548c0
         if (needBackend) {
7548c0
             virCommandAddArg(cmd, "-object");
7548c0
@@ -7412,11 +7408,19 @@ qemuBuildNumaCommandLine(virQEMUDriverConfigPtr cfg,
7548c0
         virCommandAddArg(cmd, "-numa");
7548c0
         virBufferAsprintf(&buf, "node,nodeid=%zu", i);
7548c0
 
7548c0
-        for (tmpmask = cpumask; tmpmask; tmpmask = next) {
7548c0
-            if ((next = strchr(tmpmask, ',')))
7548c0
-                *(next++) = '\0';
7548c0
-            virBufferAddLit(&buf, ",cpus=");
7548c0
-            virBufferAdd(&buf, tmpmask, -1);
7548c0
+        if (cpumask) {
7548c0
+            g_autofree char *cpumaskStr = NULL;
7548c0
+            char *tmpmask;
7548c0
+
7548c0
+            if (!(cpumaskStr = virBitmapFormat(cpumask)))
7548c0
+                goto cleanup;
7548c0
+
7548c0
+            for (tmpmask = cpumaskStr; tmpmask; tmpmask = next) {
7548c0
+                if ((next = strchr(tmpmask, ',')))
7548c0
+                    *(next++) = '\0';
7548c0
+                virBufferAddLit(&buf, ",cpus=");
7548c0
+                virBufferAdd(&buf, tmpmask, -1);
7548c0
+            }
7548c0
         }
7548c0
 
7548c0
         if (needBackend)
7548c0
@@ -7447,8 +7451,6 @@ qemuBuildNumaCommandLine(virQEMUDriverConfigPtr cfg,
7548c0
     ret = 0;
7548c0
 
7548c0
  cleanup:
7548c0
-    VIR_FREE(cpumask);
7548c0
-
7548c0
     if (nodeBackends) {
7548c0
         for (i = 0; i < ncells; i++)
7548c0
             virBufferFreeAndReset(&nodeBackends[i]);
7548c0
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
7548c0
index 35b536868a..be25790f12 100644
7548c0
--- a/src/qemu/qemu_domain.c
7548c0
+++ b/src/qemu/qemu_domain.c
7548c0
@@ -5373,7 +5373,7 @@ qemuDomainDefValidateNuma(const virDomainDef *def,
7548c0
     }
7548c0
 
7548c0
     for (i = 0; i < ncells; i++) {
7548c0
-        g_autofree char * cpumask = NULL;
7548c0
+        virBitmapPtr cpumask = virDomainNumaGetNodeCpumask(def->numa, i);
7548c0
 
7548c0
         if (!hasMemoryCap &&
7548c0
             virDomainNumaGetNodeMemoryAccessMode(def->numa, i)) {
7548c0
@@ -5383,17 +5383,19 @@ qemuDomainDefValidateNuma(const virDomainDef *def,
7548c0
             return -1;
7548c0
         }
7548c0
 
7548c0
-        if (!(cpumask = virBitmapFormat(virDomainNumaGetNodeCpumask(def->numa, i))))
7548c0
-            return -1;
7548c0
+        if (cpumask) {
7548c0
+            g_autofree char * cpumaskStr = NULL;
7548c0
+            if (!(cpumaskStr = virBitmapFormat(cpumask)))
7548c0
+                return -1;
7548c0
 
7548c0
-        if (strchr(cpumask, ',') &&
7548c0
-            !virQEMUCapsGet(qemuCaps, QEMU_CAPS_NUMA)) {
7548c0
-            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
7548c0
-                           _("disjoint NUMA cpu ranges are not supported "
7548c0
-                             "with this QEMU"));
7548c0
-            return -1;
7548c0
+            if (strchr(cpumaskStr, ',') &&
7548c0
+                !virQEMUCapsGet(qemuCaps, QEMU_CAPS_NUMA)) {
7548c0
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
7548c0
+                               _("disjoint NUMA cpu ranges are not supported "
7548c0
+                                 "with this QEMU"));
7548c0
+                return -1;
7548c0
+            }
7548c0
         }
7548c0
-
7548c0
     }
7548c0
 
7548c0
     if (virDomainNumaNodesDistancesAreBeingSet(def->numa) &&
7548c0
diff --git a/tests/qemuxml2argvdata/numatune-no-vcpu.args b/tests/qemuxml2argvdata/numatune-no-vcpu.args
7548c0
new file mode 100644
7548c0
index 0000000000..a1f1ee044e
7548c0
--- /dev/null
7548c0
+++ b/tests/qemuxml2argvdata/numatune-no-vcpu.args
7548c0
@@ -0,0 +1,33 @@
7548c0
+LC_ALL=C \
7548c0
+PATH=/bin \
7548c0
+HOME=/tmp/lib/domain--1-QEMUGuest \
7548c0
+USER=test \
7548c0
+LOGNAME=test \
7548c0
+XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest/.local/share \
7548c0
+XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest/.cache \
7548c0
+XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest/.config \
7548c0
+QEMU_AUDIO_DRV=none \
7548c0
+/usr/bin/qemu-system-x86_64 \
7548c0
+-name QEMUGuest \
7548c0
+-S \
7548c0
+-machine pc,accel=tcg,usb=off,dump-guest-core=off \
7548c0
+-m 12288 \
7548c0
+-realtime mlock=off \
7548c0
+-smp 12,sockets=12,cores=1,threads=1 \
7548c0
+-numa node,nodeid=0,cpus=0-3,mem=2048 \
7548c0
+-numa node,nodeid=1,cpus=4-7,mem=2048 \
7548c0
+-numa node,nodeid=2,cpus=8-11,mem=2048 \
7548c0
+-numa node,nodeid=3,mem=2048 \
7548c0
+-numa node,nodeid=4,mem=2048 \
7548c0
+-numa node,nodeid=5,mem=2048 \
7548c0
+-uuid c7a5fdb2-cdaf-9455-926a-d65c16db1809 \
7548c0
+-display none \
7548c0
+-no-user-config \
7548c0
+-nodefaults \
7548c0
+-chardev socket,id=charmonitor,path=/tmp/lib/domain--1-QEMUGuest/monitor.sock,\
7548c0
+server,nowait \
7548c0
+-mon chardev=charmonitor,id=monitor,mode=control \
7548c0
+-rtc base=utc \
7548c0
+-no-shutdown \
7548c0
+-usb \
7548c0
+-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3
7548c0
diff --git a/tests/qemuxml2argvdata/numatune-no-vcpu.xml b/tests/qemuxml2argvdata/numatune-no-vcpu.xml
7548c0
new file mode 100644
7548c0
index 0000000000..f25a07d7ed
7548c0
--- /dev/null
7548c0
+++ b/tests/qemuxml2argvdata/numatune-no-vcpu.xml
7548c0
@@ -0,0 +1,42 @@
7548c0
+<domain type='qemu'>
7548c0
+  <name>QEMUGuest</name>
7548c0
+  <uuid>c7a5fdb2-cdaf-9455-926a-d65c16db1809</uuid>
7548c0
+  <memory unit='KiB'>12582912</memory>
7548c0
+  <currentMemory unit='KiB'>12582912</currentMemory>
7548c0
+  <vcpu placement='static'>12</vcpu>
7548c0
+  <os>
7548c0
+    <type arch='x86_64' machine='pc'>hvm</type>
7548c0
+    <boot dev='hd'/>
7548c0
+  </os>
7548c0
+  <features>
7548c0
+    <acpi/>
7548c0
+    <apic/>
7548c0
+    <pae/>
7548c0
+  </features>
7548c0
+  <cpu>
7548c0
+    <numa>
7548c0
+      <cell id='0' cpus='0-3' memory='2097152' unit='KiB'/>
7548c0
+      <cell id='1' cpus='4-7' memory='2097152' unit='KiB'/>
7548c0
+      <cell id='2' cpus='8-11' memory='2097152' unit='KiB'/>
7548c0
+      <cell id='3' memory='2097152' unit='KiB'/>
7548c0
+      <cell id='4' memory='2097152' unit='KiB'/>
7548c0
+      <cell id='5' memory='2097152' unit='KiB'/>
7548c0
+    </numa>
7548c0
+  </cpu>
7548c0
+  <clock offset='utc'/>
7548c0
+  <on_poweroff>destroy</on_poweroff>
7548c0
+  <on_reboot>restart</on_reboot>
7548c0
+  <on_crash>restart</on_crash>
7548c0
+  <devices>
7548c0
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>
7548c0
+    <controller type='usb' index='0'>
7548c0
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
7548c0
+    </controller>
7548c0
+    <controller type='pci' index='0' model='pci-root'/>
7548c0
+    <input type='mouse' bus='ps2'/>
7548c0
+    <input type='keyboard' bus='ps2'/>
7548c0
+    <memballoon model='virtio'>
7548c0
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
7548c0
+    </memballoon>
7548c0
+  </devices>
7548c0
+</domain>
7548c0
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
7548c0
index ff92af606d..49699e495d 100644
7548c0
--- a/tests/qemuxml2argvtest.c
7548c0
+++ b/tests/qemuxml2argvtest.c
7548c0
@@ -1812,6 +1812,7 @@ mymain(void)
7548c0
     DO_TEST_PARSE_ERROR("numatune-memnode-no-memory", NONE);
7548c0
 
7548c0
     DO_TEST("numatune-distances", QEMU_CAPS_NUMA, QEMU_CAPS_NUMA_DIST);
7548c0
+    DO_TEST("numatune-no-vcpu", NONE);
7548c0
 
7548c0
     DO_TEST("numatune-auto-nodeset-invalid", NONE);
7548c0
     DO_TEST("numatune-auto-prefer", QEMU_CAPS_OBJECT_MEMORY_RAM,
7548c0
diff --git a/tests/qemuxml2xmloutdata/numatune-no-vcpu.xml b/tests/qemuxml2xmloutdata/numatune-no-vcpu.xml
7548c0
new file mode 120000
7548c0
index 0000000000..f213032685
7548c0
--- /dev/null
7548c0
+++ b/tests/qemuxml2xmloutdata/numatune-no-vcpu.xml
7548c0
@@ -0,0 +1 @@
7548c0
+../qemuxml2argvdata/numatune-no-vcpu.xml
7548c0
\ No newline at end of file
7548c0
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
7548c0
index 6c3f5c4a9e..1ddeba30f0 100644
7548c0
--- a/tests/qemuxml2xmltest.c
7548c0
+++ b/tests/qemuxml2xmltest.c
7548c0
@@ -1105,6 +1105,7 @@ mymain(void)
7548c0
     DO_TEST("numatune-memnode", QEMU_CAPS_NUMA, QEMU_CAPS_OBJECT_MEMORY_FILE);
7548c0
     DO_TEST("numatune-memnode-no-memory", QEMU_CAPS_OBJECT_MEMORY_FILE);
7548c0
     DO_TEST("numatune-distances", QEMU_CAPS_NUMA, QEMU_CAPS_NUMA_DIST);
7548c0
+    DO_TEST("numatune-no-vcpu", QEMU_CAPS_NUMA);
7548c0
 
7548c0
     DO_TEST("bios-nvram", NONE);
7548c0
     DO_TEST("bios-nvram-os-interleave", NONE);
7548c0
-- 
7548c0
2.29.2
7548c0