diff --git a/README.md b/README.md index 4f50f93..87a4b25 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ scripts/run-local-e2e.sh # сквозной прог | [docs/USAGE.md](docs/USAGE.md) | Повседневная работа: очередь, сканирование Floating IP, автоматический цикл, статус, реестр и история, разбор результатов | | [docs/API.md](docs/API.md) | Спецификация HTTP API `control-api` и примеры запросов (curl) | | [docs/DASHBOARD.md](docs/DASHBOARD.md) | Устройство `admin-dashboard`: страницы, поиск и фильтр, обработка ошибок | +| [docs/ADMIN_CLEANUP.md](docs/ADMIN_CLEANUP.md) | Ручная очистка БД администратором (SQL): сброс данных перед новым прогоном, журнал событий, реестр проверок, отдельные адреса, резервная копия и восстановление | | [docs/DIAGRAMS.md](docs/DIAGRAMS.md) | Диаграммы потоков данных: control plane, egress-проверка, телеметрия | | [docs/LOCAL_E2E.md](docs/LOCAL_E2E.md) | Полностью офлайн-прогон всей системы одним скриптом | | [docs/changes/](docs/changes/) | Планы доработок и отчёты ревью с отметкой времени в имени файла (последняя: [скан при тысячах адресов](docs/changes/2026-10-01_18-59_fip-scan-at-scale-review.md)) | diff --git a/docs/ADMIN_CLEANUP.md b/docs/ADMIN_CLEANUP.md new file mode 100644 index 0000000..5857d43 --- /dev/null +++ b/docs/ADMIN_CLEANUP.md @@ -0,0 +1,248 @@ +# Ручная очистка базы данных control-api (SQL) + +Когда нужна: подготовка к новому полному прогону, разбор после инцидента, освобождение места, удаление отдельных адресов. +Все примеры проверены 2026-10-02 на копии боевой БД (20 валидаторов, 4 площадки, 6445 адресов в реестре, 29 889 проверок, +19 897 событий): без ошибок, `integrity_check` = `ok`, нарушений внешних ключей нет. + +> **Сначала API.** Если в очереди есть адреса в работе, очищайте очередь штатно: кнопка «Очистить всё» на `/ips` или +> `POST /api/v1/admin/ips/clear` (см. [USAGE.md](USAGE.md#удаление-адресов-из-очереди)). Только так control-api отвяжет Floating IP от портов +> валидаторов. SQL ниже работает с базой «в покое»: он не обращается к OpenStack. + +## 1. Что в базе и что нельзя трогать + +| Группа | Таблицы | Можно чистить | +|---|---|---| +| Данные прогона | `ip_queue` (очередь), `ip_registry` (реестр адресов), `checks` (реестр проверок), `ip_site_checks` (признаки площадок по адресам в работе), `events` (журнал событий) | да | +| Настройки (не трогать) | `validators`, `sites`, `target_groups` (цели), `check_types`, `inbound_checks_settings`, `settings`, `auto_cycle` | **нет** | +| Служебное | `sqlite_sequence` (нумерация записей), `PRAGMA user_version` (версия схемы) | нумерацию можно сбросить, версию не менять | + +Связи (внешние ключи): `checks`, `events`, `ip_site_checks` ссылаются на `ip_queue`; `checks`, `events`, `ip_queue` — на `ip_registry`; +`validators.current_ip_id` — на `ip_queue`. Поэтому порядок удаления всегда такой: сначала `validators.current_ip_id` в `NULL`, затем +`ip_site_checks`, `checks`, `events`, `ip_queue` и в конце `ip_registry`. Времена в БД хранятся строками `2026-10-02T07:14:58.857Z` (UTC). + +## 2. Подготовка (всегда, перед любой очисткой) + +**2.1. Где лежит база и чем её открывать.** Нужен клиент `sqlite3` на хосте (`apt install sqlite3`). + +| Развёртывание | Файл базы | +|---|---| +| `rxprod-compose/` (боевой стенд) | `rxprod-compose/capi-db/control-api.db` | +| Docker (`deploy/docker`) | в томе `cloud-ip-validator-db`: `docker volume inspect cloud-ip-validator-db --format '{{.Mountpoint}}'`, файл `control-api.db` в этом каталоге (нужен root) | +| systemd | `/var/lib/cloud-ip-validator/control-api.db` (`database.path` в `control-api.yaml`) | + +Дальше в примерах `DB=путь/к/control-api.db`. + +**2.2. Проверить, что в очереди ничего не в работе:** + +```bash +sqlite3 -readonly "$DB" " +SELECT state, COUNT(*) FROM ip_queue +WHERE state NOT IN ('done', 'failed', 'occupied') GROUP BY state;" +``` + +Пустой вывод — всё завершено. Адреса в работе есть — сначала «Очистить всё» через API (см. выше). Затем проверьте в OpenStack, что на портах +валидаторов нет лишних Floating IP; по базе видно только то, что control-api считает привязанным: + +```bash +sqlite3 -readonly "$DB" " +SELECT ip_address, state, fip_id FROM ip_queue +WHERE fip_id <> '' AND state NOT IN ('done', 'failed', 'occupied');" +``` + +**2.3. Остановить control-api.** Он держит базу открытой и пишет в неё на каждом такте; ручные правки поверх работающего процесса +ненадёжны. + +```bash +cd rxprod-compose && docker compose stop control-api # compose-развёртывание +sudo systemctl stop control-api # systemd +``` + +**2.4. Сделать резервную копию** (штатной командой SQLite, не `cp`: у базы есть журнал `-wal`): + +```bash +TS=$(date -u +%Y-%m-%d_%H-%M) +sqlite3 "$DB" ".backup '$(dirname "$DB")/backup-$TS-before-cleanup.db'" +sqlite3 -readonly "$(dirname "$DB")/backup-$TS-before-cleanup.db" "PRAGMA integrity_check;" # должно быть: ok +``` + +Лог контейнера до чистки при необходимости сохраните отдельно: `docker logs <контейнер> > backup-$TS.container.log 2>&1`. + +**2.5. Посмотреть, что и сколько лежит** (до и после чистки): + +```bash +sqlite3 -readonly "$DB" " +SELECT 'ip_queue' AS tbl, COUNT(*) AS n FROM ip_queue +UNION ALL SELECT 'ip_registry', COUNT(*) FROM ip_registry +UNION ALL SELECT 'checks', COUNT(*) FROM checks +UNION ALL SELECT 'ip_site_checks', COUNT(*) FROM ip_site_checks +UNION ALL SELECT 'events', COUNT(*) FROM events +UNION ALL SELECT 'validators', COUNT(*) FROM validators +UNION ALL SELECT 'sites', COUNT(*) FROM sites;" +``` + +## 3. Сценарии + +Команды выполняются так: `sqlite3 "$DB"` и вставить блок, либо сохранить блок в файл и выполнить `sqlite3 "$DB" < файл.sql`. + +### 3.1. Полный сброс данных прогона (перед новым полным прогоном) + +Очищает очередь, реестр адресов, реестр проверок, журнал событий. Валидаторы, площадки, цели, типы проверок и все настройки остаются. + +```sql +PRAGMA foreign_keys = ON; +BEGIN; +-- валидаторы больше не ссылаются на адреса очереди +UPDATE validators SET current_ip_id = NULL WHERE current_ip_id IS NOT NULL; +UPDATE validators SET state = 'idle' WHERE state = 'assigned'; +-- порядок важен: сначала зависимые таблицы +DELETE FROM ip_site_checks; +DELETE FROM checks; +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'); +COMMIT; +``` + +Затем освободите место (отдельной командой, не внутри транзакции): + +```sql +PRAGMA wal_checkpoint(TRUNCATE); +VACUUM; +``` + +Файл сжимается до сотен килобайт (на проверочной копии: 20 МБ → 128 КБ). + +### 3.2. Только журнал событий + +Очередь, реестр и проверки не затрагиваются. + +Весь журнал: + +```sql +DELETE FROM events; +DELETE FROM sqlite_sequence WHERE name = 'events'; +``` + +Только старше 7 дней (число дней меняйте в `'-7 days'`): + +```sql +DELETE FROM events +WHERE occurred_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days'); +``` + +### 3.3. Только реестр проверок (история проверок) + +Адреса и их итоги в очереди остаются; пропадает подробная история проверок (на странице «Реестр» исчезнут результаты). + +Вся история: + +```sql +DELETE FROM checks; +DELETE FROM sqlite_sequence WHERE name = 'checks'; +``` + +Оставить последние 3 цикла каждого адреса (число `3` меняйте): + +```sql +DELETE FROM checks +WHERE cycle_id <= (SELECT MAX(c2.cycle_id) FROM checks c2 WHERE c2.registry_id = checks.registry_id) - 3; +``` + +Постоянное ограничение глубины истории лучше задать настройкой `history_retention_cycles` (страница `/settings` или +`PUT /api/v1/admin/config/orchestrator`): control-api сам подрезает историю при завершении каждого адреса. SQL выше нужен для разовой чистки. + +### 3.4. Удалить конкретные адреса целиком + +Удаляет адрес из очереди и реестра вместе со всей его историей (проверки и события). Список адресов подставьте в первую команду +`CREATE TEMP TABLE doomed_reg`. Адрес, который сейчас проверяется, удалять этим способом нельзя: используйте API (раздел 5). + +```sql +PRAGMA foreign_keys = ON; +BEGIN; +CREATE TEMP TABLE doomed_reg AS + SELECT id FROM ip_registry WHERE ip_address IN ('5.188.140.6', '5.188.140.62'); -- ваши адреса +CREATE TEMP TABLE doomed_ip AS + SELECT id FROM ip_queue WHERE registry_id IN (SELECT id FROM doomed_reg); +UPDATE validators SET current_ip_id = NULL WHERE current_ip_id IN (SELECT id FROM doomed_ip); +DELETE FROM ip_site_checks WHERE ip_id IN (SELECT id FROM doomed_ip); +DELETE FROM checks 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); +DROP TABLE doomed_ip; +DROP TABLE doomed_reg; +COMMIT; +``` + +### 3.5. Только освободить место + +Если удалили много, а файл не уменьшился (SQLite не отдаёт место ОС до `VACUUM`): + +```sql +PRAGMA wal_checkpoint(TRUNCATE); +VACUUM; +``` + +Размер и свободные страницы: + +```sql +SELECT page_count * page_size / 1024 AS size_kb, freelist_count * page_size / 1024 AS free_kb +FROM pragma_page_count(), pragma_page_size(), pragma_freelist_count(); +``` + +## 4. После очистки: запуск и проверка + +```bash +cd rxprod-compose && docker compose up -d --no-deps control-api # или: sudo systemctl start control-api +``` + +Если нужно, чтобы и лог контейнера начался с нуля, пересоздайте контейнер: `docker compose up -d --force-recreate --no-deps control-api` +(старый лог сохраните заранее, п. 2.4). + +Проверка базы (до запуска или на копии): + +```bash +sqlite3 -readonly "$DB" " +PRAGMA integrity_check; +PRAGMA foreign_key_check; +SELECT state, COUNT(*) FROM validators GROUP BY state; +SELECT COUNT(*) AS validators_with_address FROM validators WHERE current_ip_id IS NOT NULL; +SELECT COUNT(*) AS queue_rows FROM ip_queue;" +``` + +Ожидается: `ok`; пустой результат `foreign_key_check`; у валидаторов состояние `idle`; `validators_with_address` = 0; для полного сброса `queue_rows` = 0. +Через API: `GET /api/v1/admin/status` (`total_ips` = 0, `total_validators` = число валидаторов) и `GET /api/v1/admin/validators` +(через 10–15 секунд после запуска у всех свежий `last_heartbeat_at`). + +## 5. Что делать через API, а не через SQL + +| Задача | Как | +|---|---| +| Остановить проверку адреса, удалить адрес в работе | `POST /api/v1/admin/ips/{ip}/cancel`, `DELETE /api/v1/admin/ips/{ip}` | +| Очистить всю очередь с отвязкой Floating IP | `POST /api/v1/admin/ips/clear` | +| Перепроверить завершённые адреса | `POST /api/v1/admin/ips` со списком адресов | +| Валидатор «завис» с адресом | ничего не править: лизинг истечёт, адрес вернётся в очередь, валидатор освободится сам | +| Добавить/убрать валидатор, площадку, цель | `/api/v1/admin/config/*` или страницы дашборда | + +## 6. Восстановление из копии + +```bash +cd rxprod-compose && docker compose stop control-api +cp capi-db/backup-<метка>-before-cleanup.db capi-db/control-api.db +rm -f capi-db/control-api.db-wal capi-db/control-api.db-shm # старый журнал к новой копии не относится +docker compose up -d --no-deps control-api +``` + +## 7. Ловушки + +- Не выполняйте `DELETE` при работающем control-api: он пишет в ту же базу. +- Не удаляйте строки настроек (`validators`, `sites`, `target_groups`, `check_types`, `inbound_checks_settings`, `settings`, `auto_cycle`): + после этого control-api либо не стартует, либо работает без площадок и целей. +- Не нарушайте порядок удаления (раздел 1) и не отключайте `PRAGMA foreign_keys = ON` в блоках выше: база сама остановит ошибочное удаление. +- `VACUUM` нельзя вызывать внутри транзакции и пока control-api запущен. +- Не копируйте файл базы командой `cp` при работающем процессе: журнал `-wal` останется в неконсистентном состоянии. Используйте `.backup`. +- Не меняйте `PRAGMA user_version`: по нему control-api применяет миграции схемы. +- Не правьте состояние адресов и валидаторов вручную (`state`, `owner_validator_id`, `lease_expires_at`) вместо API: control-api сверяет их на каждом такте и исправит расхождение, + но до этого результат непредсказуем.