Merge pull request #269 from Pouzor/fix/proxmox-autosync-scan-history

fix: record ScanRun for scheduled Proxmox auto-sync
This commit is contained in:
Pouzor - Rémy Jardient
2026-07-10 13:42:16 +02:00
committed by GitHub
2 changed files with 46 additions and 47 deletions
+27 -18
View File
@@ -107,32 +107,41 @@ async def _run_service_checks() -> None:
async def _run_proxmox_sync() -> None:
"""Fetch the Proxmox inventory and upsert it into pending (auto-sync)."""
"""Fetch the Proxmox inventory and upsert it into pending (auto-sync).
Records a ScanRun (kind=proxmox) so the scheduled sync shows in Scan
history, exactly like the manual /sync-now and /import-pending paths.
"""
if not settings.proxmox_sync_enabled:
return
if not (settings.proxmox_host and settings.proxmox_token_id and settings.proxmox_token_secret):
logger.warning("Proxmox auto-sync enabled but host/token not configured — skipping")
return
# Lazy import to avoid a circular import at module load.
from app.api.routes.proxmox import _persist_pending_import
from app.services.proxmox_service import fetch_proxmox_inventory
from app.api.routes.proxmox import _background_proxmox_import
from app.db.models import ScanRun
try:
nodes_raw, edges_raw = await fetch_proxmox_inventory(
host=settings.proxmox_host,
port=settings.proxmox_port,
token_id=settings.proxmox_token_id,
token_secret=settings.proxmox_token_secret,
verify_tls=settings.proxmox_verify_tls,
async with AsyncSessionLocal() as db:
run = ScanRun(
status="running",
kind="proxmox",
ranges=[f"{settings.proxmox_host}:{settings.proxmox_port}"],
)
async with AsyncSessionLocal() as db:
result = await _persist_pending_import(db, nodes_raw, edges_raw)
logger.info(
"Proxmox auto-sync: %d devices (%d new, %d updated)",
result.device_count, result.pending_created, result.pending_updated,
)
except Exception as exc:
logger.error("Proxmox auto-sync failed: %s", exc)
db.add(run)
await db.commit()
await db.refresh(run)
run_id = run.id
# Shares the manual-sync flow: fetch + persist + mark the run done/error +
# broadcast the inventory-reload signal.
await _background_proxmox_import(
run_id,
settings.proxmox_host,
settings.proxmox_port,
settings.proxmox_token_id,
settings.proxmox_token_secret,
settings.proxmox_verify_tls,
)
def _add_service_check_job() -> None:
+19 -29
View File
@@ -313,44 +313,34 @@ def _proxmox_settings(mock_settings):
@pytest.mark.asyncio
async def test_run_proxmox_sync_persists_inventory():
db = MagicMock()
session_ctx = MagicMock()
session_ctx.__aenter__ = AsyncMock(return_value=db)
session_ctx.__aexit__ = AsyncMock(return_value=False)
result = MagicMock(device_count=3, pending_created=2, pending_updated=1)
async def test_run_proxmox_sync_records_scan_run(mem_db):
"""Auto-sync must create a ScanRun (kind=proxmox) so it shows in Scan
history, then delegate to the shared background import with the run id."""
from app.db.models import ScanRun
with (
patch("app.core.scheduler.settings") as mock_settings,
patch("app.core.scheduler.AsyncSessionLocal", MagicMock(return_value=session_ctx)),
patch("app.core.scheduler.AsyncSessionLocal", mem_db),
patch(
"app.services.proxmox_service.fetch_proxmox_inventory",
"app.api.routes.proxmox._background_proxmox_import",
new_callable=AsyncMock,
return_value=(["node-raw"], ["edge-raw"]),
) as mock_fetch,
patch(
"app.api.routes.proxmox._persist_pending_import",
new_callable=AsyncMock,
return_value=result,
) as mock_persist,
) as mock_bg,
):
_proxmox_settings(mock_settings)
await _run_proxmox_sync()
mock_fetch.assert_awaited_once()
mock_persist.assert_awaited_once_with(db, ["node-raw"], ["edge-raw"])
# A ScanRun row exists — the missing scan-history trace.
async with mem_db() as db:
from sqlalchemy import select
run = (await db.execute(select(ScanRun))).scalars().one()
assert run.kind == "proxmox"
assert run.ranges == ["pve:8006"]
@pytest.mark.asyncio
async def test_run_proxmox_sync_logs_and_swallows_errors():
with (
patch("app.core.scheduler.settings") as mock_settings,
patch(
"app.services.proxmox_service.fetch_proxmox_inventory",
new_callable=AsyncMock,
side_effect=RuntimeError("proxmox api down"),
),
):
_proxmox_settings(mock_settings)
await _run_proxmox_sync() # exception is logged, never propagated
# Delegated to the same background flow the manual /sync-now uses, passing
# the run id + env connection settings.
mock_bg.assert_awaited_once_with(
run.id, "pve", 8006, "root@pam!tok", "secret", False,
)
# --- reschedule_* validation and not-running guards ---