mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-21 01:53:24 +00:00
fix(backend): Inactive APPLIED entities incorrectly showing as pending
CRITICAL FIX: has_pending_config calculation for both backends and frontends
Problem Found (Console Log):
Backend/Frontend: {
last_config_status: 'APPLIED', <- Already applied!
has_pending_config: true, <- But flag is TRUE!
is_active: false <- Inactive (soft-deleted)
}
Apply response: 'No pending changes to apply'
Reject response: 'No pending changes found to reject'
Result: Entity stuck in Apply Management forever!
Root Cause:
OLD LOGIC (backend.py line 438, frontend.py line 363):
has_pending_config = (config_version OR status=PENDING OR is_inactive) AND NOT rejected
For inactive + APPLIED entity:
- has_config_version: FALSE
- has_pending_status: FALSE (status=APPLIED)
- is_inactive: TRUE
- Result: TRUE (incorrectly marked as pending!)
Why It Matters:
- Design: Inactive entities should show as pending for soft-delete workflow
- Problem: Entity with is_active=FALSE + last_config_status='APPLIED' = soft-delete already applied!
- Apply/Reject: Both look for PENDING status, find none, do nothing
- UI: Entity remains in Apply Management (has_pending_config=true forever)
Solution (Both backend.py and frontend.py):
Inactive entity is only pending if last_config_status is PENDING, not APPLIED:
OLD: is_inactive → always pending
NEW: (is_inactive AND status=PENDING) → only pending if not yet applied
Formula:
has_pending_config = (
config_version OR
status=PENDING OR
(is_inactive AND status=PENDING)
) AND NOT rejected AND NOT (is_inactive AND is_applied)
Test Cases:
✅ Active entity, PENDING → pending=TRUE
✅ Active entity, APPLIED → pending=FALSE
✅ Inactive entity, PENDING → pending=TRUE (soft-delete needs apply)
✅ Inactive entity, APPLIED → pending=FALSE (soft-delete already applied) <- FIXED!
✅ Inactive entity, REJECTED → pending=FALSE
Backend.py Changes (lines 426-445):
- Added is_applied flag
- Added is_inactive_and_pending logic
- Updated has_pending_config formula
- Comprehensive comments
Frontend.py Changes (lines 359-370):
- Same logic as backend for consistency
- Inline expression (no loop variables)
- Comprehensive comments
Impact:
- 'deneme-sil' backend will show has_pending_config=FALSE
- Backend/Frontend will disappear from Apply Management
- No more stuck entities after soft-delete apply
- Agent sync race condition protected (inactive+APPLIED=not pending)
- Consistent behavior across all entity types
Related: c120445 (apply endpoint fix), f80c026 (debug logs)
Refs: #has-pending-config #inactive-entity #apply-management #consistency
This commit is contained in:
committed by
taylanbakircioglu
parent
60b77734a5
commit
d6ee9517d0
@@ -428,14 +428,21 @@ async def get_backends(cluster_id: Optional[int] = None, include_inactive: bool
|
||||
# Check if backend has pending changes via:
|
||||
# 1. Config versions (pending_backend_ids) - version name parsing
|
||||
# 2. Entity's own last_config_status (for bulk import and other operations)
|
||||
# 3. Backend is inactive (soft delete) - BUT NOT if REJECTED
|
||||
# 3. Backend is inactive (soft delete) AND pending - NOT if already APPLIED
|
||||
has_config_version = backend["id"] in pending_backend_ids
|
||||
has_pending_status = backend.get("last_config_status") == "PENDING"
|
||||
is_inactive = not backend.get("is_active", True)
|
||||
is_rejected = backend.get("last_config_status") == "REJECTED"
|
||||
is_applied = backend.get("last_config_status") == "APPLIED"
|
||||
|
||||
# CRITICAL: Exclude REJECTED entities from pending (already rejected, no action needed)
|
||||
backend["has_pending_config"] = (has_config_version or has_pending_status or is_inactive) and not is_rejected
|
||||
# CRITICAL FIX: Inactive backend should only be pending if not already APPLIED/REJECTED
|
||||
# Problem: Backend with is_active=FALSE and last_config_status='APPLIED' was showing as pending
|
||||
# Result: Apply/Reject couldn't process it (no PENDING status), but UI kept showing it
|
||||
# Solution: Inactive backend is only pending if last_config_status is PENDING
|
||||
is_inactive_and_pending = is_inactive and has_pending_status
|
||||
|
||||
# CRITICAL: Exclude REJECTED and APPLIED inactive entities from pending
|
||||
backend["has_pending_config"] = (has_config_version or has_pending_status or is_inactive_and_pending) and not is_rejected and not (is_inactive and is_applied)
|
||||
|
||||
# Enhanced debug logging
|
||||
if backend['name'] == 'backend7' or has_config_version or has_pending_status:
|
||||
|
||||
@@ -357,12 +357,16 @@ async def get_frontends(cluster_id: Optional[int] = None, include_inactive: bool
|
||||
"updated_at": f["updated_at"].isoformat().replace('+00:00', 'Z') if f["updated_at"] else None,
|
||||
"cluster_id": f.get("cluster_id"),
|
||||
"has_pending_config": (
|
||||
# CRITICAL FIX: Same as backend.py logic for consistency
|
||||
# Problem: Frontend with is_active=FALSE and last_config_status='APPLIED' was showing as pending
|
||||
# Solution: Inactive frontend is only pending if last_config_status is PENDING, not APPLIED
|
||||
(
|
||||
f["id"] in pending_frontend_ids or
|
||||
f.get("last_config_status") == "PENDING" or
|
||||
not f.get("is_active", True)
|
||||
(f["id"] in pending_frontend_ids) or
|
||||
(f.get("last_config_status") == "PENDING") or
|
||||
(not f.get("is_active", True) and f.get("last_config_status") == "PENDING")
|
||||
) and
|
||||
f.get("last_config_status") != "REJECTED" # Exclude REJECTED entities
|
||||
(f.get("last_config_status") != "REJECTED") and
|
||||
not (not f.get("is_active", True) and f.get("last_config_status") == "APPLIED")
|
||||
)
|
||||
} for f in frontends
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user