Files
cloud-ip-validator/docs/changes/2026-10-03_16-24_registry-egress-ingress-levels-plan.md
T
ayurishchevandClaude Sonnet 5.5 864208238f Show egress/ingress levels in the registry; freeze checks at the verdict
Registry: the "last result" column now also shows, per level (egress,
ingress), how many of the recorded checks of the latest cycle succeeded, split
by check family (tcp-22 and tcp-443 are both "tcp"). One grouped query per
chunk of addresses; new fields last_cycle_id, egress, ingress in
GET /admin/registry; the dashboard renders them under the verdict.

Verdict integrity (migration 0010):
- the prober is handed an address once per site and attempt, not on every
  poll, so results are no longer overwritten by later probe rounds;
- UpsertCheckIfOpen refuses writes once the address is aggregating or has its
  verdict, or for an older attempt; senders get {"ok":true,"ignored":N} and a
  result_dropped event is recorded;
- the checking window counts from checking_started_at, not from assigned_at;
- checks.recorded_at (server clock) and checks.after_verdict (flag for rows
  written after the verdict in existing data);
- the verdict rule is a pure function (computeVerdict) and the aggregated
  event carries the egress/ingress check counts.

Rebuilt bin/control-api and bin/admin-dashboard to match. Plans and summaries
are in docs/changes; README, API, USAGE, DASHBOARD and DIAGRAMS are updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 17:59:52 +03:00

8.9 KiB
Raw Blame History

План: уровни Egress / Ingress в таблице «Реестр»

Контекст

Колонка «Последний результат» в «Реестре» показывает одну пилюлю общего вердикта (pass / partial / fail / cancelled). Для аналитики этого мало: не видно, где именно сбой — на выходе (egress) или на входе (ingress) — и какой тип проверки упал.

Нужно разделить результат на два уровня и показать «успешно из всего» (например, «5 из 5») по каждому уровню, с разбивкой по типу проверки (icmp, ssh, tcp, https, tls и новые типы, когда появятся).

Решения пользователя:

  • Пилюля общего вердикта остаётся, уровни добавляются под ней. Фильтр по вердикту не меняется.
  • «Всего» = число записанных проверок последнего цикла (не ожидаемое по настройкам).

Что уже есть (по графу и коду)

  • Проверки лежат в checks (internal/db/migrations/0007_ip_registry.sql:39-65). Уровень задаёт source: egress или inbound-site-N (internal/db/models.go:39,68-72). Тип — в check_type: https, icmp, ssh, tcp-<порт>, tls-<порт>.
  • Индекс idx_checks_registry_cycle(registry_id, cycle_id) уже есть. Миграция не нужна.
  • Цепочка данных: fillRegistrySummary (internal/db/queries_registry.go:199) → registrySummaryToDTO (internal/httpapi/handlers_registry.go:75) → registryDTO (internal/httpapi/dto_admin.go:87) → клиент дашборда → registryItem (internal/dashboard/dto.go:258) → шаблон internal/dashboard/templates/registry.html (registry_table, строки 69–101).
  • Дашборд ходит в БД только через HTTP control-api. Менять нужно оба слоя.

Правила расчёта

  • Цикл: максимальный cycle_id в checks для адреса (так же, как сейчас считается LastCheckedAt).
  • Уровень: source = 'egress' → Egress; source LIKE 'inbound-site-%' → Ingress. Прочее игнорируется.
  • Тип: часть check_type до первого - (tcp-22, tcp-443 → tcp; tls-443 → tls; ssh, icmp, https без изменений). Новый тип попадает в разбивку сам, без правок кода.
  • Ingress: считаются проверки всех площадок вместе (площадок может быть любое число).
  • Правило разбора живёт в одном месте: две функции в internal/db/models.go рядом с InboundSource: CheckLevel(source) и CheckFamily(checkType).
  • Группировка делается в Go, не в SQL: SQL отдаёт GROUP BY registry_id, source, check_type, и это не больше ~30 строк на адрес.

Изменения

