Files
ayurishchevandClaude Sonnet 5 bcf8156085 Initial commit: RIPE CIDR/FQDN collector
Collector daemon, FastAPI server (addresses, diff, collect, sources),
SQLite storage with change journal, Docker Compose deployment,
tests, documentation and project rules.

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

7.3 KiB
Raw Permalink Blame History

План: разделение сборщика и API на два процесса

Context

Сейчас планировщик (APScheduler) живёт внутри процесса API: перезапуск или падение API останавливает сбор, а тяжёлый сбор делит процесс с обработкой запросов. Плюс ASN и FQDN можно менять только через CLI и правкой config.json.

Запрос состоит из двух частей, порядок выполнения важен, так как часть B опирается на часть A:

  • Часть A - разделение на два процесса: планируется и выполняется после утверждения плана.
  • Часть B - управление ASN и FQDN через API: только план. Реализация начнётся отдельной командой после части A.

Артефакты по правилам проекта: docs/plan-process-split.md и docs/plan-asn-fqdn-api.md (копии соответствующих частей плана, первым шагом), затем docs/summary-process-split.md (после части A) и обновление README.md.

Часть A. Разделение на два процесса (выполняется)

Архитектура

Два независимых сервиса без IPC, общение через файлы в каталоге проекта (уже атомарные и под flock):

  • API (uvicorn api_server:app): только читает данные и пишет config.json; планировщика в нём больше нет.
  • Сборщик-демон (python collector_daemon.py, новый): держит расписание, запускает сбор, пишет status.json.

Изменение расписания через POST /schedule доходит до демона через config.json: демон раз в 30 секунд сверяет секцию schedule и перепланирует задания без перезапуска (задержка применения до 30 с, документируется).

Изменения

  1. collector_daemon.py (новый)
    • BlockingScheduler, задания asn_job и fqdn_job из schedule (значения по умолчанию как сейчас: 0 2 * * * / 0 3 * * *), max_instances=1, coalesce=True.
    • Задание sync_schedule каждые 30 с: читает load_full_config(), при изменении cron делает reschedule_job; невалидный cron логируется и игнорируется (остаётся прежнее расписание).
    • run_job(name, collector_cls) переезжает из api_server.py (CIDRCollector/FQDNCollector создаются заново на каждый запуск - свежий конфиг); статусы last_run, last_error, next_run пишутся в status.json атомарно вместе с updated_at (heartbeat каждые 30 с).
    • Единственный экземпляр: неблокирующий flock на collector.daemon.lock; второй экземпляр завершается с понятной ошибкой.
    • Корректное завершение по SIGTERM/SIGINT (scheduler.shutdown).
  2. storage.py: добавить try_lock(path) (неблокирующая эксклюзивная блокировка для singleton).
  3. cidr_collector.py: константа STATUS_FILE рядом с остальными путями (единая точка для API и тестов).
  4. api_server.py
    • Убрать BackgroundScheduler, lifespan, job_state, run_*_job, JOBS, start_scheduler.
    • POST /schedule: валидация cron через CronTrigger.from_crontab (как сейчас), запись в config.json под блокировкой; ответ уточняет, что применение демоном до 30 с.
    • GET /health: читает status.json; collector_alive = now - updated_at < 120 с; status = ok только если демон жив и нет last_error, иначе degraded (нет файла = демон не запускался). HTTP-код остаётся 200.
  5. Развёртывание (README.md, .gitignore, .dockerignore)
    • Второй сервис ripe-collector для systemd и OpenRC (та же учётная запись ripe, ExecStart=.../python collector_daemon.py, Restart=always); юниты API и сборщика независимы.
    • Раздел про cron остаётся как альтернатива ручного запуска (cidr_collector.py run), но основной путь - демон.
    • Примечание об обновлении: после разделения одного ripe-api недостаточно, без ripe-collector сбор не идёт; /health покажет degraded.
    • Переписать раздел «Scheduler Logic» под новую схему; добавить status.json в .gitignore и .dockerignore.

Тесты (минимум, в контейнере)

  1. sync_schedule: смена cron в config.json перепланирует задание; невалидный cron игнорируется (планировщик без запуска, проверка триггера задания).
  2. /health: свежий status.json -> collector_alive: true, устаревший или отсутствующий -> false и degraded.

Проверка

  1. docker build -f Dockerfile.test -t ripe-collector-test . && docker run --rm ripe-collector-test - все тесты (прежние 9 + новые) проходят.
  2. Вручную на копии данных в scratchpad (реальные данные не трогаем): запустить демон с расписанием */1 * * * * и API в двух процессах; убедиться, что сбор идёт без API; POST /schedule с токеном меняет расписание демона не позднее чем через 30 с (по логу и /health); остановка демона -> /health через 2 минуты degraded, collector_alive: false; второй запуск демона завершается с ошибкой singleton; kill -TERM останавливает демон чисто.
  3. Перезапуск API во время работы демона не прерывает сбор.

Критичные файлы

collector_daemon.py (новый), api_server.py, cidr_collector.py, storage.py, README.md, .gitignore, .dockerignore, tests/. Переиспользуем: load_full_config, save_json_atomic, file_lock, load_json, verify_token, merge_entry.

Порядок выполнения после утверждения

  1. Скопировать планы в docs/.
  2. Выполнить часть A (код, тесты, проверка, README, docs/summary-process-split.md).
  3. Часть B не реализуется, пока не будет отдельной команды.