149 lines
5.6 KiB
Python
149 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Ordinal-local signing boundary for a model-facing Hermes worker."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import threading
|
|
import urllib.error
|
|
import urllib.request
|
|
from http.server import BaseHTTPRequestHandler
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from execution_pool_protocol import (
|
|
MAX_WIRE_BYTES,
|
|
BoundedHTTPServer,
|
|
ProtocolError,
|
|
canonical_json,
|
|
parse_wire,
|
|
read_key,
|
|
sign_envelope,
|
|
verify_envelope,
|
|
)
|
|
|
|
|
|
KEY_PATH = Path(os.environ.get("HERMES_EXECUTION_POOL_KEY_FILE", "/pool-access/execution-pool-key"))
|
|
COORDINATOR = os.environ.get(
|
|
"HERMES_EXECUTION_POOL_URL", "http://hermes-execution-pool.hermes.svc.cluster.local:9007"
|
|
).rstrip("/")
|
|
ORDINAL = int(os.environ.get("HERMES_WORKER_ORDINAL", "-1"))
|
|
PORT = int(os.environ.get("HERMES_EXECUTION_CLIENT_PORT", "9009"))
|
|
|
|
|
|
def _binding(value: dict[str, Any]) -> dict[str, Any]:
|
|
return {name: value[name] for name in (
|
|
"board", "task_id", "run_id", "worker_ordinal", "attempt"
|
|
)}
|
|
|
|
|
|
class ClientBoundary:
|
|
"""Hold the master key and fence local requests to the current ordinal run."""
|
|
|
|
def __init__(self, key: bytes):
|
|
self.key = key
|
|
self.current: dict[str, Any] | None = None
|
|
self.lock = threading.RLock()
|
|
|
|
def _post(self, path: str, envelope: dict[str, Any]) -> dict[str, Any]:
|
|
request = urllib.request.Request(
|
|
COORDINATOR + path, data=canonical_json(envelope), method="POST",
|
|
headers={"Content-Type": "application/json", "Cache-Control": "no-store"},
|
|
)
|
|
with urllib.request.urlopen(request, timeout=60) as response:
|
|
body = response.read(MAX_WIRE_BYTES + 1)
|
|
if len(body) > MAX_WIRE_BYTES:
|
|
raise ProtocolError("coordinator response exceeds the wire limit")
|
|
value = json.loads(body)
|
|
return verify_envelope(self.key, value)
|
|
|
|
def poll(self) -> dict[str, Any]:
|
|
poll_binding = {
|
|
"board": "", "task_id": "", "run_id": "",
|
|
"worker_ordinal": ORDINAL, "attempt": 0,
|
|
}
|
|
response = self._post(
|
|
"/v1/poll", sign_envelope(self.key, "poll", poll_binding, {"ready": True})
|
|
)
|
|
with self.lock:
|
|
if response["kind"] == "ack":
|
|
self.current = None
|
|
return {"assignment": None}
|
|
if response["kind"] != "assignment" or response["worker_ordinal"] != ORDINAL:
|
|
raise ProtocolError("coordinator returned a foreign assignment")
|
|
self.current = _binding(response)
|
|
return {"assignment": response}
|
|
|
|
def forward(self, kind: str, request: dict[str, Any]) -> dict[str, Any]:
|
|
supplied = request.get("binding")
|
|
payload = request.get("payload")
|
|
if not isinstance(supplied, dict) or not isinstance(payload, dict):
|
|
raise ProtocolError("local request binding and payload must be objects")
|
|
with self.lock:
|
|
if self.current is None or supplied != self.current:
|
|
raise ProtocolError("local request does not own the current assignment")
|
|
binding = dict(self.current)
|
|
response = self._post(
|
|
f"/v1/{kind}", sign_envelope(self.key, kind, binding, payload)
|
|
)
|
|
if response["kind"] != "ack" or _binding(response) != binding:
|
|
raise ProtocolError("coordinator acknowledgement binding changed")
|
|
if kind == "result" and response["payload"].get("accepted"):
|
|
with self.lock:
|
|
self.current = None
|
|
return {"ack": response["payload"]}
|
|
|
|
|
|
def handler_factory(boundary: ClientBoundary) -> type[BaseHTTPRequestHandler]:
|
|
class Handler(BaseHTTPRequestHandler):
|
|
server_version = "hermes-execution-client/1"
|
|
|
|
def _reply(self, status: int, value: dict[str, Any]) -> None:
|
|
body = canonical_json(value)
|
|
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 do_GET(self) -> None: # noqa: N802
|
|
self._reply(200, {"ready": True}) if self.path == "/ready" else self._reply(404, {"error": "not found"})
|
|
|
|
def do_POST(self) -> None: # noqa: N802
|
|
try:
|
|
length = int(self.headers.get("Content-Length", "0"))
|
|
request = parse_wire(self.rfile.read(length)) if 0 < length <= MAX_WIRE_BYTES else None
|
|
if not request or set(request) - {"operation", "binding", "payload"}:
|
|
raise ProtocolError("invalid local client request")
|
|
operation = str(request.get("operation") or "")
|
|
if operation == "poll":
|
|
result = boundary.poll()
|
|
elif operation in {"heartbeat", "result"}:
|
|
result = boundary.forward(operation, request)
|
|
else:
|
|
raise ProtocolError("unsupported local client operation")
|
|
self._reply(200, result)
|
|
except (ProtocolError, OSError, ValueError, urllib.error.URLError) as error:
|
|
self._reply(409, {"error": str(error)[:2000]})
|
|
|
|
def log_message(self, _format: str, *_arguments: Any) -> None:
|
|
return
|
|
|
|
return Handler
|
|
|
|
|
|
def main() -> int:
|
|
if ORDINAL not in range(3):
|
|
raise SystemExit("HERMES_WORKER_ORDINAL must be 0, 1, or 2")
|
|
key = read_key(KEY_PATH)
|
|
BoundedHTTPServer(
|
|
("0.0.0.0", PORT), handler_factory(ClientBoundary(key)), max_workers=4
|
|
).serve_forever()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|