Files
ros_control/app/services/poller.py
T
ayurishchevandClaude Sonnet 5 4c1841b61b ros_control: централизованное управление парком MikroTik RouterOS
Control Server (FastAPI) с WEB UI (Jinja2 + HTMX) и JSON API для группового
администрирования устройств RouterOS 7 через REST.

- Устройства: список, статус (модель, канал, версии ROS/FW, uptime, доступные
  обновления), примечания, пароли шифруются (Fernet); фоновый опрос каждые 30 с
  и быстрое обнаружение недоступности (таймаут соединения 4 с).
- Группы устройств и фильтры (устройства, резервные копии).
- Резервные копии: .backup и .rsc (show-sensitive) создаются через REST,
  скачиваются сервером и загружаются в S3 (Yandex Object Storage).
- Обновление ROS/FW и выбор канала, групповые операции задачами.
- Интерфейс по утверждённому макету: светлая/тёмная темы, окна, шрифты IBM Plex
  локально. Docker Compose, SQLite в томе, минимальный набор тестов.
- Планы и итоги каждого изменения — в docs/changes/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:13:29 +03:00

46 lines
1.7 KiB
Python

"""Фоновый опрос устройств: быстрое обнаружение недоступности (period = POLL_INTERVAL)."""
import asyncio
import logging
import time
from app.config import get_settings
from app.services import devices, ops
log = logging.getLogger("ros_control.poller")
async def cycle(last_full: dict[int, float]) -> None:
"""Один проход по всем устройствам (параллельно, не более MAX_CONCURRENCY одновременно)."""
s = get_settings()
ids = [d.id for d in devices.list_devices()]
for gone in set(last_full) - set(ids):
del last_full[gone]
sem = asyncio.Semaphore(s.max_concurrency)
started = time.monotonic()
async def one(device_id: int) -> None:
async with sem:
full = started - last_full.get(device_id, float("-inf")) >= s.update_check_interval
try:
await ops.poll_device(device_id, full)
except LookupError: # удалили во время цикла
return
except Exception: # noqa: BLE001 — сбой одного устройства не должен ронять опрос остальных
log.exception("poll failed: device %s", device_id)
return
if full:
last_full[device_id] = time.monotonic()
await asyncio.gather(*(one(i) for i in ids))
async def run() -> None:
last_full: dict[int, float] = {}
interval = get_settings().poll_interval
while True:
try:
await cycle(last_full)
except Exception: # noqa: BLE001
log.exception("poll cycle failed")
await asyncio.sleep(interval)