Retry a failed self-check on another validator; add the self-check failure ceiling
A validator that failed the self-check of an address no longer gets that address
again in the current round (ClaimNextQueued skips it); the validator itself stays
in service and takes all other addresses. The verdict fail is set when the number
of failed self-checks of an address reaches settings.self_check_max_attempts
(1..50, default 5, independent of the number of validators); max_retries and
retry_count are no longer used for self-check. If every working validator has
already failed the address, a new round starts and the exclusions lapse.
Migration 0012: ip_self_check_failures (permanent history per registry address),
ip_queue.sc_failures and sc_round_start_cycle (cycle_id is used instead of
attempt_number, which restarts when a queue row is recreated), the setting.
db.FailSelfCheck does it in one transaction; re-submission starts a new series.
API: self_check_max_attempts in GET/PUT /admin/config/orchestrator,
self_check_failed_on in /admin/ips/{ip} and /admin/registry/{ip}. Dashboard: the
field on /settings and the line "Self-check не прошёл на: ..." on the address
pages. Docs, plan and summary in docs/changes/.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b7669c9e41
commit
e95b5eb7d5
34 files changed
+1092
-82
No files matched your search
@@ -12,7 +12,7 @@
|
||||
|
||||
| Группа | Таблицы | Можно чистить |
|
||||
|---|---|---|
|
||||
| Данные прогона | `ip_queue` (очередь), `ip_registry` (реестр адресов), `checks` (реестр проверок), `ip_site_checks` (признаки площадок по адресам в работе), `check_runs` и `run_results` (запуски и итоги адресов в них), `events` (журнал событий) | да |
|
||||
| Данные прогона | `ip_queue` (очередь), `ip_registry` (реестр адресов), `checks` (реестр проверок), `ip_site_checks` (признаки площадок по адресам в работе), `check_runs` и `run_results` (запуски и итоги адресов в них), `ip_self_check_failures` (история сбоев self-check по адресам), `events` (журнал событий) | да |
|
||||
| Настройки (не трогать) | `validators`, `sites`, `target_groups` (цели), `check_types`, `inbound_checks_settings`, `settings`, `auto_cycle`, `subnets` (список подсетей для аналитики) | **нет** |
|
||||
| Служебное | `sqlite_sequence` (нумерация записей), `PRAGMA user_version` (версия схемы) | нумерацию можно сбросить, версию не менять |
|
||||
|
||||
@@ -86,7 +86,7 @@ UNION ALL SELECT 'sites', COUNT(*) FROM sites;"
|
||||
|
||||
### 3.1. Полный сброс данных прогона (перед новым полным прогоном)
|
||||
|
||||
Очищает очередь, реестр адресов, реестр проверок, журнал событий. Валидаторы, площадки, цели, типы проверок и все настройки остаются.
|
||||
Очищает очередь, реестр адресов, реестр проверок, историю сбоев self-check, журнал событий. Валидаторы, площадки, цели, типы проверок и все настройки остаются.
|
||||
|
||||
```sql
|
||||
PRAGMA foreign_keys = ON;
|
||||
@@ -99,11 +99,12 @@ DELETE FROM ip_site_checks;
|
||||
DELETE FROM run_results;
|
||||
DELETE FROM check_runs;
|
||||
DELETE FROM checks;
|
||||
DELETE FROM ip_self_check_failures;
|
||||
DELETE FROM events;
|
||||
DELETE FROM ip_queue;
|
||||
DELETE FROM ip_registry;
|
||||
-- нумерация снова с 1 (необязательно)
|
||||
DELETE FROM sqlite_sequence WHERE name IN ('ip_registry', 'checks', 'ip_queue', 'events', 'check_runs');
|
||||
DELETE FROM sqlite_sequence WHERE name IN ('ip_registry', 'checks', 'ip_queue', 'events', 'check_runs', 'ip_self_check_failures');
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
@@ -157,7 +158,7 @@ WHERE cycle_id <= (SELECT MAX(c2.cycle_id) FROM checks c2 WHERE c2.registry_id =
|
||||
|
||||
### 3.4. Удалить конкретные адреса целиком
|
||||
|
||||
Удаляет адрес из очереди и реестра вместе со всей его историей (проверки и события). Список адресов подставьте в первую команду
|
||||
Удаляет адрес из очереди и реестра вместе со всей его историей (проверки, сбои self-check и события). Список адресов подставьте в первую команду
|
||||
`CREATE TEMP TABLE doomed_reg`. Адрес, который сейчас проверяется, удалять этим способом нельзя: используйте API (раздел 5).
|
||||
|
||||
```sql
|
||||
@@ -171,6 +172,7 @@ UPDATE validators SET current_ip_id = NULL WHERE current_ip_id IN (SELECT id FRO
|
||||
DELETE FROM ip_site_checks WHERE ip_id IN (SELECT id FROM doomed_ip);
|
||||
DELETE FROM run_results WHERE registry_id IN (SELECT id FROM doomed_reg);
|
||||
DELETE FROM checks WHERE registry_id IN (SELECT id FROM doomed_reg);
|
||||
DELETE FROM ip_self_check_failures WHERE registry_id IN (SELECT id FROM doomed_reg);
|
||||
DELETE FROM events WHERE registry_id IN (SELECT id FROM doomed_reg) OR ip_id IN (SELECT id FROM doomed_ip);
|
||||
DELETE FROM ip_queue WHERE id IN (SELECT id FROM doomed_ip);
|
||||
DELETE FROM ip_registry WHERE id IN (SELECT id FROM doomed_reg);
|
||||
|
||||
+28
-7
@@ -198,8 +198,13 @@ self-check способом `control_api` (`self_check.methods` в
|
||||
```
|
||||
|
||||
Ответ: `{"ok": true}`. При `success: false` control-api сам решает —
|
||||
повторить попытку назначения FIP или пометить IP как `failed` (после
|
||||
исчерпания `orchestrator.max_self_check_retries`).
|
||||
вернуть адрес в очередь или пометить IP как `failed`. Сбой записывается в
|
||||
историю адреса; повтор **не отдаётся этому валидатору** (он остаётся в
|
||||
работе и берёт остальные адреса). Итог `fail` ставится, когда число
|
||||
проваленных self-check у адреса достигло потолка `self_check_max_attempts`
|
||||
(по умолчанию 5, см. [«Настройки оркестратора»](#настройки-оркестратора-apiv1adminconfigorchestrator));
|
||||
`orchestrator.max_self_check_retries` не используется. Сбой привязки FIP и
|
||||
истечение лизинга идут по `max_retries`, как раньше.
|
||||
|
||||
### `POST /api/v1/agents/{id}/events`
|
||||
|
||||
@@ -415,10 +420,14 @@ IP на данном проходе". До этого момента control-api
|
||||
{
|
||||
"ip": { "ID": 42, "IPAddress": "203.0.113.10", "State": "done", "OverallResult": "pass", "...": "..." },
|
||||
"checks": [ {"Source": "egress", "CheckType": "https", "Target": "https://github.com", "Success": true, "...": "..."} ],
|
||||
"events": [ {"EventType": "fip_associated", "OccurredAt": "...", "...": "..."} ]
|
||||
"events": [ {"EventType": "fip_associated", "OccurredAt": "...", "...": "..."} ],
|
||||
"self_check_failed_on": ["vkiplab-v17"]
|
||||
}
|
||||
```
|
||||
|
||||
`self_check_failed_on` — валидаторы, у которых self-check на этом адресе не
|
||||
прошёл в текущем запуске (по алфавиту; `[]`, если сбоев не было).
|
||||
|
||||
> Обратите внимание: вложенные объекты `ip`/`checks`/`events` сериализуются
|
||||
> без переопределения имён полей (используются имена Go-структур, например
|
||||
> `IPAddress`, `State`, `Success`) — в отличие от методов для
|
||||
@@ -722,10 +731,14 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/auto-cycle/stop
|
||||
"checks": [
|
||||
{"CycleID": 4, "Source": "egress", "CheckType": "https", "Success": true, "...": "..."},
|
||||
{"CycleID": 3, "Source": "egress", "CheckType": "https", "Success": false, "...": "..."}
|
||||
]
|
||||
],
|
||||
"self_check_failed_on": ["vkiplab-v17"]
|
||||
}
|
||||
```
|
||||
|
||||
`self_check_failed_on` — валидаторы, у которых self-check на этом адресе не
|
||||
прошёл, по всем запускам (по алфавиту; `[]`, если сбоев не было).
|
||||
|
||||
`404`, если адрес никогда не ставился на проверку.
|
||||
|
||||
**Глубина хранения.** Сколько последних циклов на адрес хранится в
|
||||
@@ -797,7 +810,7 @@ curl -s -X POST "$BASE/api/v1/admin/ips/clear"
|
||||
|
||||
### Настройки оркестратора: `/api/v1/admin/config/orchestrator`
|
||||
|
||||
Два параметра:
|
||||
Три параметра:
|
||||
|
||||
- `fip_settle_seconds` — пауза между привязкой Floating IP к валидатору и
|
||||
моментом, когда self-check по этому адресу становится доступен агенту
|
||||
@@ -811,11 +824,19 @@ curl -s -X POST "$BASE/api/v1/admin/ips/clear"
|
||||
на адрес в реестре (`GET /api/v1/admin/registry/{ip}`, см.
|
||||
[«Реестр адресов»](#реестр-адресов-и-история-проверок)). `0` — без
|
||||
ограничения (поведение по умолчанию).
|
||||
- `self_check_max_attempts` — потолок провалов self-check на один адрес
|
||||
(1…50, по умолчанию 5). Когда у адреса провалено столько self-check,
|
||||
он получает итог `fail`. Не зависит от числа валидаторов. Валидатор,
|
||||
проваливший self-check на адресе, этому адресу больше не выдаётся (на
|
||||
остальные адреса это не влияет); если все рабочие валидаторы уже
|
||||
провалили адрес, исключения сбрасываются и повторы продолжаются до
|
||||
потолка. Значение действует на следующих повторах без перезапуска.
|
||||
В `PUT` поле необязательно: если не передано, не меняется.
|
||||
|
||||
| Метод | Путь | Тело | Успех | Ошибки |
|
||||
|---|---|---|---|---|
|
||||
| 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`, и адрес будет вечно возвращаться в очередь) |
|
||||
| GET | `/api/v1/admin/config/orchestrator` | — | `{"fip_settle_seconds":N,"history_retention_cycles":M,"self_check_max_attempts":K}` | |
|
||||
| PUT | `/api/v1/admin/config/orchestrator` | `{"fip_settle_seconds":N,"history_retention_cycles":M,"self_check_max_attempts":K}` | `200` | `400`, если `N < 0`, `M < 0` или `K` вне 1…50, или если `fip_settle_seconds + self_check_timeout_seconds >= lease_ttl_seconds` (пауза не должна съедать весь лизинг адреса — иначе self-check не успеет пройти до истечения `lease_ttl_seconds`, и адрес будет вечно возвращаться в очередь) |
|
||||
|
||||
Как и остальные разделы этой группы, YAML-поле `orchestrator.
|
||||
fip_settle_seconds` в `control-api.yaml` — только одноразовый bootstrap
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ admin-dashboard -config /etc/cloud-ip-validator/admin-dashboard.yaml
|
||||
| `/sites` | Площадки — число слотов не ограничено, форма сверху добавляет новый слот, назначить/сменить/освободить `site_id` в каждой строке; колонка «Статус» показывает бейдж подключения пробера (`unregistered`/`idle`/`unreachable`, по аналогии с `/validators`), см. [USAGE.md](USAGE.md#состояния-площадки). |
|
||||
| `/targets` | Группы целей для egress-проверок — создание/редактирование/удаление. |
|
||||
| `/check-types` | Типы проверок (`https`/`icmp`/`ssh`/...), включение/выключение, привязка к группам целей. |
|
||||
| `/settings` | Четыре блока. Первый — панель **«Автоматический цикл»**: статус и фаза, время последнего/следующего запуска, результат последнего цикла, поля «Интервал между циклами (мин)» и «Максимальная длительность проверки (мин, 0 = без лимита)» с кнопкой «Сохранить» и кнопка «Включить»/«Выключить» (показывается та, что сейчас применима). Значения вводятся в минутах (допустимы дробные), в control-api уходят секундами; минимум интервала — 1 минута (`60` с), нарушение приходит предупреждением в баннере. Подробности — [USAGE.md](USAGE.md#автоматический-цикл-проверок), API — [API.md](API.md#автоматический-цикл-проверок). Далее три формы: `fip_settle_seconds` — пауза (в секундах) между привязкой Floating IP и началом self-check («прогрев» дата-плейна OpenStack, см. [USAGE.md](USAGE.md#пауза-перед-self-check-fip_settle_seconds)); `history_retention_cycles` — сколько последних циклов проверки хранить на адрес в реестре (0 — без ограничения); и типы проверок пробера — TCP-порты (через запятую) + чекбокс ICMP, общие для всех площадок (см. [USAGE.md](USAGE.md#управление-типами-проверок-пробера)). |
|
||||
| `/settings` | Четыре блока. Первый — панель **«Автоматический цикл»**: статус и фаза, время последнего/следующего запуска, результат последнего цикла, поля «Интервал между циклами (мин)» и «Максимальная длительность проверки (мин, 0 = без лимита)» с кнопкой «Сохранить» и кнопка «Включить»/«Выключить» (показывается та, что сейчас применима). Значения вводятся в минутах (допустимы дробные), в control-api уходят секундами; минимум интервала — 1 минута (`60` с), нарушение приходит предупреждением в баннере. Подробности — [USAGE.md](USAGE.md#автоматический-цикл-проверок), API — [API.md](API.md#автоматический-цикл-проверок). Далее формы: `fip_settle_seconds` — пауза (в секундах) между привязкой Floating IP и началом self-check («прогрев» дата-плейна OpenStack, см. [USAGE.md](USAGE.md#пауза-перед-self-check-fip_settle_seconds)); `self_check_max_attempts` — «Потолок провалов self-check на адрес» (1…50, по умолчанию 5; повторы идут на других валидаторах, см. [USAGE.md](USAGE.md#повтор-после-сбоя-self-check)); `history_retention_cycles` — сколько последних циклов проверки хранить на адрес в реестре (0 — без ограничения); и типы проверок пробера — TCP-порты (через запятую) + чекбокс ICMP, общие для всех площадок (см. [USAGE.md](USAGE.md#управление-типами-проверок-пробера)). |
|
||||
|
||||
### «В работе», «в очереди» и «последняя завершённая» проверка
|
||||
|
||||
|
||||
+36
-2
@@ -33,6 +33,7 @@
|
||||
- [Принудительная остановка проверки](#принудительная-остановка-проверки)
|
||||
- [Удаление адресов из очереди](#удаление-адресов-из-очереди)
|
||||
- [Пауза перед self-check (fip_settle_seconds)](#пауза-перед-self-check-fip_settle_seconds)
|
||||
- [Повтор после сбоя self-check](#повтор-после-сбоя-self-check)
|
||||
- [Частые проблемы и что с ними делать](#частые-проблемы-и-что-с-ними-делать)
|
||||
|
||||
## Как устроена работа с системой
|
||||
@@ -306,8 +307,9 @@ curl -s http://<control-api>:8080/api/v1/admin/ips \
|
||||
оператора: годится ли адрес для данного случая использования.
|
||||
- **`fail`** — либо ни одна проверка не прошла, либо адрес вообще не
|
||||
дошёл до стадии проверок (например, self-check не подтвердился —
|
||||
трафик валидатора не пошёл через назначенный FIP — и попытки
|
||||
исчерпались). Смотрите `events` по этому адресу (см. ниже), чтобы
|
||||
трафик валидатора не пошёл через назначенный FIP — и число провалов
|
||||
достигло потолка `self_check_max_attempts`, см.
|
||||
[«Повтор после сбоя self-check»](#повтор-после-сбоя-self-check)). Смотрите `events` по этому адресу (см. ниже), чтобы
|
||||
понять, на каком шаге и почему.
|
||||
- **`cancelled`** — проверку остановил оператор через `POST
|
||||
/api/v1/admin/ips/{ip}/cancel` (`State` при этом — `failed`), а не
|
||||
@@ -774,6 +776,37 @@ curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/clear
|
||||
всё» — каждая с подтверждением, явно предупреждающим о необратимости
|
||||
(см. [DASHBOARD.md](DASHBOARD.md)).
|
||||
|
||||
## Повтор после сбоя self-check
|
||||
|
||||
Если self-check адреса не прошёл на валидаторе N, адрес возвращается в
|
||||
очередь, но **этому же валидатору больше не выдаётся**: повтор достаётся
|
||||
другому. Правило действует только для этого адреса. Валидатор N не
|
||||
блокируется и не помечается неисправным, он продолжает брать остальные
|
||||
адреса. Так один временно неисправный валидатор (например, у него не
|
||||
работает трансляция плавающего IP) не расходует все попытки адреса.
|
||||
|
||||
Итог `fail` ставится, когда число проваленных self-check у адреса достигло
|
||||
потолка **«Потолок провалов self-check на адрес»** (`self_check_max_attempts`,
|
||||
по умолчанию 5; `/settings` или `PUT /api/v1/admin/config/orchestrator`,
|
||||
допустимо 1…50). Потолок не зависит от числа валидаторов и действует на
|
||||
следующих повторах без перезапуска. Если все рабочие валидаторы
|
||||
(`idle`, `assigned`, `checking`) уже провалили адрес, а потолок не достигнут
|
||||
(валидаторов меньше потолка, в том числе один), исключения сбрасываются и
|
||||
повторы продолжаются до потолка.
|
||||
|
||||
Исключение создаёт только проваленный self-check. Ошибка привязки
|
||||
плавающего IP, потеря валидатора и истечение лизинга исключений не создают и
|
||||
идут по `orchestrator.max_retries`, как раньше. Поле
|
||||
`orchestrator.max_self_check_retries` в YAML больше не используется.
|
||||
|
||||
Ручная перепроверка или повторная постановка адреса начинает серию заново
|
||||
(счётчик сбоев обнуляется). История сбоев хранится постоянно (таблица
|
||||
`ip_self_check_failures`) и отдаётся в поле `self_check_failed_on` ответов
|
||||
`GET /admin/ips/{ip}` и `GET /admin/registry/{ip}`; на страницах `/ips/{ip}` и
|
||||
`/registry/{ip}` показана строка «Self-check не прошёл на: …». В журнале
|
||||
событий при повторе появляется `validator_excluded`. Показ на странице
|
||||
аналитики — отдельный следующий шаг.
|
||||
|
||||
## Пауза перед self-check (fip_settle_seconds)
|
||||
|
||||
Как только Floating IP привязывается к валидатору, control-api по
|
||||
@@ -833,6 +866,7 @@ https://api.ipify.org`) и логи `journalctl -u validator-agent` на пре
|
||||
|
||||
**Адрес постоянно проваливает self-check (не зависает, а именно
|
||||
возвращается в очередь снова и снова).**
|
||||
См. также [«Повтор после сбоя self-check»](#повтор-после-сбоя-self-check): повторы идут на других валидаторах, а список «Self-check не прошёл на: …» показан на странице адреса.
|
||||
Смотрите `events` по адресу (`GET /api/v1/admin/ips/{ip}`) — в детали
|
||||
события `self_check_result` будет указан обнаруженный исходящий адрес.
|
||||
Если он не совпадает с ожидаемым — вероятно, на ВМ-валидаторе есть другой
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# План: повтор после сбоя self-check — на другом валидаторе
|
||||
|
||||
Статус: реализовано, см. [итог](2026-10-04_08-01_self-check-exclude-validator-summary.md).
|
||||
|
||||
## 1. Проблема
|
||||
|
||||
В запуске 2 два адреса получили `fail`: `89.208.220.90` и `89.208.221.74`. Оба раза self-check провалился четыре раза подряд на одном валидаторе `vkiplab-v17` (он выходил в сеть с общего адреса облака `109.120.182.224`, а не с плавающего IP), с интервалом около 70 секунд.
|
||||
|
||||
Причина в логике повтора: после сбоя адрес возвращается в начало очереди, а освободившийся `v17` берёт следующий адрес с наименьшим `sequence`, то есть тот же. Один временно неисправный валидатор расходует все попытки адреса. Сам адрес исправен: `89.208.216.141`, попавший на `v17` дважды, после перехода на `v13` прошёл проверки.
|
||||
|
||||
## 2. Требования и принятые решения
|
||||
|
||||
1. Адрес, не прошедший self-check на валидаторе N, при повторе **не отдаётся валидатору N**. Правило действует только для этого адреса.
|
||||
2. Валидатор N остаётся в системе как был: не блокируется, не помечается неисправным, продолжает брать и проверять все остальные адреса.
|
||||
3. **Итог `fail`** ставится, когда число проваленных self-check у адреса достигло **потолка**. Потолок не зависит от числа валидаторов. Это **настройка** `self_check_max_attempts`: она меняется в разделе настроек `/settings` и через API; **значение по умолчанию для текущего окружения — 5**. Исключение валидаторов (п.1) гарантирует, что первые попытки идут на разных валидаторах: адрес получает `fail` после 5 провалов на 5 разных валидаторах (а не на одном).
|
||||
4. Исключение создаёт **только** проваленный self-check; на ошибки привязки плавающего IP, потерю валидатора и истечение аренды оно не распространяется.
|
||||
5. Сведения «Self-check не прошёл на: …» сохраняются и отдаются в API для последующего показа на странице аналитики.
|
||||
|
||||
## 3. Решение
|
||||
|
||||
### 3.1. Данные (миграция `0012_self_check_failures.sql`)
|
||||
|
||||
- Таблица `ip_self_check_failures(registry_id, run_id, cycle_id, attempt_number, validator_id, failed_at, detail)`. Постоянная история сбоев self-check по адресу; не очищается при перепроверках, живёт вместе с реестром (не удаляется вместе со строкой очереди) и служит источником для аналитики.
|
||||
- Колонки `ip_queue.sc_failures` (сколько self-check провалено в текущей серии попыток) и `ip_queue.sc_round_start_attempt` (номер попытки, с которой действуют исключения; «раунд»).
|
||||
- Настройка `self_check_max_attempts` в таблице `settings`: миграция записывает **5** (допустимо 1…50), дальше значение меняется в `/settings` или через API. Поле `orchestrator.max_self_check_retries` в YAML для self-check больше не используется (остаётся в файле для совместимости, в документации помечается устаревшим).
|
||||
|
||||
Исключение для валидатора N на адресе действует, если в `ip_self_check_failures` есть сбой этого валидатора на этом адресе с `attempt_number >= sc_round_start_attempt`.
|
||||
|
||||
### 3.2. Обработка сбоя (`Orchestrator.SelfCheckResult`, ветка «не прошёл»)
|
||||
|
||||
После проверки, что адрес принадлежит этому валидатору и ждёт self-check, и отвязки плавающего IP (как сейчас):
|
||||
|
||||
1. Записать сбой в `ip_self_check_failures`, увеличить `sc_failures`.
|
||||
2. Решение:
|
||||
- **`sc_failures >= self_check_max_attempts`** → `fail` (причина «self-check не прошёл N раз», в событии перечислены валидаторы);
|
||||
- иначе → вернуть адрес в очередь. Если среди рабочих валидаторов (`idle`, `assigned`, `checking`; `unreachable` и `unregistered` не считаются) не осталось ни одного без сбоя в текущем раунде, начинается новый раунд (`sc_round_start_attempt` = следующая попытка): исключения теряют силу, повтор идёт по обычным правилам. Так при числе валидаторов меньше потолка (в том числе при единственном) повторы продолжаются до потолка, а адрес не застревает в очереди.
|
||||
|
||||
Лимит `max_retries` и счётчик `retry_count` для self-check больше не используются: возврат в очередь после сбоя self-check не увеличивает `retry_count` и не может привести к `fail` по нему. Для остальных причин возврата (ошибка привязки, истечение аренды) всё остаётся как сейчас.
|
||||
|
||||
### 3.3. Выдача адресов (`DB.ClaimNextQueued`)
|
||||
|
||||
Валидатор получает ближайший по `sequence` адрес, **кроме** тех, где он исключён (по 3.1). Исключённый адрес остаётся в очереди для остальных валидаторов и очередь не блокирует. Запрос по-прежнему одной транзакцией.
|
||||
|
||||
### 3.4. Жизненный цикл
|
||||
|
||||
- Ручная перепроверка или повторная постановка адреса (`SubmitIPs`) начинает новую серию: `sc_failures = 0`, `sc_round_start_attempt` = текущая попытка.
|
||||
- История в `ip_self_check_failures` остаётся.
|
||||
- Удаление адреса и очистка очереди историю не трогают; `docs/ADMIN_CLEANUP.md` дополняется новой таблицей.
|
||||
|
||||
### 3.5. Данные для аналитики и видимость
|
||||
|
||||
- Событие `self_check_result` остаётся как сейчас; добавляется событие `validator_excluded` (`{"validator_id": …, "failures": N}`).
|
||||
- `GET /admin/ips/{ip}` и `GET /admin/registry/{ip}` получают поле `self_check_failed_on` (список валидаторов, у которых self-check на этом адресе не прошёл; для реестра — по всем запускам, для запуска — фильтр по `run_id`).
|
||||
- Строка «Self-check не прошёл на: v17, …» на странице адреса (`/ips/{ip}`, `/registry/{ip}`).
|
||||
- **Отображение на странице аналитики не входит в эту доработку:** данные и API будут готовы, показ (колонка в списках адресов, блок «Self-check по валидаторам») делается следующим шагом отдельным планом.
|
||||
|
||||
### 3.6. Настройка в интерфейсе (`/settings`)
|
||||
|
||||
Изменения отражаются в разделе настроек явно:
|
||||
- В форме с `fip_settle_seconds` и `history_retention_cycles` добавляется поле **«Потолок провалов self-check на адрес»** (`self_check_max_attempts`), целое число, по умолчанию 5, допустимо 1…50.
|
||||
- Под полем пояснение: «Сколько раз self-check может не пройти у одного адреса, прежде чем адрес получит итог `fail`. Повторы идут на других валидаторах: валидатор, на котором адрес не прошёл self-check, этому адресу больше не выдаётся (на остальные адреса это не влияет)».
|
||||
- Значение сохраняется той же кнопкой «Сохранить», действует на следующих повторах без перезапуска; неверное значение (не число, вне 1…50) показывается предупреждением в баннере, форма остаётся прежней.
|
||||
- В API: `GET/PUT /admin/config/orchestrator` получает поле `self_check_max_attempts` (ошибка 400 при неверном значении).
|
||||
- Описание поля добавляется в `docs/DASHBOARD.md` (строка `/settings`) и `docs/USAGE.md`.
|
||||
|
||||
## 4. Файлы
|
||||
|
||||
- БД: `internal/db/migrations/0012_self_check_failures.sql`, `db.go`, `queries_ipqueue.go` (`ClaimNextQueued`, запись сбоя, новый возврат в очередь без `retry_count`, принудительный `fail`, сброс серии в `SubmitIPsAs`), `queries_settings.go` (настройка), запрос истории для API.
|
||||
- Оркестратор: `orchestrator.go` (`SelfCheckResult`), `config.go` (начальное значение настройки).
|
||||
- API и дашборд: `handlers_admin.go`, `handlers_config.go`, DTO, `handlers_settings.go`, `settings.html`, `ip_detail.html`, `registry_detail.html`.
|
||||
- Документы: `USAGE.md` («Как читать итоговый результат», повторы self-check, настройка), `API.md`, `ADMIN_CLEANUP.md`, `README.md`, итог `docs/changes/…-summary.md`.
|
||||
|
||||
## 5. Тесты
|
||||
|
||||
- **БД:** валидатор, на котором адрес исключён, пропускает его и берёт следующий; другой валидатор берёт исключённый адрес; исключения действуют только для этого адреса; новый раунд снимает исключения; перепостановка сбрасывает серию, но не историю; настройка (значения по умолчанию, границы).
|
||||
- **Оркестратор (сценарий инцидента на трёх валидаторах):** v1 всегда проваливает self-check — адрес не возвращается на v1, проходит на v2, а v1 в это время берёт и проходит другие адреса; потолок: адрес, провалившийся `self_check_max_attempts` раз, получает `fail`, раньше — нет; при потолке меньше числа валидаторов `fail` наступает на потолке без перебора всех; при потолке больше числа валидаторов начинается новый раунд; единственный валидатор повторяет на себе до потолка; `unreachable` не считается валидатором; сбой привязки и истечение аренды исключений и счётчика self-check не создают и по-прежнему идут по `max_retries`; изменение настройки действует на следующем повторе.
|
||||
- **API и дашборд:** поле `self_check_failed_on`; поле «Потолок провалов self-check на адрес» отображается в `/settings` со значением 5, сохраняется, неверные значения показывают предупреждение; поле в `GET/PUT /admin/config/orchestrator`; строка «Self-check не прошёл на: …» на странице адреса.
|
||||
- Миграция `0012` на копии боевой БД (настройка равна 5), повторный запуск безопасен. Полный `go build ./... && go vet ./... && go test ./...`.
|
||||
|
||||
## 6. Выкладка и проверка
|
||||
|
||||
Пересборка и перезапуск `control-api` и `admin-dashboard` (меняется форма настроек и страница адреса); агенты и prober без изменений. Порядок: проверка пустой очереди, копия БД, тег отката образа, миграция `0012`. Сбой на стенде воспроизвести нельзя, поэтому проверка там — штатный небольшой прогон без зависших `queued`; логику повторов покрывает тест на трёх валидаторах.
|
||||
|
||||
## 7. Риски и последствия принятого решения
|
||||
|
||||
- **Время до `fail` у неисправного адреса** ограничено потолком: 5 попыток по ~70 секунд, около 6 минут вместо ~5 минут сейчас (4 попытки). Очередь и другие валидаторы это не блокирует.
|
||||
- **Потолок меньше числа валидаторов.** Адрес, которому не повезло с 5 неисправными валидаторами подряд, получит `fail`, хотя на остальных 15 прошёл бы. Это сознательный компромисс в пользу ограниченного времени; потолок меняется в настройках.
|
||||
- Адрес, исключённый на части валидаторов, может чуть дольше ждать подходящего валидатора.
|
||||
- Если сбой общий (проблема облака для всех валидаторов), адрес в итоге получит `fail`; это верное поведение.
|
||||
- Не решается: причина сбоя `v17` в облаке (трансляция плавающего IP) — её нужно смотреть в OpenStack.
|
||||
|
||||
## 8. Решения по согласованию (все приняты)
|
||||
|
||||
1. Потолок попыток задаётся независимо от числа валидаторов; значение меняется в меню настроек (`/settings`) и через API.
|
||||
2. Значение по умолчанию для текущего окружения — 5.
|
||||
3. Исключение действует только для проваленного self-check; на ошибки привязки не распространяется.
|
||||
4. «Self-check не прошёл на: …» сохраняется и отдаётся в API; показ на странице аналитики — следующим шагом.
|
||||
5. Изменения отражаются в разделе настроек UI (п.3.6).
|
||||
|
||||
Открытых вопросов нет. Жду команды начать реализацию.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Итог: повтор после сбоя self-check — на другом валидаторе
|
||||
|
||||
План: [2026-10-04_08-01_self-check-exclude-validator-plan.md](2026-10-04_08-01_self-check-exclude-validator-plan.md).
|
||||
Статус: код написан и проверен (gofmt, build, vet, test, миграция на копии боевой БД); стенд **не пересобирался**.
|
||||
|
||||
## Что изменено
|
||||
|
||||
- **Миграция `0012_self_check_failures.sql`** (версия схемы 12): таблица `ip_self_check_failures` (история сбоев по адресу, по `registry_id`, без внешних ключей); колонки `ip_queue.sc_failures` и `ip_queue.sc_round_start_cycle`; настройка `settings.self_check_max_attempts` (`DEFAULT 5`, допустимо 1…50).
|
||||
- **`db.ClaimNextQueued`** пропускает адреса, на которых этот валидатор провалил self-check в текущем раунде; адрес остаётся в очереди для других валидаторов.
|
||||
- **`db.FailSelfCheck`** (одна транзакция): записывает сбой, увеличивает `sc_failures`; при `sc_failures >= потолка` — `fail`; иначе возвращает адрес в очередь без изменения `retry_count`; если рабочих валидаторов без сбоя в раунде не осталось — новый раунд. Позднее сообщение (адрес не у этого валидатора) — `ErrInvalidState`.
|
||||
- **Оркестратор** (`SelfCheckResult` → `failSelfCheck`): потолок читается из настроек на каждом сбое. События: `retry_or_fail` (при `fail` — со списком валидаторов) и `validator_excluded`. `max_self_check_retries` не используется (поле в YAML осталось).
|
||||
- **`SubmitIPsAs`/`SeedQueue`**: перепостановка начинает новую серию (`sc_failures = 0`, новый раунд); история остаётся.
|
||||
- **API**: `GET/PUT /admin/config/orchestrator` — поле `self_check_max_attempts` (400 вне 1…50; в PUT необязательно); `self_check_failed_on` в `GET /admin/ips/{ip}` (фильтр по запуску) и `GET /admin/registry/{ip}` (все запуски).
|
||||
- **Дашборд**: поле «Потолок провалов self-check на адрес» на `/settings`; строка «Self-check не прошёл на: …» на `/ips/{ip}` и `/registry/{ip}`.
|
||||
- **Документы**: `USAGE.md` (новый раздел «Повтор после сбоя self-check»), `API.md`, `DASHBOARD.md`, `ADMIN_CLEANUP.md` (новая таблица в сценариях 3.1 и 3.4), `README.md`.
|
||||
|
||||
## Отступления от плана
|
||||
|
||||
1. Раунд хранится как `sc_round_start_cycle` (номер `cycle_id`), а не `sc_round_start_attempt`: `attempt_number` сбрасывается при удалении и повторном создании строки очереди, а история остаётся — новая строка получила бы чужие исключения. `cycle_id` по адресу не повторяется. Поведение для оператора то же.
|
||||
2. Значение 5 задано `DEFAULT 5` колонки (покрывает и существующую строку `settings`, и чистую установку), `config.go` не менялся.
|
||||
3. В `PUT` потолок необязателен — старые клиенты без поля не получают 400.
|
||||
4. `validator_excluded` не пишется, если начался новый раунд (исключение сразу теряет силу).
|
||||
|
||||
## Проверки
|
||||
|
||||
- `gofmt -l` — пусто; `go build ./...`, `go vet ./...` — без ошибок; `go test ./...` — все пакеты `ok`. Две проверки версии схемы в старых тестах миграций (`TestMigration0010…`, `TestMigration0011…`) обновлены с 11 на 12.
|
||||
- Новые тесты: БД (`queries_selfcheck_test.go`), оркестратор (сценарий на трёх валидаторах, потолок, один и два валидатора, смена настройки), API, дашборд.
|
||||
- Миграция `0012` на копии боевой БД: версия 11 → 12, `self_check_max_attempts = 5`, повторное открытие без ошибок, таблица сбоев пуста.
|
||||
|
||||
## Выкладка
|
||||
|
||||
Не выполнена. Нужны пересборка и перезапуск `control-api` и `admin-dashboard` по процедуре (проверка пустой очереди, копия БД, тег отката `pre-self-check-exclude`, миграция `0012`); на стенде в очереди сейчас 6489 адресов `done`, `queued` нет.
|
||||
Reference in new issue
Block a user