- OpenAI request rebuilder with tool validation and provider tunnel - Edge config runtime refresh for stream evidence gate - Filter observation contract and runtime with sink/correlation - Stream gate dispatcher, release sink, and vertical slice - Recovery coordinator for evidence tail - Parallel evaluation and commit boundary - E2E test script for OpenAI vLLM - Archive completed task groups to archive/2026/07
784 lines
25 KiB
Go
784 lines
25 KiB
Go
package openai
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
edgeservice "iop/apps/edge/internal/service"
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
// providerFakeRunService extends the normalized fakeRunService with the
|
|
// provider tunnel/pool dispatch state used by provider scenario tests.
|
|
type providerFakeRunService struct {
|
|
fakeRunService
|
|
|
|
// tunnelProviderURL, when set, makes SubmitProviderTunnel relay the tunnel
|
|
// request to this httptest provider fixture and stream the raw provider
|
|
// response back as ordered frames (emulating the Node relay).
|
|
tunnelProviderURL string
|
|
// tunnelServedTarget emulates provider-pool admission: BuildBody is called
|
|
// with this target when non-empty.
|
|
tunnelServedTarget string
|
|
// tunnelFrames, when set, is handed to the tunnel handle as-is instead of
|
|
// contacting tunnelProviderURL.
|
|
tunnelFrames chan *iop.ProviderTunnelFrame
|
|
tunnelErr error
|
|
tunnelReqs []edgeservice.SubmitProviderTunnelRequest
|
|
tunnelBodies [][]byte
|
|
// tunnelWaitTimeout overrides the tunnel handle wait timeout for
|
|
// timeout-path fixtures; zero keeps the 5s default.
|
|
tunnelWaitTimeout time.Duration
|
|
|
|
// poolDispatchPath records which execution path was returned by
|
|
// SubmitProviderPool: "provider_tunnel" or "normalized".
|
|
poolDispatchPath string
|
|
// poolRunFrames is the frame source used by the normalized path when
|
|
// SubmitProviderPool is in use.
|
|
poolRunFrames chan *iop.RunEvent
|
|
// lastTunnelHandle stores the tunnel handle from the last SubmitProviderPool
|
|
// tunnel path call, so tests can check headers set by the handler.
|
|
lastTunnelHandle *fakeTunnelHandle
|
|
// poolSubmitResults provides custom results for successive SubmitProviderPool
|
|
// calls. When non-empty, results are consumed in order; when empty, the
|
|
// poolDispatchPath / tunnelErr fields are used instead.
|
|
poolSubmitResults []edgeservice.ProviderPoolDispatchResult
|
|
// poolSubmitCount tracks how many times SubmitProviderPool was called
|
|
// (including from the chat handler). Used by tests to assert dispatch
|
|
// surface fidelity.
|
|
poolSubmitCount int
|
|
// poolLastRun records the last SubmitRunRequest passed to SubmitProviderPool
|
|
// (pre-PrepareRun). Tests assert ModelGroupKey is non-empty to catch the
|
|
// "empty Run" regression.
|
|
poolLastRun edgeservice.SubmitRunRequest
|
|
// poolPrepareRunCalled tracks whether the optional PrepareRun hook was
|
|
// invoked by SubmitProviderPool on the normalized path. Only meaningful
|
|
// when poolDispatchPath == "normalized".
|
|
poolPrepareRunCalled bool
|
|
}
|
|
|
|
type fakeTunnelHandle struct {
|
|
dispatch edgeservice.RunDispatch
|
|
headers map[string]string
|
|
frames chan *iop.ProviderTunnelFrame
|
|
waitTimeout time.Duration
|
|
}
|
|
|
|
func (h *fakeTunnelHandle) Headers() map[string]string { return h.headers }
|
|
func (h *fakeTunnelHandle) SetHeaders(hdrs map[string]string) { h.headers = hdrs }
|
|
func (h *fakeTunnelHandle) Dispatch() edgeservice.RunDispatch { return h.dispatch }
|
|
func (h *fakeTunnelHandle) Stream() edgeservice.ProviderTunnelStream {
|
|
return edgeservice.ProviderTunnelStream{Frames: h.frames}
|
|
}
|
|
func (h *fakeTunnelHandle) Close() {}
|
|
func (h *fakeTunnelHandle) WaitTimeout() time.Duration {
|
|
if h.waitTimeout > 0 {
|
|
return h.waitTimeout
|
|
}
|
|
return 5 * time.Second
|
|
}
|
|
|
|
// fakeRunResult implements edgeservice.RunResult for tests.
|
|
type fakeRunResult struct {
|
|
dispatch edgeservice.RunDispatch
|
|
events chan *iop.RunEvent
|
|
}
|
|
|
|
func (r *fakeRunResult) Dispatch() edgeservice.RunDispatch { return r.dispatch }
|
|
func (r *fakeRunResult) Stream() edgeservice.RunStream {
|
|
return edgeservice.RunStream{Events: r.events}
|
|
}
|
|
func (r *fakeRunResult) Close() {}
|
|
func (r *fakeRunResult) WaitTimeout() time.Duration {
|
|
return 5 * time.Second
|
|
}
|
|
|
|
func (s *providerFakeRunService) SubmitProviderTunnel(_ context.Context, req edgeservice.SubmitProviderTunnelRequest) (edgeservice.ProviderTunnelResult, error) {
|
|
s.submitMu.Lock()
|
|
s.tunnelReqs = append(s.tunnelReqs, req)
|
|
s.submitMu.Unlock()
|
|
if s.tunnelErr != nil {
|
|
return nil, s.tunnelErr
|
|
}
|
|
target := req.Target
|
|
if s.tunnelServedTarget != "" {
|
|
target = s.tunnelServedTarget
|
|
}
|
|
body := req.Body
|
|
if req.BuildBody != nil {
|
|
built, err := req.BuildBody(target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
body = built
|
|
}
|
|
s.submitMu.Lock()
|
|
s.tunnelBodies = append(s.tunnelBodies, body)
|
|
s.submitMu.Unlock()
|
|
|
|
frames := s.tunnelFrames
|
|
if frames == nil && s.tunnelProviderURL != "" {
|
|
relayed, err := relayProviderFixtureFrames(s.tunnelProviderURL, req.Method, req.Path, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
frames = relayed
|
|
}
|
|
return &fakeTunnelHandle{
|
|
dispatch: edgeservice.RunDispatch{
|
|
RunID: "run-tunnel",
|
|
NodeID: "node-1",
|
|
ModelGroupKey: req.ModelGroupKey,
|
|
Adapter: req.Adapter,
|
|
Target: target,
|
|
SessionID: req.SessionID,
|
|
TimeoutSec: 5,
|
|
},
|
|
frames: frames,
|
|
waitTimeout: s.tunnelWaitTimeout,
|
|
}, nil
|
|
}
|
|
|
|
// SubmitProviderPool is a minimal test double for the provider-pool one-shot
|
|
// dispatch. It records the selected path (tunnel vs normalized) and returns a
|
|
// stub result so the OpenAI handler can exercise the selection-first flow.
|
|
// It also tracks whether the optional PrepareRun hook was invoked, so tests
|
|
// can assert the handler only calls PrepareRun on the normalized path.
|
|
func (s *providerFakeRunService) SubmitProviderPool(_ context.Context, req edgeservice.ProviderPoolDispatchRequest) (*edgeservice.ProviderPoolDispatchResult, error) {
|
|
s.submitMu.Lock()
|
|
s.poolSubmitCount++
|
|
s.poolLastRun = req.Run
|
|
s.poolPrepareRunCalled = false
|
|
s.submitMu.Unlock()
|
|
|
|
// Custom results for successive calls (e.g. tool-validation retry tests).
|
|
s.submitMu.Lock()
|
|
if len(s.poolSubmitResults) > 0 {
|
|
result := s.poolSubmitResults[0]
|
|
s.poolSubmitResults = s.poolSubmitResults[1:]
|
|
s.submitMu.Unlock()
|
|
// Apply pre-dispatch tunnel preparation for tunnel path when custom
|
|
// results are used.
|
|
if result.Path == edgeservice.ProviderPoolPathTunnel && req.PrepareTunnel != nil {
|
|
tunnelReqPrepared, prepErr := req.PrepareTunnel(req.Tunnel)
|
|
if prepErr != nil {
|
|
return nil, prepErr
|
|
}
|
|
req.Tunnel = tunnelReqPrepared
|
|
}
|
|
return s.buildPoolResult(req, result)
|
|
}
|
|
err := s.tunnelErr
|
|
path := s.poolDispatchPath
|
|
s.submitMu.Unlock()
|
|
|
|
// Apply pre-dispatch tunnel preparation (mirrors real SubmitProviderPool
|
|
// behavior: PrepareTunnel runs BEFORE buildProviderTunnelRequest and the
|
|
// Node Send step). On failure, no tunnel request is recorded or sent.
|
|
tunnelReq := req.Tunnel
|
|
if path == string(edgeservice.ProviderPoolPathTunnel) || path == "" {
|
|
if req.PrepareTunnel != nil {
|
|
tunnelReqPrepared, prepErr := req.PrepareTunnel(req.Tunnel)
|
|
if prepErr != nil {
|
|
return nil, prepErr
|
|
}
|
|
tunnelReq = tunnelReqPrepared
|
|
}
|
|
}
|
|
|
|
// Record tunnel request for test assertions.
|
|
if path == string(edgeservice.ProviderPoolPathTunnel) || path == "" {
|
|
s.submitMu.Lock()
|
|
s.tunnelReqs = append(s.tunnelReqs, tunnelReq)
|
|
s.submitMu.Unlock()
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
disp := edgeservice.RunDispatch{
|
|
RunID: "run-pool-tunnel",
|
|
NodeID: "node-pool",
|
|
ModelGroupKey: req.Run.ModelGroupKey,
|
|
Adapter: "selected",
|
|
Target: "served",
|
|
ProviderID: "prov-fake",
|
|
ProviderType: "vllm",
|
|
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
|
|
QueueReason: "dispatched",
|
|
}
|
|
if path == string(edgeservice.ProviderPoolPathNormalized) {
|
|
disp.RunID = "run-pool-normalized"
|
|
}
|
|
|
|
if path == string(edgeservice.ProviderPoolPathTunnel) || path == "" {
|
|
frames := s.tunnelFrames
|
|
if frames == nil && s.tunnelProviderURL != "" {
|
|
body := req.Tunnel.Body
|
|
if req.Tunnel.BuildBody != nil {
|
|
target := req.Tunnel.Target
|
|
if s.tunnelServedTarget != "" {
|
|
target = s.tunnelServedTarget
|
|
}
|
|
built, err := req.Tunnel.BuildBody(target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
body = built
|
|
}
|
|
s.submitMu.Lock()
|
|
s.tunnelBodies = append(s.tunnelBodies, body)
|
|
s.submitMu.Unlock()
|
|
relayed, err := relayProviderFixtureFrames(s.tunnelProviderURL, req.Tunnel.Method, req.Tunnel.Path, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
frames = relayed
|
|
} else if frames == nil {
|
|
frames = staticProviderTunnelFrames(`{"ok":true}`)
|
|
// Record the built tunnel body even when using a static frame source.
|
|
body := req.Tunnel.Body
|
|
if req.Tunnel.BuildBody != nil {
|
|
target := req.Tunnel.Target
|
|
if s.tunnelServedTarget != "" {
|
|
target = s.tunnelServedTarget
|
|
}
|
|
if built, buildErr := req.Tunnel.BuildBody(target); buildErr == nil {
|
|
body = built
|
|
}
|
|
}
|
|
s.submitMu.Lock()
|
|
s.tunnelBodies = append(s.tunnelBodies, body)
|
|
s.submitMu.Unlock()
|
|
} else {
|
|
body := req.Tunnel.Body
|
|
if req.Tunnel.BuildBody != nil {
|
|
target := req.Tunnel.Target
|
|
if s.tunnelServedTarget != "" {
|
|
target = s.tunnelServedTarget
|
|
}
|
|
if built, err := req.Tunnel.BuildBody(target); err == nil {
|
|
body = built
|
|
}
|
|
}
|
|
s.submitMu.Lock()
|
|
s.tunnelBodies = append(s.tunnelBodies, body)
|
|
s.submitMu.Unlock()
|
|
}
|
|
|
|
handle := &fakeTunnelHandle{
|
|
dispatch: edgeservice.RunDispatch{
|
|
RunID: "run-tunnel",
|
|
NodeID: "node-1",
|
|
ModelGroupKey: req.Tunnel.ModelGroupKey,
|
|
Adapter: req.Tunnel.Adapter,
|
|
Target: req.Tunnel.Target,
|
|
SessionID: req.Tunnel.SessionID,
|
|
TimeoutSec: 5,
|
|
},
|
|
headers: req.Tunnel.Headers,
|
|
frames: frames,
|
|
}
|
|
// Store for later inspection by tests.
|
|
s.submitMu.Lock()
|
|
s.lastTunnelHandle = handle
|
|
s.submitMu.Unlock()
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathTunnel,
|
|
Tunnel: handle,
|
|
DispatchInfo: disp,
|
|
}, nil
|
|
}
|
|
|
|
// Normalized path (mimics Ollama/CLI behavior).
|
|
// Invoke the optional PrepareRun hook so tests can assert it only runs
|
|
// on the normalized path (mirrors real SubmitProviderPool behavior).
|
|
if req.PrepareRun != nil {
|
|
s.submitMu.Lock()
|
|
s.poolPrepareRunCalled = true
|
|
s.submitMu.Unlock()
|
|
prepared, prepErr := req.PrepareRun(req.Run)
|
|
if prepErr != nil {
|
|
return nil, prepErr
|
|
}
|
|
req.Run = prepared
|
|
}
|
|
s.submitMu.Lock()
|
|
s.reqs = append(s.reqs, req.Run)
|
|
s.submitMu.Unlock()
|
|
|
|
runChan := s.poolRunFrames
|
|
if runChan == nil {
|
|
runChan = make(chan *iop.RunEvent, 1)
|
|
runChan <- &iop.RunEvent{RunId: disp.RunID, Type: "complete"}
|
|
close(runChan)
|
|
}
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathNormalized,
|
|
Run: &fakeRunResult{
|
|
dispatch: disp,
|
|
events: runChan,
|
|
},
|
|
DispatchInfo: disp,
|
|
}, nil
|
|
}
|
|
|
|
// buildPoolResult assembles a ProviderPoolDispatchResult from a pre-configured
|
|
// custom result. It handles tunnel request recording, frame relay, and
|
|
// normalized path run construction.
|
|
func (s *providerFakeRunService) buildPoolResult(req edgeservice.ProviderPoolDispatchRequest, result edgeservice.ProviderPoolDispatchResult) (*edgeservice.ProviderPoolDispatchResult, error) {
|
|
if result.Path == edgeservice.ProviderPoolPathTunnel || result.Path == "" {
|
|
s.submitMu.Lock()
|
|
s.tunnelReqs = append(s.tunnelReqs, req.Tunnel)
|
|
s.submitMu.Unlock()
|
|
var frames chan *iop.ProviderTunnelFrame
|
|
if frames == nil && s.tunnelProviderURL != "" {
|
|
body := req.Tunnel.Body
|
|
if req.Tunnel.BuildBody != nil {
|
|
target := req.Tunnel.Target
|
|
if s.tunnelServedTarget != "" {
|
|
target = s.tunnelServedTarget
|
|
}
|
|
if built, err := req.Tunnel.BuildBody(target); err == nil {
|
|
body = built
|
|
}
|
|
}
|
|
s.submitMu.Lock()
|
|
s.tunnelBodies = append(s.tunnelBodies, body)
|
|
s.submitMu.Unlock()
|
|
relayed, err := relayProviderFixtureFrames(s.tunnelProviderURL, req.Tunnel.Method, req.Tunnel.Path, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
frames = relayed
|
|
} else if frames == nil {
|
|
frames = staticProviderTunnelFrames(`{"ok":true}`)
|
|
}
|
|
disp := result.DispatchInfo
|
|
if disp.RunID == "" {
|
|
disp.RunID = "run-tunnel"
|
|
}
|
|
if disp.NodeID == "" {
|
|
disp.NodeID = "node-1"
|
|
}
|
|
handle := &fakeTunnelHandle{
|
|
dispatch: disp,
|
|
headers: req.Tunnel.Headers,
|
|
frames: frames,
|
|
waitTimeout: s.tunnelWaitTimeout,
|
|
}
|
|
s.submitMu.Lock()
|
|
s.lastTunnelHandle = handle
|
|
s.submitMu.Unlock()
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathTunnel,
|
|
Tunnel: handle,
|
|
DispatchInfo: disp,
|
|
}, nil
|
|
}
|
|
|
|
// Normalized path.
|
|
disp := result.DispatchInfo
|
|
if disp.RunID == "" {
|
|
disp.RunID = "run-pool-normalized"
|
|
}
|
|
if disp.NodeID == "" {
|
|
disp.NodeID = "node-pool"
|
|
}
|
|
// Invoke the optional PrepareRun hook on the normalized path (mirrors
|
|
// real SubmitProviderPool behavior).
|
|
if req.PrepareRun != nil {
|
|
prepared, prepErr := req.PrepareRun(req.Run)
|
|
if prepErr != nil {
|
|
return nil, prepErr
|
|
}
|
|
req.Run = prepared
|
|
}
|
|
s.submitMu.Lock()
|
|
s.poolPrepareRunCalled = true
|
|
s.submitMu.Unlock()
|
|
s.submitMu.Lock()
|
|
s.reqs = append(s.reqs, req.Run)
|
|
s.submitMu.Unlock()
|
|
// Use the Run from the custom result if provided, otherwise create a
|
|
// default completing run.
|
|
if result.Run != nil {
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathNormalized,
|
|
Run: result.Run,
|
|
DispatchInfo: disp,
|
|
}, nil
|
|
}
|
|
runChan := make(chan *iop.RunEvent, 1)
|
|
runChan <- &iop.RunEvent{RunId: disp.RunID, Type: "complete"}
|
|
close(runChan)
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathNormalized,
|
|
Run: &fakeRunResult{
|
|
dispatch: disp,
|
|
events: runChan,
|
|
},
|
|
DispatchInfo: disp,
|
|
}, nil
|
|
}
|
|
|
|
// tunnelHandleSnapshot returns the tunnel handle from the last SubmitProviderPool
|
|
// call, allowing tests to check headers set by the handler.
|
|
type tunnelHandleSnapshot struct {
|
|
headers map[string]string
|
|
runID string
|
|
nodeID string
|
|
}
|
|
|
|
func (s *providerFakeRunService) tunnelHandleSnapshot() tunnelHandleSnapshot {
|
|
s.submitMu.Lock()
|
|
defer s.submitMu.Unlock()
|
|
h := s.lastTunnelHandle
|
|
if h == nil {
|
|
return tunnelHandleSnapshot{}
|
|
}
|
|
return tunnelHandleSnapshot{
|
|
headers: h.Headers(),
|
|
runID: h.Dispatch().RunID,
|
|
nodeID: h.Dispatch().NodeID,
|
|
}
|
|
}
|
|
|
|
// poolSubmitCountSnapshot returns the number of times SubmitProviderPool was
|
|
// called. Tests assert exactly-once dispatch from the handler.
|
|
func (s *providerFakeRunService) poolSubmitCountSnapshot() int {
|
|
s.submitMu.Lock()
|
|
defer s.submitMu.Unlock()
|
|
return s.poolSubmitCount
|
|
}
|
|
|
|
// poolLastRunSnapshot returns a copy of the last SubmitRunRequest passed to
|
|
// SubmitProviderPool. Tests assert ModelGroupKey is non-empty to catch the
|
|
// "empty Run" regression.
|
|
func (s *providerFakeRunService) poolLastRunSnapshot() edgeservice.SubmitRunRequest {
|
|
s.submitMu.Lock()
|
|
defer s.submitMu.Unlock()
|
|
return s.poolLastRun
|
|
}
|
|
|
|
// poolPrepareRunCalledSnapshot returns whether the PrepareRun hook was
|
|
// invoked by the last SubmitProviderPool call. Only meaningful when the last
|
|
// call was on the normalized path.
|
|
func (s *providerFakeRunService) poolPrepareRunCalledSnapshot() bool {
|
|
s.submitMu.Lock()
|
|
defer s.submitMu.Unlock()
|
|
return s.poolPrepareRunCalled
|
|
}
|
|
|
|
func (s *providerFakeRunService) tunnelReqsSnapshot() []edgeservice.SubmitProviderTunnelRequest {
|
|
s.submitMu.Lock()
|
|
reqs := make([]edgeservice.SubmitProviderTunnelRequest, len(s.tunnelReqs))
|
|
copy(reqs, s.tunnelReqs)
|
|
s.submitMu.Unlock()
|
|
return reqs
|
|
}
|
|
|
|
func (s *providerFakeRunService) tunnelBodiesSnapshot() [][]byte {
|
|
s.submitMu.Lock()
|
|
defer s.submitMu.Unlock()
|
|
return append([][]byte(nil), s.tunnelBodies...)
|
|
}
|
|
|
|
// --- scripted provider-pool admission double (S16 host fixtures) -------------
|
|
|
|
// scriptedPoolAttempt describes one SubmitProviderPool admission the scripted
|
|
// double must produce, in order. It carries the actual selection result the
|
|
// real pool would compute (path, provider, model), so a fixture can assert that
|
|
// one request runtime observed a real actual-path switch.
|
|
type scriptedPoolAttempt struct {
|
|
// path is the execution path this admission resolves to, as the
|
|
// service-layer string value ("normalized" or "provider_tunnel").
|
|
path string
|
|
runID string
|
|
provider string
|
|
target string
|
|
runEvents chan *iop.RunEvent
|
|
frames chan *iop.ProviderTunnelFrame
|
|
err error
|
|
// prepareErr, when set, is returned by the recorded PrepareRun hook so a
|
|
// fixture can exercise a normalized candidate whose validation fails.
|
|
prepareErr error
|
|
}
|
|
|
|
// scriptedPoolRunService is a thread-safe runService double whose provider-pool
|
|
// admissions are scripted in order. It records every dispatch, transport close,
|
|
// and cancel so an S16 fixture can assert single-dispatch, exactly-once
|
|
// cleanup, and no leak from a discarded attempt.
|
|
type scriptedPoolRunService struct {
|
|
mu sync.Mutex
|
|
attempts []scriptedPoolAttempt
|
|
next int
|
|
|
|
poolRequests []edgeservice.ProviderPoolDispatchRequest
|
|
runRequests []edgeservice.SubmitRunRequest
|
|
tunnelRequests []edgeservice.SubmitProviderTunnelRequest
|
|
cancels []edgeservice.CancelRunRequest
|
|
runCloses []string
|
|
tunnelCloses []string
|
|
prepareTunnel int
|
|
prepareRun int
|
|
}
|
|
|
|
func newScriptedPoolRunService(attempts ...scriptedPoolAttempt) *scriptedPoolRunService {
|
|
return &scriptedPoolRunService{attempts: attempts}
|
|
}
|
|
|
|
func (s *scriptedPoolRunService) SubmitRun(_ context.Context, req edgeservice.SubmitRunRequest) (edgeservice.RunResult, error) {
|
|
s.mu.Lock()
|
|
s.runRequests = append(s.runRequests, req)
|
|
s.mu.Unlock()
|
|
return nil, fmt.Errorf("scriptedPoolRunService: direct SubmitRun is not part of a provider-pool fixture")
|
|
}
|
|
|
|
func (s *scriptedPoolRunService) SubmitProviderTunnel(_ context.Context, req edgeservice.SubmitProviderTunnelRequest) (edgeservice.ProviderTunnelResult, error) {
|
|
s.mu.Lock()
|
|
s.tunnelRequests = append(s.tunnelRequests, req)
|
|
s.mu.Unlock()
|
|
return nil, fmt.Errorf("scriptedPoolRunService: direct SubmitProviderTunnel is not part of a provider-pool fixture")
|
|
}
|
|
|
|
func (s *scriptedPoolRunService) OllamaAPI(context.Context, edgeservice.OllamaAPIRequest) (edgeservice.OllamaAPIView, error) {
|
|
return edgeservice.OllamaAPIView{StatusCode: http.StatusOK}, nil
|
|
}
|
|
|
|
func (s *scriptedPoolRunService) CancelRun(_ context.Context, req edgeservice.CancelRunRequest) (edgeservice.CommandResult, error) {
|
|
s.mu.Lock()
|
|
s.cancels = append(s.cancels, req)
|
|
s.mu.Unlock()
|
|
return edgeservice.CommandResult{NodeID: req.NodeRef, SessionID: req.SessionID}, nil
|
|
}
|
|
|
|
func (s *scriptedPoolRunService) SubmitProviderPool(_ context.Context, req edgeservice.ProviderPoolDispatchRequest) (*edgeservice.ProviderPoolDispatchResult, error) {
|
|
s.mu.Lock()
|
|
s.poolRequests = append(s.poolRequests, req)
|
|
if s.next >= len(s.attempts) {
|
|
s.mu.Unlock()
|
|
return nil, fmt.Errorf("scriptedPoolRunService: no scripted attempt left")
|
|
}
|
|
attempt := s.attempts[s.next]
|
|
s.next++
|
|
s.mu.Unlock()
|
|
|
|
if attempt.err != nil {
|
|
return nil, attempt.err
|
|
}
|
|
|
|
dispatch := edgeservice.RunDispatch{
|
|
RunID: attempt.runID,
|
|
NodeID: "node-pool",
|
|
ModelGroupKey: req.Run.ModelGroupKey,
|
|
Adapter: "selected",
|
|
Target: attempt.target,
|
|
ProviderID: attempt.provider,
|
|
ProviderType: "scripted",
|
|
ExecutionPath: string(attempt.path),
|
|
SessionID: req.Run.SessionID,
|
|
TimeoutSec: 5,
|
|
}
|
|
|
|
if attempt.path == string(edgeservice.ProviderPoolPathTunnel) {
|
|
tunnelReq := req.Tunnel
|
|
if req.PrepareTunnel != nil {
|
|
s.mu.Lock()
|
|
s.prepareTunnel++
|
|
s.mu.Unlock()
|
|
prepared, err := req.PrepareTunnel(tunnelReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tunnelReq = prepared
|
|
}
|
|
if tunnelReq.BuildBody != nil {
|
|
if _, err := tunnelReq.BuildBody(attempt.target); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
s.mu.Lock()
|
|
s.tunnelRequests = append(s.tunnelRequests, tunnelReq)
|
|
s.mu.Unlock()
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathTunnel,
|
|
Tunnel: &countingTunnelHandle{service: s, dispatch: dispatch, frames: attempt.frames},
|
|
DispatchInfo: dispatch,
|
|
}, nil
|
|
}
|
|
|
|
runReq := req.Run
|
|
if req.PrepareRun != nil {
|
|
s.mu.Lock()
|
|
s.prepareRun++
|
|
s.mu.Unlock()
|
|
if attempt.prepareErr != nil {
|
|
return nil, attempt.prepareErr
|
|
}
|
|
prepared, err := req.PrepareRun(runReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
runReq = prepared
|
|
}
|
|
s.mu.Lock()
|
|
s.runRequests = append(s.runRequests, runReq)
|
|
s.mu.Unlock()
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathNormalized,
|
|
Run: &countingRunHandle{service: s, dispatch: dispatch, events: attempt.runEvents},
|
|
DispatchInfo: dispatch,
|
|
}, nil
|
|
}
|
|
|
|
func (s *scriptedPoolRunService) snapshot() (pools int, cancels []string, runCloses, tunnelCloses []string, runReqs []edgeservice.SubmitRunRequest, tunnelReqs []edgeservice.SubmitProviderTunnelRequest) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
ids := make([]string, 0, len(s.cancels))
|
|
for _, c := range s.cancels {
|
|
ids = append(ids, c.RunID)
|
|
}
|
|
return len(s.poolRequests),
|
|
ids,
|
|
append([]string(nil), s.runCloses...),
|
|
append([]string(nil), s.tunnelCloses...),
|
|
append([]edgeservice.SubmitRunRequest(nil), s.runRequests...),
|
|
append([]edgeservice.SubmitProviderTunnelRequest(nil), s.tunnelRequests...)
|
|
}
|
|
|
|
func (s *scriptedPoolRunService) poolSubmits() int {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return len(s.poolRequests)
|
|
}
|
|
|
|
type countingRunHandle struct {
|
|
service *scriptedPoolRunService
|
|
dispatch edgeservice.RunDispatch
|
|
events chan *iop.RunEvent
|
|
}
|
|
|
|
func (h *countingRunHandle) Dispatch() edgeservice.RunDispatch { return h.dispatch }
|
|
func (h *countingRunHandle) Stream() edgeservice.RunStream {
|
|
return edgeservice.RunStream{Events: h.events}
|
|
}
|
|
func (h *countingRunHandle) Close() {
|
|
h.service.mu.Lock()
|
|
h.service.runCloses = append(h.service.runCloses, h.dispatch.RunID)
|
|
h.service.mu.Unlock()
|
|
}
|
|
func (h *countingRunHandle) WaitTimeout() time.Duration { return 5 * time.Second }
|
|
|
|
type countingTunnelHandle struct {
|
|
service *scriptedPoolRunService
|
|
dispatch edgeservice.RunDispatch
|
|
headers map[string]string
|
|
frames chan *iop.ProviderTunnelFrame
|
|
}
|
|
|
|
func (h *countingTunnelHandle) Headers() map[string]string { return h.headers }
|
|
func (h *countingTunnelHandle) SetHeaders(hdrs map[string]string) { h.headers = hdrs }
|
|
func (h *countingTunnelHandle) Dispatch() edgeservice.RunDispatch { return h.dispatch }
|
|
func (h *countingTunnelHandle) Stream() edgeservice.ProviderTunnelStream {
|
|
return edgeservice.ProviderTunnelStream{Frames: h.frames}
|
|
}
|
|
func (h *countingTunnelHandle) Close() {
|
|
h.service.mu.Lock()
|
|
h.service.tunnelCloses = append(h.service.tunnelCloses, h.dispatch.RunID)
|
|
h.service.mu.Unlock()
|
|
}
|
|
func (h *countingTunnelHandle) WaitTimeout() time.Duration { return 5 * time.Second }
|
|
|
|
var (
|
|
_ runService = (*scriptedPoolRunService)(nil)
|
|
_ edgeservice.RunResult = (*countingRunHandle)(nil)
|
|
_ edgeservice.ProviderTunnelResult = (*countingTunnelHandle)(nil)
|
|
)
|
|
|
|
// bufferedTunnelFrames builds a closed, fully buffered tunnel frame channel.
|
|
func bufferedTunnelFrames(frames ...*iop.ProviderTunnelFrame) chan *iop.ProviderTunnelFrame {
|
|
ch := make(chan *iop.ProviderTunnelFrame, len(frames))
|
|
for _, f := range frames {
|
|
ch <- f
|
|
}
|
|
close(ch)
|
|
return ch
|
|
}
|
|
|
|
// relayProviderFixtureFrames performs the provider HTTP request the way the
|
|
// Node relay does and converts status/header/body bytes into ordered tunnel
|
|
// frames, chunking the body at odd boundaries to exercise ordered writes.
|
|
func relayProviderFixtureFrames(providerURL, method, path string, body []byte) (chan *iop.ProviderTunnelFrame, error) {
|
|
httpReq, err := http.NewRequest(method, providerURL+path, bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
resp, err := http.DefaultClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headers := make(map[string]string)
|
|
for k, values := range resp.Header {
|
|
if len(values) > 0 {
|
|
headers[k] = values[0]
|
|
}
|
|
}
|
|
|
|
const chunkSize = 7
|
|
frames := make(chan *iop.ProviderTunnelFrame, len(respBody)/chunkSize+3)
|
|
seq := int64(0)
|
|
frames <- &iop.ProviderTunnelFrame{
|
|
RunId: "run-tunnel",
|
|
TunnelId: "tunnel-1",
|
|
Sequence: seq,
|
|
Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START,
|
|
StatusCode: int32(resp.StatusCode),
|
|
Headers: headers,
|
|
}
|
|
seq++
|
|
for off := 0; off < len(respBody); off += chunkSize {
|
|
end := off + chunkSize
|
|
if end > len(respBody) {
|
|
end = len(respBody)
|
|
}
|
|
frames <- &iop.ProviderTunnelFrame{
|
|
RunId: "run-tunnel",
|
|
TunnelId: "tunnel-1",
|
|
Sequence: seq,
|
|
Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY,
|
|
Body: append([]byte(nil), respBody[off:end]...),
|
|
}
|
|
seq++
|
|
}
|
|
frames <- &iop.ProviderTunnelFrame{
|
|
RunId: "run-tunnel",
|
|
TunnelId: "tunnel-1",
|
|
Sequence: seq,
|
|
Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END,
|
|
End: true,
|
|
}
|
|
close(frames)
|
|
return frames, nil
|
|
}
|
|
|
|
func staticProviderTunnelFrames(body string) chan *iop.ProviderTunnelFrame {
|
|
frames := make(chan *iop.ProviderTunnelFrame, 3)
|
|
frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200}
|
|
frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(body)}
|
|
frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true}
|
|
close(frames)
|
|
return frames
|
|
}
|