master
py 147 lines 6 KB
Raw
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()