Add route declarations and automatic port allocation to MCP agent
Service definitions can now declare routes per component instead of manual port mappings: [[components.routes]] name = "rest" port = 8443 mode = "l4" The agent allocates free host ports at deploy time and injects $PORT/$PORT_<NAME> env vars into containers. Backward compatible: components with old-style ports= work unchanged. Changes: - Proto: RouteSpec message, routes + env fields on ComponentSpec - Servicedef: RouteDef parsing and validation from TOML - Registry: component_routes table with host_port tracking - Runtime: Env field on ContainerSpec, -e flag in BuildRunArgs - Agent: PortAllocator (random 10000-60000, availability check), deploy wiring for route→port mapping and env injection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
65
internal/agent/portalloc_test.go
Normal file
65
internal/agent/portalloc_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPortAllocator_Allocate(t *testing.T) {
|
||||
pa := NewPortAllocator()
|
||||
|
||||
port, err := pa.Allocate()
|
||||
if err != nil {
|
||||
t.Fatalf("allocate: %v", err)
|
||||
}
|
||||
if port < portRangeMin || port >= portRangeMax {
|
||||
t.Fatalf("port %d out of range [%d, %d)", port, portRangeMin, portRangeMax)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocator_NoDuplicates(t *testing.T) {
|
||||
pa := NewPortAllocator()
|
||||
|
||||
ports := make(map[int]bool)
|
||||
for range 20 {
|
||||
port, err := pa.Allocate()
|
||||
if err != nil {
|
||||
t.Fatalf("allocate: %v", err)
|
||||
}
|
||||
if ports[port] {
|
||||
t.Fatalf("duplicate port allocated: %d", port)
|
||||
}
|
||||
ports[port] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocator_Release(t *testing.T) {
|
||||
pa := NewPortAllocator()
|
||||
|
||||
port, err := pa.Allocate()
|
||||
if err != nil {
|
||||
t.Fatalf("allocate: %v", err)
|
||||
}
|
||||
|
||||
pa.Release(port)
|
||||
|
||||
// After release, the port should no longer be tracked as allocated.
|
||||
pa.mu.Lock()
|
||||
if pa.allocated[port] {
|
||||
t.Fatal("port should not be tracked after release")
|
||||
}
|
||||
pa.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestPortAllocator_PortIsFree(t *testing.T) {
|
||||
pa := NewPortAllocator()
|
||||
|
||||
port, err := pa.Allocate()
|
||||
if err != nil {
|
||||
t.Fatalf("allocate: %v", err)
|
||||
}
|
||||
|
||||
// The port should be free (we only track it, we don't hold the listener).
|
||||
if !isPortFree(port) {
|
||||
t.Fatalf("allocated port %d should be free on the system", port)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user