Files
Nexus/tests/quick_load.py
T
Your Name 752a24497c feat: Phase 2 security audit fixes + script execution platform
Security (P0/P1/P2):
- WebSSH: dedicated short-lived token, server_id binding, 4003 close code
- Remove /api/agent/exec from control plane (RCE surface eliminated)
- Global JWT middleware (JwtAuthMiddleware) with install-mode bypass
- decrypt_value: failure returns None, never leaks ciphertext
- Telegram: sanitize_external_message strips sensitive fields
- Agent auth: startup enforces non-empty API key, compare_digest
- Credentials: password_set bool flag, no plaintext in API responses
- audit_log: writes admin_username + ip_address on all CUD ops
- Install lock: all /api/install/* except GET /status return 403 post-install
- WebSocket dedup: publish once, subscribers deliver locally

Script execution platform:
- script_jobs.py / script_job_callback.py: async batching and callback
- script_execution_store.py / script_callback_rate.py: Redis-backed state
- script_execution_flush.py: background flusher to MySQL
- scripts.html / script-executions.html: full execution UI
- agent_url.py: centralised URL builder

Frontend:
- All 13 pages migrated from CDN to /app/vendor/ (no external deps)
- vendor/: alpinejs, tailwindcss-browser, xterm, xterm-addon-*, qrious
- Dashboard WebSocket real-time alerts; 8h JWT session timeout

Tests:
- test_security_unit.py: 15 unit tests (JWT, sanitize, vendor, install 403)
- test_api.py: env-configurable admin credentials

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:26:56 +08:00

87 lines
2.5 KiB
Python

"""轻量级压测 — health + stats
用法:
NEXUS_API_KEY=your-key python tests/quick_load.py 50 30
NEXUS_TEST_BASE=http://127.0.0.1:8600 (可选)
"""
import asyncio
import httpx
import os
import sys
import time
BASE = os.environ.get("NEXUS_TEST_BASE", "http://127.0.0.1:8600")
API_KEY = os.environ.get("NEXUS_API_KEY", "")
oks = [0]
fails = [0]
times = []
async def health(c, d):
end = time.time() + d
while time.time() < end:
t0 = time.perf_counter()
try:
r = await c.get(f"{BASE}/health", timeout=5)
if r.status_code == 200:
oks[0] += 1
else:
fails[0] += 1
except Exception:
fails[0] += 1
times.append(time.perf_counter() - t0)
await asyncio.sleep(0.001)
async def stats_w(c, d):
if not API_KEY:
return
end = time.time() + d
while time.time() < end:
t0 = time.perf_counter()
try:
r = await c.get(
f"{BASE}/api/servers/stats",
headers={"X-API-Key": API_KEY},
timeout=10,
)
if r.status_code == 200:
oks[0] += 1
else:
fails[0] += 1
except Exception:
fails[0] += 1
times.append(time.perf_counter() - t0)
await asyncio.sleep(0.05)
async def main(n, d):
if not API_KEY:
print("WARN: NEXUS_API_KEY unset — stats workers skipped", file=sys.stderr)
async with httpx.AsyncClient(limits=httpx.Limits(max_connections=n + 100)) as c:
tasks = [health(c, d) for _ in range(int(n * 0.7))]
if API_KEY:
tasks += [stats_w(c, d) for _ in range(max(1, int(n * 0.3)))]
t0 = time.time()
await asyncio.gather(*tasks, return_exceptions=True)
elapsed = time.time() - t0
total = oks[0] + fails[0]
if not total:
print("No requests completed")
return
t = sorted(times)
nt = len(t)
print(
f"并发={n} | RPS={total/elapsed:.0f} | P50={t[int(nt*0.5)]*1000:.1f}ms | "
f"P95={t[min(int(nt*0.95),nt-1)]*1000:.1f}ms | "
f"P99={t[min(int(nt*0.99),nt-1)]*1000:.1f}ms | "
f"ok={oks[0]} fail={fails[0]} err={(fails[0]/total*100):.1f}%"
)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: NEXUS_API_KEY=... python tests/quick_load.py <concurrency> <seconds>")
sys.exit(1)
asyncio.run(main(int(sys.argv[1]), int(sys.argv[2])))