# services/hermes/model-gate-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: hermes-model-gate namespace: hermes data: model_gate.py: | #!/usr/bin/env python3 """Fail-closed proxy that admits local inference only while Hermes owns titan-24.""" from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json import os from pathlib import Path import ssl import threading import time from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen LISTEN_HOST = os.environ.get("LISTEN_HOST", "0.0.0.0") LISTEN_PORT = int(os.environ.get("LISTEN_PORT", "8080")) UPSTREAM_URL = os.environ.get("UPSTREAM_URL", "http://hermes-ollama.hermes.svc.cluster.local:11434").rstrip("/") LEASE_NAMESPACE = os.environ.get("LEASE_NAMESPACE", "hermes") LEASE_NAME = os.environ.get("LEASE_NAME", "titan-24-gpu-owner") CACHE_TTL_SEC = float(os.environ.get("LEASE_CACHE_TTL_SEC", "1")) API_HOST = os.environ.get("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc") API_PORT = os.environ.get("KUBERNETES_SERVICE_PORT_HTTPS", "443") TOKEN_PATH = Path("/var/run/secrets/kubernetes.io/serviceaccount/token") CA_PATH = Path("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt") LEASE_URL = ( f"https://{API_HOST}:{API_PORT}/apis/coordination.k8s.io/v1/" f"namespaces/{LEASE_NAMESPACE}/leases/{LEASE_NAME}" ) _cache_lock = threading.Lock() _cached_owner = "unavailable" _cached_at = 0.0 def _lease_owner() -> str: """Return the current owner, failing closed when Kubernetes is unavailable.""" global _cached_at, _cached_owner now = time.monotonic() with _cache_lock: if now - _cached_at < CACHE_TTL_SEC: return _cached_owner try: token = TOKEN_PATH.read_text(encoding="utf-8").strip() request = Request(LEASE_URL, headers={"Authorization": f"Bearer {token}"}) context = ssl.create_default_context(cafile=str(CA_PATH)) with urlopen(request, timeout=3, context=context) as response: payload = json.load(response) owner = str((payload.get("spec") or {}).get("holderIdentity") or "unavailable").strip() except Exception: owner = "unavailable" _cached_owner = owner _cached_at = now return owner class Handler(BaseHTTPRequestHandler): """Proxy local model traffic while exposing health and ownership status.""" protocol_version = "HTTP/1.1" def _json(self, status: int, payload: dict) -> None: body = json.dumps(payload, separators=(",", ":")).encode("utf-8") self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(body) def _local_allowed(self) -> tuple[bool, str]: owner = _lease_owner() return owner == "hermes", owner def _proxy(self) -> None: allowed, owner = self._local_allowed() if not allowed: self._json( 503, { "error": { "message": f"local GPU inference unavailable while titan-24 owner is {owner}", "type": "server_error", }, "gpu_owner": owner, "fallback_required": True, }, ) return length = int(self.headers.get("Content-Length", "0") or 0) body = self.rfile.read(length) if length else None headers = {"Content-Type": self.headers.get("Content-Type", "application/json")} if self.headers.get("Accept"): headers["Accept"] = self.headers["Accept"] request = Request(f"{UPSTREAM_URL}{self.path}", data=body, headers=headers, method=self.command) try: response = urlopen(request, timeout=1800) except HTTPError as exc: response = exc except (TimeoutError, URLError) as exc: self._json(503, {"error": {"message": f"local model upstream unavailable: {exc}", "type": "server_error"}}) return self.send_response(response.status) content_type = response.headers.get("Content-Type") if content_type: self.send_header("Content-Type", content_type) content_length = response.headers.get("Content-Length") if content_length: self.send_header("Content-Length", content_length) else: self.send_header("Connection", "close") self.close_connection = True self.send_header("Cache-Control", "no-store") self.end_headers() while True: chunk = response.read(65536) if not chunk: break self.wfile.write(chunk) self.wfile.flush() response.close() def do_GET(self) -> None: if self.path == "/healthz": self._json(200, {"status": "ok"}) return if self.path == "/gate/status": allowed, owner = self._local_allowed() self._json(200, {"gpu_owner": owner, "local_inference_allowed": allowed}) return self._proxy() def do_POST(self) -> None: self._proxy() def log_message(self, format_string: str, *args) -> None: print(f"model-gate {self.address_string()} {format_string % args}", flush=True) if __name__ == "__main__": ThreadingHTTPServer((LISTEN_HOST, LISTEN_PORT), Handler).serve_forever()