|
| 1 | +import pytest |
| 2 | + |
| 3 | +import logging |
| 4 | + |
| 5 | +from lib.common import PackageManagerEnum, wait_for |
| 6 | + |
| 7 | +# Requirements: |
| 8 | +# From --hosts parameter: |
| 9 | +# - host(A1): first XCP-ng host >= 8.0 |
| 10 | +# From --vm parameter: |
| 11 | +# - A Linux VM with systemd and a supported package manager (RPM or APT) |
| 12 | + |
| 13 | + |
| 14 | +def _vif_published_ips(host, xs_prefix, vif_id, proto): |
| 15 | + """Return all IPs published under attr/vif/{vif_id}/{proto}/* in Xenstore.""" |
| 16 | + ips = [] |
| 17 | + for slot in range(10): # NUM_IFACE_IPS = 10 in xen-guest-agent |
| 18 | + res = host.ssh_with_result( |
| 19 | + ['xenstore-read', f'{xs_prefix}/attr/vif/{vif_id}/{proto}/{slot}'] |
| 20 | + ) |
| 21 | + if res.returncode == 0: |
| 22 | + ips.append(res.stdout.strip()) |
| 23 | + return ips |
| 24 | + |
| 25 | + |
| 26 | +@pytest.mark.multi_vms |
| 27 | +@pytest.mark.usefixtures("unix_vm") |
| 28 | +class TestXenGuestAgent: |
| 29 | + @pytest.fixture(scope="class", autouse=True) |
| 30 | + def agent_install(self, running_vm, xen_guest_agent_packages): |
| 31 | + vm = running_vm |
| 32 | + |
| 33 | + if vm.ssh_with_result(['which', 'systemctl']).returncode != 0: |
| 34 | + pytest.skip("systemd not available on this VM") |
| 35 | + |
| 36 | + pkg_mgr = vm.detect_package_manager() |
| 37 | + if pkg_mgr not in (PackageManagerEnum.RPM, PackageManagerEnum.APT_GET): |
| 38 | + pytest.skip(f"Package manager '{pkg_mgr}' not supported in this test") |
| 39 | + |
| 40 | + # Remove conflicting xe-guest-utilities if present |
| 41 | + logging.info("Removing xe-guest-utilities if present") |
| 42 | + if pkg_mgr == PackageManagerEnum.RPM: |
| 43 | + vm.ssh('rpm -qa | grep xe-guest-utilities | xargs --no-run-if-empty rpm -e') |
| 44 | + if pkg_mgr == PackageManagerEnum.APT_GET and \ |
| 45 | + vm.ssh_with_result(['dpkg', '-l', 'xe-guest-utilities']).returncode == 0: |
| 46 | + vm.ssh(['apt-get', 'remove', '-y', 'xe-guest-utilities']) |
| 47 | + |
| 48 | + # Copy package to VM and install |
| 49 | + if pkg_mgr == PackageManagerEnum.RPM: |
| 50 | + vm.scp(xen_guest_agent_packages['rpm'], '/root/xen-guest-agent.rpm') |
| 51 | + vm.ssh(['yum', 'install', '-y', '/root/xen-guest-agent.rpm']) |
| 52 | + if pkg_mgr == PackageManagerEnum.APT_GET: |
| 53 | + vm.scp(xen_guest_agent_packages['deb'], '/root/xen-guest-agent.deb') |
| 54 | + vm.ssh(['dpkg', '-i', '/root/xen-guest-agent.deb']) |
| 55 | + |
| 56 | + wait_for( |
| 57 | + lambda: vm.ssh_with_result(['systemctl', 'is-active', 'xen-guest-agent']).returncode == 0, |
| 58 | + "Wait for xen-guest-agent service to be active", |
| 59 | + ) |
| 60 | + |
| 61 | + def test_agent_running(self, running_vm): |
| 62 | + running_vm.ssh(['systemctl', 'is-active', 'xen-guest-agent']) |
| 63 | + |
| 64 | + def test_agent_running_after_reboot(self, running_vm): |
| 65 | + running_vm.reboot(verify=True) |
| 66 | + running_vm.ssh(['systemctl', 'is-active', 'xen-guest-agent']) |
| 67 | + |
| 68 | + def test_xenstore_data(self, running_vm): |
| 69 | + vm = running_vm |
| 70 | + domid = vm.param_get('dom-id') |
| 71 | + host = vm.host |
| 72 | + xs_prefix = f'/local/domain/{domid}' |
| 73 | + |
| 74 | + logging.info("Check that xen-guest-agent published version info to Xenstore") |
| 75 | + host.ssh(['xenstore-read', f'{xs_prefix}/attr/PVAddons/MajorVersion']) |
| 76 | + host.ssh(['xenstore-read', f'{xs_prefix}/attr/PVAddons/BuildVersion']) |
| 77 | + |
| 78 | + logging.info("Check that OS info is published to Xenstore") |
| 79 | + host.ssh(['xenstore-read', f'{xs_prefix}/data/os_distro']) |
| 80 | + host.ssh(['xenstore-read', f'{xs_prefix}/data/os_uname']) |
| 81 | + |
| 82 | + logging.info("Check that memory info is published to Xenstore") |
| 83 | + host.ssh(['xenstore-read', f'{xs_prefix}/data/meminfo_total']) |
| 84 | + # meminfo_free is published on a 60s timer, wait for it to appear |
| 85 | + wait_for( |
| 86 | + lambda: host.ssh_with_result(['xenstore-read', f'{xs_prefix}/data/meminfo_free']).returncode == 0, |
| 87 | + "Wait for meminfo_free in Xenstore", |
| 88 | + timeout_secs=90, |
| 89 | + ) |
| 90 | + |
| 91 | + logging.info("Check that the VM's IP is published under attr/vif") |
| 92 | + # VIF detection requires a Xen PV NIC; skip if none was detected |
| 93 | + if host.ssh_with_result(['xenstore-exists', f'{xs_prefix}/attr/vif']).returncode != 0: |
| 94 | + pytest.skip("No VIF published in Xenstore — VM may not be using a Xen PV NIC") |
| 95 | + |
| 96 | + ipv4s = _vif_published_ips(host, xs_prefix, vif_id=0, proto='ipv4') |
| 97 | + ipv6s = _vif_published_ips(host, xs_prefix, vif_id=0, proto='ipv6') |
| 98 | + logging.info("Published IPv4: %s, IPv6: %s", ipv4s, ipv6s) |
| 99 | + assert ipv4s or ipv6s, "No IPs published in Xenstore under attr/vif/0" |
| 100 | + assert vm.ip in ipv4s + ipv6s, \ |
| 101 | + f"VM IP {vm.ip!r} not found in Xenstore (ipv4: {ipv4s}, ipv6: {ipv6s})" |
0 commit comments