91 lines
4.0 KiB
Python
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()
|