From 59c3ad4918765be1e37faf2c573160a6caea7145 Mon Sep 17 00:00:00 2001 From: embeddevio Date: Wed, 21 Oct 2026 23:50:01 +0300 Subject: [PATCH] =?UTF-8?q?test(#21):=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B8=D0=BD=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20compose,=20Make-=D1=86=D0=B5=D0=BB=D0=B8=20=D1=81?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D0=B4=D0=B0=20=D0=B8=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D1=91=D0=BD=D0=BD=D1=8B=D0=B9=20deploy/READM?= =?UTF-8?q?E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tests/deploy/test_compose_manifests.py — 7 тестов на compose-инварианты (без Docker, только PyYAML) — готовы к включению в CI (#17); - Makefile: dev-up/dev-up-stub/dev-down/dev-logs/dev-verify/dev-verify-stub/ dev-reset/test-netns/test-netns-keep/test-netns-ci/test-manifests/lint-shell, переменные HOST_DIR, DASH_PORT, NETNS_ARGS; dev-up без docker падает с внятным сообщением, а не трейсбеком; - deploy/README.md — таблица артефактов и быстрые команды стенда. Refs #21 Refs #15 --- Makefile | 59 ++++++++++++++++++++-- deploy/README.md | 29 +++++++++++ tests/deploy/test_compose_manifests.py | 68 ++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 tests/deploy/test_compose_manifests.py diff --git a/Makefile b/Makefile index ae4f41a..47a94bf 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,21 @@ VENV ?= .venv BIN := $(VENV)/bin IMAGE ?= awg-dash:dev COMPOSE_DEV := deploy/docker-compose.dev.yml +COMPOSE_TEST := deploy/docker-compose.test.yml +COMPOSE_STUB := deploy/docker-compose.stub.yml +VERIFY := deploy/verify-dev-stand.sh +COMPOSE ?= docker compose +# Состояние dev-стенда: bind mount для /config и /data (AC-2 задачи #21). +HOST_DIR ?= /opt/awg-dash-dev +# Хост-порт стенда. На dev-хосте 8080 занят другим сервисом — по умолчанию 8180; +# внутри контейнера приложение всегда на 8080. +DASH_PORT ?= 8180 +NETNS_ARGS ?= +VERIFY_ARGS ?= +MANIFEST_ARGS ?= -.PHONY: help venv lint fmt test build dev-up dev-down dev-logs test-netns +.PHONY: help venv lint fmt test build dev-up dev-up-stub dev-down dev-logs dev-verify \ + dev-verify-stub dev-reset test-netns test-netns-keep test-netns-ci test-manifests lint-shell help: @grep -E '^[a-zA-Z_-]+:' $(MAKEFILE_LIST) | sed 's/:.*//' | sort -u @@ -29,16 +42,52 @@ build: @test -f deploy/Dockerfile || { echo 'deploy/Dockerfile ещё нет — задача #18'; exit 1; } docker build -f deploy/Dockerfile -t $(IMAGE) . +# --- dev/staging стенд (задача #21) ------------------------------------------ dev-up: @test -f $(COMPOSE_DEV) || { echo '$(COMPOSE_DEV) ещё нет — задача #21'; exit 1; } - docker compose -f $(COMPOSE_DEV) up -d --build + @command -v docker >/dev/null 2>&1 || { echo 'нет docker: dev-стенд поднимается на Docker-хосте (см. deploy/dev-stand.md)'; exit 1; } + AWG_DASH_PORT=$(DASH_PORT) AWG_DASH_HOST_DIR=$(HOST_DIR) $(COMPOSE) -f $(COMPOSE_DEV) up -d --build + +# Health-заглушка вместо API до мержа #31 — только для проверки плумбинга стенда. +dev-up-stub: + @test -f $(COMPOSE_STUB) || { echo '$(COMPOSE_STUB) ещё нет — задача #21'; exit 1; } + AWG_DASH_PORT=$(DASH_PORT) AWG_DASH_HOST_DIR=$(HOST_DIR) $(COMPOSE) -f $(COMPOSE_DEV) -f $(COMPOSE_STUB) up -d --build dev-down: - docker compose -f $(COMPOSE_DEV) down + AWG_DASH_HOST_DIR=$(HOST_DIR) $(COMPOSE) -f $(COMPOSE_DEV) down dev-logs: - docker compose -f $(COMPOSE_DEV) logs -f --tail=100 + AWG_DASH_HOST_DIR=$(HOST_DIR) $(COMPOSE) -f $(COMPOSE_DEV) logs -f --tail=100 +# Проверка AC #21 на Docker-хосте: health 200 + состояние переживает restart. +dev-verify: + AWG_DASH_PORT=$(DASH_PORT) AWG_DASH_HOST_DIR=$(HOST_DIR) bash $(VERIFY) $(VERIFY_ARGS) + +dev-verify-stub: + AWG_DASH_PORT=$(DASH_PORT) AWG_DASH_HOST_DIR=$(HOST_DIR) bash $(VERIFY) --stub $(VERIFY_ARGS) + +# Останавливает стенд; каталоги состояния сохраняются (удалять вручную). +dev-reset: + AWG_DASH_HOST_DIR=$(HOST_DIR) $(COMPOSE) -f $(COMPOSE_DEV) down --remove-orphans + @echo "каталоги состояния сохранены: $(HOST_DIR)/config, $(HOST_DIR)/data (удалять вручную)" + +# --- netns-стенд (задача #21) ------------------------------------------------ test-netns: @test -d tests/netns || { echo 'tests/netns ещё нет — задача #21'; exit 1; } - bash tests/netns/run.sh + bash tests/netns/run.sh $(NETNS_ARGS) + +# То же, но топология и логи остаются для отладки (снести: tests/netns/topology.sh down). +test-netns-keep: + bash tests/netns/run.sh --keep-topology --keep-logs $(NETNS_ARGS) + +# Тот же стенд в привилегированном контейнере — путь для CI (#17). +test-netns-ci: + AWG_DASH_IMAGE=$(IMAGE) $(COMPOSE) -f $(COMPOSE_TEST) run --rm netns + +# --- статические проверки манифестов и скриптов ------------------------------ +test-manifests: + $(PY) tests/deploy/validate_compose.py $(MANIFEST_ARGS) + +lint-shell: + @command -v shellcheck >/dev/null 2>&1 || { echo 'shellcheck не установлен (apt-get install shellcheck)'; exit 1; } + shellcheck -x -S style tests/netns/*.sh deploy/*.sh diff --git a/deploy/README.md b/deploy/README.md index 77c784a..c4254f3 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -6,7 +6,36 @@ | `entrypoint.sh` | супервизор: `awg-agent` (root) + `awg-api` | #18 | | `docker-compose.dev.yml` | dev-стенд: порт 8080, тома `/config` `/data`, healthcheck | #21 | | `docker-compose.test.yml` | стенд для тестов | #21 | +| `docker-compose.stub.yml` | DEV-ONLY оверрайд: health-заглушка до мержа #31 | #21 | +| `api_stub.py` | минимальный `/api/v1/health` для проверки плумбинга стенда | #21 | +| `verify-dev-stand.sh` | проверка AC #21 на Docker-хосте (`make dev-verify`) | #21 | +| `dev-stand.md` | runbook dev-стенда: запуск, AC, откат, pitfalls | #21 | | `grafana/` | дашборд JSON (опциональный профиль monitoring) | #46 | | `awg0.conf.example` | пример клиентского конфига | #22 | Запуск по ТЗ (после релиза): `--network=host`, `--device=/dev/net/tun`, `--cap-add=NET_ADMIN --cap-add=SYS_MODULE`, `--restart always`. + +## Dev-стенд (задача #21) + +```bash +make dev-up # поднять стенд (compose project awg-dash-dev) +make dev-verify # AC: health 200 + /config /data переживают restart +make dev-up-stub # + health-заглушка, пока нет API (#31) +make dev-logs +make dev-down +``` + +* Состояние: `/opt/awg-dash-dev/{config,data}` на Docker-хосте (`AWG_DASH_HOST_DIR`). +* Хост-порт: `AWG_DASH_PORT` (по умолчанию 8180 — 8080 занят на dev-хосте; в контейнере всегда 8080). +* Подробности, таблица AC, откат и pitfalls — [dev-stand.md](dev-stand.md). + +### Тестовый стенд без реального провайдера + +```bash +make test-netns # netns-топология client/gw/uplink + мок WireGuard-endpoint +make test-netns-ci # то же в привилегированном контейнере (путь для CI, #17) +make test-manifests # статическая проверка compose-файлов (без Docker) +make lint-shell # shellcheck для скриптов стенда +``` + +Описание топологии, тестов и pitfalls — [../tests/netns/README.md](../tests/netns/README.md). diff --git a/tests/deploy/test_compose_manifests.py b/tests/deploy/test_compose_manifests.py new file mode 100644 index 0000000..1ec81c8 --- /dev/null +++ b/tests/deploy/test_compose_manifests.py @@ -0,0 +1,68 @@ +"""Тесты статических инвариантов compose-файлов стенда (задача #21). + +Проверяют то, что иначе выяснилось бы только на Docker-хосте: порт 8080, +монтирования /config и /data, healthcheck, capabilities, privileged netns-раннер. +Работают без Docker — только PyYAML (см. requirements-dev.txt). +""" + +from __future__ import annotations + +import pathlib +import unittest + +import validate_compose + +HERE = pathlib.Path(__file__).resolve().parent +ROOT = HERE.parents[1] + + +@unittest.skipUnless(validate_compose.yaml is not None, "нужен PyYAML (make venv)") +class ComposeManifestsTest(unittest.TestCase): + def test_dev_compose_matches_acceptance_criteria(self) -> None: + errors, _ = validate_compose.validate(require_dockerfile=False) + self.assertEqual([], errors, "compose-файлы стенда нарушают инварианты AC #21") + + def test_dev_compose_publishes_app_port_to_host(self) -> None: + doc = validate_compose.load(validate_compose.DEV) + ports = " ".join(str(p) for p in doc["services"]["awg-dash"]["ports"]) + self.assertIn(":8080", ports, "наружу должен публиковаться контейнерный порт приложения 8080") + + def test_dev_compose_mounts_state_directories(self) -> None: + doc = validate_compose.load(validate_compose.DEV) + targets = validate_compose._mount_targets(doc["services"]["awg-dash"]) + self.assertIn("/config", targets) + self.assertIn("/data", targets) + + def test_healthcheck_hits_api_v1_health(self) -> None: + doc = validate_compose.load(validate_compose.DEV) + test_cmd = validate_compose.jsonish(doc["services"]["awg-dash"]["healthcheck"]["test"]) + self.assertIn("/api/v1/health", test_cmd) + + def test_test_compose_runs_netns_harness_privileged(self) -> None: + doc = validate_compose.load(validate_compose.TEST) + svc = doc["services"]["netns"] + self.assertTrue(svc["privileged"]) + self.assertIn("tests/netns/run.sh", " ".join(svc["entrypoint"])) + + def test_stub_override_is_labelled_dev_only(self) -> None: + text = validate_compose.STUB.read_text(encoding="utf-8") + self.assertIn("api_stub.py", text) + self.assertIn("DEV", text.upper()) + + def test_netns_harness_entrypoints_exist(self) -> None: + for rel in ( + "tests/netns/run.sh", + "tests/netns/topology.sh", + "tests/netns/lib.sh", + "tests/netns/probe.py", + "tests/netns/mock_wg_endpoint.py", + "tests/netns/leak_sink.py", + "deploy/api_stub.py", + "deploy/verify-dev-stand.sh", + ): + with self.subTest(rel=rel): + self.assertTrue((ROOT / rel).exists(), f"нет файла {rel}") + + +if __name__ == "__main__": + unittest.main(verbosity=2)