#!/usr/bin/env python3 """Fail-closed verifier for the bounded AWOOOI API two-replica stabilization.""" from __future__ import annotations import argparse import json import subprocess import time import urllib.error import urllib.request from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[2] MANIFEST = ROOT / "k8s" / "awoooi-prod" / "06-deployment-api.yaml" NAMESPACE = "awoooi-prod" DEPLOYMENT = "awoooi-api" BOUNDARY_RECEIPT = "awoooi-executor-boundary-verification" PUBLIC_LIVENESS_URL = "https://awoooi.wooo.work/api/v1/health/live" EDGE_VIP_LIVENESS_URL = "http://192.168.0.125:32334/api/v1/health/live" EDGE_LIVENESS_URLS = { "vip125": EDGE_VIP_LIVENESS_URL, "node120": "http://192.168.0.120:32334/api/v1/health/live", "node121": "http://192.168.0.121:32334/api/v1/health/live", } EDGE_VANTAGE_HOSTS = ("192.168.0.120", "192.168.0.110") ROLLBACK_COMMAND = ( "kubectl -n awoooi-prod scale deployment/awoooi-api --replicas=1" ) def _source_documents() -> list[dict[str, Any]]: """Render the manifest with kubectl's client parser; never contact the API.""" completed = subprocess.run( ["kubectl", "create", "--dry-run=client", "-f", str(MANIFEST), "-o", "json"], check=True, capture_output=True, text=True, timeout=20, ) decoder = json.JSONDecoder() remaining = completed.stdout documents: list[dict[str, Any]] = [] while remaining.strip(): document, offset = decoder.raw_decode(remaining.lstrip()) remaining = remaining.lstrip()[offset:] documents.append(document) return documents def _source_deployment() -> dict[str, Any]: return next( document for document in _source_documents() if document.get("kind") == "Deployment" ) def _source_service() -> dict[str, Any]: return next( document for document in _source_documents() if document.get("kind") == "Service" ) def _env(container: dict[str, Any]) -> dict[str, str | None]: return {entry["name"]: entry.get("value") for entry in container.get("env", [])} def evaluate_source_contract(deployment: dict[str, Any]) -> list[str]: failures: list[str] = [] spec = deployment.get("spec") or {} strategy = (spec.get("strategy") or {}).get("rollingUpdate") or {} container = spec["template"]["spec"]["containers"][0] env = _env(container) expectations = { "replicas": (spec.get("replicas"), 2), "max_surge": (strategy.get("maxSurge"), 0), "max_unavailable": (strategy.get("maxUnavailable"), 1), "cpu_limit": ((container.get("resources") or {}).get("limits", {}).get("cpu"), "1"), "memory_limit": ((container.get("resources") or {}).get("limits", {}).get("memory"), "1Gi"), "database_pool_size": (env.get("DATABASE_POOL_SIZE"), "1"), "database_max_overflow": (env.get("DATABASE_MAX_OVERFLOW"), "0"), "database_null_pool": (env.get("DATABASE_NULL_POOL"), "true"), "database_bootstrap_ddl_enabled": ( env.get("DATABASE_BOOTSTRAP_DDL_ENABLED"), "false", ), "api_background_tasks": (env.get("AWOOOI_API_BACKGROUND_TASKS_ENABLED"), "false"), } for name, (actual, expected) in expectations.items(): if actual != expected: failures.append(f"source_{name}_mismatch:{actual!r}!={expected!r}") probes = { name: container.get(name) or {} for name in ("startupProbe", "livenessProbe", "readinessProbe") } for name, probe in probes.items(): expected_path = ( "/api/v1/health/ready" if name == "readinessProbe" else "/api/v1/health/live" ) if (probe.get("httpGet") or {}).get("path") != expected_path: failures.append(f"source_{name}_not_lightweight") return failures def evaluate_routing_contract(service: dict[str, Any], edge_config: str) -> list[str]: failures: list[str] = [] spec = service.get("spec") or {} ports = spec.get("ports") or [] api_port = next((item for item in ports if item.get("name") == "http"), {}) if service.get("metadata", {}).get("name") != "awoooi-api-svc": failures.append("source_api_service_name_mismatch") if spec.get("type") != "NodePort": failures.append("source_api_service_not_nodeport") if api_port.get("port") != 8000 or api_port.get("targetPort") != 8000: failures.append("source_api_service_port_mismatch") if api_port.get("nodePort") != 32334: failures.append("source_api_nodeport_mismatch") if "server 192.168.0.125:32334;" not in edge_config: failures.append("source_edge_vip_upstream_mismatch") return failures def evaluate_live_state( deployment: dict[str, Any], receipt: dict[str, str], pods: list[dict[str, Any]], restart_before: dict[str, int], restart_after: dict[str, int], cpu_millicores: dict[str, int], max_cpu_millicores: int, ) -> list[str]: failures = evaluate_source_contract(deployment) status = deployment.get("status") or {} for name, actual in { "desired_replicas": status.get("replicas", 0), "updated_replicas": status.get("updatedReplicas", 0), "available_replicas": status.get("availableReplicas", 0), "ready_replicas": status.get("readyReplicas", 0), }.items(): if actual != 2: failures.append(f"live_{name}_not_two:{actual}") if status.get("unavailableReplicas", 0) not in (0, None): failures.append(f"live_unavailable_replicas:{status['unavailableReplicas']}") expected_receipt = { "connection_budget_verified": "true", "api_database_null_pool": "true", "api_representative_db_preflight_passed": "true", "api_http_concurrency_probe_passed": "true", } for key, expected in expected_receipt.items(): if receipt.get(key) != expected: failures.append(f"receipt_{key}_not_verified") for key, minimum in { "api_db_connection_limit": 12, "api_required_connection_budget": 8, "api_available_connection_headroom": 8, }.items(): try: actual = int(receipt.get(key, "")) except ValueError: actual = -1 if actual < minimum: failures.append(f"receipt_{key}_below_{minimum}:{actual}") ready_pods = 0 for pod in pods: statuses = (pod.get("status") or {}).get("containerStatuses") or [] api = next((item for item in statuses if item.get("name") == "api"), None) if api and api.get("ready") is True: ready_pods += 1 if ready_pods != 2: failures.append(f"live_ready_pod_count_not_two:{ready_pods}") all_names = set(restart_before) | set(restart_after) restart_delta = sum( max(0, restart_after.get(name, 0) - restart_before.get(name, 0)) for name in all_names ) if restart_delta: failures.append(f"live_restart_delta:{restart_delta}") if set(cpu_millicores) != {pod["metadata"]["name"] for pod in pods}: failures.append("live_cpu_sample_incomplete") for pod_name, cpu in cpu_millicores.items(): if cpu >= max_cpu_millicores: failures.append(f"live_cpu_saturated:{pod_name}:{cpu}m") return failures def _kubectl_json(*args: str) -> dict[str, Any]: completed = subprocess.run( ["kubectl", *args], check=True, capture_output=True, text=True, timeout=20, ) return json.loads(completed.stdout) def _restart_counts(pods: list[dict[str, Any]]) -> dict[str, int]: counts: dict[str, int] = {} for pod in pods: statuses = (pod.get("status") or {}).get("containerStatuses") or [] api = next((item for item in statuses if item.get("name") == "api"), None) counts[pod["metadata"]["name"]] = int((api or {}).get("restartCount", 0)) return counts def _cpu_samples() -> dict[str, int]: completed = subprocess.run( [ "kubectl", "-n", NAMESPACE, "top", "pod", "-l", f"app={DEPLOYMENT}", "--containers", "--no-headers", ], check=True, capture_output=True, text=True, timeout=20, ) samples: dict[str, int] = {} for line in completed.stdout.splitlines(): fields = line.split() if len(fields) >= 3 and fields[1] == "api" and fields[2].endswith("m"): samples[fields[0]] = int(fields[2][:-1]) return samples def _http_status(url: str) -> int | None: try: with urllib.request.urlopen(url, timeout=8) as response: return int(response.status) except urllib.error.HTTPError as exc: return int(exc.code) except Exception: return None def evaluate_edge_receipt( vantage_host: str | None, statuses: dict[str, int | None], ) -> list[str]: if not vantage_host: return ["edge_production_vantage_unavailable"] failures: list[str] = [] for endpoint in EDGE_LIVENESS_URLS: if statuses.get(endpoint) != 200: failures.append( f"edge_{endpoint}_liveness_unavailable:{statuses.get(endpoint)}" ) return failures def _edge_status_from_production_vantage() -> tuple[str | None, dict[str, int | None]]: for host in EDGE_VANTAGE_HOSTS: statuses: dict[str, int | None] = {} vantage_reachable = True for endpoint, url in EDGE_LIVENESS_URLS.items(): completed = subprocess.run( [ "ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-o", "ConnectionAttempts=1", "-o", "ServerAliveInterval=3", "-o", "ServerAliveCountMax=1", f"wooo@{host}", ( "curl -sS -o /dev/null -w '%{http_code}' " f"--connect-timeout 3 --max-time 6 {url}" ), ], check=False, capture_output=True, text=True, timeout=12, ) if completed.returncode == 255: vantage_reachable = False break try: statuses[endpoint] = int(completed.stdout.strip()) except ValueError: statuses[endpoint] = None if vantage_reachable: return host, statuses return None, {} def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--live", action="store_true") parser.add_argument("--sample-seconds", type=int, default=35) parser.add_argument("--max-pod-cpu-millicores", type=int, default=900) args = parser.parse_args() source = _source_deployment() failures = evaluate_source_contract(source) edge_config = (ROOT / "ops" / "nginx" / "awoooi.wooo.work.conf").read_text( encoding="utf-8" ) failures.extend(evaluate_routing_contract(_source_service(), edge_config)) result: dict[str, Any] = { "schema_version": "awoooi_api_liveness_stabilization_verifier_v1", "mode": "live" if args.live else "source", "rollback": {"target_replicas": 1, "command": ROLLBACK_COMMAND}, "routing_truth": { "kubernetes_service": "awoooi-api-svc:8000", "node_port": 32334, "edge_vip": "192.168.0.125:32334", "public_liveness": PUBLIC_LIVENESS_URL, "edge_probe_vantage": "production_host_120_then_110", "local_edge_probe_classification": "vantage_unqualified", "edge_reachability_is_separate_from_replica_rollback": True, }, } try: if args.live: deployment_before = _kubectl_json( "-n", NAMESPACE, "get", "deployment", DEPLOYMENT, "-o", "json" ) receipt = _kubectl_json( "-n", NAMESPACE, "get", "configmap", BOUNDARY_RECEIPT, "-o", "json" ).get("data") or {} pods_before = _kubectl_json( "-n", NAMESPACE, "get", "pods", "-l", f"app={DEPLOYMENT}", "-o", "json" ).get("items") or [] restart_before = _restart_counts(pods_before) time.sleep(max(0, args.sample_seconds)) deployment_after = _kubectl_json( "-n", NAMESPACE, "get", "deployment", DEPLOYMENT, "-o", "json" ) pods_after = _kubectl_json( "-n", NAMESPACE, "get", "pods", "-l", f"app={DEPLOYMENT}", "-o", "json" ).get("items") or [] failures = evaluate_live_state( deployment_after, receipt, pods_after, restart_before, _restart_counts(pods_after), _cpu_samples(), args.max_pod_cpu_millicores, ) result["sample_seconds"] = max(0, args.sample_seconds) desired_before = (deployment_before.get("spec") or {}).get("replicas") desired_after = (deployment_after.get("spec") or {}).get("replicas") result["desired_replicas_before"] = desired_before result["desired_replicas_after"] = desired_after if desired_before != desired_after: failures.append( f"live_desired_replica_drift:{desired_before}->{desired_after}" ) public_status = _http_status(PUBLIC_LIVENESS_URL) local_edge_vip_status = _http_status(EDGE_VIP_LIVENESS_URL) edge_vantage, edge_statuses = _edge_status_from_production_vantage() result["routing_truth"]["public_status"] = public_status result["routing_truth"]["local_edge_vip_status"] = local_edge_vip_status result["routing_truth"]["qualified_vantage_host"] = edge_vantage result["routing_truth"]["qualified_edge_statuses"] = edge_statuses if public_status != 200: failures.append(f"public_liveness_unavailable:{public_status}") failures.extend(evaluate_edge_receipt(edge_vantage, edge_statuses)) except Exception as exc: # fail closed without leaking command output failures.append(f"verifier_error:{type(exc).__name__}") result["status"] = "verified" if not failures else "blocked_with_safe_rollback" result["failures"] = failures print(json.dumps(result, ensure_ascii=False, sort_keys=True)) return 0 if not failures else 1 if __name__ == "__main__": raise SystemExit(main())