1. БД (internal/db)

  • models.go: типы TypeStat{Type, Total, OK} и LevelResult{Total, OK, ByType []TypeStat} (по типам — в алфавитном порядке). Функции CheckLevel, CheckFamily.
  • queries_registry.go: в RegistrySummary добавить LastCycleID, Egress, Ingress. Один пакетный запрос на страницу вместо запроса на каждый адрес:
    SELECT c.registry_id, c.source, c.check_type, COUNT(*), SUM(c.success)
    FROM checks c
    JOIN (SELECT registry_id, MAX(cycle_id) AS cid FROM checks
          WHERE registry_id IN (…) GROUP BY registry_id) m
      ON m.registry_id = c.registry_id AND m.cid = c.cycle_id
    GROUP BY c.registry_id, c.source, c.check_type
    
    Новая функция заполняет сводки списком адресов (порциями по 500). Её вызывают ListRegistryPage, ListRegistry и GetRegistryByAddress. Это заодно убирает N+1 для новых полей.
  • lastResultCond и fillRegistrySummary (вердикт) не меняются.

2. HTTP API (internal/httpapi)

  • dto_admin.go: в registryDTO добавить last_cycle_id, egress, ingress как {total, ok, by_type: [{type, total, ok}]}. Изменение только добавляет поля, старые клиенты не ломаются.
  • handlers_registry.go: registrySummaryToDTO заполняет новые поля.
  • docs/API.md (раздел «Реестр адресов», ~645–668): новые поля и пример JSON.

3. Дашборд (internal/dashboard)

  • dto.go: registryItem получает те же поля.
  • templates/registry.html: в ячейке «Последний результат» сверху пилюля вердикта, ниже две строки: Egress 5 из 5 и Ingress 3 из 4, под каждой чипы по типам: https 3 из 3, icmp 2 из 2. Цвет: все успешны — зелёный, ни одной — красный, иначе жёлтый. Нет проверок — «—». В title ячейки: «по записанным проверкам цикла N; вердикт учитывает недостающие результаты». Сохранить data-label для мобильной раскладки.
  • static/dashboard.css (~417–423): стили строки уровня и чипа по образцу pill-*.
  • Страница деталей registry_detail.html не меняется (вне объёма).

4. Тесты

  • internal/db/queries_registry_test.go: новые тесты — разбивка egress/ingress по типам; tcp-22 и tcp-443 сливаются в tcp; tls, ssh; новый тип (например dns) появляется в разбивке; адрес без проверок даёт нули; берётся последний цикл; работает после удаления строки очереди; несколько адресов в одном пакетном запросе. Добавить хелпер с ingress-проверками рядом с finishWithChecks (queries_scale_test.go:24).
  • internal/httpapi/handlers_scale_test.go (TestAdminRegistryPaginationFiltersAndCompat): проверить новые поля в ответе.
  • internal/dashboard/handlers_test.go (TestRegistryPageAndDetail): фикстура с уровнями, проверка вывода «5 из 5» и имён типов.
  • TestScaleSmoke6440 (queries_scale_test.go:356): добавить ingress-проверки в засев, лимит 10 с сохраняется.

Порядок работы (по правилам проекта)

  1. Скопировать этот план в docs/changes/2026-10-03_<ЧЧ-ММ>_registry-egress-ingress-levels-plan.md.
  2. Реализовать п.1 → п.2 → п.3 → п.4.
  3. Написать docs/changes/…-summary.md (что изменено).
  4. Обновить README.md и docs/API.md/docs/USAGE.md, где описан реестр.
  5. Обновить граф: /graphify . --update.

Проверка

  • go build ./... && go vet ./... && go test ./...
  • EXPLAIN QUERY PLAN пакетного запроса: должен идти по idx_checks_registry_cycle, без полного перебора checks.
  • Локальный стенд (scripts/run-local-e2e.sh, docs/LOCAL_E2E.md): прогнать цикл, открыть /registry, сверить «N из M» по уровням и типам с таблицей на странице деталей /registry/<ip>.
  • Страница на 6440 адресов отвечает за время порядка прежних 0,12 с (допустим рост в разы, но не секунды).

Ограничения и риски

  • «N из M» считается по записанным проверкам. Если результатов не хватает, вердикт может быть хуже, чем «N из M» (например, все записанные успешны, а вердикт partial). Это показано подсказкой.
  • Пока идёт цикл, счётчики отражают частично пришедшие проверки текущего цикла. Состояние цикла видно в колонке состояния.
  • Фильтра и сортировки по уровням нет: сейчас вне объёма. Если понадобятся для аналитики, это отдельное изменение.