Files
planet/scripts/harness/test_docker_mirror.py
linkong 60eefb19c9
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
release / images (push) Has been cancelled
ci / delivery (push) Has been cancelled
release: bump version to 0.74.7
2026-09-17 17:30:54 +08:00

135 lines
7.0 KiB
Python

"""Mirror routing regressions without pulling images or editing daemon configuration."""
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
from test_database_startup import run_shell
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "scripts"))
import docker_mirror as mirror
import docker_proxy as proxy
DIGEST = "sha256:" + "a" * 64
class MirrorTests(unittest.TestCase):
def test_recoverable_uv_failure_does_not_freeze_status_before_tuna_fallback(self) -> None:
result = run_shell(["run_uv_sync_with_mirror_fallback", "run_with_retry"], """
DEPENDENCY_INSTALL_MAX_RETRIES=1
DEPENDENCY_INSTALL_RETRY_INTERVAL=0
WAIT_FAILURE_REPORTED=0
uv_lock_digest() { echo stable; }
assert_uv_lock_unchanged() { :; }
run_command_with_spinner() { shift; "$@"; }
run_command_quiet_unless_verbose() { return 1; }
configure_uv_tuna_index() { echo MIRROR_READY; }
run_uv_sync_with_tuna_config() { echo RECOVERED; }
set_wait_detail() { :; }
log_error() { echo FATAL; WAIT_FAILURE_REPORTED=1; }
""", "run_uv_sync_with_mirror_fallback /dev/null\n[ $WAIT_FAILURE_REPORTED -eq 0 ]")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.splitlines(), ["MIRROR_READY", "RECOVERED"])
def test_build_network_failure_retries_once_with_temporary_args(self) -> None:
with tempfile.TemporaryDirectory() as folder:
result = run_shell(["run_ai_provider_build_command"], f"""
AI_PROVIDER_BUILD_LOG_FILE={folder}/build.log
attempts=0
DOCKER_BUILD_USE_MIRROR=0
PYTHON_IMAGE=python:3.14-slim
run_docker_observed() {{
attempts=$((attempts + 1))
echo "$*" >&2
echo failure > "$3"
[ $attempts -eq 2 ]
}}
docker_failure_allows_mirror() {{ return 0; }}
prepare_docker_build_proxy() {{ DOCKER_BUILD_USE_MIRROR=1; }}
resolve_build_mirror_args() {{ DOCKER_MIRROR_BUILD_ARGS=(--build-arg PYTHON_IMAGE=mirror/python@{DIGEST}); }}
""", 'run_ai_provider_build_command "docker compose"\n'
'[ $attempts -eq 2 ] && [ "$PYTHON_IMAGE" = python:3.14-slim ]')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("--build-arg PYTHON_IMAGE=mirror/python@", result.stderr)
self.assertTrue((Path(folder) / "build.primary.log").exists())
def test_mapping_retains_original_repository_and_digest(self) -> None:
for image, expected in (
("python:3.14-slim", "docker.io/library/python:3.14-slim"),
("docker.io/postgres:15", "docker.io/library/postgres:15"),
("ghcr.io/astral-sh/uv:latest", "ghcr.io/astral-sh/uv:latest"),
("redis@" + DIGEST, "docker.io/library/redis@" + DIGEST),
):
self.assertEqual(mirror.mirror_image(image, {}), "m.daocloud.io/" + expected)
def test_private_repository_and_credentials_are_not_sent_to_public_mirror(self) -> None:
for image in ("private.example/team/python:3", "docker.io/private/app:latest"):
with self.assertRaises(mirror.MirrorError):
mirror.mirror_image(image, {})
with self.assertRaises(mirror.MirrorError):
mirror.mirror_prefix({"PLANET_DOCKER_MIRROR_PREFIX": "user:secret@mirror.test"})
def test_manifest_is_required_and_pinned_even_for_mutable_tags(self) -> None:
with patch.object(mirror, "run", return_value=json.dumps({"digest": DIGEST})):
self.assertEqual(mirror.resolve_image("python:3.14-slim", {}),
"m.daocloud.io/docker.io/library/python@" + DIGEST)
self.assertEqual(mirror.resolve_image("redis@" + DIGEST, {}),
"m.daocloud.io/docker.io/library/redis@" + DIGEST)
for manifest in ([], {}, {"digest": None}, {"digest": "invalid"}, {"digest": "sha256:" + "b" * 64}):
with patch.object(mirror, "run", return_value=json.dumps(manifest)):
with self.assertRaises(mirror.MirrorError):
mirror.resolve_image("redis@" + DIGEST, {})
def test_primary_route_is_preferred_and_fallback_can_be_disabled(self) -> None:
for reachable, disabled, calls in ((True, False, 1), (False, True, 1), (False, False, 2)):
with patch.object(proxy, "select_route", side_effect=[
{"reachable": reachable}, {"reachable": True}
]) as select:
plan = proxy.plan_routes({"PLANET_DOCKER_MIRROR_FALLBACK": "0"} if disabled else {})
self.assertEqual(select.call_count, calls)
self.assertEqual(plan["mirror"], calls == 2)
def test_database_pull_keeps_canonical_tag_and_does_not_replace_cached_images(self) -> None:
for cached in (True, False):
calls = []
def run(command, timeout=30):
calls.append(command)
return json.dumps({"services": {"postgres": {"image": "postgres:15"}}})
with patch.object(mirror, "run", side_effect=run), \
patch.object(mirror, "resolve_image", return_value="mirror/pg@" + DIGEST), \
patch.object(mirror.subprocess, "run", return_value=subprocess.CompletedProcess([], 0 if cached else 1)):
mirror.pull_services(["postgres"], {})
self.assertEqual(len(calls), 1 if cached else 3)
if not cached:
self.assertEqual(calls[-1], ["docker", "tag", "mirror/pg@" + DIGEST, "postgres:15"])
def test_only_registry_network_errors_allow_fallback(self) -> None:
cases = (
('Head "https://registry-1.docker.io/v2/python": i/o timeout', True),
('#3 [internal] load metadata for python\nPLANET_DOCKER_COMMAND_TIMEOUT', True),
('Head "https://registry-1.docker.io/v2/python": 429 Too Many Requests', False),
('Head "https://registry-1.docker.io/v2/python": unauthorized:', False),
('Head "https://registry-1.docker.io/v2/python": x509: unknown authority', False),
('#3 load metadata for python\nno such host\nprocess "/bin/sh -c uv sync" failed', False),
('Bind for 127.0.0.1:5432 failed: port is already allocated', False),
)
with tempfile.TemporaryDirectory() as folder:
log = Path(folder) / "failure.log"
for evidence, expected in cases:
log.write_text(evidence)
result = run_shell([], f"""
SCRIPT_DIR={ROOT}
source {ROOT}/scripts/lib/error-diagnostics.zsh
source {ROOT}/scripts/lib/docker-mirror.zsh
""", f"docker_failure_allows_mirror {log}")
self.assertEqual(result.returncode == 0, expected, evidence + result.stderr)
if __name__ == "__main__":
unittest.main()