@samitouri / QOSamiQemu / commits / 051364b36b

tests/functional/x86_64: Add vhost-user-bridge test

Introduce a functional test of vhost-user-bridge and enter it into MAINTAINERS under the vhost section. The test runs vhost-user-bridge as a subprocess, then launches a guest with four backends: a unix domain socket for vhost-user, a UDP socket, a user-mode net, and a hubport to hub the UDP and user backends; only the vhost-user backend is exposed, the rest are deviceless. This configuration mimics the testing setup described in the initial commit of vhost-user-bridge in 3595e2eb0a23. The test creates a scratch file containing a hardcoded UUID on the host and exposes it to the the guest via the tftp parameter of the user netdev. After the guest invokes tftp to request the file, the test verifies the transfer by hashsum. Similarly, the test creates a file with another hardcoded UUID in the guest. A call to check_http_download() serves the file to the host via http, whereupon a check of the file hashsum occurs on the host. Lastly, add the test to the thorough tests suite in meson.build. Suggested-by: Cédric Le Goater <clg@redhat.com> Suggested-by: Marc-André Lureau <marcandre.lureau@gmail.com> Suggested-by: Michael S. Tsirkin <mst@redhat.com> Suggested-by: Thomas Huth <thuth@redhat.com> Reviewed-by: Thomas Huth <thuth@redhat.com> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Yodel Eldar <yodel.eldar@yodel.dev> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20260129133049.119829-3-yodel.eldar@yodel.dev>

