Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe0b3bf12 | ||
|
|
ccc29d5acc | ||
|
|
b944eca41d | ||
|
|
5c23fb7d78 |
@@ -93,9 +93,15 @@ def assert_serving(domain: str, meta) -> None:
|
|||||||
Steps 1–2 are BOUNDED POLLS (no bare sleep), so a state-mutating op (upgrade/restore) that leaves
|
Steps 1–2 are BOUNDED POLLS (no bare sleep), so a state-mutating op (upgrade/restore) that leaves
|
||||||
the app briefly reconverging settles, while a persistent failure still fails within the timeout."""
|
the app briefly reconverging settles, while a persistent failure still fails within the timeout."""
|
||||||
deadline = time.time() + meta.DEPLOY_TIMEOUT
|
deadline = time.time() + meta.DEPLOY_TIMEOUT
|
||||||
while time.time() < deadline and not lifecycle.services_converged(domain):
|
converged = False
|
||||||
|
while time.time() < deadline:
|
||||||
|
# Keep both observations inside the retry budget: a service may restart
|
||||||
|
# between the initial convergence sample and its confirmation.
|
||||||
|
if lifecycle.services_converged(domain) and lifecycle.services_converged(domain):
|
||||||
|
converged = True
|
||||||
|
break
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
assert lifecycle.services_converged(domain), f"{domain}: services did not converge"
|
assert converged, f"{domain}: services did not converge"
|
||||||
|
|
||||||
path = meta.HEALTH_PATH
|
path = meta.HEALTH_PATH
|
||||||
ok = tuple(meta.HEALTH_OK)
|
ok = tuple(meta.HEALTH_OK)
|
||||||
|
|||||||
@@ -487,6 +487,10 @@ def deploy_app(
|
|||||||
# may already include the environmental compose.ccci.yml). Stripped before the head redeploy
|
# may already include the environmental compose.ccci.yml). Stripped before the head redeploy
|
||||||
# (generic.perform_upgrade) so the PR head runs unmodified.
|
# (generic.perform_upgrade) so the PR head runs unmodified.
|
||||||
if apply_previous:
|
if apply_previous:
|
||||||
|
# This untracked overlay also trips abra's pinned-deploy clean-tree gate.
|
||||||
|
# The exact version was checked out above; chaos deploys that base plus
|
||||||
|
# its repair, just as for compose.ccci.yml, without fetching latest.
|
||||||
|
chaos = True
|
||||||
cf = compose_file_add(extra.get("COMPOSE_FILE", "compose.yml"), PREVIOUS_COMPOSE)
|
cf = compose_file_add(extra.get("COMPOSE_FILE", "compose.yml"), PREVIOUS_COMPOSE)
|
||||||
abra.env_set(domain, "COMPOSE_FILE", cf)
|
abra.env_set(domain, "COMPOSE_FILE", cf)
|
||||||
provide_previous_overlay(recipe)
|
provide_previous_overlay(recipe)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
0.4.0+v5.4.1
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# The published base points at the removed docker.io/minio/minio image.
|
||||||
|
# Keep its /data layout, credentials and bucket initialization; run the same
|
||||||
|
# available MinIO build used by current recipes without Bitnami's wrapper.
|
||||||
|
services:
|
||||||
|
minio:
|
||||||
|
image: docker.io/bitnamilegacy/minio:2025.7.23-debian-12-r5@sha256:6dabb4a2088c9a79908de3bc05f4586c23ad2182c8908e7e3acbf61c1467fb20
|
||||||
|
user: "0:0"
|
||||||
|
environment:
|
||||||
|
MC_CONFIG_DIR: /tmp/mc-base-init
|
||||||
|
entrypoint:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- /minio-initialize.sh & exec "$$@"
|
||||||
|
- --
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0.12.0+v0.21.0
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# The published base points at the removed docker.io/minio/minio image.
|
||||||
|
# Keep its /data layout, credentials and bucket initialization; run the same
|
||||||
|
# available MinIO build used by current recipes without Bitnami's wrapper.
|
||||||
|
services:
|
||||||
|
minio:
|
||||||
|
image: docker.io/bitnamilegacy/minio:2025.7.23-debian-12-r5@sha256:6dabb4a2088c9a79908de3bc05f4586c23ad2182c8908e7e3acbf61c1467fb20
|
||||||
|
user: "0:0"
|
||||||
|
environment:
|
||||||
|
MC_CONFIG_DIR: /tmp/mc-base-init
|
||||||
|
entrypoint:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- /minio-initialize.sh & exec "$$@"
|
||||||
|
- --
|
||||||
@@ -59,13 +59,6 @@ def EXTRA_ENV(ctx):
|
|||||||
# postgres, redis, collabora ~1GB, onlyoffice ~2GB). Bump so abra waits long enough for
|
# postgres, redis, collabora ~1GB, onlyoffice ~2GB). Bump so abra waits long enough for
|
||||||
# convergence; kept under DEPLOY_TIMEOUT (1800) so Python never kills abra mid-wait.
|
# convergence; kept under DEPLOY_TIMEOUT (1800) so Python never kills abra mid-wait.
|
||||||
"TIMEOUT": "1500",
|
"TIMEOUT": "1500",
|
||||||
# abra's pinned-version `app new` regenerates the .env from generation hints and DROPS
|
|
||||||
# SECRET_COLLABORA_P_VERSION (not a generatable secret — the .env.sample ships its
|
|
||||||
# version line manually). Without it every secret-touching abra command for the app
|
|
||||||
# FATAs "missing version for secret? (collabora_p)" → the base deploy goes RED at
|
|
||||||
# install. env_set runs after app_new, so re-asserting the shipped version pin here
|
|
||||||
# restores the sample's contract.
|
|
||||||
"SECRET_COLLABORA_P_VERSION": "v1",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -171,22 +171,6 @@ def test_extra_env_dict_and_callable_forms(tmp_path):
|
|||||||
assert meta_mod.extra_env(meta2, ctx2) == {} # unset EXTRA_ENV resolves to {}
|
assert meta_mod.extra_env(meta2, ctx2) == {} # unset EXTRA_ENV resolves to {}
|
||||||
|
|
||||||
|
|
||||||
TESTS_DIR = os.path.join(os.path.dirname(__file__), "..")
|
|
||||||
|
|
||||||
|
|
||||||
def test_lasuite_drive_extra_env_carries_collabora_p_version():
|
|
||||||
"""(REGRESSION, 2026-10-25) abra's pinned-version `app new` regenerates the .env from
|
|
||||||
generation hints and drops SECRET_COLLABORA_P_VERSION (the .env.sample ships it manually),
|
|
||||||
so every secret-touching abra command for the app FATAs "missing version for secret?
|
|
||||||
(collabora_p)" and the cold base deploy goes RED at install. The per-recipe EXTRA_ENV must
|
|
||||||
re-assert the shipped version pin (env_set runs after app_new). Pin it here so abra/upstream
|
|
||||||
drift that loses it again surfaces as a red unit test, not a weekly red build."""
|
|
||||||
meta = meta_mod.load("lasuite-drive", tests_dir=TESTS_DIR)
|
|
||||||
ctx = meta_mod.hook_ctx("lasuite-drive-pr0-abc123.ci.commoninternet.net", meta)
|
|
||||||
extra = meta_mod.extra_env(meta, ctx)
|
|
||||||
assert extra.get("SECRET_COLLABORA_P_VERSION") == "v1"
|
|
||||||
|
|
||||||
|
|
||||||
# ---- P3: uniform ctx hook convention -------------------------------------------------------------
|
# ---- P3: uniform ctx hook convention -------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -116,3 +116,37 @@ def test_previous_status_stale_without_marker(monkeypatch):
|
|||||||
monkeypatch.setattr(lifecycle, "previous_target_version", lambda r: None)
|
monkeypatch.setattr(lifecycle, "previous_target_version", lambda r: None)
|
||||||
st = lifecycle.previous_status("rx", "version", "0.7.0+3.3.1")
|
st = lifecycle.previous_status("rx", "version", "0.7.0+3.3.1")
|
||||||
assert st["apply"] is False and st["stale"] is True and "marker" in st["reason"]
|
assert st["apply"] is False and st["stale"] is True and "marker" in st["reason"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_overlay_deploys_checked_out_base_without_clean_tree_gate(monkeypatch):
|
||||||
|
events = []
|
||||||
|
for name in (
|
||||||
|
"acquire_app_lock",
|
||||||
|
"provide_ccci_overlay",
|
||||||
|
"provide_previous_overlay",
|
||||||
|
"prepull_images",
|
||||||
|
):
|
||||||
|
monkeypatch.setattr(lifecycle, name, lambda *a, **kw: None)
|
||||||
|
for name in ("app_config_remove", "app_new", "env_set", "secret_generate"):
|
||||||
|
monkeypatch.setattr(lifecycle.abra, name, lambda *a, **kw: None)
|
||||||
|
monkeypatch.setattr(lifecycle.meta_mod, "extra_env", lambda *a: {})
|
||||||
|
monkeypatch.setattr(lifecycle.meta_mod, "hook_ctx", lambda *a: None)
|
||||||
|
monkeypatch.setattr(lifecycle.abra, "has_lightweight_version_tags", lambda r: False)
|
||||||
|
monkeypatch.setattr(lifecycle, "has_ccci_overlay", lambda r: False)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
lifecycle.abra, "recipe_checkout", lambda r, v: events.append(("checkout", v))
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
lifecycle.abra, "deploy", lambda d, **kw: events.append(("deploy", kw["chaos"]))
|
||||||
|
)
|
||||||
|
for apply_previous in (False, True):
|
||||||
|
events.clear()
|
||||||
|
lifecycle.deploy_app(
|
||||||
|
"example",
|
||||||
|
"example.invalid",
|
||||||
|
version="1.0.0",
|
||||||
|
apply_previous=apply_previous,
|
||||||
|
meta=object(),
|
||||||
|
_count_deploy=False,
|
||||||
|
)
|
||||||
|
assert events == [("checkout", "1.0.0"), ("deploy", apply_previous)]
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Convergence confirmation must retry a transient second-sample failure."""
|
||||||
|
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from harness import generic
|
||||||
|
|
||||||
|
|
||||||
|
def _setup(monkeypatch, samples):
|
||||||
|
clock = [0]
|
||||||
|
monkeypatch.setattr(generic.time, "time", lambda: clock[0])
|
||||||
|
monkeypatch.setattr(generic.time, "sleep", lambda n: clock.__setitem__(0, clock[0] + n))
|
||||||
|
monkeypatch.setattr(generic.lifecycle, "services_converged", lambda d: next(samples))
|
||||||
|
monkeypatch.setattr(generic.lifecycle, "http_fetch", lambda *a: (200, "application"))
|
||||||
|
monkeypatch.setattr(generic, "served_cert", lambda d: (True, "*.ci.autonomic.zone"))
|
||||||
|
return SimpleNamespace(DEPLOY_TIMEOUT=20, HTTP_TIMEOUT=20, HEALTH_PATH="/", HEALTH_OK=(200,))
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirmation_failure_retries_until_services_settle(monkeypatch):
|
||||||
|
meta = _setup(monkeypatch, iter([True, False, True, True]))
|
||||||
|
generic.assert_serving("example.ci.autonomic.zone", meta)
|
||||||
|
|
||||||
|
|
||||||
|
def test_persistent_confirmation_failure_still_fails(monkeypatch):
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
meta = _setup(monkeypatch, itertools.cycle([True, False]))
|
||||||
|
with pytest.raises(AssertionError, match="services did not converge"):
|
||||||
|
generic.assert_serving("example.ci.autonomic.zone", meta)
|
||||||
Reference in New Issue
Block a user