Scan floating IPs in the background, page by page, so thousands of addresses work

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>
This commit is contained in:
ayurishchevandClaude Sonnet 5.5 committed 2026-10-01 19:31:11 +03:00
1 parent debf2afed2
commit aff8fe38b5
61 files changed
+5833 -536

No files matched your search

+38 -7
View File
@@ -90,7 +90,8 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/ips \
самому найти их в облаке:
```bash
curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/scan
curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/scan # 202: сканирование запущено в фоне
curl -s http://<control-api>:8080/api/v1/admin/ips/scan # ход и результат
```
Сканируются все Floating IP текущего проекта OpenStack, но в очередь
@@ -101,8 +102,23 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/scan
встают в очередь, уже завершённые перезапускаются, активно проверяемые не
трогаются (см. [выше](#добавление-новых-ip-в-очередь)).
В `admin-dashboard` то же самое — кнопка «Сканировать Floating IP» на
странице `/ips`.
**Сколько адресов — не важно.** Сканирование работает в фоне и читает список из OpenStack
**страницами** (по 200 адресов, с повторами при обрывах), поэтому подходит и для проекта с
тысячами Floating IP: на стенде с 6441 адресом чтение занимает около 1,5–2 минут. Сначала
обнаруживаются **все** адреса, и только потом они ставятся в очередь (кусками по 500, в порядке
возрастания IP); сразу после этого начинаются проверки. Если чтение сорвалось даже после повторов,
в очередь не попадает ничего — очередь остаётся как была, а на панели виден `error` и причина.
Одновременно идёт одно сканирование: повторное нажатие присоединяется к текущему.
В `admin-dashboard` — кнопка «Сканировать Floating IP» на странице `/ips`: она сразу отвечает, а под
кнопкой появляется панель прогресса (читаются страницы → ставятся в очередь → готово: прочитано
страниц, найдено, свободных, добавлено, время), по окончании таблица обновляется сама. Рядом —
«Пробное сканирование»: оно проходит все страницы и показывает, сколько свободных адресов нашлось,
**не меняя очередь** (удобно проверить, что облако отвечает и сколько адресов будет поставлено).
Параметры чтения (`control-api.yaml`): `openstack.list_page_size` (200), `openstack.request_timeout_seconds`
(60 — таймаут одного запроса к OpenStack), `openstack.list_page_retries` (5), `orchestrator.fip_scan_timeout_seconds`
(1800 — предел всего сканирования).
Если хочется, чтобы сканирование происходило само по расписанию, а не
только по запросу — задайте `orchestrator.fip_scan_interval_seconds`
@@ -111,6 +127,11 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/scan
[автоматический цикл](#автоматический-цикл-проверок), это периодическое
сканирование не выполняется — цикл сам управляет очередью.
> **Сколько займут проверки.** Один адрес занимает около 50 секунд на валидаторе (из них 30 с — пауза
> `fip_settle_seconds`). Поэтому очередь из 6440 адресов — примерно 18 часов на 5 валидаторах,
> 9 часов на 10, 4,5 часа на 20. Ускорить можно числом валидаторов и (осторожно) `fip_settle_seconds`;
> на странице «Обзор» виден прогресс «Готово D из T» и оценка оставшегося времени.
## Автоматический цикл проверок
Опциональный режим, который сам повторяет то, что оператор делает руками:
@@ -122,7 +143,9 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/scan
в [реестре](#реестр-адресов-и-глубина-истории) при этом сохраняется.
2. Control-api находит все свободные Floating IP и ставит их в очередь —
то же, что «Сканировать Floating IP» (см.
[выше](#сканирование-floating-ip-из-openstack)).
[выше](#сканирование-floating-ip-из-openstack)). Шаги 1–2 выполняются одним
фоновым заданием, поэтому долгое чтение тысяч адресов не блокирует работу
оркестратора (назначение валидаторов, лизинги, heartbeat).
3. Проверки запускаются сами — как для любого адреса в очереди.
4. Цикл ждёт, пока **все** адреса очереди дойдут до конечного состояния
(`done`, `failed` или `occupied`). К этому моменту результат каждого
@@ -136,7 +159,7 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/scan
| Параметр | По умолчанию | Смысл |
|---|---|---|
| `interval_seconds` | `3600` (1 час) | Пауза между циклами. Не меньше `60`: слишком частые сканы нагружают API OpenStack. |
| `max_run_seconds` | `0` (без лимита) | Сколько максимум ждать на шаге 4. По истечении цикл фиксирует `timeout` и переходит к паузе — защита от зависания (нет свободных валидаторов, недоступна площадка). Очередь при этом не трогается: следующий цикл её очистит, а до тех пор видно, что именно не дошло до конца. |
| `max_run_seconds` | `0` (без лимита) | Сколько максимум ждать на шаге 4 (отсчёт — **от конца сканирования**). По истечении цикл фиксирует `timeout` и переходит к паузе — защита от зависания (нет свободных валидаторов, недоступна площадка). Очередь при этом не трогается: следующий цикл её очистит, а до тех пор видно, что именно не дошло до конца. **При тысячах адресов оставьте `0`** (или задайте больше расчётного времени: 6440 адресов — часы). |
Параметры хранятся в базе и меняются на лету, без перезапуска; в `control-api.yaml`
ничего задавать не нужно. Новый `interval_seconds` применяется к паузе
@@ -170,7 +193,8 @@ curl -s http://<control-api>:8080/api/v1/admin/auto-cycle
### Фазы и результат последнего цикла
`phase` показывает, что происходит сейчас: `idle` (автоцикл выключен или ещё не
стартовал), `running` (идут проверки — шаги 3–4) и `waiting` (пауза между циклами,
стартовал), `scanning` (шаги 1–2: очистка очереди и чтение/постановка Floating IP),
`running` (идут проверки — шаги 3–4) и `waiting` (пауза между циклами,
шаг 5; время следующего запуска — `next_run_at`). Результат последнего цикла
(`last_outcome`):
@@ -185,7 +209,8 @@ curl -s http://<control-api>:8080/api/v1/admin/auto-cycle
### Что важно знать
- **Выключение не прерывает проверки**, которые уже идут: они закончатся и попадут
в реестр, остановится только повторение.
в реестр, остановится только повторение. Если выключить цикл во время фазы `scanning`,
сканирование отменяется (уже поставленные в очередь куски остаются).
- Автоцикл **владеет очередью**: каждый цикл начинается с её полной очистки,
поэтому адреса, добавленные вручную, будут удалены (их история в реестре
остаётся). Ручные «Очистить всё» и «Сканировать Floating IP» во время цикла
@@ -195,6 +220,12 @@ curl -s http://<control-api>:8080/api/v1/admin/auto-cycle
- События цикла (`auto_cycle_started`, `auto_cycle_completed`, `auto_cycle_timeout`,
`auto_cycle_error`, `auto_cycle_stopped`) пишутся в журнал событий вместе с
`queue_cleared` и `fip_scan`.
- Если в момент старта цикла уже идёт чужое сканирование (ручное, пробное или по расписанию),
цикл **дожидается** его окончания и запускает собственное (с очисткой очереди) — присоединяться
к чужому нельзя: оно могло ничего не поставить в очередь.
- Если сканирование завершилось ошибкой, очередь уже очищена (шаг 1) и остаётся пустой до следующего
цикла (`interval_seconds`); исход цикла — `error` с причиной в `last_error`.
- Цикл на тысячах адресов длится часы; пауза `interval_seconds` отсчитывается после его завершения.
- В реальном OpenStack отвязка Floating IP после очистки очереди может
отразиться с задержкой; если скан сразу после неё не увидел свободных адресов,
цикл завершится с `no_free_ips` и повторится через `interval_seconds`.