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:
kaffa
2026-02-03 00:55:24 +09:00
parent 95aecccb03
commit 46c86b62f2
5 changed files with 81 additions and 149 deletions

View File

@@ -1,6 +1,5 @@
"""Configuration management tools for HAProxy MCP Server."""
import fcntl
import subprocess
import time
@@ -9,12 +8,9 @@ from ..config import (
HAPROXY_CONTAINER,
SUBPROCESS_TIMEOUT,
STARTUP_RETRY_COUNT,
StateField,
STATE_MIN_COLUMNS,
logger,
)
from ..exceptions import HaproxyError
from ..validation import validate_ip, validate_port, validate_backend_name
from ..haproxy_client import haproxy_cmd, haproxy_cmd_batch, reload_haproxy
from ..file_ops import (
atomic_write_file,
@@ -76,7 +72,7 @@ def restore_servers_from_config() -> int:
if not commands:
return 0
# Execute all commands in single batch
# Execute all commands
try:
haproxy_cmd_batch(commands)
return len(server_info_list)
@@ -141,6 +137,20 @@ def register_config_tools(mcp):
if not success:
return msg
# Wait for HAProxy to fully reload (new process takes over)
# USR2 signal spawns new process but old one may still be serving
time.sleep(2)
# Verify HAProxy is responding
for _ in range(STARTUP_RETRY_COUNT):
try:
haproxy_cmd("show info")
break
except HaproxyError:
time.sleep(0.5)
else:
return "HAProxy reloaded but not responding after reload"
# Restore servers from config after reload
try:
restored = restore_servers_from_config()
@@ -191,82 +201,17 @@ def register_config_tools(mcp):
def haproxy_restore_state() -> str:
"""Restore server state from disk.
Uses batched commands for efficiency.
Reads server configuration from servers.json and restores to HAProxy.
Returns:
Summary of restored servers or error description
"""
try:
with open(STATE_FILE, "r", encoding="utf-8") as f:
try:
fcntl.flock(f.fileno(), fcntl.LOCK_SH)
except OSError:
pass # Continue without lock if not supported
try:
state = f.read()
finally:
try:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
except OSError:
pass
# Build batch of all commands
commands: list[str] = []
server_info_list: list[tuple[str, str]] = []
skipped = 0
for line in state.split("\n"):
parts = line.split()
if len(parts) >= STATE_MIN_COLUMNS and not line.startswith("#"):
backend = parts[StateField.BE_NAME]
server = parts[StateField.SRV_NAME]
addr = parts[StateField.SRV_ADDR]
port = parts[StateField.SRV_PORT]
# Skip disabled servers
if addr == "0.0.0.0":
continue
# Validate names from state file to prevent injection
if not validate_backend_name(backend) or not validate_backend_name(server):
skipped += 1
continue
# Validate IP and port
if not validate_ip(addr) or not validate_port(port):
skipped += 1
continue
commands.append(f"set server {backend}/{server} addr {addr} port {port}")
commands.append(f"set server {backend}/{server} state ready")
server_info_list.append((backend, server))
if not commands:
result = "No servers to restore"
if skipped:
result += f", {skipped} entries skipped due to validation"
return result
# Execute all commands in single batch
try:
haproxy_cmd_batch(commands)
restored = len(server_info_list)
except HaproxyError:
# Fallback: try individual pairs
restored = 0
for i in range(0, len(commands), 2):
try:
haproxy_cmd_batch([commands[i], commands[i + 1]])
restored += 1
except HaproxyError as e:
backend, server = server_info_list[i // 2]
logger.warning("Failed to restore %s/%s: %s", backend, server, e)
result = f"Server state restored ({restored} servers)"
if skipped:
result += f", {skipped} entries skipped due to validation"
return result
except FileNotFoundError:
return "Error: No saved state found"
restored = restore_servers_from_config()
if restored == 0:
return "No servers to restore"
return f"Server state restored ({restored} servers)"
except HaproxyError as e:
return f"Error: {e}"
except (OSError, ValueError) as e:
return f"Error: {e}"