95 lines
3.2 KiB
YAML
95 lines
3.2 KiB
YAML
|
|
# services/crypto/monerod/status-proxy-configmap.yaml
|
||
|
|
apiVersion: v1
|
||
|
|
kind: ConfigMap
|
||
|
|
metadata:
|
||
|
|
name: monerod-status-proxy
|
||
|
|
namespace: crypto
|
||
|
|
data:
|
||
|
|
status_proxy.py: |
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import re
|
||
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||
|
|
from urllib import error, request
|
||
|
|
|
||
|
|
LOG_FILE = os.environ.get("MONEROD_LOG_FILE", "/tmp/monerod.log")
|
||
|
|
LISTEN_PORT = int(os.environ.get("MONEROD_STATUS_PORT", "18084"))
|
||
|
|
RPC_TIMEOUT_SEC = float(os.environ.get("MONEROD_STATUS_RPC_TIMEOUT_SEC", "1.25"))
|
||
|
|
RPC_URL = os.environ.get("MONEROD_RPC_URL", "http://127.0.0.1:18081/get_info")
|
||
|
|
|
||
|
|
SYNC_RE = re.compile(r"Synced\s+(\d+)/(\d+).*?(\d+)\s+left")
|
||
|
|
CANDIDATE_RE = re.compile(
|
||
|
|
r"candidate:\s+(\d+)\s+->\s+(\d+).*?node is\s+(\d+)\s+blocks",
|
||
|
|
re.IGNORECASE,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _tail_log(path: str, limit: int = 262_144) -> str:
|
||
|
|
try:
|
||
|
|
size = os.path.getsize(path)
|
||
|
|
with open(path, "rb") as handle:
|
||
|
|
handle.seek(max(0, size - limit))
|
||
|
|
return handle.read().decode("utf-8", "ignore")
|
||
|
|
except OSError:
|
||
|
|
return ""
|
||
|
|
|
||
|
|
|
||
|
|
def _sync_from_logs() -> tuple[int, int]:
|
||
|
|
for line in reversed(_tail_log(LOG_FILE).splitlines()):
|
||
|
|
match = SYNC_RE.search(line) or CANDIDATE_RE.search(line)
|
||
|
|
if match:
|
||
|
|
return int(match.group(1)), int(match.group(2))
|
||
|
|
return 0, 0
|
||
|
|
|
||
|
|
|
||
|
|
def _rpc_get_info() -> dict:
|
||
|
|
with request.urlopen(RPC_URL, timeout=RPC_TIMEOUT_SEC) as response:
|
||
|
|
payload = json.loads(response.read().decode("utf-8"))
|
||
|
|
if isinstance(payload, dict):
|
||
|
|
payload.setdefault("rpc_status", "ok")
|
||
|
|
return payload
|
||
|
|
return {"status": "BAD_UPSTREAM", "rpc_status": "bad_upstream"}
|
||
|
|
|
||
|
|
|
||
|
|
def _busy_status() -> dict:
|
||
|
|
height, target_height = _sync_from_logs()
|
||
|
|
return {
|
||
|
|
"nettype": "mainnet",
|
||
|
|
"status": "SYNCING_RPC_BUSY",
|
||
|
|
"height": height,
|
||
|
|
"target_height": target_height,
|
||
|
|
"synchronized": False,
|
||
|
|
"rpc_status": "busy",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class Handler(BaseHTTPRequestHandler):
|
||
|
|
def _json(self, status: int, payload: dict) -> None:
|
||
|
|
body = json.dumps(payload).encode("utf-8")
|
||
|
|
self.send_response(status)
|
||
|
|
self.send_header("Content-Type", "application/json")
|
||
|
|
self.send_header("Content-Length", str(len(body)))
|
||
|
|
self.end_headers()
|
||
|
|
self.wfile.write(body)
|
||
|
|
|
||
|
|
def do_GET(self) -> None: # noqa: N802
|
||
|
|
if self.path == "/healthz":
|
||
|
|
self._json(200, {"status": "ok"})
|
||
|
|
return
|
||
|
|
if self.path.split("?", 1)[0] != "/get_info":
|
||
|
|
self._json(404, {"status": "NOT_FOUND"})
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
self._json(200, _rpc_get_info())
|
||
|
|
except (OSError, TimeoutError, ValueError, error.URLError):
|
||
|
|
self._json(200, _busy_status())
|
||
|
|
|
||
|
|
def log_message(self, fmt: str, *args) -> None:
|
||
|
|
return
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
ThreadingHTTPServer(("", LISTEN_PORT), Handler).serve_forever()
|