Yodel Eldar committed Jan 29, 2026 at 07:30 UTC 051364b36b2c68058f7e6d2204ef2a877b0bb221
3 files changed +149
MAINTAINERS
+1
@@ -2406,6 +2406,7 @@ F: subprojects/libvhost-user/
2406 F: block/export/vhost-user*
2407 F: util/vhost-user-server.c
2408 F: net/vhost*
2409 +F: tests/functional/x86_64/test_vhost_user_bridge.py
2410
2411 vhost-shadow-virtqueue
2412 R: Eugenio Pérez <eperezma@redhat.com>
tests/functional/x86_64/meson.build
+1
@@ -34,6 +34,7 @@ tests_x86_64_system_thorough = [
34 'reverse_debug',
35 'tuxrun',
36 'vfio_user_client',
37 + 'vhost_user_bridge',
38 'virtio_balloon',
39 'virtio_gpu',
40 ]
tests/functional/x86_64/test_vhost_user_bridge.py new
+147
@@ -0,0 +1,147 @@
1 +#!/usr/bin/env python3
2 +#
3 +# Copyright (c) 2025 Software Freedom Conservancy, Inc.
4 +#
5 +# Author: Yodel Eldar <yodel.eldar@yodel.dev>
6 +#
7 +# SPDX-License-Identifier: GPL-2.0-or-later
8 +"""
9 +Test vhost-user-bridge (vubr) functionality:
10 +
11 + 1) Run vhost-user-bridge on the host.
12 + 2) Launch a guest VM:
13 + a) Instantiate a unix domain socket to the vubr-created path
14 + b) Instantiate a vhost-user backend on top of that socket
15 + c) Map a virtio-net-pci device to the vhost-user backend
16 + d) Instantiate a UDP socket backend
17 + e) Instantiate a user-mode net backend
18 + i) Forward an ephemeral port to port 8080 in-guest with hostfwd=
19 + ii) Expose a generated scratch file to the guest with tftp=
20 + f) Hub the UDP and user-mode backends.
21 + 3) Invoke tftp in the guest to download exported scratch file from the host.
22 + 4) Serve a file to the host via http server in the guest.
23 +"""
24 +
25 +import os
26 +import shutil
27 +import subprocess
28 +from qemu_test import Asset, LinuxKernelTest, which
29 +from qemu_test import exec_command_and_wait_for_pattern
30 +from qemu_test import is_readable_executable_file
31 +from qemu_test import wait_for_console_pattern
32 +from qemu_test.ports import Ports
33 +
34 +class VhostUserBridge(LinuxKernelTest):
35 +
36 + ASSET_KERNEL_INITRAMFS = Asset(
37 + "https://github.com/yodel/vhost-user-bridge-test/raw/refs/heads/main/bzImage",
38 + "8860d7aa59434f483542cdf25b42eacae0d4d4aa7ec923af9589d1ad4703d42b")
39 +
40 + HOST_UUID = "ba4c2e39-627f-487d-ae3b-93cc5d783eb8"
41 + HOST_UUID_HSUM = \
42 + "d2932e34bf6c17b33e7325140b691e27c191d9ac4dfa550f68c09506facb09b9"
43 +
44 + GUEST_UUID = "143d2b21-fdf0-4c5e-a9ef-f35ebbac8945"
45 + GUEST_UUID_HSUM = \
46 + "14b64203f5cf2afe520f8be0fdfe630aafc1e85d1301f55a0d1681e68881f3a2"
47 +
48 + def configure_vm(self, ud_socket_path, lport, rport, hostfwd_port, tftpdir):
49 + self.require_accelerator("kvm")
50 + self.require_netdev("vhost-user")
51 + self.require_netdev("socket")
52 + self.require_netdev("hubport")
53 + self.require_netdev("user")
54 + self.require_device("virtio-net-pci")
55 + self.set_machine("q35")
56 + self.vm.add_args(
57 + "-cpu", "host",
58 + "-accel", "kvm",
59 + "-append", "printk.time=0 console=ttyS0",
60 + "-smp", "2",
61 + "-m", "128M",
62 + "-object", "memory-backend-memfd,id=mem0,"
63 + "size=128M,share=on,prealloc=on",
64 + "-numa", "node,memdev=mem0",
65 + "-chardev", f"socket,id=char0,path={ud_socket_path}",
66 + "-netdev", "vhost-user,id=vhost0,chardev=char0,vhostforce=on",
67 + "-device", "virtio-net-pci,netdev=vhost0",
68 + "-netdev", f"socket,id=udp0,udp=localhost:{lport},"
69 + f"localaddr=localhost:{rport}",
70 + "-netdev", "hubport,id=hub0,hubid=0,netdev=udp0",
71 + "-netdev", f"user,id=user0,tftp={tftpdir},"
72 + f"hostfwd=tcp:127.0.0.1:{hostfwd_port}-:8080",
73 + "-netdev", "hubport,id=hub1,hubid=0,netdev=user0"
74 + )
75 +
76 + def assemble_vubr_args(self, vubr_path, ud_socket_path, lport, rport):
77 + vubr_args = []
78 +
79 + if (stdbuf_path := which("stdbuf")) is None:
80 + self.log.info("Could not find stdbuf: vhost-user-bridge "
81 + "log lines may appear out of order")
82 + else:
83 + vubr_args += [stdbuf_path, "-o0", "-e0"]
84 +
85 + vubr_args += [vubr_path, "-u", f"{ud_socket_path}",
86 + "-l", f"127.0.0.1:{lport}", "-r", f"127.0.0.1:{rport}"]
87 +
88 + return vubr_args
89 +
90 + def test_vhost_user_bridge(self):
91 + prompt = "~ # "
92 + host_uuid_filename = "vubr-test-uuid.txt"
93 + guest_uuid_path = "/tmp/uuid.txt"
94 + kernel_path = self.ASSET_KERNEL_INITRAMFS.fetch()
95 +
96 + vubr_path = self.build_file("contrib", "vhost-user-bridge",
97 + "vhost-user-bridge")
98 + if not is_readable_executable_file(vubr_path):
99 + self.skipTest("Could not find a readable and executable "
100 + "vhost-user-bridge")
101 +
102 + vubr_log_path = self.log_file("vhost-user-bridge.log")
103 + self.log.info("For the vhost-user-bridge application log,"
104 + f" see: {vubr_log_path}")
105 +
106 + sock_dir = self.socket_dir()
107 + ud_socket_path = os.path.join(sock_dir.name, "vubr-test.sock")
108 +
109 + tftpdir = self.scratch_file("tftp")
110 + shutil.rmtree(tftpdir, ignore_errors=True)
111 + os.mkdir(tftpdir)
112 + host_uuid_path = self.scratch_file("tftp", host_uuid_filename)
113 + with open(host_uuid_path, "w", encoding="utf-8") as host_uuid_file:
114 + host_uuid_file.write(self.HOST_UUID)
115 +
116 + with Ports() as ports:
117 + # pylint: disable=unbalanced-tuple-unpacking
118 + lport, rport, hostfwd_port = ports.find_free_ports(3)
119 +
120 + self.configure_vm(ud_socket_path, lport, rport, hostfwd_port,
121 + tftpdir)
122 +
123 + vubr_args = self.assemble_vubr_args(vubr_path, ud_socket_path,
124 + lport, rport)
125 +
126 + with open(vubr_log_path, "w", encoding="utf-8") as vubr_log, \
127 + subprocess.Popen(vubr_args, stdin=subprocess.DEVNULL,
128 + stdout=vubr_log,
129 + stderr=subprocess.STDOUT) as vubr_proc:
130 + self.launch_kernel(kernel_path, wait_for=prompt)
131 +
132 + exec_command_and_wait_for_pattern(self,
133 + f"tftp -g -r {host_uuid_filename} 10.0.2.2 ; "
134 + f"sha256sum {host_uuid_filename}", self.HOST_UUID_HSUM)
135 + wait_for_console_pattern(self, prompt)
136 +
137 + exec_command_and_wait_for_pattern(self,
138 + f"echo -n '{self.GUEST_UUID}' > {guest_uuid_path}", prompt)
139 + self.check_http_download(guest_uuid_path, self.GUEST_UUID_HSUM)
140 + wait_for_console_pattern(self, prompt)
141 +
142 + self.vm.shutdown()
143 + vubr_proc.terminate()
144 + vubr_proc.wait()
145 +
146 +if __name__ == '__main__':
147 + LinuxKernelTest.main()