Stream Gate 준비 실패에서도 요청 terminal 관측 계약을 지키고, direct provider identity 마이그레이션 누락으로 기존 검증과 운영 설정이 깨지지 않게 한다.
235 lines
6.9 KiB
Bash
Executable file
235 lines
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
TMP_DIR=$(mktemp -d)
|
|
|
|
EDGE_PID=""
|
|
NODE_PID=""
|
|
OLLAMA_PID=""
|
|
EDGE_FD_OPEN=0
|
|
|
|
cleanup() {
|
|
if [ "$EDGE_FD_OPEN" -eq 1 ]; then
|
|
{ echo "/exit" >&3; } 2>/dev/null || true
|
|
exec 3>&- 2>/dev/null || true
|
|
fi
|
|
if [ -n "$EDGE_PID" ]; then wait "$EDGE_PID" 2>/dev/null || true; fi
|
|
if [ -n "$NODE_PID" ]; then kill "$NODE_PID" 2>/dev/null || true; fi
|
|
if [ -n "$OLLAMA_PID" ]; then kill "$OLLAMA_PID" 2>/dev/null || true; fi
|
|
rm -rf "$TMP_DIR" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
PORT=$((31000 + RANDOM % 5000))
|
|
BOOTSTRAP_PORT=$((21000 + RANDOM % 5000))
|
|
OPENAI_PORT=$((36000 + RANDOM % 5000))
|
|
OLLAMA_PORT=$((41000 + RANDOM % 5000))
|
|
EDGE_METRICS_PORT=$((46000 + RANDOM % 5000))
|
|
NODE_METRICS_PORT=$((51000 + RANDOM % 5000))
|
|
MODEL="fake-ollama-model"
|
|
|
|
FAKE_OLLAMA="$TMP_DIR/fake_ollama.go"
|
|
cat > "$FAKE_OLLAMA" <<'EOF'
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
)
|
|
|
|
type chatRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []struct {
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
} `json:"messages"`
|
|
Options struct {
|
|
NumCtx int `json:"num_ctx"`
|
|
} `json:"options"`
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
log.Fatal("missing listen addr")
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/tags", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"models":[{"name":"fake-ollama-model"}]}`))
|
|
})
|
|
mux.HandleFunc("/api/chat", func(w http.ResponseWriter, r *http.Request) {
|
|
var req chatRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Model != "fake-ollama-model" {
|
|
http.Error(w, "unexpected model: "+req.Model, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Options.NumCtx != 262144 {
|
|
http.Error(w, "unexpected num_ctx", http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/x-ndjson")
|
|
_, _ = w.Write([]byte(`{"message":{"role":"assistant","thinking":"thinking..."},"done":false}` + "\n"))
|
|
if f, ok := w.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
_, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"IOP_OPENAI_"},"done":false}` + "\n"))
|
|
if f, ok := w.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
_, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"OLLAMA_OK"},"done":false}` + "\n"))
|
|
_, _ = w.Write([]byte(`{"done":true,"prompt_eval_count":5,"eval_count":2}` + "\n"))
|
|
})
|
|
log.Fatal(http.ListenAndServe(os.Args[1], mux))
|
|
}
|
|
EOF
|
|
|
|
go run "$FAKE_OLLAMA" "127.0.0.1:$OLLAMA_PORT" > "$TMP_DIR/fake_ollama.out" 2>&1 &
|
|
OLLAMA_PID=$!
|
|
|
|
wait_port() {
|
|
local host="$1"
|
|
local port="$2"
|
|
local label="$3"
|
|
local deadline=$((SECONDS + 20))
|
|
while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/"$1"/"$2"' _ "$host" "$port" 2>/dev/null; do
|
|
if (( SECONDS >= deadline )); then
|
|
echo "[openai-ollama] $label did not open on $host:$port"
|
|
return 1
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
}
|
|
|
|
wait_port 127.0.0.1 "$OLLAMA_PORT" "fake ollama"
|
|
|
|
EDGE_CONFIG="$TMP_DIR/edge.yaml"
|
|
NODE_CONFIG="$TMP_DIR/node.yaml"
|
|
cat > "$EDGE_CONFIG" <<EOF
|
|
server:
|
|
listen: "127.0.0.1:$PORT"
|
|
metrics:
|
|
port: $EDGE_METRICS_PORT
|
|
bootstrap:
|
|
listen: "127.0.0.1:$BOOTSTRAP_PORT"
|
|
artifact_dir: "$TMP_DIR/artifacts"
|
|
openai:
|
|
enabled: true
|
|
listen: "127.0.0.1:$OPENAI_PORT"
|
|
provider_id: "ollama-smoke-provider"
|
|
adapter: "ollama"
|
|
session_id: "cline"
|
|
timeout_sec: 30
|
|
strict_output: false
|
|
model_routes:
|
|
- model: "client-request-model"
|
|
adapter: "ollama"
|
|
target: "$MODEL"
|
|
- model: "$MODEL"
|
|
adapter: "ollama"
|
|
target: "$MODEL"
|
|
console:
|
|
adapter: ollama
|
|
target: "$MODEL"
|
|
session_id: default
|
|
nodes:
|
|
- id: test-node
|
|
alias: test-node
|
|
token: test-token
|
|
adapters:
|
|
ollama:
|
|
enabled: true
|
|
base_url: "http://127.0.0.1:$OLLAMA_PORT"
|
|
context_size: 262144
|
|
EOF
|
|
|
|
cat > "$NODE_CONFIG" <<EOF
|
|
transport:
|
|
edge_addr: "127.0.0.1:$PORT"
|
|
token: test-token
|
|
reconnect_interval: 1s
|
|
metrics:
|
|
port: $NODE_METRICS_PORT
|
|
node:
|
|
id: test-node
|
|
alias: test-node
|
|
EOF
|
|
|
|
EDGE_OUT="$TMP_DIR/edge.out"
|
|
NODE_OUT="$TMP_DIR/node.out"
|
|
mkfifo "$TMP_DIR/edge_fifo"
|
|
IOP_EDGE_CONFIG="$EDGE_CONFIG" "$REPO_ROOT/scripts/dev/edge.sh" < "$TMP_DIR/edge_fifo" > "$EDGE_OUT" 2>&1 &
|
|
EDGE_PID=$!
|
|
exec 3> "$TMP_DIR/edge_fifo"
|
|
EDGE_FD_OPEN=1
|
|
|
|
wait_port 127.0.0.1 "$PORT" "edge node transport"
|
|
wait_port 127.0.0.1 "$OPENAI_PORT" "edge openai api"
|
|
|
|
IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" > "$NODE_OUT" 2>&1 &
|
|
NODE_PID=$!
|
|
|
|
deadline=$((SECONDS + 30))
|
|
while ! grep -q '\[node0-evt\] connected reason="registered"' "$EDGE_OUT"; do
|
|
if (( SECONDS >= deadline )); then
|
|
echo "[openai-ollama] node registration timed out"
|
|
echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT"
|
|
echo "=== NODE OUTPUT ==="; cat "$NODE_OUT"
|
|
exit 1
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
|
|
MODELS_OUT="$TMP_DIR/models.json"
|
|
curl -fsS "http://127.0.0.1:$OPENAI_PORT/v1/models" > "$MODELS_OUT"
|
|
grep -q "$MODEL" "$MODELS_OUT"
|
|
|
|
CHAT_OUT="$TMP_DIR/chat.json"
|
|
curl -fsS \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"client-request-model","messages":[{"role":"user","content":"say the test token"}]}' \
|
|
"http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$CHAT_OUT"
|
|
grep -q "IOP_OPENAI_OLLAMA_OK" "$CHAT_OUT"
|
|
grep -q "reasoning_content" "$CHAT_OUT"
|
|
|
|
RESPONSES_OUT="$TMP_DIR/responses.json"
|
|
curl -fsS \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"client-request-model","input":"say the responses test token","stream":false,"metadata":{"request_id":"e2e-openai-ollama","task_id":"task-smoke"}}' \
|
|
"http://127.0.0.1:$OPENAI_PORT/v1/responses" > "$RESPONSES_OUT"
|
|
grep -q "IOP_OPENAI_OLLAMA_OK" "$RESPONSES_OUT"
|
|
grep -q '"output_text"' "$RESPONSES_OUT"
|
|
|
|
STREAM_OUT="$TMP_DIR/stream.txt"
|
|
curl -fsS -N \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"client-request-model","stream":true,"messages":[{"role":"user","content":"stream the test token"}]}' \
|
|
"http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$STREAM_OUT"
|
|
grep -q '"reasoning_content":"thinking..."' "$STREAM_OUT"
|
|
grep -q '"content":"IOP_OPENAI_"' "$STREAM_OUT"
|
|
grep -q 'data: \[DONE\]' "$STREAM_OUT"
|
|
|
|
SMOKE_OUT="$TMP_DIR/iop-edge-smoke-openai.txt"
|
|
(cd "$REPO_ROOT" && go run ./apps/edge/cmd/edge smoke openai \
|
|
--model "$MODEL" \
|
|
--base-url "http://127.0.0.1:$OPENAI_PORT" \
|
|
--prompt "say the command smoke token" \
|
|
--timeout 20s) > "$SMOKE_OUT"
|
|
grep -q "Step 3: Checking /v1/responses ... \[OK\]" "$SMOKE_OUT"
|
|
grep -q "IOP Edge OpenAI Smoke Test SUCCESS!" "$SMOKE_OUT"
|
|
|
|
if grep -i -E "node reported error|error run_id=|\[[^]]+-evt\] error|panic:" "$EDGE_OUT" "$NODE_OUT" >/dev/null; then
|
|
echo "[openai-ollama] detected failure marker"
|
|
echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT"
|
|
echo "=== NODE OUTPUT ==="; cat "$NODE_OUT"
|
|
exit 1
|
|
fi
|
|
|
|
echo "[openai-ollama] OpenAI-compatible Ollama serving test PASSED."
|