Files
awoooi/scripts/backup/tests/test_clickhouse_native_restore_drill.py

849 lines
29 KiB
Python

from __future__ import annotations
import hashlib
import json
import os
import shutil
import subprocess
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "clickhouse-native-restore-drill.sh"
INVENTORY_HELPER = ROOT / "clickhouse-restore-inventory.py"
REPO_ROOT = ROOT.parents[1]
CLICKHOUSE_IMAGE_ID = (
"sha256:8248e5926d7304400e44ecdf0c7181fbde28e6a9b1d647780eca839f34c00cc6"
)
ZOOKEEPER_IMAGE_ID = (
"sha256:3ab0e8f032ab58f14ac1e929ac40305a4a464cf320bdfe4151d62d6922729ba0"
)
EMPTY_SHA256 = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
EXPECTED_DATABASES = [
"signoz_analytics",
"signoz_logs",
"signoz_metadata",
"signoz_meter",
"signoz_metrics",
"signoz_traces",
]
def _inventory(
*,
restored_delta: int = 0,
engine_mismatch: bool = False,
schema_mismatch: bool = False,
) -> str:
rows = [
("signoz_analytics", "events", "ReplicatedMergeTree", 10, 100),
("signoz_analytics", "events_all", "Distributed", 10, 100),
("signoz_logs", "logs_v2", "ReplicatedMergeTree", 20, 200),
("signoz_metadata", "resource_attrs", "ReplicatedMergeTree", 2, 20),
("signoz_meter", "meter", "ReplicatedMergeTree", 3, 30),
("signoz_metrics", "samples_v4", "ReplicatedMergeTree", 30, 300),
("signoz_metrics", "time_series_v4", "ReplicatedMergeTree", 12, 120),
("signoz_traces", "signoz_index_v3", "ReplicatedMergeTree", 40, 400),
]
rendered: list[str] = []
for index, (database, table, engine, row_count, byte_count) in enumerate(rows):
if engine_mismatch and table == "meter":
engine = "MergeTree"
schema_hash = (
hashlib.sha256(f"{database}.{table}:{engine}".encode("utf-8"))
.hexdigest()
.upper()
)
if schema_mismatch and table == "meter":
schema_hash = "F" * 64
rendered.append(
"\t".join(
(
database,
table,
engine,
str(row_count + (restored_delta if index == 0 else 0)),
str(byte_count + (restored_delta if index == 0 else 0)),
schema_hash,
)
)
)
return "\n".join(rendered) + "\n"
def _write_backup_fixture(tmp_path: Path) -> tuple[Path, Path, Path]:
artifact = tmp_path / "clickhouse-native.zip.partial"
with zipfile.ZipFile(artifact, "w") as archive:
archive.writestr(".backup", "bounded native backup fixture\n")
inventory = tmp_path / "source-inventory.tsv.partial"
inventory.write_text(_inventory(), encoding="utf-8")
artifact_sha = hashlib.sha256(artifact.read_bytes()).hexdigest()
inventory_sha = hashlib.sha256(inventory.read_bytes()).hexdigest()
manifest = tmp_path / "manifest.json.partial"
manifest.write_text(
json.dumps(
{
"trace_id": "backup-trace",
"run_id": "backup-run",
"work_item_id": "P0-OBS-002",
"operation_id": "awoooi-backup-operation",
"databases": EXPECTED_DATABASES,
"source_inventory_sha256": inventory_sha,
"sha256": artifact_sha,
"size_bytes": artifact.stat().st_size,
"status": "BACKUP_CREATED",
},
separators=(",", ":"),
)
+ "\n",
encoding="utf-8",
)
return artifact, manifest, inventory
def _write_fake_docker(tmp_path: Path) -> tuple[Path, Path, Path]:
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
state_dir = tmp_path / "docker-state"
state_dir.mkdir()
log_path = tmp_path / "docker.log"
docker = bin_dir / "docker"
docker.write_text(
r"""#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >> "${FAKE_DOCKER_LOG:?}"
state="${FAKE_DOCKER_STATE:?}"
cmd="${1:?}"
shift
last_arg() { eval "printf '%s' \"\${$#}\""; }
case "$cmd" in
info)
[ "${FAKE_DOCKER_INFO_FAIL:-0}" = 0 ] || exit 78
printf '25.0.0\\n'
;;
image)
sub="${1:?}"
shift
[ "$sub" = inspect ] || exit 90
image="$(last_arg "$@")"
case "$image" in
clickhouse/clickhouse-server:25.5.6) printf '%s\n' "${FAKE_CLICKHOUSE_IMAGE_ID:?}" ;;
signoz/zookeeper:3.7.1) printf '%s\n' "${FAKE_ZOOKEEPER_IMAGE_ID:?}" ;;
*) exit 1 ;;
esac
;;
container)
sub="${1:?}"
shift
case "$sub" in
inspect)
format=""
if [ "${1:-}" = --format ]; then
format="$2"
shift 2
fi
name="${1:?}"
[ -f "$state/container-$name" ] || exit 1
if [[ "$format" == *Labels* ]]; then
if [ "${FAKE_LABEL_INSPECT_HANG:-0}" = 1 ] && [[ "$name" == *ch-restore* ]]; then
sleep 10
fi
cat "$state/container-label-$name"
fi
;;
ls)
for item in "$state"/container-*; do
[ -e "$item" ] || continue
case "$item" in
*-label-*|*-network-*|*-image-*) continue ;;
esac
basename "$item" | sed 's/^container-//'
done
;;
*) exit 90 ;;
esac
;;
inspect)
format=""
if [ "${1:-}" = --format ]; then
format="$2"
shift 2
fi
name="${1:?}"
[ -f "$state/container-$name" ] || exit 1
case "$format" in
*State.Running*) printf 'true\n' ;;
*HostConfig.NetworkMode*) cat "$state/container-network-$name" ;;
*.Image*) cat "$state/container-image-$name" ;;
*) printf '%s\n' "fake-$name" ;;
esac
;;
network)
sub="${1:?}"
shift
case "$sub" in
create)
name="$(last_arg "$@")"
label=""
while [ "$#" -gt 0 ]; do
if [ "$1" = --label ]; then label="${2#*=}"; shift 2; else shift; fi
done
touch "$state/network-$name"
printf '%s\n' "$label" > "$state/network-label-$name"
printf '%s\n' "$name" > "$state/network-name"
printf '%s\n' "$name"
;;
inspect)
format=""
if [ "${1:-}" = --format ]; then
format="$2"
shift 2
fi
name="${1:?}"
[ -f "$state/network-$name" ] || exit 1
if [[ "$format" == *Internal* ]]; then printf 'true\n'; fi
if [[ "$format" == *Labels* ]]; then cat "$state/network-label-$name"; fi
;;
ls)
for item in "$state"/network-*; do
[ -e "$item" ] || continue
case "$item" in *-label-*|*/network-name) continue ;; esac
basename "$item" | sed 's/^network-//'
done
;;
rm)
name="${1:?}"
rm -f "$state/network-$name" "$state/network-label-$name" "$state/network-name"
printf '%s\n' "$name"
;;
*) exit 90 ;;
esac
;;
volume)
sub="${1:?}"
shift
case "$sub" in
create)
name="$(last_arg "$@")"
label=""
while [ "$#" -gt 0 ]; do
if [ "$1" = --label ]; then label="${2#*=}"; shift 2; else shift; fi
done
touch "$state/volume-$name"
printf '%s\n' "$label" > "$state/volume-label-$name"
printf '%s\n' "$name"
;;
inspect)
format=""
if [ "${1:-}" = --format ]; then format="$2"; shift 2; fi
name="${1:?}"
[ -f "$state/volume-$name" ] || exit 1
if [[ "$format" == *Labels* ]]; then cat "$state/volume-label-$name"; fi
;;
ls)
for item in "$state"/volume-*; do
[ -e "$item" ] || continue
case "$item" in *-label-*) continue ;; esac
basename "$item" | sed 's/^volume-//'
done
;;
rm)
name="${1:?}"
rm -f "$state/volume-$name" "$state/volume-label-$name"
if [[ "$name" == *-backup ]]; then
rm -f "$state/staged-artifact-sha256"
fi
printf '%s\n' "$name"
;;
*) exit 90 ;;
esac
;;
run)
name=""
replica=""
label=""
network=""
image=""
expected_artifact_sha256=""
while [ "$#" -gt 0 ]; do
case "$1" in
--name)
name="$2"
shift 2
;;
--env)
if [[ "$2" == CLICKHOUSE_RESTORE_REPLICA=* ]]; then
replica="${2#*=}"
fi
if [[ "$2" == EXPECTED_ARTIFACT_SHA256=* ]]; then
expected_artifact_sha256="${2#*=}"
fi
shift 2
;;
--label)
label="${2#*=}"
shift 2
;;
--network)
network="$2"
shift 2
;;
--network-alias|--restart|--mount|--user|--entrypoint)
shift 2
;;
--detach|--pull=never)
shift
;;
clickhouse/clickhouse-server:25.5.6|signoz/zookeeper:3.7.1)
image="$1"
shift
;;
*)
shift
;;
esac
done
[ -n "$name" ] || exit 91
touch "$state/container-$name"
printf '%s\n' "$label" > "$state/container-label-$name"
printf '%s\n' "$network" > "$state/container-network-$name"
case "$image" in
clickhouse/clickhouse-server:25.5.6)
printf '%s\n' "${FAKE_CLICKHOUSE_IMAGE_ID:?}" > "$state/container-image-$name"
;;
signoz/zookeeper:3.7.1)
printf '%s\n' "${FAKE_ZOOKEEPER_IMAGE_ID:?}" > "$state/container-image-$name"
;;
*) exit 96 ;;
esac
if [ -n "$replica" ]; then printf '%s\n' "$replica" > "$state/replica"; fi
if [ "$network" = none ]; then
if [ "${FAKE_STAGING_HANG:-0}" = 1 ]; then sleep 10; fi
[ "${FAKE_STAGING_FAIL:-0}" = 0 ] || exit 94
staged_hash="$expected_artifact_sha256"
if [ "${FAKE_STAGING_HASH_MISMATCH:-0}" = 1 ]; then
staged_hash="$(printf '0%.0s' {1..64})"
fi
printf '%s\n' "$staged_hash" > "$state/staged-artifact-sha256"
printf '%s\n' "$staged_hash"
else
printf 'fake-container-%s\n' "$name"
fi
;;
exec)
container="${1:?}"
shift
[ -f "$state/container-$container" ] || exit 1
if [ "${1:-}" = sha256sum ]; then
[ -f "$state/staged-artifact-sha256" ] || exit 97
artifact_hash="$(cat "$state/staged-artifact-sha256")"
if [ "${FAKE_CLICKHOUSE_HASH_MISMATCH:-0}" = 1 ]; then
artifact_hash="$(printf 'f%.0s' {1..64})"
fi
printf '%s /backups/native-backup.zip\n' "$artifact_hash"
exit 0
fi
query=""
operation=""
while [ "$#" -gt 0 ]; do
case "$1" in
--query)
query="$2"
shift 2
;;
--param_operation_id)
operation="$2"
shift 2
;;
*) shift ;;
esac
done
case "$query" in
CHECK\ TABLE*)
if [ "${FAKE_CHECK_TABLE_FAIL:-0}" = 1 ]; then printf '0\n'; else printf '1\n'; fi
;;
RESTORE\ *)
case "$query" in
*" SETTINGS id='${EXPECTED_FAKE_RESTORE_OPERATION_ID:?}' ASYNC")
operation="${EXPECTED_FAKE_RESTORE_OPERATION_ID}"
;;
*) exit 93 ;;
esac
touch "$state/restored"
printf '%s\tRESTORING\n' "$operation"
;;
*FROM\ system.backups*)
printf "Disk('backups', 'native-backup.zip')\tRESTORED\t100\t1000\t%s\n" "${EMPTY_SHA256:?}"
;;
*FROM\ system.disks*) printf '/backups/\n' ;;
*FROM\ system.macros*)
printf 'replica\t%s\nshard\t01\n' "$(cat "$state/replica")"
;;
*FROM\ system.clusters*) printf 'cluster\trestore-clickhouse\t9000\n' ;;
*FROM\ system.zookeeper*) printf '1\n' ;;
*"SELECT count() FROM system.databases"*) printf '0\n' ;;
*"SELECT count() FROM system.tables"*) printf '0\n' ;;
*"SELECT database,name,engine"*) cat "${FAKE_RESTORED_INVENTORY:?}" ;;
"SELECT 1") printf '1\n' ;;
*) printf 'UNHANDLED QUERY: %s\n' "$query" >&2; exit 92 ;;
esac
;;
logs)
printf 'bounded fake container log\n'
;;
rm)
if [ "${1:-}" = -f ]; then shift; fi
name="${1:?}"
rm -f "$state/container-$name" "$state/container-label-$name" \
"$state/container-network-$name" "$state/container-image-$name"
printf '%s\n' "$name"
;;
*) exit 99 ;;
esac
""",
encoding="utf-8",
)
docker.chmod(0o755)
return bin_dir, state_dir, log_path
def _environment(
tmp_path: Path,
artifact: Path,
manifest: Path,
inventory: Path,
restored_inventory: Path,
) -> dict[str, str]:
bin_dir, state_dir, log_path = _write_fake_docker(tmp_path)
return os.environ | {
"PATH": f"{bin_dir}:{os.environ['PATH']}",
"TRACE_ID": "trace-restore-test",
"RUN_ID": "run-restore-test",
"WORK_ITEM_ID": "P0-OBS-002",
"CLICKHOUSE_NATIVE_ARTIFACT_PATH": str(artifact),
"CLICKHOUSE_NATIVE_MANIFEST_PATH": str(manifest),
"CLICKHOUSE_NATIVE_SOURCE_INVENTORY_PATH": str(inventory),
"CLICKHOUSE_NATIVE_ARTIFACT_SHA256": hashlib.sha256(
artifact.read_bytes()
).hexdigest(),
"CLICKHOUSE_NATIVE_OPERATION_ID": "awoooi-backup-operation",
"CLICKHOUSE_NATIVE_EXPECTED_DATABASES": ",".join(EXPECTED_DATABASES),
"CLICKHOUSE_RESTORE_RECEIPT_ROOT": str(tmp_path / "receipts"),
"CLICKHOUSE_RESTORE_POLL_INTERVAL_SECONDS": "1",
"CLICKHOUSE_RESTORE_STARTUP_TIMEOUT_SECONDS": "5",
"CLICKHOUSE_RESTORE_TIMEOUT_SECONDS": "5",
"CLICKHOUSE_RESTORE_COMMAND_TIMEOUT_SECONDS": "5",
"FAKE_DOCKER_LOG": str(log_path),
"FAKE_DOCKER_STATE": str(state_dir),
"FAKE_CLICKHOUSE_IMAGE_ID": CLICKHOUSE_IMAGE_ID,
"FAKE_ZOOKEEPER_IMAGE_ID": ZOOKEEPER_IMAGE_ID,
"FAKE_RESTORED_INVENTORY": str(restored_inventory),
"EXPECTED_FAKE_RESTORE_OPERATION_ID": "restore-"
+ hashlib.sha256(
b"trace-restore-test\0run-restore-test\0P0-OBS-002"
).hexdigest(),
"EMPTY_SHA256": EMPTY_SHA256,
}
def _run(mode: str, env: dict[str, str]) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["bash", str(SCRIPT), mode],
text=True,
capture_output=True,
env=env,
)
def _status(tmp_path: Path) -> dict[str, object]:
return json.loads(
(tmp_path / "receipts" / "run-restore-test" / "status.json").read_text(
encoding="utf-8"
)
)
def _assert_ephemeral_resources_absent(state_dir: Path) -> None:
assert not list(state_dir.glob("container-*"))
assert not list(state_dir.glob("volume-*"))
assert not list(state_dir.glob("network-*"))
assert not (state_dir / "staged-artifact-sha256").exists()
def test_hook_check_uses_env_contract_without_runtime_creation(tmp_path: Path) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(restored_delta=3), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
result = _run("--check", env)
assert result.returncode == 0, result.stderr
assert "terminal=check_pass_no_runtime_write" in result.stdout
docker_log = Path(env["FAKE_DOCKER_LOG"]).read_text(encoding="utf-8")
assert "image inspect" in docker_log
assert "network create" not in docker_log
assert "volume create" not in docker_log
assert not any(line.startswith("run ") for line in docker_log.splitlines())
status = _status(tmp_path)
assert status["check_pass"] is True
assert status["ephemeral_runtime_created"] is False
assert status["production_network_attached"] is False
assert status["production_restore_performed"] is False
def test_apply_restores_on_internal_pair_and_cleans_every_resource(
tmp_path: Path,
) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(restored_delta=3), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
check = _run("--check", env)
assert check.returncode == 0, check.stderr
result = _run("--apply", env)
assert result.returncode == 0, result.stderr
assert "terminal=verified" in result.stdout
docker_lines = Path(env["FAKE_DOCKER_LOG"]).read_text(encoding="utf-8").splitlines()
network_create = next(
line for line in docker_lines if line.startswith("network create")
)
assert "--internal" in network_create
run_lines = [line for line in docker_lines if line.startswith("run ")]
assert len(run_lines) == 3
assert all("--pull=never" in line for line in run_lines)
assert all("--publish" not in line and " -p " not in line for line in run_lines)
staging_run = next(line for line in run_lines if "--network none" in line)
zookeeper_run = next(line for line in run_lines if "signoz/zookeeper:3.7.1" in line)
clickhouse_run = next(
line for line in run_lines if "--network-alias restore-clickhouse" in line
)
assert "--user 0:0" in staging_run
assert "--entrypoint /bin/sh" in staging_run
assert (
f"type=bind,src={artifact},dst=/tmp/awoooi-source-native-backup.zip,readonly"
in staging_run
)
assert sum(f"src={artifact}" in line for line in run_lines) == 1
assert "type=volume,src=awoooi-ch-restore-" in staging_run
assert "-backup,dst=/backups" in staging_run
assert "ALLOW_ANONYMOUS_LOGIN=yes" in zookeeper_run
assert "--network awoooi-ch-restore-" in zookeeper_run
assert "--network awoooi-ch-restore-" in clickhouse_run
assert str(artifact) not in clickhouse_run
assert "type=volume,src=awoooi-ch-restore-" in clickhouse_run
assert "-backup,dst=/backups" in clickhouse_run
assert "dst=/backups,readonly" not in clickhouse_run
assert (
"dst=/etc/clickhouse-server/config.d/awoooi-backup-disk.xml,readonly"
in clickhouse_run
)
assert (
"dst=/etc/clickhouse-server/config.d/awoooi-restore-isolation.xml,readonly"
in clickhouse_run
)
hash_readback = next(
line
for line in docker_lines
if line.startswith("exec ") and "sha256sum /backups/native-backup.zip" in line
)
assert "exec awoooi-ch-restore-" in hash_readback
restore_line = next(line for line in docker_lines if "RESTORE DATABASE" in line)
assert "allow_non_empty_tables" not in restore_line
assert " SETTINGS id='restore-" in restore_line
assert "id={operation_id:String}" not in restore_line
macro_line = next(line for line in docker_lines if "FROM system.macros" in line)
assert "SELECT macro,substitution" in macro_line
assert "SELECT name,value" not in macro_line
state_dir = Path(env["FAKE_DOCKER_STATE"])
_assert_ephemeral_resources_absent(state_dir)
status = _status(tmp_path)
assert status["terminal"] == "verified"
assert status["check_pass"] is True
assert status["restore_terminal"] == "RESTORED"
assert status["manifest_parity"] == 1
assert status["check_table_count"] == status["check_table_pass_count"] == 7
assert status["critical_nonzero_count"] == 4
assert status["row_delta"] == 3
expected_hash = hashlib.sha256(artifact.read_bytes()).hexdigest()
assert status["artifact_sha256"] == expected_hash
assert status["staged_artifact_sha256"] == expected_hash
assert status["clickhouse_artifact_sha256"] == expected_hash
assert status["artifact_staging_verified"] is True
assert status["clickhouse_artifact_readback_verified"] is True
assert status["staging_network_mode"] == "none"
assert status["artifact_source_mount"] == "read_only_staging_only"
assert status["backup_volume_mount"] == "writable_ephemeral"
assert status["cleanup_verified"] is True
def test_apply_failure_still_cleans_pair_network_and_volumes(tmp_path: Path) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
assert _run("--check", env).returncode == 0
env["FAKE_CHECK_TABLE_FAIL"] = "1"
result = _run("--apply", env)
assert result.returncode == 1
assert "terminal=failed" in result.stdout
state_dir = Path(env["FAKE_DOCKER_STATE"])
_assert_ephemeral_resources_absent(state_dir)
status = _status(tmp_path)
assert status["terminal"] == "failed"
assert status["cleanup_verified"] is True
assert status["apply_pass"] is False
def test_artifact_staging_failure_cleans_exact_resources(tmp_path: Path) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
assert _run("--check", env).returncode == 0
env["FAKE_STAGING_FAIL"] = "1"
result = _run("--apply", env)
assert result.returncode == 1
_assert_ephemeral_resources_absent(Path(env["FAKE_DOCKER_STATE"]))
status = _status(tmp_path)
assert status["terminal"] == "failed"
assert status["reason"] == "isolated_artifact_staging_failed"
assert status["artifact_staging_verified"] is False
assert status["cleanup_verified"] is True
def test_artifact_staging_hang_is_bounded_and_cleans_exact_resources(
tmp_path: Path,
) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
env["CLICKHOUSE_RESTORE_COMMAND_TIMEOUT_SECONDS"] = "1"
assert _run("--check", env).returncode == 0
env["FAKE_STAGING_HANG"] = "1"
result = subprocess.run(
["bash", str(SCRIPT), "--apply"],
text=True,
capture_output=True,
env=env,
timeout=12,
)
assert result.returncode == 1
_assert_ephemeral_resources_absent(Path(env["FAKE_DOCKER_STATE"]))
status = _status(tmp_path)
assert status["terminal"] == "failed"
assert status["reason"] == "isolated_artifact_staging_failed"
assert status["artifact_staging_verified"] is False
assert status["cleanup_verified"] is True
def test_staged_artifact_hash_mismatch_fails_before_isolated_services(
tmp_path: Path,
) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
assert _run("--check", env).returncode == 0
env["FAKE_STAGING_HASH_MISMATCH"] = "1"
result = _run("--apply", env)
assert result.returncode == 1
docker_lines = Path(env["FAKE_DOCKER_LOG"]).read_text(encoding="utf-8").splitlines()
assert not any(
"signoz/zookeeper:3.7.1" in line
for line in docker_lines
if line.startswith("run ")
)
_assert_ephemeral_resources_absent(Path(env["FAKE_DOCKER_STATE"]))
status = _status(tmp_path)
assert status["reason"] == "staged_artifact_hash_mismatch"
assert status["cleanup_verified"] is True
def test_clickhouse_artifact_hash_readback_mismatch_fails_and_cleans(
tmp_path: Path,
) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
assert _run("--check", env).returncode == 0
env["FAKE_CLICKHOUSE_HASH_MISMATCH"] = "1"
result = _run("--apply", env)
assert result.returncode == 1
_assert_ephemeral_resources_absent(Path(env["FAKE_DOCKER_STATE"]))
status = _status(tmp_path)
assert status["reason"] == "isolated_clickhouse_artifact_hash_mismatch"
assert status["artifact_staging_verified"] is True
assert status["clickhouse_artifact_readback_verified"] is False
assert status["clickhouse_artifact_sha256"] == "f" * 64
assert status["cleanup_verified"] is True
def test_docker_daemon_failure_cannot_claim_cleanup_verified(tmp_path: Path) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
assert _run("--check", env).returncode == 0
env["FAKE_DOCKER_INFO_FAIL"] = "1"
result = _run("--apply", env)
assert result.returncode == 1
status = _status(tmp_path)
assert status["terminal"] == "failed"
assert status["cleanup_verified"] is False
assert status["reason"] == "ephemeral_resource_cleanup_failed"
def test_cleanup_label_inspect_hang_is_bounded_and_fails_closed(tmp_path: Path) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
env["CLICKHOUSE_RESTORE_COMMAND_TIMEOUT_SECONDS"] = "1"
assert _run("--check", env).returncode == 0
env["FAKE_CHECK_TABLE_FAIL"] = "1"
env["FAKE_LABEL_INSPECT_HANG"] = "1"
result = subprocess.run(
["bash", str(SCRIPT), "--apply"],
text=True,
capture_output=True,
env=env,
timeout=12,
)
assert result.returncode == 1
status = _status(tmp_path)
assert status["terminal"] == "failed"
assert status["cleanup_verified"] is False
assert status["reason"] == "ephemeral_resource_cleanup_failed"
def test_check_fails_closed_on_local_image_id_drift(tmp_path: Path) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
env["FAKE_CLICKHOUSE_IMAGE_ID"] = "sha256:" + "0" * 64
result = _run("--check", env)
assert result.returncode == 1
assert "clickhouse_local_image_id_mismatch" in result.stderr
docker_log = Path(env["FAKE_DOCKER_LOG"]).read_text(encoding="utf-8")
assert "network create" not in docker_log
assert "volume create" not in docker_log
def test_flat_host_deploy_accepts_reviewed_absolute_helper_and_config_paths(
tmp_path: Path,
) -> None:
artifact, manifest, inventory = _write_backup_fixture(tmp_path)
restored = tmp_path / "restored.tsv"
restored.write_text(_inventory(), encoding="utf-8")
env = _environment(tmp_path, artifact, manifest, inventory, restored)
host_root = tmp_path / "host-backup"
host_root.mkdir()
helper = host_root / "clickhouse-restore-inventory.py"
backup_config = host_root / "backup_disk.xml"
cluster_config = host_root / "isolated-cluster.xml"
shutil.copyfile(INVENTORY_HELPER, helper)
shutil.copyfile(
REPO_ROOT / "ops/signoz/clickhouse/config.d/backup_disk.xml", backup_config
)
shutil.copyfile(
REPO_ROOT / "ops/signoz/clickhouse/restore-drill/config.d/isolated-cluster.xml",
cluster_config,
)
env.update(
{
"CLICKHOUSE_RESTORE_INVENTORY_HELPER": str(helper),
"CLICKHOUSE_RESTORE_BACKUP_DISK_CONFIG": str(backup_config),
"CLICKHOUSE_RESTORE_CLUSTER_CONFIG": str(cluster_config),
}
)
result = _run("--check", env)
assert result.returncode == 0, result.stderr
assert "terminal=check_pass_no_runtime_write" in result.stdout
def test_inventory_compare_allows_online_row_delta_but_not_engine_drift(
tmp_path: Path,
) -> None:
source = tmp_path / "source.tsv"
restored = tmp_path / "restored.tsv"
source.write_text(_inventory(), encoding="utf-8")
restored.write_text(_inventory(restored_delta=9), encoding="utf-8")
accepted = subprocess.run(
[
"python3",
str(INVENTORY_HELPER),
"compare",
"--source-inventory",
str(source),
"--restored-inventory",
str(restored),
],
text=True,
capture_output=True,
)
assert accepted.returncode == 0, accepted.stderr
assert "manifest_parity=1" in accepted.stdout
assert "row_delta=9" in accepted.stdout
restored.write_text(_inventory(engine_mismatch=True), encoding="utf-8")
rejected = subprocess.run(
[
"python3",
str(INVENTORY_HELPER),
"compare",
"--source-inventory",
str(source),
"--restored-inventory",
str(restored),
],
text=True,
capture_output=True,
)
assert rejected.returncode == 1
assert "table_engine_mismatch_signoz_meter_meter" in rejected.stderr
restored.write_text(_inventory(schema_mismatch=True), encoding="utf-8")
schema_rejected = subprocess.run(
[
"python3",
str(INVENTORY_HELPER),
"compare",
"--source-inventory",
str(source),
"--restored-inventory",
str(restored),
],
text=True,
capture_output=True,
)
assert schema_rejected.returncode == 1
assert "table_schema_mismatch_signoz_meter_meter" in schema_rejected.stderr