fix: HAProxy batch commands and improve routing/subdomain handling
- Fix haproxy_cmd_batch to send each command on separate connection (HAProxy Runtime API only processes first command on single connection) - HTTP frontend now routes to backends instead of redirecting to HTTPS - Add subdomain detection to avoid duplicate wildcard entries - Add reload verification with retry logic - Optimize SSL: TLS 1.3 ciphersuites, extended session lifetime - Add CPU steal monitoring script Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,10 +106,11 @@ def _check_response_for_errors(response: str) -> None:
|
||||
|
||||
|
||||
def haproxy_cmd_batch(commands: list[str]) -> list[str]:
|
||||
"""Send multiple commands to HAProxy in a single connection.
|
||||
"""Send multiple commands to HAProxy.
|
||||
|
||||
This is more efficient than multiple haproxy_cmd calls as it reuses
|
||||
the same TCP connection for all commands.
|
||||
Note: HAProxy Runtime API only processes the first command when multiple
|
||||
commands are sent on a single connection. This function sends each command
|
||||
on a separate connection to ensure all commands are executed.
|
||||
|
||||
Args:
|
||||
commands: List of HAProxy commands to execute
|
||||
@@ -126,64 +127,16 @@ def haproxy_cmd_batch(commands: list[str]) -> list[str]:
|
||||
if len(commands) == 1:
|
||||
return [haproxy_cmd_checked(commands[0])]
|
||||
|
||||
# Send all commands separated by newlines
|
||||
combined = "\n".join(commands)
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(SOCKET_TIMEOUT)
|
||||
s.connect(HAPROXY_SOCKET)
|
||||
s.sendall(f"{combined}\n".encode())
|
||||
s.shutdown(socket.SHUT_WR)
|
||||
# Send each command on separate connection (HAProxy limitation)
|
||||
responses = []
|
||||
for cmd in commands:
|
||||
try:
|
||||
resp = haproxy_cmd_checked(cmd)
|
||||
responses.append(resp)
|
||||
except HaproxyError:
|
||||
raise
|
||||
|
||||
# Set socket to non-blocking for select-based recv loop
|
||||
s.setblocking(False)
|
||||
response = b""
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed >= SOCKET_RECV_TIMEOUT:
|
||||
raise HaproxyError(f"Response timeout after {SOCKET_RECV_TIMEOUT} seconds")
|
||||
|
||||
remaining = SOCKET_RECV_TIMEOUT - elapsed
|
||||
ready, _, _ = select.select([s], [], [], min(remaining, 1.0))
|
||||
|
||||
if ready:
|
||||
data = s.recv(8192)
|
||||
if not data:
|
||||
break
|
||||
response += data
|
||||
if len(response) > MAX_RESPONSE_SIZE:
|
||||
raise HaproxyError(f"Response exceeded {MAX_RESPONSE_SIZE} bytes limit")
|
||||
|
||||
full_response = response.decode().strip()
|
||||
|
||||
# Split responses - HAProxy separates responses with empty lines
|
||||
# For commands that return nothing, we get empty strings
|
||||
responses = full_response.split("\n\n") if full_response else [""] * len(commands)
|
||||
|
||||
# If we got fewer responses than commands, pad with empty strings
|
||||
while len(responses) < len(commands):
|
||||
responses.append("")
|
||||
|
||||
# Check each response for errors
|
||||
for i, resp in enumerate(responses):
|
||||
resp = resp.strip()
|
||||
_check_response_for_errors(resp)
|
||||
responses[i] = resp
|
||||
|
||||
return responses
|
||||
|
||||
except socket.timeout:
|
||||
raise HaproxyError("Connection timeout")
|
||||
except ConnectionRefusedError:
|
||||
raise HaproxyError("Connection refused - HAProxy not running?")
|
||||
except UnicodeDecodeError:
|
||||
raise HaproxyError("Invalid UTF-8 in response")
|
||||
except HaproxyError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HaproxyError(str(e)) from e
|
||||
return responses
|
||||
|
||||
|
||||
def reload_haproxy() -> tuple[bool, str]:
|
||||
|
||||
Reference in New Issue
Block a user