将 Worker 发现、路由画像、容量与执行传输抽象为平台无关接口,新增 Kubernetes 和静态容量适配器,并以 shadow 模式接入生产配置。 实现网络与容量评分、路由防抖、池队列、同步 Worker 租约、一次性执行令牌,以及提交状态不明时禁止重复分配的安全语义。 新增 0105 兼容迁移、管理接口、指标、OpenAPI 和回归测试。已执行全量 Go 测试、go vet、OpenAPI、迁移安全、Compose 与 Kustomize 验证。
50 lines
2.1 KiB
Go
50 lines
2.1 KiB
Go
package runner
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/executionpool"
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
|
)
|
|
|
|
func TestNextRouteHealthRequiresFailuresAndRecoverySamples(t *testing.T) {
|
|
now := time.Now()
|
|
health := executionpool.RouteHealth{}
|
|
for index := 0; index < 2; index++ {
|
|
health = nextRouteHealth(health, "pool", "route", executionpool.ProbeResult{SampledAt: now.Add(time.Duration(index) * time.Second), ErrorClass: "network"})
|
|
if health.State == executionpool.RouteUnreachable {
|
|
t.Fatalf("became unreachable after %d failures", index+1)
|
|
}
|
|
}
|
|
health = nextRouteHealth(health, "pool", "route", executionpool.ProbeResult{SampledAt: now.Add(2 * time.Second), ErrorClass: "network"})
|
|
if health.State != executionpool.RouteUnreachable {
|
|
t.Fatalf("state=%s, want unreachable", health.State)
|
|
}
|
|
health = nextRouteHealth(health, "pool", "route", executionpool.ProbeResult{SampledAt: now.Add(3 * time.Second), Reachable: true, TCPDuration: 5 * time.Millisecond, FirstByte: 20 * time.Millisecond})
|
|
if health.State != executionpool.RouteDegraded {
|
|
t.Fatalf("state=%s, want degraded during recovery", health.State)
|
|
}
|
|
health = nextRouteHealth(health, "pool", "route", executionpool.ProbeResult{SampledAt: now.Add(4 * time.Second), Reachable: true, TCPDuration: 5 * time.Millisecond, FirstByte: 20 * time.Millisecond})
|
|
if health.State != executionpool.RouteHealthy {
|
|
t.Fatalf("state=%s, want healthy", health.State)
|
|
}
|
|
if health.ExpiresAt.Sub(health.SampledAt) != 45*time.Second {
|
|
t.Fatalf("health TTL=%s", health.ExpiresAt.Sub(health.SampledAt))
|
|
}
|
|
}
|
|
|
|
func TestPinCandidatesToRoutingAssignment(t *testing.T) {
|
|
candidates := []store.RuntimeModelCandidate{
|
|
{PlatformID: "platform-a", PlatformModelID: "model-a"},
|
|
{PlatformID: "platform-b", PlatformModelID: "model-b"},
|
|
}
|
|
pinned := pinCandidatesToRoutingAssignment(candidates, "platform-b", "model-b")
|
|
if len(pinned) != 1 || pinned[0].PlatformModelID != "model-b" {
|
|
t.Fatalf("pinned=%+v", pinned)
|
|
}
|
|
if got := pinCandidatesToRoutingAssignment(candidates, "platform-a", "missing"); len(got) != 0 {
|
|
t.Fatalf("unexpected fallback: %+v", got)
|
|
}
|
|
}
|