#!/bin/sh # Smoke run: two fake upstreams, one crossbar, real HTTP. Prints "smoke: ok" or fails. # Needs: bin/crossbar and bin/fakeupstream (make build), curl. set -eu cd "$(dirname "$0")/.." tmp=$(mktemp -d); trap 'kill $pids 2>/dev/null; rm -rf "$tmp"' EXIT INT TERM pids="" bin/fakeupstream -listen 127.0.0.1:18081 -name alpha -models ornith-1.5-35b-a3b,small-9b -down-file "$tmp/alpha.down" >"$tmp/alpha.log" 2>&1 & pids="$pids $!" bin/fakeupstream -listen 127.0.0.1:18082 -name beta -models ornith-1.5-35b-a3b -down-file "$tmp/beta.down" >"$tmp/beta.log" 2>&1 & pids="$pids $!" bin/crossbar -config example.toml >"$tmp/crossbar.log" 2>&1 & pids="$pids $!" sleep 1.5 fail() { echo "smoke: FAIL: $*" >&2; echo "--- crossbar.log"; cat "$tmp/crossbar.log"; exit 1; } base=http://127.0.0.1:17777 h=$(curl -s -o /dev/null -w '%{http_code} %header{X-Crossbar-Host}' "$base/opencode-a/v1/models") [ "$h" = "200 alpha" ] || fail "opencode-a should go to alpha, got '$h'" h=$(curl -s -o /dev/null -w '%{http_code} %header{X-Crossbar-Host}' "$base/hermes-x/v1/models") [ "$h" = "200 beta" ] || fail "hermes-x should go to beta, got '$h'" h=$(curl -s -o /dev/null -w '%{http_code}' "$base/nope/v1/models") [ "$h" = "404" ] || fail "unknown route should be 404, got '$h'" touch "$tmp/alpha.down"; sleep 2.5 # poll_interval is 1s in example.toml h=$(curl -s -o /dev/null -w '%{http_code} %header{X-Crossbar-Host}' "$base/opencode-a/v1/models") [ "$h" = "200 beta" ] || fail "with alpha down, opencode-a should fail over to beta, got '$h'" curl -s "$base/_crossbar/hosts" | grep -q '"alpha":{"healthy":false' || fail "/_crossbar/hosts does not show alpha unhealthy: $(curl -s $base/_crossbar/hosts)" rm "$tmp/alpha.down"; sleep 3.5 # recovery needs two good polls h=$(curl -s -o /dev/null -w '%header{X-Crossbar-Host}' "$base/opencode-a/v1/models") [ "$h" = "alpha" ] || fail "alpha should be back after two good polls, got '$h'" # Streaming: five chunks 200 ms apart must arrive over >= 0.6 s, not all at once at the end. start=$(date +%s%N) first="" curl -sN -X POST -H 'Content-Type: application/json' -d '{"model":"ornith-1.5-35b-a3b","stream":true,"messages":[]}' \ "$base/opencode-a/v1/chat/completions" | while IFS= read -r line; do [ -n "$line" ] || continue now=$(date +%s%N); echo "$(( (now - start) / 1000000 )) $line" done > "$tmp/stream.txt" firstms=$(head -1 "$tmp/stream.txt" | cut -d' ' -f1); lastms=$(tail -1 "$tmp/stream.txt" | cut -d' ' -f1) [ -n "$firstms" ] && [ "$((lastms - firstms))" -ge 600 ] || fail "stream arrived in one burst (first ${firstms:-?} ms, last ${lastms:-?} ms): $(cat "$tmp/stream.txt")" grep -q 'DONE' "$tmp/stream.txt" || fail "stream did not end with [DONE]" grep -q 'route=opencode-a host=alpha' "$tmp/crossbar.log" || fail "no request log line" echo "smoke: ok (stream spread $((lastms - firstms)) ms)"