Files
xas-test-library/deploy/cutover_console.py
T

91 lines
4.0 KiB
Python

"""Cut over the verified dedicated console; preserve both previous deployments."""
from datetime import datetime, timezone
import json
from pathlib import Path
import shutil
import sqlite3
import subprocess
import sys
import time
import urllib.request
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from deploy.dedicated import ROOT, NAME, IMAGE, UNIT, command, prepare, web_arguments
from console.settings import Settings
from console.store import Store
PREVIEW = NAME + "-preview-20261013"
def inspect(name):
return json.loads(command("docker", "inspect", name))[0]
def get(port, path):
req = urllib.request.Request(f"http://10.88.118.188:{port}{path}", headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=8) as response:
return json.load(response)
def main():
prepare()
preview = inspect(NAME)
if preview["Config"]["Labels"].get("xas.application") != "script-console":
raise RuntimeError("Unexpected preview owner")
if command("docker", "ps", "-aq", "--filter", "name=^/" + PREVIEW + "$"):
raise RuntimeError("Cutover has already been attempted; inspect saved state")
for name in ("adamwen-test-console", "adamwen-test-console-proxy"):
if inspect(name)["Config"]["Labels"].get("cn.aisuan.owner") != "adamwen-test-console":
raise RuntimeError("Unexpected old console owner")
old_runs = get(14440, "/api/41/project/xas-test-library/executions/running")
if not isinstance(old_runs.get("executions"), list):
raise RuntimeError("Cannot verify old console running tasks")
if get(14442, "/api/summary")["active"] or old_runs["executions"]:
raise RuntimeError("Console has running work; wait before cutover")
store = Store(Settings.from_env())
if not any(r["state"] == "succeeded" and r["config"]["profile"] == "legacy_smoke" for r in store.runs()):
raise RuntimeError("Successful single smoke acceptance missing")
backup = ROOT / "runtime/backups/before-dedicated-20261013"
backup.mkdir(mode=0o700, exist_ok=False)
command("systemctl", "stop", UNIT)
command("docker", "stop", NAME)
with sqlite3.connect(store.settings.db) as source, sqlite3.connect(backup / "console.sqlite3") as destination:
source.backup(destination)
if destination.execute("PRAGMA integrity_check").fetchone()[0] != "ok":
raise RuntimeError("Snapshot integrity failure")
for folder in ("library", "runs"):
shutil.copytree(ROOT / "runtime" / folder, backup / folder)
(backup / "metadata.json").write_text(json.dumps({"observed_at": datetime.now(timezone.utc).isoformat(),
"preview_image": preview["Image"], "new_image": IMAGE, "old_console": "adamwen-test-console",
"old_proxy": "adamwen-test-console-proxy"}, indent=2))
command("docker", "rename", NAME, PREVIEW)
command("docker", "stop", "adamwen-test-console")
command("docker", "stop", "adamwen-test-console-proxy")
try:
print(command(*web_arguments(14440)), flush=True)
command("systemctl", "start", UNIT)
for _ in range(20):
try:
if get(14440, "/api/summary")["worker"]["online"]:
print("CUTOVER_OK", flush=True)
break
except (OSError, ValueError):
pass
time.sleep(.5)
else:
raise RuntimeError("New entry point failed readiness check")
except Exception:
# Only revert app availability, never restore old data over newer records.
result = subprocess.run(["docker", "inspect", NAME], capture_output=True, text=True)
if result.returncode == 0 and json.loads(result.stdout)[0]["Config"]["Labels"].get("xas.application") == "script-console":
command("docker", "rm", "-f", NAME)
command("docker", "start", "adamwen-test-console-proxy")
command("docker", "start", "adamwen-test-console")
command("docker", "start", PREVIEW)
command("systemctl", "start", UNIT)
raise
if __name__ == "__main__":
main()