forked from luopingyi/xc_agent_platform_demo
Phase1 skeleton: replace demo main.py with src.server contract
- Overlay ModelHub XC Phase1 (health/status, STRATEGY_ID gate, dry-run) - Dockerfile CMD -> python -m src.server; FROM stays platform TCR base - Remove demo main.py and requirements.txt (stdlib only) - Add tests/, scripts/, docs/, Makefile, config/examples (no secrets)
This commit is contained in:
100
scripts/smoke_health.py
Executable file
100
scripts/smoke_health.py
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Smoke: GET /health must return 200 {"status":"ok"}.
|
||||
|
||||
Usage:
|
||||
# Against already-running server:
|
||||
SMOKE_BASE_URL=http://127.0.0.1:8080 python3 scripts/smoke_health.py
|
||||
|
||||
# Or let this script start a local server (needs STRATEGY_ID):
|
||||
STRATEGY_ID=test python3 scripts/smoke_health.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _get(url: str, timeout: float = 2.0) -> tuple[int, dict]:
|
||||
req = urllib.request.Request(url, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
body = resp.read().decode("utf-8")
|
||||
return resp.status, json.loads(body)
|
||||
|
||||
|
||||
def _wait_health(base: str, attempts: int = 30) -> None:
|
||||
url = base.rstrip("/") + "/health"
|
||||
last_err: Exception | None = None
|
||||
for _ in range(attempts):
|
||||
try:
|
||||
code, data = _get(url)
|
||||
if code == 200 and data.get("status") == "ok":
|
||||
print(f"OK: GET {url} -> {code} {data}")
|
||||
return
|
||||
raise AssertionError(f"unexpected response: {code} {data}")
|
||||
except (urllib.error.URLError, ConnectionError, AssertionError, TimeoutError) as exc:
|
||||
last_err = exc
|
||||
time.sleep(0.2)
|
||||
raise SystemExit(f"smoke-health failed: {last_err}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
external = os.environ.get("SMOKE_BASE_URL", "").strip()
|
||||
if external:
|
||||
_wait_health(external)
|
||||
# optional status check
|
||||
try:
|
||||
code, data = _get(external.rstrip("/") + "/status")
|
||||
print(f"OK: GET /status -> {code} {data}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"WARN: /status check skipped: {exc}", file=sys.stderr)
|
||||
return
|
||||
|
||||
if not os.environ.get("STRATEGY_ID", "").strip():
|
||||
os.environ["STRATEGY_ID"] = "smoke-test-strategy"
|
||||
|
||||
port = os.environ.get("SMOKE_PORT", "18080")
|
||||
env = os.environ.copy()
|
||||
env["PORT"] = port
|
||||
env["HOST"] = "127.0.0.1"
|
||||
env["STRATEGY_ID"] = env.get("STRATEGY_ID") or "smoke-test-strategy"
|
||||
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, "-m", "src.server"],
|
||||
cwd=str(ROOT),
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
try:
|
||||
_wait_health(f"http://127.0.0.1:{port}")
|
||||
code, data = _get(f"http://127.0.0.1:{port}/status")
|
||||
print(f"OK: GET /status -> {code} {data}")
|
||||
assert "strategy_id_present" in data
|
||||
assert data["strategy_id_present"] is True
|
||||
# never assert on secret values — only booleans
|
||||
finally:
|
||||
proc.send_signal(signal.SIGTERM)
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.wait(timeout=2)
|
||||
if proc.returncode not in (0, -signal.SIGTERM, signal.SIGTERM):
|
||||
# 0 or terminated by SIGTERM is fine
|
||||
err = (proc.stderr.read() or b"").decode("utf-8", errors="replace")
|
||||
if proc.returncode is not None and proc.returncode > 0:
|
||||
print(f"WARN: server exit code {proc.returncode}: {err}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user