Add Floating IP scanning and a durable address registry with configurable history depth

Adds POST /api/v1/admin/ips/scan (plus an optional periodic ticker) to
discover free Floating IPs in the OpenStack project and feed them straight
into the check queue. More importantly, decouples check/event history from
ip_queue's lifecycle: a new ip_registry table (migration 0007) gives every
address ever submitted a durable identity, so deleting it from the queue no
longer destroys its history — it's still reachable via the new
GET /api/v1/admin/registry[/{ip}] endpoints and the dashboard's /registry
pages, with retention depth configurable in check cycles per address
(history_retention_cycles, 0 = unlimited).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5 committed 2026-09-23 09:52:01 +03:00
1 parent 78b20fa5be
commit 582b44f314
47 files changed
+1781 -107

No files matched your search

+128 -22
View File
@@ -28,6 +28,7 @@ JSON, базовый префикс прикладных методов — `/ap
- [Методы для prober](#методы-для-prober)
- [Служебные и административные методы](#служебные-и-административные-методы)
- [Управление очередью и конфигурацией](#управление-очередью-и-конфигурацией)
- [Реестр адресов и история проверок](#реестр-адресов-и-история-проверок)
- [Модель состояний и связь методов с ней](#модель-состояний-и-связь-методов-с-ней)
- [Сквозной пример работы (curl)](#сквозной-пример-работы-curl)
@@ -416,12 +417,18 @@ YAML для этой секции больше не перечитывается
### `DELETE /api/v1/admin/ips/{ip}`, `POST /api/v1/admin/ips/delete`, `POST /api/v1/admin/ips/clear`
**Безвозвратное удаление**, в отличие от `cancel` выше: строка `ip_queue`
и вся её история (`checks`, `events`) стираются физически, без возможности
восстановления. Работает из любого состояния, включая активно
проверяемое — если Floating IP привязан, он отвязывается тем же
best-effort способом, что и при `cancel`/обычном завершении, владеющий
валидатор освобождается.
Удаляет строку `ip_queue` — адрес пропадает из очереди/`GET
/api/v1/admin/ips*` — безвозвратно, без возможности восстановить именно
эту строку. Работает из любого состояния, включая активно проверяемое —
если Floating IP привязан, он отвязывается тем же best-effort способом,
что и при `cancel`/обычном завершении, владеющий валидатор освобождается.
**Накопленная история адреса при этом не теряется**: `checks`/`events`
остаются в реестре (`ip_registry`, см. раздел [«Реестр
адресов»](#реестр-адресов-и-история-проверок) ниже) и доступны через `GET
/api/v1/admin/registry/{ip}` даже после удаления строки из очереди — в
отличие от `ip_queue`, реестровая запись никогда не удаляется этими
методами.
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
@@ -433,7 +440,95 @@ best-effort способом, что и при `cancel`/обычном заве
идут в `not_found`, не ошибка — тот же терпимый стиль, что у `POST
/api/v1/admin/ips`). `POST .../clear` удаляет **вообще всё**, что сейчас в
очереди, включая адреса в процессе проверки — самая опасная операция
этого API, используйте с осторожностью.
этого API, используйте с осторожностью (и, опять же, ничья история при
этом физически не стирается — см. выше).
### `POST /api/v1/admin/ips/scan`
Сканирует текущий проект OpenStack на предмет свободных (не привязанных ни
к одному порту) Floating IP и сразу передаёт найденный список в `POST
/api/v1/admin/ips` — тот же add/requeue/reorder-вызов, как если бы
оператор ввёл эти адреса вручную. Не принимает тело запроса.
Ответ (`200`):
```json
{
"scanned_free": 3,
"added": ["203.0.113.20"],
"requeued": [],
"reordered": ["203.0.113.10", "203.0.113.11"],
"skipped_in_progress": []
}
```
`scanned_free` — сколько свободных Floating IP нашлось в проекте всего
(включая уже стоящие в очереди — они попадут в `reordered`, а не
`added`). Если свободных адресов нет вообще, это не ошибка: ответ будет
`{"scanned_free": 0, "added": [], ...}`.
Помимо ручного вызова, сканирование можно включить по расписанию —
`orchestrator.fip_scan_interval_seconds` в `control-api.yaml` (0, по
умолчанию, — только по запросу через эту ручку или кнопку «Сканировать
Floating IP» в дашборде).
## Реестр адресов и история проверок
В отличие от `ip_queue` (текущая рабочая очередь, см. выше), реестр —
`ip_registry` — это накопительная запись **обо всех адресах, когда-либо
поставленных на проверку**, вне зависимости от того, стоят ли они сейчас в
очереди. Запись в реестре переживает удаление адреса из `ip_queue` (`DELETE
/api/v1/admin/ips/{ip}` и т.п.) и повторное добавление того же адреса
позже — обе истории (до и после) остаются доступны и не перекрывают друг
друга (каждой постановке на проверку соответствует свой `cycle_id`,
уникальный в пределах адреса на всё время).
### `GET /api/v1/admin/registry`
Список всех адресов реестра с краткой сводкой по каждому.
```json
[
{
"ip_address": "203.0.113.10",
"first_seen_at": "2026-01-10T12:00:00Z",
"last_seen_at": "2026-02-01T09:00:00Z",
"total_cycles": 4,
"last_result": "pass",
"last_checked_at": "2026-02-01T09:05:00Z",
"in_queue": true,
"current_state": "done"
}
]
```
`in_queue`/`current_state` отражают, есть ли у адреса сейчас живая строка в
`ip_queue`, а не только в реестре.
### `GET /api/v1/admin/registry/{ip}`
Реестровая запись по одному адресу плюс вся сохранённая история проверок
по нему, по всем циклам (не только текущему — в отличие от `GET
/api/v1/admin/ips/{ip}`, который отдаёт проверки только текущей попытки).
Порядок — от новых циклов к старым.
```json
{
"registry": { "ip_address": "203.0.113.10", "total_cycles": 4, "...": "..." },
"checks": [
{"CycleID": 4, "Source": "egress", "CheckType": "https", "Success": true, "...": "..."},
{"CycleID": 3, "Source": "egress", "CheckType": "https", "Success": false, "...": "..."}
]
}
```
`404`, если адрес никогда не ставился на проверку.
**Глубина хранения.** Сколько последних циклов на адрес хранится в
`checks` (и синхронно — в `events`), управляется полем
`history_retention_cycles` в `GET`/`PUT /api/v1/admin/config/orchestrator`
(0, по умолчанию, — без ограничения). Сама реестровая запись (`ip_address`,
`first_seen_at`, счётчик циклов) не удаляется никогда, независимо от этой
настройки — она лишь ограничивает глубину детальной истории проверок.
```bash
curl -s -X DELETE "$BASE/api/v1/admin/ips/203.0.113.10"
@@ -497,25 +592,33 @@ curl -s -X POST "$BASE/api/v1/admin/ips/clear"
### Настройки оркестратора: `/api/v1/admin/config/orchestrator`
Единственный на сегодня параметр — `fip_settle_seconds`: пауза между
привязкой Floating IP к валидатору и моментом, когда self-check по этому
адресу становится доступен агенту (`GET
/api/v1/agents/{id}/assignment` до истечения паузы отдаёт `204`, как если
бы валидатору просто нечего было делать — никаких изменений в протоколе
агента). Нужна, чтобы дать data plane OpenStack время реально начать
пропускать трафик через только что привязанный адрес, прежде чем
запускать по нему проверки. `0` — без паузы (поведение по умолчанию, как
до появления этого параметра).
Два параметра:
- `fip_settle_seconds` — пауза между привязкой Floating IP к валидатору и
моментом, когда self-check по этому адресу становится доступен агенту
(`GET /api/v1/agents/{id}/assignment` до истечения паузы отдаёт `204`,
как если бы валидатору просто нечего было делать — никаких изменений в
протоколе агента). Нужна, чтобы дать data plane OpenStack время реально
начать пропускать трафик через только что привязанный адрес, прежде чем
запускать по нему проверки. `0` — без паузы (поведение по умолчанию, как
до появления этого параметра).
- `history_retention_cycles` — сколько последних циклов проверки хранить
на адрес в реестре (`GET /api/v1/admin/registry/{ip}`, см.
[«Реестр адресов»](#реестр-адресов-и-история-проверок)). `0` — без
ограничения (поведение по умолчанию).
| Метод | Путь | Тело | Успех | Ошибки |
|---|---|---|---|---|
| GET | `/api/v1/admin/config/orchestrator` | — | `{"fip_settle_seconds":N}` | |
| PUT | `/api/v1/admin/config/orchestrator` | `{"fip_settle_seconds":N}` | `200` | `400`, если `N < 0`, или если `fip_settle_seconds + self_check_timeout_seconds >= lease_ttl_seconds` (пауза не должна съедать весь лизинг адреса — иначе self-check не успеет пройти до истечения `lease_ttl_seconds`, и адрес будет вечно возвращаться в очередь) |
| GET | `/api/v1/admin/config/orchestrator` | — | `{"fip_settle_seconds":N,"history_retention_cycles":M}` | |
| PUT | `/api/v1/admin/config/orchestrator` | `{"fip_settle_seconds":N,"history_retention_cycles":M}` | `200` | `400`, если `N < 0` или `M < 0`, или если `fip_settle_seconds + self_check_timeout_seconds >= lease_ttl_seconds` (пауза не должна съедать весь лизинг адреса — иначе self-check не успеет пройти до истечения `lease_ttl_seconds`, и адрес будет вечно возвращаться в очередь) |
Как и остальные разделы этой группы, YAML-поле `orchestrator.
fip_settle_seconds` в `control-api.yaml` — только одноразовый bootstrap
для пустой БД; дальше источник истины — сама база, менять значение нужно
через `PUT` выше (или страницу `/settings` в дашборде).
`history_retention_cycles` не имеет YAML-эквивалента вообще — управляется
только через `PUT` выше/дашборд, значение по умолчанию `0` всегда
применяется на пустой БД.
### Типы проверок пробера: `/api/v1/admin/config/inbound-checks`
@@ -633,12 +736,15 @@ queued ──(control-api сам, без вызова API)──▶ assigning_fi
"cancelled"`)** — `POST /api/v1/admin/ips/{ip}/cancel`;
- **`done`/`failed`/`occupied` → `queued` (новая попытка)** — `POST
/api/v1/admin/ips` с уже завершённым (или занятым) адресом в списке;
- **любое состояние → адрес физически исчезает из очереди**, вместе со
всей историей — `DELETE /api/v1/admin/ips/{ip}`, `POST
/api/v1/admin/ips/delete`, `POST /api/v1/admin/ips/clear` (см.
- **любое состояние → адрес физически исчезает из очереди** — `DELETE
/api/v1/admin/ips/{ip}`, `POST /api/v1/admin/ips/delete`, `POST
/api/v1/admin/ips/clear` (см.
[выше](#delete-apiv1adminipsip-post-apiv1adminipsdelete-post-apiv1adminipsclear)).
Не путать с cancel — cancel сохраняет запись как историю (`failed`/
`cancelled`), delete стирает её целиком без возможности восстановления.
`cancelled`) прямо в `ip_queue`; delete убирает саму строку `ip_queue`
безвозвратно, но накопленная история проверок остаётся в реестре (`GET
/api/v1/admin/registry/{ip}`) — см.
[«Реестр адресов»](#реестр-адресов-и-история-проверок).
## Сквозной пример работы (curl)