This patch seeks the support of system default memory policy
when numatune is configured. Before this patch, numatune only
has three memory modes: static, interleave and prefered.
These memory policies are ultimately set by mbind() system call.
Memory policy could be 'hard coded' into the kernel, but none of
above policies fit our requirment under this case. mbind() support
default memory policy, but it requires a NULL nodemask. So obviously
setting allowed memory nodes is cgroups' mission. That means if
'default' mode is specified in numatune, numatune config will be
completely cgroups setting, which restricting the memory
nodes allowed for each vcpu thread in different cells.
<numatune>
<memory mode="default" nodeset="1-4,^3"
migratable="yes" />
<memnode cellid="0" mode="default" nodeset="1"/>
<memnode cellid="2" mode="default" nodeset="2"/>
</numatune>
If you want to set default memory policy, please set migratable to
"yes" and mode to "default" at the same time.
Some implementation details are not determained, so this patch
is not split into logical blocks yet.
---
docs/formatdomain.rst | 12 ++++-
docs/schemas/domaincommon.rng | 7 +++
include/libvirt/libvirt-domain.h | 1 +
src/conf/numa_conf.c | 50 ++++++++++++++++++-
src/conf/numa_conf.h | 2 +
src/libvirt_private.syms | 1 +
src/qemu/qemu_command.c | 7 ++-
src/qemu/qemu_process.c | 25 ++++++++++
src/util/virnuma.c | 3 ++
.../numatune-memory-migratable.args | 34 +++++++++++++
...atune-memory-migratable.x86_64-latest.args | 35 +++++++++++++
.../numatune-memory-migratable.xml | 33 ++++++++++++
tests/qemuxml2argvtest.c | 2 +
...matune-memory-migratable.x86_64-latest.xml | 40 +++++++++++++++
.../numatune-memory-migratable.xml | 39 +++++++++++++++
tests/qemuxml2xmltest.c | 1 +
16 files changed, 288 insertions(+), 4 deletions(-)
create mode 100644 tests/qemuxml2argvdata/numatune-memory-migratable.args
create mode 100644 tests/qemuxml2argvdata/numatune-memory-migratable.x86_64-latest.args
create mode 100644 tests/qemuxml2argvdata/numatune-memory-migratable.xml
create mode 100644 tests/qemuxml2xmloutdata/numatune-memory-migratable.x86_64-latest.xml
create mode 100644 tests/qemuxml2xmloutdata/numatune-memory-migratable.xml
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index ae635bedff..4ab9873c32 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -1086,8 +1086,9 @@ NUMA Node Tuning
``memory``
The optional ``memory`` element specifies how to allocate memory for the
domain process on a NUMA host. It contains several optional attributes.
- Attribute ``mode`` is either 'interleave', 'strict', or
'preferred', defaults
- to 'strict'. Attribute ``nodeset`` specifies the NUMA nodes, using the same
+ Attribute ``mode`` is either 'interleave', 'strict',
'preferred' or 'default',
+ defaults to 'strict', 'default' specifies that memory policy is
system
+ default policy. Attribute ``nodeset`` specifies the NUMA nodes, using the same
syntax as attribute ``cpuset`` of element ``vcpu``. Attribute ``placement`` (
:since:`since 0.9.12` ) can be used to indicate the memory placement mode for
domain process, its value can be either "static" or "auto",
defaults to
@@ -1097,6 +1098,13 @@ NUMA Node Tuning
will be ignored if it's specified. If ``placement`` of ``vcpu`` is
'auto',
and ``numatune`` is not specified, a default ``numatune`` with ``placement``
'auto' and ``mode`` 'strict' will be added implicitly. :since:`Since
0.9.3`
+ Attribute ``migratable`` is 'no' by default, and 'yes' indicates that
it
+ allows operating system or hypervisor migrating the memory pages between
+ different memory nodes according to system default policy, that also means
+ we will not rely on mbind() or set_mempolicy() system calls to set the memory
+ policy or memory affinity, we only use cgroups to restrict the initial memory
+ allocation. So if ``migratable`` is 'yes', the ``mode`` is required to set to
+ 'default'.
``memnode``
Optional ``memnode`` elements can specify memory allocation policies per each
guest NUMA node. For those nodes having no corresponding ``memnode`` element,
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
index a62a598568..546566df41 100644
--- a/docs/schemas/domaincommon.rng
+++ b/docs/schemas/domaincommon.rng
@@ -1085,12 +1085,18 @@
<interleave>
<optional>
<element name="memory">
+ <optional>
+ <attribute name="migratable">
+ <ref name="virYesNo"/>
+ </attribute>
+ </optional>
<optional>
<attribute name="mode">
<choice>
<value>strict</value>
<value>preferred</value>
<value>interleave</value>
+ <value>default</value>
</choice>
</attribute>
</optional>
@@ -1123,6 +1129,7 @@
<value>strict</value>
<value>preferred</value>
<value>interleave</value>
+ <value>default</value>
</choice>
</attribute>
<attribute name="nodeset">
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
index b3310729bf..28f3346e07 100644
--- a/include/libvirt/libvirt-domain.h
+++ b/include/libvirt/libvirt-domain.h
@@ -1527,6 +1527,7 @@ typedef enum {
VIR_DOMAIN_NUMATUNE_MEM_STRICT = 0,
VIR_DOMAIN_NUMATUNE_MEM_PREFERRED = 1,
VIR_DOMAIN_NUMATUNE_MEM_INTERLEAVE = 2,
+ VIR_DOMAIN_NUMATUNE_MEM_DEFAULT = 3,
# ifdef VIR_ENUM_SENTINELS
VIR_DOMAIN_NUMATUNE_MEM_LAST /* This constant is subject to change */
diff --git a/src/conf/numa_conf.c b/src/conf/numa_conf.c
index f8a7a01ac9..b299e7d8fc 100644
--- a/src/conf/numa_conf.c
+++ b/src/conf/numa_conf.c
@@ -43,6 +43,7 @@ VIR_ENUM_IMPL(virDomainNumatuneMemMode,
"strict",
"preferred",
"interleave",
+ "default",
);
VIR_ENUM_IMPL(virDomainNumatunePlacement,
@@ -99,6 +100,7 @@ struct _virDomainNuma {
virBitmapPtr nodeset;
virDomainNumatuneMemMode mode;
virDomainNumatunePlacement placement;
+ virTristateBool migratable;
} memory; /* pinning for all the memory */
struct _virDomainNumaNode {
@@ -234,10 +236,18 @@ virDomainNumatuneNodeParseXML(virDomainNumaPtr numa,
_("Invalid mode attribute in memnode
element"));
goto cleanup;
}
- VIR_FREE(tmp);
mem_node->mode = mode;
}
+ if ((numa->memory.migratable == VIR_TRISTATE_BOOL_YES) &&
+ (mem_node->mode != VIR_DOMAIN_NUMATUNE_MEM_DEFAULT)) {
+ virReportError(VIR_ERR_XML_ERROR,
+ _("Invalid 'mode' attribute value
'%s' when "
+ "'migratable' is 'yes',
'default' is required"), tmp);
+ goto cleanup;
+ }
+ VIR_FREE(tmp);
+
tmp = virXMLPropString(cur_node, "nodeset");
if (!tmp) {
virReportError(VIR_ERR_XML_ERROR, "%s",
@@ -275,6 +285,7 @@ virDomainNumatuneParseXML(virDomainNumaPtr numa,
int ret = -1;
virBitmapPtr nodeset = NULL;
xmlNodePtr node = NULL;
+ int migratable = 0;
if (virXPathInt("count(./numatune)", ctxt, &n) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
@@ -292,11 +303,27 @@ virDomainNumatuneParseXML(virDomainNumaPtr numa,
placement = VIR_DOMAIN_NUMATUNE_PLACEMENT_AUTO;
if (node) {
+ if ((tmp = virXMLPropString(node, "migratable")) &&
+ (migratable = virTristateBoolTypeFromString(tmp)) <= 0) {
+ virReportError(VIR_ERR_XML_ERROR,
+ _("Invalid 'migratable' attribute value
'%s'"), tmp);
+ goto cleanup;
+ }
+ numa->memory.migratable = migratable;
+ VIR_FREE(tmp);
+
if ((tmp = virXMLPropString(node, "mode")) &&
(mode = virDomainNumatuneMemModeTypeFromString(tmp)) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported NUMA memory tuning mode
'%s'"), tmp);
goto cleanup;
+ } else if (migratable == VIR_TRISTATE_BOOL_YES) {
+ if ((tmp == NULL) || (mode != VIR_DOMAIN_NUMATUNE_MEM_DEFAULT)) {
+ virReportError(VIR_ERR_XML_ERROR,
+ _("Invalid 'mode' attribute value
'%s' when "
+ "'migratable' is 'yes',
'default' is required"), tmp);
+ goto cleanup;
+ }
}
VIR_FREE(tmp);
@@ -369,6 +396,11 @@ virDomainNumatuneFormatXML(virBufferPtr buf,
tmp = virDomainNumatuneMemModeTypeToString(numatune->memory.mode);
virBufferAsprintf(buf, "<memory mode='%s' ", tmp);
+ if (numatune->memory.migratable != VIR_TRISTATE_BOOL_ABSENT) {
+ tmp = virTristateBoolTypeToString(numatune->memory.migratable);
+ virBufferAsprintf(buf, "migratable='%s' ", tmp);
+ }
+
if (numatune->memory.placement == VIR_DOMAIN_NUMATUNE_PLACEMENT_STATIC) {
if (!(nodeset = virBitmapFormat(numatune->memory.nodeset)))
return -1;
@@ -427,6 +459,22 @@ virDomainNumaFree(virDomainNumaPtr numa)
VIR_FREE(numa);
}
+/**
+ * virDomainNumatuneGetMigratable:
+ * @numatune: pointer to numatune definition
+ *
+ * Get the migratable attribute for domain's memory.
+ *
+ * Returns: migratable value
+ */
+int virDomainNumatuneGetMigratable(virDomainNumaPtr numatune)
+{
+ if (!numatune)
+ return VIR_TRISTATE_BOOL_ABSENT;
+
+ return numatune->memory.migratable;
+}
+
/**
* virDomainNumatuneGetMode:
* @numatune: pointer to numatune definition
diff --git a/src/conf/numa_conf.h b/src/conf/numa_conf.h
index db5d79e62a..d58d0c2352 100644
--- a/src/conf/numa_conf.h
+++ b/src/conf/numa_conf.h
@@ -102,6 +102,8 @@ int virDomainNumatuneFormatXML(virBufferPtr buf, virDomainNumaPtr
numatune)
/*
* Getters
*/
+int virDomainNumatuneGetMigratable(virDomainNumaPtr numatune);
+
int virDomainNumatuneGetMode(virDomainNumaPtr numatune,
int cellid,
virDomainNumatuneMemMode *mode);
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 95e50835ad..69676b9bd1 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -878,6 +878,7 @@ virDomainNumaSetNodeDistanceCount;
virDomainNumaSetNodeMemorySize;
virDomainNumatuneFormatNodeset;
virDomainNumatuneFormatXML;
+virDomainNumatuneGetMigratable;
virDomainNumatuneGetMode;
virDomainNumatuneGetNodeset;
virDomainNumatuneHasPerNodeBinding;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 7847706594..6f3b4aecf6 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -174,6 +174,7 @@ VIR_ENUM_IMPL(qemuNumaPolicy,
"bind",
"preferred",
"interleave",
+ "default",
);
@@ -3095,7 +3096,11 @@ qemuBuildMemoryBackendProps(virJSONValuePtr *backendProps,
return -1;
}
- if (nodemask) {
+ /* If migratable attribute is yes, we should only use cgroups setting
+ * memory affinity, and skip passing the host-nodes and policy parameters
+ * to QEMU command line. */
+ if (nodemask &&
+ virDomainNumatuneGetMigratable(def->numa) != VIR_TRISTATE_BOOL_YES) {
if (!virNumaNodesetIsAvailable(nodemask))
return -1;
if (virJSONValueObjectAdd(props,
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index cd0f57da97..e7eeb1e928 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -2713,6 +2713,7 @@ qemuProcessSetupPid(virDomainObjPtr vm,
g_autoptr(virBitmap) hostcpumap = NULL;
g_autofree char *mem_mask = NULL;
int ret = -1;
+ size_t i;
if ((period || quota) &&
!virCgroupHasController(priv->cgroup, VIR_CGROUP_CONTROLLER_CPU)) {
@@ -2753,6 +2754,30 @@ qemuProcessSetupPid(virDomainObjPtr vm,
&mem_mask, -1) < 0)
goto cleanup;
+ /* For vCPU threads, mem_mask is different among cells and mem_mask
+ * is used to set cgroups cpuset.mems for vcpu threads. Then If we
+ * allow orperate system to migrate pages between nodes, cgroups can
+ * restrict memory allocation from the right node at the beginning
+ * for vcpu thread. */
+ if (nameval == VIR_CGROUP_THREAD_VCPU) {
+ virDomainNumaPtr numatune = vm->def->numa;
+ virBitmapPtr numanode_cpumask = NULL;
+ for (i = 0; i < virDomainNumaGetNodeCount(numatune); i++) {
+ numanode_cpumask = virDomainNumaGetNodeCpumask(numatune, i);
+ /* 'i' indicates the cell id, if the vCPU id is in this cell,
+ * we need get the corresonding nodeset. */
+ if (virBitmapIsBitSet(numanode_cpumask, id)) {
+ if (virDomainNumatuneMaybeFormatNodeset(numatune,
+ priv->autoNodeset,
+ &mem_mask, i) < 0) {
+ goto cleanup;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
if (virCgroupNewThread(priv->cgroup, nameval, id, true, &cgroup) < 0)
goto cleanup;
diff --git a/src/util/virnuma.c b/src/util/virnuma.c
index 5d40d13977..5fb9c79921 100644
--- a/src/util/virnuma.c
+++ b/src/util/virnuma.c
@@ -152,6 +152,9 @@ virNumaSetupMemoryPolicy(virDomainNumatuneMemMode mode,
numa_set_interleave_mask(&mask);
break;
+ case VIR_DOMAIN_NUMATUNE_MEM_DEFAULT:
+ break;
+
case VIR_DOMAIN_NUMATUNE_MEM_LAST:
break;
}
diff --git a/tests/qemuxml2argvdata/numatune-memory-migratable.args
b/tests/qemuxml2argvdata/numatune-memory-migratable.args
new file mode 100644
index 0000000000..62300d72a2
--- /dev/null
+++ b/tests/qemuxml2argvdata/numatune-memory-migratable.args
@@ -0,0 +1,34 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/tmp/lib/domain--1-QEMUGuest \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest/.local/share \
+XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest/.cache \
+XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest/.config \
+QEMU_AUDIO_DRV=none \
+/usr/bin/qemu-system-x86_64 \
+-name QEMUGuest \
+-S \
+-machine pc,accel=tcg,usb=off,dump-guest-core=off \
+-m 24105 \
+-realtime mlock=off \
+-smp 32,sockets=32,cores=1,threads=1 \
+-object memory-backend-ram,id=ram-node0,size=20971520 \
+-numa node,nodeid=0,cpus=0,memdev=ram-node0 \
+-object memory-backend-ram,id=ram-node1,size=676331520 \
+-numa node,nodeid=1,cpus=1-27,cpus=29,memdev=ram-node1 \
+-object memory-backend-ram,id=ram-node2,size=24578621440 \
+-numa node,nodeid=2,cpus=28,cpus=30-31,memdev=ram-node2 \
+-uuid 9f4b6512-e73a-4a25-93e8-5307802821ce \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,path=/tmp/lib/domain--1-QEMUGuest/monitor.sock,\
+server,nowait \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-no-acpi \
+-usb \
+-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3
diff --git a/tests/qemuxml2argvdata/numatune-memory-migratable.x86_64-latest.args
b/tests/qemuxml2argvdata/numatune-memory-migratable.x86_64-latest.args
new file mode 100644
index 0000000000..6a99540792
--- /dev/null
+++ b/tests/qemuxml2argvdata/numatune-memory-migratable.x86_64-latest.args
@@ -0,0 +1,35 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/tmp/lib/domain--1-QEMUGuest \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest/.local/share \
+XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest/.cache \
+XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest/.config \
+QEMU_AUDIO_DRV=none /usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest,debug-threads=on \
+-S \
+-object secret,id=masterKey0,format=raw,file=/tmp/lib/domain--1-QEMUGuest/master-key.aes
\
+-machine pc,accel=tcg,usb=off,dump-guest-core=off \
+-cpu qemu64 -m 24105 -overcommit mem-lock=off \
+-smp 32,sockets=32,cores=1,threads=1 \
+-object memory-backend-ram,id=ram-node0,size=20971520 \
+-numa node,nodeid=0,cpus=0,memdev=ram-node0 \
+-object memory-backend-ram,id=ram-node1,size=676331520 \
+-numa node,nodeid=1,cpus=1-27,cpus=29,memdev=ram-node1 \
+-object memory-backend-ram,id=ram-node2,size=24578621440 \
+-numa node,nodeid=2,cpus=28,cpus=30-31,memdev=ram-node2 \
+-uuid 9f4b6512-e73a-4a25-93e8-5307802821ce \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server,nowait \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-no-acpi \
+-boot strict=on \
+-device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 \
+-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x2 \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/numatune-memory-migratable.xml
b/tests/qemuxml2argvdata/numatune-memory-migratable.xml
new file mode 100644
index 0000000000..c3f83dfa25
--- /dev/null
+++ b/tests/qemuxml2argvdata/numatune-memory-migratable.xml
@@ -0,0 +1,33 @@
+<domain type='qemu'>
+ <name>QEMUGuest</name>
+ <uuid>9f4b6512-e73a-4a25-93e8-5307802821ce</uuid>
+ <memory unit='KiB'>24682468</memory>
+ <currentMemory unit='KiB'>24682468</currentMemory>
+ <vcpu placement='static'>32</vcpu>
+ <numatune>
+ <memory mode='default' nodeset='0-7'
migratable="yes"/>
+ <memnode cellid='0' mode='default' nodeset='3'/>
+ <memnode cellid='2' mode='default'
nodeset='1-2,5-7,^6'/>
+ </numatune>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <cpu>
+ <numa>
+ <cell id='0' cpus='0' memory='20002'
unit='KiB'/>
+ <cell id='1' cpus='1-27,29' memory='660066'
unit='KiB'/>
+ <cell id='2' cpus='28,30-31' memory='24002400'
unit='KiB'/>
+ </numa>
+ </cpu>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <controller type='usb' index='0'/>
+ <controller type='pci' index='0' model='pci-root'/>
+ <memballoon model='virtio'/>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index ef8a871a19..1bcd0d525d 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -1987,6 +1987,8 @@ mymain(void)
DO_TEST("numatune-memory", NONE);
DO_TEST_PARSE_ERROR("numatune-memory-invalid-nodeset", NONE);
+ DO_TEST_CAPS_LATEST("numatune-memory-migratable");
+
DO_TEST("numatune-memnode",
QEMU_CAPS_NUMA,
QEMU_CAPS_OBJECT_MEMORY_RAM);
diff --git a/tests/qemuxml2xmloutdata/numatune-memory-migratable.x86_64-latest.xml
b/tests/qemuxml2xmloutdata/numatune-memory-migratable.x86_64-latest.xml
new file mode 100644
index 0000000000..8460843b33
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/numatune-memory-migratable.x86_64-latest.xml
@@ -0,0 +1,40 @@
+<domain type='qemu'>
+ <name>QEMUGuest</name>
+ <uuid>9f4b6512-e73a-4a25-93e8-5307802821ce</uuid>
+ <memory unit='KiB'>24682468</memory>
+ <currentMemory unit='KiB'>24682468</currentMemory>
+ <vcpu placement='static'>32</vcpu>
+ <numatune>
+ <memory mode='default' migratable='yes'
nodeset='0-7'/>
+ <memnode cellid='0' mode='default' nodeset='3'/>
+ <memnode cellid='2' mode='default' nodeset='1-2,5,7'/>
+ </numatune>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <cpu mode='custom' match='exact' check='none'>
+ <model fallback='forbid'>qemu64</model>
+ <numa>
+ <cell id='0' cpus='0' memory='20002'
unit='KiB'/>
+ <cell id='1' cpus='1-27,29' memory='660066'
unit='KiB'/>
+ <cell id='2' cpus='28,30-31' memory='24002400'
unit='KiB'/>
+ </numa>
+ </cpu>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <controller type='usb' index='0' model='piix3-uhci'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x2'/>
+ </controller>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='virtio'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x02' function='0x0'/>
+ </memballoon>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2xmloutdata/numatune-memory-migratable.xml
b/tests/qemuxml2xmloutdata/numatune-memory-migratable.xml
new file mode 100644
index 0000000000..92a7c028ee
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/numatune-memory-migratable.xml
@@ -0,0 +1,39 @@
+<domain type='qemu'>
+ <name>QEMUGuest</name>
+ <uuid>9f4b6512-e73a-4a25-93e8-5307802821ce</uuid>
+ <memory unit='KiB'>24682468</memory>
+ <currentMemory unit='KiB'>24682468</currentMemory>
+ <vcpu placement='static'>32</vcpu>
+ <numatune>
+ <memory mode='default' migratable='yes'
nodeset='0-7'/>
+ <memnode cellid='0' mode='default' nodeset='3'/>
+ <memnode cellid='2' mode='default' nodeset='1-2,5,7'/>
+ </numatune>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <cpu>
+ <numa>
+ <cell id='0' cpus='0' memory='20002'
unit='KiB'/>
+ <cell id='1' cpus='1-27,29' memory='660066'
unit='KiB'/>
+ <cell id='2' cpus='28,30-31' memory='24002400'
unit='KiB'/>
+ </numa>
+ </cpu>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <controller type='usb' index='0'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x2'/>
+ </controller>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='virtio'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
+ </memballoon>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index 44ac9fbce7..a3d5a18b57 100644
--- a/tests/qemuxml2xmltest.c
+++ b/tests/qemuxml2xmltest.c
@@ -1147,6 +1147,7 @@ mymain(void)
DO_TEST("numatune-distances", QEMU_CAPS_NUMA, QEMU_CAPS_NUMA_DIST);
DO_TEST("numatune-no-vcpu", QEMU_CAPS_NUMA);
DO_TEST("numatune-hmat", QEMU_CAPS_NUMA_HMAT,
QEMU_CAPS_OBJECT_MEMORY_RAM);
+ DO_TEST_CAPS_LATEST("numatune-memory-migratable");
DO_TEST("bios-nvram", NONE);
DO_TEST("bios-nvram-os-interleave", NONE);
--
2.25.4