Files
ipam_control/docs/changes/010-next-free-prefix/PLAN.md
T

38 lines
6.4 KiB
Markdown
Raw Normal View History

# Автовыделение следующего вложенного префикса (изменение 010)
## Context
Когда префикс-контейнер (например `172.20.0.0/24`) дробят на подсети одного размера (`/30`), свободный блок приходится считать вручную.
Нужна кнопка: пользователь выбирает родителя и размер (`/30`), система сама находит первый свободный выровненный блок внутри родителя и создаёт дочерний префикс.
## Алгоритм выбора блока (`app/services.py`, чистая функция, без БД)
`next_free_subnet(parent_cidr, length, occupied) -> str | None`:
1. `occupied` — диапазоны `[start, end]` (целые) всех **уже существующих префиксов внутри родителя в том же VRF** (все уровни вложенности)
и адресов, записанных на самом родителе (чтобы не выдать блок, где уже есть назначенные IP). Диапазоны сортируются и склеиваются.
2. Кандидат начинается с адреса родителя и выравнивается по размеру блока (`ceil(start / size) * size`); если пересекает занятый диапазон — перескок за его конец, повторное выравнивание.
Сложность O(n) от числа занятых диапазонов, без перебора всех подсетей (работает и для `/8 → /30`, и для IPv6).
3. Кандидат не выходит за границу родителя; иначе `None`. Первый подходящий (наименьший адрес) — предсказуемо: `.0/30`, `.4/30`, `.8/30`…; для смешанных размеров блок выравнивается сам (`/29` после `.0/30`, `.4/30` → `.8/29`).
## API (`app/api/v1/prefixes.py`, схемы в `app/schemas.py`)
- `POST /prefixes/{id}/subnets/next` (admin) — тело `SubnetNextIn`: `length` (int), `description`, `status` (по умолчанию `active`), `is_pool`, `note`. Создаёт префикс в VRF и организации родителя, `parent_id = id`.
Возвращает `PrefixOut` (201). Ошибки: `length` не больше длины родителя или больше 32/128 → 422 (с пределами в тексте); нет свободного блока → 409 «В префиксе нет свободного блока /30».
Родитель блокируется `SELECT … FOR UPDATE` — параллельные запросы не выдают один блок; уникальность `(vrf_id, prefix)` остаётся страховкой (`flush` → 409).
Переиспользуются `get_or_404`, `flush/commit`, `attach_to_tree(keep_parent=True)` (подхват существующих вложенных, если они шире), `audit(... "created", …, {"vrf": …, "allocated_from": "<родитель>"})`, `_prefix_outs`.
- `GET /prefixes/{id}/subnets/next?length=30` — предпросмотр без создания: `{"prefix": "172.20.0.4/30"|null, "length_min": 25, "length_max": 32}` (для окна в UI; те же проверки диапазона).
- БД, миграции, существующие эндпоинты не меняются.
## UI (`web/app.js`)
- Пункт **«Добавить вложенный (авто)»** в меню «⋯» строки префикса: у листовых — рядом с «Открыть адреса/Редактировать/Удалить»; у родителей (сейчас без меню) — меню из этого одного пункта.
Доступно только admin (как остальные меню записи).
- Окно `subnetDialog(parent)` «Новый вложенный префикс в 172.20.0.0/24»: выпадающий «Размер» (`/25 … /32` для v4, для v6 — до `/128`; по умолчанию — длина последнего дочернего префикса, иначе `/30` (v4) / `/64` (v6) в пределах допустимого),
строка «Будет создан: **172.20.0.4/30**» (обновляется по `change` списка через `GET …/subnets/next`; при отсутствии места — предупреждение и заблокированная кнопка), «Описание», «Статус», «Примечание», флажок «Пул». Использует `openDialog`, `formBody`, `fSelect`, `fInput`, `dlgFoot`, `S.dialog`.
- После успеха: toast «Создан префикс 172.20.0.4/30», родитель раскрывается (`S.collapsed.delete(parent.id)`), `draw()`.
## Артефакты и тесты
- `docs/changes/010-next-free-prefix/PLAN.md` (копия) и `SUMMARY.md`; `README.md` — раздел API/Модель данных: алгоритм и эндпоинты.
- Один тест `test_allocate_next_subnet` в `tests/test_api.py`: родитель `/24` + ручной `/30` в начале → `/30` даёт `.4/30`; `/29` даёт `.8/29` (выравнивание); `parent_id` = родитель; предпросмотр совпадает с созданием;
`length` ≤ длины родителя → 422; после заполнения всех блоков `/25` → 409. Плюс мини-проверка чистой функции на IPv6 (в том же тесте).
## Проверка
1. `node --check web/app.js`; `docker compose -p ipam_control_006 up -d --build app`; `venv/bin/python -m pytest -q`.
2. Браузером (Playwright headless, как в 009): создать `172.20.0.0/24` → вручную `172.20.0.0/30` → на родителе «⋯» → «Добавить вложенный (авто)» → в окне «Будет создан: 172.20.0.4/30» → создать 3 раза подряд (`.4`, `.8`, `.12`) → строки в дереве под родителем, родитель раскрыт;
размер `/29` → следующий выровненный блок; в «Журнале» записи `prefix.created`. Тестовые данные удалить.