Files
go-omni/scripts/verifyHTTP2.py

96 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run the opt-in HTTP/2 TLS soak in a pinned Linux Go image."""
import argparse
import math
from pathlib import Path
import shutil
import signal
import subprocess
import uuid
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--image", default="golang:1.25")
parser.add_argument("--goproxy", default="", help="optional Go module proxy passed to the container")
parser.add_argument("--duration", help="soak duration (default: 30m, or 60s with --race)")
parser.add_argument("--timeout", default="35m", help="go test timeout (default: 35m)")
parser.add_argument("--gomaxprocs", type=int, default=2)
parser.add_argument("--cpus", type=float, default=2, help="container CPU quota (default: 2)")
parser.add_argument("--memory-mb", type=int, default=2048, help="container memory cap, with no swap (default: 2048 MiB)")
parser.add_argument("--pids-limit", type=int, default=256, help="container process/thread cap (default: 256)")
parser.add_argument("--wall-timeout", type=int, default=2400, help="total time limit including build/download, in seconds (default: 2400)")
parser.add_argument(
"--race",
action="store_true",
help="enable the race detector for short concurrency checks; its memory overhead is not a production baseline",
)
args = parser.parse_args()
if args.duration is None:
args.duration = "60s" if args.race else "30m"
if not args.duration or not args.timeout or args.gomaxprocs < 1:
parser.error("--duration and --timeout must be non-empty and --gomaxprocs must be positive")
if not math.isfinite(args.cpus) or args.cpus <= 0 or args.memory_mb < 128 or args.pids_limit < 16 or args.wall_timeout < 1:
parser.error("--cpus and --wall-timeout must be positive; --memory-mb >= 128 and --pids-limit >= 16 are required")
docker = shutil.which("docker")
if docker is None:
raise SystemExit("docker is required for HTTP/2 Linux soak")
root = Path(__file__).resolve().parent.parent
name = "go-omni-http2-soak-" + uuid.uuid4().hex[:12]
command = [
docker, "run", "--rm", "--pull=missing", "--name", name,
"--cpus", str(args.cpus), "--memory", f"{args.memory_mb}m",
"--memory-swap", f"{args.memory_mb}m", "--pids-limit", str(args.pids_limit),
"--log-opt", "max-size=10m", "--log-opt", "max-file=1",
"-v", f"{root}:/workspace:ro", "-w", "/workspace",
"-e", "GOWORK=off", "-e", f"GOMAXPROCS={args.gomaxprocs}",
"-e", f"GO_OMNI_HTTP2_SOAK_DURATION={args.duration}",
]
if args.goproxy:
command += ["-e", f"GOPROXY={args.goproxy}"]
command += [args.image, "go", "test"]
if args.race:
command.append("-race")
command += [
"-v", "-timeout", args.timeout, "./transport/http2", "-run", "^TestTLSSoak$", "-count=1",
]
print(f"HTTP/2 Linux soak image: {args.image}", flush=True)
print(f"HTTP/2 TLS soak duration: {args.duration}", flush=True)
print(f"HTTP/2 TLS soak mode: {'race' if args.race else 'regular'}", flush=True)
print(f"HTTP/2 soak container: {name}; limits: {args.cpus:g} CPUs, {args.memory_mb} MiB, {args.pids_limit} tasks", flush=True)
def interrupt(signum, frame):
raise KeyboardInterrupt
previous_handlers = {
sig: signal.signal(sig, interrupt) for sig in (signal.SIGTERM, signal.SIGHUP)
}
try:
result = subprocess.run(command, cwd=root, timeout=args.wall_timeout)
except subprocess.TimeoutExpired:
raise SystemExit("HTTP/2 Linux soak exceeded --wall-timeout; stopping its container")
except KeyboardInterrupt:
raise SystemExit("HTTP/2 Linux soak interrupted; stopping its container")
finally:
for sig, handler in previous_handlers.items():
signal.signal(sig, handler)
# Only this run's unique container is targeted, including on timeout or
# a disconnected SSH session. Do not prune other jobs.
try:
cleanup = subprocess.run(
[docker, "rm", "--force", name], capture_output=True, text=True, timeout=15,
)
if cleanup.returncode != 0 and "No such container" not in cleanup.stderr:
raise SystemExit(f"Could not clean up {name}: {cleanup.stderr.strip()}")
except subprocess.TimeoutExpired:
raise SystemExit(f"Cleanup timed out; inspect container {name} before another run")
if result.returncode != 0:
raise SystemExit(f"HTTP/2 Linux soak failed with exit code {result.returncode}")
print("PASS HTTP/2 TLS soak", flush=True)
if __name__ == "__main__":
main()