Skip to content

Commit 86662db

Browse files
committed
tests: add xen-guest-agent integration tests
Add a test suite that validates the xen-guest-agent daemon running inside a guest VM correctly publishes data to Xenstore. The conftest downloads RPM and DEB packages from the xen-guest-agent GitLab CI artifacts and SCPs them to the VM for installation. The test class (TestXenGuestAgent) then installs the agent on the VM via yum (RPM distros) or dpkg (APT distros), then verifies: - the systemd service is active after install and after reboot - Xenstore paths for version, OS info, memory and VIF IP are populated (meminfo_free is polled with a 90s timeout as it is only published on a 60s timer; the VIF/IP check is skipped if no Xen PV NIC is present) Signed-off-by: Julian Vetter <julian.vetter@vates.tech>
1 parent d3d1b55 commit 86662db

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed

tests/guest_tools/unix/conftest.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pytest
2+
3+
import os
4+
import tempfile
5+
import zipfile
6+
7+
import requests
8+
9+
from lib.common import url_download
10+
11+
_GITLAB_API = 'https://gitlab.com/api/v4/projects/xen-project%2Fxen-guest-' \
12+
'agent/jobs/artifacts/main/download?search_recent_successful_' \
13+
'pipelines=true&job='
14+
15+
16+
def _extract_from_zip(zip_path, suffix, dest_dir):
17+
"""
18+
Extract the main package matching suffix from zip_path into dest_dir.
19+
Excludes debug/dbgsym packages which may also be present.
20+
"""
21+
with zipfile.ZipFile(zip_path) as zf:
22+
matches = [
23+
n for n in zf.namelist()
24+
if n.endswith(suffix) and not any(
25+
kw in os.path.basename(n) for kw in ('debug', 'dbgsym')
26+
)
27+
]
28+
assert len(matches) == 1, \
29+
f"Expected exactly one non-debug *{suffix} in artifact zip, found: {matches}"
30+
zf.extract(matches[0], dest_dir)
31+
return os.path.join(dest_dir, matches[0])
32+
33+
34+
@pytest.fixture(scope="module")
35+
def xen_guest_agent_packages():
36+
"""
37+
Download the latest xen-guest-agent RPM and DEB from GitLab CI artifacts.
38+
Yields a dict with keys 'rpm' and 'deb' pointing to the local file paths.
39+
"""
40+
artifact_urls = { 'rpm': f'{_GITLAB_API}pkg-rpm-x86_64',
41+
'deb': f'{_GITLAB_API}pkg-deb-amd64' }
42+
43+
with tempfile.TemporaryDirectory() as tmpdir:
44+
zip_path = os.path.join(tmpdir, 'artifacts.zip')
45+
46+
url_download(artifact_urls['rpm'], zip_path)
47+
rpm_path = _extract_from_zip(zip_path, '.rpm', tmpdir)
48+
49+
url_download(artifact_urls['deb'], zip_path)
50+
deb_path = _extract_from_zip(zip_path, '.deb', tmpdir)
51+
52+
yield {'rpm': rpm_path, 'deb': deb_path}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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

Comments
 (0)