All checks were successful
Deploy / changes (push) Successful in 7s
Deploy Trade-In / changes (push) Successful in 10s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 51s
Deploy / build-backend (push) Successful in 52s
Deploy / build-frontend (push) Successful in 51s
Deploy Trade-In / build-browser (push) Successful in 32s
Deploy / deploy (push) Successful in 1m21s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Successful in 3m30s
Deploy Trade-In / test (push) Successful in 4m15s
Deploy Trade-In / build-backend (push) Successful in 31s
Deploy Trade-In / deploy (push) Successful in 2m6s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
62 lines
3.5 KiB
Python
62 lines
3.5 KiB
Python
"""Гейт: оба прод-деплоя обязаны лежать в ОДНОЙ группе concurrency.
|
||
|
||
Почему это тест, а не комментарий в yml (#2950). Деплой ПТИЦЫ и деплой Trade-In
|
||
ходят по SSH в один и тот же докер-демон — стеки gendesign-* и tradein-* плюс сам
|
||
forgejo-runner живут на одной VM. Каждый в конце делает `docker image prune -af`.
|
||
Пока группы разные, Forgejo запускает их параллельно, и прун одного сносит leases
|
||
ещё не доехавшего `compose pull` другого:
|
||
|
||
unable to lease content: lease does not exist: not found
|
||
|
||
20.08 так и вышло: run 8083 упал за 5 секунд, прод остался на старом коде. Разводка
|
||
групп обратно ничего не сломает в CI и не даст ни одного сигнала — отказ проявится
|
||
только в следующем совпадении окон двух мержей. Поэтому инвариант зафиксирован здесь.
|
||
|
||
`cancel-in-progress: false` частью инварианта тоже является: прод-деплои обязаны
|
||
достаиваться в очереди, а не отменять друг друга — отменённый деплой оставляет прод
|
||
на старом коде ровно так же, как упавший.
|
||
"""
|
||
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
import yaml
|
||
|
||
# backend/tests/ops/<этот файл> → корень репозитория
|
||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||
WORKFLOWS = REPO_ROOT / ".forgejo" / "workflows"
|
||
|
||
PROD_DEPLOYS = ("deploy.yml", "deploy-tradein.yml")
|
||
|
||
|
||
def _concurrency(name: str) -> dict:
|
||
path = WORKFLOWS / name
|
||
assert path.is_file(), f"нет {path} — переехал workflow, гейт ослеп"
|
||
return yaml.safe_load(path.read_text()).get("concurrency") or {}
|
||
|
||
|
||
@pytest.mark.parametrize("name", PROD_DEPLOYS)
|
||
def test_prod_deploy_declares_concurrency(name: str) -> None:
|
||
"""У каждого прод-деплоя concurrency вообще объявлена."""
|
||
assert _concurrency(name).get(
|
||
"group"
|
||
), f"{name}: пропала секция concurrency — деплои снова могут пойти параллельно"
|
||
|
||
|
||
def test_both_prod_deploys_share_one_group() -> None:
|
||
"""Группа у обоих одна и та же — иначе прун одного убьёт pull другого."""
|
||
groups = {name: _concurrency(name).get("group") for name in PROD_DEPLOYS}
|
||
assert len(set(groups.values())) == 1, (
|
||
f"прод-деплои разведены по разным группам concurrency: {groups}. "
|
||
"Они ходят в ОДИН докер-демон; `docker image prune -af` одного обрывает "
|
||
"`compose pull` другого (#2950)."
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize("name", PROD_DEPLOYS)
|
||
def test_prod_deploy_queues_instead_of_cancelling(name: str) -> None:
|
||
"""Раны достаиваются в очереди: отменённый деплой = прод на старом коде."""
|
||
assert _concurrency(name).get("cancel-in-progress") is False, (
|
||
f"{name}: cancel-in-progress должен быть false — иначе более новый мерж "
|
||
"отменит выкатку предыдущего и та молча не доедет"
|
||
)
|