mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 00:50:46 +08:00
Add industrial config sync separate from collection tasks.
Periodic vendor-aware CLI sync stores zlib snapshots in Postgres with dashboard, retry, and config viewer under Network Management. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2fc47e5135
commit
0eead0e662
23 changed files with 2993 additions and 9 deletions
113
netx_api/config_sync_scheduler.py
Normal file
113
netx_api/config_sync_scheduler.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
"""Background scheduler for periodic config sync."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from .config import settings
|
||||
from .config_sync_runner import dispatch_cycle
|
||||
from .config_sync_service import (
|
||||
ensure_policy,
|
||||
expand_targets,
|
||||
has_running_cycle,
|
||||
next_due_at,
|
||||
)
|
||||
from .db import SessionLocal
|
||||
from .models import ConfigSyncCycle, ConfigSyncTask
|
||||
|
||||
_log = logging.getLogger("netx.config_sync.scheduler")
|
||||
_stop = threading.Event()
|
||||
_thread: threading.Thread | None = None
|
||||
|
||||
|
||||
def _utcnow() -> datetime:
|
||||
return datetime.utcnow()
|
||||
|
||||
|
||||
def try_start_scheduled_cycle() -> str | None:
|
||||
"""Create and dispatch a scheduled cycle if policy is due. Returns cycle id or None."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
policy = ensure_policy(db)
|
||||
if not policy.enabled:
|
||||
return None
|
||||
if has_running_cycle(db):
|
||||
return None
|
||||
due = next_due_at(db, policy)
|
||||
if due is not None and due > _utcnow():
|
||||
return None
|
||||
targets = expand_targets(db, policy)
|
||||
if not targets:
|
||||
_log.info("config_sync schedule skip: no targets")
|
||||
return None
|
||||
concurrency = max(1, min(30, int(policy.concurrency or 5)))
|
||||
cycle = ConfigSyncCycle(
|
||||
id=uuid4().hex,
|
||||
trigger_mode="schedule",
|
||||
status="running",
|
||||
concurrency=concurrency,
|
||||
planned_count=len(targets),
|
||||
started_at=_utcnow(),
|
||||
created_at=_utcnow(),
|
||||
)
|
||||
db.add(cycle)
|
||||
db.flush()
|
||||
for t in targets:
|
||||
db.add(
|
||||
ConfigSyncTask(
|
||||
id=uuid4().hex,
|
||||
cycle_id=cycle.id,
|
||||
source=t["source"],
|
||||
target_id=t["id"],
|
||||
ne_name=t.get("ne_name") or "",
|
||||
ne_ip=t.get("ne_ip") or "",
|
||||
vendor=t.get("vendor") or "",
|
||||
status="pending",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
cycle_id = str(cycle.id)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
_log.exception("config_sync schedule create failed")
|
||||
return None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
dispatch_cycle(cycle_id)
|
||||
_log.info("config_sync scheduled cycle started id=%s", cycle_id)
|
||||
return cycle_id
|
||||
|
||||
|
||||
def _loop() -> None:
|
||||
tick = max(15, int(settings.config_sync_scheduler_tick_sec or 60))
|
||||
_log.info("config_sync scheduler started tick=%ss", tick)
|
||||
while not _stop.is_set():
|
||||
try:
|
||||
if bool(settings.config_sync_scheduler_enabled):
|
||||
try_start_scheduled_cycle()
|
||||
except Exception:
|
||||
_log.exception("config_sync scheduler tick failed")
|
||||
_stop.wait(tick)
|
||||
_log.info("config_sync scheduler stopped")
|
||||
|
||||
|
||||
def start_config_sync_scheduler() -> None:
|
||||
global _thread
|
||||
if not bool(settings.config_sync_scheduler_enabled):
|
||||
_log.info("config_sync scheduler disabled by settings")
|
||||
return
|
||||
if _thread and _thread.is_alive():
|
||||
return
|
||||
_stop.clear()
|
||||
_thread = threading.Thread(target=_loop, name="config-sync-scheduler", daemon=True)
|
||||
_thread.start()
|
||||
_log.info("started thread %s alive=%s", _thread.name, _thread.is_alive())
|
||||
|
||||
|
||||
def stop_config_sync_scheduler() -> None:
|
||||
_stop.set()
|
||||
Loading…
Add table
Add a link
Reference in a new issue