The "Scan Floating IP" button failed with a client timeout: the project now holds ~6.4k floating IPs and the scan listed them all in one unpaginated, timeout-less Neutron request on the HTTP request context. openstack: ListFreeFloatingIPs reads marker-based pages (fields= keeps them small) with per-page retry/backoff on transport errors, 5xx and 429, and every request now has a timeout (also ends hangs inside the orchestrator tick). orchestrator: the scan is a single-flight background job on the process context with progress (clearing/listing/enqueuing/done/error), dry_run, full discovery before anything is enqueued, then SubmitIPs in chunks of 500 in ascending IP order; a failed read leaves the queue untouched. The auto-cycle gets a "scanning" phase that polls the job, so the control loop and autoCycleMu are never held across OpenStack/DB work; it recovers after a restart and waits for (instead of adopting) a scan started by someone else. db: migration 0009 (indexes), paged ListIPsPage/ListRegistryPage, GROUP BY counters, EXISTS completion check, set-based ClearAllIPs. API: POST /admin/ips/scan -> 202 (dry_run, wait), GET /admin/ips/scan, paging and filters on /admin/ips and /admin/registry (bare arrays without limit), results_by_overall in /admin/status. dashboard: scan progress panel and dry-run button, paginated /ips and /registry with server-side filters, Overview on counters and capped lists with progress/ETA, "select all N by filter", hx-params fix for per-row buttons, real counts in confirmations. Also: docs (API, USAGE, DASHBOARD, README), plan and review under docs/changes/, bin/ rebuilt with new SHA256SUMS. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
31 KiB
Cloud IP Validator
Система проверки освобождённых публичных IPv4-адресов перед их повторной выдачей. Каждый адрес временно привязывается как Floating IP
к ВМ-валидатору в облаке (OpenStack) и проверяется в двух направлениях одновременно: исходящий трафик валидатора (egress: HTTPS/ICMP/опционально SSH
до внешних целей) и входящая доступность самого адреса с нескольких независимых внешних площадок (inbound: TCP 22/80/443/8080 + ICMP).
Итог по каждому адресу — pass/partial/fail; полная история проверок сохраняется в реестре даже после удаления адреса из очереди.
Быстрый старт
go build ./... && go test ./... # сборка и юнит-тесты
scripts/run-local-e2e.sh # офлайн-прогон всей системы: mock OpenStack, без облака и интернета
cd deploy/docker
cp .env.example .env
cp control-api/control-api.docker.example.yaml control-api/control-api.docker.yaml
docker compose up -d --build # весь стенд на одной машине в mock-режиме (профили control-plane, dashboard, prober, validator)
- UI:
http://<хост>:8090/, API:http://<хост>:8080/api/v1, проверка живости:GET /healthz. - Docker-стенд работает с
openstack.mode: mockи тестовыми адресами203.0.113.10–12; реальный OpenStack и учётные данные не нужны. - Реальный стенд (systemd или Docker на нескольких хостах) — по шагам в docs/SETUP.md.
Конфигурация
Каждый компонент читает свой YAML (примеры — configs/*.example.yaml). Учётные данные OpenStack в YAML не хранятся — только имена переменных окружения.
control-api (control-api.yaml)
| Ключ | Назначение |
|---|---|
server.listen_addr |
Адрес API (:8080) |
database.path |
Файл SQLite (/var/lib/cloud-ip-validator/control-api.db) |
openstack.mode |
real или mock (встроенная заглушка OpenStack для разработки и тестов) |
openstack.auth_method |
token (готовый токен проекта из OS_TOKEN, сам не обновляется) или password (логин/пароль Keystone, токен перевыпускается автоматически) |
openstack.*_env |
Имена переменных окружения: OS_AUTH_URL, OS_PROJECT_ID, OS_REGION_NAME, OS_INTERFACE, OS_TOKEN либо OS_USERNAME/OS_USER_DOMAIN_NAME/OS_PASSWORD |
orchestrator.poll_interval_seconds |
Период такта оркестратора (5) |
orchestrator.self_check_timeout_seconds, max_self_check_retries |
Ожидание self-check валидатора (60) и число его повторов (3) |
orchestrator.checking_window_seconds |
Сколько ждать результаты проверок, прежде чем подвести итог (120) |
orchestrator.lease_ttl_seconds, max_retries |
Лизинг адреса за валидатором (180) и число возвратов в очередь при его истечении (3) |
orchestrator.heartbeat_timeout_seconds |
После скольких секунд тишины валидатор или площадка считаются потерянными (30) |
orchestrator.fip_settle_seconds |
Только начальное значение: пауза между привязкой FIP и self-check; далее управляется на лету. Должно выполняться fip_settle_seconds + self_check_timeout_seconds < lease_ttl_seconds |
orchestrator.fip_scan_interval_seconds |
Периодический скан Floating IP (0 — выключен). Не выполняется, пока включён автоматический цикл |
orchestrator.fip_scan_timeout_seconds |
Предел всего сканирования Floating IP (1800) |
openstack.list_page_size, list_page_retries, request_timeout_seconds |
Скан читает Floating IP страницами (200), повторяя страницу при обрыве/5xx/429 (5 раз); таймаут одного запроса к OpenStack (60 с) |
auth.admin_token_env, auth.agent_token_env |
Имена переменных окружения с токеном администратора (CONTROL_API_ADMIN_TOKEN) и токеном агентов (CONTROL_API_AGENT_TOKEN). Значения в YAML не хранятся; пустой токен — соответствующий уровень API открыт (с предупреждением в логе) |
aggregation.missing_counts_as_fail |
Отсутствие ответа источника засчитывается как провал (true) |
validators, sites, check_types, targets, inbound_checks |
Начальная загрузка пустой БД: валидаторы (validator_id + os_port_id), внешние площадки, типы и цели egress-проверок, порты inbound-проверок. Дальше источник истины — БД, правки через API/UI |
ip_addresses |
Адреса, которые доливаются в очередь при каждом старте (только новые) |
Остальные компоненты
| Компонент | Ключи |
|---|---|
validator-agent |
validator_id, control_api_url, control_api_token_env (CONTROL_API_AGENT_TOKEN), poll_interval_seconds, self_check.* (таймаут, ip_echo_urls), checks.* (таймауты HTTPS/ICMP, число ICMP-пакетов, ssh.*) |
prober |
site_id, control_api_url, control_api_token_env (CONTROL_API_AGENT_TOKEN), poll_interval_seconds, checks.* (таймауты TCP/ICMP, число ICMP-пакетов) |
admin-dashboard |
server.listen_addr (:8090), control_api.base_url, control_api.timeout_seconds, control_api.token_env (ADMIN_DASHBOARD_CONTROL_API_TOKEN), auth.username_env / password_env / session_secret_env (ADMIN_DASHBOARD_USERNAME / _PASSWORD / _SESSION_SECRET), auth.session_ttl_minutes (480), overview.last_completed_count (20), overview.poll_interval_seconds (5) |
Секреты (токены, пароль дашборда, ключ сессии) задаются только переменными окружения; генерация — openssl rand -hex 32. Подробности и порядок включения — docs/SETUP.md.
Настройки, меняющиеся на лету (пауза перед self-check, глубина истории, типы inbound-проверок, автоматический цикл, валидаторы, площадки, цели), хранятся в БД и правятся через API или страницу /settings.
Архитектура
| Слой | Технологии |
|---|---|
| Backend | Go 1.26, net/http (маршруты Go 1.22), SQLite (чистый Go-драйвер modernc.org/sqlite, WAL, одно соединение), встроенные миграции 0001–0008 |
| Облако | Интерфейс FloatingIPClient: реальный клиент OpenStack Neutron и MockClient |
| UI | Серверный рендеринг (Go-шаблоны) + htmx и Alpine.js, шрифты и скрипты лежат в репозитории, внешних зависимостей нет |
| Сборка | Статические бинарники (CGO_ENABLED=0) в bin/, Docker-образы, systemd-юниты |
| Компонент | Роль | Состояние | Где работает |
|---|---|---|---|
control-api |
Ведёт очередь адресов и реестр их истории, назначает валидаторов, агрегирует результаты. Единственная точка, с которой общаются все остальные компоненты | Хранит (SQLite) | Одна управляющая машина |
validator-agent |
Привязывает к себе выданный Floating IP и прогоняет egress-проверки до внешних целей | Без состояния | Каждая ВМ-валидатор в облаке |
prober |
Проверяет входящую доступность адреса снаружи (TCP/ICMP) — с независимой от облака сети | Без состояния | Каждая внешняя тестовая площадка |
admin-dashboard |
Браузерная админ-панель — то же, что API control-api, но графически. Опционален |
Без состояния | Любая машина с доступом к control-api |
Все четыре общаются только через HTTP API control-api. Pull-модель: validator-agent и prober сами опрашивают control-api, он к ним не обращается.
Схемы control/data plane — docs/DIAGRAMS.md.
cmd/ control-api validator-agent prober admin-dashboard
internal/ config db orchestrator httpapi openstack dashboard agentcore probercore checkrunner apiclient
db/migrations/ схема SQLite (0001–0008)
configs/ примеры конфигураций компонентов
deploy/ docker/ (compose, Dockerfile, RUN.txt) systemd/ (юниты)
rxprod-compose/ compose реального стенда (control-api на :8081, дашборд на :8091)
scripts/ run-local-e2e.sh httpstub/ (заглушки целей для e2e)
bin/ готовые бинарники linux/amd64 и SHA256SUMS
docs/ документация и планы доработок
Модель данных и правила
ip_queue (текущая очередь) · ip_registry (все адреса, когда-либо бывшие в очереди) · checks (результаты по циклам) · events (журнал) · validators · sites · target_groups · check_types · settings · inbound_checks_settings · auto_cycle.
Очередь и состояния
- Путь адреса:
queued→assigning_fip→awaiting_self_check→checking→aggregating→doneилиfailed. Все переходы, кроме команд оператора, выполняет оркестратор сам по таймеру. - Терминальные состояния:
done,failed,occupied.occupied— Floating IP к моменту привязки уже занят чужим портом: цикл проверки не стартует,overall_resultпуст, это неfail. - Оператор может отменить проверку (
failed+cancelled), перепроверить завершённый адрес (возврат вqueued) или удалить адрес из очереди. - Адреса обрабатываются в порядке
sequence; каждый свободный валидатор получает следующийqueued-адрес под лизинг. Истёкший лизинг возвращает адрес в очередь, послеmax_retries— вfailed.
Итоговый результат
pass— прошли все проверки: исходящие и все настроенные площадки по всем портам и ICMP.partial— часть прошла, часть нет.fail— не прошла ни одна проверка или адрес не дошёл до проверок (self-check не подтвердился).cancelled— остановлено оператором.- Итог подводится, когда отчитались валидатор и все настроенные площадки либо истекло
checking_window_seconds. Молчание источника — провал (aggregation.missing_counts_as_fail). - Площадки опциональны: с пустым списком
sitesитог строится только по egress-проверкам.
Реестр
- Запись реестра создаётся при первой постановке адреса и не удаляется: она переживает удаление из очереди и повторное добавление.
- История проверок хранится по циклам;
history_retention_cyclesограничивает глубину (0 — без ограничения), сама запись реестра остаётся.
Автоматический цикл (опционально, по умолчанию выключен)
- Один цикл: очистить очередь → просканировать Floating IP (фаза
scanning, фоновое задание) → дождаться, пока все адреса станут терминальными → паузаinterval_seconds→ заново. Пауза считается от завершения цикла. Сканирование не блокирует оркестратор; чужое идущее сканирование цикл дожидается, а не присоединяется к нему. interval_seconds— по умолчанию 3600, минимум 60;max_run_seconds— максимальное ожидание проверок (0 — без лимита), по истечении исходtimeout.- Исходы цикла:
completed,no_free_ips,timeout,error,stopped. Опустевшая очередь посреди цикла считается завершением. - Состояние хранится в БД и переживает перезапуск; выключение не прерывает идущие проверки. Подробности — docs/USAGE.md.
Удаление и сканирование
- Удаление (точечное, списком, «очистить всё») убирает строку очереди, но не историю в реестре.
- Скан Floating IP ставит в очередь только свободные адреса (не привязанные ни к одному порту); уже идущие проверки не трогаются. Он идёт в фоне и читает облако страницами: подходит и для тысяч адресов (на стенде 6441 Floating IP читаются ≈ 1,5–2 мин). Адреса ставятся в очередь только после полного обнаружения (кусками по 500, по возрастанию IP); при сбое чтения очередь не меняется.
dry_run=true/ «Пробное сканирование» считает адреса, не меняя очередь. - Пропускная способность: ≈ 50 с на адрес на валидатор — очередь из 6440 адресов это ≈ 18 ч на 5 валидаторах, ≈ 9 ч на 10 (рычаги: число валидаторов и
fip_settle_seconds).
API (/api/v1)
| Область | Эндпоинты |
|---|---|
| Валидатор | POST /agents/register, POST /agents/{id}/heartbeat, GET /agents/{id}/assignment, POST /agents/{id}/self-check|events|results|complete |
| Пробер | POST /probers/register, POST /probers/{site_id}/heartbeat, GET /probers/{site_id}/assignments, POST /probers/{site_id}/results |
| Очередь | GET /admin/status, GET|POST /admin/ips (limit/offset/state/q/result/order — постранично), GET /admin/ips/{ip}, POST /admin/ips/{ip}/cancel, DELETE /admin/ips/{ip}, POST /admin/ips/delete|clear, POST|GET /admin/ips/scan (фоновый скан: 202, dry_run, wait; статус и прогресс) |
| Реестр | GET /admin/registry (limit/offset/q/last_result — постранично), GET /admin/registry/{ip} |
| Автоцикл | GET|PUT /admin/auto-cycle, POST /admin/auto-cycle/start|stop |
| Конфигурация | /admin/config/validators, /sites, /targets, /check-types, GET|PUT /admin/config/orchestrator, GET|PUT /admin/config/inbound-checks |
| Служебное | GET /admin/validators, GET /healthz |
Соглашения
- Тело запросов и ответов — JSON. Успех —
200,204— когда данных нет (например, у валидатора нет назначения). - Ошибки —
4xx/5xxс телом{"error": "..."}; нет или неверен токен —401(сWWW-Authenticate: Bearer), неизвестная сущность —404, конфликт состояния —409, неверные данные —400, ошибка OpenStack при скане —502. - Токен передаётся заголовком
Authorization: Bearer <токен>(docs/API.md). - Времена — RFC 3339. Полная спецификация и примеры
curl— docs/API.md.
Безопасность
- Доступ к API — два статических Bearer-токена (без срока жизни, из переменных окружения, сравнение в константное время):
Уровень Токен Методы admin CONTROL_API_ADMIN_TOKENвсе /api/v1/admin/*agent CONTROL_API_AGENT_TOKENзапись результатов валидатора и пробера: self-check,events,results,completeоткрыто — GET /healthz,register,heartbeatи получение задания (GET assignment/assignments)Токены разные: административный не открывает методы агентов, и наоборот. Валидатор и пробер получают настройку и задание без токена, но не могут отправить результат без токена агентов. - Пустой токен — уровень открыт (обратная совместимость):
control-apiстартует с предупреждением в логе. На реальном стенде задайте оба токена и ограничьте доступ на уровне сети (docs/SETUP.md); токены идут открытым текстом без TLS — публикуйте через reverse-proxy с TLS. Включать токен агентов нужно после его раздачи валидаторам и проберам (порядок). - Дашборд закрыт логином и паролем (один администратор; пароль и ключ сессии — из env). Сессия — подписанная cookie (
HttpOnly,SameSite=Strict, без состояния на сервере), CSRF-защита поOrigin, 5 неудачных входов за 10 минут с одного IP →429. Без заданных логина/пароля дашборд открыт (с предупреждением в логе). Дашборд ходит в API с токеном администратора. Подробности — docs/DASHBOARD.md. - Учётные данные OpenStack передаются только через переменные окружения процесса (
EnvironmentFile=в systemd,OS_*в Docker) и не попадают в YAML; режимpasswordперевыпускает токен сам, режимtoken— нет. - Компоненты работают по pull-модели: на валидаторах и площадках не нужно открывать входящие порты для
control-api. - Бинарники статические, без
cgo; целостность проверяетсяsha256sum -c bin/SHA256SUMS.
Публикация и эксплуатация
- Бинарники. Готовые linux/amd64 лежат в
bin/и не обновляются автоматически: после правок кода пересоберите их и обновитеSHA256SUMS(команды — docs/SETUP.md); Dockerfile копируют именноbin/*. - systemd. Юниты в
deploy/systemd/; уcontrol-api—EnvironmentFileс учётными данными OpenStack. - Docker.
deploy/docker/docker-compose.yml+docker-compose.override.yml(dev, mock) илиdocker-compose.prod.yml(без публикации портов,restart: unless-stopped). Какие сервисы поднимаются на хосте, задаётCOMPOSE_PROFILES:control-plane,dashboard,prober,validator. БД — volumecloud-ip-validator-db. - Реальный стенд.
rxprod-compose/— compose с готовыми образами, собственнымcontrol-api.yamlи каталогом БДcapi-db/;.envс учётными данными в репозиторий не входит. - Миграции применяются при старте
control-api; версия схемы —PRAGMA user_version. Начальная загрузка (validators,sites,targets,check_types,inbound_checks) выполняется только в пустые таблицы. - Остановка (
SIGTERM) корректно завершает HTTP-сервер и фоновые циклы. Состояние автоцикла и очереди сохраняется в БД.
Интерфейс
Страницы admin-dashboard (подробно — docs/DASHBOARD.md):
| Страница | Назначение |
|---|---|
/overview |
Счётчики и прогресс («Готово D из T», оценка времени), «в работе», «в очереди: Q», «последние завершённые», поиск по IP и фильтр по статусу, индикатор скана и автоцикла; работает на счётчиках и ограниченных списках, поэтому быстрый и при тысячах адресов |
/ips, /ips/{ip} |
Очередь постранично с поиском и фильтром на сервере: добавление адресов, «Сканировать Floating IP» (панель прогресса) и «Пробное сканирование», перепроверка, отмена, удаление (страница или «все N по фильтру», «Очистить всё»); детали и события адреса |
/registry, /registry/{ip} |
Реестр всех адресов (постранично) и полная история проверок адреса; поиск, фильтр и страница сохраняются в адресной строке |
/validators, /sites, /targets, /check-types |
Управление валидаторами, внешними площадками, группами целей и типами проверок |
/settings |
Панель «Автоматический цикл», пауза перед self-check, глубина истории, TCP-порты и ICMP для inbound-проверок |
- Порядок блоков на
/overviewфиксирован: статистика → фильтр → таблицы; поллится только блок таблиц, поэтому набранный в фильтре текст не сбрасывается. - Ошибки control-api показываются баннером; при недоступном API страница остаётся рабочей.
- Тёмная и светлая темы, переключатель в шапке.
Тесты
go build ./... && go vet ./... && go test ./... # юнит-тесты: db, orchestrator, httpapi, dashboard, agentcore, probercore, checkrunner, openstack
go test -race ./internal/orchestrator ./internal/httpapi ./internal/dashboard ./internal/db
scripts/run-local-e2e.sh # сквозной прогон: lease-reclaim, перепроверка, автоматический цикл
Юнит-тесты используют временную SQLite и MockClient, внешних ресурсов не требуют. E2E поднимает все компоненты локальными процессами с включёнными токенами (проверяет 401/открытые маршруты и работу агента и пробера с токеном) и завершается ненулевым кодом при провале проверок автоцикла — docs/LOCAL_E2E.md.
Документация
| Документ | Для чего |
|---|---|
| docs/SETUP.md | Развёртывание с нуля: бинарники или сборка из исходников, конфигурация, systemd и Docker/docker-compose — пошагово |
| docs/USAGE.md | Повседневная работа: очередь, сканирование Floating IP, автоматический цикл, статус, реестр и история, разбор результатов |
| docs/API.md | Спецификация HTTP API control-api и примеры запросов (curl) |
| docs/DASHBOARD.md | Устройство admin-dashboard: страницы, поиск и фильтр, обработка ошибок |
| docs/DIAGRAMS.md | Диаграммы потоков данных: control plane, egress-проверка, телеметрия |
| docs/LOCAL_E2E.md | Полностью офлайн-прогон всей системы одним скриптом |
| docs/changes/ | Планы доработок и отчёты ревью с отметкой времени в имени файла (последняя: скан при тысячах адресов) |
| docs/CONTROL_DATA_PLANE.html | Презентационные схемы control/data plane для docker-compose-деплоя — открыть в браузере |
История изменений
Дизайн крупных доработок зафиксирован в планах docs/PLAN_*.md; остальное — в истории git (git log).
| Изменение | Документ |
|---|---|
Веб-панель администратора admin-dashboard |
план · описание |
| Динамическое управление конфигурацией и очередью через API | план · API |
| Удаление адресов: точечное, массовое, «очистить всё» | план |
Пауза перед self-check после привязки Floating IP (fip_settle_seconds) |
план · USAGE |
| Механизм аутентификации OpenStack-клиента (token / password) | план |
| Дата | Веха | Документ |
|---|---|---|
| 2026-10-01 | Скан Floating IP при тысячах адресов: фоновый постраничный скан с прогрессом, фаза scanning в автоцикле, постраничные /ips и /registry, «Обзор» на счётчиках |
план · ревью и тесты · USAGE · API |
| 2026-10-01 | Аутентификация: токены администратора и агентов для API, логин и пароль для дашборда | план · ревью и тесты · API |
| 2026-10-01 | Автоматический цикл проверок по сценарию: очистка → скан FIP → проверка → пауза | USAGE · API |
| 2026-09-23 | Сканирование Floating IP и устойчивый реестр адресов с настраиваемой глубиной истории | USAGE |
| 2026-09-23 | Поиск по IP и фильтр по статусу на «Обзоре» и «Реестре» | DASHBOARD |
| 2026-09-23 | Пошаговое руководство по развёртыванию в Docker | SETUP |
| 2026-09-18 | Массовая перепроверка в дашборде; compose реального стенда rxprod-compose |
DASHBOARD |
| 2026-09-13 | Состояние occupied: пропуск цикла для уже занятого Floating IP; схемы control/data plane |
DIAGRAMS |
| 2026-08-26 | Управление внешними площадками и heartbeat пробера, новый UI дашборда, переключатель темы | DASHBOARD |