Files
ayurishchevandClaude Sonnet 5 cd09ef0805 Задачи 006-010: пользователи, исправление удаления, журнал отказов, групповые операции, автовыделение префиксов
006 Пользователи: API /users (CRUD, смена своего пароля), раздел UI «Пользователи»,
    события журнала user.*, защита от отключения/удаления себя и последнего админа.
007 Исправление удаления организации: VRF удаляются явным DELETE до организации
    (без relationship() порядок DELETE не гарантирован → ложный 409).
008 Журнал фиксирует отказы в удалении (<entity>.delete_blocked) со списком
    мешающих объектов в «Данных»: организация, VRF, тип устройства, префикс, пользователь.
009 Выбор строк чекбоксами и групповые операции в UI (удаление, смена типа устройств,
    статус префиксов и адресов, доступ пользователей); цикл запросов из UI, итог и список отказов.
010 Автовыделение следующего вложенного префикса: POST/GET /prefixes/{id}/subnets/next,
    первый свободный выровненный блок; пункт «Добавить вложенный (авто)» в меню префикса.

Тесты: 14 (добавлены сценарии для 006, 007/008, 010); исправлена нестабильность
тестов журнала (IPv6-группы с ведущими нулями нормализуются PostgreSQL).
Документация: README.md, docs/changes/006-010 (планы и итоги).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 13:25:33 +03:00

6.4 KiB

Автовыделение следующего вложенного префикса (изменение 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. Тестовые данные удалить.