scripts/fixtures/sandbox-environment-seed.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""Small pinned-image QA service: SQLite JSON/TCP 5432 and HTTP backend 8080.
This deliberately uses a bounded fixture protocol, not PostgreSQL. Auto-role
selection is only for a fresh dedicated /24 with Debug/Test/Backend/DB leases
in that order (.2/.3/.4/.5). Production discovery never relies on these offsets.
"""
import argparse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import ipaddress
import json
from pathlib import Path
import socket
import socketserver
import sqlite3
SEED = "firecrab-environment-seed-414-v1"
LIMIT = 8192
def initialize(path):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
with sqlite3.connect(path) as database:
database.execute("CREATE TABLE IF NOT EXISTS seed(identity TEXT PRIMARY KEY)")
database.execute("INSERT OR IGNORE INTO seed(identity) VALUES(?)", (SEED,))
database.execute("CREATE TABLE IF NOT EXISTS writes(key TEXT PRIMARY KEY,value TEXT NOT NULL)")
def database_request(path, request):
if not isinstance(request, dict):
raise ValueError("request must be an object")
operation = request.get("operation")
if operation not in ("read", "write"):
raise ValueError("unsupported operation")
with sqlite3.connect(path, timeout=3) as database:
if operation == "write":
key, value = request.get("key"), request.get("value")
if not isinstance(key, str) or not 1 <= len(key) <= 64 or not all(char.isalnum() or char in "_.-" for char in key):
raise ValueError("invalid key")
if not isinstance(value, str) or len(value.encode()) > 4096:
raise ValueError("invalid value")
database.execute("INSERT INTO writes(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", (key, value))
return {"seed": database.execute("SELECT identity FROM seed ORDER BY identity").fetchall(),
"values": dict(database.execute("SELECT key,value FROM writes ORDER BY key"))}
class DatabaseHandler(socketserver.StreamRequestHandler):
def handle(self):
self.request.settimeout(3)
try:
payload = self.rfile.readline(LIMIT + 1)
if len(payload) > LIMIT or not payload.endswith(b"\n"):
raise ValueError("request limit exceeded")
reply = database_request(self.server.database_path, json.loads(payload))
self.wfile.write(json.dumps(reply, separators=(",", ":")).encode() + b"\n")
except (ValueError, sqlite3.Error, TimeoutError):
self.wfile.write(b'{"error":"fixture_request_failed"}\n')
class DatabaseServer(socketserver.ThreadingTCPServer):
allow_reuse_address = True
daemon_threads = True
def __init__(self, address, path):
initialize(path)
self.database_path = path
super().__init__(address, DatabaseHandler)
def remote_database(host, port, request):
payload = json.dumps(request, separators=(",", ":")).encode() + b"\n"
if len(payload) > LIMIT:
raise ValueError("request limit exceeded")
with socket.create_connection((host, port), timeout=3) as connection:
connection.sendall(payload)
with connection.makefile("rb") as stream:
response = stream.readline(LIMIT + 1)
if len(response) > LIMIT or not response.endswith(b"\n"):
raise ValueError("response limit exceeded")
return json.loads(response)
class BackendHandler(BaseHTTPRequestHandler):
def log_message(self, *_):
pass
def reply(self, operation):
try:
body = remote_database(self.server.database_host, self.server.database_port, operation)
status = 200 if "error" not in body else 503
except (OSError, ValueError):
status, body = 503, {"error": "fixture_database_unavailable"}
payload = json.dumps(body, separators=(",", ":")).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
def do_GET(self):
if self.path not in ("/health", "/seed"):
self.send_error(404)
return
self.reply({"operation": "read"})
def do_POST(self):
if self.path != "/write":
self.send_error(404)
return
try:
length = int(self.headers.get("Content-Length", "0"))
if not 1 <= length <= LIMIT:
raise ValueError("request limit exceeded")
body = json.loads(self.rfile.read(length))
self.reply({"operation": "write", "key": body["key"], "value": body["value"]})
except (ValueError, KeyError, TypeError):
self.send_error(400)
class BackendServer(ThreadingHTTPServer):
daemon_threads = True
def __init__(self, address, database_host, database_port):
self.database_host, self.database_port = database_host, database_port
super().__init__(address, BackendHandler)
def auto_role():
# UDP connect only selects a local route. It does not send an Internet packet.
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as route:
route.connect(("192.0.2.1", 9))
address = ipaddress.IPv4Address(route.getsockname()[0])
role = {2: "idle", 3: "idle", 4: "backend", 5: "db"}.get(address.packed[-1])
if role is None:
raise ValueError("auto fixture requires fresh /24 leases .2/.3/.4/.5")
database_host = str(ipaddress.IPv4Address((int(address) & 0xffffff00) + 5))
return role, database_host
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--role", choices=("db", "backend", "auto"), default="auto")
parser.add_argument("--listen", default="0.0.0.0")
parser.add_argument("--database", default="/var/lib/firecrab-environment-seed/state.sqlite")
parser.add_argument("--database-host")
parser.add_argument("--database-port", type=int, default=5432)
parser.add_argument("--http-port", type=int, default=8080)
parser.add_argument("--init-only", action="store_true")
args = parser.parse_args()
if args.init_only:
initialize(args.database)
return
role, database_host = auto_role() if args.role == "auto" else (args.role, args.database_host)
if role == "idle":
return
if role == "db":
server = DatabaseServer((args.listen, args.database_port), args.database)
else:
if not database_host:
parser.error("backend requires --database-host")
server = BackendServer((args.listen, args.http_port), database_host, args.database_port)
with server:
server.serve_forever()
if __name__ == "__main__":
main()