deploy: реальные прогоны раунда 2 в VERIFICATION-21 (#56)

- netns-ci в контейнере: причина (ip netns exec/ip -n перемонтируют /sys),
  переход на nsenter, контракт кодов 0/1/3/4 — все четыре воспроизведены.
- гейт покрытия: make test-deploy = 51 passed, 98.86 %.

Refs #21, Refs #15, Refs #56
This commit is contained in:
2026-10-28 21:32:32 +03:00
parent edf22d7c93
commit d63b9ceafe
+67
View File
@@ -125,3 +125,70 @@ make dev-down # остановить стенд, каталоги /opt/
make dev-reset # + удалить осиротевшие контейнеры/сети проекта
make test-netns # netns-харнесс сносит топологию сам; аварийно: bash tests/netns/topology.sh down
```
## 6. Раунд 2 (issue #56): netns в контейнере и покрытие тестов
Дата: 2026-10-28 · Хост: `192.168.1.147` · Ветка: `feat/21-dev-stand` · Ref: #21, #15, #17, #56
Ревью qa-engineer (раунд 1) нашло два блокера: `make test-netns-ci` падал в контейнере
(`mount of /sys failed: Operation not permitted`, exit 255 вместо 0/1/3/4) и гейт покрытия
`pytest tests/deploy` обходился флагом `--no-cov` (0.00 %, `deploy/api_stub.py` 0 %).
### 6.1 Причина падения netns в контейнере
Воспроизведено на образе `awg-dash:dev` (iproute2 6.11.0):
| команда в контейнере | результат |
|---|---|
| `ip netns add <ns>` | rc=0 |
| `ip netns exec <ns> true` | `mount of /sys failed: Operation not permitted`, rc=255 |
| `ip -n <ns> link set lo up` | `mount of /sys failed: Operation not permitted`, rc=255 |
| `nsenter --net=/var/run/netns/<ns> true` | rc=0 |
`ip netns exec` и `ip -n` перемонтируют `/sys` внутри namespace; в контейнере это падает.
Поэтому харнесс больше не использует эти формы: вход в namespace идёт через `ns_exec()` /
`ns_prefix()` (`tests/netns/lib.sh`) и `netns_common.ns_sh()`, которые один раз пробуют
`ip netns exec`, а при отказе переключаются на `nsenter --net=...` (`AWG_NS_EXEC_MODE=auto|ip|nsenter`).
Все `ip -n "$NS"` заменены на `ns_exec "$NS" ip ...`.
Коды возврата приведены к контракту `0/1/3/4` (`run.sh`, `topology.sh`): неожиданный код
утилиты (255) нормализуется в `4` («ошибка стенда»), нет прав — `3`, упавшие тесты — `1`.
### 6.2 Реальные прогоны
| прогон | команда | результат |
|---|---|---|
| стенд на хосте | `make test-netns` | exit 0, 8 ok / 0 fail, 5.46 s, `ip netns list` пуст, мок и sink не остались в процессах |
| стенд в контейнере (путь CI) | `make test-netns-ci` | exit 0, `[netns] вход в netns: nsenter`, backend `iptables`, 8 ok / 0 fail, 5.50 s, `[ ok ] топология снесена`, отчёт `/artifacts/netns-report.json` |
| нет прав | `docker run` без `--privileged` | exit **3**, `[blocked] не удалось создать netns — нет CAP_NET_ADMIN/SYS_ADMIN` |
| форс нерабочего режима | `AWG_NS_EXEC_MODE=ip` в контейнере | exit **4**, `mount of /sys failed` → `[warn] run.sh: непредвиденный код 255 — трактую как ошибку стенда (4)` |
| упавшие тесты | синтетический проваленный тест в контейнере | exit **1**, `[warn] netns-стенд: тесты/очистка упали (код 1)` |
| гейт покрытия | `make test-deploy` | exit 0, 51 passed, coverage **98.86 %** (`deploy/api_stub.py` 98 %, `tests/deploy/validate_compose.py` 99 %) |
| манифесты / shellcheck | `make test-manifests`, `make lint-shell` | exit 0 / exit 0 |
### 6.3 Что изменено для гейта покрытия
* `tests/deploy/pytest.ini` — отдельный профиль: `--cov=deploy --cov=tests/deploy
--cov-config=tests/deploy/.coveragerc --cov-fail-under=70`. Раньше `pytest tests/deploy`
наследовал addopts корневого `pyproject.toml` (`--cov=awg_agent --cov=awg_api`), поэтому
покрытие всегда было 0.00 % и RC=1 — гейт гоняли с `--no-cov` в обход порога.
* `tests/deploy/.coveragerc` — из покрытия исключены сами тестовые модули
(`tests/deploy/test_*.py`), иначе «покрытие» измеряет тестовый код.
* `tests/deploy/test_api_stub.py` — 9 тестов на живой HTTP-сервер заглушки: 200 и
`"stub": true` на `/api/v1/health`, алиас `/healthz`, игнор query-string, 404
`not_implemented` на прочие пути, HEAD, отказ на POST, разбор аргументов `main()`.
* `tests/deploy/test_validate_compose.py` — 39 негативных/граничных тестов валидатора:
каждая инварианта AC #21 ломается по отдельности (порт, тома, healthcheck, NET_ADMIN,
`/dev/net/tun`, `ip_forward`, privileged netns-сервис, DEV-ONLY-метка, `--require-dockerfile`).
* `deploy/api_stub.py` — `make_server()` выделен из `main()` (тесты поднимают сервер на порту 0).
* `make test-deploy` — гейт покрытия; `make test-netns-ci` прокидывает `AWG_NS_EXEC_MODE`.
### 6.4 Что закрыто и что осталось
Закрыты оба блокера раунда 1 (`make test-netns-ci` работает в контейнере, покрытие
измеряется и ≥70 %). `CI green` по-прежнему недостижим: Gitea Actions runner не
зарегистрирован (#17), все прогоны в таблице 6.2 — ручные на `192.168.1.147`; команды
`make test-netns-ci` и `make test-deploy` — ровно те, что должны попасть в workflow #17.
Порт и stub-оверрайд (см. §4) без изменений: `privileged: true` и `cap_add SYS_MODULE`
из `deploy/docker-compose.test.yml` — только DEV/TEST, для staging/prod непригодны.