Files
haproxy-openmanager/backend/models/agent.py
T
Taylan Bakırcıoğlu cb645b5ef9 CRITICAL FIX: Agent Offline Issue - Backend Tolerance for Legacy Agents
🔴 PRODUCTION CRITICAL FIX - Agent HTTP 422 Validation Error

PROBLEM:
- Production agents sending heartbeat with flat system_info fields
- Backend Pydantic model was strict and rejecting unknown fields
- Agents going offline with 'Validation error in request data' (HTTP 422)

ROOT CAUSE:
- Legacy agents embed system_info as flat key-value pairs in heartbeat JSON
- Backend expected only defined fields, rejected extra fields
- No backward compatibility for agent format variations

SOLUTION - BACKEND ONLY (NO AGENT CHANGES):
 Added 'extra = "allow"' to AgentHeartbeat Pydantic Config
 Backend now accepts both formats:
   - Flat format: operating_system, kernel_version, etc. (legacy agents)
   - Nested format: system_info: {...} (future agents)
 Updated comments to clarify backward compatibility

IMPACT:
-  ZERO CHANGES to production agent scripts
-  Existing agents will work immediately after backend deploy
-  Forward compatible with future agent upgrades
-  Tolerant to agent format variations

SAFETY:
- Minimal change (3 lines)
- Pydantic still validates required fields
- Extra fields ignored silently (no breaking changes)
- Production agents continue without restart or upgrade

DEPLOYMENT:
1. Deploy backend (this commit)
2. Agents come online automatically (no action needed)
3. Agent upgrades can happen later (when convenient)

This fix ensures production stability without touching agent scripts.
2025-11-18 21:58:05 +03:00

116 lines
3.7 KiB
Python

from pydantic import BaseModel
from typing import Optional, List, Dict, Any
class AgentCreate(BaseModel):
name: str
pool_id: int
platform: str = "linux"
architecture: str = "amd64"
version: str = "1.0.0"
class AgentRegister(BaseModel):
name: str
pool_id: int
platform: str = "linux"
architecture: str = "amd64"
version: str = "1.0.0"
hostname: Optional[str] = None
ip_address: Optional[str] = None
operating_system: Optional[str] = None
kernel_version: Optional[str] = None
uptime: Optional[int] = None
cpu_count: Optional[int] = None
memory_total: Optional[int] = None # in MB
disk_space: Optional[int] = None # in MB
network_interfaces: Optional[List[str]] = None
capabilities: Optional[List[str]] = None
class AgentHeartbeat(BaseModel):
name: str
status: str = "online"
# Optional system info from agent
hostname: Optional[str] = None
platform: Optional[str] = None
architecture: Optional[str] = None
version: Optional[str] = None # Agent version
cluster_id: Optional[int] = None
# Detailed system information (flat format - for backward compatibility with old agents)
operating_system: Optional[str] = None
kernel_version: Optional[str] = None
uptime: Optional[int] = None # in seconds
cpu_count: Optional[int] = None
memory_total: Optional[int] = None # in bytes
disk_space: Optional[int] = None # in bytes
network_interfaces: Optional[List[str]] = None
capabilities: Optional[List[str]] = None
ip_address: Optional[str] = None
# Optional performance metrics
cpu_usage: Optional[float] = None
memory_usage: Optional[float] = None
disk_usage: Optional[float] = None
load_average: Optional[List[float]] = None
network_io: Optional[Dict[str, Any]] = None
# HAProxy info
haproxy_status: Optional[str] = None
haproxy_version: Optional[str] = None
# Server status from HAProxy stats socket
server_statuses: Optional[Dict[str, Dict[str, str]]] = None # {backend_name: {server_name: status}}
# Full HAProxy stats CSV (base64 encoded) for dashboard metrics
haproxy_stats_csv: Optional[str] = None
# Config sync tracking (agent reports what it successfully applied)
applied_config_version: Optional[str] = None
# System info collected by agent (JSON object - nested format for new agents)
system_info: Optional[Dict[str, Any]] = None
# Other optional fields
last_config_update: Optional[str] = None
errors: Optional[List[str]] = None
class Config:
# Allow extra fields from agents (for forward compatibility and tolerance)
extra = "allow"
class AgentToggle(BaseModel):
enabled: bool
class AgentPoolCreate(BaseModel):
name: str
description: Optional[str] = None
environment: str = "development" # development, staging, production
location: Optional[str] = None
default_config: Optional[Dict[str, Any]] = None
is_active: Optional[bool] = True
class AgentPoolUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
environment: Optional[str] = None
location: Optional[str] = None
default_config: Optional[Dict[str, Any]] = None
is_active: Optional[bool] = None
# Aliases for pool management
PoolCreate = AgentPoolCreate
PoolUpdate = AgentPoolUpdate
class AgentScriptRequest(BaseModel):
platform: str
architecture: str
pool_id: int
cluster_id: int # ✅ FIXED: Added cluster_id field
agent_name: str
hostname_prefix: str
haproxy_bin_path: str
haproxy_config_path: str
stats_socket_path: str
class AgentUpgradeRequest(BaseModel):
agent_id: int