feat(e2e): add distributed E2E test framework with parametric traffic generation
Add run-e2e-test.sh with CLI parameters (--hits, --http-ratio, --dns, --tls, --src-ips, --keep-analysis, --up) for configurable traffic generation. Traffic runs from VM endpoints with multiple source IPs (alias IPs on eth0) to produce distinct sessions for the ML pipeline. Fix curl TLS flags (--tlsv1.2 instead of --tls-v1-2), skip redundant local verification in distributed mode, and fix dashboard is_available() cache that never retried after ClickHouse recovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -4,15 +4,28 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from backend.database import ClickHouseUnavailable, is_available
|
||||
from backend.routes.api import router as api_router
|
||||
from backend.routes.pages import router as pages_router
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
||||
|
||||
_templates = Jinja2Templates(directory="backend/templates")
|
||||
|
||||
_PAGE_MAP = {
|
||||
"/": "overview", "/detections": "detections", "/scores": "scores",
|
||||
"/traffic": "traffic", "/classify": "classify", "/features": "features",
|
||||
"/models": "models", "/network": "network", "/campaigns": "campaigns",
|
||||
"/tactics": "tactics", "/reflists": "reflists", "/fleet": "fleet",
|
||||
"/health": "health", "/browsers": "browsers", "/fingerprints": "fingerprints",
|
||||
}
|
||||
|
||||
app = FastAPI(title="JA4 SOC Dashboard", version="1.0.0")
|
||||
|
||||
# CORS — allow all origins for dashboard access
|
||||
@ -24,6 +37,29 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(ClickHouseUnavailable)
|
||||
async def ch_unavailable_handler(request: Request, exc: ClickHouseUnavailable):
|
||||
"""Return 503 for API calls, render degraded pages for HTML requests."""
|
||||
accept = request.headers.get("accept", "")
|
||||
path = request.url.path
|
||||
|
||||
# If the client expects JSON (API call), return 503 JSON
|
||||
if "application/json" in accept or path.startswith("/api/"):
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
content={"detail": "ClickHouse unavailable", "error": str(exc)},
|
||||
)
|
||||
|
||||
# For HTML pages, render the template with ch_available=False
|
||||
page_name = _PAGE_MAP.get(path, "overview")
|
||||
return _templates.TemplateResponse(
|
||||
f"{page_name}.html",
|
||||
{"request": request, "active_page": page_name, "ch_available": False},
|
||||
status_code=503,
|
||||
)
|
||||
|
||||
|
||||
# Static assets
|
||||
app.mount("/static", StaticFiles(directory="backend/static"), name="static")
|
||||
|
||||
@ -32,6 +68,7 @@ app.include_router(api_router)
|
||||
app.include_router(pages_router)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
@app.get("/api/healthcheck")
|
||||
async def healthcheck():
|
||||
ch = is_available()
|
||||
return {"status": "ok" if ch else "degraded", "clickhouse": "up" if ch else "down"}
|
||||
|
||||
Reference in New Issue
Block a user