admin control features and admin dashboard
This commit is contained in:
1 parent
c630f13c57
commit
37910e410b
69 files changed
+4959
-400
No files matched your search
+172
-7
@@ -6,7 +6,10 @@ JSON, базовый префикс прикладных методов — `/ap
|
||||
|
||||
> **Важно.** На данный момент API не защищён аутентификацией/авторизацией
|
||||
> — эндпоинты доступны любому, кто может достучаться до порта control-api
|
||||
> по сети. Для эксплуатации за пределами доверенного сегмента сети
|
||||
> по сети. Это касается и методов из раздела
|
||||
> [«Управление очередью и конфигурацией»](#управление-очередью-и-конфигурацией)
|
||||
> ниже — они меняют, что и как проверяется, без подтверждения личности
|
||||
> вызывающего. Для эксплуатации за пределами доверенного сегмента сети
|
||||
> обязательно ограничьте доступ на уровне сети/файрвола (см.
|
||||
> [SETUP.md](SETUP.md#сетевые-доступы)). Добавление bearer-токена — известное
|
||||
> направление доработки, в текущей версии не реализовано.
|
||||
@@ -14,12 +17,17 @@ JSON, базовый префикс прикладных методов — `/ap
|
||||
Базовый URL в примерах — `http://control-api.internal:8080`, замените на
|
||||
адрес вашего стенда (см. `server.listen_addr` в конфиге control-api).
|
||||
|
||||
> Для работы из браузера вместо `curl` есть `admin-dashboard` — веб-панель,
|
||||
> дающая графический доступ ко всему административному API ниже, см.
|
||||
> [DASHBOARD.md](DASHBOARD.md).
|
||||
|
||||
## Содержание
|
||||
|
||||
- [Общие соглашения](#общие-соглашения)
|
||||
- [Методы для validator-agent](#методы-для-validator-agent)
|
||||
- [Методы для prober](#методы-для-prober)
|
||||
- [Служебные и административные методы](#служебные-и-административные-методы)
|
||||
- [Управление очередью и конфигурацией](#управление-очередью-и-конфигурацией)
|
||||
- [Модель состояний и связь методов с ней](#модель-состояний-и-связь-методов-с-ней)
|
||||
- [Сквозной пример работы (curl)](#сквозной-пример-работы-curl)
|
||||
|
||||
@@ -37,9 +45,11 @@ JSON, базовый префикс прикладных методов — `/ap
|
||||
не удалось распарсить, сервер молча подставит текущее время сервера — не
|
||||
полагайтесь на это в продакшене, всегда передавайте валидную метку.
|
||||
- `validator_id` и `site_id` в пути запроса должны совпадать со
|
||||
значениями, заданными в конфиге control-api (`validators[].validator_id`,
|
||||
`sites[].site_id`) — иначе методы, требующие существующую сущность,
|
||||
вернут `404`.
|
||||
значениями, известными control-api — заданными в `control-api.yaml`
|
||||
при первом запуске (пустая база) либо созданными позже через
|
||||
`/api/v1/admin/config/*` (см.
|
||||
[«Управление очередью и конфигурацией»](#управление-очередью-и-конфигурацией))
|
||||
— иначе методы, требующие существующую сущность, вернут `404`.
|
||||
|
||||
## Методы для validator-agent
|
||||
|
||||
@@ -297,6 +307,152 @@ IP на данном проходе". До этого момента control-api
|
||||
`assigned`, `checking`, `unreachable`) и `CurrentIPID`, если валидатор
|
||||
сейчас занят.
|
||||
|
||||
## Управление очередью и конфигурацией
|
||||
|
||||
Методы этого раздела — единственный способ менять состав очереди
|
||||
(`ip_addresses`), список валидаторов, площадок (`sites`) и целей
|
||||
проверки (`targets`/`check_types`) **без остановки процесса**: изменения
|
||||
применяются немедленно и переживают последующий рестарт control-api. Все
|
||||
тела запросов/ответов — `snake_case` (в отличие от `GET
|
||||
/admin/status|ips|validators` выше, которые отдают сырые поля Go-структур
|
||||
в PascalCase — эти два стиля сосуществуют осознанно, см. примечание к
|
||||
`GET /api/v1/admin/ips/{ip}`).
|
||||
|
||||
**Источник истины.** `control-api.yaml` используется только как
|
||||
одноразовый bootstrap для пустой базы данных: секции `validators`,
|
||||
`sites`, `targets`, `check_types` читаются из YAML один раз, при самом
|
||||
первом старте на пустых таблицах. Как только в соответствующей таблице
|
||||
появилась хотя бы одна строка (через bootstrap либо через методы ниже) —
|
||||
YAML для этой секции больше не перечитывается ни при одном последующем
|
||||
рестарте; правки нужно вносить через API. Список IP-адресов
|
||||
(`ip_addresses` в YAML) — исключение, он остаётся отдельным, всегда
|
||||
аддитивным путём постановки в очередь при каждом старте (см.
|
||||
[SETUP.md](SETUP.md#развёртывание-control-api)); он не конфликтует с
|
||||
`POST /api/v1/admin/ips` ниже.
|
||||
|
||||
### `POST /api/v1/admin/ips`
|
||||
|
||||
Единая точка для двух задач: добавить новые адреса в очередь **и**
|
||||
принудительно перепроверить уже завершённые — один и тот же вызов, разница
|
||||
только в текущем состоянии каждого конкретного адреса. Список
|
||||
обрабатывается в one transaction, в порядке следования адресов:
|
||||
|
||||
- адрес неизвестен control-api → добавляется в очередь как новый
|
||||
(`queued`);
|
||||
- адрес сейчас `done`/`failed` → принудительно перезапускается: сбрасывается
|
||||
результат, `attempt_number` увеличивается, `retry_count` обнуляется,
|
||||
адрес снова становится `queued`;
|
||||
- адрес сейчас `queued` (ещё не взят в работу) → только переупорядочивается
|
||||
под порядок текущего списка, повторно не добавляется;
|
||||
- адрес сейчас активно проверяется (`assigning_fip` / `awaiting_self_check`
|
||||
/ `checking` / `aggregating`) → не трогается вообще — нельзя запустить
|
||||
вторую параллельную проверку одного и того же адреса.
|
||||
|
||||
Порядок обработки внутри одного вызова соответствует порядку адресов в
|
||||
списке; повторная отправка того же списка позже даёт тот же относительный
|
||||
порядок прогона.
|
||||
|
||||
Запрос:
|
||||
```json
|
||||
{"addresses": ["203.0.113.10", "203.0.113.11"]}
|
||||
```
|
||||
|
||||
Ответ (`200`):
|
||||
```json
|
||||
{
|
||||
"added": ["203.0.113.11"],
|
||||
"requeued": ["203.0.113.10"],
|
||||
"reordered": [],
|
||||
"skipped_in_progress": []
|
||||
}
|
||||
```
|
||||
|
||||
`400`, если `addresses` пуст.
|
||||
|
||||
### `POST /api/v1/admin/ips/{ip}/cancel`
|
||||
|
||||
Принудительно останавливает проверку конкретного адреса, не дожидаясь
|
||||
`checking_window_seconds` — работает из любого нетерминального состояния,
|
||||
включая `queued` (в этом случае это просто удаление ещё не начатой
|
||||
проверки из очереди). Если Floating IP уже привязан — отвязывается
|
||||
(best-effort, как и при обычном завершении проверки); владеющий валидатор
|
||||
освобождается. Итог записывается как `overall_result: "cancelled"`
|
||||
(состояние `failed`).
|
||||
|
||||
Ответ: `{"ok": true}`. `404`, если адрес неизвестен. `409`, если адрес уже
|
||||
в терминальном состоянии (`done`/`failed`/уже отменён) — отменять нечего.
|
||||
|
||||
### Валидаторы: `/api/v1/admin/config/validators`
|
||||
|
||||
| Метод | Путь | Тело | Успех | Ошибки |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/api/v1/admin/config/validators` | — | `[{"validator_id","os_port_id","state"}]` | |
|
||||
| POST | `/api/v1/admin/config/validators` | `{"validator_id","os_port_id"}` | `201` | `409`, если `validator_id` уже существует |
|
||||
| PUT | `/api/v1/admin/config/validators/{id}` | `{"os_port_id"}` | `200` | `404` |
|
||||
| DELETE | `/api/v1/admin/config/validators/{id}` | — | `200` | `404`; `409`, если валидатор сейчас владеет IP |
|
||||
|
||||
### Площадки: `/api/v1/admin/config/sites`
|
||||
|
||||
Слотов ровно три (`index` ∈ {1, 2, 3}) — это ограничение схемы БД
|
||||
(`ip_queue.site{1,2,3}_complete`), а не искусственное. Пустой список слотов
|
||||
— штатный сценарий, отключающий inbound-проверки целиком (см.
|
||||
[USAGE.md](USAGE.md#управление-площадками-проберами)).
|
||||
|
||||
| Метод | Путь | Тело | Успех | Ошибки |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/api/v1/admin/config/sites` | — | `[{"index","site_id"}]` (до 3 строк) | |
|
||||
| PUT | `/api/v1/admin/config/sites/{index}` | `{"site_id"}` | `200` | `400`, если `index` не 1..3; `409`, если `site_id` уже занят другим слотом |
|
||||
| DELETE | `/api/v1/admin/config/sites/{index}` | — | `200` | `404` |
|
||||
|
||||
### Группы целей: `/api/v1/admin/config/targets`
|
||||
|
||||
Группа целей — именованный список URL/адресов (например `stub-targets: [
|
||||
"https://hub.docker.com", ...]`), на который затем ссылаются типы
|
||||
проверок.
|
||||
|
||||
| Метод | Путь | Тело | Успех | Ошибки |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/api/v1/admin/config/targets` | — | `[{"name","targets"}]` | |
|
||||
| PUT | `/api/v1/admin/config/targets/{group}` | `{"targets":[...]}` | `200` | `400`, если список пуст |
|
||||
| DELETE | `/api/v1/admin/config/targets/{group}` | — | `200` | `404`; `409`, если группа используется каким-то `check_type` |
|
||||
|
||||
### Типы проверок: `/api/v1/admin/config/check-types`
|
||||
|
||||
Тип проверки (`https`, `icmp`, `ssh`, ...) ссылается на одну или несколько
|
||||
групп целей по имени; именно развёрнутый список отсюда validator-agent
|
||||
получает в `check_config` при `GET /api/v1/agents/{id}/assignment`.
|
||||
|
||||
| Метод | Путь | Тело | Успех | Ошибки |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/api/v1/admin/config/check-types` | — | `[{"name","enabled","targets"}]` (`targets` — имена групп) | |
|
||||
| PUT | `/api/v1/admin/config/check-types/{name}` | `{"enabled","targets":["group",...]}` | `200` | `400`, если названа несуществующая группа |
|
||||
| DELETE | `/api/v1/admin/config/check-types/{name}` | — | `200` | `404` |
|
||||
|
||||
### Пример: конфигурация целиком через API, без единой строки в YAML
|
||||
|
||||
```bash
|
||||
BASE=http://127.0.0.1:8080
|
||||
|
||||
curl -s -X POST "$BASE/api/v1/admin/config/validators" \
|
||||
-d '{"validator_id":"validator_01","os_port_id":"port-abc123"}'
|
||||
|
||||
curl -s -X PUT "$BASE/api/v1/admin/config/targets/web" \
|
||||
-d '{"targets":["https://hub.docker.com","https://github.com"]}'
|
||||
|
||||
curl -s -X PUT "$BASE/api/v1/admin/config/check-types/https" \
|
||||
-d '{"enabled":true,"targets":["web"]}'
|
||||
|
||||
curl -s -X PUT "$BASE/api/v1/admin/config/sites/1" -d '{"site_id":"site-1"}'
|
||||
|
||||
# Поставить адрес в очередь и, отдельным вызовом позже, принудительно
|
||||
# перепроверить его ещё раз — тот же метод, разница только в состоянии:
|
||||
curl -s -X POST "$BASE/api/v1/admin/ips" -d '{"addresses":["203.0.113.10"]}'
|
||||
curl -s -X POST "$BASE/api/v1/admin/ips" -d '{"addresses":["203.0.113.10"]}' # forced recheck
|
||||
|
||||
# Остановить проверку, не дожидаясь checking_window_seconds:
|
||||
curl -s -X POST "$BASE/api/v1/admin/ips/203.0.113.10/cancel"
|
||||
```
|
||||
|
||||
## Модель состояний и связь методов с ней
|
||||
|
||||
```
|
||||
@@ -327,9 +483,18 @@ queued ──(control-api сам, без вызова API)──▶ assigning_fi
|
||||
нет — это фоновый цикл (`Tick`), а не запрос/ответ.
|
||||
|
||||
Площадки (`siteN_complete`) — опциональны: сколько их учитывается,
|
||||
целиком определяется списком `sites` в конфиге control-api (0–3 записи).
|
||||
Пустой список — агрегация ждёт только `egress_complete`, ни одна площадка
|
||||
не требуется. Подробнее — [USAGE.md](USAGE.md#управление-площадками-проберами).
|
||||
целиком определяется текущим списком `sites` (0–3 записи, управляется
|
||||
через `/api/v1/admin/config/sites` — см.
|
||||
[выше](#управление-очередью-и-конфигурацией)). Пустой список — агрегация
|
||||
ждёт только `egress_complete`, ни одна площадка не требуется. Подробнее —
|
||||
[USAGE.md](USAGE.md#управление-площадками-проберами).
|
||||
|
||||
Два дополнительных перехода, оба инициируются оператором через
|
||||
`/api/v1/admin/ips`, а не самим оркестратором:
|
||||
- **любое нетерминальное состояние → `failed` (`overall_result:
|
||||
"cancelled"`)** — `POST /api/v1/admin/ips/{ip}/cancel`;
|
||||
- **`done`/`failed` → `queued` (новая попытка)** — `POST
|
||||
/api/v1/admin/ips` с уже завершённым адресом в списке.
|
||||
|
||||
## Сквозной пример работы (curl)
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Admin Dashboard
|
||||
|
||||
`admin-dashboard` — 4-й компонент системы: браузерная веб-панель,
|
||||
дающая полное покрытие административного API `control-api`
|
||||
([docs/API.md](API.md#управление-очередью-и-конфигурацией)) без единого
|
||||
`curl`. Отдельный, полностью самостоятельный процесс — не хранит
|
||||
состояния, не подключается к базе данных напрямую, общается с
|
||||
`control-api` только через его же HTTP admin API.
|
||||
|
||||
## Устройство
|
||||
|
||||
- Рендеринг полностью на сервере: `html/template` + [htmx](https://htmx.org)
|
||||
(частичные обновления без перезагрузки страницы) +
|
||||
[Alpine.js](https://alpinejs.dev) (точечная клиентская интерактивность) +
|
||||
[Pico CSS](https://picocss.com) в classless-сборке (минималистичный вид
|
||||
на голой семантической разметке). Никакой сборки фронтенда нет — все три
|
||||
библиотеки вендорены как статические файлы
|
||||
(`internal/dashboard/static/vendor/`, см. `VENDOR.md` там же) и встроены
|
||||
в бинарник через `//go:embed`. Дашборд работает полностью офлайн — при
|
||||
открытии страницы браузер не делает ни одного запроса за пределы самого
|
||||
дашборда (можно проверить через DevTools → Network).
|
||||
- Браузер никогда не видит JSON `control-api` напрямую: каждая страница и
|
||||
каждый htmx-фрагмент — это HTML, отрендеренный Go-хендлером дашборда
|
||||
после вызова `control-api`. CORS и reverse-proxy не нужны.
|
||||
- Как и `control-api`, дашборд **не аутентифицирован** — ограничивайте
|
||||
доступ на уровне сети/файрвола (см.
|
||||
[SETUP.md](SETUP.md#сетевые-доступы)).
|
||||
|
||||
## Запуск
|
||||
|
||||
```bash
|
||||
cp configs/admin-dashboard.example.yaml /etc/cloud-ip-validator/admin-dashboard.yaml
|
||||
# отредактируйте control_api.base_url под ваш стенд
|
||||
admin-dashboard -config /etc/cloud-ip-validator/admin-dashboard.yaml
|
||||
```
|
||||
|
||||
Развёртывание как systemd-юнита — по образцу остальных компонентов, см.
|
||||
[SETUP.md](SETUP.md#развёртывание-admin-dashboard). Порт по умолчанию —
|
||||
`:8090` (у `control-api` — `:8080`).
|
||||
|
||||
## Страницы и что на них можно делать
|
||||
|
||||
| Страница | Назначение |
|
||||
|---|---|
|
||||
| `/overview` | Сводная статистика: счётчики по состояниям, «текущая проверка» (live-снимок всех IP не в терминальном состоянии) и «последние N завершённых» (по умолчанию 20, `overview.last_completed_count`) с разбивкой pass/partial/fail/cancelled. Обновляется каждые `overview.poll_interval_seconds` секунд без перезагрузки страницы. |
|
||||
| `/ips` | Полная очередь. Форма сверху принимает список адресов (по одному на строке или через запятую) и отправляет их в `POST /api/v1/admin/ips` — **один и тот же вызов** добавляет новые адреса и принудительно перезапускает уже завершённые (см. ниже). У каждого адреса — кнопка «Перепроверить» (для `done`/`failed`) или «Отменить» (для активных состояний). |
|
||||
| `/ips/{ip}` | Детали одного адреса: все проверки текущей попытки и вся история событий. |
|
||||
| `/validators` | Список валидаторов + создание/изменение `os_port_id`/удаление. |
|
||||
| `/sites` | Три фиксированных слота площадок (1/2/3) — назначить/сменить/освободить `site_id`. |
|
||||
| `/targets` | Группы целей для egress-проверок — создание/редактирование/удаление. |
|
||||
| `/check-types` | Типы проверок (`https`/`icmp`/`ssh`/...), включение/выключение, привязка к группам целей. |
|
||||
|
||||
### «Текущая» и «последняя завершённая» проверка
|
||||
|
||||
В `control-api` нет понятия «запуска»/«цикла проверки» как отдельной
|
||||
сущности — есть только общая очередь IP-адресов
|
||||
(`docs/PLAN_ADMIN_DASHBOARD.md`). Дашборд ничего не меняет в этом
|
||||
устройстве и не заводит своего состояния:
|
||||
|
||||
- **Текущая проверка** — все адреса, которые прямо сейчас не в
|
||||
состоянии `done`/`failed` (`queued`, `assigning_fip`,
|
||||
`awaiting_self_check`, `checking`, `aggregating`), вычисляется заново на
|
||||
каждый запрос из `GET /api/v1/admin/status` + `GET /api/v1/admin/ips`.
|
||||
- **Последняя завершённая проверка** — последние N адресов, перешедших в
|
||||
`done`/`failed`, отсортированные по `AggregatedAt` по убыванию (не
|
||||
«последний запуск», а именно скользящее окно последних по времени
|
||||
завершений).
|
||||
|
||||
### Добавление адресов и принудительный повтор — один и тот же вызов
|
||||
|
||||
Форма на `/ips` всегда бьёт в `POST /api/v1/admin/ips`. Поведение зависит
|
||||
от текущего состояния каждого конкретного адреса (см.
|
||||
[docs/API.md](API.md#post-apiv1adminips)): новый — встаёт в очередь;
|
||||
уже `done`/`failed` — принудительно перезапускается; уже `queued` —
|
||||
просто переупорядочивается; уже активно проверяется — не трогается
|
||||
(дашборд честно показывает это в таблице, а не делает вид, что запрос
|
||||
ничего не значил).
|
||||
|
||||
## Конфигурация
|
||||
|
||||
См. `configs/admin-dashboard.example.yaml`. Ключевые поля:
|
||||
|
||||
- `server.listen_addr` — где слушает сам дашборд (по умолчанию `:8090`).
|
||||
- `control_api.base_url` — адрес `control-api`, обязателен.
|
||||
- `control_api.timeout_seconds` — таймаут HTTP-запросов к `control-api`.
|
||||
- `overview.last_completed_count` — размер окна «последних завершённых»
|
||||
на странице обзора.
|
||||
- `overview.poll_interval_seconds` — как часто браузер опрашивает
|
||||
`/overview/fragment` для live-обновления.
|
||||
|
||||
## Отображение ошибок
|
||||
|
||||
Любая ошибка `control-api` (4xx/5xx с телом `{"error":"..."}`) или сбой
|
||||
связи с ним (недоступен, таймаут) показывается баннером наверху страницы,
|
||||
а не приводит к падению дашборда — таблица/страница при этом всегда
|
||||
отражает актуальное состояние `control-api` (дашборд перезапрашивает
|
||||
данные после любой попытки мутации, независимо от её исхода). Жёлтый
|
||||
баннер — бизнес-ошибка (4xx, например «валидатор занят»), красный —
|
||||
инфраструктурная проблема (5xx или `control-api` недоступен).
|
||||
+17
-10
@@ -32,15 +32,15 @@ control-api, фоновый оркестратор, база данных, вы
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph OP["Оператор"]
|
||||
CFG["control-api.yaml<br/>(validators, sites, targets,<br/>ip_addresses, check_types)"]
|
||||
CFG["control-api.yaml<br/>(bootstrap пустой БД:<br/>validators, sites, targets,<br/>check_types, ip_addresses)"]
|
||||
ENV["control-api.env<br/>(OS_AUTH_URL, OS_TOKEN, ...)"]
|
||||
ADMIN["curl /api/v1/admin/*"]
|
||||
ADMIN["curl /api/v1/admin/*<br/>(status/ips/validators,<br/>ips submit/cancel,<br/>config CRUD)"]
|
||||
end
|
||||
|
||||
subgraph CAPI["control-api (управляющая машина, 1 экземпляр)"]
|
||||
HTTP["HTTP API<br/>/api/v1/agents/*<br/>/api/v1/probers/*<br/>/api/v1/admin/*<br/>/healthz"]
|
||||
ORCH["Оркестратор: Tick раз в<br/>poll_interval_seconds<br/>claim → associate FIP →<br/>ожидание self-check →<br/>checking → aggregate → release<br/>+ lease sweep + heartbeat sweep"]
|
||||
DB[("SQLite<br/>validators / ip_queue<br/>checks / events")]
|
||||
DB[("SQLite<br/>validators / ip_queue / sites /<br/>target_groups / check_types /<br/>checks / events")]
|
||||
OSCLIENT["OpenStack-клиент<br/>(mode: mock | real)"]
|
||||
end
|
||||
|
||||
@@ -49,9 +49,10 @@ flowchart TB
|
||||
VA["validator-agent ×N<br/>(на каждой ВМ-валидаторе)"]
|
||||
PR["prober ×3<br/>(на каждой внешней площадке)"]
|
||||
|
||||
CFG -->|"читается при старте<br/>(инициализация validators, ip_queue)"| CAPI
|
||||
CFG -->|"читается только один раз,<br/>на пустых таблицах (bootstrap)"| DB
|
||||
ENV -->|"переменные окружения процесса"| OSCLIENT
|
||||
ADMIN --> HTTP
|
||||
HTTP -->|"config/queue CRUD:<br/>источник истины после<br/>первого изменения"| DB
|
||||
HTTP --> ORCH
|
||||
ORCH <--> DB
|
||||
ORCH --> OSCLIENT
|
||||
@@ -63,14 +64,20 @@ flowchart TB
|
||||
|
||||
**Пояснение.** `control-api` — единственный компонент с состоянием и
|
||||
единственная точка принятия решений (какой IP кому назначить, когда
|
||||
считать проверку завершённой). Конфигурация читается один раз при
|
||||
старте процесса (горячей перезагрузки нет — изменения требуют
|
||||
`systemctl restart control-api`, см. [SETUP.md](SETUP.md)). Оркестратор
|
||||
считать проверку завершённой). `control-api.yaml` используется только как
|
||||
одноразовый bootstrap для четырёх секций (`validators`, `sites`,
|
||||
`targets`, `check_types`) — читается лишь пока соответствующая таблица в
|
||||
БД пуста; `ip_addresses` — отдельный, всегда аддитивный путь постановки в
|
||||
очередь при каждом старте. После bootstrap все изменения этих сущностей,
|
||||
включая состав очереди и принудительные повтор/остановку проверки, идут
|
||||
через `/api/v1/admin/*` — «на лету», без `systemctl restart control-api`
|
||||
(см. [API.md](API.md#управление-очередью-и-конфигурацией)). Оркестратор
|
||||
работает по таймеру независимо от HTTP-запросов — назначение IP
|
||||
валидаторам и агрегация результатов не привязаны к конкретному входящему
|
||||
запросу, а выполняются фоновым циклом `Tick`. `validator-agent` и
|
||||
`prober` — активная сторона: они сами инициируют все HTTP-запросы к
|
||||
control-api (pull-модель), сам control-api к ним не обращается.
|
||||
запросу, читая актуальную конфигурацию из БД на каждом проходе, а не
|
||||
единожды при старте. `validator-agent` и `prober` — активная сторона: они
|
||||
сами инициируют все HTTP-запросы к control-api (pull-модель), сам
|
||||
control-api к ним не обращается.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# План: `cmd/admin-dashboard` — веб-панель администратора
|
||||
|
||||
> Статус: **реализовано**. Актуальное описание — [docs/DASHBOARD.md](DASHBOARD.md).
|
||||
|
||||
## Context
|
||||
|
||||
Единственный способ управлять `control-api` (очередь IP, валидаторы,
|
||||
площадки, цели проверки) — HTTP API через `curl` (см. `docs/API.md`). API
|
||||
уже покрывает весь необходимый функционал (`POST /api/v1/admin/ips` для
|
||||
постановки/принудительного повтора, `POST /api/v1/admin/ips/{ip}/cancel`
|
||||
для остановки, `/api/v1/admin/config/{validators,sites,targets,check-types}`
|
||||
для CRUD), но curl неудобен для повседневного оперирования и не даёт
|
||||
наглядной картины состояния очереди. Нужен браузерный admin dashboard —
|
||||
графический доступ ко всей этой функциональности: сводная статистика
|
||||
(текущая проверка / итог последней завершённой), управление конфигурацией
|
||||
и принудительные операции над очередью — без YAML и без перезапуска
|
||||
`control-api`.
|
||||
|
||||
**Согласованные решения:**
|
||||
- **Без сборки фронтенда.** Server-rendered `html/template` + htmx
|
||||
(частичные AJAX-обновления) + Alpine.js (точечная клиентская
|
||||
интерактивность) + Pico.css classless (минимализм на голой семантической
|
||||
разметке). Все три библиотеки вендорятся как статические файлы и
|
||||
встраиваются через `//go:embed` — без CDN во время выполнения (принцип
|
||||
проекта — полностью автономный бинарник, работает офлайн).
|
||||
- **Никакого нового backend-состояния.** «Текущая проверка» — live-снимок
|
||||
IP не в терминальном состоянии. «Последняя завершённая» — последние N
|
||||
(по умолчанию 20, настраивается) по `AggregatedAt` desc среди
|
||||
`done`/`failed`, с разбивкой по `OverallResult`. Оба вычисляются на
|
||||
каждый запрос из `GET /admin/status` + `GET /admin/ips` — никакого
|
||||
понятия «запуска»/«батча» в `control-api` не добавляется.
|
||||
- **Без аутентификации** — как и сам API; доступ ограничивается сетью/firewall.
|
||||
- **Отдельный 4-й бинарник** (`cmd/admin-dashboard`), не новые маршруты
|
||||
внутри `control-api`. Ходит в `control-api` только через существующий
|
||||
HTTP admin API (`internal/apiclient.Client`). Рендеринг на сервере —
|
||||
браузер не видит JSON `control-api` напрямую, CORS/reverse-proxy не нужны.
|
||||
- Малые допущения: без пагинации очереди (текущий масштаб — десятки
|
||||
адресов); баннер ошибок различает 4xx (жёлтый) и 5xx/транспортные
|
||||
(красный); порт дашборда по умолчанию `:8090`.
|
||||
|
||||
## 1. Структура файлов
|
||||
|
||||
```
|
||||
cmd/admin-dashboard/main.go
|
||||
|
||||
internal/dashboard/
|
||||
server.go, routes.go, client.go, dto.go, render.go, embed.go
|
||||
handlers_overview.go, handlers_ips.go, handlers_validators.go,
|
||||
handlers_sites.go, handlers_targets.go, handlers_checktypes.go
|
||||
templates/ (layout, overview[+fragment], ips[+table], ip_detail,
|
||||
validators[+table+row], sites[+table], targets[+table+row],
|
||||
checktypes[+table+row], error_banner)
|
||||
static/vendor/{htmx.min.js,alpine.min.js,pico.classless.min.css,VENDOR.md}
|
||||
static/dashboard.css
|
||||
|
||||
configs/admin-dashboard.example.yaml
|
||||
deploy/systemd/admin-dashboard.service
|
||||
docs/DASHBOARD.md
|
||||
```
|
||||
|
||||
Таблица `ips` перерисовывается целиком при любой мутации (`POST
|
||||
/admin/ips` может завести новые строки и поменять `sequence`).
|
||||
`validators`/`sites`/`targets`/`check-types` — точечный swap одной `<tr>`.
|
||||
|
||||
## 2. Маршруты дашборда
|
||||
|
||||
| Метод | Путь | Вызов к control-api |
|
||||
|---|---|---|
|
||||
| GET | `/` | редирект на `/overview` |
|
||||
| GET | `/overview`, `/overview/fragment` | `GET /admin/status`, `GET /admin/ips` |
|
||||
| GET | `/ips`, `/ips/{ip}` | `GET /admin/ips`, `GET /admin/ips/{ip}` |
|
||||
| POST | `/ips` | `POST /admin/ips` |
|
||||
| POST | `/ips/{ip}/recheck` | `POST /admin/ips` `{"addresses":[ip]}` |
|
||||
| POST | `/ips/{ip}/cancel` | `POST /admin/ips/{ip}/cancel` |
|
||||
| GET/POST `/validators`, PUT/DELETE `/validators/{id}` | `.../config/validators[/{id}]` |
|
||||
| GET `/sites`, PUT/DELETE `/sites/{index}` | `.../config/sites[/{index}]` |
|
||||
| GET/POST `/targets`, PUT/DELETE `/targets/{group}` | `.../config/targets[/{group}]` |
|
||||
| GET/POST `/check-types`, PUT/DELETE `/check-types/{name}` | `.../config/check-types[/{name}]` |
|
||||
| GET | `/static/*` | embed.FS |
|
||||
|
||||
`overview/fragment` — `hx-trigger="every Ns"` (из конфига), без фонового
|
||||
тикера на сервере. `POST /targets`/`/check-types` — перевод «форма с
|
||||
именем» → `PUT .../{name}` (control-api там upsert).
|
||||
|
||||
## 3. Ошибки control-api
|
||||
|
||||
`client.go`: не-2xx → `*apiErr{Status, Message}`. Хендлеры не отдают 500 —
|
||||
рендерят страницу/фрагмент + out-of-band `error_banner.html`
|
||||
(`hx-swap-oob="true"`, `id="error-banner"` в `layout.html`); статус ответа
|
||||
дашборда = статус control-api (502 при транспортной ошибке). Баннер жёлтый
|
||||
для 4xx, красный для 5xx/транспортных.
|
||||
|
||||
## 4. Конфигурация
|
||||
|
||||
`internal/config/config.go`, секция `AdminDashboard{Server, ControlAPI{
|
||||
BaseURL, TimeoutSeconds}, Overview{LastCompletedCount, PollIntervalSeconds}}`
|
||||
+ `LoadAdminDashboard` (defaults: `:8090`, timeout 10s, N=20, poll=5s;
|
||||
`BaseURL` обязателен). `configs/admin-dashboard.example.yaml`,
|
||||
`deploy/systemd/admin-dashboard.service` (без CAP_NET_RAW/EnvironmentFile).
|
||||
|
||||
## 5. DTO и клиент
|
||||
|
||||
`internal/dashboard/dto.go` — свои wire-структуры (не импортируют
|
||||
приватные DTO `internal/httpapi`, тот же паттерн, что `internal/probercore`):
|
||||
snake_case-структуры дословно повторяют `internal/httpapi/dto_admin.go`;
|
||||
для `GET /admin/ips[/{ip}]`/`GET /admin/validators` — зеркала untagged
|
||||
PascalCase `db.IPQueueItem`/`db.Validator`/`db.Check`/`db.Event`.
|
||||
|
||||
`client.go` — обёртка над `apiclient.Client`: `Status`, `ListIPs`, `GetIP`,
|
||||
`SubmitIPs`, `CancelIP`, CRUD-методы для validators/sites/target-groups/
|
||||
check-types.
|
||||
|
||||
`handlers_overview.go` — чистые функции: `currentlyChecking`,
|
||||
`lastCompleted(items, n)`, `resultBreakdown`.
|
||||
|
||||
## 6. Вендоринг статики
|
||||
|
||||
Скачать один раз, закоммитить, задокументировать в `VENDOR.md`:
|
||||
`htmx.min.js` (unpkg htmx.org, ядро без расширений), `alpine.min.js`
|
||||
(unpkg alpinejs `dist/cdn.min.js`, IIFE-сборка), `pico.classless.min.css`
|
||||
(unpkg @picocss/pico).
|
||||
|
||||
## 7. Тестирование
|
||||
|
||||
`httptest`-фейковый control-api + `httptest`-дашборд поверх него, проверка
|
||||
рендера через `strings.Contains` (по образцу `internal/httpapi/handlers_config_test.go`).
|
||||
Кейсы: overview live-агрегация и `resultBreakdown`; submit (happy +
|
||||
пустой список); recheck (done→requeued, checking→skipped, явно показано);
|
||||
cancel (happy + 409); CRUD-раунд-трип + конфликты (409/400) по всем 4
|
||||
сущностям; control-api недоступен → баннер + 502.
|
||||
|
||||
**Обязательный ручной шаг:** браузерный смоук-тест против
|
||||
`scripts/run-local-e2e.sh` (или отдельного mock control-api) — все
|
||||
страницы/формы/действия, live-обновление во время реального прогона,
|
||||
проверка через DevTools Network отсутствия внешних (CDN) запросов.
|
||||
|
||||
## 8. Документация
|
||||
|
||||
Новый `docs/DASHBOARD.md`; `README.md` («три компонента» →
|
||||
«четыре»); `docs/SETUP.md` (компонент + раздел развёртывания + сетевые
|
||||
доступы); `docs/API.md` (отсылка на дашборд в предупреждении об
|
||||
открытости API).
|
||||
|
||||
## Критичные файлы
|
||||
|
||||
`internal/dashboard/{client.go,dto.go,routes.go,server.go,
|
||||
handlers_overview.go,render.go,embed.go}`, `internal/config/config.go`
|
||||
(`AdminDashboard`), `cmd/admin-dashboard/main.go`.
|
||||
|
||||
## Проверка
|
||||
|
||||
1. `go build ./... && go test ./...`
|
||||
2. `scripts/run-local-e2e.sh` + `admin-dashboard` отдельно против того же control-api
|
||||
3. Ручной браузерный смоук-тест (обязателен, не пропускается)
|
||||
+130
-244
@@ -1,53 +1,70 @@
|
||||
# План доработки: API управления конфигурацией
|
||||
# План доработки: динамическое управление конфигурацией и очередью через API
|
||||
|
||||
> Статус: **план на будущее, не реализовано**. Документ фиксирует
|
||||
> согласованный дизайн доработки Control API, дающей возможность
|
||||
> управлять `check_types`, `targets`, `validators` и `sites` через HTTP
|
||||
> API вместо правки YAML + рестарта. Реализация — отдельная задача.
|
||||
> Статус: **реализовано**. Документ фиксирует дизайн доработки Control
|
||||
> API, дающей возможность управлять `validators`, `sites`,
|
||||
> `check_types`/`targets` и очередью IP-адресов через HTTP API вместо
|
||||
> правки YAML + рестарта, без перезапуска процесса. Актуальная
|
||||
> спецификация методов — [docs/API.md](API.md#управление-очередью-и-конфигурацией);
|
||||
> повседневные сценарии — [docs/USAGE.md](USAGE.md).
|
||||
|
||||
## Context
|
||||
|
||||
Сейчас `control-api` полностью read-only в части конфигурации: типы
|
||||
проверок (`check_types`), список целей (`targets`), состав валидаторов
|
||||
(`validators`) и внешних площадок (`sites`) читаются один раз из
|
||||
`control-api.yaml` при старте процесса и живут дальше только в памяти
|
||||
(`Orchestrator.Checks`, `Orchestrator.Sites`) либо (для валидаторов) в
|
||||
таблице `validators`, куда при каждом рестарте они переупорядочиваются из
|
||||
YAML. Единственный способ что-то поменять — отредактировать YAML и
|
||||
выполнить `systemctl restart control-api`. Это задокументированное
|
||||
ограничение (см. `docs/USAGE.md`).
|
||||
Сейчас `control-api` в части конфигурации и очереди полностью read-only:
|
||||
`validators`, `sites`, `check_types`/`targets` читаются один раз из YAML при
|
||||
старте процесса (`cmd/control-api/main.go`) и живут в памяти
|
||||
(`Orchestrator.Checks`, `Orchestrator.Sites`) либо переприменяются в БД при
|
||||
каждом рестарте (`RegisterValidator` upsert). Список адресов на проверку
|
||||
(`ip_addresses`) добавляется в очередь только при старте, и нет способа
|
||||
принудительно перепроверить уже завершённый адрес или остановить проверку,
|
||||
которая уже идёт. Единственный способ что-то поменять — отредактировать
|
||||
YAML и выполнить `systemctl restart control-api`.
|
||||
|
||||
Согласованные решения (зафиксированы для будущей реализации):
|
||||
- **Область доработки** — ровно четыре сущности: `check_types` (типы
|
||||
проверок + их привязка к группам целей), `targets` (группы целей),
|
||||
`validators` (состав ВМ-валидаторов), `sites` (состав из ≤3 внешних
|
||||
площадок). Очередь `ip_addresses` и тайминги оркестратора
|
||||
(`orchestrator.*`, `aggregation.*`, `inbound_checks.*`) — вне scope,
|
||||
остаются YAML-only как сейчас.
|
||||
- **Источник истины после первого изменения — БД.** YAML используется
|
||||
только для одноразового bootstrap при пустой базе; после первого
|
||||
запуска (или после первого API-изменения) YAML для этих 4 секций
|
||||
больше не перечитывается и не переприменяется при рестартах.
|
||||
- **Добавляется базовая аутентификация** — bearer-токен администратора,
|
||||
которым закрывается весь namespace `/api/v1/admin/*` (не только новые
|
||||
write-методы, но и существующие read-методы `status/ips/validators` —
|
||||
единая политика для всего admin-namespace проще и логичнее половинчатой
|
||||
защиты). Протокол `/api/v1/agents/*` и `/api/v1/probers/*`
|
||||
(agent/prober) — вне scope, остаётся как есть.
|
||||
Целевой набор фич:
|
||||
|
||||
## Важное архитектурное ограничение: площадки жёстко капнуты на 3
|
||||
1. Администратор передаёт через API список IP-адресов на проверку.
|
||||
2. Администратор передаёт через API список валидаторов.
|
||||
3. Администратор передаёт через API список целей (`targets`/`check_types`).
|
||||
4. Администратор может принудительно инициировать проверку адреса, даже
|
||||
если она уже была выполнена ранее.
|
||||
5. Администратор может принудительно остановить идущую проверку.
|
||||
|
||||
Схема `ip_queue` хранит завершённость площадок как три отдельные колонки
|
||||
(`site1_complete`, `site2_complete`, `site3_complete`) — это не список
|
||||
произвольной длины. Поэтому API для `sites` не может быть обычным
|
||||
CRUD-списком: это управление максимум тремя пронумерованными слотами
|
||||
(`index` ∈ {1,2,3}), где `site_id` можно назначить, переименовать или
|
||||
снять со слота. Это ограничение уже описано в `docs/USAGE.md` и явно
|
||||
закладывается в дизайн API ниже, а не игнорируется.
|
||||
Все действия — «на лету», без перезапуска процесса. Источник истины после
|
||||
первого изменения через API — БД, YAML остаётся только bootstrap для пустой
|
||||
базы.
|
||||
|
||||
## Общий план реализации
|
||||
**Согласованные решения:**
|
||||
- **Sites (площадки) включены в объём доработки** — тем же CRUD-подходом,
|
||||
что и validators/targets/check_types.
|
||||
- **Аутентификация (admin bearer-токен) НЕ входит в этот план** — API
|
||||
остаётся открытым, как сейчас. Ограничение доступа — на уровне
|
||||
сети/firewall (см. `docs/SETUP.md`).
|
||||
- **Фичи 1 и 4 реализуются одним механизмом**, а не двумя разными
|
||||
эндпоинтами. Администратор передаёт список IP-адресов в
|
||||
`POST /api/v1/admin/ips`; для каждого адреса в списке:
|
||||
- если адрес не встречался раньше — добавляется в очередь как новый;
|
||||
- если адрес уже в терминальном состоянии (`done`/`failed`) —
|
||||
принудительно перезапускается на проверку (сброс результата, новая
|
||||
попытка, `attempt_number` увеличивается, `retry_count` обнуляется);
|
||||
- если адрес уже в очереди (`queued`) — переупорядочивается под порядок
|
||||
текущего списка (без дублирования);
|
||||
- если адрес сейчас активно проверяется (`assigning_fip` /
|
||||
`awaiting_self_check` / `checking` / `aggregating`) — не трогается
|
||||
вообще (не создаём вторую параллельную проверку одного и того же
|
||||
адреса).
|
||||
|
||||
### 1. Новая схема БД — `internal/db/migrations/0002_dynamic_config.sql`
|
||||
Порядок обработки в рамках одного вызова соответствует порядку адресов в
|
||||
переданном списке — повторная отправка того же списка без изменений даёт
|
||||
тот же порядок прогона.
|
||||
|
||||
## 1. Схема БД — новая миграция + обобщение `migrate()`
|
||||
|
||||
`internal/db/db.go: migrate()` сейчас гейтится по `PRAGMA user_version`:
|
||||
`>=1 → no-op`, иначе применяет единственный embedded `migrations/0001_init.sql`
|
||||
и ставит `user_version=1`. Обобщается на упорядоченный список миграций
|
||||
(embed `0002_dynamic_config.sql` вторым файлом), применяются по очереди все
|
||||
версии выше текущей.
|
||||
|
||||
Новый файл `internal/db/migrations/0002_dynamic_config.sql`:
|
||||
|
||||
```sql
|
||||
CREATE TABLE sites (
|
||||
@@ -73,229 +90,98 @@ CREATE TABLE check_types (
|
||||
);
|
||||
```
|
||||
|
||||
Список целей внутри группы и список групп внутри типа проверки хранятся
|
||||
как JSON-массив в TEXT-колонке (тот же паттерн, что уже используется для
|
||||
`events.payload`) — они всегда читаются/пишутся целиком, отдельная
|
||||
реляционная таблица тут не нужна (не переусложняем).
|
||||
`validators` — существующая таблица (`0001_init.sql`), новых колонок не
|
||||
требует.
|
||||
|
||||
`validators` — существующая таблица, новых колонок не требует.
|
||||
## 2. Типизированные ошибки — `internal/db/errors.go`
|
||||
|
||||
`internal/db/db.go`: функцию `migrate()` обобщить со списка из одной
|
||||
миграции (`version >= 1 → return`) на упорядоченный список
|
||||
`{version, sql}` и применение всех версий выше текущего
|
||||
`PRAGMA user_version` — понадобится и для этой, и для будущих миграций.
|
||||
Сентинелы (`errors.New` + `%w`-обёртка), чтобы `httpapi`-хендлеры маппили
|
||||
их в HTTP-статусы через `errors.Is`: `ErrNotFound` (404), `ErrConflict`
|
||||
(409), `ErrBusy` (409, валидатор владеет IP), `ErrInUse` (409, группа
|
||||
целей используется check_type'ом), `ErrValidation` (400), `ErrInvalidState`
|
||||
(409, попытка отменить уже завершённую проверку).
|
||||
|
||||
### 2. Bootstrap-логика — новый файл `internal/db/bootstrap.go`
|
||||
## 3. Bootstrap — `internal/db/bootstrap.go`
|
||||
|
||||
```go
|
||||
func (d *DB) BootstrapFromConfig(ctx context.Context, cfg *config.ControlAPI) error
|
||||
```
|
||||
|
||||
Переносит и обобщает то, что сейчас разбросано по
|
||||
`cmd/control-api/main.go` (`RegisterValidator` в цикле + `SeedQueue`):
|
||||
Заменяет текущий цикл `RegisterValidator` + `SeedQueue` в
|
||||
`cmd/control-api/main.go`:
|
||||
- `ip_addresses` → `SeedQueue` — без изменений (всегда доливает новые
|
||||
адреса при каждом старте; отдельный YAML-only путь, не путать с runtime
|
||||
`POST /api/v1/admin/ips`).
|
||||
- `validators`, `sites`, `target_groups`, `check_types` — применяются
|
||||
только если соответствующая таблица пуста. Если строки уже есть — YAML
|
||||
для этой секции игнорируется.
|
||||
|
||||
- `ip_addresses` → `SeedQueue` — **без изменений**, как сейчас (всегда
|
||||
доливает новые адреса, это уже вне scope доработки).
|
||||
- `validators`, `sites`, `target_groups`, `check_types` — **новая
|
||||
семантика**: применяется, **только если соответствующая таблица
|
||||
сейчас пуста** (`SELECT COUNT(*) ... == 0`). Если в таблице уже есть
|
||||
строки — YAML для этой секции полностью игнорируется, ничего не
|
||||
трогаем. Это и есть «bootstrap один раз, дальше БД главная».
|
||||
**Осознанное изменение поведения**: сейчас `RegisterValidator` при каждом
|
||||
рестарте переприменяет `os_port_id` из YAML поверх БД. После доработки —
|
||||
только на пустой таблице (иначе API-правки не переживали бы рестарт).
|
||||
|
||||
`internal/db` уже не будет зависеть от `internal/orchestrator` — только
|
||||
новая зависимость `internal/db → internal/config` (обратной зависимости
|
||||
`config → db` нет, циклов не возникает).
|
||||
## 4. Запросы к БД
|
||||
|
||||
`cmd/control-api/main.go`: заменить текущий цикл `RegisterValidator` +
|
||||
`SeedQueue` одним вызовом `database.BootstrapFromConfig(ctx, cfg)`. Это
|
||||
же делает функцию тестируемой напрямую (используется в обновлённых
|
||||
`orchestrator_test.go`/`httpapi_test.go` вместо ручного построения
|
||||
`Orchestrator.Checks`/`.Sites`).
|
||||
- `internal/db/queries_sites.go`: `ListSites`, `UpsertSite(idx, siteID)`,
|
||||
`DeleteSite(idx)`, `GetSiteIndex(ctx, siteID) (int, error)` (0, если не
|
||||
найден — не ошибка).
|
||||
- `internal/db/queries_targetgroups.go`: `ListTargetGroups`,
|
||||
`UpsertTargetGroup(name, targets)`, `DeleteTargetGroup(name)`
|
||||
(`ErrInUse`, если ссылается check_type), `GetTargetGroup(name)`.
|
||||
- `internal/db/queries_checktypes.go`: `ListCheckTypes`,
|
||||
`ListResolvedCheckTypes` (разворачивает группы в плоский список targets),
|
||||
`UpsertCheckType(name, enabled, targetGroups)` (`ErrValidation`, если
|
||||
группа не существует), `DeleteCheckType(name)`.
|
||||
- `internal/db/queries_validators.go` (дополнить): `AdminCreateValidator`,
|
||||
`AdminUpdateValidatorPort`, `DeleteValidator` (`ErrBusy`, если владеет
|
||||
IP).
|
||||
- `internal/db/queries_ipqueue.go` (дополнить): `SubmitIPs(ctx,
|
||||
addresses []string) (SubmitIPsResult, error)` — единая транзакция,
|
||||
реализует правило из Context выше; `CancelIP(ctx, ipID int64) error` —
|
||||
условный `UPDATE ... WHERE state NOT IN ('done','failed')`,
|
||||
`ErrInvalidState` при гонке/уже завершённой проверке.
|
||||
|
||||
**Важное следствие смены семантики валидаторов**: сейчас при каждом
|
||||
рестарте `control-api` валидаторы из YAML переприменяются (в частности,
|
||||
может тихо откатить `os_port_id`, изменённый через API/вручную в БД).
|
||||
После доработки — только на пустой таблице. Это осознанное поведенческое
|
||||
изменение, требует апдейта `docs/SETUP.md`/`docs/USAGE.md` (шаг 6 плана).
|
||||
`ResultCancelled = "cancelled"` добавляется в `internal/db/models.go`.
|
||||
|
||||
### 3. Запросы к БД для новых сущностей
|
||||
## 5. Оркестратор
|
||||
|
||||
Новые файлы, по аналогии с существующими `queries_*.go`:
|
||||
`internal/orchestrator/orchestrator.go`: убрать статические поля
|
||||
`Checks`/`Sites`, читать динамически из БД (`ListResolvedCheckTypes`,
|
||||
`ListSites`, `GetSiteIndex`) в `AssignmentForValidator`,
|
||||
`expectedCheckCount()`, `isReadyToAggregate()`. Новый метод `ForceCancel`
|
||||
для фичи 5: отвязывает FIP (best-effort), помечает IP `cancelled`,
|
||||
освобождает валидатора.
|
||||
|
||||
- **`internal/db/queries_sites.go`**: `ListSites`, `UpsertSite(idx, siteID)`
|
||||
(проверяет допустимость `idx` 1..3 и уникальность `site_id` до записи,
|
||||
чтобы вернуть чистую типизированную ошибку, а не сырую SQL), `DeleteSite(idx)`,
|
||||
`GetSiteIndex(siteID) (int, error)` — заменяет текущий
|
||||
`Orchestrator.SiteIndexForID`, который сканирует статический слайс.
|
||||
- **`internal/db/queries_targetgroups.go`**: `ListTargetGroups`,
|
||||
`UpsertTargetGroup(name, targets)`, `DeleteTargetGroup(name)` —
|
||||
**перед удалением проверяет**, что ни один `check_types` не ссылается
|
||||
на эту группу (иначе `ErrInUse`), `GetTargetGroup(name)`.
|
||||
- **`internal/db/queries_checktypes.go`**: `ListCheckTypes`,
|
||||
`ListResolvedCheckTypes` (сразу разворачивает имена групп в плоский
|
||||
список URL — то, что раньше строил `orchestrator.New()` один раз при
|
||||
старте), `UpsertCheckType(name, enabled, targetGroups)` (**проверяет**,
|
||||
что все переданные `targetGroups` существуют — иначе `ErrValidation`),
|
||||
`DeleteCheckType(name)`.
|
||||
- **`internal/db/queries_validators.go`** (дополнить существующий файл):
|
||||
`AdminCreateValidator(id, osPortID)` (409/`ErrConflict`, если уже
|
||||
есть), `AdminUpdateValidatorPort(id, osPortID)` (404/`ErrNotFound`,
|
||||
если нет), `DeleteValidator(id)` (409/`ErrBusy`, если
|
||||
`current_ip_id IS NOT NULL` — валидатор сейчас владеет IP). Не путать
|
||||
с существующим `RegisterValidator` — тот остаётся as-is и продолжает
|
||||
использоваться только агентом при самостоятельной регистрации
|
||||
(`handleAgentRegister`), полей `os_port_id` не трогает при
|
||||
self-registration (это уже так в текущем коде).
|
||||
**Принятый компромисс**: если конфигурация меняется API-запросом ровно в
|
||||
момент агрегации уже идущей проверки, эта попытка агрегирует по текущей
|
||||
(уже изменённой) конфигурации — деградирует безопасно через
|
||||
`missing_counts_as_fail`, самоисправляется на следующей попытке.
|
||||
|
||||
Новый файл **`internal/db/errors.go`** с типизированными сентинелами
|
||||
(`ErrNotFound`, `ErrConflict`, `ErrBusy`, `ErrValidation`, через `errors.New`
|
||||
+ `%w`-обёртку в местах возврата) — чтобы `httpapi`-хендлеры мапили их в
|
||||
404/409/400 через `errors.Is`, а не всё подряд в 500 (как сейчас местами
|
||||
получается по умолчанию).
|
||||
## 6. HTTP API
|
||||
|
||||
### 4. Оркестратор — переход на динамическое чтение конфигурации
|
||||
|
||||
`internal/orchestrator/orchestrator.go`:
|
||||
- Убрать поля `Checks []CheckConfig` и `Sites []config.SiteConfig` из
|
||||
`Orchestrator` (сейчас вычисляются один раз в `New()` и застывают на
|
||||
весь жизненный цикл процесса — это и есть корень проблемы). `Inbound`
|
||||
остаётся статическим полем как сейчас (вне scope).
|
||||
- `AssignmentForValidator` — вместо `return item, o.Checks, nil` вызывает
|
||||
`o.DB.ListResolvedCheckTypes(ctx)` и возвращает актуальный на данный
|
||||
момент список.
|
||||
- `SiteIndexForID` — удаляется, вызовы (`handleProberRegister`,
|
||||
`handleProberAssignments`, `handleProberResults`) переходят на
|
||||
`o.DB.GetSiteIndex(ctx, siteID)`.
|
||||
- `expectedCheckCount()` — читает актуальные `ListResolvedCheckTypes` и
|
||||
`ListSites` из БД на момент агрегации, а не статические поля.
|
||||
|
||||
**Принятый компромисс (осознанно, без over-engineering):** если
|
||||
`check_types`/`targets`/`sites` меняются API-запросом ровно в момент,
|
||||
когда чей-то IP уже находится в `checking` (self-check уже пройден,
|
||||
проверки уже назначены агенту), агрегация этой конкретной попытки
|
||||
посчитает *текущую* (уже изменённую) конфигурацию, а не ту, что была на
|
||||
момент выдачи задания. На практике это узкое окно в несколько секунд
|
||||
между админ-изменением и завершением проверки; деградирует безопасно —
|
||||
через существующий механизм `missing_counts_as_fail` результат в худшем
|
||||
случае будет `partial` вместо `pass` для одной попытки, самоисправляется
|
||||
на следующей (после retry/requeue). Полный snapshot-per-attempt (доп.
|
||||
колонки в `ip_queue` с зафиксированным ожидаемым числом проверок) —
|
||||
возможное будущее усиление, не требуется для этой доработки.
|
||||
|
||||
### 5. HTTP API
|
||||
|
||||
Новый файл **`internal/httpapi/handlers_config.go`** и DTO в
|
||||
`dto.go`. Все — под префиксом `/api/v1/admin/config/*`, JSON в
|
||||
snake_case (в отличие от существующих `/admin/status|ips|validators`,
|
||||
которые отдают сырые Go-поля в PascalCase — для новых, «настоящих»
|
||||
management-эндпоинтов сразу делаем нормальный контракт, старые не
|
||||
трогаем, чтобы не ломать уже задокументированное поведение).
|
||||
|
||||
| Метод | Путь | Тело | Успех | Ошибки |
|
||||
|---|---|---|---|---|
|
||||
| GET | `/api/v1/admin/config/validators` | — | `[{validator_id, os_port_id, state}]` | |
|
||||
| POST | `/api/v1/admin/config/validators` | `{validator_id, os_port_id}` | 201 | 409 если уже есть |
|
||||
| PUT | `/api/v1/admin/config/validators/{id}` | `{os_port_id}` | 200 | 404 |
|
||||
| DELETE | `/api/v1/admin/config/validators/{id}` | — | 200 | 404, 409 если владеет IP |
|
||||
| GET | `/api/v1/admin/config/sites` | — | `[{index, site_id}]` (до 3 строк) | |
|
||||
| PUT | `/api/v1/admin/config/sites/{index}` | `{site_id}` | 200 | 400 если `index` не 1..3, 409 если `site_id` занят другим слотом |
|
||||
| DELETE | `/api/v1/admin/config/sites/{index}` | — | 200 | 404 |
|
||||
| GET | `/api/v1/admin/config/targets` | — | `[{name, targets}]` | |
|
||||
| PUT | `/api/v1/admin/config/targets/{group}` | `{targets:[...]}` | 200 | 400 пустой список |
|
||||
| DELETE | `/api/v1/admin/config/targets/{group}` | — | 200 | 404, 409 если используется check_type'ом |
|
||||
| GET | `/api/v1/admin/config/check-types` | — | `[{name, enabled, targets}]` | |
|
||||
| PUT | `/api/v1/admin/config/check-types/{name}` | `{enabled, targets:[group,...]}` | 200 | 400 если группа не существует |
|
||||
| DELETE | `/api/v1/admin/config/check-types/{name}` | — | 200 | 404 |
|
||||
|
||||
`routes.go`: все существующие и новые `/api/v1/admin/*`-маршруты
|
||||
оборачиваются `s.requireAdmin(...)`.
|
||||
|
||||
### 6. Аутентификация
|
||||
|
||||
- `internal/config/config.go`: в `ServerConfig` добавить
|
||||
`AdminTokenEnv string \`yaml:"admin_token_env"\`` — по аналогии с
|
||||
`openstack.*_env` полями (в YAML — только *имя* переменной, не сам
|
||||
токен).
|
||||
- `internal/httpapi/server.go`: `Server.AdminToken string` +
|
||||
`func (s *Server) requireAdmin(next http.HandlerFunc) http.HandlerFunc`
|
||||
— сверяет `Authorization: Bearer <token>` через
|
||||
`crypto/subtle.ConstantTimeCompare`. Если `s.AdminToken == ""` —
|
||||
пропускает без проверки (обратная совместимость).
|
||||
- `cmd/control-api/main.go`: если `cfg.Server.AdminTokenEnv` задан, но
|
||||
`os.Getenv(...)` пуст — **отказ запуска** с понятной ошибкой
|
||||
(fail-safe, не запускаемся с «пустым паролем»). Если
|
||||
`AdminTokenEnv` вообще не задан — запускаемся как сейчас, но пишем
|
||||
явный `log.Warn` про незащищённый admin API.
|
||||
- `configs/control-api.example.yaml`, `deploy/systemd/control-api.service`
|
||||
(добавить пример переменной в `EnvironmentFile`) — обновить.
|
||||
|
||||
### 7. Обновление существующих тестов и добавление новых
|
||||
|
||||
- `internal/orchestrator/orchestrator_test.go`,
|
||||
`internal/httpapi/httpapi_test.go`: заменить ручное построение
|
||||
`cfg.CheckTypes/.Targets/.Sites` + прямые поля `Orchestrator{Checks:...}`
|
||||
на `db.BootstrapFromConfig(ctx, cfg)` перед `orchestrator.New(...)` —
|
||||
сами тестовые сценарии (happy path, partial, lease reclaim) не меняются
|
||||
по сути, меняется только способ засеять конфигурацию.
|
||||
- Новые unit-тесты: `internal/db/queries_dynconfig_test.go` (CRUD +
|
||||
граничные случаи: удаление занятого валидатора → `ErrBusy`, удаление
|
||||
группы целей, на которую ссылается check_type → `ErrInUse`,
|
||||
upsert check_type с несуществующей группой → `ErrValidation`, upsert
|
||||
сайта с чужим `site_id` → `ErrConflict`, bootstrap на непустой таблице
|
||||
→ YAML игнорируется).
|
||||
- Новый `internal/httpapi/handlers_config_test.go` (или расширение
|
||||
`httpapi_test.go`): сквозной сценарий — создать валидатора и сайт через
|
||||
API вместо конфига, убедиться, что IP реально дошёл до `done`; смена
|
||||
`check_types` между запусками влияет на следующий назначенный IP.
|
||||
- Обновить `scripts/run-local-e2e.sh` не требуется по сути (bootstrap
|
||||
из YAML при пустой БД работает как раньше), но стоит добавить один шаг
|
||||
с `curl -X PUT .../config/check-types/ssh` как живую демонстрацию.
|
||||
|
||||
### 8. Документация (после реализации)
|
||||
|
||||
- `docs/API.md`: новый раздел «Методы управления конфигурацией» с
|
||||
таблицей выше + примеры curl (создание валидатора, отключение ssh,
|
||||
добавление цели, назначение площадки на слот) + раздел про
|
||||
`Authorization: Bearer`.
|
||||
- `docs/SETUP.md`: шаг про `server.admin_token_env` в
|
||||
«Переменные окружения для OpenStack» (переименовать раздел или
|
||||
добавить рядом «и для admin-токена»); явно описать новую
|
||||
bootstrap-once семантику `validators`/`sites`/`check_types`/`targets`.
|
||||
- `docs/USAGE.md`: заменить текущие разделы «Управление валидаторами» /
|
||||
«Управление площадками» (сейчас там «только через YAML + restart») на
|
||||
актуальные — через API; убрать утверждение «нет API-метода» там, где
|
||||
оно перестало быть верным.
|
||||
- `docs/DIAGRAMS.md`: в диаграмму control plane (раздел 1) добавить
|
||||
новую стрелку «Оператор → HTTP API → БД (config CRUD)» вместо текущей
|
||||
«CFG → читается при старте (инициализация)» как единственного пути.
|
||||
`POST /api/v1/admin/ips` — постановка/принудительный перезапуск (фичи 1 и
|
||||
4). `POST /api/v1/admin/ips/{ip}/cancel` — остановка (фича 5).
|
||||
`/api/v1/admin/config/{validators,sites,targets,check-types}` — CRUD
|
||||
(фичи 2 и 3), snake_case DTO. Подробности — `docs/API.md`.
|
||||
|
||||
## Критичные файлы
|
||||
|
||||
- `internal/db/migrations/0002_dynamic_config.sql` (новый)
|
||||
- `internal/db/db.go` (обобщить `migrate()`)
|
||||
- `internal/db/bootstrap.go` (новый)
|
||||
- `internal/db/errors.go` (новый)
|
||||
- `internal/db/migrations/0002_dynamic_config.sql`, `internal/db/db.go`,
|
||||
`internal/db/bootstrap.go`, `internal/db/errors.go`, `internal/db/models.go`
|
||||
- `internal/db/queries_sites.go`, `queries_targetgroups.go`,
|
||||
`queries_checktypes.go` (новые), `queries_validators.go` (дополнить)
|
||||
- `internal/orchestrator/orchestrator.go` (убрать статические
|
||||
`Checks`/`Sites`, читать из БД)
|
||||
- `internal/httpapi/handlers_config.go` (новый), `dto.go`, `routes.go`,
|
||||
`server.go` (`requireAdmin`)
|
||||
- `internal/config/config.go` (`AdminTokenEnv`)
|
||||
- `cmd/control-api/main.go` (bootstrap-вызов, проверка токена при старте)
|
||||
`queries_checktypes.go`, `queries_validators.go`, `queries_ipqueue.go`
|
||||
- `internal/orchestrator/orchestrator.go`
|
||||
- `internal/httpapi/handlers_config.go`, `handlers_admin.go`,
|
||||
`dto_admin.go`, `routes.go`
|
||||
- `cmd/control-api/main.go`
|
||||
|
||||
## Проверка (когда план будет реализовываться)
|
||||
## Проверка
|
||||
|
||||
1. `go build ./... && go test ./...` — все существующие + новые unit- и
|
||||
httpapi-тесты проходят.
|
||||
2. `scripts/run-local-e2e.sh` — офлайн-сценарий по-прежнему проходит от
|
||||
начала до конца без ручного вмешательства (bootstrap из YAML при
|
||||
пустой БД работает как раньше).
|
||||
3. Ручная проверка нового контракта: поднять `control-api` с пустой БД и
|
||||
`admin_token_env` без токена → админ-запрос без заголовка проходит;
|
||||
задать токен → запрос без `Authorization` получает 401; создать
|
||||
валидатора/площадку/группу целей/тип проверки через API без
|
||||
единой строчки в YAML, убедиться, что IP реально проходит полный цикл
|
||||
проверки на этой конфигурации; попытаться удалить валидатора, пока он
|
||||
владеет IP → 409; перезапустить `control-api` и убедиться, что
|
||||
API-изменения пережили рестарт, а YAML их не затёр.
|
||||
1. `go build ./... && go test ./...`
|
||||
2. `scripts/run-local-e2e.sh`
|
||||
3. Ручная проверка: создать validator/site/target-group/check-type только
|
||||
через API; `POST /admin/ips` с уже `done`-адресом → повторный полный
|
||||
цикл; `POST /admin/ips` с адресом в `checking` → не трогается;
|
||||
`POST /admin/ips/{ip}/cancel` во время `checking` → FIP отвязан,
|
||||
валидатор свободен, `overall_result=cancelled`; удаление занятого
|
||||
валидатора → 409; рестарт control-api → все API-изменения сохранились.
|
||||
+75
-15
@@ -21,6 +21,7 @@
|
||||
- [Развёртывание control-api](#развёртывание-control-api)
|
||||
- [Развёртывание validator-agent на ВМ-валидаторах](#развёртывание-validator-agent-на-вм-валидаторах)
|
||||
- [Развёртывание prober на внешних площадках](#развёртывание-prober-на-внешних-площадках)
|
||||
- [Развёртывание admin-dashboard](#развёртывание-admin-dashboard)
|
||||
- [Проверка после запуска](#проверка-после-запуска)
|
||||
- [Сетевые доступы](#сетевые-доступы)
|
||||
|
||||
@@ -31,10 +32,13 @@
|
||||
| `control-api` | Отдельная управляющая машина/ВМ с доступом к OpenStack API | 1 (без HA) |
|
||||
| `validator-agent` | Каждая ВМ-валидатор в сервисном проекте облака | по числу валидаторов |
|
||||
| `prober` | По одному на каждой из внешних тестовых площадок | 3 (по числу площадок) |
|
||||
| `admin-dashboard` | Любая машина с сетевым доступом до `control-api` (опционально) | 0 или 1 |
|
||||
|
||||
`control-api` — единственный компонент с состоянием (SQLite). Валидаторы и
|
||||
проберы не хранят локального состояния и полностью управляются через опрос
|
||||
control-api (см. [API.md](API.md)).
|
||||
`control-api` — единственный компонент с состоянием (SQLite). Валидаторы,
|
||||
проберы и `admin-dashboard` не хранят локального состояния и полностью
|
||||
управляются через опрос control-api (см. [API.md](API.md),
|
||||
[DASHBOARD.md](DASHBOARD.md)). `admin-dashboard` не обязателен — вся его
|
||||
функциональность доступна и через `curl` напрямую по API.
|
||||
|
||||
## Требования
|
||||
|
||||
@@ -58,7 +62,7 @@ control-api (см. [API.md](API.md)).
|
||||
|
||||
### Вариант A: готовые бинарники из репозитория (рекомендуется)
|
||||
|
||||
В директории `bin/` репозитория уже лежат три готовых бинарника —
|
||||
В директории `bin/` репозитория уже лежат четыре готовых бинарника —
|
||||
собирать их на целевых серверах не нужно, разворачивание сразу
|
||||
начинается с копирования и запуска (раздел
|
||||
[«Развёртывание control-api»](#развёртывание-control-api) и далее).
|
||||
@@ -68,6 +72,7 @@ bin/
|
||||
├── control-api # ~11 МБ
|
||||
├── validator-agent # ~7 МБ
|
||||
├── prober # ~7 МБ
|
||||
├── admin-dashboard # ~9 МБ (опционален, см. DASHBOARD.md)
|
||||
└── SHA256SUMS
|
||||
```
|
||||
|
||||
@@ -97,6 +102,7 @@ sha256sum -c bin/SHA256SUMS
|
||||
scp bin/control-api control-api-host:/tmp/
|
||||
scp bin/validator-agent validator-host-01:/tmp/
|
||||
scp bin/prober probe-site-1:/tmp/
|
||||
scp bin/admin-dashboard dashboard-host:/tmp/ # опционально
|
||||
```
|
||||
|
||||
> Если целевая платформа отличается от linux/amd64 (например, ВМ на
|
||||
@@ -113,11 +119,13 @@ export CGO_ENABLED=0 GOOS=linux GOARCH=amd64 # поменяйте GOARCH дл
|
||||
go build -trimpath -ldflags="-s -w" -o bin/control-api ./cmd/control-api
|
||||
go build -trimpath -ldflags="-s -w" -o bin/validator-agent ./cmd/validator-agent
|
||||
go build -trimpath -ldflags="-s -w" -o bin/prober ./cmd/prober
|
||||
go build -trimpath -ldflags="-s -w" -o bin/admin-dashboard ./cmd/admin-dashboard
|
||||
```
|
||||
|
||||
Каждый бинарник самодостаточен — скопируйте нужный файл на
|
||||
соответствующую машину (control-api → управляющая машина, validator-agent
|
||||
→ каждый валидатор, prober → каждая площадка).
|
||||
→ каждый валидатор, prober → каждая площадка, admin-dashboard →
|
||||
опционально, любая машина с доступом до control-api).
|
||||
|
||||
Убедиться, что всё собирается и юнит-тесты проходят:
|
||||
|
||||
@@ -130,7 +138,7 @@ go build ./... && go test ./...
|
||||
обновляются автоматически**:
|
||||
|
||||
```bash
|
||||
sha256sum bin/control-api bin/validator-agent bin/prober | sed 's#bin/##' > bin/SHA256SUMS
|
||||
sha256sum bin/control-api bin/validator-agent bin/prober bin/admin-dashboard | sed 's#bin/##' > bin/SHA256SUMS
|
||||
```
|
||||
|
||||
## Быстрая проверка без OpenStack (offline-режим)
|
||||
@@ -181,6 +189,15 @@ cp configs/control-api.example.yaml /etc/cloud-ip-validator/control-api.yaml
|
||||
целей для egress-проверок (по умолчанию — hub.docker.com, github.com,
|
||||
packages.ubuntu.com) или включите `ssh` (по умолчанию выключен).
|
||||
|
||||
> `validators`, `sites`, `targets` и `check_types` читаются из этого файла
|
||||
> только один раз — при самом первом старте против пустой базы данных
|
||||
> (bootstrap). После этого все последующие изменения этих четырёх секций
|
||||
> вносятся через `/api/v1/admin/config/*`, а не правкой YAML — см.
|
||||
> [API.md](API.md#управление-очередью-и-конфигурацией) и
|
||||
> [USAGE.md](USAGE.md#управление-валидаторами). `ip_addresses` — исключение,
|
||||
> он остаётся YAML + аддитивным добавлением при каждом старте (плюс
|
||||
> `POST /api/v1/admin/ips` для управления очередью без рестарта).
|
||||
|
||||
### 2. Переменные окружения для OpenStack
|
||||
|
||||
Учётные данные передаются **только** через переменные окружения — никогда
|
||||
@@ -276,15 +293,29 @@ systemctl enable --now control-api
|
||||
|
||||
**Первичная инициализация базы данных происходит автоматически** — при
|
||||
первом старте `control-api` создаёт файл SQLite по пути `database.path`
|
||||
из конфига (миграция схемы применяется один раз, повторные запуски —
|
||||
no-op). Отдельной команды "init db" не требуется.
|
||||
из конфига (миграции схемы применяются один раз каждая, повторные запуски
|
||||
— no-op). Отдельной команды "init db" не требуется.
|
||||
|
||||
При каждом старте control-api также:
|
||||
1. Регистрирует в БД всех валидаторов из `validators` конфига (если их
|
||||
там ещё нет).
|
||||
2. Добавляет в очередь все адреса из `ip_addresses`, которых там ещё нет
|
||||
(уже обработанные ранее адреса повторно не добавляются и не
|
||||
сбрасываются — см. [USAGE.md](USAGE.md#добавление-новых-ip-в-очередь)).
|
||||
1. **Bootstrap-once для `validators`/`sites`/`targets`/`check_types`.**
|
||||
YAML применяется **только если соответствующая таблица в БД сейчас
|
||||
пуста** — то есть только на самом первом старте против чистой базы.
|
||||
Как только в таблице появилась хотя бы одна строка (через этот
|
||||
bootstrap либо через `/api/v1/admin/config/*`, см.
|
||||
[API.md](API.md#управление-очередью-и-конфигурацией)), YAML для этой
|
||||
секции больше не перечитывается ни при одном последующем рестарте —
|
||||
источник истины переключается на БД. Это осознанное отличие от более
|
||||
ранних версий, где `validators` из YAML переприменялись при каждом
|
||||
рестарте: теперь правки, сделанные через admin API (например, смена
|
||||
`os_port_id` валидатора), переживают рестарт вместо того, чтобы
|
||||
тихо откатываться.
|
||||
2. **Всегда аддитивно** добавляет в очередь все адреса из
|
||||
`ip_addresses`, которых там ещё нет (уже обработанные ранее адреса
|
||||
повторно не добавляются и не сбрасываются — см.
|
||||
[USAGE.md](USAGE.md#добавление-новых-ip-в-очередь)). Это отдельный,
|
||||
не завязанный на bootstrap-once путь — не путайте с
|
||||
`POST /api/v1/admin/ips`, который умеет то же самое (и ещё
|
||||
принудительный повтор уже проверенных адресов) без перезапуска.
|
||||
|
||||
Проверить, что процесс поднялся:
|
||||
|
||||
@@ -327,6 +358,28 @@ systemctl enable --now prober
|
||||
journalctl -u prober -f
|
||||
```
|
||||
|
||||
## Развёртывание admin-dashboard
|
||||
|
||||
Опционально — вся его функциональность доступна и через `curl` напрямую
|
||||
по API (см. [API.md](API.md)). На любой машине с сетевым доступом до
|
||||
`control-api`:
|
||||
|
||||
```bash
|
||||
cp bin/admin-dashboard /usr/local/bin/admin-dashboard
|
||||
cp deploy/systemd/admin-dashboard.service /etc/systemd/system/
|
||||
mkdir -p /etc/cloud-ip-validator
|
||||
cp configs/admin-dashboard.example.yaml /etc/cloud-ip-validator/admin-dashboard.yaml
|
||||
# отредактировать control_api.base_url под ваш стенд
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now admin-dashboard
|
||||
journalctl -u admin-dashboard -f
|
||||
```
|
||||
|
||||
Открыть `http://<admin-dashboard>:8090/` в браузере. Подробнее о
|
||||
страницах и о том, что дашборд может (и не может) — в
|
||||
[DASHBOARD.md](DASHBOARD.md).
|
||||
|
||||
## Проверка после запуска
|
||||
|
||||
После того как control-api, все валидаторы и все три пробера запущены:
|
||||
@@ -376,7 +429,14 @@ curl -s http://<control-api>:8080/api/v1/admin/validators | python3 -m json.tool
|
||||
очередь — см. [USAGE.md](USAGE.md#частые-проблемы-и-что-с-ними-делать).
|
||||
- `control-api` → OpenStack Keystone/Neutron API (`OS_AUTH_URL` и далее по
|
||||
каталогу сервисов).
|
||||
- `admin-dashboard` → `control-api`: тот же порт (`server.listen_addr`),
|
||||
адрес задаётся в `control_api.base_url` конфига дашборда.
|
||||
- Оператор (браузер) → `admin-dashboard`: порт из `server.listen_addr`
|
||||
дашборда (по умолчанию 8090).
|
||||
|
||||
API control-api сейчас не аутентифицирован (см. предупреждение в начале
|
||||
[API.md](API.md)) — ограничивайте доступ к порту control-api на уровне
|
||||
сети/firewall теми хостами, где реально работают валидаторы и проберы.
|
||||
[API.md](API.md)) — то же самое верно и для `admin-dashboard`, который
|
||||
это API оборачивает. Ограничивайте доступ к обоим портам на уровне
|
||||
сети/firewall: к control-api — теми хостами, где реально работают
|
||||
валидаторы, проберы и сам дашборд; к дашборду — теми, кому разрешено
|
||||
администрировать стенд.
|
||||
+178
-45
@@ -17,7 +17,9 @@
|
||||
- [Просмотр деталей и истории по конкретному адресу](#просмотр-деталей-и-истории-по-конкретному-адресу)
|
||||
- [Управление валидаторами](#управление-валидаторами)
|
||||
- [Управление площадками (проберами)](#управление-площадками-проберами)
|
||||
- [Управление целями проверки](#управление-целями-проверки)
|
||||
- [Повторная проверка адреса](#повторная-проверка-адреса)
|
||||
- [Принудительная остановка проверки](#принудительная-остановка-проверки)
|
||||
- [Частые проблемы и что с ними делать](#частые-проблемы-и-что-с-ними-делать)
|
||||
|
||||
## Как устроена работа с системой
|
||||
@@ -43,29 +45,32 @@
|
||||
|
||||
## Добавление новых IP в очередь
|
||||
|
||||
**В текущей версии добавление адресов происходит только через конфиг
|
||||
control-api**, отдельного API-метода "добавить IP в очередь" нет.
|
||||
Основной способ — API, без перезапуска процесса:
|
||||
|
||||
1. Добавьте новые адреса в список `ip_addresses` в
|
||||
`/etc/cloud-ip-validator/control-api.yaml` (в конец списка, либо в
|
||||
нужном порядке — очередь обрабатывается строго в порядке следования
|
||||
списка, `sequence`).
|
||||
2. Перезапустите control-api:
|
||||
```bash
|
||||
systemctl restart control-api
|
||||
```
|
||||
```bash
|
||||
curl -s -X POST http://<control-api>:8080/api/v1/admin/ips \
|
||||
-d '{"addresses": ["203.0.113.10", "203.0.113.11"]}'
|
||||
```
|
||||
|
||||
Это безопасно для уже идущей работы: при старте control-api добавляет в
|
||||
очередь только **новые** адреса (те, которых там ещё нет) — уже
|
||||
обработанные ранее адреса не сбрасываются и повторно не проверяются.
|
||||
Адреса, которые были удалены из `ip_addresses`, но уже есть в базе,
|
||||
**не удаляются** из очереди/истории автоматически — если конкретный адрес
|
||||
больше не нужно проверять и его нет в очереди/в процессе, можно просто
|
||||
оставить как есть (историю он не портит).
|
||||
```json
|
||||
{"added": ["203.0.113.10", "203.0.113.11"], "requeued": [], "reordered": [], "skipped_in_progress": []}
|
||||
```
|
||||
|
||||
> Совет: держите `control-api.yaml` под версионным контролем (git) —
|
||||
> список адресов на проверку тогда одновременно служит и журналом того,
|
||||
> что вообще когда-либо ставилось в очередь.
|
||||
Адреса обрабатываются в порядке, в котором перечислены в `addresses` —
|
||||
именно в этом порядке они и встанут в очередь друг за другом. Метод
|
||||
идемпотентен относительно уже идущих проверок: адрес, который сейчас
|
||||
активно проверяется, в ответе окажется в `skipped_in_progress` и не будет
|
||||
тронут (см. [«Повторная проверка адреса»](#повторная-проверка-адреса)
|
||||
ниже — тот же метод форсирует перепроверку уже завершённых адресов).
|
||||
|
||||
Также по-прежнему можно добавить адреса через `ip_addresses` в
|
||||
`/etc/cloud-ip-validator/control-api.yaml` и перезапустить control-api —
|
||||
при каждом старте control-api доливает в очередь только новые адреса из
|
||||
этого списка (уже обработанные ранее не сбрасываются и повторно не
|
||||
проверяются). Держать `control-api.yaml` под версионным контролем (git)
|
||||
по-прежнему полезно как журнал того, что изначально ставилось в очередь
|
||||
при разворачивании стенда — но для повседневного добавления адресов проще
|
||||
и быстрее пользоваться API выше.
|
||||
|
||||
## Наблюдение за очередью
|
||||
|
||||
@@ -108,7 +113,7 @@ curl -s http://<control-api>:8080/api/v1/admin/ips \
|
||||
| Поле | Значение |
|
||||
|---|---|
|
||||
| `IPAddress` | Проверяемый адрес |
|
||||
| `Sequence` | Позиция в очереди (порядок из конфига) |
|
||||
| `Sequence` | Позиция в очереди (порядок постановки — из конфига при первом старте либо из последнего вызова `POST /api/v1/admin/ips`) |
|
||||
| `State` | Текущий этап: `queued`, `assigning_fip`, `awaiting_self_check`, `checking`, `aggregating`, `done`, `failed` |
|
||||
| `OwnerValidatorID` | Какой валидатор сейчас (или последним) занимался этим адресом |
|
||||
| `FIPID` | Идентификатор Floating IP в OpenStack, к которому привязан адрес (пусто, если ещё/уже не привязан) |
|
||||
@@ -116,7 +121,7 @@ curl -s http://<control-api>:8080/api/v1/admin/ips \
|
||||
| `RetryCount` | Сколько раз адрес уже переставлялся в очередь заново |
|
||||
| `EgressComplete` | Валидатор закончил исходящие проверки |
|
||||
| `Site1Complete` / `Site2Complete` / `Site3Complete` | Соответствующая площадка закончила входящие проверки |
|
||||
| `OverallResult` | Итог: `pass`, `partial`, `fail`, либо пусто, пока проверка не завершена |
|
||||
| `OverallResult` | Итог: `pass`, `partial`, `fail`, `cancelled` (принудительно остановлена, см. [«Принудительная остановка проверки»](#принудительная-остановка-проверки)), либо пусто, пока проверка не завершена |
|
||||
| `AssignedAt` / `AggregatedAt` / `FIPReleasedAt` | Метки времени соответствующих этапов |
|
||||
|
||||
## Как читать итоговый результат (pass/partial/fail)
|
||||
@@ -137,6 +142,10 @@ curl -s http://<control-api>:8080/api/v1/admin/ips \
|
||||
трафик валидатора не пошёл через назначенный FIP — и попытки
|
||||
исчерпались). Смотрите `events` по этому адресу (см. ниже), чтобы
|
||||
понять, на каком шаге и почему.
|
||||
- **`cancelled`** — проверку остановил оператор через `POST
|
||||
/api/v1/admin/ips/{ip}/cancel` (`State` при этом — `failed`), а не
|
||||
система по итогам проверок. Отличать от обычного `fail` полезно, чтобы
|
||||
не путать «адрес не прошёл проверку» с «проверку прервали вручную».
|
||||
|
||||
Отсутствие ответа от источника (площадка не прислала результат до
|
||||
истечения `checking_window_seconds`) засчитывается как провал — это
|
||||
@@ -180,21 +189,48 @@ curl -s http://<control-api>:8080/api/v1/admin/validators | python3 -m json.tool
|
||||
(занят), `unreachable` (пропустил heartbeat дольше
|
||||
`orchestrator.heartbeat_timeout_seconds`).
|
||||
|
||||
**Добавление нового валидатора:**
|
||||
**Добавление нового валидатора (без перезапуска control-api):**
|
||||
1. Поднимите новую ВМ в сервисном проекте облака, узнайте её Neutron
|
||||
`port_id`.
|
||||
2. Добавьте запись в `validators` в `control-api.yaml`
|
||||
(`validator_id` + `os_port_id`) и перезапустите `control-api`.
|
||||
2. Зарегистрируйте валидатора через API:
|
||||
```bash
|
||||
curl -s -X POST http://<control-api>:8080/api/v1/admin/config/validators \
|
||||
-d '{"validator_id": "validator_05", "os_port_id": "port-abc123"}'
|
||||
```
|
||||
3. Разверните и запустите `validator-agent` на новой ВМ с тем же
|
||||
`validator_id` в его конфиге (см. [SETUP.md](SETUP.md#развёртывание-validator-agent-на-вм-валидаторах)).
|
||||
|
||||
Сменить `os_port_id` уже существующего валидатора (например, после
|
||||
пересоздания ВМ) — `PUT /api/v1/admin/config/validators/{id}` с телом
|
||||
`{"os_port_id": "новый-port-id"}`.
|
||||
|
||||
Полный список зарегистрированных валидаторов — `GET
|
||||
/api/v1/admin/config/validators` (в отличие от `GET
|
||||
/api/v1/admin/validators`, отдаёт `snake_case` и без текущего IP —
|
||||
только конфигурационные поля).
|
||||
|
||||
**Вывод валидатора из эксплуатации:** остановите на нём
|
||||
`validator-agent` (`systemctl stop validator-agent`). Он перестанет
|
||||
получать новые задания после того, как закончит текущее (если оно было);
|
||||
если он был убит посреди работы — control-api сам заберёт у него
|
||||
незавершённый адрес обратно в очередь по истечении
|
||||
`orchestrator.lease_ttl_seconds`. Удалять запись из `control-api.yaml`
|
||||
не обязательно — просто выключенный агент не будет ничего забирать.
|
||||
`orchestrator.lease_ttl_seconds`. Удалять регистрацию валидатора не
|
||||
обязательно — просто выключенный агент не будет ничего забирать. Если всё
|
||||
же нужно убрать валидатора из системы совсем:
|
||||
```bash
|
||||
curl -s -X DELETE http://<control-api>:8080/api/v1/admin/config/validators/validator_05
|
||||
```
|
||||
Возвращает `409`, если валидатор прямо сейчас владеет каким-то IP —
|
||||
дождитесь освобождения (или принудительно остановите его проверку, см.
|
||||
[«Принудительная остановка проверки»](#принудительная-остановка-проверки))
|
||||
перед удалением.
|
||||
|
||||
> Правки через `validators[]` в `control-api.yaml` тоже поддерживаются,
|
||||
> но только как bootstrap пустой базы данных при самом первом старте — как
|
||||
> только в БД есть хотя бы один валидатор, YAML для этой секции
|
||||
> игнорируется при всех последующих рестартах (см.
|
||||
> [SETUP.md](SETUP.md#развёртывание-control-api)). Для стенда, который уже
|
||||
> хоть раз запускался, используйте API выше.
|
||||
|
||||
## Управление площадками (проберами)
|
||||
|
||||
@@ -207,12 +243,28 @@ curl -s http://<control-api>:8080/api/v1/admin/validators | python3 -m json.tool
|
||||
Явного отдельного флага "включить/выключить" нет — самого списка `sites`
|
||||
достаточно.
|
||||
|
||||
Чтобы добавить площадку: добавьте `site_id` + `index` (1, 2 или 3 — см.
|
||||
ограничение ниже) в `sites` конфига control-api и разверните на площадке
|
||||
`prober` с тем же `site_id`. Чтобы отключить конкретную площадку —
|
||||
уберите соответствующую запись из `sites` и перезапустите control-api;
|
||||
Чтобы добавить площадку (без перезапуска control-api) — назначьте
|
||||
`site_id` на один из трёх слотов (`index` 1, 2 или 3 — см. ограничение
|
||||
ниже) через API:
|
||||
```bash
|
||||
curl -s -X PUT http://<control-api>:8080/api/v1/admin/config/sites/1 \
|
||||
-d '{"site_id": "site-1"}'
|
||||
```
|
||||
и разверните на площадке `prober` с тем же `site_id`. Чтобы отключить
|
||||
конкретную площадку — освободите слот:
|
||||
```bash
|
||||
curl -s -X DELETE http://<control-api>:8080/api/v1/admin/config/sites/1
|
||||
```
|
||||
процесс `prober` на ней можно не останавливать (он просто перестанет
|
||||
получать назначения).
|
||||
получать назначения — `POST /api/v1/probers/register` для отвязанного
|
||||
`site_id` начнёт отвечать `400`). Текущее распределение слотов — `GET
|
||||
/api/v1/admin/config/sites`.
|
||||
|
||||
> Правки через `sites[]` в `control-api.yaml` тоже поддерживаются, но
|
||||
> только как bootstrap пустой базы данных при самом первом старте — как
|
||||
> только в БД есть хотя бы одна площадка, YAML для этой секции
|
||||
> игнорируется при всех последующих рестартах. Для стенда, который уже
|
||||
> хоть раз запускался, используйте API выше.
|
||||
|
||||
> Важно: количество *возможных* слотов площадок жёстко зашито в схему БД
|
||||
> (`Site1Complete`/`Site2Complete`/`Site3Complete`) — не более **трёх**,
|
||||
@@ -221,23 +273,104 @@ curl -s http://<control-api>:8080/api/v1/admin/validators | python3 -m json.tool
|
||||
> поддерживаемый сценарий; *больше* трёх потребует доработки схемы
|
||||
> данных, одной правкой конфига не обойтись.
|
||||
|
||||
## Управление целями проверки
|
||||
|
||||
Набор egress-целей (`targets`) и типов проверок (`check_types`,
|
||||
привязывающих тип — `https`/`icmp`/`ssh` — к одной или нескольким группам
|
||||
целей) управляется через API так же, как валидаторы и площадки —
|
||||
изменения подхватываются немедленно, следующим же назначением от
|
||||
оркестратора, без перезапуска.
|
||||
|
||||
Посмотреть текущий набор:
|
||||
```bash
|
||||
curl -s http://<control-api>:8080/api/v1/admin/config/targets | python3 -m json.tool
|
||||
curl -s http://<control-api>:8080/api/v1/admin/config/check-types | python3 -m json.tool
|
||||
```
|
||||
|
||||
Создать/заменить группу целей и включить тип проверки, ссылающийся на
|
||||
неё:
|
||||
```bash
|
||||
curl -s -X PUT http://<control-api>:8080/api/v1/admin/config/targets/web \
|
||||
-d '{"targets": ["https://hub.docker.com", "https://github.com"]}'
|
||||
|
||||
curl -s -X PUT http://<control-api>:8080/api/v1/admin/config/check-types/ssh \
|
||||
-d '{"enabled": true, "targets": ["web"]}'
|
||||
```
|
||||
|
||||
Отключить тип проверки, не удаляя его (значения целей сохраняются):
|
||||
```bash
|
||||
curl -s -X PUT http://<control-api>:8080/api/v1/admin/config/check-types/ssh \
|
||||
-d '{"enabled": false, "targets": ["web"]}'
|
||||
```
|
||||
|
||||
Удалить группу целей можно только если на неё не ссылается ни один
|
||||
`check_type` (иначе — `409`); удалить сам `check_type` можно в любой
|
||||
момент (`DELETE /api/v1/admin/config/check-types/{name}`).
|
||||
|
||||
**Важное следствие принятого компромисса**: если конфигурация меняется
|
||||
ровно в момент, когда чей-то IP уже находится в `checking` (self-check
|
||||
уже пройден, проверки уже назначены агенту), агрегация этой конкретной
|
||||
попытки посчитает уже изменённую конфигурацию, а не ту, что была на
|
||||
момент выдачи задания. На практике это узкое окно в несколько секунд;
|
||||
деградирует безопасно — через `aggregation.missing_counts_as_fail` худший
|
||||
исход для одной попытки — `partial` вместо `pass`, самоисправляется на
|
||||
следующей попытке (в том числе через принудительный повтор, см. ниже).
|
||||
|
||||
> Правки через `targets`/`check_types` в `control-api.yaml` тоже
|
||||
> поддерживаются, но только как bootstrap пустой базы данных при самом
|
||||
> первом старте — см. примечание в разделах выше.
|
||||
|
||||
## Повторная проверка адреса
|
||||
|
||||
Если адрес завершился с `failed` или `partial`, а вы хотите перепроверить
|
||||
его ещё раз (например, после устранения блокировки на стороне сети):
|
||||
на данный момент нет отдельного API-метода "перезапустить проверку".
|
||||
Самый простой путь:
|
||||
1. Убедитесь, что адрес не находится в активном состоянии (`checking`
|
||||
и т.п.) — то есть уже `done`/`failed`.
|
||||
2. Временно уберите и снова добавьте адрес в список `ip_addresses`
|
||||
(либо просто пересоздайте запись в БД вручную, если это единичный
|
||||
случай и у вас есть доступ к SQLite) и перезапустите `control-api`.
|
||||
его ещё раз (например, после устранения блокировки на стороне сети) —
|
||||
отправьте его тем же методом, что используется для постановки новых
|
||||
адресов в очередь:
|
||||
|
||||
Поскольку сидирование очереди идёт по уникальности `ip_address`
|
||||
(конфликт по уже существующей записи просто игнорируется), самый чистый
|
||||
способ гарантированно перепроверить конкретный адрес — обратиться к
|
||||
администратору БД (см. следующий раздел) либо дождаться штатной
|
||||
доработки API под повторные проверки.
|
||||
```bash
|
||||
curl -s -X POST http://<control-api>:8080/api/v1/admin/ips \
|
||||
-d '{"addresses": ["203.0.113.10"]}'
|
||||
```
|
||||
|
||||
```json
|
||||
{"added": [], "requeued": ["203.0.113.10"], "reordered": [], "skipped_in_progress": []}
|
||||
```
|
||||
|
||||
Адрес в `requeued` означает, что он был в терминальном состоянии
|
||||
(`done`/`failed`) и его перезапустили: `AttemptNumber` увеличился,
|
||||
`RetryCount` обнулён, предыдущий `OverallResult` сброшен, адрес снова
|
||||
`queued` и будет обработан на общих основаниях. Никакой особой обработки
|
||||
для уже проверенных адресов не требуется — тот же вызов безопасно
|
||||
принимает список из новых и уже проверенных адресов одновременно;
|
||||
единственное, что метод не сделает — не запустит вторую параллельную
|
||||
проверку адреса, который прямо сейчас уже проверяется (такой адрес
|
||||
вернётся в `skipped_in_progress`, см.
|
||||
[«Добавление новых IP в очередь»](#добавление-новых-ip-в-очередь)).
|
||||
|
||||
## Принудительная остановка проверки
|
||||
|
||||
Если проверка адреса зависла дольше ожидаемого либо просто больше не
|
||||
актуальна, не дожидайтесь истечения `checking_window_seconds` —
|
||||
остановите её сразу:
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://<control-api>:8080/api/v1/admin/ips/203.0.113.10/cancel
|
||||
```
|
||||
|
||||
Работает из любого состояния, кроме уже терминального (`done`/`failed`
|
||||
вернут `409` — отменять нечего). Если на момент отмены был привязан
|
||||
Floating IP — он отвязывается (best-effort, как и при обычном завершении
|
||||
проверки), владевший валидатор освобождается и снова становится `idle`.
|
||||
Итог записывается как `OverallResult: "cancelled"` (в `State: "failed"`),
|
||||
и виден в истории адреса наравне с обычными результатами:
|
||||
|
||||
```bash
|
||||
curl -s http://<control-api>:8080/api/v1/admin/ips/203.0.113.10 | python3 -m json.tool
|
||||
```
|
||||
|
||||
Чтобы позже всё же проверить этот адрес — используйте
|
||||
[«Повторную проверку адреса»](#повторная-проверка-адреса) выше, она
|
||||
одинаково работает и для отменённых, и для обычно завершённых адресов.
|
||||
|
||||
## Частые проблемы и что с ними делать
|
||||
|
||||
|
||||
Reference in new issue
Block a user