refactor: Improve code quality, error handling, and test coverage

- Add file_lock context manager to eliminate duplicate locking patterns
- Add ValidationError, ConfigurationError, CertificateError exceptions
- Improve rollback logic in haproxy_add_servers (track successful ops only)
- Decompose haproxy_add_domain into smaller helper functions
- Consolidate certificate constants (CERTS_DIR, ACME_HOME) to config.py
- Enhance docstrings for internal functions and magic numbers
- Add pytest framework with 48 new tests (269 -> 317 total)
- Increase test coverage from 76% to 86%
  - servers.py: 58% -> 82%
  - certificates.py: 67% -> 86%
  - configuration.py: 69% -> 94%

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kaffa
2026-02-03 12:50:00 +09:00
parent 18ce812920
commit 6bcfee519c
25 changed files with 6852 additions and 125 deletions

View File

@@ -32,6 +32,11 @@ WILDCARDS_MAP_FILE_CONTAINER: str = os.getenv("HAPROXY_WILDCARDS_MAP_FILE_CONTAI
SERVERS_FILE: str = os.getenv("HAPROXY_SERVERS_FILE", "/opt/haproxy/conf/servers.json")
CERTS_FILE: str = os.getenv("HAPROXY_CERTS_FILE", "/opt/haproxy/conf/certificates.json")
# Certificate paths
CERTS_DIR: str = os.getenv("HAPROXY_CERTS_DIR", "/opt/haproxy/certs")
CERTS_DIR_CONTAINER: str = os.getenv("HAPROXY_CERTS_DIR_CONTAINER", "/etc/haproxy/certs")
ACME_HOME: str = os.getenv("ACME_HOME", os.path.expanduser("~/.acme.sh"))
# Pool configuration
POOL_COUNT: int = int(os.getenv("HAPROXY_POOL_COUNT", "100"))
MAX_SLOTS: int = int(os.getenv("HAPROXY_MAX_SLOTS", "10"))
@@ -49,15 +54,37 @@ BACKEND_NAME_PATTERN = re.compile(r'^[a-zA-Z0-9_-]+$')
# Pattern for converting domain to backend name
NON_ALNUM_PATTERN = re.compile(r'[^a-zA-Z0-9]')
# Limits
# Limits and Constants
MAX_RESPONSE_SIZE = 10 * 1024 * 1024 # 10 MB max response from HAProxy
SUBPROCESS_TIMEOUT = 30 # seconds
STARTUP_RETRY_COUNT = 10 # HAProxy ready check retries
STATE_MIN_COLUMNS = 19 # Minimum columns in HAProxy server state output
SOCKET_TIMEOUT = 5 # seconds for HAProxy socket connection
SOCKET_RECV_TIMEOUT = 30 # seconds for HAProxy socket recv loop
MAX_BULK_SERVERS = 10 # Max servers per bulk add call
MAX_SERVERS_JSON_SIZE = 10000 # Max size of servers JSON in haproxy_add_servers
SUBPROCESS_TIMEOUT = 30 # seconds for podman exec commands (config validation, reload)
# STARTUP_RETRY_COUNT: Number of attempts to verify HAProxy is ready on MCP startup.
# During startup, MCP needs to restore server configurations from servers.json.
# HAProxy may take a few seconds to fully initialize the Runtime API socket.
# Each retry waits 1 second, so 10 retries = max 10 seconds startup wait.
# If HAProxy isn't ready after 10 attempts, startup proceeds but logs a warning.
STARTUP_RETRY_COUNT = 10
# STATE_MIN_COLUMNS: Expected minimum column count in 'show servers state' output.
# HAProxy 'show servers state' returns tab-separated values with the following fields:
# 0: be_id - Backend ID
# 1: be_name - Backend name
# 2: srv_id - Server ID
# 3: srv_name - Server name
# 4: srv_addr - Server IP address
# 5: srv_op_state - Operational state (0=stopped, 1=starting, 2=running, etc.)
# 6: srv_admin_state - Admin state (0=ready, 1=drain, 2=maint, etc.)
# 7-17: Various internal state fields (weight, check info, etc.)
# 18: srv_port - Server port
# Total: 19+ columns (may increase in future HAProxy versions)
# Lines with fewer columns are invalid/incomplete and should be skipped.
STATE_MIN_COLUMNS = 19
SOCKET_TIMEOUT = 5 # seconds for HAProxy socket connection establishment
SOCKET_RECV_TIMEOUT = 30 # seconds for complete response (large stats output)
MAX_BULK_SERVERS = 10 # Max servers per bulk add call (prevents oversized requests)
MAX_SERVERS_JSON_SIZE = 10000 # Max size of servers JSON input (10KB, prevents abuse)
# CSV field indices for HAProxy stats (show stat command)