blob: c36c62542053ed1042fd2cf7e5f6495123858380 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2025 Software Freedom Conservancy, Inc.
#
# Author: Yodel Eldar <yodel.eldar@yodel.dev>
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Test vhost-user-bridge (vubr) functionality:
1) Run vhost-user-bridge on the host.
2) Launch a guest VM:
a) Instantiate a unix domain socket to the vubr-created path
b) Instantiate a vhost-user backend on top of that socket
c) Map a virtio-net-pci device to the vhost-user backend
d) Instantiate a UDP socket backend
e) Instantiate a user-mode net backend
i) Forward an ephemeral port to port 8080 in-guest with hostfwd=
ii) Expose a generated scratch file to the guest with tftp=
f) Hub the UDP and user-mode backends.
3) Invoke tftp in the guest to download exported scratch file from the host.
4) Serve a file to the host via http server in the guest.
"""
import os
import shutil
import subprocess
from qemu_test import Asset, LinuxKernelTest, which
from qemu_test import exec_command_and_wait_for_pattern
from qemu_test import is_readable_executable_file
from qemu_test import wait_for_console_pattern
from qemu_test.ports import Ports
class VhostUserBridge(LinuxKernelTest):
ASSET_KERNEL_INITRAMFS = Asset(
"https://github.com/yodel/vhost-user-bridge-test/raw/refs/heads/main/bzImage",
"8860d7aa59434f483542cdf25b42eacae0d4d4aa7ec923af9589d1ad4703d42b")
HOST_UUID = "ba4c2e39-627f-487d-ae3b-93cc5d783eb8"
HOST_UUID_HSUM = \
"d2932e34bf6c17b33e7325140b691e27c191d9ac4dfa550f68c09506facb09b9"
GUEST_UUID = "143d2b21-fdf0-4c5e-a9ef-f35ebbac8945"
GUEST_UUID_HSUM = \
"14b64203f5cf2afe520f8be0fdfe630aafc1e85d1301f55a0d1681e68881f3a2"
def configure_vm(self, ud_socket_path, lport, rport, hostfwd_port, tftpdir):
self.require_accelerator("kvm")
self.require_netdev("vhost-user")
self.require_netdev("socket")
self.require_netdev("hubport")
self.require_netdev("user")
self.require_device("virtio-net-pci")
self.set_machine("q35")
self.vm.add_args(
"-cpu", "host",
"-accel", "kvm",
"-append", "printk.time=0 console=ttyS0",
"-smp", "2",
"-m", "128M",
"-object", "memory-backend-memfd,id=mem0,"
"size=128M,share=on,prealloc=on",
"-numa", "node,memdev=mem0",
"-chardev", f"socket,id=char0,path={ud_socket_path}",
"-netdev", "vhost-user,id=vhost0,chardev=char0,vhostforce=on",
"-device", "virtio-net-pci,netdev=vhost0",
"-netdev", f"socket,id=udp0,udp=localhost:{lport},"
f"localaddr=localhost:{rport}",
"-netdev", "hubport,id=hub0,hubid=0,netdev=udp0",
"-netdev", f"user,id=user0,tftp={tftpdir},"
f"hostfwd=tcp:127.0.0.1:{hostfwd_port}-:8080",
"-netdev", "hubport,id=hub1,hubid=0,netdev=user0"
)
def assemble_vubr_args(self, vubr_path, ud_socket_path, lport, rport):
vubr_args = []
if (stdbuf_path := which("stdbuf")) is None:
self.log.info("Could not find stdbuf: vhost-user-bridge "
"log lines may appear out of order")
else:
vubr_args += [stdbuf_path, "-o0", "-e0"]
vubr_args += [vubr_path, "-u", f"{ud_socket_path}",
"-l", f"127.0.0.1:{lport}", "-r", f"127.0.0.1:{rport}"]
return vubr_args
def test_vhost_user_bridge(self):
prompt = "~ # "
host_uuid_filename = "vubr-test-uuid.txt"
guest_uuid_path = "/tmp/uuid.txt"
kernel_path = self.ASSET_KERNEL_INITRAMFS.fetch()
vubr_path = self.build_file("contrib", "vhost-user-bridge",
"vhost-user-bridge")
if not is_readable_executable_file(vubr_path):
self.skipTest("Could not find a readable and executable "
"vhost-user-bridge")
vubr_log_path = self.log_file("vhost-user-bridge.log")
self.log.info("For the vhost-user-bridge application log,"
f" see: {vubr_log_path}")
sock_dir = self.socket_dir()
ud_socket_path = os.path.join(sock_dir.name, "vubr-test.sock")
tftpdir = self.scratch_file("tftp")
shutil.rmtree(tftpdir, ignore_errors=True)
os.mkdir(tftpdir)
host_uuid_path = self.scratch_file("tftp", host_uuid_filename)
with open(host_uuid_path, "w", encoding="utf-8") as host_uuid_file:
host_uuid_file.write(self.HOST_UUID)
with Ports() as ports:
# pylint: disable=unbalanced-tuple-unpacking
lport, rport, hostfwd_port = ports.find_free_ports(3)
self.configure_vm(ud_socket_path, lport, rport, hostfwd_port,
tftpdir)
vubr_args = self.assemble_vubr_args(vubr_path, ud_socket_path,
lport, rport)
with open(vubr_log_path, "w", encoding="utf-8") as vubr_log, \
subprocess.Popen(vubr_args, stdin=subprocess.DEVNULL,
stdout=vubr_log,
stderr=subprocess.STDOUT) as vubr_proc:
self.launch_kernel(kernel_path, wait_for=prompt)
exec_command_and_wait_for_pattern(self,
f"tftp -g -r {host_uuid_filename} 10.0.2.2 ; "
f"sha256sum {host_uuid_filename}", self.HOST_UUID_HSUM)
wait_for_console_pattern(self, prompt)
exec_command_and_wait_for_pattern(self,
f"echo -n '{self.GUEST_UUID}' > {guest_uuid_path}", prompt)
self.check_http_download(guest_uuid_path, self.GUEST_UUID_HSUM)
wait_for_console_pattern(self, prompt)
self.vm.shutdown()
vubr_proc.terminate()
vubr_proc.wait()
if __name__ == '__main__':
LinuxKernelTest.main()