Exercise packaged runtime topology
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s

This commit is contained in:
2026-08-03 18:26:04 +02:00
parent 282c90c54b
commit 313249b8fc
3 changed files with 89 additions and 1 deletions
@@ -157,6 +157,9 @@ migrations against the pinned PostgreSQL image, reach API and WebUI readiness
as non-root/read-only processes, and complete a task through the pinned Redis as non-root/read-only processes, and complete a task through the pinned Redis
image and packaged worker. Sanitized per-platform smoke receipts are retained image and packaged worker. Sanitized per-platform smoke receipts are retained
as immutable release assets. as immutable release assets.
The smoke also proves a bounded post-migration table contract and aborts as
soon as a required container exits, rather than allowing a dead process to
consume the full readiness timeout.
Ingress acceptance streams generated configuration into Docker-managed Ingress acceptance streams generated configuration into Docker-managed
volumes before starting the read-only containers. It therefore also works when volumes before starting the read-only containers. It therefore also works when
+24
View File
@@ -3,9 +3,11 @@ from __future__ import annotations
import importlib.util import importlib.util
import json import json
from pathlib import Path from pathlib import Path
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
@@ -52,6 +54,28 @@ class RuntimeImageSmokeTests(unittest.TestCase):
with self.assertRaisesRegex(MODULE.SmokeError, "exact sha256"): with self.assertRaisesRegex(MODULE.SmokeError, "exact sha256"):
MODULE.platform_image(path, "linux/arm64", "API") MODULE.platform_image(path, "linux/arm64", "API")
def test_readiness_fails_immediately_when_container_exits(self) -> None:
exited = subprocess.CompletedProcess([], 0, "false\n", "")
logs = subprocess.CompletedProcess([], 0, "fatal startup error\n", "")
with patch.object(MODULE, "_run", side_effect=(exited, logs)):
with self.assertRaisesRegex(
MODULE.SmokeError,
"container exited before readiness: fatal startup error",
):
MODULE._wait_for(
"WebUI",
lambda: self.fail("probe must not run for an exited container"),
timeout=60,
container="web",
)
def test_smoke_supplies_the_packaged_web_upstream_and_schema_contract(self) -> None:
source = SCRIPT.read_text(encoding="utf-8")
self.assertIn('"--network-alias",\n "load-balancer"', source)
self.assertIn("'core_system_settings'", source)
self.assertIn("'core_runtime_nodes'", source)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+62 -1
View File
@@ -104,10 +104,28 @@ def _wait_for(
probe: Callable[[], subprocess.CompletedProcess[str]], probe: Callable[[], subprocess.CompletedProcess[str]],
*, *,
timeout: float, timeout: float,
container: str | None = None,
) -> None: ) -> None:
deadline = time.monotonic() + timeout deadline = time.monotonic() + timeout
last = "" last = ""
while time.monotonic() < deadline: while time.monotonic() < deadline:
if container is not None:
state = _run(
("docker", "inspect", "--format", "{{.State.Running}}", container),
check=False,
timeout=30,
)
if state.returncode != 0 or state.stdout.strip() != "true":
logs = _run(
("docker", "logs", "--tail", "100", container),
check=False,
timeout=30,
)
detail = _tail(logs.stdout + logs.stderr, limit=8000)
raise SmokeError(
f"{label} container exited before readiness: "
f"{detail or 'no diagnostic output'}"
)
result = probe() result = probe()
if result.returncode == 0: if result.returncode == 0:
return return
@@ -212,13 +230,15 @@ def run_smoke(
started = time.monotonic() started = time.monotonic()
def record(check_id: str, began: float) -> None: def record(check_id: str, began: float) -> None:
duration = round(time.monotonic() - began, 3)
checks.append( checks.append(
{ {
"id": check_id, "id": check_id,
"state": "passed", "state": "passed",
"duration_seconds": round(time.monotonic() - began, 3), "duration_seconds": duration,
} }
) )
print(f"PASS {platform} {check_id} ({duration}s)", flush=True)
common_runtime = [ common_runtime = [
"--platform", "--platform",
@@ -309,6 +329,7 @@ def run_smoke(
timeout=30, timeout=30,
), ),
timeout=timeout, timeout=timeout,
container=names["postgres"],
) )
_wait_for( _wait_for(
"Redis", "Redis",
@@ -318,6 +339,7 @@ def run_smoke(
timeout=30, timeout=30,
), ),
timeout=timeout, timeout=timeout,
container=names["redis"],
) )
record("managed_dependencies_ready", began) record("managed_dependencies_ready", began)
@@ -347,6 +369,40 @@ def run_smoke(
) )
record("release_migrations", began) record("release_migrations", began)
began = time.monotonic()
_run(
(
"docker",
"run",
"--rm",
"--name",
f"{prefix}-schema",
*common_runtime,
*_env_arguments(
environment,
role="migration",
node_id=f"runtime-smoke-{slug}-schema",
),
api_image,
"python",
"-c",
(
"import os;"
"from sqlalchemy import create_engine,inspect;"
"engine=create_engine(os.environ['DATABASE_URL']);"
"tables=set(inspect(engine).get_table_names());"
"required={'alembic_version','core_scopes','core_system_settings',"
"'core_runtime_nodes'};"
"missing=required-tables;"
"assert not missing, f'missing release tables: {sorted(missing)}';"
"engine.dispose()"
),
),
timeout=timeout,
redactions=redactions,
)
record("release_schema_contract", began)
began = time.monotonic() began = time.monotonic()
_run( _run(
( (
@@ -357,6 +413,8 @@ def run_smoke(
names["api"], names["api"],
"--network-alias", "--network-alias",
"api", "api",
"--network-alias",
"load-balancer",
*common_runtime, *common_runtime,
*_env_arguments( *_env_arguments(
environment, environment,
@@ -388,6 +446,7 @@ def run_smoke(
timeout=30, timeout=30,
), ),
timeout=timeout, timeout=timeout,
container=names["api"],
) )
_run( _run(
( (
@@ -446,6 +505,7 @@ def run_smoke(
timeout=30, timeout=30,
), ),
timeout=timeout, timeout=timeout,
container=names["web"],
) )
_run( _run(
("docker", "exec", names["web"], "sh", "-c", "test \"$(id -u)\" = 101"), ("docker", "exec", names["web"], "sh", "-c", "test \"$(id -u)\" = 101"),
@@ -507,6 +567,7 @@ def run_smoke(
timeout=30, timeout=30,
), ),
timeout=timeout, timeout=timeout,
container=names["worker"],
) )
_run(("docker", "stop", "--time", "20", names["worker"]), timeout=30) _run(("docker", "stop", "--time", "20", names["worker"]), timeout=30)
record("worker_delivery_and_shutdown", began) record("worker_delivery_and_shutdown", began)