Files
ipam_control/docs/changes/021-concurrency-locks/PLAN.md
T

24 lines
2.7 KiB
Markdown
Raw Normal View History

# Гонки: последний администратор и регистр логина (изменение 021)
Находка ревью № 11, серьёзность — низкая. Блокировка автоназначения адреса — в № 020.
## Context
- `update_user` / `delete_user` проверяют «последний активный администратор» через `count()` без блокировки: два администратора, одновременно отключающие друг друга,
могут оставить систему без администратора.
- Логин уникален без учёта регистра только на уровне проверки в коде; в БД `users.username` уникален с учётом регистра — параллельное создание `Ivanov`/`ivanov` проходит.
## Решение
1. **Сериализация изменений администраторов:** в `update_user` и `delete_user`, если операция может лишить учётную запись прав администратора (`loses_admin` / удаление активного admin),
до проверки выполнить `pg_advisory_xact_lock(<USERS_ADMIN_LOCK>)`; проверка и изменение — в одной транзакции. Остальные изменения пользователей не блокируются.
2. **Уникальность логина в БД:** новая миграция — уникальный индекс `lower(username)`; перед созданием миграция проверяет дубли по регистру и останавливается с перечнем.
Предварительная проверка в `create_user` остаётся (даёт понятный 409), `flush` ловит гонку.
3. Вход: поиск пользователя остаётся точным по регистру (логин = `sub` токена); при желании — отдельное решение, не в этом изменении.
## Файлы
`app/api/v1/users.py`, `alembic/versions/<next>_users_lower_username.py`, `app/models.py` (`Index`), `tests/test_users.py`.
## Тест
Два активных администратора (временные), параллельно (потоки) каждый отключает другого → ровно одна операция успешна, вторая 409; активный администратор остаётся.
## Проверка
`pytest -q`; миграция на стенде проходит (дублей нет).