# plugins/host_agent/scheduler.py from __future__ import annotations import logging from datetime import datetime, timedelta, timezone from typing import Iterable from sqlalchemy import select from steward.core.scheduler import ScheduledTask from steward.models.hosts import Host from .models import HostAgentRegistration logger = logging.getLogger(__name__) def _filter_stale( regs: Iterable, *, now: datetime, stale_after_seconds: int, ) -> list: """Pure staleness filter: returns the subset with last_seen_at strictly older than (now - stale_after_seconds). Rows with last_seen_at=None are never stale (they are unregistered-in-practice).""" cutoff = now - timedelta(seconds=stale_after_seconds) return [ r for r in regs if r.last_seen_at is not None and r.last_seen_at < cutoff ] async def find_stale_registrations(app, stale_after_seconds: int = 180) -> list[dict]: async with app.db_sessionmaker() as session: all_regs = (await session.execute( select(HostAgentRegistration) )).scalars().all() stale_rows = _filter_stale( all_regs, now=datetime.now(timezone.utc), stale_after_seconds=stale_after_seconds, ) if not stale_rows: return [] hosts = { h.id: h for h in (await session.execute( select(Host).where(Host.id.in_([r.host_id for r in stale_rows])) )).scalars().all() } return [ { "host_id": r.host_id, "host_name": hosts[r.host_id].name if r.host_id in hosts else "?", "last_seen_at": r.last_seen_at, } for r in stale_rows ] def make_task(app) -> ScheduledTask: async def _check_stale(): try: stale = await find_stale_registrations(app) except Exception: logger.exception("host_agent stale check failed") return if stale: logger.info( "host_agent: %d stale agent(s): %s", len(stale), [s["host_name"] for s in stale], ) return ScheduledTask( name="host_agent_stale_check", coro_factory=_check_stale, interval_seconds=60, run_on_startup=False, )