增强(08-23-isolated-backup-restore-drill): 固化八阶段RTO合同
这个提交包含在:
@@ -432,14 +432,17 @@ finish = re.search(r"\nfinish\(\) \{(?P<body>.*?)\n\}\ntrap finish", text, re.S)
|
||||
if not finish:
|
||||
raise SystemExit("finish function not found")
|
||||
body = finish.group("body")
|
||||
finalize_position = body.find("\n if ! rto_finalize; then")
|
||||
emit_position = body.find("\n emit_restore_diagnostics")
|
||||
cleanup_position = body.find("\n cleanup_runtime ||")
|
||||
if finalize_position == -1:
|
||||
raise SystemExit("RTO finalization missing")
|
||||
if emit_position == -1:
|
||||
raise SystemExit("diagnostic emission missing")
|
||||
if cleanup_position == -1:
|
||||
raise SystemExit("runtime cleanup call missing")
|
||||
if emit_position > cleanup_position:
|
||||
raise SystemExit("diagnostics are emitted after cleanup")
|
||||
if not (finalize_position < emit_position < cleanup_position):
|
||||
raise SystemExit("RTO/stage diagnostics are not finalized before cleanup")
|
||||
required = {
|
||||
'echo "dump_list=$DUMP_LIST_STATUS"',
|
||||
'echo "target_confirmation=$TARGET_CONFIRMATION_STATUS"',
|
||||
@@ -452,14 +455,41 @@ required = {
|
||||
'echo "pg_restore_exit_code=$PG_RESTORE_EXIT_CODE"',
|
||||
'echo "pg_restore_error_count=$PG_RESTORE_ERROR_COUNT"',
|
||||
'echo "restore_error_category=$RESTORE_ERROR_CATEGORY"',
|
||||
'echo "rto_batch_copy_seconds=$RTO_BATCH_COPY_SECONDS"',
|
||||
'echo "rto_postgres_container_start_seconds=$RTO_POSTGRES_CONTAINER_START_SECONDS"',
|
||||
'echo "rto_dump_list_seconds=$RTO_DUMP_LIST_SECONDS"',
|
||||
'echo "rto_target_confirmation_seconds=$RTO_TARGET_CONFIRMATION_SECONDS"',
|
||||
'echo "rto_restore_apply_seconds=$RTO_RESTORE_APPLY_SECONDS"',
|
||||
'echo "rto_database_verify_seconds=$RTO_DATABASE_VERIFY_SECONDS"',
|
||||
'echo "rto_storage_attachment_verify_seconds=$RTO_STORAGE_ATTACHMENT_VERIFY_SECONDS"',
|
||||
'echo "rto_final_integrity_seconds=$RTO_FINAL_INTEGRITY_SECONDS"',
|
||||
'echo "data_restore_rto_seconds=$RTO_SECONDS"',
|
||||
}
|
||||
missing = sorted(value for value in required if value not in text)
|
||||
if missing:
|
||||
raise SystemExit(f"diagnostic fields missing: {missing}")
|
||||
if 'RESTORE_ERROR_CATEGORY="RESTORE_APPLY_NONZERO"' not in text:
|
||||
raise SystemExit("non-zero apply classification missing")
|
||||
transitions = [
|
||||
"rto_begin batch_copy",
|
||||
"rto_transition batch_copy postgres_container_start",
|
||||
"rto_transition postgres_container_start dump_list",
|
||||
"rto_transition dump_list target_confirmation",
|
||||
"rto_transition target_confirmation restore_apply",
|
||||
"rto_transition restore_apply database_verify",
|
||||
"rto_transition database_verify storage_attachment_verify",
|
||||
"rto_transition storage_attachment_verify final_integrity",
|
||||
]
|
||||
positions = [text.find(value) for value in transitions]
|
||||
if any(position == -1 for position in positions) or positions != sorted(positions):
|
||||
raise SystemExit("RTO stage transitions missing or out of order")
|
||||
target_call_position = text.find("if ! confirm_postgres_target; then")
|
||||
if not positions[3] < target_call_position < positions[4]:
|
||||
raise SystemExit("TCP target confirmation is outside its RTO stage boundary")
|
||||
if text.count('echo "data_restore_rto_seconds=$RTO_SECONDS"') != 1:
|
||||
raise SystemExit("data restore RTO must be emitted exactly once")
|
||||
PY
|
||||
pass "diagnostics precede finally-equivalent cleanup and use allowlisted fields"
|
||||
pass "eight-stage RTO and restore diagnostics precede cleanup with one vocabulary"
|
||||
|
||||
cat >"$TEST_DIR/mock-target-docker" <<'MOCK_DOCKER'
|
||||
#!/usr/bin/env bash
|
||||
@@ -738,6 +768,107 @@ CONTAINER_NAME=""
|
||||
sed -n '1,/^trap finish EXIT HUP INT TERM$/p' "$RUNNER" \
|
||||
| sed '$d' >"$TEST_DIR/runner-finalization-functions.sh"
|
||||
|
||||
(
|
||||
# shellcheck disable=SC1090
|
||||
source "$TEST_DIR/runner-finalization-functions.sh"
|
||||
RTO_TEST_NOW=100
|
||||
rto_now() {
|
||||
printf '%s\n' "$RTO_TEST_NOW"
|
||||
}
|
||||
rto_begin batch_copy
|
||||
RTO_TEST_NOW=103
|
||||
rto_transition batch_copy postgres_container_start
|
||||
RTO_TEST_NOW=108
|
||||
rto_transition postgres_container_start dump_list
|
||||
RTO_TEST_NOW=110
|
||||
rto_transition dump_list target_confirmation
|
||||
RTO_TEST_NOW=121
|
||||
rto_transition target_confirmation restore_apply
|
||||
RTO_TEST_NOW=128
|
||||
rto_transition restore_apply database_verify
|
||||
RTO_TEST_NOW=141
|
||||
rto_transition database_verify storage_attachment_verify
|
||||
RTO_TEST_NOW=158
|
||||
rto_transition storage_attachment_verify final_integrity
|
||||
RTO_TEST_NOW=161
|
||||
rto_finalize
|
||||
emit_restore_diagnostics
|
||||
) >"$TEST_DIR/captured-rto-success" 2>&1
|
||||
for expected in \
|
||||
'rto_batch_copy_seconds=3' \
|
||||
'rto_postgres_container_start_seconds=5' \
|
||||
'rto_dump_list_seconds=2' \
|
||||
'rto_target_confirmation_seconds=11' \
|
||||
'rto_restore_apply_seconds=7' \
|
||||
'rto_database_verify_seconds=13' \
|
||||
'rto_storage_attachment_verify_seconds=17' \
|
||||
'rto_final_integrity_seconds=3' \
|
||||
'data_restore_rto_seconds=61'; do
|
||||
grep -Fxq "$expected" "$TEST_DIR/captured-rto-success" \
|
||||
|| fail "successful eight-stage RTO total or boundary mismatch"
|
||||
done
|
||||
[[ "$(grep -c '^data_restore_rto_seconds=' "$TEST_DIR/captured-rto-success")" -eq 1 ]] \
|
||||
|| fail "successful RTO total was not emitted exactly once"
|
||||
pass "rendered runner measures eight contiguous stages with an exact total"
|
||||
|
||||
set +e
|
||||
(
|
||||
# shellcheck disable=SC1090
|
||||
source "$TEST_DIR/runner-finalization-functions.sh"
|
||||
cleanup_runtime() {
|
||||
echo "cleanup_marker=runtime"
|
||||
return 0
|
||||
}
|
||||
cleanup_access() {
|
||||
echo "cleanup_marker=access"
|
||||
return 0
|
||||
}
|
||||
RTO_BATCH_COPY_SECONDS="2"
|
||||
RTO_POSTGRES_CONTAINER_START_SECONDS="4"
|
||||
RTO_DUMP_LIST_SECONDS="1"
|
||||
RTO_ACTIVE_STAGE="target_confirmation"
|
||||
RTO_STAGE_STARTED_EPOCH="200"
|
||||
RTO_TEST_NOW=209
|
||||
rto_now() {
|
||||
printf '%s\n' "$RTO_TEST_NOW"
|
||||
}
|
||||
FAILURE_CATEGORY="restore_target_confirmation_failed"
|
||||
DUMP_LIST_STATUS="PASS"
|
||||
DUMP_LIST_EXIT_CODE="0"
|
||||
TARGET_CONFIRMATION_STATUS="FAIL"
|
||||
TARGET_CONFIRMATION_EXIT_CODE="1"
|
||||
TARGET_CONFIRMATION_ATTEMPT_COUNT="60"
|
||||
RESTORE_EXECUTION_PATH="DOCKER_STDIN"
|
||||
RESTORE_ERROR_CATEGORY="TARGET_QUERY_NONZERO"
|
||||
trap finish EXIT
|
||||
exit 23
|
||||
) >"$TEST_DIR/captured-rto-target-failure" 2>&1
|
||||
rto_target_failure_exit="$?"
|
||||
set -e
|
||||
[[ "$rto_target_failure_exit" -eq 23 ]] \
|
||||
|| fail "target-confirmation timing failure did not preserve exit code"
|
||||
for expected in \
|
||||
'rto_batch_copy_seconds=2' \
|
||||
'rto_postgres_container_start_seconds=4' \
|
||||
'rto_dump_list_seconds=1' \
|
||||
'rto_target_confirmation_seconds=9' \
|
||||
'rto_restore_apply_seconds=NOT_RUN' \
|
||||
'rto_database_verify_seconds=NOT_RUN' \
|
||||
'rto_storage_attachment_verify_seconds=NOT_RUN' \
|
||||
'rto_final_integrity_seconds=NOT_RUN' \
|
||||
'data_restore_rto_seconds=16'; do
|
||||
grep -Fxq "$expected" "$TEST_DIR/captured-rto-target-failure" \
|
||||
|| fail "target-confirmation failure RTO boundary mismatch"
|
||||
done
|
||||
rto_line="$(grep -n '^rto_target_confirmation_seconds=9$' \
|
||||
"$TEST_DIR/captured-rto-target-failure" | cut -d: -f1)"
|
||||
rto_cleanup_line="$(grep -n '^cleanup_marker=runtime$' \
|
||||
"$TEST_DIR/captured-rto-target-failure" | cut -d: -f1)"
|
||||
[[ "$rto_line" =~ ^[0-9]+$ && "$rto_cleanup_line" =~ ^[0-9]+$ \
|
||||
&& "$rto_line" -lt "$rto_cleanup_line" ]] \
|
||||
|| fail "failed-stage RTO was not emitted before teardown"
|
||||
pass "target-confirmation failure preserves elapsed stages and later NOT_RUN before cleanup"
|
||||
|
||||
capture_finalization_case() {
|
||||
local output_file="$1"
|
||||
local failure_category="$2"
|
||||
@@ -805,7 +936,13 @@ grep -Fxq 'target_confirmation=NOT_RUN' \
|
||||
|| fail "target stage ran after dump-list failure"
|
||||
grep -Fxq 'restore_apply=NOT_RUN' "$TEST_DIR/captured-dump-list-failure" \
|
||||
|| fail "apply stage ran after dump-list failure"
|
||||
pass "dump-list failure keeps target and apply stages NOT_RUN"
|
||||
grep -Fxq 'rto_batch_copy_seconds=NOT_RUN' \
|
||||
"$TEST_DIR/captured-dump-list-failure" \
|
||||
|| fail "pre-RTO failure unexpectedly reported a measured stage"
|
||||
grep -Fxq 'data_restore_rto_seconds=NOT_RUN' \
|
||||
"$TEST_DIR/captured-dump-list-failure" \
|
||||
|| fail "pre-RTO failure unexpectedly reported a measured total"
|
||||
pass "pre-RTO failure keeps timing, target and apply fields NOT_RUN"
|
||||
|
||||
capture_finalization_case \
|
||||
"$TEST_DIR/captured-target-failure" \
|
||||
@@ -849,6 +986,16 @@ set +e
|
||||
PG_RESTORE_EXIT_CODE="23"
|
||||
PG_RESTORE_ERROR_COUNT="UNKNOWN"
|
||||
RESTORE_ERROR_CATEGORY="RESTORE_APPLY_NONZERO"
|
||||
RTO_BATCH_COPY_SECONDS="2"
|
||||
RTO_POSTGRES_CONTAINER_START_SECONDS="4"
|
||||
RTO_DUMP_LIST_SECONDS="1"
|
||||
RTO_TARGET_CONFIRMATION_SECONDS="9"
|
||||
RTO_ACTIVE_STAGE="restore_apply"
|
||||
RTO_STAGE_STARTED_EPOCH="300"
|
||||
RTO_TEST_NOW=312
|
||||
rto_now() {
|
||||
printf '%s\n' "$RTO_TEST_NOW"
|
||||
}
|
||||
trap finish EXIT
|
||||
exit 23
|
||||
) >"$TEST_DIR/captured-finalization" 2>&1
|
||||
@@ -869,7 +1016,13 @@ grep -Fxq 'temporary_access_cleanup=PASS' "$TEST_DIR/captured-finalization" \
|
||||
|| fail "access teardown result was not retained"
|
||||
grep -Fxq 'pg_restore_exit_code=23' "$TEST_DIR/captured-finalization" \
|
||||
|| fail "pg_restore exit code disappeared after teardown"
|
||||
pass "injected failure emits readable stages before finally-equivalent teardown"
|
||||
grep -Fxq 'rto_restore_apply_seconds=12' "$TEST_DIR/captured-finalization" \
|
||||
|| fail "restore-apply failure duration missing"
|
||||
grep -Fxq 'rto_database_verify_seconds=NOT_RUN' "$TEST_DIR/captured-finalization" \
|
||||
|| fail "database verification ran after restore-apply failure"
|
||||
grep -Fxq 'data_restore_rto_seconds=28' "$TEST_DIR/captured-finalization" \
|
||||
|| fail "restore-apply failure RTO total mismatch"
|
||||
pass "injected restore-apply failure preserves diagnostics and RTO before teardown"
|
||||
|
||||
if grep -Eiq 'postgresql://|SELECT[[:space:]]|/tmp/|local_fixture_password' \
|
||||
"$TEST_DIR/captured-dump-list-failure" \
|
||||
|
||||
在新工单中引用
屏蔽一个用户