feat: stabilize multi-region runtime sync and worker orchestration

This commit is contained in:
root
2026-04-27 15:48:12 +08:00
parent 7cbde2aa78
commit 215a364891
137 changed files with 31931 additions and 1943 deletions

View File

@@ -12,6 +12,8 @@ from threading import Lock
from urllib.parse import urlsplit, urlunsplit
from uuid import uuid4
from psycopg2 import errors
from app.core.db import get_db
from app.services.build_info_service import get_runtime_build_info
from app.services.ops_command_service import build_bash_command
@@ -74,6 +76,19 @@ _ROLLOUT_INSPECTION_ACTION_KEYS = ("health.snapshot", "logs.collect", "diagnosti
_SAFE_RELEASE_PACKAGE_NAME_RE = re.compile(r"^[A-Za-z0-9._-]+$")
_RELEASE_SCHEMA_LOCK = Lock()
_RELEASE_SCHEMA_READY = False
_RELEASE_SCHEMA_ADVISORY_LOCK_KEY = 90421803
_RELEASE_REQUIRED_TABLES = ("ops_releases", "ops_release_rollouts")
_RELEASE_REQUIRED_COLUMNS = {
"ops_release_rollouts": (
"rollout_code",
"target_nodes_json",
"batch_cursor",
"batches_total",
"jobs_total",
"jobs_created",
"result_summary_json",
),
}
def ensure_ops_release_schema() -> None:
@@ -86,13 +101,52 @@ def ensure_ops_release_schema() -> None:
if _RELEASE_SCHEMA_READY:
return
with get_db() as conn:
conn.autocommit = False
with conn.cursor() as cur:
cur.execute(_RELEASE_SCHEMA_SQL)
conn.commit()
if _ops_release_schema_basics_present(cur):
_RELEASE_SCHEMA_READY = True
return
conn.autocommit = False
try:
with conn.cursor() as cur:
cur.execute("SELECT pg_advisory_xact_lock(%s)", (_RELEASE_SCHEMA_ADVISORY_LOCK_KEY,))
cur.execute(_RELEASE_SCHEMA_SQL)
conn.commit()
except Exception as exc:
recoverable = isinstance(exc, (errors.DeadlockDetected, errors.LockNotAvailable))
try:
conn.rollback()
except Exception:
pass
if not recoverable:
raise
with conn.cursor() as cur:
if not _ops_release_schema_basics_present(cur):
raise
_RELEASE_SCHEMA_READY = True
def _ops_release_schema_basics_present(cur) -> bool:
for table_name in _RELEASE_REQUIRED_TABLES:
cur.execute("SELECT to_regclass(%s)", (f"public.{table_name}",))
row = cur.fetchone()
if not row or not row[0]:
return False
for table_name, required_columns in _RELEASE_REQUIRED_COLUMNS.items():
cur.execute(
"""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = %s
""",
(table_name,),
)
existing_columns = {str(row[0] or "").strip() for row in list(cur.fetchall() or [])}
if not set(required_columns).issubset(existing_columns):
return False
return True
def _decode_json(value: object) -> dict:
if isinstance(value, dict):
return value
@@ -1091,7 +1145,11 @@ def build_rollout_target_operational_readiness(
desired_release: dict | None = None,
) -> dict:
from app.services.cluster_runtime_service import get_cluster_snapshot
from app.services.ops_agent_service import ensure_ops_agent_schema, get_managed_node_onboarding
from app.services.ops_agent_service import (
ensure_ops_agent_schema,
get_managed_node_onboarding,
list_managed_nodes_with_agent_state,
)
from app.services.ops_job_service import list_managed_nodes
ensure_ops_agent_schema()
@@ -1132,6 +1190,7 @@ def build_rollout_target_operational_readiness(
for item in list(cluster_snapshot.get("nodes") or [])
if str(item.get("node_code") or "").strip()
}
managed_nodes_payload = list_managed_nodes_with_agent_state()
managed_nodes = list_managed_nodes()
managed_map = {
str(item.get("node_code") or "").strip(): item
@@ -1239,7 +1298,11 @@ def build_rollout_target_operational_readiness(
last_seen_at = str(managed.get("last_seen_at") or "").strip() or str(metadata.get("last_seen_at") or "").strip()
cluster_status = str(cluster_node.get("status") or target.get("status") or "").strip()
current_load = int(cluster_node.get("current_load", target.get("current_load", 0)) or 0)
onboarding = get_managed_node_onboarding(node_code) if node_code else {}
onboarding = (
get_managed_node_onboarding(node_code, nodes_payload=managed_nodes_payload)
if node_code
else {}
)
onboarding_stage = dict(onboarding.get("onboarding_stage") or {})
recovery_decision = dict(onboarding.get("recovery_decision") or {})
onboarding_stage_code = str(onboarding_stage.get("code") or "").strip()