feat(scada): add calibrated monitoring and incident recovery
This commit is contained in:
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
"""Spawn-safe HTTP/SSE fixture, controlled only through a test-owned directory."""
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import StreamingResponse
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
directory = Path(os.environ["SCADA_HANDOFF_FIXTURE_DIR"])
|
||||
pid = os.getpid()
|
||||
app.state.directory = directory
|
||||
app.state.version = (directory / "version").read_text().strip()
|
||||
mode = (directory / "startup-mode").read_text().strip()
|
||||
(directory / f"started-{pid}").write_text(app.state.version)
|
||||
if mode == "fail":
|
||||
raise RuntimeError("test candidate startup failed")
|
||||
if mode == "delay":
|
||||
await asyncio.sleep(0.3)
|
||||
while mode == "wait" and not (directory / "allow-startup").exists():
|
||||
await asyncio.sleep(0.02)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
(directory / f"stopped-{pid}").write_text(app.state.version)
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
async def readiness_check() -> bool:
|
||||
directory = Path(os.environ["SCADA_HANDOFF_FIXTURE_DIR"])
|
||||
ready = (directory / "readiness").read_text().strip() == "true"
|
||||
(directory / f"checked-{os.getpid()}").write_text(str(ready).lower())
|
||||
await asyncio.sleep(.05)
|
||||
return ready
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"pid": os.getpid(), "version": app.state.version}
|
||||
|
||||
|
||||
@app.get("/events")
|
||||
async def events():
|
||||
async def stream():
|
||||
directory = app.state.directory
|
||||
pid = os.getpid()
|
||||
while not (directory / f"release-{pid}").exists() and not (directory / "release-all").exists():
|
||||
payload = {"pid": pid, "version": app.state.version, "time_ns": time.time_ns()}
|
||||
yield f"data: {json.dumps(payload)}\n\n"
|
||||
await asyncio.sleep(0.025)
|
||||
yield f"event: complete\ndata: {json.dumps({'pid': pid})}\n\n"
|
||||
|
||||
return StreamingResponse(stream(), media_type="text/event-stream")
|
||||
Reference in New Issue
Block a user