refactor: Modularize MCP server with command batching
- Split monolithic mcp/server.py (1874 lines) into haproxy_mcp/ package:
- config.py: Configuration constants and environment variables
- exceptions.py: Custom exception classes
- validation.py: Input validation functions
- haproxy_client.py: HAProxy Runtime API client with batch support
- file_ops.py: Atomic file operations with locking
- utils.py: CSV parsing utilities
- tools/: MCP tools organized by function
- domains.py: Domain management (3 tools)
- servers.py: Server management (7 tools)
- health.py: Health checks (3 tools)
- monitoring.py: Monitoring (4 tools)
- configuration.py: Config management (4 tools)
- Add haproxy_cmd_batch() for sending multiple commands in single TCP connection
- Optimize server operations: 1 connection instead of 2 per server
- Optimize startup restore: All servers in 1 connection (was 2×N)
- Update type hints to Python 3.9+ style (built-in generics)
- Remove unused imports and functions
- Update CLAUDE.md with new structure and performance notes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
81
haproxy_mcp/config.py
Normal file
81
haproxy_mcp/config.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""Configuration constants and environment variables for HAProxy MCP Server."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
# Configure structured logging
|
||||
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, log_level, logging.INFO),
|
||||
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger("haproxy_mcp")
|
||||
|
||||
# MCP Server configuration
|
||||
MCP_HOST: str = os.getenv("MCP_HOST", "0.0.0.0")
|
||||
MCP_PORT: int = int(os.getenv("MCP_PORT", "8000"))
|
||||
|
||||
# HAProxy Runtime API configuration
|
||||
HAPROXY_HOST: str = os.getenv("HAPROXY_HOST", "localhost")
|
||||
HAPROXY_PORT: int = int(os.getenv("HAPROXY_PORT", "9999"))
|
||||
HAPROXY_SOCKET: tuple[str, int] = (HAPROXY_HOST, HAPROXY_PORT)
|
||||
|
||||
# File paths (configurable via environment)
|
||||
STATE_FILE: str = os.getenv("HAPROXY_STATE_FILE", "/opt/haproxy/data/servers.state")
|
||||
MAP_FILE: str = os.getenv("HAPROXY_MAP_FILE", "/opt/haproxy/conf/domains.map")
|
||||
MAP_FILE_CONTAINER: str = os.getenv("HAPROXY_MAP_FILE_CONTAINER", "/usr/local/etc/haproxy/domains.map")
|
||||
SERVERS_FILE: str = os.getenv("HAPROXY_SERVERS_FILE", "/opt/haproxy/conf/servers.json")
|
||||
|
||||
# Pool configuration
|
||||
POOL_COUNT: int = int(os.getenv("HAPROXY_POOL_COUNT", "100"))
|
||||
MAX_SLOTS: int = int(os.getenv("HAPROXY_MAX_SLOTS", "10"))
|
||||
|
||||
# Container configuration
|
||||
HAPROXY_CONTAINER: str = os.getenv("HAPROXY_CONTAINER", "haproxy")
|
||||
|
||||
# Validation patterns - compiled once for performance
|
||||
DOMAIN_PATTERN = re.compile(
|
||||
r'^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?'
|
||||
r'(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$'
|
||||
)
|
||||
# Backend/server names: alphanumeric, underscore, hyphen only
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
# CSV field indices for HAProxy stats (show stat command)
|
||||
class StatField:
|
||||
"""HAProxy CSV stat field indices."""
|
||||
PXNAME = 0 # Proxy name (frontend/backend)
|
||||
SVNAME = 1 # Server name (or FRONTEND/BACKEND)
|
||||
SCUR = 4 # Current sessions
|
||||
SMAX = 6 # Max sessions
|
||||
STATUS = 17 # Status (UP/DOWN/MAINT/etc)
|
||||
WEIGHT = 18 # Server weight
|
||||
CHECK_STATUS = 36 # Check status
|
||||
|
||||
|
||||
# Field indices for HAProxy server state (show servers state command)
|
||||
class StateField:
|
||||
"""HAProxy server state field indices."""
|
||||
BE_ID = 0 # Backend ID
|
||||
BE_NAME = 1 # Backend name
|
||||
SRV_ID = 2 # Server ID
|
||||
SRV_NAME = 3 # Server name
|
||||
SRV_ADDR = 4 # Server address
|
||||
SRV_OP_STATE = 5 # Operational state
|
||||
SRV_ADMIN_STATE = 6 # Admin state
|
||||
SRV_PORT = 18 # Server port
|
||||
Reference in New Issue
Block a user