From 85336d31cec50982584c579eeb8f26dc8f21fcfb Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Mon, 7 Jul 2025 09:35:11 +0100 Subject: [PATCH 1/5] lib/vm.py: Return newly created UUID in create_vif This allows avoiding a subsequent vif-list or similar to immediately operate on the VIF Signed-off-by: Andrii Sultanov --- lib/vm.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/vm.py b/lib/vm.py index 7ae585702..e10a698c5 100644 --- a/lib/vm.py +++ b/lib/vm.py @@ -368,10 +368,11 @@ def create_vif(self, vif_num, *, network_uuid=None, network_name=None): network_uuid = self.host.pool.network_named(network_name) assert network_uuid, f"No UUID given, and network name {network_name!r} not found" logging.info("Create VIF %d to network %r on VM %s", vif_num, network_uuid, self.uuid) - self.host.xe('vif-create', {'vm-uuid': self.uuid, - 'device': str(vif_num), - 'network-uuid': network_uuid, - }) + vif_uuid = self.host.xe('vif-create', {'vm-uuid': self.uuid, + 'device': str(vif_num), + 'network-uuid': network_uuid, + }) + return VIF(vif_uuid, self) def is_running_on_host(self, host): return self.is_running() and self.param_get('resident-on') == host.uuid From 480ba7a0e42c7dd10f71d10f86e05a174594d771 Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Mon, 7 Jul 2025 10:02:20 +0100 Subject: [PATCH 2/5] Add limit-tests to jobs.py and debian_uefi_vm as a new marker Signed-off-by: Andrii Sultanov --- jobs.py | 15 ++++++++++++++- pytest.ini | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/jobs.py b/jobs.py index 2c9f16405..721cde393 100755 --- a/jobs.py +++ b/jobs.py @@ -446,6 +446,19 @@ "nb_pools": 2, "params": {}, "paths": ["tests/misc/test_pool.py"], + }, + "limit-tests": { + "description": "Tests verifying we can hit our supported limits", + "requirements": [ + "1 XCP-ng host >= 8.2" + ], + "nb_pools": 1, + "params": { + # The test does not work on Alpine because of how it handles + # multiple interfaces on the same network, so use Debian instead + "--vm": "single/debian_uefi_vm", + }, + "paths": ["tests/limits/test_vif_limit.py"], } } @@ -637,7 +650,7 @@ def extract_tests(cmd): print("*** Checking that all tests that use VMs have VM target markers (small_vm, etc.)... ", end="") tests_missing_vm_markers = extract_tests( - ["pytest", "--collect-only", "-q", "-m", "not no_vm and not (small_vm or multi_vm or big_vm)"] + ["pytest", "--collect-only", "-q", "-m", "not no_vm and not (small_vm or multi_vm or big_vm or debian_uefi_vm)"] ) if tests_missing_vm_markers: error = True diff --git a/pytest.ini b/pytest.ini index 2d6a28758..9ee82e501 100644 --- a/pytest.ini +++ b/pytest.ini @@ -30,6 +30,7 @@ markers = small_vm: tests that it is enough to run just once, using the smallest possible VM. big_vm: tests that it would be good to run with a big VM. multi_vms: tests that it would be good to run on a variety of VMs (includes `small_vm` but excludes `big_vm`). + debian_uefi_vm: tests that require a Debian UEFI VM # * Other markers reboot: tests that reboot one or more hosts. From c01b132d72b10b8e22ceb21b2d852e459e21e5bd Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Mon, 7 Jul 2025 09:38:40 +0100 Subject: [PATCH 3/5] tests/limit - Add a test verifying we can reach the new VIF limit of 16 Currently the test verifies that interfaces have been created correctly (that there were enough grant frames to allocate all the queues), and simply prints performance metrics. This should be integrated with whatever database we are going to use for performance monitoring in the future. Signed-off-by: Andrii Sultanov --- tests/limits/__init__.py | 0 tests/limits/test_vif_limit.py | 95 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tests/limits/__init__.py create mode 100644 tests/limits/test_vif_limit.py diff --git a/tests/limits/__init__.py b/tests/limits/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/limits/test_vif_limit.py b/tests/limits/test_vif_limit.py new file mode 100644 index 000000000..91c0a5338 --- /dev/null +++ b/tests/limits/test_vif_limit.py @@ -0,0 +1,95 @@ +import pytest + +import ipaddress +import logging +import os +import tempfile + +from pkgfixtures import host_with_saved_yum_state + +# Requirements: +# - one XCP-ng host (--host) >= 8.2 +# - a VM (--vm) +# - the first network on the host can be used to reach the host + +vif_limit = 16 +interface_name = "enX" +vcpus = '8' + +# There is a ResourceWarning due to background=True on an ssh call +# We do ensure the processes are killed +@pytest.mark.filterwarnings("ignore::ResourceWarning") +@pytest.mark.debian_uefi_vm +class TestVIFLimit: + def test_vif_limit(self, host_with_saved_yum_state, imported_vm): + host = host_with_saved_yum_state + vm = imported_vm + if (vm.is_running()): + logging.info("VM already running, shutting it down first") + vm.shutdown(verify=True) + + network_uuid = vm.vifs()[0].param_get('network-uuid') + existing_vifs = len(vm.vifs()) + + logging.info(f'Get {vcpus} vCPUs for the VM') + vm.param_set('VCPUs-max', vcpus) + vm.param_set('VCPUs-at-startup', vcpus) + + logging.info('Create VIFs before starting the VM') + for i in range(existing_vifs, vif_limit): + vm.create_vif(i, network_uuid=network_uuid) + + vm.start() + vm.wait_for_os_booted() + + logging.info('Verify the interfaces exist in the guest') + for i in range(0, vif_limit): + if vm.ssh_with_result([f'test -d /sys/class/net/{interface_name}{i}']).returncode != 0: + guest_error = vm.ssh_with_result(['dmesg | grep -B1 -A3 xen_netfront']).stdout + logging.error("dmesg:\n%s", guest_error) + assert False, "The interface does not exist in the guest, check dmesg output above for errors" + + logging.info('Configure interfaces') + config = '\n'.join([f'iface {interface_name}{i} inet dhcp\n' + f'auto {interface_name}{i}' + for i in range(existing_vifs, vif_limit)]) + vm.ssh([f'echo "{config}" >> /etc/network/interfaces']) + + logging.info('Install iperf3 on VM and host') + if vm.ssh_with_result(['apt install iperf3 --assume-yes']).returncode != 0: + assert False, "Failed to install iperf3 on the VM" + host.yum_install(['iperf3']) + + logging.info('Reconfigure VM networking') + if vm.ssh_with_result(['systemctl restart networking']).returncode != 0: + assert False, "Failed to configure networking" + + # Test iperf on all interfaces in parallel + # Clean up on exceptions + try: + logging.info('Create separate iperf servers on the host') + with tempfile.NamedTemporaryFile('w') as host_script: + iperf_configs = [f'iperf3 -s -p {5100+i} &' + for i in range(0, vif_limit)] + host_script.write('\n'.join(iperf_configs)) + host_script.flush() + host.scp(host_script.name, host_script.name) + host.ssh([f'nohup bash -c "bash {host_script.name}" < /dev/null &>/dev/null &'], + background=True) + + logging.info('Start multiple iperfs on separate interfaces on the VM') + with tempfile.NamedTemporaryFile('w') as vm_script: + iperf_configs = [f'iperf3 --no-delay -c {host.hostname_or_ip} ' + f'-p {5100+i} --bind-dev {interface_name}{i} ' + f'--interval 0 --parallel 1 --time 30 &' + for i in range(0, vif_limit)] + vm_script.write('\n'.join(iperf_configs)) + vm_script.flush() + vm.scp(vm_script.name, vm_script.name) + stdout = vm.ssh([f'bash {vm_script.name}']) + + # TODO: log this into some performance time series DB + logging.info(stdout) + finally: + vm.ssh(['pkill iperf3 || true']) + host.ssh('killall iperf3') From 672eaf7dddf44d5320fcb5a4039afb3fd916f025 Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Tue, 21 Oct 2025 09:26:38 +0100 Subject: [PATCH 4/5] tests/limit: Restore the VM to its original state Signed-off-by: Andrii Sultanov --- lib/vif.py | 3 +++ tests/limits/test_vif_limit.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/vif.py b/lib/vif.py index ffa4d273a..343d50a76 100644 --- a/lib/vif.py +++ b/lib/vif.py @@ -30,3 +30,6 @@ def device_id(self): def move(self, network_uuid): self.vm.host.xe('vif-move', {'uuid': self.uuid, 'network-uuid': network_uuid}) + + def destroy(self): + self.vm.host.xe('vif-destroy', {'uuid': self.uuid}) diff --git a/tests/limits/test_vif_limit.py b/tests/limits/test_vif_limit.py index 91c0a5338..2f7647568 100644 --- a/tests/limits/test_vif_limit.py +++ b/tests/limits/test_vif_limit.py @@ -32,12 +32,16 @@ def test_vif_limit(self, host_with_saved_yum_state, imported_vm): existing_vifs = len(vm.vifs()) logging.info(f'Get {vcpus} vCPUs for the VM') + original_vcpus_max = vm.param_get('VCPUs-max') + original_vcpus_at_startup = vm.param_get('VCPUs-at-startup') vm.param_set('VCPUs-max', vcpus) vm.param_set('VCPUs-at-startup', vcpus) logging.info('Create VIFs before starting the VM') + vifs = [] for i in range(existing_vifs, vif_limit): - vm.create_vif(i, network_uuid=network_uuid) + vif = vm.create_vif(i, network_uuid=network_uuid) + vifs.append(vif) vm.start() vm.wait_for_os_booted() @@ -92,4 +96,9 @@ def test_vif_limit(self, host_with_saved_yum_state, imported_vm): logging.info(stdout) finally: vm.ssh(['pkill iperf3 || true']) + vm.shutdown(verify=True) + vm.param_set('VCPUs-at-startup', original_vcpus_at_startup) + vm.param_set('VCPUs-max', original_vcpus_max) + for vif in vifs: + vif.destroy() host.ssh('killall iperf3') From 441367bf957b0f599a5c4e61dac685f3ef34b43c Mon Sep 17 00:00:00 2001 From: Andrii Sultanov Date: Fri, 31 Oct 2025 11:36:50 +0000 Subject: [PATCH 5/5] fixup Signed-off-by: Andrii Sultanov --- tests/limits/test_vif_limit.py | 70 +++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/limits/test_vif_limit.py b/tests/limits/test_vif_limit.py index 2f7647568..d50c452bc 100644 --- a/tests/limits/test_vif_limit.py +++ b/tests/limits/test_vif_limit.py @@ -12,9 +12,8 @@ # - a VM (--vm) # - the first network on the host can be used to reach the host -vif_limit = 16 -interface_name = "enX" -vcpus = '8' +VIF_LIMIT = 16 +VCPUS = '8' # There is a ResourceWarning due to background=True on an ssh call # We do ensure the processes are killed @@ -24,6 +23,8 @@ class TestVIFLimit: def test_vif_limit(self, host_with_saved_yum_state, imported_vm): host = host_with_saved_yum_state vm = imported_vm + interface_name = "enX" + if (vm.is_running()): logging.info("VM already running, shutting it down first") vm.shutdown(verify=True) @@ -31,50 +32,49 @@ def test_vif_limit(self, host_with_saved_yum_state, imported_vm): network_uuid = vm.vifs()[0].param_get('network-uuid') existing_vifs = len(vm.vifs()) - logging.info(f'Get {vcpus} vCPUs for the VM') + logging.info(f'Get {VCPUS} vCPUs for the VM') original_vcpus_max = vm.param_get('VCPUs-max') original_vcpus_at_startup = vm.param_get('VCPUs-at-startup') - vm.param_set('VCPUs-max', vcpus) - vm.param_set('VCPUs-at-startup', vcpus) + vm.param_set('VCPUs-max', VCPUS) + vm.param_set('VCPUs-at-startup', VCPUS) logging.info('Create VIFs before starting the VM') vifs = [] - for i in range(existing_vifs, vif_limit): + for i in range(existing_vifs, VIF_LIMIT): vif = vm.create_vif(i, network_uuid=network_uuid) vifs.append(vif) vm.start() vm.wait_for_os_booted() - - logging.info('Verify the interfaces exist in the guest') - for i in range(0, vif_limit): - if vm.ssh_with_result([f'test -d /sys/class/net/{interface_name}{i}']).returncode != 0: - guest_error = vm.ssh_with_result(['dmesg | grep -B1 -A3 xen_netfront']).stdout - logging.error("dmesg:\n%s", guest_error) - assert False, "The interface does not exist in the guest, check dmesg output above for errors" - - logging.info('Configure interfaces') - config = '\n'.join([f'iface {interface_name}{i} inet dhcp\n' - f'auto {interface_name}{i}' - for i in range(existing_vifs, vif_limit)]) - vm.ssh([f'echo "{config}" >> /etc/network/interfaces']) - - logging.info('Install iperf3 on VM and host') - if vm.ssh_with_result(['apt install iperf3 --assume-yes']).returncode != 0: - assert False, "Failed to install iperf3 on the VM" - host.yum_install(['iperf3']) - - logging.info('Reconfigure VM networking') - if vm.ssh_with_result(['systemctl restart networking']).returncode != 0: - assert False, "Failed to configure networking" - - # Test iperf on all interfaces in parallel - # Clean up on exceptions try: + logging.info('Verify the interfaces exist in the guest') + for i in range(0, VIF_LIMIT): + if vm.ssh_with_result([f'test -d /sys/class/net/{interface_name}{i}']).returncode != 0: + guest_error = vm.ssh_with_result(['dmesg | grep -B1 -A3 xen_netfront']).stdout + logging.error("dmesg:\n%s", guest_error) + assert False, "The interface does not exist in the guest, check dmesg output above for errors" + + logging.info('Configure interfaces') + config = '\n'.join([f'iface {interface_name}{i} inet dhcp\n' + f'auto {interface_name}{i}' + for i in range(existing_vifs, VIF_LIMIT)]) + vm.ssh([f'echo "{config}" >> /etc/network/interfaces']) + + logging.info('Install iperf3 on VM and host') + if vm.ssh_with_result(['apt install iperf3 --assume-yes']).returncode != 0: + assert False, "Failed to install iperf3 on the VM" + host.yum_install(['iperf3']) + + logging.info('Reconfigure VM networking') + if vm.ssh_with_result(['systemctl restart networking']).returncode != 0: + assert False, "Failed to configure networking" + + # Test iperf on all interfaces in parallel + # Clean up on exceptions logging.info('Create separate iperf servers on the host') with tempfile.NamedTemporaryFile('w') as host_script: iperf_configs = [f'iperf3 -s -p {5100+i} &' - for i in range(0, vif_limit)] + for i in range(0, VIF_LIMIT)] host_script.write('\n'.join(iperf_configs)) host_script.flush() host.scp(host_script.name, host_script.name) @@ -86,7 +86,7 @@ def test_vif_limit(self, host_with_saved_yum_state, imported_vm): iperf_configs = [f'iperf3 --no-delay -c {host.hostname_or_ip} ' f'-p {5100+i} --bind-dev {interface_name}{i} ' f'--interval 0 --parallel 1 --time 30 &' - for i in range(0, vif_limit)] + for i in range(0, VIF_LIMIT)] vm_script.write('\n'.join(iperf_configs)) vm_script.flush() vm.scp(vm_script.name, vm_script.name) @@ -101,4 +101,4 @@ def test_vif_limit(self, host_with_saved_yum_state, imported_vm): vm.param_set('VCPUs-max', original_vcpus_max) for vif in vifs: vif.destroy() - host.ssh('killall iperf3') + host.ssh('killall iperf3 || true')