From 15763e27a5be5e11eb57ae367321c1adc05c72a2 Mon Sep 17 00:00:00 2001 From: Anushree-Mathur Date: Tue, 11 Aug 2026 21:06:35 +0530 Subject: [PATCH] libvirt_vcpu_plug_unplug: add multi-NUMA hotplug/unplug test variants! Add support for testing CPU hotplug/unplug on VMs configured with multiple NUMA nodes. The test builds N equal NUMA cells, distributing vCPUs and memory evenly across them, then runs continuous plug/unplug cycles to verify stability. Changes: libvirt/tests/cfg/libvirt_vcpu_plug_unplug.cfg: Add multi_numa_hotplug variant with three sub-variants: - numa_2_cells: 2 NUMA nodes, vcpu_max=8, start=2, plug=6, unplug=2 - numa_4_cells: 4 NUMA nodes, vcpu_max=16, start=4, plug=12, unplug=4 - numa_8_cells: 8 NUMA nodes, vcpu_max=32, start=8, plug=24, unplug=8 All variants run --live with test_itr=3 (3 continuous plug/unplug cycles). libvirt/tests/src/libvirt_vcpu_plug_unplug.py: When numa_cells param is set, replace the standard set_vm_vcpus() call with explicit multi-NUMA XML setup: - Set vcpu max and current counts directly on vmxml - Align CPU topology to sockets=1, cores=vcpu_max, threads=1 - Build N NUMA cells with evenly split CPU ranges and memory (MiB->KiB) - Apply via cpu_xml.dicts_to_cells() and vmxml.sync() All existing variants continue through the original set_vm_vcpus() else-branch unchanged. Signed-off-by: Anushree-Mathur Signed-off-by: Anushree-Mathur --- .../tests/cfg/libvirt_vcpu_plug_unplug.cfg | 30 +++++++++ libvirt/tests/src/libvirt_vcpu_plug_unplug.py | 67 ++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/libvirt/tests/cfg/libvirt_vcpu_plug_unplug.cfg b/libvirt/tests/cfg/libvirt_vcpu_plug_unplug.cfg index c16e88d4bb3..864f6f03163 100644 --- a/libvirt/tests/cfg/libvirt_vcpu_plug_unplug.cfg +++ b/libvirt/tests/cfg/libvirt_vcpu_plug_unplug.cfg @@ -94,6 +94,36 @@ vcpu_plug_num = 240 vcpu_unplug_num = 1 vcpu_max_num = 240 + - multi_numa_hotplug: + only live + only libvirt_vcpu_plug_unplug + numa = "yes" + test_itr = 3 + variants: + - numa_2_cells: + numa_cells = "2" + vcpu_max_num = "8" + vcpu_current_num = "2" + vcpu_plug_num = "6" + vcpu_unplug_num = "2" + smp = 8 + vcpu_cores = 8 + - numa_4_cells: + numa_cells = "4" + vcpu_max_num = "16" + vcpu_current_num = "4" + vcpu_plug_num = "12" + vcpu_unplug_num = "4" + smp = 16 + vcpu_cores = 16 + - numa_8_cells: + numa_cells = "8" + vcpu_max_num = "32" + vcpu_current_num = "8" + vcpu_plug_num = "24" + vcpu_unplug_num = "8" + smp = 32 + vcpu_cores = 32 variants: - live: diff --git a/libvirt/tests/src/libvirt_vcpu_plug_unplug.py b/libvirt/tests/src/libvirt_vcpu_plug_unplug.py index d6404bd72f8..d1e650cf69e 100644 --- a/libvirt/tests/src/libvirt_vcpu_plug_unplug.py +++ b/libvirt/tests/src/libvirt_vcpu_plug_unplug.py @@ -4,6 +4,7 @@ import time from avocado.utils import cpu as cpu_util +from virttest.libvirt_xml.vm_xml import VMCPUXML from virttest import virsh from virttest import data_dir @@ -240,6 +241,7 @@ def check_setvcpus_result(cmd_result, expect_error): with_stress = "yes" == params.get("run_stress", "no") iterations = int(params.get("test_itr", 1)) topology_correction = "yes" == params.get("topology_correction", "no") + numa_cells_param = params.get("numa_cells") # Init expect vcpu count values expect_vcpu_num = {'max_config': vcpu_max_num, 'max_live': vcpu_max_num, 'cur_config': vcpu_current_num, @@ -300,8 +302,69 @@ def check_setvcpus_result(cmd_result, expect_error): vmxml.remove_agent_channels() vmxml.sync() - vmxml.set_vm_vcpus(vm_name, vcpu_max_num, vcpu_current_num, - topology_correction=topology_correction) + if numa_cells_param: + # Configure multiple NUMA cells with evenly distributed CPUs/memory. + # Each cell gets (vcpu_max_num // numa_cells) CPUs and an equal + # share of the total guest memory (mem param, in MiB). + numa_cells = int(numa_cells_param) + logging.info("Configuring %d NUMA cells for multi-NUMA test", + numa_cells) + + vmxml.vcpu = vcpu_max_num + vmxml.current_vcpu = vcpu_current_num + + try: + cpu_xml = vmxml.cpu + except Exception: + cpu_xml = VMCPUXML() + + # Ensure topology matches vcpu_max_num (sockets=1, cores=max, threads=1) + existing_topology = getattr(cpu_xml, 'topology', None) + topo_ok = False + if existing_topology: + topo_total = (int(existing_topology.get('sockets', 1)) * + int(existing_topology.get('cores', 1)) * + int(existing_topology.get('threads', 1))) + topo_ok = (topo_total == vcpu_max_num) + if not topo_ok: + cpu_xml.topology = {'sockets': 1, + 'cores': vcpu_max_num, + 'threads': 1} + logging.info("Set topology: sockets=1, cores=%d, threads=1", + vcpu_max_num) + + # Build NUMA cell list — CPUs and memory split equally + cpus_per_cell = vcpu_max_num // numa_cells + remaining = vcpu_max_num % numa_cells + total_mem_mib = int(params.get("mem", "16384")) + cell_memory_kib = (total_mem_mib * 1024) // numa_cells + + numa_cell_list = [] + cpu_start = 0 + for i in range(numa_cells): + cell_cpus = cpus_per_cell + (1 if i < remaining else 0) + cpu_end = cpu_start + cell_cpus - 1 + cell_dict = { + 'id': str(i), + 'cpus': ("%d-%d" % (cpu_start, cpu_end) + if cell_cpus > 1 else str(cpu_start)), + 'memory': str(cell_memory_kib), + 'unit': 'KiB', + } + numa_cell_list.append(cell_dict) + logging.info("NUMA cell %d: CPUs %s, Memory %d KiB", + i, cell_dict['cpus'], cell_memory_kib) + cpu_start = cpu_end + 1 + + cpu_xml.numa_cell = cpu_xml.dicts_to_cells(numa_cell_list) + vmxml.cpu = cpu_xml + vmxml.sync() + logging.info("Multi-NUMA configuration applied: %d cells", + numa_cells) + else: + vmxml.set_vm_vcpus(vm_name, vcpu_max_num, vcpu_current_num, + topology_correction=topology_correction) + vm.start() vm_uptime_init = vm.uptime() if with_stress: