iop/apps/edge/internal/service/status_provider_test.go
toki 5b95208f4c feat: edge runtime model queue and status dispatch updates
- Update control-plane edge wire and edge-node runtime wire contracts
- Refactor model queue service with admission control
- Update chat handler and responses handler for edge
- Modify run dispatch and status provider logic
- Add/modify runtime proto definitions
- Move G07 status logs to archive
2026-07-05 20:01:27 +09:00

548 lines
16 KiB
Go

package service
import (
"testing"
edgeevents "iop/apps/edge/internal/events"
edgenode "iop/apps/edge/internal/node"
"iop/packages/go/config"
)
func TestListNodeSnapshotsUsesEdgeQueueState(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-q-1", Alias: "node-q-1"})
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-q-1",
Adapters: config.AdaptersConf{
OllamaInstances: []config.OllamaInstanceConf{
{
Name: "ollama-inst",
Enabled: true,
Capacity: 3,
},
},
},
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
// Manually inject group state to modelQueueManager
svc.queue.mu.Lock()
svc.queue.groups["test-group"] = &modelQueueGroup{
key: "test-group",
adapter: "ollama-inst",
inflight: map[string]int{"node-q-1": 2},
queue: []*queueItem{
{
candidates: []candidateNode{
{entry: reg.All()[0], capacity: 3},
},
},
},
}
svc.queue.mu.Unlock()
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
snap := snaps[0]
if len(snap.ProviderSnapshots) != 1 {
t.Fatalf("expected 1 provider snapshot, got %d", len(snap.ProviderSnapshots))
}
p := snap.ProviderSnapshots[0]
if p.Adapter != "ollama-inst" {
t.Errorf("expected adapter ollama-inst, got %s", p.Adapter)
}
if p.Capacity != 3 {
t.Errorf("expected capacity 3, got %d", p.Capacity)
}
if p.InFlight != 2 {
t.Errorf("expected inflight 2, got %d", p.InFlight)
}
if p.Queued != 1 {
t.Errorf("expected queued 1, got %d", p.Queued)
}
}
func TestListNodeSnapshotsTypeRouteSingleNamedInstance(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-q-2", Alias: "node-q-2"})
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-q-2",
Adapters: config.AdaptersConf{
OllamaInstances: []config.OllamaInstanceConf{
{
Name: "ollama-local",
Enabled: true,
Capacity: 5,
},
},
},
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
// Manually inject group state to modelQueueManager with type-name route "ollama"
svc.queue.mu.Lock()
svc.queue.groups["test-group"] = &modelQueueGroup{
key: "test-group",
adapter: "ollama",
inflight: map[string]int{"node-q-2": 3},
queue: []*queueItem{
{
candidates: []candidateNode{
{entry: reg.All()[0], capacity: 5},
},
},
},
}
svc.queue.mu.Unlock()
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
snap := snaps[0]
if len(snap.ProviderSnapshots) != 1 {
t.Fatalf("expected 1 provider snapshot, got %d", len(snap.ProviderSnapshots))
}
p := snap.ProviderSnapshots[0]
if p.Adapter != "ollama-local" {
t.Errorf("expected adapter ollama-local, got %s", p.Adapter)
}
if p.Capacity != 5 {
t.Errorf("expected capacity 5, got %d", p.Capacity)
}
if p.InFlight != 3 {
t.Errorf("expected inflight 3, got %d", p.InFlight)
}
if p.Queued != 1 {
t.Errorf("expected queued 1, got %d", p.Queued)
}
}
// REVIEW_API-2: provider id와 adapter key가 다른 경우에도 queue stats가 올바르게 반영되는지 검증.
// provider id는 "provider-vllm-primary"지만, adapter/instance key는 "vllm-gpu"이다.
// queue group에 adapter="vllm-gpu"로 inject하면 provider snapshot이 이 stats를 올바르게 반영해야 한다.
// REVIEW_REVIEW_API-2: adapter가 비어 있을 때 provider id로 fallback하여
// queue stats(InFlight, Queued, LoadRatio)가 올바르게 반영되는지 검증.
func TestListNodeSnapshotsProviderEmptyAdapterFallbackToProviderID(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-e-1", Alias: "node-e-1"})
// Node has a provider with empty adapter. Queue state is keyed by provider id.
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-e-1",
Adapters: config.AdaptersConf{
// No OllamaInstances/vllm_instances matching the provider id.
// This ensures the fallback path is tested.
},
Providers: []config.NodeProviderConf{
{
ID: "standalone-provider",
Type: "cli",
Category: config.CategoryCLI,
Adapter: "", // empty — should fallback to provider id
Models: []string{"test-model"},
Capacity: 2,
},
},
Runtime: config.RuntimeConf{Concurrency: 2},
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
// Inject tracking state the way provider-pool dispatch does it:
// inflightByRun keyed by providerID, and queue candidates with providerID set.
svc.queue.mu.Lock()
svc.queue.inflightByRun["run-sp-001"] = inflightRec{
groupKey: "test-group",
nodeID: "node-e-1",
providerID: "standalone-provider",
}
svc.queue.groups["test-group"] = &modelQueueGroup{
key: "test-group",
adapter: "standalone-provider",
inflight: map[string]int{"node-e-1": 1},
queue: []*queueItem{
{
candidates: []candidateNode{
{entry: reg.All()[0], capacity: 2, providerID: "standalone-provider"},
},
},
},
}
svc.queue.mu.Unlock()
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
snap := snaps[0]
// Find the provider snapshot by its provider id.
found := false
for i, ps := range snap.ProviderSnapshots {
if ps.Id == "standalone-provider" {
found = true
if ps.InFlight != 1 {
t.Errorf("expected InFlight=1 for standalone-provider (adapter empty), got %d", ps.InFlight)
}
if ps.Queued != 1 {
t.Errorf("expected Queued=1 for standalone-provider (adapter empty), got %d", ps.Queued)
}
if ps.Capacity != 2 {
t.Errorf("expected Capacity=2 for standalone-provider, got %d", ps.Capacity)
}
// LoadRatio = InFlight / Capacity = 1 / 2 = 0.5
expectedLoadRatio := float32(1) / float32(2)
if ps.LoadRatio != expectedLoadRatio {
t.Errorf("expected LoadRatio=%.2f for standalone-provider, got %.2f", expectedLoadRatio, ps.LoadRatio)
}
t.Logf("provider snapshot[%d]: id=%s, adapter=%s, InFlight=%d, Queued=%d, Capacity=%d, LoadRatio=%.2f",
i, ps.Id, ps.Adapter, ps.InFlight, ps.Queued, ps.Capacity, ps.LoadRatio)
break
}
}
if !found {
t.Fatalf("expected provider snapshot with id=standalone-provider, got %d provider snapshots", len(snap.ProviderSnapshots))
}
}
// TestListNodeSnapshotsProviderCatalogSupersedesAdapterSnapshots verifies the
// catalog-first rule: when a node has both adapter instances (CLI + OpenAICompat)
// and a providers[] catalog, ListNodeSnapshots returns only the catalog snapshots
// with no adapter duplicates.
func TestListNodeSnapshotsProviderCatalogSupersedesAdapterSnapshots(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-cs-1", Alias: "node-cs-1"})
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-cs-1",
Adapters: config.AdaptersConf{
CLI: config.CLIConf{Enabled: true},
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
{Name: "vllm-gpu", Enabled: true},
},
},
Providers: []config.NodeProviderConf{
{ID: "prov-cat-1", Adapter: "vllm-gpu", Models: []string{"model-x"}, Health: "available", Capacity: 4},
{ID: "prov-cat-2", Adapter: "vllm-gpu", Models: []string{"model-y"}, Health: "available", Capacity: 2},
},
Runtime: config.RuntimeConf{Concurrency: 1},
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
snap := snaps[0]
// Only catalog snapshots must be returned — exactly 2 (one per provider).
if len(snap.ProviderSnapshots) != 2 {
t.Fatalf("expected 2 catalog snapshots, got %d (cli/openai_compat adapter duplicates must be absent): %+v",
len(snap.ProviderSnapshots), snap.ProviderSnapshots)
}
ids := map[string]bool{}
for _, p := range snap.ProviderSnapshots {
if p.Id == "" {
t.Errorf("unexpected adapter-only snapshot (empty Id): %+v", p)
}
ids[p.Id] = true
}
if !ids["prov-cat-1"] || !ids["prov-cat-2"] {
t.Errorf("expected catalog IDs prov-cat-1 and prov-cat-2, got: %v", ids)
}
}
func TestListNodeSnapshotsCatalogProviderZeroCapacityDoesNotUseRuntimeFallback(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-zero-cap", Alias: "node-zero-cap"})
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-zero-cap",
Adapters: config.AdaptersConf{
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
{Name: "vllm-gpu", Enabled: true, Capacity: 7},
},
},
Providers: []config.NodeProviderConf{
{ID: "prov-zero", Adapter: "vllm-gpu", Models: []string{"model-zero"}, Health: "available", Capacity: 0},
},
Runtime: config.RuntimeConf{Concurrency: 7},
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
if len(snaps[0].ProviderSnapshots) != 1 {
t.Fatalf("expected 1 provider snapshot, got %d", len(snaps[0].ProviderSnapshots))
}
p := snaps[0].ProviderSnapshots[0]
if p.Id != "prov-zero" {
t.Fatalf("expected provider id prov-zero, got %q", p.Id)
}
if p.Capacity != 0 {
t.Fatalf("provider catalog capacity must not fall back to runtime concurrency: got %d", p.Capacity)
}
if p.LoadRatio != 0 {
t.Fatalf("expected zero load ratio for zero capacity provider, got %.2f", p.LoadRatio)
}
}
func TestStaticProviderCatalogSnapshotsZeroCapacityDoesNotUseRuntimeFallback(t *testing.T) {
rec := &edgenode.NodeRecord{
ID: "node-static-zero-cap",
Providers: []config.NodeProviderConf{
{ID: "prov-zero", Adapter: "vllm-gpu", Models: []string{"model-zero"}, Health: "available", Capacity: 0},
},
Runtime: config.RuntimeConf{Concurrency: 7},
}
snaps := staticProviderCatalogSnapshots(rec)
if len(snaps) != 1 {
t.Fatalf("expected 1 provider snapshot, got %d", len(snaps))
}
if snaps[0].Capacity != 0 {
t.Fatalf("static provider catalog capacity must not fall back to runtime concurrency: got %d", snaps[0].Capacity)
}
}
// TestListNodeSnapshotsLegacyAdapterFallbackWhenNoProviders verifies that when
// a node has no providers[] catalog, ListNodeSnapshots returns the legacy adapter
// snapshots (CLI, Ollama, etc.) rather than empty results.
func TestListNodeSnapshotsLegacyAdapterFallbackWhenNoProviders(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-legacy-snap", Alias: "node-legacy-snap"})
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-legacy-snap",
Adapters: config.AdaptersConf{
CLI: config.CLIConf{Enabled: true},
OllamaInstances: []config.OllamaInstanceConf{
{Name: "ollama-local", Enabled: true, Capacity: 3},
},
},
Runtime: config.RuntimeConf{Concurrency: 2},
// Providers is empty → legacy adapter fallback
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
snap := snaps[0]
// Must return CLI + ollama-local adapter snapshots.
if len(snap.ProviderSnapshots) != 2 {
t.Fatalf("expected 2 adapter snapshots (cli + ollama-local), got %d: %+v",
len(snap.ProviderSnapshots), snap.ProviderSnapshots)
}
adapters := map[string]bool{}
for _, p := range snap.ProviderSnapshots {
adapters[p.Adapter] = true
}
if !adapters["cli"] || !adapters["ollama-local"] {
t.Errorf("expected cli and ollama-local adapter snapshots, got: %v", adapters)
}
}
func TestListNodeSnapshotsProviderIdDiffersFromAdapterKey(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-p-1", Alias: "node-p-1"})
// Node has providers[] with provider id "provider-vllm-primary" and adapter="vllm-gpu".
// The adapter field points to an existing OllamaInstance named "vllm-gpu" (used here as a stand-in for the adapter key).
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-p-1",
Adapters: config.AdaptersConf{
OllamaInstances: []config.OllamaInstanceConf{
{
Name: "vllm-gpu",
Enabled: true,
Capacity: 4,
},
},
},
Providers: []config.NodeProviderConf{
{
ID: "provider-vllm-primary",
Type: "vllm",
Category: config.CategoryAPI,
Adapter: "vllm-gpu", // adapter key differs from provider id
Models: []string{"nvidia/Qwen3.6-35B"},
Capacity: 4,
},
},
Runtime: config.RuntimeConf{Concurrency: 2},
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
// Inject tracking state the way provider-pool dispatch does it:
// inflightByRun entries keyed by providerID, and queue candidates with providerID set.
svc.queue.mu.Lock()
svc.queue.inflightByRun["run-vp-001"] = inflightRec{
groupKey: "test-group",
nodeID: "node-p-1",
providerID: "provider-vllm-primary",
}
svc.queue.inflightByRun["run-vp-002"] = inflightRec{
groupKey: "test-group",
nodeID: "node-p-1",
providerID: "provider-vllm-primary",
}
svc.queue.groups["test-group"] = &modelQueueGroup{
key: "test-group",
adapter: "vllm-gpu",
inflight: map[string]int{"node-p-1": 2},
queue: []*queueItem{
{
candidates: []candidateNode{
{entry: reg.All()[0], capacity: 4, providerID: "provider-vllm-primary"},
},
},
},
}
svc.queue.mu.Unlock()
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
snap := snaps[0]
// Find the provider snapshot by its provider id.
var p *config.NodeProviderConf
for i := range snap.ProviderSnapshots {
ps := snap.ProviderSnapshots[i]
if ps.Id == "provider-vllm-primary" {
p = &config.NodeProviderConf{
Capacity: int(ps.Capacity),
}
// Verify queue stats match the injected queue state.
if ps.InFlight != 2 {
t.Errorf("expected InFlight=2 for provider-vllm-primary, got %d", ps.InFlight)
}
if ps.Queued != 1 {
t.Errorf("expected Queued=1 for provider-vllm-primary, got %d", ps.Queued)
}
if ps.Adapter != "vllm-gpu" {
t.Errorf("expected Adapter=vllm-gpu for provider-vllm-primary, got %s", ps.Adapter)
}
break
}
}
if p == nil {
t.Fatalf("expected provider snapshot with id=provider-vllm-primary, got %d provider snapshots", len(snap.ProviderSnapshots))
}
}
func TestListNodeSnapshotsLongContextFields(t *testing.T) {
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{NodeID: "node-long-1", Alias: "node-long-1"})
store := edgenode.NewNodeStore()
rec := &edgenode.NodeRecord{
ID: "node-long-1",
Providers: []config.NodeProviderConf{
{
ID: "long-provider",
Type: "vllm",
Category: config.CategoryAPI,
Adapter: "vllm-gpu",
Models: []string{"long-model"},
Capacity: 4,
LongContextCapacity: 2,
},
},
Runtime: config.RuntimeConf{Concurrency: 2},
}
store.Add(rec)
bus := edgeevents.NewBus()
svc := New(reg, bus)
svc.SetNodeStore(store)
svc.queue.mu.Lock()
svc.queue.inflightByRun["run-lp-001"] = inflightRec{
groupKey: "test-group",
nodeID: "node-long-1",
providerID: "long-provider",
long: true,
}
svc.queue.groups["test-group"] = &modelQueueGroup{
key: "test-group",
adapter: "vllm-gpu",
inflight: map[string]int{"node-long-1": 1},
queue: []*queueItem{
{
candidates: []candidateNode{
{entry: reg.All()[0], capacity: 4, providerID: "long-provider"},
},
long: true,
},
},
}
svc.queue.mu.Unlock()
snaps := svc.ListNodeSnapshots()
if len(snaps) != 1 {
t.Fatalf("expected 1 node snapshot, got %d", len(snaps))
}
snap := snaps[0]
if len(snap.ProviderSnapshots) != 1 {
t.Fatalf("expected 1 provider snapshot, got %d", len(snap.ProviderSnapshots))
}
p := snap.ProviderSnapshots[0]
if p.Id != "long-provider" {
t.Errorf("expected long-provider, got %s", p.Id)
}
if p.LongContextCapacity != 2 {
t.Errorf("expected LongContextCapacity 2, got %d", p.LongContextCapacity)
}
if p.LongInFlight != 1 {
t.Errorf("expected LongInFlight 1, got %d", p.LongInFlight)
}
if p.LongQueued != 1 {
t.Errorf("expected LongQueued 1, got %d", p.LongQueued)
}
}