Задачи 025-030: ёмкость префиксов, политика входа, дерево префиксов

Повторный анализ кодовой базы (docs/reviews/2026-09-26-codebase-review-2.md) и доработки:
025 Ёмкость префикса — размер его подсети (а не сумма листьев); «Обзор» считает ёмкость
    по корневым активным IPv4-префиксам и адреса внутри них.
026 Политика блокировки входа: 5 неудач на логин+IP, 20 на IP, 50 на логин со всех IP,
    кроме известных IP (known_logins, миграция 0009) — владельца нельзя заблокировать анонимно.
027 Сериализация попыток входа по IP (advisory-lock после блокировки логина).
028 UI «Префиксы»: загрузка всех страниц (до 20 000), счётчики по total, предупреждение об усечении.
029 Advisory-lock по VRF для операций, меняющих дерево префиксов и раскладку адресов.
030 Исправление замечаний ревью 025-029: _lock_prefix (VRF блокируется до чтения префикса,
    409 при одновременном переносе), константы политики входа перенесены в app/services.py.

Тесты: 14 passed (проверка ёмкости родителя приведена к семантике 025); сквозные сценарии
и гонки — docs/reviews/2026-09-26-changes-025-029-review.md, 2026-09-27-changes-030-review.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Opus 5.5 committed 2026-09-27 08:28:50 +03:00
1 parent 13e17fbb47
commit 03d727e496
25 files changed
+877 -78

No files matched your search

@@ -0,0 +1,47 @@
# Ревью и тестирование изменения 030 — 2026-09-27
**Объём:** правки агента (Sonnet 5) по плану `docs/changes/030-review-fixes-025-029/PLAN.md`, которые закрывают замечания 1 и 2 ревью `2026-09-26-changes-025-029-review.md`.
Изменены `app/api/v1/prefixes.py`, `app/api/v1/auth.py`, `app/services.py`, `app/rotation.py`.
**Тестировались только доработки 1 и 2.** Помимо этого выполнен регрессионный прогон `pytest`.
## Ревью кода
### Замечание 1 — `_lock_prefix`
Хелпер работает в таком порядке:
1. `SELECT vrf_id` префикса;
2. `_lock_vrf` для этого VRF и дополнительных (целевого VRF при переносе), одним вызовом, по возрастанию id;
3. повторное чтение префикса под `FOR UPDATE` с `populate_existing`;
4. если `vrf_id` к этому моменту изменился — 409 без повтора.
Хелпер применён в `delete_prefix`, `update_prefix` (только при смене VRF), `create_address`, `allocate_subnet`, `allocate_next`.
- **Порядок блокировок единый:** везде сначала advisory-блокировка VRF, затем строка префикса. `update_prefix` без смены VRF блокирует только строку (при flush) и не запрашивает блокировку VRF, поэтому цикла ожидания нет.
- **Отказ от повтора обоснован.** Повтор оставил бы в транзакции блокировку устаревшего VRF и нарушил бы возрастающий порядок захвата между попытками. Сценарий 1d показал, что ветка 409 реально срабатывает.
- **Встречные переносы** (A: 5→3, B: 3→5) блокируют пару VRF одним вызовом в одинаковом порядке, взаимной блокировки нет. Подтверждено сценарием 1c.
- **Побочный эффект:** `create_address` теперь берёт `FOR UPDATE` строки префикса, и назначения адресов в одном префиксе выполняются по одному. На нагрузку это не влияет, операции короткие.
### Замечание 2 — константы входа
- `LOGIN_WINDOW`, `MAX_PER_LOGIN_IP`, `MAX_PER_IP`, `MAX_PER_LOGIN`, `KNOWN_IP_DAYS` перенесены в `app/services.py`.
- `auth.py` и `rotation.py` импортируют их оттуда. Пространства ключей advisory-блокировок остались в `auth.py`.
- Импорт `app.rotation` больше не загружает модули `app.api.*`: проверено по `sys.modules`, список пуст. Докстринг `auth.py` форматируется корректно.
Новых замечаний нет.
## Тесты
| Проверка | Результат |
|---|---|
| Стенд `ipam_control_006` пересобран, `app` healthy | ✅ |
| Регрессия `pytest -q` | ✅ 14 passed |
| 2: `app.rotation` не импортирует API-слой | ✅ |
| 2: политика входа не изменилась: 5 неудач → 401×4 + 429, на верный пароль тоже 429, `Retry-After` ≈ 600 с | ✅ |
| 1a: удаление префикса ∥ вставка промежуточного префикса, ×20 | ✅ все пары ответов 204/201; дерево согласовано: у каждого префикса родитель — самый узкий охватывающий |
| 1b: перенос в другой VRF ∥ создание адреса в переносимом префиксе, ×20 | ✅ без 500 и взаимных блокировок; у всех адресов `vrf_id` совпадает с VRF их префикса; все 20 префиксов перенесены |
| 1c: встречные переносы двух префиксов между двумя VRF, ×15 | ✅ всё 200/200, взаимных блокировок нет |
| 1d: перенос ∥ удаление того же префикса, ×10 | ✅ только ожидаемые исходы: 5× (перенос 200, удаление 409 «одновременно изменяется»), 5× (перенос 404, удаление 204) |
| Лог приложения: `deadlock` / `Traceback` | ✅ 0 |
Сценарии выполнялись скриптом во временной папке, в репозиторий он не добавлен. Временные данные (`vt30-*`), попытки входа и записи `known_logins` удалены.
## Вывод
Замечания 1 и 2 закрыты. Изменения 025–030 готовы к коммиту.