diff --git a/sandbox/v2/DESIGN.md b/sandbox/v2/DESIGN.md index c8dac981..243589cc 100644 --- a/sandbox/v2/DESIGN.md +++ b/sandbox/v2/DESIGN.md @@ -627,11 +627,11 @@ YAO_REFRESH_TOKEN= ```go var ( - ErrNotAvailable = errors.New("sandbox: not available (no pools configured)") + ErrNotAvailable = errors.New("sandbox: not available (no nodes registered)") ErrNotFound = errors.New("sandbox: not found") ErrLimitExceeded = errors.New("sandbox: limit exceeded") - ErrPoolNotFound = errors.New("sandbox: pool not found") - ErrPoolInUse = errors.New("sandbox: pool has running boxes") + ErrNodeNotFound = errors.New("sandbox: node not found") + ErrNodeMissing = errors.New("sandbox: node ID missing") ) ``` diff --git a/sandbox/v2/IMPL.md b/sandbox/v2/IMPL.md index 418ae85d..e3c597c8 100644 --- a/sandbox/v2/IMPL.md +++ b/sandbox/v2/IMPL.md @@ -22,11 +22,10 @@ Reference: [DESIGN.md](./DESIGN.md) | File | What | Status | |------|------|--------| | `sandbox.go` | `Init()`, `M()`, global singleton | DONE | -| `manager.go` | Manager: Create/Get/GetOrCreate/List/Remove/Cleanup/Close, Start (container recovery), AddPool/RemovePool/Pools, Heartbeat, SetGRPCPort, SetWorkspaceManager, ImageExists/PullImage/EnsureImage | DONE | +| `manager.go` | Manager: Create/Get/GetOrCreate/List/Remove/Cleanup/Close, Start (container recovery), Nodes, Heartbeat, ImageExists/PullImage/EnsureImage | DONE | | `box.go` | Box: Exec, Stream, Attach, Workspace, VNC, Proxy, Start/Stop/Remove, Info, touch/lastActiveTime/idleTimeout/maxLifetime/stopTimeout | DONE | -| `types.go` | LifecyclePolicy (OneShot/Session/LongRunning/Persistent), Pool, PoolInfo, PortMapping, CreateOptions (with WorkspaceID/MountMode/MountPath), ListOptions, ExecOption/ExecResult/ExecStream, AttachOption/ServiceConn, ImagePullOptions/RegistryAuth, BoxInfo, DefaultStopTimeout | DONE | -| `config.go` | Config struct | DONE | -| `errors.go` | ErrNotAvailable, ErrNotFound, ErrLimitExceeded, ErrPoolNotFound, ErrPoolInUse | DONE | +| `types.go` | LifecyclePolicy (OneShot/Session/LongRunning/Persistent), NodeID, PortMapping, CreateOptions (with WorkspaceID/MountMode/MountPath), ListOptions, ExecOption/ExecResult/ExecStream, AttachOption/ServiceConn, ImagePullOptions/RegistryAuth, BoxInfo, DefaultStopTimeout | DONE | +| `errors.go` | ErrNotAvailable, ErrNotFound, ErrNodeNotFound, ErrNodeMissing | DONE | | `grpc.go` | BuildGRPCEnv (sandbox ID + gRPC addr only; token injection is caller's responsibility via Env) | DONE | ### workspace Module — DONE @@ -51,7 +50,7 @@ Reference: [DESIGN.md](./DESIGN.md) | `box_image_test.go` | ImageExists (Docker+K8s), PullImage (progress+K8s no-op), EnsureImage, bad ref | DONE | | `grpc_test.go` | Token creation/revocation, env var building (local vs remote) | DONE | | `bench_test.go` | ContainerLifecycle, Create, Exec, ExecHeavy, Remove, Info, StopStart, WorkspaceReadWrite | DONE | -| `testutils_test.go` | testPools (local/remote/k8s), setupManager, createTestBox, ensureTestImage | DONE | +| `testutils_test.go` | testNodes (local/remote/k8s), setupManager, setupManagerForNode, createTestBox, ensureTestImage | DONE | | `export_test.go` | ResetForTest | DONE | | **workspace** | | | | `workspace_test.go` | Create (auto/explicit ID, labels, invalid node), Get, List (owner/node filter), Update (name/labels), Delete, Nodes, NodeForWorkspace, AddPool/RemovePool, MountPath | DONE | @@ -88,7 +87,7 @@ Box and Host now share a single `Computer` interface (`types.go`). Both `sandbox |------|---------|------|--------| | Computer interface | `sandbox/v2/types.go` | `Computer` interface: Exec, Stream, VNC, Proxy, ComputerInfo, BindWorkplace, Workplace | DONE | | Host implementation | `sandbox/v2/host.go` | `Host` struct implements `Computer` via tai HostExec + VNC/Proxy | DONE | -| ComputerInfo | `sandbox/v2/types.go` | `ComputerInfo` struct with Kind, Pool, TaiID, System, Capabilities, box-specific fields | DONE | +| ComputerInfo | `sandbox/v2/types.go` | `ComputerInfo` struct with Kind, NodeID, TaiID, System, Capabilities, box-specific fields | DONE | ### JSAPI — DONE @@ -224,7 +223,7 @@ Manager injects these labels at creation time: managed-by=yao-sandbox sandbox-id= sandbox-owner= -sandbox-pool= +sandbox-node-id= sandbox-policy= workspace-id= (if WorkspaceID set) ``` @@ -237,14 +236,14 @@ When `CreateOptions.WorkspaceID` is set: ``` 1. NodeForWorkspace(wsID) → node name -2. Force pool = node name +2. Force nodeID = node name 3. MountPath(wsID) → hostDir 4. Bind: hostDir:/workspace:rw ``` ### Multi-Mode Testing -`testPools()` returns all available pool configurations: +`testNodes()` returns all available node configurations: ```go func testPools() []poolConfig { diff --git a/sandbox/v2/TEST.md b/sandbox/v2/TEST.md index a5a3459e..6434d830 100644 --- a/sandbox/v2/TEST.md +++ b/sandbox/v2/TEST.md @@ -426,7 +426,7 @@ func TestRemovePool_InUse(t *testing.T) { // manager_test.go package sandbox_test -func TestMultiPool(t *testing.T) { +func TestMultiNode(t *testing.T) { skipIfNoDocker(t) skipIfNoTai(t) cleanup := setupManagerWithRemote(t) diff --git a/sandbox/v2/bench_test.go b/sandbox/v2/bench_test.go index c242995d..eb535961 100644 --- a/sandbox/v2/bench_test.go +++ b/sandbox/v2/bench_test.go @@ -13,7 +13,7 @@ import ( // BenchmarkContainerLifecycle measures the full Create → Exec → Remove cycle. func BenchmarkContainerLifecycle(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { m := setupManagerForBench(b, &pc) @@ -44,7 +44,7 @@ func BenchmarkContainerLifecycle(b *testing.B) { // BenchmarkCreate measures container creation time only. func BenchmarkCreate(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { m := setupManagerForBench(b, &pc) @@ -73,7 +73,7 @@ func BenchmarkCreate(b *testing.B) { // BenchmarkExec measures command execution latency on a pre-created container. func BenchmarkExec(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { m := setupManagerForBench(b, &pc) @@ -95,7 +95,7 @@ func BenchmarkExec(b *testing.B) { // BenchmarkExecHeavy measures execution of a heavier command (write + read file). func BenchmarkExecHeavy(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { m := setupManagerForBench(b, &pc) @@ -118,7 +118,7 @@ func BenchmarkExecHeavy(b *testing.B) { // BenchmarkRemove measures container removal time. func BenchmarkRemove(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { m := setupManagerForBench(b, &pc) @@ -148,7 +148,7 @@ func BenchmarkRemove(b *testing.B) { // BenchmarkInfo measures Info() latency on a running container. func BenchmarkInfo(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { m := setupManagerForBench(b, &pc) @@ -167,7 +167,7 @@ func BenchmarkInfo(b *testing.B) { // BenchmarkStopStart measures Stop → Start cycle time. func BenchmarkStopStart(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { if pc.Name == "k8s" { @@ -191,7 +191,7 @@ func BenchmarkStopStart(b *testing.B) { // BenchmarkWorkspaceReadWrite measures workspace file read/write via container Box. func BenchmarkWorkspaceReadWrite(b *testing.B) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc b.Run(pc.Name, func(b *testing.B) { m := setupManagerForBench(b, &pc) @@ -223,7 +223,7 @@ func BenchmarkWorkspaceReadWrite(b *testing.B) { // --- helpers --- -func setupManagerForBench(b *testing.B, pc *poolConfig) *sandbox.Manager { +func setupManagerForBench(b *testing.B, pc *nodeConfig) *sandbox.Manager { b.Helper() reg := registry.Global() if reg == nil { @@ -240,29 +240,29 @@ func setupManagerForBench(b *testing.B, pc *poolConfig) *sandbox.Manager { return m } -func ensureTestImageBench(b *testing.B, m *sandbox.Manager, pool string) { +func ensureTestImageBench(b *testing.B, m *sandbox.Manager, nodeID string) { b.Helper() ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() - if err := m.EnsureImage(ctx, pool, testImage(), sandbox.ImagePullOptions{}); err != nil { + if err := m.EnsureImage(ctx, nodeID, testImage(), sandbox.ImagePullOptions{}); err != nil { b.Fatalf("EnsureImage: %v", err) } } func createBoxForBench(b *testing.B, m *sandbox.Manager) *sandbox.Box { b.Helper() - pools := m.Pools() - var poolName string - if len(pools) > 0 { - poolName = pools[0].TaiID - ensureTestImageBench(b, m, poolName) + nodes := m.Nodes() + var nodeID string + if len(nodes) > 0 { + nodeID = nodes[0].TaiID + ensureTestImageBench(b, m, nodeID) } ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() box, err := m.Create(ctx, sandbox.CreateOptions{ - Image: testImage(), - Owner: "bench", - Pool: poolName, + Image: testImage(), + Owner: "bench", + NodeID: nodeID, }) if err != nil { b.Fatalf("Create: %v", err) diff --git a/sandbox/v2/box.go b/sandbox/v2/box.go index c55416fb..67d9999d 100644 --- a/sandbox/v2/box.go +++ b/sandbox/v2/box.go @@ -15,7 +15,7 @@ import ( type Box struct { id string containerID string - pool string + nodeID string owner string policy LifecyclePolicy labels map[string]string @@ -39,13 +39,13 @@ var _ Computer = (*Box)(nil) func (b *Box) ID() string { return b.id } func (b *Box) Owner() string { return b.owner } func (b *Box) ContainerID() string { return b.containerID } -func (b *Box) Pool() string { return b.pool } +func (b *Box) NodeID() string { return b.nodeID } // ComputerInfo returns identity and registry information for this Box. func (b *Box) ComputerInfo() ComputerInfo { return ComputerInfo{ Kind: "box", - Pool: b.pool, + NodeID: b.nodeID, Status: "online", BoxID: b.id, ContainerID: b.containerID, @@ -79,7 +79,7 @@ func (b *Box) Exec(ctx context.Context, cmd []string, opts ...ExecOption) (*Exec o(cfg) } - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return nil, err } @@ -113,7 +113,7 @@ func (b *Box) Stream(ctx context.Context, cmd []string, opts ...ExecOption) (*Ex o(cfg) } - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return nil, err } @@ -143,7 +143,7 @@ func (b *Box) Attach(ctx context.Context, port int, opts ...AttachOption) (*Serv o(cfg) } - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return nil, err } @@ -189,7 +189,7 @@ func (b *Box) Workspace() workspace.FS { if sessionID == "" { sessionID = b.id } - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return nil } @@ -203,7 +203,7 @@ func (b *Box) WorkspaceID() string { return b.workspaceID } // VNC returns the VNC WebSocket URL. func (b *Box) VNC(ctx context.Context) (string, error) { b.touch() - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return "", err } @@ -213,7 +213,7 @@ func (b *Box) VNC(ctx context.Context) (string, error) { // Proxy returns the HTTP URL for a service on the given port inside the sandbox. func (b *Box) Proxy(ctx context.Context, port int, path string) (string, error) { b.touch() - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return "", err } @@ -222,7 +222,7 @@ func (b *Box) Proxy(ctx context.Context, port int, path string) (string, error) // Start starts a stopped sandbox. func (b *Box) Start(ctx context.Context) error { - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return err } @@ -231,7 +231,7 @@ func (b *Box) Start(ctx context.Context) error { // Stop stops the sandbox without removing it. func (b *Box) Stop(ctx context.Context) error { - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return err } @@ -245,7 +245,7 @@ func (b *Box) Remove(ctx context.Context) error { // Info returns current sandbox status. func (b *Box) Info(ctx context.Context) (*BoxInfo, error) { - client, err := b.manager.getPool(b.pool) + client, err := b.manager.getNode(b.nodeID) if err != nil { return nil, err } @@ -258,7 +258,7 @@ func (b *Box) Info(ctx context.Context) (*BoxInfo, error) { return &BoxInfo{ ID: b.id, ContainerID: b.containerID, - Pool: b.pool, + NodeID: b.nodeID, Owner: b.owner, Status: info.Status, Policy: b.policy, diff --git a/sandbox/v2/box_attach_test.go b/sandbox/v2/box_attach_test.go index 0bfdfdcb..78156387 100644 --- a/sandbox/v2/box_attach_test.go +++ b/sandbox/v2/box_attach_test.go @@ -65,10 +65,10 @@ func TestAttachWS(t *testing.T) { t.Skip("WebSocket test requires tai-sandbox-test image with ws-echo service") } - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) { co.Ports = []sandbox.PortMapping{ {ContainerPort: 9800, HostPort: 0, Protocol: "tcp"}, @@ -114,10 +114,10 @@ func TestAttachSSE(t *testing.T) { t.Skip("SSE test requires tai-sandbox-test image with sse-server service") } - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) { co.Ports = []sandbox.PortMapping{ {ContainerPort: 9801, HostPort: 0, Protocol: "tcp"}, @@ -164,10 +164,10 @@ func TestVNCURL(t *testing.T) { t.Skip("VNC test requires tai-sandbox-test image with VNC desktop") } - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) { co.VNC = true }) @@ -195,10 +195,10 @@ func TestVNCConnect(t *testing.T) { t.Skip("VNC test requires tai-sandbox-test image with VNC desktop") } - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) { co.VNC = true }) diff --git a/sandbox/v2/box_image_test.go b/sandbox/v2/box_image_test.go index c4f56fb3..6c3927c9 100644 --- a/sandbox/v2/box_image_test.go +++ b/sandbox/v2/box_image_test.go @@ -11,12 +11,12 @@ import ( ) func TestImageExists(t *testing.T) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { if pc.Name == "k8s" { t.Run("always_true", func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() exists, err := m.ImageExists(ctx, pc.TaiID, "anything:nonexistent") @@ -26,7 +26,7 @@ func TestImageExists(t *testing.T) { return } - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() @@ -46,12 +46,12 @@ func TestImageExists(t *testing.T) { } func TestImagePull(t *testing.T) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { if pc.Name == "k8s" { t.Run("noop", func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() ch, err := m.PullImage(ctx, pc.TaiID, "alpine:latest", sandbox.ImagePullOptions{}) @@ -61,7 +61,7 @@ func TestImagePull(t *testing.T) { return } - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() @@ -84,10 +84,10 @@ func TestImagePull(t *testing.T) { } func TestEnsureImage(t *testing.T) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() @@ -104,13 +104,13 @@ func TestEnsureImage(t *testing.T) { } func TestEnsureImage_BadRef(t *testing.T) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc if pc.Name == "k8s" { continue } t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() diff --git a/sandbox/v2/box_test.go b/sandbox/v2/box_test.go index 8de47dd2..6870e164 100644 --- a/sandbox/v2/box_test.go +++ b/sandbox/v2/box_test.go @@ -13,10 +13,10 @@ import ( func TestBoxExec(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -36,10 +36,10 @@ func TestBoxExec(t *testing.T) { func TestBoxExecWithOptions(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) ctx := context.Background() @@ -59,10 +59,10 @@ func TestBoxExecWithOptions(t *testing.T) { func TestBoxStream(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) ctx := context.Background() @@ -93,10 +93,10 @@ func TestBoxStream(t *testing.T) { func TestBoxWorkspace(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) ws := box.Workspace() @@ -135,10 +135,10 @@ func TestBoxWorkspace(t *testing.T) { func TestBoxInfo(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) ctx := context.Background() @@ -162,10 +162,10 @@ func TestBoxInfo(t *testing.T) { func TestBoxStopStart(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) ctx := context.Background() @@ -191,16 +191,16 @@ func TestBoxStopStart(t *testing.T) { func TestBoxGetOrCreate(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ctx := context.Background() box1, err := m.GetOrCreate(ctx, sandbox.CreateOptions{ - ID: "goc-" + pc.Name, - Image: testImage(), - Owner: "test-user", - Pool: pc.TaiID, + ID: "goc-" + pc.Name, + Image: testImage(), + Owner: "test-user", + NodeID: pc.TaiID, }) if err != nil { t.Fatalf("GetOrCreate first: %v", err) @@ -208,10 +208,10 @@ func TestBoxGetOrCreate(t *testing.T) { defer m.Remove(ctx, box1.ID()) box2, err := m.GetOrCreate(ctx, sandbox.CreateOptions{ - ID: "goc-" + pc.Name, - Image: testImage(), - Owner: "test-user", - Pool: pc.TaiID, + ID: "goc-" + pc.Name, + Image: testImage(), + Owner: "test-user", + NodeID: pc.TaiID, }) if err != nil { t.Fatalf("GetOrCreate second: %v", err) diff --git a/sandbox/v2/box_workspace_test.go b/sandbox/v2/box_workspace_test.go index 40525a50..aff1053f 100644 --- a/sandbox/v2/box_workspace_test.go +++ b/sandbox/v2/box_workspace_test.go @@ -15,7 +15,7 @@ import ( func TestWorkspaceID_Set(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { sbm, wsm := setupManagerWithWorkspace(t, &pc) @@ -41,10 +41,10 @@ func TestWorkspaceID_Set(t *testing.T) { func TestWorkspaceID_Empty(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) assert.Empty(t, box.WorkspaceID()) }) @@ -54,7 +54,7 @@ func TestWorkspaceID_Empty(t *testing.T) { func TestWorkspace_NodeRouting(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { sbm, wsm := setupManagerWithWorkspace(t, &pc) @@ -72,7 +72,7 @@ func TestWorkspace_NodeRouting(t *testing.T) { co.WorkspaceID = ws.ID }) - assert.Equal(t, pc.TaiID, box.Pool()) + assert.Equal(t, pc.TaiID, box.NodeID()) }) } } @@ -80,7 +80,7 @@ func TestWorkspace_NodeRouting(t *testing.T) { func TestWorkspace_InvalidID(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { sbm, _ := setupManagerWithWorkspace(t, &pc) @@ -103,7 +103,7 @@ func TestWorkspace_InvalidID(t *testing.T) { func TestWorkspace_BindMountLocal(t *testing.T) { skipIfNoDocker(t) - pc := poolConfig{Name: "local", Addr: testLocalAddr()} + pc := nodeConfig{Name: "local", Addr: testLocalAddr()} sbm, wsm := setupManagerWithWorkspace(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -129,7 +129,7 @@ func TestWorkspace_BindMountLocal(t *testing.T) { func TestWorkspace_ContainerWriteBack(t *testing.T) { skipIfNoDocker(t) - pc := poolConfig{Name: "local", Addr: testLocalAddr()} + pc := nodeConfig{Name: "local", Addr: testLocalAddr()} sbm, wsm := setupManagerWithWorkspace(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -156,7 +156,7 @@ func TestWorkspace_ContainerWriteBack(t *testing.T) { func TestWorkspace_ReadOnlyMount(t *testing.T) { skipIfNoDocker(t) - pc := poolConfig{Name: "local", Addr: testLocalAddr()} + pc := nodeConfig{Name: "local", Addr: testLocalAddr()} sbm, wsm := setupManagerWithWorkspace(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -189,7 +189,7 @@ func TestWorkspace_ReadOnlyMount(t *testing.T) { func TestWorkspace_CustomMountPath(t *testing.T) { skipIfNoDocker(t) - pc := poolConfig{Name: "local", Addr: testLocalAddr()} + pc := nodeConfig{Name: "local", Addr: testLocalAddr()} sbm, wsm := setupManagerWithWorkspace(t, &pc) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -216,7 +216,7 @@ func TestWorkspace_CustomMountPath(t *testing.T) { func TestWorkspace_BoxWorkspaceFS(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { if pc.Name == "local" { continue } @@ -254,7 +254,7 @@ func TestWorkspace_BoxWorkspaceFS(t *testing.T) { func TestWorkspace_LabelPersistence(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { sbm, wsm := setupManagerWithWorkspace(t, &pc) diff --git a/sandbox/v2/docs/API.md b/sandbox/v2/docs/API.md index bffbe3f6..32f81b73 100644 --- a/sandbox/v2/docs/API.md +++ b/sandbox/v2/docs/API.md @@ -2,7 +2,7 @@ Package: `github.com/yaoapp/yao/sandbox/v2` -Sandbox V2 manages sandboxes through a pool of Tai nodes. Two primary abstractions: +Sandbox V2 manages sandboxes through a set of Tai nodes. Two primary abstractions: - **Box** — a container (Docker or K8s pod). Created via `Manager.Create`. - **Host** — the Tai host machine itself. Obtained via `Manager.Host` (no Create needed). @@ -42,10 +42,10 @@ mgr := sandbox.M() ## Node Discovery -Sandbox V2 no longer uses a static pool configuration. Nodes are discovered dynamically +Sandbox V2 no longer uses static node configuration. Nodes are discovered dynamically through `tai/registry`. Each Tai node registers itself with a unique **TaiID** (e.g. `"192.168.1.10-19100"` for direct mode, `"local"` for Docker). The TaiID is used as the -`Pool` identifier in `CreateOptions`, `ListOptions`, `Host()`, `ImageExists()`, etc. +`NodeID` identifier in `CreateOptions`, `ListOptions`, `Host()`, `ImageExists()`, etc. --- @@ -72,7 +72,7 @@ const ( func (m *Manager) Start(ctx context.Context) error ``` -Recovers existing containers from all pools and starts the background cleanup loop (1 min interval). +Recovers existing containers from all nodes and starts the background cleanup loop (1 min interval). ```go ctx := context.Background() @@ -85,7 +85,7 @@ err := sandbox.M().Start(ctx) func (m *Manager) Close() error ``` -Stops the cleanup loop and closes all pool connections. +Stops the cleanup loop and closes all node connections. ### Create @@ -99,7 +99,7 @@ Creates and starts a new sandbox container. Returns a `Box` handle. box, err := sandbox.M().Create(ctx, sandbox.CreateOptions{ Image: "alpine:latest", Owner: "user-123", - Pool: "192.168.1.10-19100", // TaiID from registry + NodeID: "192.168.1.10-19100", // TaiID from registry Policy: sandbox.Session, WorkDir: "/workspace", Env: map[string]string{"LANG": "en_US.UTF-8"}, @@ -121,13 +121,13 @@ box, err := sandbox.M().Create(ctx, sandbox.CreateOptions{ ### Host ```go -func (m *Manager) Host(ctx context.Context, pool string) (*Host, error) +func (m *Manager) Host(ctx context.Context, nodeID string) (*Host, error) ``` -Returns a `Host` handle for the given pool (identified by TaiID). Unlike `Create`, no +Returns a `Host` handle for the given node (identified by TaiID). Unlike `Create`, no container is provisioned — the Host is available as long as the Tai server reports -`host_exec` capability. Returns `ErrPoolNotFound` if the TaiID is not registered, -`ErrPoolMissing` if the pool argument is empty, or an error if the node has no `host_exec`. +`host_exec` capability. Returns `ErrNodeNotFound` if the TaiID is not registered, +`ErrNodeMissing` if the nodeID argument is empty, or an error if the node has no `host_exec`. ```go host, err := sandbox.M().Host(ctx, "192.168.1.10-19100") @@ -172,7 +172,7 @@ Returns all sandboxes matching the given filters. Empty fields = no filter. ```go boxes, err := sandbox.M().List(ctx, sandbox.ListOptions{ Owner: "user-123", - Pool: "192.168.1.10-19100", + NodeID: "192.168.1.10-19100", Labels: map[string]string{"project": "demo"}, }) ``` @@ -210,16 +210,16 @@ Updates a sandbox's last-active timestamp. Called by the gRPC heartbeat service. err := sandbox.M().Heartbeat("sb-12345", true, 3) ``` -### Pools +### Nodes ```go -func (m *Manager) Pools() []registry.NodeSnapshot +func (m *Manager) Nodes() []registry.NodeSnapshot ``` Returns all registered Tai nodes from the `tai/registry`. ```go -for _, n := range sandbox.M().Pools() { +for _, n := range sandbox.M().Nodes() { fmt.Printf("tai_id=%s mode=%s addr=%s status=%s\n", n.TaiID, n.Mode, n.Addr, n.Status) } @@ -228,11 +228,11 @@ for _, n := range sandbox.M().Pools() { ### ImageExists ```go -func (m *Manager) ImageExists(ctx context.Context, pool, ref string) (bool, error) +func (m *Manager) ImageExists(ctx context.Context, nodeID, ref string) (bool, error) ``` -Reports whether the given image ref exists on the target pool node. -Returns `(true, nil)` when the pool has no image service (e.g. K8s — kubelet handles pulls). +Reports whether the given image ref exists on the target node. +Returns `(true, nil)` when the node has no image service (e.g. K8s — kubelet handles pulls). ```go exists, err := sandbox.M().ImageExists(ctx, "192.168.1.10-19100", "alpine:latest") @@ -241,11 +241,11 @@ exists, err := sandbox.M().ImageExists(ctx, "192.168.1.10-19100", "alpine:latest ### PullImage ```go -func (m *Manager) PullImage(ctx context.Context, pool, ref string, opts ImagePullOptions) (<-chan taisandbox.PullProgress, error) +func (m *Manager) PullImage(ctx context.Context, nodeID, ref string, opts ImagePullOptions) (<-chan taisandbox.PullProgress, error) ``` -Pulls an image to the target pool node. Returns a channel of `taisandbox.PullProgress` -(from `github.com/yaoapp/yao/tai/sandbox`). Returns `(nil, nil)` when the pool has no image +Pulls an image to the target node. Returns a channel of `taisandbox.PullProgress` +(from `github.com/yaoapp/yao/tai/sandbox`). Returns `(nil, nil)` when the node has no image service (e.g. K8s). `PullProgress` fields: `Status string`, `Layer string`, `Current int64`, `Total int64`, `Error string`. @@ -266,7 +266,7 @@ for p := range ch { ### EnsureImage ```go -func (m *Manager) EnsureImage(ctx context.Context, pool, ref string, opts ImagePullOptions) error +func (m *Manager) EnsureImage(ctx context.Context, nodeID, ref string, opts ImagePullOptions) error ``` Checks if the image exists; if not, pulls it and blocks until complete. @@ -287,7 +287,7 @@ A `Box` is a handle to a running sandbox container. func (b *Box) ID() string func (b *Box) Owner() string func (b *Box) ContainerID() string -func (b *Box) Pool() string +func (b *Box) NodeID() string func (b *Box) WorkspaceID() string ``` @@ -423,12 +423,12 @@ fmt.Printf("status=%s processes=%d vnc=%v created=%s\n", ## Host A `Host` represents a Tai host machine execution environment, distinct from `Box` (containers). -No `Create` call is needed — a Host is available as long as the pool's Tai server reports `host_exec`. +No `Create` call is needed — a Host is available as long as the node's Tai server reports `host_exec`. ### Accessors ```go -func (h *Host) Pool() string +func (h *Host) NodeID() string ``` ### Exec @@ -539,7 +539,7 @@ type CreateOptions struct { ID string Owner string Labels map[string]string - Pool string // TaiID from registry (required unless WorkspaceID routes to a node) + NodeID string // TaiID from registry (required unless WorkspaceID routes to a node) Image string // required WorkDir string // default "/workspace" User string // container user @@ -563,7 +563,7 @@ type CreateOptions struct { ```go type ListOptions struct { Owner string - Pool string + NodeID string Labels map[string]string } ``` @@ -619,7 +619,7 @@ type ServiceConn struct { type BoxInfo struct { ID string ContainerID string - Pool string + NodeID string Owner string Status string // "running", "stopped", etc. Policy LifecyclePolicy @@ -676,10 +676,10 @@ type HostExecStream struct { ```go var ( - ErrNotAvailable = errors.New("sandbox: not available (no pools configured)") + ErrNotAvailable = errors.New("sandbox: not available (no nodes registered)") ErrNotFound = errors.New("sandbox: not found") - ErrPoolNotFound = errors.New("sandbox: pool not found") - ErrPoolMissing = errors.New("sandbox: pool name is required") + ErrNodeNotFound = errors.New("sandbox: node not found") + ErrNodeMissing = errors.New("sandbox: node ID is required") ) ``` diff --git a/sandbox/v2/errors.go b/sandbox/v2/errors.go index bfc0de8d..55c6285f 100644 --- a/sandbox/v2/errors.go +++ b/sandbox/v2/errors.go @@ -3,8 +3,8 @@ package sandbox import "errors" var ( - ErrNotAvailable = errors.New("sandbox: not available (no pools configured)") + ErrNotAvailable = errors.New("sandbox: not available (no nodes registered)") ErrNotFound = errors.New("sandbox: not found") - ErrPoolNotFound = errors.New("sandbox: pool not found") - ErrPoolMissing = errors.New("sandbox: pool name is required") + ErrNodeNotFound = errors.New("sandbox: node not found") + ErrNodeMissing = errors.New("sandbox: node ID is required") ) diff --git a/sandbox/v2/host.go b/sandbox/v2/host.go index 130c4a92..7a8a6df3 100644 --- a/sandbox/v2/host.go +++ b/sandbox/v2/host.go @@ -16,7 +16,7 @@ import ( // // Host implements the Computer interface. type Host struct { - pool string + nodeID string workplaceID string manager *Manager } @@ -25,12 +25,12 @@ type Host struct { var _ Computer = (*Host)(nil) // ComputerInfo returns identity and registry information for the host. -// Registry-level details (TaiID, System, etc.) are populated when the pool -// is backed by a registered Tai node; otherwise only Kind and Pool are set. +// Registry-level details (TaiID, System, etc.) are populated when the node +// is backed by a registered Tai node; otherwise only Kind and NodeID are set. func (h *Host) ComputerInfo() ComputerInfo { return ComputerInfo{ Kind: "host", - Pool: h.pool, + NodeID: h.nodeID, Status: "online", } } @@ -42,14 +42,14 @@ func (h *Host) Exec(ctx context.Context, cmd []string, opts ...ExecOption) (*Exe return nil, fmt.Errorf("sandbox: empty command") } - client, err := h.manager.getPool(h.pool) + client, err := h.manager.getNode(h.nodeID) if err != nil { return nil, err } he := client.HostExec() if he == nil { - return nil, fmt.Errorf("sandbox: host_exec not available on pool %q", h.pool) + return nil, fmt.Errorf("sandbox: host_exec not available on node %q", h.nodeID) } cfg := &execConfig{} @@ -98,14 +98,14 @@ func (h *Host) Stream(ctx context.Context, cmd []string, opts ...ExecOption) (*E return nil, fmt.Errorf("sandbox: empty command") } - client, err := h.manager.getPool(h.pool) + client, err := h.manager.getNode(h.nodeID) if err != nil { return nil, err } he := client.HostExec() if he == nil { - return nil, fmt.Errorf("sandbox: host_exec not available on pool %q", h.pool) + return nil, fmt.Errorf("sandbox: host_exec not available on node %q", h.nodeID) } cfg := &execConfig{} @@ -187,7 +187,7 @@ func (h *Host) Stream(ctx context.Context, cmd []string, opts ...ExecOption) (*E // VNC returns the VNC WebSocket URL for the Tai host machine. // Uses the special __host__ identifier to route to localhost:5900 on the Tai server. func (h *Host) VNC(ctx context.Context) (string, error) { - client, err := h.manager.getPool(h.pool) + client, err := h.manager.getNode(h.nodeID) if err != nil { return "", err } @@ -197,7 +197,7 @@ func (h *Host) VNC(ctx context.Context) (string, error) { // Proxy returns the HTTP URL for a service running on the Tai host machine. // Uses the special __host__ identifier to route to localhost:{port} on the Tai server. func (h *Host) Proxy(ctx context.Context, port int, path string) (string, error) { - client, err := h.manager.getPool(h.pool) + client, err := h.manager.getNode(h.nodeID) if err != nil { return "", err } @@ -215,15 +215,15 @@ func (h *Host) Workplace() workspace.FS { if h.workplaceID == "" { return nil } - client, err := h.manager.getPool(h.pool) + client, err := h.manager.getNode(h.nodeID) if err != nil { return nil } return client.Workspace(h.workplaceID) } -// Pool returns the pool name this Host belongs to. -func (h *Host) Pool() string { return h.pool } +// NodeID returns the node ID this Host belongs to. +func (h *Host) NodeID() string { return h.nodeID } // nopWriteCloser wraps an io.Writer with a no-op Close. type nopWriteCloser struct{ io.Writer } diff --git a/sandbox/v2/host_test.go b/sandbox/v2/host_test.go index 2d9301e3..303feca6 100644 --- a/sandbox/v2/host_test.go +++ b/sandbox/v2/host_test.go @@ -15,8 +15,8 @@ import ( func setupHostManager(t *testing.T, tgt *hostExecTarget) *sandbox.Manager { t.Helper() addr := fmt.Sprintf("tai://%s", tgt.Addr) - m, pools := setupManager(t, poolConfig{Name: tgt.Name, Addr: addr}) - tgt.TaiID = pools[0].TaiID + m, nodes := setupManager(t, nodeConfig{Name: tgt.Name, Addr: addr}) + tgt.TaiID = nodes[0].TaiID return m } @@ -384,8 +384,8 @@ func TestHost_ComputerInfo(t *testing.T) { if info.Kind != "host" { t.Errorf("Kind = %q, want 'host'", info.Kind) } - if info.Pool != tgt.TaiID { - t.Errorf("Pool = %q, want %q", info.Pool, tgt.TaiID) + if info.NodeID != tgt.TaiID { + t.Errorf("NodeID = %q, want %q", info.NodeID, tgt.TaiID) } }) } @@ -414,7 +414,7 @@ func TestHost_ComputerInterface(t *testing.T) { } } -func TestHost_CreateRejectsNoContainerPool(t *testing.T) { +func TestHost_CreateRejectsNoContainerNode(t *testing.T) { tgt := findHostExecOnly(t) if tgt == nil { t.Skip("no host-exec-only target available") @@ -426,24 +426,24 @@ func TestHost_CreateRejectsNoContainerPool(t *testing.T) { defer cancel() _, err := m.Create(ctx, sandbox.CreateOptions{ - Image: "alpine:latest", - Owner: "test", - Pool: tgt.TaiID, + Image: "alpine:latest", + Owner: "test", + NodeID: tgt.TaiID, }) if err == nil { - t.Fatal("expected error for Create on host-exec-only pool, got nil") + t.Fatal("expected error for Create on host-exec-only node, got nil") } if !strings.Contains(err.Error(), "no container runtime") { t.Errorf("error = %q, want contains 'no container runtime'", err.Error()) } } -func TestHost_PoolNotFound(t *testing.T) { +func TestHost_NodeNotFound(t *testing.T) { skipIfNoHostExec(t) tgt := hostExecTargets()[0] m := setupHostManager(t, &tgt) - _, err := m.Host(context.Background(), "nonexistent-pool") + _, err := m.Host(context.Background(), "nonexistent-node") if err == nil { t.Fatal("expected error, got nil") } diff --git a/sandbox/v2/jsapi/API.md b/sandbox/v2/jsapi/API.md index 0f2807a5..5eb9c6b8 100644 --- a/sandbox/v2/jsapi/API.md +++ b/sandbox/v2/jsapi/API.md @@ -31,7 +31,7 @@ Create a new sandbox container. Returns a Computer (`kind = "box"`). If `options const pc = sandbox.Create({ image: "node:20", // required — container image owner: "user-123", // required — owner identifier - pool: "192.168.1.10-19100", // optional — TaiID from registry (required unless workspace_id routes to a node) + node_id: "192.168.1.10-19100", // optional — TaiID from registry (required unless workspace_id routes to a node) id: "my-sandbox", // optional — if set, uses GetOrCreate workdir: "/app", // optional — working directory user: "1000:1000", // optional — UID:GID @@ -59,7 +59,7 @@ Get an existing sandbox by ID. Returns a Computer (`kind = "box"`) or `null` if ```javascript const pc = sandbox.Get("my-sandbox") if (pc) { - console.log(pc.kind, pc.id, pc.owner, pc.pool) + console.log(pc.kind, pc.id, pc.owner, pc.node_id) } ``` @@ -74,8 +74,8 @@ const all = sandbox.List() // Filter by owner const mine = sandbox.List({ owner: "user-123" }) -// Filter by pool (TaiID) and labels -const gpu = sandbox.List({ pool: "10.0.0.5-19100", labels: { team: "ml" } }) +// Filter by node_id (TaiID) and labels +const gpu = sandbox.List({ node_id: "10.0.0.5-19100", labels: { team: "ml" } }) ``` Each element in the returned array: @@ -84,7 +84,7 @@ Each element in the returned array: { id: "sb-xxx", container_id: "abc123...", - pool: "192.168.1.10-19100", + node_id: "192.168.1.10-19100", owner: "user-123", status: "running", // "running"|"stopped"|"creating"|... image: "node:20", @@ -105,9 +105,9 @@ Remove a sandbox and its container. sandbox.Delete("my-sandbox") ``` -### sandbox.Host(pool) → Computer +### sandbox.Host(nodeID?) → Computer -Get a Computer (`kind = "host"`) for executing commands directly on the Tai host machine (no container). Only available when the node's Tai server has `host_exec` capability. The `pool` argument is the TaiID (e.g. `"192.168.1.10-19100"`). +Get a Computer (`kind = "host"`) for executing commands directly on the Tai host machine (no container). Only available when the node's Tai server has `host_exec` capability. The `nodeID` argument is the TaiID (e.g. `"192.168.1.10-19100"`). ```javascript const host = sandbox.Host("192.168.1.10-19100") @@ -131,7 +131,7 @@ List all registered nodes. ```javascript const nodes = sandbox.Nodes() nodes.forEach(function(n) { - console.log(n.tai_id, n.status, n.pool, n.system.os) + console.log(n.tai_id, n.status, n.display_name, n.system.os) }) ``` @@ -158,7 +158,7 @@ Use the `kind` property to check the type. Methods marked **box-only** throw an | `pc.kind` | string | `"box"` or `"host"` | | `pc.id` | string | Sandbox ID (box-only; empty for host) | | `pc.owner` | string | Owner identifier (box-only; empty for host) | -| `pc.pool` | string | TaiID (e.g. `"192.168.1.10-19100"`, `"local"`) | +| `pc.node_id` | string | TaiID (e.g. `"192.168.1.10-19100"`, `"local"`) | ### pc.Exec(cmd, options?) → ExecResult @@ -260,7 +260,7 @@ Get identity and registry information. ```javascript const info = pc.ComputerInfo() console.log(info.kind) // "box" or "host" -console.log(info.pool) // TaiID +console.log(info.node_id) // TaiID console.log(info.system.os) // "linux" | "windows" | "darwin" console.log(info.status) // "running" | "stopped" | ... ``` @@ -332,7 +332,7 @@ Returned by `pc.ComputerInfo()`. Read-only snapshot of a Computer's identity and ```javascript { kind: "box", // "box" | "host" - pool: "192.168.1.10-19100", // TaiID + node_id: "192.168.1.10-19100", // TaiID tai_id: "tai-abc123", machine_id: "m-xyz", version: "1.2.3", @@ -371,7 +371,8 @@ Returned by `sandbox.GetNode()`, `sandbox.Nodes()`, `sandbox.NodesByTeam()`. Rea mode: "direct", // "direct" | "tunnel" addr: "tai://192.168.1.100:19100", status: "online", // "online" | "offline" | "connecting" - pool: "gpu", + display_name: "GPU Node", // optional human-readable name for UI + node_id: "gpu", connected_at: "2026-03-07T08:00:00Z", last_ping: "2026-03-07T10:05:00Z", ports: { @@ -550,7 +551,7 @@ const nodes = sandbox.Nodes() // Find online GPU nodes const gpuNodes = nodes.filter(function(n) { - return n.status === "online" && n.pool === "gpu" + return n.status === "online" && n.display_name === "gpu" // n.display_name is optional label for UI }) console.log("Available GPU nodes:", gpuNodes.length) diff --git a/sandbox/v2/jsapi/computer.go b/sandbox/v2/jsapi/computer.go index 27c95a7a..4180593c 100644 --- a/sandbox/v2/jsapi/computer.go +++ b/sandbox/v2/jsapi/computer.go @@ -117,7 +117,7 @@ func boxInfoToJS(v8ctx *v8go.Context, b *sandbox.BoxInfo) *v8go.Value { data, _ := json.Marshal(map[string]interface{}{ "id": b.ID, "container_id": b.ContainerID, - "pool": b.Pool, + "node_id": b.NodeID, "owner": b.Owner, "status": b.Status, "image": b.Image, @@ -135,7 +135,7 @@ func boxInfoToJS(v8ctx *v8go.Context, b *sandbox.BoxInfo) *v8go.Value { func computerInfoToJS(v8ctx *v8go.Context, c sandbox.ComputerInfo) *v8go.Value { data, _ := json.Marshal(map[string]interface{}{ "kind": c.Kind, - "pool": c.Pool, + "node_id": c.NodeID, "tai_id": c.TaiID, "machine_id": c.MachineID, "version": c.Version, @@ -161,7 +161,7 @@ func computerInfoToJS(v8ctx *v8go.Context, c sandbox.ComputerInfo) *v8go.Value { } // getComputer re-fetches a Computer from the Manager by kind + identifier. -// kind="box" → identifier is boxID, kind="host" → identifier is pool name. +// kind="box" → identifier is boxID, kind="host" → identifier is node ID. func getComputer(ctx context.Context, kind, identifier string) (sandbox.Computer, error) { m := sandbox.M() if kind == "box" { @@ -171,24 +171,24 @@ func getComputer(ctx context.Context, kind, identifier string) (sandbox.Computer } // --------------------------------------------------------------------------- -// sbHost — sandbox.Host(pool?) +// sbHost — sandbox.Host(nodeID?) // --------------------------------------------------------------------------- func sbHost(info *v8go.FunctionCallbackInfo) *v8go.Value { ctx := context.Background() v8ctx := info.Context() - pool := "" + nodeID := "" args := info.Args() if len(args) > 0 && args[0].IsString() { - pool = args[0].String() + nodeID = args[0].String() } - if _, err := sandbox.M().Host(ctx, pool); err != nil { + if _, err := sandbox.M().Host(ctx, nodeID); err != nil { return throwError(info, err.Error()) } - val, err := NewComputerObject(v8ctx, "host", pool) + val, err := NewComputerObject(v8ctx, "host", nodeID) if err != nil { return throwError(info, err.Error()) } @@ -452,20 +452,20 @@ func NewComputerObject(v8ctx *v8go.Context, kind string, identifier string) (*v8 idStr := "" ownerStr := "" - poolStr := identifier + nodeIDStr := identifier if kind == "box" { if comp, err := getComputer(ctx, kind, identifier); err == nil { box := comp.(*sandbox.Box) idStr = box.ID() ownerStr = box.Owner() - poolStr = box.Pool() + nodeIDStr = box.NodeID() } else { idStr = identifier } } obj.Set("id", idStr) obj.Set("owner", ownerStr) - obj.Set("pool", poolStr) + obj.Set("node_id", nodeIDStr) return obj.Value, nil } diff --git a/sandbox/v2/jsapi/jsapi.go b/sandbox/v2/jsapi/jsapi.go index e73e6e74..d2c32999 100644 --- a/sandbox/v2/jsapi/jsapi.go +++ b/sandbox/v2/jsapi/jsapi.go @@ -21,7 +21,7 @@ // sandbox.Get(id) → Manager.Get(ctx, id) → Computer (Box) // sandbox.List(filter?) → Manager.List(ctx, ListOptions) → BoxInfo[] // sandbox.Delete(id) → Manager.Remove(ctx, id) → void -// sandbox.Host(pool?) → Manager.Host(ctx, pool) → Computer (Host) +// sandbox.Host(nodeID?) → Manager.Host(ctx, nodeID) → Computer (Host) // sandbox.GetNode(id) → registry.Global().Get(id) → NodeInfo | null // sandbox.Nodes() → registry.Global().List() → NodeInfo[] // sandbox.NodesByTeam(t)→ registry.Global().ListByTeam(t) → NodeInfo[] @@ -86,8 +86,8 @@ func sbCreate(info *v8go.FunctionCallbackInfo) *v8go.Value { if v, ok := raw["owner"].(string); ok { opts.Owner = v } - if v, ok := raw["pool"].(string); ok { - opts.Pool = v + if v, ok := raw["node_id"].(string); ok { + opts.NodeID = v } if v, ok := raw["image"].(string); ok { opts.Image = v @@ -221,8 +221,8 @@ func sbList(info *v8go.FunctionCallbackInfo) *v8go.Value { if v, ok := raw["owner"].(string); ok { opts.Owner = v } - if v, ok := raw["pool"].(string); ok { - opts.Pool = v + if v, ok := raw["node_id"].(string); ok { + opts.NodeID = v } if v, ok := raw["labels"].(map[string]interface{}); ok { labels := make(map[string]string, len(v)) @@ -250,7 +250,7 @@ func sbList(info *v8go.FunctionCallbackInfo) *v8go.Value { items = append(items, map[string]interface{}{ "id": bi.ID, "container_id": bi.ContainerID, - "pool": bi.Pool, + "node_id": bi.NodeID, "owner": bi.Owner, "status": bi.Status, "image": bi.Image, diff --git a/sandbox/v2/jsapi/jsapi_test.go b/sandbox/v2/jsapi/jsapi_test.go index a135b4bf..567dfc2e 100644 --- a/sandbox/v2/jsapi/jsapi_test.go +++ b/sandbox/v2/jsapi/jsapi_test.go @@ -103,7 +103,7 @@ func TestCreate(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestCreate() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); if (pc.kind !== "box") throw new Error("kind=" + pc.kind); if (!pc.id) throw new Error("no id"); var id = pc.id; @@ -124,7 +124,7 @@ func TestGet(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestGet() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var id = pc.id; var got = sandbox.Get(id); if (!got) throw new Error("Get returned null"); @@ -161,7 +161,7 @@ func TestDelete(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestDelete() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var id = pc.id; sandbox.Delete(id); var got = sandbox.Get(id); @@ -185,8 +185,8 @@ func TestList(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestList() { - var a = sandbox.Create({ image: "%s", owner: "list-user", pool: "%s" }); - var b = sandbox.Create({ image: "%s", owner: "list-user", pool: "%s" }); + var a = sandbox.Create({ image: "%s", owner: "list-user", node_id: "%s" }); + var b = sandbox.Create({ image: "%s", owner: "list-user", node_id: "%s" }); var list = sandbox.List({ owner: "list-user" }); var count = list.length; sandbox.Delete(a.id); @@ -212,7 +212,7 @@ func TestExec(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestExec() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var r = pc.Exec(["echo", "hello-jsapi"]); sandbox.Delete(pc.id); return r.stdout; @@ -232,7 +232,7 @@ func TestExecWithOptions(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestExecWithOptions() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var r = pc.Exec(["pwd"], { workdir: "/tmp" }); sandbox.Delete(pc.id); return r.stdout; @@ -256,7 +256,7 @@ func TestStream(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestStream() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var chunks = []; var exitCode = -1; pc.Stream(["echo", "streaming"], function(type, data) { @@ -285,7 +285,7 @@ func TestComputerInfo(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestComputerInfo() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var info = pc.ComputerInfo(); sandbox.Delete(pc.id); return info.kind; @@ -308,7 +308,7 @@ func TestBoxInfo(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestBoxInfo() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var info = pc.Info(); sandbox.Delete(pc.id); return info.id ? "ok" : "no-id"; @@ -356,7 +356,7 @@ func TestComputerKind(t *testing.T) { setupSandbox(t, &m) img := testImage() res := runJS(t, fmt.Sprintf(`function TestComputerKind() { - var pc = sandbox.Create({ image: "%s", owner: "test-user", pool: "%s" }); + var pc = sandbox.Create({ image: "%s", owner: "test-user", node_id: "%s" }); var k = pc.kind; sandbox.Delete(pc.id); return k; diff --git a/sandbox/v2/jsapi/node.go b/sandbox/v2/jsapi/node.go index fdeb1cfc..37dc6139 100644 --- a/sandbox/v2/jsapi/node.go +++ b/sandbox/v2/jsapi/node.go @@ -83,7 +83,7 @@ func snapshotToJS(v8ctx *v8go.Context, snap *registry.NodeSnapshot) (*v8go.Value "mode": snap.Mode, "addr": snap.Addr, "status": snap.Status, - "pool": snap.PoolName, + "display_name": snap.DisplayName, "connected_at": snap.ConnectedAt.Format(time.RFC3339), "last_ping": snap.LastPing.Format(time.RFC3339), "ports": ports, @@ -117,12 +117,13 @@ func snapshotsToJSArray(v8ctx *v8go.Context, snaps []registry.NodeSnapshot) *v8g } items = append(items, map[string]interface{}{ "tai_id": snap.TaiID, + "node_id": snap.TaiID, "machine_id": snap.MachineID, "version": snap.Version, "mode": snap.Mode, "addr": snap.Addr, "status": snap.Status, - "pool": snap.PoolName, + "display_name": snap.DisplayName, "connected_at": snap.ConnectedAt.Format(time.RFC3339), "last_ping": snap.LastPing.Format(time.RFC3339), "ports": ports, diff --git a/sandbox/v2/manager.go b/sandbox/v2/manager.go index d3c9cd16..c25b060f 100644 --- a/sandbox/v2/manager.go +++ b/sandbox/v2/manager.go @@ -32,7 +32,7 @@ func (m *Manager) Start(ctx context.Context) error { } for _, snap := range reg.List() { - client, err := m.getPool(snap.TaiID) + client, err := m.getNode(snap.TaiID) if err != nil { continue } @@ -45,8 +45,8 @@ func (m *Manager) Start(ctx context.Context) error { return nil } -// Pools returns the list of registered Tai nodes from the registry. -func (m *Manager) Pools() []registry.NodeSnapshot { +// Nodes returns the list of registered Tai nodes from the registry. +func (m *Manager) Nodes() []registry.NodeSnapshot { reg := registry.Global() if reg == nil { return nil @@ -69,21 +69,21 @@ func (m *Manager) Heartbeat(sandboxID string, active bool, processCount int) err } // Host returns a Host handle for executing commands on the Tai host machine. -func (m *Manager) Host(_ context.Context, pool string) (*Host, error) { - if pool == "" { - return nil, ErrPoolMissing +func (m *Manager) Host(_ context.Context, nodeID string) (*Host, error) { + if nodeID == "" { + return nil, ErrNodeMissing } - client, err := m.getPool(pool) + client, err := m.getNode(nodeID) if err != nil { - return nil, fmt.Errorf("sandbox: connect pool %q: %w", pool, err) + return nil, fmt.Errorf("sandbox: connect node %q: %w", nodeID, err) } if client.HostExec() == nil { - return nil, fmt.Errorf("sandbox: pool %q has no host_exec capability", pool) + return nil, fmt.Errorf("sandbox: node %q has no host_exec capability", nodeID) } - return &Host{pool: pool, manager: m}, nil + return &Host{nodeID: nodeID, manager: m}, nil } // Create creates and starts a new sandbox. @@ -92,7 +92,7 @@ func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Box, error) return nil, fmt.Errorf("sandbox: image is required") } - poolName := opts.Pool + nodeID := opts.NodeID if opts.WorkspaceID != "" { if wsm := workspace.M(); wsm != nil { @@ -100,12 +100,12 @@ func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Box, error) if err != nil { return nil, fmt.Errorf("sandbox: resolve workspace %q: %w", opts.WorkspaceID, err) } - poolName = node + nodeID = node } } - if poolName == "" { - return nil, ErrPoolMissing + if nodeID == "" { + return nil, ErrNodeMissing } id := opts.ID @@ -113,16 +113,16 @@ func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Box, error) id = fmt.Sprintf("sb-%d", time.Now().UnixNano()) } - client, err := m.getPool(poolName) + client, err := m.getNode(nodeID) if err != nil { - return nil, fmt.Errorf("sandbox: connect pool %q: %w", poolName, err) + return nil, fmt.Errorf("sandbox: connect node %q: %w", nodeID, err) } if client.Sandbox() == nil { - return nil, fmt.Errorf("sandbox: pool %q has no container runtime", poolName) + return nil, fmt.Errorf("sandbox: node %q has no container runtime", nodeID) } - taiOpts := m.buildTaiCreateOptions(opts, poolName, id) + taiOpts := m.buildTaiCreateOptions(opts, nodeID, id) containerID, err := client.Sandbox().Create(ctx, taiOpts) if err != nil { @@ -142,7 +142,7 @@ func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Box, error) box := &Box{ id: id, containerID: containerID, - pool: poolName, + nodeID: nodeID, owner: opts.Owner, policy: policy, labels: opts.Labels, @@ -188,7 +188,7 @@ func (m *Manager) List(_ context.Context, opts ListOptions) ([]*Box, error) { if opts.Owner != "" && b.owner != opts.Owner { return true } - if opts.Pool != "" && b.pool != opts.Pool { + if opts.NodeID != "" && b.nodeID != opts.NodeID { return true } if len(opts.Labels) > 0 { @@ -212,7 +212,7 @@ func (m *Manager) Remove(ctx context.Context, id string) error { } b := v.(*Box) - client, err := m.getPool(b.pool) + client, err := m.getNode(b.nodeID) if err == nil && client.Sandbox() != nil { client.Sandbox().Remove(ctx, b.containerID, true) } @@ -237,7 +237,7 @@ func (m *Manager) Cleanup(ctx context.Context) error { } case LongRunning: if timeout := b.idleTimeout(); timeout > 0 && idle > timeout { - if client, err := m.getPool(b.pool); err == nil && client.Sandbox() != nil { + if client, err := m.getNode(b.nodeID); err == nil && client.Sandbox() != nil { client.Sandbox().Stop(ctx, b.containerID, b.stopTimeout()) } } @@ -273,20 +273,20 @@ func (m *Manager) cleanupLoop(ctx context.Context) { } } -func (m *Manager) getPool(name string) (*tai.Client, error) { +func (m *Manager) getNode(name string) (*tai.Client, error) { client, ok := tai.GetClient(name) if !ok { - return nil, ErrPoolNotFound + return nil, ErrNodeNotFound } return client, nil } -func (m *Manager) buildTaiCreateOptions(opts CreateOptions, poolName, sandboxID string) taisandbox.CreateOptions { +func (m *Manager) buildTaiCreateOptions(opts CreateOptions, nodeID, sandboxID string) taisandbox.CreateOptions { env := make(map[string]string) reg := registry.Global() if reg != nil { - if snap, ok := reg.Get(poolName); ok { + if snap, ok := reg.Get(nodeID); ok { grpcEnv := BuildGRPCEnv(snap.Mode, snap.Addr, sandboxID) for k, v := range grpcEnv { env[k] = v @@ -299,11 +299,11 @@ func (m *Manager) buildTaiCreateOptions(opts CreateOptions, poolName, sandboxID } labels := map[string]string{ - "managed-by": "yao-sandbox", - "sandbox-id": sandboxID, - "sandbox-owner": opts.Owner, - "sandbox-pool": poolName, - "sandbox-policy": string(opts.Policy), + "managed-by": "yao-sandbox", + "sandbox-id": sandboxID, + "sandbox-owner": opts.Owner, + "sandbox-node-id": nodeID, + "sandbox-policy": string(opts.Policy), } if opts.WorkspaceID != "" { labels["workspace-id"] = opts.WorkspaceID @@ -363,7 +363,7 @@ func (m *Manager) buildTaiCreateOptions(opts CreateOptions, poolName, sandboxID } } -func (m *Manager) recoverBoxes(ctx context.Context, poolName string, client *tai.Client) { +func (m *Manager) recoverBoxes(ctx context.Context, nodeID string, client *tai.Client) { if client.Sandbox() == nil { return } @@ -391,7 +391,7 @@ func (m *Manager) recoverBoxes(ctx context.Context, poolName string, client *tai box := &Box{ id: sandboxID, containerID: cid, - pool: c.Labels["sandbox-pool"], + nodeID: c.Labels["sandbox-node-id"], owner: c.Labels["sandbox-owner"], policy: LifecyclePolicy(c.Labels["sandbox-policy"]), labels: c.Labels, @@ -405,9 +405,9 @@ func (m *Manager) recoverBoxes(ctx context.Context, poolName string, client *tai } } -// ImageExists reports whether the given image ref exists on the target pool node. -func (m *Manager) ImageExists(ctx context.Context, pool, ref string) (bool, error) { - client, err := m.getPool(pool) +// ImageExists reports whether the given image ref exists on the target node. +func (m *Manager) ImageExists(ctx context.Context, nodeID, ref string) (bool, error) { + client, err := m.getNode(nodeID) if err != nil { return false, err } @@ -418,10 +418,10 @@ func (m *Manager) ImageExists(ctx context.Context, pool, ref string) (bool, erro return img.Exists(ctx, ref) } -// PullImage pulls an image to the target pool node, returning a channel of +// PullImage pulls an image to the target node, returning a channel of // real-time progress events. -func (m *Manager) PullImage(ctx context.Context, pool, ref string, opts ImagePullOptions) (<-chan taisandbox.PullProgress, error) { - client, err := m.getPool(pool) +func (m *Manager) PullImage(ctx context.Context, nodeID, ref string, opts ImagePullOptions) (<-chan taisandbox.PullProgress, error) { + client, err := m.getNode(nodeID) if err != nil { return nil, err } @@ -440,10 +440,10 @@ func (m *Manager) PullImage(ctx context.Context, pool, ref string, opts ImagePul return img.Pull(ctx, ref, pullOpts) } -// EnsureImage checks whether the image exists on the pool node; if not, it +// EnsureImage checks whether the image exists on the node; if not, it // pulls the image and blocks until the pull completes. -func (m *Manager) EnsureImage(ctx context.Context, pool, ref string, opts ImagePullOptions) error { - exists, err := m.ImageExists(ctx, pool, ref) +func (m *Manager) EnsureImage(ctx context.Context, nodeID, ref string, opts ImagePullOptions) error { + exists, err := m.ImageExists(ctx, nodeID, ref) if err != nil { return fmt.Errorf("image exists check: %w", err) } @@ -451,7 +451,7 @@ func (m *Manager) EnsureImage(ctx context.Context, pool, ref string, opts ImageP return nil } - ch, err := m.PullImage(ctx, pool, ref, opts) + ch, err := m.PullImage(ctx, nodeID, ref, opts) if err != nil { return fmt.Errorf("image pull: %w", err) } diff --git a/sandbox/v2/manager_lifecycle_test.go b/sandbox/v2/manager_lifecycle_test.go index 385c2d04..67412792 100644 --- a/sandbox/v2/manager_lifecycle_test.go +++ b/sandbox/v2/manager_lifecycle_test.go @@ -11,10 +11,10 @@ import ( func TestHeartbeatUpdates(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) err := m.Heartbeat(box.ID(), true, 5) @@ -34,10 +34,10 @@ func TestHeartbeatUpdates(t *testing.T) { } func TestHeartbeatUnknownBox(t *testing.T) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) err := m.Heartbeat("nonexistent", true, 1) if err != sandbox.ErrNotFound { t.Errorf("err = %v, want ErrNotFound", err) @@ -49,17 +49,17 @@ func TestHeartbeatUnknownBox(t *testing.T) { func TestIdleCleanup(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ensureTestImage(t, m, pc.TaiID) ctx := context.Background() box, err := m.Create(ctx, sandbox.CreateOptions{ Image: testImage(), Owner: "test-user", - Pool: pc.TaiID, + NodeID: pc.TaiID, Policy: sandbox.Session, IdleTimeout: 1 * time.Second, }) @@ -85,10 +85,10 @@ func TestIdleCleanup(t *testing.T) { func TestStartRecovery(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m1 := setupManagerForPool(t, &pc) + m1 := setupManagerForNode(t, &pc) box := createTestBox(t, m1, pc) boxID := box.ID() @@ -117,10 +117,10 @@ func TestStartRecovery(t *testing.T) { func TestPersistentNotCleaned(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) { co.Policy = sandbox.Persistent diff --git a/sandbox/v2/manager_test.go b/sandbox/v2/manager_test.go index b5863b2b..5c0be967 100644 --- a/sandbox/v2/manager_test.go +++ b/sandbox/v2/manager_test.go @@ -11,10 +11,10 @@ import ( func TestCreateAndExec(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -37,10 +37,10 @@ func TestCreateAndExec(t *testing.T) { func TestCreateWithLabels(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) { co.Labels = map[string]string{"app": "test-app"} }) @@ -60,10 +60,10 @@ func TestCreateWithLabels(t *testing.T) { func TestGet(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc) got, err := m.Get(context.Background(), box.ID()) @@ -78,10 +78,10 @@ func TestGet(t *testing.T) { } func TestGetNotFound(t *testing.T) { - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) _, err := m.Get(context.Background(), "nonexistent") if err != sandbox.ErrNotFound { t.Errorf("err = %v, want ErrNotFound", err) @@ -93,10 +93,10 @@ func TestGetNotFound(t *testing.T) { func TestList(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) { co.Owner = "user-list" }) @@ -129,16 +129,16 @@ func TestList(t *testing.T) { func TestRemove(t *testing.T) { skipIfNoDocker(t) - for _, pc := range testPools() { + for _, pc := range testNodes() { pc := pc t.Run(pc.Name, func(t *testing.T) { - m := setupManagerForPool(t, &pc) + m := setupManagerForNode(t, &pc) ensureTestImage(t, m, pc.TaiID) ctx := context.Background() box, err := m.Create(ctx, sandbox.CreateOptions{ - Image: testImage(), - Owner: "test-user", - Pool: pc.TaiID, + Image: testImage(), + Owner: "test-user", + NodeID: pc.TaiID, }) if err != nil { t.Fatalf("Create: %v", err) @@ -157,38 +157,38 @@ func TestRemove(t *testing.T) { } func TestCreateNoImage(t *testing.T) { - m, pools := setupManager(t, poolConfig{Name: "local", Addr: testLocalAddr()}) + m, nodes := setupManager(t, nodeConfig{Name: "local", Addr: testLocalAddr()}) _, err := m.Create(context.Background(), sandbox.CreateOptions{ - Owner: "test", - Pool: pools[0].TaiID, + Owner: "test", + NodeID: nodes[0].TaiID, }) if err == nil { t.Error("expected error for missing image") } } -func TestCreateNoPool(t *testing.T) { - m, _ := setupManager(t, poolConfig{Name: "local", Addr: testLocalAddr()}) +func TestCreateNoNodeID(t *testing.T) { + m, _ := setupManager(t, nodeConfig{Name: "local", Addr: testLocalAddr()}) _, err := m.Create(context.Background(), sandbox.CreateOptions{ Image: testImage(), }) - if err != sandbox.ErrPoolMissing { - t.Errorf("err = %v, want ErrPoolMissing", err) + if err != sandbox.ErrNodeMissing { + t.Errorf("err = %v, want ErrNodeMissing", err) } } -func TestMultiPool(t *testing.T) { +func TestMultiNode(t *testing.T) { skipIfNoDocker(t) skipIfNoTai(t) - pools := testPools() - if len(pools) < 2 { - t.Skip("need at least 2 pools (local + remote) for multi-pool test") + nodes := testNodes() + if len(nodes) < 2 { + t.Skip("need at least 2 nodes (local + remote) for multi-node test") } - m, registered := setupManager(t, pools...) + m, registered := setupManager(t, nodes...) for _, pc := range registered { ensureTestImage(t, m, pc.TaiID) @@ -198,9 +198,9 @@ func TestMultiPool(t *testing.T) { defer cancel() localBox, err := m.Create(ctx, sandbox.CreateOptions{ - Image: testImage(), - Owner: "test-user", - Pool: registered[0].TaiID, + Image: testImage(), + Owner: "test-user", + NodeID: registered[0].TaiID, }) if err != nil { t.Fatalf("Create on local: %v", err) @@ -208,9 +208,9 @@ func TestMultiPool(t *testing.T) { defer m.Remove(ctx, localBox.ID()) remoteBox, err := m.Create(ctx, sandbox.CreateOptions{ - Image: testImage(), - Owner: "test-user", - Pool: registered[1].TaiID, + Image: testImage(), + Owner: "test-user", + NodeID: registered[1].TaiID, }) if err != nil { t.Fatalf("Create on remote: %v", err) diff --git a/sandbox/v2/testutils_test.go b/sandbox/v2/testutils_test.go index 94b120e4..9f34526b 100644 --- a/sandbox/v2/testutils_test.go +++ b/sandbox/v2/testutils_test.go @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { } // purgeStaleContainers removes leftover sb-* containers/pods from previous -// test runs across all configured pools (Docker + K8s). +// test runs across all configured nodes (Docker + K8s). func purgeStaleContainers() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -98,30 +98,30 @@ func purgeStaleContainers() { } } -type poolConfig struct { +type nodeConfig struct { Name string // human-readable label for t.Run (e.g. "remote", "k8s") Addr string TaiID string // actual registry key, filled after tai.New Options []tai.Option } -// testPools returns all available pool configurations for multi-mode testing. -func testPools() []poolConfig { - pools := []poolConfig{ +// testNodes returns all available node configurations for multi-mode testing. +func testNodes() []nodeConfig { + nodes := []nodeConfig{ {Name: "local", Addr: testLocalAddr()}, } if addr := os.Getenv("SANDBOX_TEST_REMOTE_ADDR"); addr != "" { - pools = append(pools, poolConfig{Name: "remote", Addr: addr}) + nodes = append(nodes, nodeConfig{Name: "remote", Addr: addr}) } if host := os.Getenv("TAI_TEST_CONTAINERIZED_HOST"); host != "" { grpcPort := envPort("TAI_TEST_CONTAINERIZED_GRPC_PORT", 9200) addr := fmt.Sprintf("tai://%s:%d", host, grpcPort) - pools = append(pools, poolConfig{Name: "containerized", Addr: addr}) + nodes = append(nodes, nodeConfig{Name: "containerized", Addr: addr}) } if host := os.Getenv("TAI_TEST_K8S_HOST"); host != "" { kubeconfig := os.Getenv("TAI_TEST_KUBECONFIG") if kubeconfig == "" { - return pools + return nodes } grpcPort := envPort("TAI_TEST_K8S_GRPC_PORT", envPort("TAI_TEST_GRPC_PORT", 19100)) addr := fmt.Sprintf("tai://%s:%d", host, grpcPort) @@ -136,9 +136,9 @@ func testPools() []poolConfig { if ns := os.Getenv("TAI_TEST_K8S_NAMESPACE"); ns != "" { opts = append(opts, tai.WithNamespace(ns)) } - pools = append(pools, poolConfig{Name: "k8s", Addr: addr, Options: opts}) + nodes = append(nodes, nodeConfig{Name: "k8s", Addr: addr, Options: opts}) } - return pools + return nodes } func skipIfNoDocker(t *testing.T) { @@ -237,9 +237,9 @@ func envPort(key string, fallback int) int { return fallback } -// registerPool creates a tai.Client and registers it in the global registry. +// registerNode creates a tai.Client and registers it in the global registry. // It fills pc.TaiID with the actual registry key returned by tai.New. -func registerPool(t *testing.T, pc *poolConfig) { +func registerNode(t *testing.T, pc *nodeConfig) { t.Helper() reg := registry.Global() @@ -255,7 +255,7 @@ func registerPool(t *testing.T, pc *poolConfig) { t.Cleanup(func() { client.Close() }) } -func setupManager(t *testing.T, pools ...poolConfig) (*sandbox.Manager, []poolConfig) { +func setupManager(t *testing.T, nodes ...nodeConfig) (*sandbox.Manager, []nodeConfig) { t.Helper() reg := registry.Global() @@ -264,8 +264,8 @@ func setupManager(t *testing.T, pools ...poolConfig) (*sandbox.Manager, []poolCo } _ = reg - out := make([]poolConfig, len(pools)) - copy(out, pools) + out := make([]nodeConfig, len(nodes)) + copy(out, nodes) for i := range out { client, err := tai.New(out[i].Addr, out[i].Options...) if err != nil { @@ -280,7 +280,7 @@ func setupManager(t *testing.T, pools ...poolConfig) (*sandbox.Manager, []poolCo return m, out } -func setupManagerForPool(t *testing.T, pc *poolConfig) *sandbox.Manager { +func setupManagerForNode(t *testing.T, pc *nodeConfig) *sandbox.Manager { t.Helper() m, registered := setupManager(t, *pc) *pc = registered[0] @@ -289,38 +289,38 @@ func setupManagerForPool(t *testing.T, pc *poolConfig) *sandbox.Manager { // setupManagerWithWorkspace creates a sandbox Manager and returns // the global workspace.Manager (which uses the registry for client lookups). -func setupManagerWithWorkspace(t *testing.T, pc *poolConfig) (*sandbox.Manager, *workspace.Manager) { +func setupManagerWithWorkspace(t *testing.T, pc *nodeConfig) (*sandbox.Manager, *workspace.Manager) { t.Helper() - sbm := setupManagerForPool(t, pc) + sbm := setupManagerForNode(t, pc) return sbm, workspace.M() } -func ensureTestImage(t *testing.T, m *sandbox.Manager, pool string) { +func ensureTestImage(t *testing.T, m *sandbox.Manager, nodeID string) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() - if err := m.EnsureImage(ctx, pool, testImage(), sandbox.ImagePullOptions{}); err != nil { - t.Fatalf("EnsureImage(%s, %s): %v", pool, testImage(), err) + if err := m.EnsureImage(ctx, nodeID, testImage(), sandbox.ImagePullOptions{}); err != nil { + t.Fatalf("EnsureImage(%s, %s): %v", nodeID, testImage(), err) } } -func createTestBox(t *testing.T, m *sandbox.Manager, pc poolConfig, opts ...func(*sandbox.CreateOptions)) *sandbox.Box { +func createTestBox(t *testing.T, m *sandbox.Manager, pc nodeConfig, opts ...func(*sandbox.CreateOptions)) *sandbox.Box { t.Helper() co := sandbox.CreateOptions{ - Image: testImage(), - Owner: "test-user", - Pool: pc.TaiID, + Image: testImage(), + Owner: "test-user", + NodeID: pc.TaiID, } for _, fn := range opts { fn(&co) } - pool := co.Pool - if pool == "" { - pools := m.Pools() - if len(pools) > 0 { - pool = pools[0].TaiID - co.Pool = pool + nodeID := co.NodeID + if nodeID == "" { + nodes := m.Nodes() + if len(nodes) > 0 { + nodeID = nodes[0].TaiID + co.NodeID = nodeID } } @@ -332,12 +332,12 @@ func createTestBox(t *testing.T, m *sandbox.Manager, pc poolConfig, opts ...func ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - if pool != "" { - if err := m.EnsureImage(ctx, pool, co.Image, sandbox.ImagePullOptions{}); err != nil { + if nodeID != "" { + if err := m.EnsureImage(ctx, nodeID, co.Image, sandbox.ImagePullOptions{}); err != nil { if isK8s { <-k8sSem } - t.Fatalf("EnsureImage(%s, %s): %v", pool, co.Image, err) + t.Fatalf("EnsureImage(%s, %s): %v", nodeID, co.Image, err) } } diff --git a/sandbox/v2/types.go b/sandbox/v2/types.go index 94f23333..e5c5bca1 100644 --- a/sandbox/v2/types.go +++ b/sandbox/v2/types.go @@ -27,7 +27,7 @@ type Computer interface { // ComputerInfo holds identity and registry information for a Computer. type ComputerInfo struct { Kind string // "box" | "host" - Pool string + NodeID string TaiID string MachineID string Version string @@ -84,7 +84,7 @@ type CreateOptions struct { ID string Owner string Labels map[string]string - Pool string + NodeID string Image string WorkDir string User string @@ -105,7 +105,7 @@ type CreateOptions struct { type ListOptions struct { Owner string - Pool string + NodeID string Labels map[string]string } @@ -211,7 +211,7 @@ type ServiceConn struct { type BoxInfo struct { ID string ContainerID string - Pool string + NodeID string Owner string Status string Policy LifecyclePolicy diff --git a/tai/docs/registry.md b/tai/docs/registry.md index a8f46834..3d48d248 100644 --- a/tai/docs/registry.md +++ b/tai/docs/registry.md @@ -30,7 +30,7 @@ type TaiNode struct { Status string // "online" | "offline" | "connecting" ConnectedAt time.Time LastPing time.Time - PoolName string + DisplayName string } ``` @@ -48,7 +48,7 @@ type NodeSnapshot struct { Capabilities map[string]bool Status string ConnectedAt, LastPing time.Time - PoolName string + DisplayName string } ``` diff --git a/tai/registry/registry.go b/tai/registry/registry.go index ffd2cb4e..d7151978 100644 --- a/tai/registry/registry.go +++ b/tai/registry/registry.go @@ -42,7 +42,7 @@ type TaiNode struct { Status string // "online" | "offline" | "connecting" ConnectedAt time.Time LastPing time.Time - PoolName string + DisplayName string // optional human-readable name for UI client any // *tai.Client; stored as any to avoid import cycle @@ -64,7 +64,7 @@ type NodeSnapshot struct { Status string ConnectedAt time.Time LastPing time.Time - PoolName string + DisplayName string client any } @@ -83,8 +83,8 @@ func (n *TaiNode) snapshot() NodeSnapshot { Mode: n.Mode, Addr: n.Addr, YaoBase: n.YaoBase, Ports: ports, Capabilities: caps, Status: n.Status, ConnectedAt: n.ConnectedAt, LastPing: n.LastPing, - PoolName: n.PoolName, - client: n.client, + DisplayName: n.DisplayName, + client: n.client, } }