Some checks failed
CD Pipeline / select-latest-carrier (push) Successful in 49s
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m28s
CD Pipeline / revalidate-deploy-carrier (push) Successful in 27s
CD Pipeline / build-and-deploy (push) Failing after 17m18s
CD Pipeline / revalidate-post-deploy-carrier (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
E2E Health Check / e2e-health (push) Successful in 48s
AI 技術雷達監控 / ai-technology-watch (push) Successful in 1m4s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 22s
98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
PLAYBOOK = ROOT / "infra" / "ansible" / "playbooks" / "110-backup-runtime-deploy.yml"
|
|
|
|
EXPECTED_FILES = {
|
|
"common.sh",
|
|
"backup-all.sh",
|
|
"backup-awoooi.sh",
|
|
"backup-awoooi-frequent.sh",
|
|
"backup-clawbot.sh",
|
|
"backup-sentry.sh",
|
|
"backup-host188-products.sh",
|
|
"verify-host188-products-backup.sh",
|
|
"verify-host188-products-archive.py",
|
|
"check-backup-integrity.sh",
|
|
"sync-offsite-backups.sh",
|
|
"backup-offsite-readiness-gate.sh",
|
|
"verify-offsite-full-sync.sh",
|
|
"enforce-latest-only-retention.sh",
|
|
}
|
|
|
|
|
|
def test_backup_runtime_deploy_is_fixed_to_host_110_and_fourteen_files() -> None:
|
|
plays = yaml.safe_load(PLAYBOOK.read_text(encoding="utf-8"))
|
|
assert len(plays) == 1
|
|
play = plays[0]
|
|
|
|
assert play["hosts"] == "host_110"
|
|
assert play["gather_facts"] is False
|
|
assert play["become"] is True
|
|
assert play["serial"] == 1
|
|
assert play["any_errors_fatal"] is True
|
|
assert play["vars"]["backup_runtime_root"] == "/backup/scripts"
|
|
stage1 = set(play["vars"]["backup_runtime_stage1_files"])
|
|
stage2 = set(play["vars"]["backup_runtime_stage2_files"])
|
|
assert stage1 | stage2 == EXPECTED_FILES
|
|
assert not stage1 & stage2
|
|
assert play["vars"]["backup_runtime_fault_injection_after_stage1"] is False
|
|
|
|
|
|
def test_backup_runtime_deploy_has_validation_rollback_and_identity_readback() -> None:
|
|
source = PLAYBOOK.read_text(encoding="utf-8")
|
|
|
|
assert 'inventory_hostname == "host_110"' in source
|
|
assert 'validate: "/bin/bash -n %s"' in source
|
|
assert "backup: true" in source
|
|
assert "checksum_algorithm: sha256" in source
|
|
assert "rstrip=false" in source
|
|
assert "backup_runtime_identity_failed" in source
|
|
assert "backup_runtime_stage_identity_failed" in source
|
|
assert "backup_runtime_fault_injection_after_stage1" in source
|
|
assert "backup_runtime_promotion_failed_and_rolled_back" in source
|
|
assert "rescue:" in source
|
|
assert "ansible.builtin.slurp:" in source
|
|
assert "remote_src: true" in source
|
|
assert "backup-health-textfile-exporter.py" in source
|
|
assert "changed_when: false" in source
|
|
|
|
for forbidden in (
|
|
"ansible.builtin.service:",
|
|
"ansible.builtin.systemd:",
|
|
"ansible.builtin.cron:",
|
|
"ansible.builtin.shell:",
|
|
"ansible.builtin.command:",
|
|
"ansible.builtin.git:",
|
|
"docker_container",
|
|
"kubectl",
|
|
):
|
|
assert forbidden not in source
|
|
|
|
|
|
def test_fault_injection_occurs_mid_promotion_and_rescue_restores_prior_state() -> None:
|
|
play = yaml.safe_load(PLAYBOOK.read_text(encoding="utf-8"))[0]
|
|
transaction = next(
|
|
task
|
|
for task in play["tasks"]
|
|
if task["name"] == "Promote the staged backup runtime transaction"
|
|
)
|
|
block_names = [task["name"] for task in transaction["block"]]
|
|
rescue_names = [task["name"] for task in transaction["rescue"]]
|
|
|
|
assert block_names.index("Promote transaction stage one") < block_names.index(
|
|
"Exercise bounded rollback fault injection when explicitly requested"
|
|
)
|
|
assert block_names.index(
|
|
"Exercise bounded rollback fault injection when explicitly requested"
|
|
) < block_names.index("Promote transaction stage two")
|
|
assert "Restore runtime files that existed before promotion" in rescue_names
|
|
assert "Remove runtime files that did not exist before promotion" in rescue_names
|
|
assert "Restore the exporter that existed before promotion" in rescue_names
|
|
assert rescue_names[-1] == "Fail closed after transactional rollback"
|