penguin/tinyproxy

An L4 proxy designed to act as a tiny transparent shim

tests/test_haproxy_proxy_v2.py

raw ยท 2621 bytes

import os
import sys
import subprocess

from .support import (
	BACKEND_PORT,
	LISTEN_HOST,
	PROXY_PORT,
	SkipTest,
	find_program,
	print_process_output,
	proxy_roundtrip,
	run_echo_backend,
	run_tinyproxy_with_conf,
	terminate_process,
	wait_for_port,
	write_temp_file,
)


HAPROXY_FRONTEND_PORT = 41233


def require_haproxy() -> str:
	if sys.platform.startswith("win"):
		raise SkipTest("HAProxy proxy-v2 tests are skipped on Windows")

	haproxy_bin = find_program("HAPROXY_BIN", "haproxy")
	if haproxy_bin is None:
		raise SkipTest("HAProxy not found; set HAPROXY_BIN to enable this test")

	return haproxy_bin


async def test_tinyproxy_sends_proxy_v2_to_haproxy() -> None:
	haproxy_bin = require_haproxy()

	haproxy_conf = f"""
global
	maxconn 4096

defaults
	mode tcp
	timeout connect 5s
	timeout client 30s
	timeout server 30s

frontend proxy_v2_in
	bind {LISTEN_HOST}:{HAPROXY_FRONTEND_PORT} accept-proxy
	default_backend echo_backend

backend echo_backend
	server echo1 {LISTEN_HOST}:{BACKEND_PORT}
"""

	haproxy_conf_path = None
	haproxy = None

	# Adjust this if your config parser uses a different spelling.
	#
	# Current expected route shape:
	#
	#   listen upstream tcp proxy_v2
	#
	proxy_v2_option = os.environ.get("TINYPROXY_PROXY_V2_OPTION", "proxy_v2")

	tinyproxy_conf = (
		f"{LISTEN_HOST}:{PROXY_PORT} "
		f"{LISTEN_HOST}:{HAPROXY_FRONTEND_PORT} "
		f"tcp {proxy_v2_option}\n"
	)

	try:
		haproxy_conf_path = write_temp_file(haproxy_conf, ".haproxy.cfg")

		haproxy = subprocess.Popen(
			[haproxy_bin, "-f", haproxy_conf_path, "-db"],
			stdout=subprocess.PIPE,
			stderr=subprocess.PIPE,
			text=True,
		)

		async with run_echo_backend(LISTEN_HOST, BACKEND_PORT):
			await wait_for_port(LISTEN_HOST, HAPROXY_FRONTEND_PORT)

			if haproxy.poll() is not None:
				stderr = haproxy.stderr.read() if haproxy.stderr else ""
				raise RuntimeError(
					f"haproxy exited early with code {haproxy.returncode}\n{stderr}"
				)

			async with run_tinyproxy_with_conf(
				proxy_bin=os.environ["TINYPROXY_BIN"],
				conf_text=tinyproxy_conf,
				listen_host=LISTEN_HOST,
				listen_port=PROXY_PORT,
			):
				payload = b"hello through proxy v2 via haproxy\n"
				got = await proxy_roundtrip(payload, port=PROXY_PORT)

				assert got == payload, f"proxy-v2 haproxy roundtrip mismatch: {got!r}"

	finally:
		terminate_process("haproxy", haproxy)
		print_process_output("haproxy", haproxy)

		if haproxy_conf_path is not None:
			try:
				os.unlink(haproxy_conf_path)
			except FileNotFoundError:
				pass


TESTS = [
	("test_tinyproxy_sends_proxy_v2_to_haproxy", test_tinyproxy_sends_proxy_v2_to_haproxy),
]