Merge pull request #269 from Pouzor/fix/proxmox-autosync-scan-history
fix: record ScanRun for scheduled Proxmox auto-sync
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
Reference in New Issue
Block a user