refactor(sandbox/v2): transition from pool to node configuration
- Updated benchmark and test functions to utilize node configurations instead of pool configurations for improved clarity and consistency. - Refactored related setup functions and test cases to align with the new node-based architecture. - Adjusted error messages and documentation to reflect the transition from pool to node terminology. Made-with: Cursor
This commit is contained in:
parent
ce0a97c0af
commit
e633640998
25 changed files with 329 additions and 328 deletions
|
|
@ -627,11 +627,11 @@ YAO_REFRESH_TOKEN=<refresh_token>
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var (
|
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")
|
ErrNotFound = errors.New("sandbox: not found")
|
||||||
ErrLimitExceeded = errors.New("sandbox: limit exceeded")
|
ErrLimitExceeded = errors.New("sandbox: limit exceeded")
|
||||||
ErrPoolNotFound = errors.New("sandbox: pool not found")
|
ErrNodeNotFound = errors.New("sandbox: node not found")
|
||||||
ErrPoolInUse = errors.New("sandbox: pool has running boxes")
|
ErrNodeMissing = errors.New("sandbox: node ID missing")
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,10 @@ Reference: [DESIGN.md](./DESIGN.md)
|
||||||
| File | What | Status |
|
| File | What | Status |
|
||||||
|------|------|--------|
|
|------|------|--------|
|
||||||
| `sandbox.go` | `Init()`, `M()`, global singleton | DONE |
|
| `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 |
|
| `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 |
|
| `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 |
|
||||||
| `config.go` | Config struct | DONE |
|
| `errors.go` | ErrNotAvailable, ErrNotFound, ErrNodeNotFound, ErrNodeMissing | DONE |
|
||||||
| `errors.go` | ErrNotAvailable, ErrNotFound, ErrLimitExceeded, ErrPoolNotFound, ErrPoolInUse | DONE |
|
|
||||||
| `grpc.go` | BuildGRPCEnv (sandbox ID + gRPC addr only; token injection is caller's responsibility via Env) | DONE |
|
| `grpc.go` | BuildGRPCEnv (sandbox ID + gRPC addr only; token injection is caller's responsibility via Env) | DONE |
|
||||||
|
|
||||||
### workspace Module — 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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `export_test.go` | ResetForTest | DONE |
|
||||||
| **workspace** | | |
|
| **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 |
|
| `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 |
|
| 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 |
|
| 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
|
### JSAPI — DONE
|
||||||
|
|
||||||
|
|
@ -224,7 +223,7 @@ Manager injects these labels at creation time:
|
||||||
managed-by=yao-sandbox
|
managed-by=yao-sandbox
|
||||||
sandbox-id=<id>
|
sandbox-id=<id>
|
||||||
sandbox-owner=<owner>
|
sandbox-owner=<owner>
|
||||||
sandbox-pool=<pool>
|
sandbox-node-id=<nodeID>
|
||||||
sandbox-policy=<policy>
|
sandbox-policy=<policy>
|
||||||
workspace-id=<workspace-id> (if WorkspaceID set)
|
workspace-id=<workspace-id> (if WorkspaceID set)
|
||||||
```
|
```
|
||||||
|
|
@ -237,14 +236,14 @@ When `CreateOptions.WorkspaceID` is set:
|
||||||
|
|
||||||
```
|
```
|
||||||
1. NodeForWorkspace(wsID) → node name
|
1. NodeForWorkspace(wsID) → node name
|
||||||
2. Force pool = node name
|
2. Force nodeID = node name
|
||||||
3. MountPath(wsID) → hostDir
|
3. MountPath(wsID) → hostDir
|
||||||
4. Bind: hostDir:/workspace:rw
|
4. Bind: hostDir:/workspace:rw
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multi-Mode Testing
|
### Multi-Mode Testing
|
||||||
|
|
||||||
`testPools()` returns all available pool configurations:
|
`testNodes()` returns all available node configurations:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func testPools() []poolConfig {
|
func testPools() []poolConfig {
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ func TestRemovePool_InUse(t *testing.T) {
|
||||||
// manager_test.go
|
// manager_test.go
|
||||||
package sandbox_test
|
package sandbox_test
|
||||||
|
|
||||||
func TestMultiPool(t *testing.T) {
|
func TestMultiNode(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
skipIfNoTai(t)
|
skipIfNoTai(t)
|
||||||
cleanup := setupManagerWithRemote(t)
|
cleanup := setupManagerWithRemote(t)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
// BenchmarkContainerLifecycle measures the full Create → Exec → Remove cycle.
|
// BenchmarkContainerLifecycle measures the full Create → Exec → Remove cycle.
|
||||||
func BenchmarkContainerLifecycle(b *testing.B) {
|
func BenchmarkContainerLifecycle(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
m := setupManagerForBench(b, &pc)
|
m := setupManagerForBench(b, &pc)
|
||||||
|
|
@ -44,7 +44,7 @@ func BenchmarkContainerLifecycle(b *testing.B) {
|
||||||
|
|
||||||
// BenchmarkCreate measures container creation time only.
|
// BenchmarkCreate measures container creation time only.
|
||||||
func BenchmarkCreate(b *testing.B) {
|
func BenchmarkCreate(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
m := setupManagerForBench(b, &pc)
|
m := setupManagerForBench(b, &pc)
|
||||||
|
|
@ -73,7 +73,7 @@ func BenchmarkCreate(b *testing.B) {
|
||||||
|
|
||||||
// BenchmarkExec measures command execution latency on a pre-created container.
|
// BenchmarkExec measures command execution latency on a pre-created container.
|
||||||
func BenchmarkExec(b *testing.B) {
|
func BenchmarkExec(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
m := setupManagerForBench(b, &pc)
|
m := setupManagerForBench(b, &pc)
|
||||||
|
|
@ -95,7 +95,7 @@ func BenchmarkExec(b *testing.B) {
|
||||||
|
|
||||||
// BenchmarkExecHeavy measures execution of a heavier command (write + read file).
|
// BenchmarkExecHeavy measures execution of a heavier command (write + read file).
|
||||||
func BenchmarkExecHeavy(b *testing.B) {
|
func BenchmarkExecHeavy(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
m := setupManagerForBench(b, &pc)
|
m := setupManagerForBench(b, &pc)
|
||||||
|
|
@ -118,7 +118,7 @@ func BenchmarkExecHeavy(b *testing.B) {
|
||||||
|
|
||||||
// BenchmarkRemove measures container removal time.
|
// BenchmarkRemove measures container removal time.
|
||||||
func BenchmarkRemove(b *testing.B) {
|
func BenchmarkRemove(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
m := setupManagerForBench(b, &pc)
|
m := setupManagerForBench(b, &pc)
|
||||||
|
|
@ -148,7 +148,7 @@ func BenchmarkRemove(b *testing.B) {
|
||||||
|
|
||||||
// BenchmarkInfo measures Info() latency on a running container.
|
// BenchmarkInfo measures Info() latency on a running container.
|
||||||
func BenchmarkInfo(b *testing.B) {
|
func BenchmarkInfo(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
m := setupManagerForBench(b, &pc)
|
m := setupManagerForBench(b, &pc)
|
||||||
|
|
@ -167,7 +167,7 @@ func BenchmarkInfo(b *testing.B) {
|
||||||
|
|
||||||
// BenchmarkStopStart measures Stop → Start cycle time.
|
// BenchmarkStopStart measures Stop → Start cycle time.
|
||||||
func BenchmarkStopStart(b *testing.B) {
|
func BenchmarkStopStart(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
if pc.Name == "k8s" {
|
if pc.Name == "k8s" {
|
||||||
|
|
@ -191,7 +191,7 @@ func BenchmarkStopStart(b *testing.B) {
|
||||||
|
|
||||||
// BenchmarkWorkspaceReadWrite measures workspace file read/write via container Box.
|
// BenchmarkWorkspaceReadWrite measures workspace file read/write via container Box.
|
||||||
func BenchmarkWorkspaceReadWrite(b *testing.B) {
|
func BenchmarkWorkspaceReadWrite(b *testing.B) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
b.Run(pc.Name, func(b *testing.B) {
|
b.Run(pc.Name, func(b *testing.B) {
|
||||||
m := setupManagerForBench(b, &pc)
|
m := setupManagerForBench(b, &pc)
|
||||||
|
|
@ -223,7 +223,7 @@ func BenchmarkWorkspaceReadWrite(b *testing.B) {
|
||||||
|
|
||||||
// --- helpers ---
|
// --- helpers ---
|
||||||
|
|
||||||
func setupManagerForBench(b *testing.B, pc *poolConfig) *sandbox.Manager {
|
func setupManagerForBench(b *testing.B, pc *nodeConfig) *sandbox.Manager {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
reg := registry.Global()
|
reg := registry.Global()
|
||||||
if reg == nil {
|
if reg == nil {
|
||||||
|
|
@ -240,29 +240,29 @@ func setupManagerForBench(b *testing.B, pc *poolConfig) *sandbox.Manager {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureTestImageBench(b *testing.B, m *sandbox.Manager, pool string) {
|
func ensureTestImageBench(b *testing.B, m *sandbox.Manager, nodeID string) {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||||
defer cancel()
|
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)
|
b.Fatalf("EnsureImage: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBoxForBench(b *testing.B, m *sandbox.Manager) *sandbox.Box {
|
func createBoxForBench(b *testing.B, m *sandbox.Manager) *sandbox.Box {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
pools := m.Pools()
|
nodes := m.Nodes()
|
||||||
var poolName string
|
var nodeID string
|
||||||
if len(pools) > 0 {
|
if len(nodes) > 0 {
|
||||||
poolName = pools[0].TaiID
|
nodeID = nodes[0].TaiID
|
||||||
ensureTestImageBench(b, m, poolName)
|
ensureTestImageBench(b, m, nodeID)
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
box, err := m.Create(ctx, sandbox.CreateOptions{
|
box, err := m.Create(ctx, sandbox.CreateOptions{
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "bench",
|
Owner: "bench",
|
||||||
Pool: poolName,
|
NodeID: nodeID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Create: %v", err)
|
b.Fatalf("Create: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
type Box struct {
|
type Box struct {
|
||||||
id string
|
id string
|
||||||
containerID string
|
containerID string
|
||||||
pool string
|
nodeID string
|
||||||
owner string
|
owner string
|
||||||
policy LifecyclePolicy
|
policy LifecyclePolicy
|
||||||
labels map[string]string
|
labels map[string]string
|
||||||
|
|
@ -39,13 +39,13 @@ var _ Computer = (*Box)(nil)
|
||||||
func (b *Box) ID() string { return b.id }
|
func (b *Box) ID() string { return b.id }
|
||||||
func (b *Box) Owner() string { return b.owner }
|
func (b *Box) Owner() string { return b.owner }
|
||||||
func (b *Box) ContainerID() string { return b.containerID }
|
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.
|
// ComputerInfo returns identity and registry information for this Box.
|
||||||
func (b *Box) ComputerInfo() ComputerInfo {
|
func (b *Box) ComputerInfo() ComputerInfo {
|
||||||
return ComputerInfo{
|
return ComputerInfo{
|
||||||
Kind: "box",
|
Kind: "box",
|
||||||
Pool: b.pool,
|
NodeID: b.nodeID,
|
||||||
Status: "online",
|
Status: "online",
|
||||||
BoxID: b.id,
|
BoxID: b.id,
|
||||||
ContainerID: b.containerID,
|
ContainerID: b.containerID,
|
||||||
|
|
@ -79,7 +79,7 @@ func (b *Box) Exec(ctx context.Context, cmd []string, opts ...ExecOption) (*Exec
|
||||||
o(cfg)
|
o(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := b.manager.getPool(b.pool)
|
client, err := b.manager.getNode(b.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +113,7 @@ func (b *Box) Stream(ctx context.Context, cmd []string, opts ...ExecOption) (*Ex
|
||||||
o(cfg)
|
o(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := b.manager.getPool(b.pool)
|
client, err := b.manager.getNode(b.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +143,7 @@ func (b *Box) Attach(ctx context.Context, port int, opts ...AttachOption) (*Serv
|
||||||
o(cfg)
|
o(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := b.manager.getPool(b.pool)
|
client, err := b.manager.getNode(b.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +189,7 @@ func (b *Box) Workspace() workspace.FS {
|
||||||
if sessionID == "" {
|
if sessionID == "" {
|
||||||
sessionID = b.id
|
sessionID = b.id
|
||||||
}
|
}
|
||||||
client, err := b.manager.getPool(b.pool)
|
client, err := b.manager.getNode(b.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +203,7 @@ func (b *Box) WorkspaceID() string { return b.workspaceID }
|
||||||
// VNC returns the VNC WebSocket URL.
|
// VNC returns the VNC WebSocket URL.
|
||||||
func (b *Box) VNC(ctx context.Context) (string, error) {
|
func (b *Box) VNC(ctx context.Context) (string, error) {
|
||||||
b.touch()
|
b.touch()
|
||||||
client, err := b.manager.getPool(b.pool)
|
client, err := b.manager.getNode(b.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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.
|
// 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) {
|
func (b *Box) Proxy(ctx context.Context, port int, path string) (string, error) {
|
||||||
b.touch()
|
b.touch()
|
||||||
client, err := b.manager.getPool(b.pool)
|
client, err := b.manager.getNode(b.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +222,7 @@ func (b *Box) Proxy(ctx context.Context, port int, path string) (string, error)
|
||||||
|
|
||||||
// Start starts a stopped sandbox.
|
// Start starts a stopped sandbox.
|
||||||
func (b *Box) Start(ctx context.Context) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +231,7 @@ func (b *Box) Start(ctx context.Context) error {
|
||||||
|
|
||||||
// Stop stops the sandbox without removing it.
|
// Stop stops the sandbox without removing it.
|
||||||
func (b *Box) Stop(ctx context.Context) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -245,7 +245,7 @@ func (b *Box) Remove(ctx context.Context) error {
|
||||||
|
|
||||||
// Info returns current sandbox status.
|
// Info returns current sandbox status.
|
||||||
func (b *Box) Info(ctx context.Context) (*BoxInfo, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +258,7 @@ func (b *Box) Info(ctx context.Context) (*BoxInfo, error) {
|
||||||
return &BoxInfo{
|
return &BoxInfo{
|
||||||
ID: b.id,
|
ID: b.id,
|
||||||
ContainerID: b.containerID,
|
ContainerID: b.containerID,
|
||||||
Pool: b.pool,
|
NodeID: b.nodeID,
|
||||||
Owner: b.owner,
|
Owner: b.owner,
|
||||||
Status: info.Status,
|
Status: info.Status,
|
||||||
Policy: b.policy,
|
Policy: b.policy,
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,10 @@ func TestAttachWS(t *testing.T) {
|
||||||
t.Skip("WebSocket test requires tai-sandbox-test image with ws-echo service")
|
t.Skip("WebSocket test requires tai-sandbox-test image with ws-echo service")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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) {
|
box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) {
|
||||||
co.Ports = []sandbox.PortMapping{
|
co.Ports = []sandbox.PortMapping{
|
||||||
{ContainerPort: 9800, HostPort: 0, Protocol: "tcp"},
|
{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")
|
t.Skip("SSE test requires tai-sandbox-test image with sse-server service")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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) {
|
box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) {
|
||||||
co.Ports = []sandbox.PortMapping{
|
co.Ports = []sandbox.PortMapping{
|
||||||
{ContainerPort: 9801, HostPort: 0, Protocol: "tcp"},
|
{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")
|
t.Skip("VNC test requires tai-sandbox-test image with VNC desktop")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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) {
|
box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) {
|
||||||
co.VNC = true
|
co.VNC = true
|
||||||
})
|
})
|
||||||
|
|
@ -195,10 +195,10 @@ func TestVNCConnect(t *testing.T) {
|
||||||
t.Skip("VNC test requires tai-sandbox-test image with VNC desktop")
|
t.Skip("VNC test requires tai-sandbox-test image with VNC desktop")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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) {
|
box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) {
|
||||||
co.VNC = true
|
co.VNC = true
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestImageExists(t *testing.T) {
|
func TestImageExists(t *testing.T) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
if pc.Name == "k8s" {
|
if pc.Name == "k8s" {
|
||||||
t.Run("always_true", func(t *testing.T) {
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
exists, err := m.ImageExists(ctx, pc.TaiID, "anything:nonexistent")
|
exists, err := m.ImageExists(ctx, pc.TaiID, "anything:nonexistent")
|
||||||
|
|
@ -26,7 +26,7 @@ func TestImageExists(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -46,12 +46,12 @@ func TestImageExists(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImagePull(t *testing.T) {
|
func TestImagePull(t *testing.T) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
if pc.Name == "k8s" {
|
if pc.Name == "k8s" {
|
||||||
t.Run("noop", func(t *testing.T) {
|
t.Run("noop", func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
ch, err := m.PullImage(ctx, pc.TaiID, "alpine:latest", sandbox.ImagePullOptions{})
|
ch, err := m.PullImage(ctx, pc.TaiID, "alpine:latest", sandbox.ImagePullOptions{})
|
||||||
|
|
@ -61,7 +61,7 @@ func TestImagePull(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -84,10 +84,10 @@ func TestImagePull(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnsureImage(t *testing.T) {
|
func TestEnsureImage(t *testing.T) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -104,13 +104,13 @@ func TestEnsureImage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnsureImage_BadRef(t *testing.T) {
|
func TestEnsureImage_BadRef(t *testing.T) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
if pc.Name == "k8s" {
|
if pc.Name == "k8s" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ import (
|
||||||
func TestBoxExec(t *testing.T) {
|
func TestBoxExec(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
|
@ -36,10 +36,10 @@ func TestBoxExec(t *testing.T) {
|
||||||
func TestBoxExecWithOptions(t *testing.T) {
|
func TestBoxExecWithOptions(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -59,10 +59,10 @@ func TestBoxExecWithOptions(t *testing.T) {
|
||||||
func TestBoxStream(t *testing.T) {
|
func TestBoxStream(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -93,10 +93,10 @@ func TestBoxStream(t *testing.T) {
|
||||||
func TestBoxWorkspace(t *testing.T) {
|
func TestBoxWorkspace(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
ws := box.Workspace()
|
ws := box.Workspace()
|
||||||
|
|
@ -135,10 +135,10 @@ func TestBoxWorkspace(t *testing.T) {
|
||||||
func TestBoxInfo(t *testing.T) {
|
func TestBoxInfo(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -162,10 +162,10 @@ func TestBoxInfo(t *testing.T) {
|
||||||
func TestBoxStopStart(t *testing.T) {
|
func TestBoxStopStart(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -191,16 +191,16 @@ func TestBoxStopStart(t *testing.T) {
|
||||||
func TestBoxGetOrCreate(t *testing.T) {
|
func TestBoxGetOrCreate(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
box1, err := m.GetOrCreate(ctx, sandbox.CreateOptions{
|
box1, err := m.GetOrCreate(ctx, sandbox.CreateOptions{
|
||||||
ID: "goc-" + pc.Name,
|
ID: "goc-" + pc.Name,
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "test-user",
|
Owner: "test-user",
|
||||||
Pool: pc.TaiID,
|
NodeID: pc.TaiID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetOrCreate first: %v", err)
|
t.Fatalf("GetOrCreate first: %v", err)
|
||||||
|
|
@ -208,10 +208,10 @@ func TestBoxGetOrCreate(t *testing.T) {
|
||||||
defer m.Remove(ctx, box1.ID())
|
defer m.Remove(ctx, box1.ID())
|
||||||
|
|
||||||
box2, err := m.GetOrCreate(ctx, sandbox.CreateOptions{
|
box2, err := m.GetOrCreate(ctx, sandbox.CreateOptions{
|
||||||
ID: "goc-" + pc.Name,
|
ID: "goc-" + pc.Name,
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "test-user",
|
Owner: "test-user",
|
||||||
Pool: pc.TaiID,
|
NodeID: pc.TaiID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetOrCreate second: %v", err)
|
t.Fatalf("GetOrCreate second: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
func TestWorkspaceID_Set(t *testing.T) {
|
func TestWorkspaceID_Set(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
@ -41,10 +41,10 @@ func TestWorkspaceID_Set(t *testing.T) {
|
||||||
func TestWorkspaceID_Empty(t *testing.T) {
|
func TestWorkspaceID_Empty(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
assert.Empty(t, box.WorkspaceID())
|
assert.Empty(t, box.WorkspaceID())
|
||||||
})
|
})
|
||||||
|
|
@ -54,7 +54,7 @@ func TestWorkspaceID_Empty(t *testing.T) {
|
||||||
func TestWorkspace_NodeRouting(t *testing.T) {
|
func TestWorkspace_NodeRouting(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
@ -72,7 +72,7 @@ func TestWorkspace_NodeRouting(t *testing.T) {
|
||||||
co.WorkspaceID = ws.ID
|
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) {
|
func TestWorkspace_InvalidID(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
sbm, _ := setupManagerWithWorkspace(t, &pc)
|
sbm, _ := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
@ -103,7 +103,7 @@ func TestWorkspace_InvalidID(t *testing.T) {
|
||||||
func TestWorkspace_BindMountLocal(t *testing.T) {
|
func TestWorkspace_BindMountLocal(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
pc := poolConfig{Name: "local", Addr: testLocalAddr()}
|
pc := nodeConfig{Name: "local", Addr: testLocalAddr()}
|
||||||
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
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) {
|
func TestWorkspace_ContainerWriteBack(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
pc := poolConfig{Name: "local", Addr: testLocalAddr()}
|
pc := nodeConfig{Name: "local", Addr: testLocalAddr()}
|
||||||
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
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) {
|
func TestWorkspace_ReadOnlyMount(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
pc := poolConfig{Name: "local", Addr: testLocalAddr()}
|
pc := nodeConfig{Name: "local", Addr: testLocalAddr()}
|
||||||
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
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) {
|
func TestWorkspace_CustomMountPath(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
pc := poolConfig{Name: "local", Addr: testLocalAddr()}
|
pc := nodeConfig{Name: "local", Addr: testLocalAddr()}
|
||||||
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
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) {
|
func TestWorkspace_BoxWorkspaceFS(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
if pc.Name == "local" {
|
if pc.Name == "local" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +254,7 @@ func TestWorkspace_BoxWorkspaceFS(t *testing.T) {
|
||||||
func TestWorkspace_LabelPersistence(t *testing.T) {
|
func TestWorkspace_LabelPersistence(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
sbm, wsm := setupManagerWithWorkspace(t, &pc)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Package: `github.com/yaoapp/yao/sandbox/v2`
|
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`.
|
- **Box** — a container (Docker or K8s pod). Created via `Manager.Create`.
|
||||||
- **Host** — the Tai host machine itself. Obtained via `Manager.Host` (no Create needed).
|
- **Host** — the Tai host machine itself. Obtained via `Manager.Host` (no Create needed).
|
||||||
|
|
@ -42,10 +42,10 @@ mgr := sandbox.M()
|
||||||
|
|
||||||
## Node Discovery
|
## 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.
|
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
|
`"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
|
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
|
```go
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -85,7 +85,7 @@ err := sandbox.M().Start(ctx)
|
||||||
func (m *Manager) Close() error
|
func (m *Manager) Close() error
|
||||||
```
|
```
|
||||||
|
|
||||||
Stops the cleanup loop and closes all pool connections.
|
Stops the cleanup loop and closes all node connections.
|
||||||
|
|
||||||
### Create
|
### Create
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ Creates and starts a new sandbox container. Returns a `Box` handle.
|
||||||
box, err := sandbox.M().Create(ctx, sandbox.CreateOptions{
|
box, err := sandbox.M().Create(ctx, sandbox.CreateOptions{
|
||||||
Image: "alpine:latest",
|
Image: "alpine:latest",
|
||||||
Owner: "user-123",
|
Owner: "user-123",
|
||||||
Pool: "192.168.1.10-19100", // TaiID from registry
|
NodeID: "192.168.1.10-19100", // TaiID from registry
|
||||||
Policy: sandbox.Session,
|
Policy: sandbox.Session,
|
||||||
WorkDir: "/workspace",
|
WorkDir: "/workspace",
|
||||||
Env: map[string]string{"LANG": "en_US.UTF-8"},
|
Env: map[string]string{"LANG": "en_US.UTF-8"},
|
||||||
|
|
@ -121,13 +121,13 @@ box, err := sandbox.M().Create(ctx, sandbox.CreateOptions{
|
||||||
### Host
|
### Host
|
||||||
|
|
||||||
```go
|
```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
|
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,
|
`host_exec` capability. Returns `ErrNodeNotFound` if the TaiID is not registered,
|
||||||
`ErrPoolMissing` if the pool argument is empty, or an error if the node has no `host_exec`.
|
`ErrNodeMissing` if the nodeID argument is empty, or an error if the node has no `host_exec`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
host, err := sandbox.M().Host(ctx, "192.168.1.10-19100")
|
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
|
```go
|
||||||
boxes, err := sandbox.M().List(ctx, sandbox.ListOptions{
|
boxes, err := sandbox.M().List(ctx, sandbox.ListOptions{
|
||||||
Owner: "user-123",
|
Owner: "user-123",
|
||||||
Pool: "192.168.1.10-19100",
|
NodeID: "192.168.1.10-19100",
|
||||||
Labels: map[string]string{"project": "demo"},
|
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)
|
err := sandbox.M().Heartbeat("sb-12345", true, 3)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pools
|
### Nodes
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (m *Manager) Pools() []registry.NodeSnapshot
|
func (m *Manager) Nodes() []registry.NodeSnapshot
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns all registered Tai nodes from the `tai/registry`.
|
Returns all registered Tai nodes from the `tai/registry`.
|
||||||
|
|
||||||
```go
|
```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",
|
fmt.Printf("tai_id=%s mode=%s addr=%s status=%s\n",
|
||||||
n.TaiID, n.Mode, n.Addr, n.Status)
|
n.TaiID, n.Mode, n.Addr, n.Status)
|
||||||
}
|
}
|
||||||
|
|
@ -228,11 +228,11 @@ for _, n := range sandbox.M().Pools() {
|
||||||
### ImageExists
|
### ImageExists
|
||||||
|
|
||||||
```go
|
```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.
|
Reports whether the given image ref exists on the target node.
|
||||||
Returns `(true, nil)` when the pool has no image service (e.g. K8s — kubelet handles pulls).
|
Returns `(true, nil)` when the node has no image service (e.g. K8s — kubelet handles pulls).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
exists, err := sandbox.M().ImageExists(ctx, "192.168.1.10-19100", "alpine:latest")
|
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
|
### PullImage
|
||||||
|
|
||||||
```go
|
```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`
|
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 pool has no image
|
(from `github.com/yaoapp/yao/tai/sandbox`). Returns `(nil, nil)` when the node has no image
|
||||||
service (e.g. K8s).
|
service (e.g. K8s).
|
||||||
|
|
||||||
`PullProgress` fields: `Status string`, `Layer string`, `Current int64`, `Total int64`, `Error string`.
|
`PullProgress` fields: `Status string`, `Layer string`, `Current int64`, `Total int64`, `Error string`.
|
||||||
|
|
@ -266,7 +266,7 @@ for p := range ch {
|
||||||
### EnsureImage
|
### EnsureImage
|
||||||
|
|
||||||
```go
|
```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.
|
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) ID() string
|
||||||
func (b *Box) Owner() string
|
func (b *Box) Owner() string
|
||||||
func (b *Box) ContainerID() string
|
func (b *Box) ContainerID() string
|
||||||
func (b *Box) Pool() string
|
func (b *Box) NodeID() string
|
||||||
func (b *Box) WorkspaceID() string
|
func (b *Box) WorkspaceID() string
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -423,12 +423,12 @@ fmt.Printf("status=%s processes=%d vnc=%v created=%s\n",
|
||||||
## Host
|
## Host
|
||||||
|
|
||||||
A `Host` represents a Tai host machine execution environment, distinct from `Box` (containers).
|
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
|
### Accessors
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (h *Host) Pool() string
|
func (h *Host) NodeID() string
|
||||||
```
|
```
|
||||||
|
|
||||||
### Exec
|
### Exec
|
||||||
|
|
@ -539,7 +539,7 @@ type CreateOptions struct {
|
||||||
ID string
|
ID string
|
||||||
Owner string
|
Owner string
|
||||||
Labels map[string]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
|
Image string // required
|
||||||
WorkDir string // default "/workspace"
|
WorkDir string // default "/workspace"
|
||||||
User string // container user
|
User string // container user
|
||||||
|
|
@ -563,7 +563,7 @@ type CreateOptions struct {
|
||||||
```go
|
```go
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
Owner string
|
Owner string
|
||||||
Pool string
|
NodeID string
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -619,7 +619,7 @@ type ServiceConn struct {
|
||||||
type BoxInfo struct {
|
type BoxInfo struct {
|
||||||
ID string
|
ID string
|
||||||
ContainerID string
|
ContainerID string
|
||||||
Pool string
|
NodeID string
|
||||||
Owner string
|
Owner string
|
||||||
Status string // "running", "stopped", etc.
|
Status string // "running", "stopped", etc.
|
||||||
Policy LifecyclePolicy
|
Policy LifecyclePolicy
|
||||||
|
|
@ -676,10 +676,10 @@ type HostExecStream struct {
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var (
|
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")
|
ErrNotFound = errors.New("sandbox: not found")
|
||||||
ErrPoolNotFound = errors.New("sandbox: pool not found")
|
ErrNodeNotFound = errors.New("sandbox: node not found")
|
||||||
ErrPoolMissing = errors.New("sandbox: pool name is required")
|
ErrNodeMissing = errors.New("sandbox: node ID is required")
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package sandbox
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
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")
|
ErrNotFound = errors.New("sandbox: not found")
|
||||||
ErrPoolNotFound = errors.New("sandbox: pool not found")
|
ErrNodeNotFound = errors.New("sandbox: node not found")
|
||||||
ErrPoolMissing = errors.New("sandbox: pool name is required")
|
ErrNodeMissing = errors.New("sandbox: node ID is required")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import (
|
||||||
//
|
//
|
||||||
// Host implements the Computer interface.
|
// Host implements the Computer interface.
|
||||||
type Host struct {
|
type Host struct {
|
||||||
pool string
|
nodeID string
|
||||||
workplaceID string
|
workplaceID string
|
||||||
manager *Manager
|
manager *Manager
|
||||||
}
|
}
|
||||||
|
|
@ -25,12 +25,12 @@ type Host struct {
|
||||||
var _ Computer = (*Host)(nil)
|
var _ Computer = (*Host)(nil)
|
||||||
|
|
||||||
// ComputerInfo returns identity and registry information for the host.
|
// ComputerInfo returns identity and registry information for the host.
|
||||||
// Registry-level details (TaiID, System, etc.) are populated when the pool
|
// Registry-level details (TaiID, System, etc.) are populated when the node
|
||||||
// is backed by a registered Tai node; otherwise only Kind and Pool are set.
|
// is backed by a registered Tai node; otherwise only Kind and NodeID are set.
|
||||||
func (h *Host) ComputerInfo() ComputerInfo {
|
func (h *Host) ComputerInfo() ComputerInfo {
|
||||||
return ComputerInfo{
|
return ComputerInfo{
|
||||||
Kind: "host",
|
Kind: "host",
|
||||||
Pool: h.pool,
|
NodeID: h.nodeID,
|
||||||
Status: "online",
|
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")
|
return nil, fmt.Errorf("sandbox: empty command")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := h.manager.getPool(h.pool)
|
client, err := h.manager.getNode(h.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
he := client.HostExec()
|
he := client.HostExec()
|
||||||
if he == nil {
|
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{}
|
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")
|
return nil, fmt.Errorf("sandbox: empty command")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := h.manager.getPool(h.pool)
|
client, err := h.manager.getNode(h.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
he := client.HostExec()
|
he := client.HostExec()
|
||||||
if he == nil {
|
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{}
|
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.
|
// 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.
|
// Uses the special __host__ identifier to route to localhost:5900 on the Tai server.
|
||||||
func (h *Host) VNC(ctx context.Context) (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
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.
|
// 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.
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -215,15 +215,15 @@ func (h *Host) Workplace() workspace.FS {
|
||||||
if h.workplaceID == "" {
|
if h.workplaceID == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
client, err := h.manager.getPool(h.pool)
|
client, err := h.manager.getNode(h.nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return client.Workspace(h.workplaceID)
|
return client.Workspace(h.workplaceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool returns the pool name this Host belongs to.
|
// NodeID returns the node ID this Host belongs to.
|
||||||
func (h *Host) Pool() string { return h.pool }
|
func (h *Host) NodeID() string { return h.nodeID }
|
||||||
|
|
||||||
// nopWriteCloser wraps an io.Writer with a no-op Close.
|
// nopWriteCloser wraps an io.Writer with a no-op Close.
|
||||||
type nopWriteCloser struct{ io.Writer }
|
type nopWriteCloser struct{ io.Writer }
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ import (
|
||||||
func setupHostManager(t *testing.T, tgt *hostExecTarget) *sandbox.Manager {
|
func setupHostManager(t *testing.T, tgt *hostExecTarget) *sandbox.Manager {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
addr := fmt.Sprintf("tai://%s", tgt.Addr)
|
addr := fmt.Sprintf("tai://%s", tgt.Addr)
|
||||||
m, pools := setupManager(t, poolConfig{Name: tgt.Name, Addr: addr})
|
m, nodes := setupManager(t, nodeConfig{Name: tgt.Name, Addr: addr})
|
||||||
tgt.TaiID = pools[0].TaiID
|
tgt.TaiID = nodes[0].TaiID
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,8 +384,8 @@ func TestHost_ComputerInfo(t *testing.T) {
|
||||||
if info.Kind != "host" {
|
if info.Kind != "host" {
|
||||||
t.Errorf("Kind = %q, want 'host'", info.Kind)
|
t.Errorf("Kind = %q, want 'host'", info.Kind)
|
||||||
}
|
}
|
||||||
if info.Pool != tgt.TaiID {
|
if info.NodeID != tgt.TaiID {
|
||||||
t.Errorf("Pool = %q, want %q", info.Pool, 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)
|
tgt := findHostExecOnly(t)
|
||||||
if tgt == nil {
|
if tgt == nil {
|
||||||
t.Skip("no host-exec-only target available")
|
t.Skip("no host-exec-only target available")
|
||||||
|
|
@ -426,24 +426,24 @@ func TestHost_CreateRejectsNoContainerPool(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
_, err := m.Create(ctx, sandbox.CreateOptions{
|
_, err := m.Create(ctx, sandbox.CreateOptions{
|
||||||
Image: "alpine:latest",
|
Image: "alpine:latest",
|
||||||
Owner: "test",
|
Owner: "test",
|
||||||
Pool: tgt.TaiID,
|
NodeID: tgt.TaiID,
|
||||||
})
|
})
|
||||||
if err == nil {
|
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") {
|
if !strings.Contains(err.Error(), "no container runtime") {
|
||||||
t.Errorf("error = %q, want contains 'no container runtime'", err.Error())
|
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)
|
skipIfNoHostExec(t)
|
||||||
tgt := hostExecTargets()[0]
|
tgt := hostExecTargets()[0]
|
||||||
m := setupHostManager(t, &tgt)
|
m := setupHostManager(t, &tgt)
|
||||||
|
|
||||||
_, err := m.Host(context.Background(), "nonexistent-pool")
|
_, err := m.Host(context.Background(), "nonexistent-node")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error, got nil")
|
t.Fatal("expected error, got nil")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ Create a new sandbox container. Returns a Computer (`kind = "box"`). If `options
|
||||||
const pc = sandbox.Create({
|
const pc = sandbox.Create({
|
||||||
image: "node:20", // required — container image
|
image: "node:20", // required — container image
|
||||||
owner: "user-123", // required — owner identifier
|
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
|
id: "my-sandbox", // optional — if set, uses GetOrCreate
|
||||||
workdir: "/app", // optional — working directory
|
workdir: "/app", // optional — working directory
|
||||||
user: "1000:1000", // optional — UID:GID
|
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
|
```javascript
|
||||||
const pc = sandbox.Get("my-sandbox")
|
const pc = sandbox.Get("my-sandbox")
|
||||||
if (pc) {
|
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
|
// Filter by owner
|
||||||
const mine = sandbox.List({ owner: "user-123" })
|
const mine = sandbox.List({ owner: "user-123" })
|
||||||
|
|
||||||
// Filter by pool (TaiID) and labels
|
// Filter by node_id (TaiID) and labels
|
||||||
const gpu = sandbox.List({ pool: "10.0.0.5-19100", labels: { team: "ml" } })
|
const gpu = sandbox.List({ node_id: "10.0.0.5-19100", labels: { team: "ml" } })
|
||||||
```
|
```
|
||||||
|
|
||||||
Each element in the returned array:
|
Each element in the returned array:
|
||||||
|
|
@ -84,7 +84,7 @@ Each element in the returned array:
|
||||||
{
|
{
|
||||||
id: "sb-xxx",
|
id: "sb-xxx",
|
||||||
container_id: "abc123...",
|
container_id: "abc123...",
|
||||||
pool: "192.168.1.10-19100",
|
node_id: "192.168.1.10-19100",
|
||||||
owner: "user-123",
|
owner: "user-123",
|
||||||
status: "running", // "running"|"stopped"|"creating"|...
|
status: "running", // "running"|"stopped"|"creating"|...
|
||||||
image: "node:20",
|
image: "node:20",
|
||||||
|
|
@ -105,9 +105,9 @@ Remove a sandbox and its container.
|
||||||
sandbox.Delete("my-sandbox")
|
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
|
```javascript
|
||||||
const host = sandbox.Host("192.168.1.10-19100")
|
const host = sandbox.Host("192.168.1.10-19100")
|
||||||
|
|
@ -131,7 +131,7 @@ List all registered nodes.
|
||||||
```javascript
|
```javascript
|
||||||
const nodes = sandbox.Nodes()
|
const nodes = sandbox.Nodes()
|
||||||
nodes.forEach(function(n) {
|
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.kind` | string | `"box"` or `"host"` |
|
||||||
| `pc.id` | string | Sandbox ID (box-only; empty for host) |
|
| `pc.id` | string | Sandbox ID (box-only; empty for host) |
|
||||||
| `pc.owner` | string | Owner identifier (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
|
### pc.Exec(cmd, options?) → ExecResult
|
||||||
|
|
||||||
|
|
@ -260,7 +260,7 @@ Get identity and registry information.
|
||||||
```javascript
|
```javascript
|
||||||
const info = pc.ComputerInfo()
|
const info = pc.ComputerInfo()
|
||||||
console.log(info.kind) // "box" or "host"
|
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.system.os) // "linux" | "windows" | "darwin"
|
||||||
console.log(info.status) // "running" | "stopped" | ...
|
console.log(info.status) // "running" | "stopped" | ...
|
||||||
```
|
```
|
||||||
|
|
@ -332,7 +332,7 @@ Returned by `pc.ComputerInfo()`. Read-only snapshot of a Computer's identity and
|
||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
kind: "box", // "box" | "host"
|
kind: "box", // "box" | "host"
|
||||||
pool: "192.168.1.10-19100", // TaiID
|
node_id: "192.168.1.10-19100", // TaiID
|
||||||
tai_id: "tai-abc123",
|
tai_id: "tai-abc123",
|
||||||
machine_id: "m-xyz",
|
machine_id: "m-xyz",
|
||||||
version: "1.2.3",
|
version: "1.2.3",
|
||||||
|
|
@ -371,7 +371,8 @@ Returned by `sandbox.GetNode()`, `sandbox.Nodes()`, `sandbox.NodesByTeam()`. Rea
|
||||||
mode: "direct", // "direct" | "tunnel"
|
mode: "direct", // "direct" | "tunnel"
|
||||||
addr: "tai://192.168.1.100:19100",
|
addr: "tai://192.168.1.100:19100",
|
||||||
status: "online", // "online" | "offline" | "connecting"
|
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",
|
connected_at: "2026-03-07T08:00:00Z",
|
||||||
last_ping: "2026-03-07T10:05:00Z",
|
last_ping: "2026-03-07T10:05:00Z",
|
||||||
ports: {
|
ports: {
|
||||||
|
|
@ -550,7 +551,7 @@ const nodes = sandbox.Nodes()
|
||||||
|
|
||||||
// Find online GPU nodes
|
// Find online GPU nodes
|
||||||
const gpuNodes = nodes.filter(function(n) {
|
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)
|
console.log("Available GPU nodes:", gpuNodes.length)
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ func boxInfoToJS(v8ctx *v8go.Context, b *sandbox.BoxInfo) *v8go.Value {
|
||||||
data, _ := json.Marshal(map[string]interface{}{
|
data, _ := json.Marshal(map[string]interface{}{
|
||||||
"id": b.ID,
|
"id": b.ID,
|
||||||
"container_id": b.ContainerID,
|
"container_id": b.ContainerID,
|
||||||
"pool": b.Pool,
|
"node_id": b.NodeID,
|
||||||
"owner": b.Owner,
|
"owner": b.Owner,
|
||||||
"status": b.Status,
|
"status": b.Status,
|
||||||
"image": b.Image,
|
"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 {
|
func computerInfoToJS(v8ctx *v8go.Context, c sandbox.ComputerInfo) *v8go.Value {
|
||||||
data, _ := json.Marshal(map[string]interface{}{
|
data, _ := json.Marshal(map[string]interface{}{
|
||||||
"kind": c.Kind,
|
"kind": c.Kind,
|
||||||
"pool": c.Pool,
|
"node_id": c.NodeID,
|
||||||
"tai_id": c.TaiID,
|
"tai_id": c.TaiID,
|
||||||
"machine_id": c.MachineID,
|
"machine_id": c.MachineID,
|
||||||
"version": c.Version,
|
"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.
|
// 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) {
|
func getComputer(ctx context.Context, kind, identifier string) (sandbox.Computer, error) {
|
||||||
m := sandbox.M()
|
m := sandbox.M()
|
||||||
if kind == "box" {
|
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 {
|
func sbHost(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
v8ctx := info.Context()
|
v8ctx := info.Context()
|
||||||
|
|
||||||
pool := ""
|
nodeID := ""
|
||||||
args := info.Args()
|
args := info.Args()
|
||||||
if len(args) > 0 && args[0].IsString() {
|
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())
|
return throwError(info, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err := NewComputerObject(v8ctx, "host", pool)
|
val, err := NewComputerObject(v8ctx, "host", nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return throwError(info, err.Error())
|
return throwError(info, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -452,20 +452,20 @@ func NewComputerObject(v8ctx *v8go.Context, kind string, identifier string) (*v8
|
||||||
|
|
||||||
idStr := ""
|
idStr := ""
|
||||||
ownerStr := ""
|
ownerStr := ""
|
||||||
poolStr := identifier
|
nodeIDStr := identifier
|
||||||
if kind == "box" {
|
if kind == "box" {
|
||||||
if comp, err := getComputer(ctx, kind, identifier); err == nil {
|
if comp, err := getComputer(ctx, kind, identifier); err == nil {
|
||||||
box := comp.(*sandbox.Box)
|
box := comp.(*sandbox.Box)
|
||||||
idStr = box.ID()
|
idStr = box.ID()
|
||||||
ownerStr = box.Owner()
|
ownerStr = box.Owner()
|
||||||
poolStr = box.Pool()
|
nodeIDStr = box.NodeID()
|
||||||
} else {
|
} else {
|
||||||
idStr = identifier
|
idStr = identifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj.Set("id", idStr)
|
obj.Set("id", idStr)
|
||||||
obj.Set("owner", ownerStr)
|
obj.Set("owner", ownerStr)
|
||||||
obj.Set("pool", poolStr)
|
obj.Set("node_id", nodeIDStr)
|
||||||
|
|
||||||
return obj.Value, nil
|
return obj.Value, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
// sandbox.Get(id) → Manager.Get(ctx, id) → Computer (Box)
|
// sandbox.Get(id) → Manager.Get(ctx, id) → Computer (Box)
|
||||||
// sandbox.List(filter?) → Manager.List(ctx, ListOptions) → BoxInfo[]
|
// sandbox.List(filter?) → Manager.List(ctx, ListOptions) → BoxInfo[]
|
||||||
// sandbox.Delete(id) → Manager.Remove(ctx, id) → void
|
// 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.GetNode(id) → registry.Global().Get(id) → NodeInfo | null
|
||||||
// sandbox.Nodes() → registry.Global().List() → NodeInfo[]
|
// sandbox.Nodes() → registry.Global().List() → NodeInfo[]
|
||||||
// sandbox.NodesByTeam(t)→ registry.Global().ListByTeam(t) → 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 {
|
if v, ok := raw["owner"].(string); ok {
|
||||||
opts.Owner = v
|
opts.Owner = v
|
||||||
}
|
}
|
||||||
if v, ok := raw["pool"].(string); ok {
|
if v, ok := raw["node_id"].(string); ok {
|
||||||
opts.Pool = v
|
opts.NodeID = v
|
||||||
}
|
}
|
||||||
if v, ok := raw["image"].(string); ok {
|
if v, ok := raw["image"].(string); ok {
|
||||||
opts.Image = v
|
opts.Image = v
|
||||||
|
|
@ -221,8 +221,8 @@ func sbList(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
if v, ok := raw["owner"].(string); ok {
|
if v, ok := raw["owner"].(string); ok {
|
||||||
opts.Owner = v
|
opts.Owner = v
|
||||||
}
|
}
|
||||||
if v, ok := raw["pool"].(string); ok {
|
if v, ok := raw["node_id"].(string); ok {
|
||||||
opts.Pool = v
|
opts.NodeID = v
|
||||||
}
|
}
|
||||||
if v, ok := raw["labels"].(map[string]interface{}); ok {
|
if v, ok := raw["labels"].(map[string]interface{}); ok {
|
||||||
labels := make(map[string]string, len(v))
|
labels := make(map[string]string, len(v))
|
||||||
|
|
@ -250,7 +250,7 @@ func sbList(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
items = append(items, map[string]interface{}{
|
items = append(items, map[string]interface{}{
|
||||||
"id": bi.ID,
|
"id": bi.ID,
|
||||||
"container_id": bi.ContainerID,
|
"container_id": bi.ContainerID,
|
||||||
"pool": bi.Pool,
|
"node_id": bi.NodeID,
|
||||||
"owner": bi.Owner,
|
"owner": bi.Owner,
|
||||||
"status": bi.Status,
|
"status": bi.Status,
|
||||||
"image": bi.Image,
|
"image": bi.Image,
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ func TestCreate(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestCreate() {
|
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.kind !== "box") throw new Error("kind=" + pc.kind);
|
||||||
if (!pc.id) throw new Error("no id");
|
if (!pc.id) throw new Error("no id");
|
||||||
var id = pc.id;
|
var id = pc.id;
|
||||||
|
|
@ -124,7 +124,7 @@ func TestGet(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestGet() {
|
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 id = pc.id;
|
||||||
var got = sandbox.Get(id);
|
var got = sandbox.Get(id);
|
||||||
if (!got) throw new Error("Get returned null");
|
if (!got) throw new Error("Get returned null");
|
||||||
|
|
@ -161,7 +161,7 @@ func TestDelete(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestDelete() {
|
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;
|
var id = pc.id;
|
||||||
sandbox.Delete(id);
|
sandbox.Delete(id);
|
||||||
var got = sandbox.Get(id);
|
var got = sandbox.Get(id);
|
||||||
|
|
@ -185,8 +185,8 @@ func TestList(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestList() {
|
res := runJS(t, fmt.Sprintf(`function TestList() {
|
||||||
var a = 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", pool: "%s" });
|
var b = sandbox.Create({ image: "%s", owner: "list-user", node_id: "%s" });
|
||||||
var list = sandbox.List({ owner: "list-user" });
|
var list = sandbox.List({ owner: "list-user" });
|
||||||
var count = list.length;
|
var count = list.length;
|
||||||
sandbox.Delete(a.id);
|
sandbox.Delete(a.id);
|
||||||
|
|
@ -212,7 +212,7 @@ func TestExec(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestExec() {
|
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"]);
|
var r = pc.Exec(["echo", "hello-jsapi"]);
|
||||||
sandbox.Delete(pc.id);
|
sandbox.Delete(pc.id);
|
||||||
return r.stdout;
|
return r.stdout;
|
||||||
|
|
@ -232,7 +232,7 @@ func TestExecWithOptions(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestExecWithOptions() {
|
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" });
|
var r = pc.Exec(["pwd"], { workdir: "/tmp" });
|
||||||
sandbox.Delete(pc.id);
|
sandbox.Delete(pc.id);
|
||||||
return r.stdout;
|
return r.stdout;
|
||||||
|
|
@ -256,7 +256,7 @@ func TestStream(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestStream() {
|
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 chunks = [];
|
||||||
var exitCode = -1;
|
var exitCode = -1;
|
||||||
pc.Stream(["echo", "streaming"], function(type, data) {
|
pc.Stream(["echo", "streaming"], function(type, data) {
|
||||||
|
|
@ -285,7 +285,7 @@ func TestComputerInfo(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestComputerInfo() {
|
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();
|
var info = pc.ComputerInfo();
|
||||||
sandbox.Delete(pc.id);
|
sandbox.Delete(pc.id);
|
||||||
return info.kind;
|
return info.kind;
|
||||||
|
|
@ -308,7 +308,7 @@ func TestBoxInfo(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestBoxInfo() {
|
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();
|
var info = pc.Info();
|
||||||
sandbox.Delete(pc.id);
|
sandbox.Delete(pc.id);
|
||||||
return info.id ? "ok" : "no-id";
|
return info.id ? "ok" : "no-id";
|
||||||
|
|
@ -356,7 +356,7 @@ func TestComputerKind(t *testing.T) {
|
||||||
setupSandbox(t, &m)
|
setupSandbox(t, &m)
|
||||||
img := testImage()
|
img := testImage()
|
||||||
res := runJS(t, fmt.Sprintf(`function TestComputerKind() {
|
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;
|
var k = pc.kind;
|
||||||
sandbox.Delete(pc.id);
|
sandbox.Delete(pc.id);
|
||||||
return k;
|
return k;
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func snapshotToJS(v8ctx *v8go.Context, snap *registry.NodeSnapshot) (*v8go.Value
|
||||||
"mode": snap.Mode,
|
"mode": snap.Mode,
|
||||||
"addr": snap.Addr,
|
"addr": snap.Addr,
|
||||||
"status": snap.Status,
|
"status": snap.Status,
|
||||||
"pool": snap.PoolName,
|
"display_name": snap.DisplayName,
|
||||||
"connected_at": snap.ConnectedAt.Format(time.RFC3339),
|
"connected_at": snap.ConnectedAt.Format(time.RFC3339),
|
||||||
"last_ping": snap.LastPing.Format(time.RFC3339),
|
"last_ping": snap.LastPing.Format(time.RFC3339),
|
||||||
"ports": ports,
|
"ports": ports,
|
||||||
|
|
@ -117,12 +117,13 @@ func snapshotsToJSArray(v8ctx *v8go.Context, snaps []registry.NodeSnapshot) *v8g
|
||||||
}
|
}
|
||||||
items = append(items, map[string]interface{}{
|
items = append(items, map[string]interface{}{
|
||||||
"tai_id": snap.TaiID,
|
"tai_id": snap.TaiID,
|
||||||
|
"node_id": snap.TaiID,
|
||||||
"machine_id": snap.MachineID,
|
"machine_id": snap.MachineID,
|
||||||
"version": snap.Version,
|
"version": snap.Version,
|
||||||
"mode": snap.Mode,
|
"mode": snap.Mode,
|
||||||
"addr": snap.Addr,
|
"addr": snap.Addr,
|
||||||
"status": snap.Status,
|
"status": snap.Status,
|
||||||
"pool": snap.PoolName,
|
"display_name": snap.DisplayName,
|
||||||
"connected_at": snap.ConnectedAt.Format(time.RFC3339),
|
"connected_at": snap.ConnectedAt.Format(time.RFC3339),
|
||||||
"last_ping": snap.LastPing.Format(time.RFC3339),
|
"last_ping": snap.LastPing.Format(time.RFC3339),
|
||||||
"ports": ports,
|
"ports": ports,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ func (m *Manager) Start(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, snap := range reg.List() {
|
for _, snap := range reg.List() {
|
||||||
client, err := m.getPool(snap.TaiID)
|
client, err := m.getNode(snap.TaiID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +45,8 @@ func (m *Manager) Start(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pools returns the list of registered Tai nodes from the registry.
|
// Nodes returns the list of registered Tai nodes from the registry.
|
||||||
func (m *Manager) Pools() []registry.NodeSnapshot {
|
func (m *Manager) Nodes() []registry.NodeSnapshot {
|
||||||
reg := registry.Global()
|
reg := registry.Global()
|
||||||
if reg == nil {
|
if reg == nil {
|
||||||
return 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.
|
// Host returns a Host handle for executing commands on the Tai host machine.
|
||||||
func (m *Manager) Host(_ context.Context, pool string) (*Host, error) {
|
func (m *Manager) Host(_ context.Context, nodeID string) (*Host, error) {
|
||||||
if pool == "" {
|
if nodeID == "" {
|
||||||
return nil, ErrPoolMissing
|
return nil, ErrNodeMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := m.getPool(pool)
|
client, err := m.getNode(nodeID)
|
||||||
if err != nil {
|
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 {
|
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.
|
// 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")
|
return nil, fmt.Errorf("sandbox: image is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
poolName := opts.Pool
|
nodeID := opts.NodeID
|
||||||
|
|
||||||
if opts.WorkspaceID != "" {
|
if opts.WorkspaceID != "" {
|
||||||
if wsm := workspace.M(); wsm != nil {
|
if wsm := workspace.M(); wsm != nil {
|
||||||
|
|
@ -100,12 +100,12 @@ func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Box, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("sandbox: resolve workspace %q: %w", opts.WorkspaceID, err)
|
return nil, fmt.Errorf("sandbox: resolve workspace %q: %w", opts.WorkspaceID, err)
|
||||||
}
|
}
|
||||||
poolName = node
|
nodeID = node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if poolName == "" {
|
if nodeID == "" {
|
||||||
return nil, ErrPoolMissing
|
return nil, ErrNodeMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
id := opts.ID
|
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())
|
id = fmt.Sprintf("sb-%d", time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := m.getPool(poolName)
|
client, err := m.getNode(nodeID)
|
||||||
if err != nil {
|
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 {
|
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)
|
containerID, err := client.Sandbox().Create(ctx, taiOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -142,7 +142,7 @@ func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Box, error)
|
||||||
box := &Box{
|
box := &Box{
|
||||||
id: id,
|
id: id,
|
||||||
containerID: containerID,
|
containerID: containerID,
|
||||||
pool: poolName,
|
nodeID: nodeID,
|
||||||
owner: opts.Owner,
|
owner: opts.Owner,
|
||||||
policy: policy,
|
policy: policy,
|
||||||
labels: opts.Labels,
|
labels: opts.Labels,
|
||||||
|
|
@ -188,7 +188,7 @@ func (m *Manager) List(_ context.Context, opts ListOptions) ([]*Box, error) {
|
||||||
if opts.Owner != "" && b.owner != opts.Owner {
|
if opts.Owner != "" && b.owner != opts.Owner {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if opts.Pool != "" && b.pool != opts.Pool {
|
if opts.NodeID != "" && b.nodeID != opts.NodeID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(opts.Labels) > 0 {
|
if len(opts.Labels) > 0 {
|
||||||
|
|
@ -212,7 +212,7 @@ func (m *Manager) Remove(ctx context.Context, id string) error {
|
||||||
}
|
}
|
||||||
b := v.(*Box)
|
b := v.(*Box)
|
||||||
|
|
||||||
client, err := m.getPool(b.pool)
|
client, err := m.getNode(b.nodeID)
|
||||||
if err == nil && client.Sandbox() != nil {
|
if err == nil && client.Sandbox() != nil {
|
||||||
client.Sandbox().Remove(ctx, b.containerID, true)
|
client.Sandbox().Remove(ctx, b.containerID, true)
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +237,7 @@ func (m *Manager) Cleanup(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
case LongRunning:
|
case LongRunning:
|
||||||
if timeout := b.idleTimeout(); timeout > 0 && idle > timeout {
|
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())
|
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)
|
client, ok := tai.GetClient(name)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrPoolNotFound
|
return nil, ErrNodeNotFound
|
||||||
}
|
}
|
||||||
return client, nil
|
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)
|
env := make(map[string]string)
|
||||||
|
|
||||||
reg := registry.Global()
|
reg := registry.Global()
|
||||||
if reg != nil {
|
if reg != nil {
|
||||||
if snap, ok := reg.Get(poolName); ok {
|
if snap, ok := reg.Get(nodeID); ok {
|
||||||
grpcEnv := BuildGRPCEnv(snap.Mode, snap.Addr, sandboxID)
|
grpcEnv := BuildGRPCEnv(snap.Mode, snap.Addr, sandboxID)
|
||||||
for k, v := range grpcEnv {
|
for k, v := range grpcEnv {
|
||||||
env[k] = v
|
env[k] = v
|
||||||
|
|
@ -299,11 +299,11 @@ func (m *Manager) buildTaiCreateOptions(opts CreateOptions, poolName, sandboxID
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
"managed-by": "yao-sandbox",
|
"managed-by": "yao-sandbox",
|
||||||
"sandbox-id": sandboxID,
|
"sandbox-id": sandboxID,
|
||||||
"sandbox-owner": opts.Owner,
|
"sandbox-owner": opts.Owner,
|
||||||
"sandbox-pool": poolName,
|
"sandbox-node-id": nodeID,
|
||||||
"sandbox-policy": string(opts.Policy),
|
"sandbox-policy": string(opts.Policy),
|
||||||
}
|
}
|
||||||
if opts.WorkspaceID != "" {
|
if opts.WorkspaceID != "" {
|
||||||
labels["workspace-id"] = 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 {
|
if client.Sandbox() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -391,7 +391,7 @@ func (m *Manager) recoverBoxes(ctx context.Context, poolName string, client *tai
|
||||||
box := &Box{
|
box := &Box{
|
||||||
id: sandboxID,
|
id: sandboxID,
|
||||||
containerID: cid,
|
containerID: cid,
|
||||||
pool: c.Labels["sandbox-pool"],
|
nodeID: c.Labels["sandbox-node-id"],
|
||||||
owner: c.Labels["sandbox-owner"],
|
owner: c.Labels["sandbox-owner"],
|
||||||
policy: LifecyclePolicy(c.Labels["sandbox-policy"]),
|
policy: LifecyclePolicy(c.Labels["sandbox-policy"]),
|
||||||
labels: c.Labels,
|
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.
|
// ImageExists reports whether the given image ref exists on the target node.
|
||||||
func (m *Manager) ImageExists(ctx context.Context, pool, ref string) (bool, error) {
|
func (m *Manager) ImageExists(ctx context.Context, nodeID, ref string) (bool, error) {
|
||||||
client, err := m.getPool(pool)
|
client, err := m.getNode(nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
@ -418,10 +418,10 @@ func (m *Manager) ImageExists(ctx context.Context, pool, ref string) (bool, erro
|
||||||
return img.Exists(ctx, ref)
|
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.
|
// real-time progress events.
|
||||||
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) {
|
||||||
client, err := m.getPool(pool)
|
client, err := m.getNode(nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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.
|
// pulls the image and blocks until the pull completes.
|
||||||
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 {
|
||||||
exists, err := m.ImageExists(ctx, pool, ref)
|
exists, err := m.ImageExists(ctx, nodeID, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("image exists check: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ch, err := m.PullImage(ctx, pool, ref, opts)
|
ch, err := m.PullImage(ctx, nodeID, ref, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("image pull: %w", err)
|
return fmt.Errorf("image pull: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ import (
|
||||||
func TestHeartbeatUpdates(t *testing.T) {
|
func TestHeartbeatUpdates(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
err := m.Heartbeat(box.ID(), true, 5)
|
err := m.Heartbeat(box.ID(), true, 5)
|
||||||
|
|
@ -34,10 +34,10 @@ func TestHeartbeatUpdates(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeartbeatUnknownBox(t *testing.T) {
|
func TestHeartbeatUnknownBox(t *testing.T) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
err := m.Heartbeat("nonexistent", true, 1)
|
err := m.Heartbeat("nonexistent", true, 1)
|
||||||
if err != sandbox.ErrNotFound {
|
if err != sandbox.ErrNotFound {
|
||||||
t.Errorf("err = %v, want ErrNotFound", err)
|
t.Errorf("err = %v, want ErrNotFound", err)
|
||||||
|
|
@ -49,17 +49,17 @@ func TestHeartbeatUnknownBox(t *testing.T) {
|
||||||
func TestIdleCleanup(t *testing.T) {
|
func TestIdleCleanup(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
ensureTestImage(t, m, pc.TaiID)
|
ensureTestImage(t, m, pc.TaiID)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
box, err := m.Create(ctx, sandbox.CreateOptions{
|
box, err := m.Create(ctx, sandbox.CreateOptions{
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "test-user",
|
Owner: "test-user",
|
||||||
Pool: pc.TaiID,
|
NodeID: pc.TaiID,
|
||||||
Policy: sandbox.Session,
|
Policy: sandbox.Session,
|
||||||
IdleTimeout: 1 * time.Second,
|
IdleTimeout: 1 * time.Second,
|
||||||
})
|
})
|
||||||
|
|
@ -85,10 +85,10 @@ func TestIdleCleanup(t *testing.T) {
|
||||||
func TestStartRecovery(t *testing.T) {
|
func TestStartRecovery(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m1 := setupManagerForPool(t, &pc)
|
m1 := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m1, pc)
|
box := createTestBox(t, m1, pc)
|
||||||
boxID := box.ID()
|
boxID := box.ID()
|
||||||
|
|
||||||
|
|
@ -117,10 +117,10 @@ func TestStartRecovery(t *testing.T) {
|
||||||
func TestPersistentNotCleaned(t *testing.T) {
|
func TestPersistentNotCleaned(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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) {
|
box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) {
|
||||||
co.Policy = sandbox.Persistent
|
co.Policy = sandbox.Persistent
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ import (
|
||||||
func TestCreateAndExec(t *testing.T) {
|
func TestCreateAndExec(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
|
@ -37,10 +37,10 @@ func TestCreateAndExec(t *testing.T) {
|
||||||
func TestCreateWithLabels(t *testing.T) {
|
func TestCreateWithLabels(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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) {
|
box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) {
|
||||||
co.Labels = map[string]string{"app": "test-app"}
|
co.Labels = map[string]string{"app": "test-app"}
|
||||||
})
|
})
|
||||||
|
|
@ -60,10 +60,10 @@ func TestCreateWithLabels(t *testing.T) {
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
box := createTestBox(t, m, pc)
|
box := createTestBox(t, m, pc)
|
||||||
|
|
||||||
got, err := m.Get(context.Background(), box.ID())
|
got, err := m.Get(context.Background(), box.ID())
|
||||||
|
|
@ -78,10 +78,10 @@ func TestGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetNotFound(t *testing.T) {
|
func TestGetNotFound(t *testing.T) {
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
_, err := m.Get(context.Background(), "nonexistent")
|
_, err := m.Get(context.Background(), "nonexistent")
|
||||||
if err != sandbox.ErrNotFound {
|
if err != sandbox.ErrNotFound {
|
||||||
t.Errorf("err = %v, want ErrNotFound", err)
|
t.Errorf("err = %v, want ErrNotFound", err)
|
||||||
|
|
@ -93,10 +93,10 @@ func TestGetNotFound(t *testing.T) {
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
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) {
|
box := createTestBox(t, m, pc, func(co *sandbox.CreateOptions) {
|
||||||
co.Owner = "user-list"
|
co.Owner = "user-list"
|
||||||
})
|
})
|
||||||
|
|
@ -129,16 +129,16 @@ func TestList(t *testing.T) {
|
||||||
func TestRemove(t *testing.T) {
|
func TestRemove(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
for _, pc := range testPools() {
|
for _, pc := range testNodes() {
|
||||||
pc := pc
|
pc := pc
|
||||||
t.Run(pc.Name, func(t *testing.T) {
|
t.Run(pc.Name, func(t *testing.T) {
|
||||||
m := setupManagerForPool(t, &pc)
|
m := setupManagerForNode(t, &pc)
|
||||||
ensureTestImage(t, m, pc.TaiID)
|
ensureTestImage(t, m, pc.TaiID)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
box, err := m.Create(ctx, sandbox.CreateOptions{
|
box, err := m.Create(ctx, sandbox.CreateOptions{
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "test-user",
|
Owner: "test-user",
|
||||||
Pool: pc.TaiID,
|
NodeID: pc.TaiID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create: %v", err)
|
t.Fatalf("Create: %v", err)
|
||||||
|
|
@ -157,38 +157,38 @@ func TestRemove(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateNoImage(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{
|
_, err := m.Create(context.Background(), sandbox.CreateOptions{
|
||||||
Owner: "test",
|
Owner: "test",
|
||||||
Pool: pools[0].TaiID,
|
NodeID: nodes[0].TaiID,
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for missing image")
|
t.Error("expected error for missing image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateNoPool(t *testing.T) {
|
func TestCreateNoNodeID(t *testing.T) {
|
||||||
m, _ := setupManager(t, poolConfig{Name: "local", Addr: testLocalAddr()})
|
m, _ := setupManager(t, nodeConfig{Name: "local", Addr: testLocalAddr()})
|
||||||
|
|
||||||
_, err := m.Create(context.Background(), sandbox.CreateOptions{
|
_, err := m.Create(context.Background(), sandbox.CreateOptions{
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
})
|
})
|
||||||
if err != sandbox.ErrPoolMissing {
|
if err != sandbox.ErrNodeMissing {
|
||||||
t.Errorf("err = %v, want ErrPoolMissing", err)
|
t.Errorf("err = %v, want ErrNodeMissing", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiPool(t *testing.T) {
|
func TestMultiNode(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
skipIfNoTai(t)
|
skipIfNoTai(t)
|
||||||
|
|
||||||
pools := testPools()
|
nodes := testNodes()
|
||||||
if len(pools) < 2 {
|
if len(nodes) < 2 {
|
||||||
t.Skip("need at least 2 pools (local + remote) for multi-pool test")
|
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 {
|
for _, pc := range registered {
|
||||||
ensureTestImage(t, m, pc.TaiID)
|
ensureTestImage(t, m, pc.TaiID)
|
||||||
|
|
@ -198,9 +198,9 @@ func TestMultiPool(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
localBox, err := m.Create(ctx, sandbox.CreateOptions{
|
localBox, err := m.Create(ctx, sandbox.CreateOptions{
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "test-user",
|
Owner: "test-user",
|
||||||
Pool: registered[0].TaiID,
|
NodeID: registered[0].TaiID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create on local: %v", err)
|
t.Fatalf("Create on local: %v", err)
|
||||||
|
|
@ -208,9 +208,9 @@ func TestMultiPool(t *testing.T) {
|
||||||
defer m.Remove(ctx, localBox.ID())
|
defer m.Remove(ctx, localBox.ID())
|
||||||
|
|
||||||
remoteBox, err := m.Create(ctx, sandbox.CreateOptions{
|
remoteBox, err := m.Create(ctx, sandbox.CreateOptions{
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "test-user",
|
Owner: "test-user",
|
||||||
Pool: registered[1].TaiID,
|
NodeID: registered[1].TaiID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create on remote: %v", err)
|
t.Fatalf("Create on remote: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// purgeStaleContainers removes leftover sb-* containers/pods from previous
|
// 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() {
|
func purgeStaleContainers() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
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")
|
Name string // human-readable label for t.Run (e.g. "remote", "k8s")
|
||||||
Addr string
|
Addr string
|
||||||
TaiID string // actual registry key, filled after tai.New
|
TaiID string // actual registry key, filled after tai.New
|
||||||
Options []tai.Option
|
Options []tai.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
// testPools returns all available pool configurations for multi-mode testing.
|
// testNodes returns all available node configurations for multi-mode testing.
|
||||||
func testPools() []poolConfig {
|
func testNodes() []nodeConfig {
|
||||||
pools := []poolConfig{
|
nodes := []nodeConfig{
|
||||||
{Name: "local", Addr: testLocalAddr()},
|
{Name: "local", Addr: testLocalAddr()},
|
||||||
}
|
}
|
||||||
if addr := os.Getenv("SANDBOX_TEST_REMOTE_ADDR"); addr != "" {
|
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 != "" {
|
if host := os.Getenv("TAI_TEST_CONTAINERIZED_HOST"); host != "" {
|
||||||
grpcPort := envPort("TAI_TEST_CONTAINERIZED_GRPC_PORT", 9200)
|
grpcPort := envPort("TAI_TEST_CONTAINERIZED_GRPC_PORT", 9200)
|
||||||
addr := fmt.Sprintf("tai://%s:%d", host, grpcPort)
|
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 != "" {
|
if host := os.Getenv("TAI_TEST_K8S_HOST"); host != "" {
|
||||||
kubeconfig := os.Getenv("TAI_TEST_KUBECONFIG")
|
kubeconfig := os.Getenv("TAI_TEST_KUBECONFIG")
|
||||||
if kubeconfig == "" {
|
if kubeconfig == "" {
|
||||||
return pools
|
return nodes
|
||||||
}
|
}
|
||||||
grpcPort := envPort("TAI_TEST_K8S_GRPC_PORT", envPort("TAI_TEST_GRPC_PORT", 19100))
|
grpcPort := envPort("TAI_TEST_K8S_GRPC_PORT", envPort("TAI_TEST_GRPC_PORT", 19100))
|
||||||
addr := fmt.Sprintf("tai://%s:%d", host, grpcPort)
|
addr := fmt.Sprintf("tai://%s:%d", host, grpcPort)
|
||||||
|
|
@ -136,9 +136,9 @@ func testPools() []poolConfig {
|
||||||
if ns := os.Getenv("TAI_TEST_K8S_NAMESPACE"); ns != "" {
|
if ns := os.Getenv("TAI_TEST_K8S_NAMESPACE"); ns != "" {
|
||||||
opts = append(opts, tai.WithNamespace(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) {
|
func skipIfNoDocker(t *testing.T) {
|
||||||
|
|
@ -237,9 +237,9 @@ func envPort(key string, fallback int) int {
|
||||||
return fallback
|
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.
|
// 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()
|
t.Helper()
|
||||||
|
|
||||||
reg := registry.Global()
|
reg := registry.Global()
|
||||||
|
|
@ -255,7 +255,7 @@ func registerPool(t *testing.T, pc *poolConfig) {
|
||||||
t.Cleanup(func() { client.Close() })
|
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()
|
t.Helper()
|
||||||
|
|
||||||
reg := registry.Global()
|
reg := registry.Global()
|
||||||
|
|
@ -264,8 +264,8 @@ func setupManager(t *testing.T, pools ...poolConfig) (*sandbox.Manager, []poolCo
|
||||||
}
|
}
|
||||||
_ = reg
|
_ = reg
|
||||||
|
|
||||||
out := make([]poolConfig, len(pools))
|
out := make([]nodeConfig, len(nodes))
|
||||||
copy(out, pools)
|
copy(out, nodes)
|
||||||
for i := range out {
|
for i := range out {
|
||||||
client, err := tai.New(out[i].Addr, out[i].Options...)
|
client, err := tai.New(out[i].Addr, out[i].Options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -280,7 +280,7 @@ func setupManager(t *testing.T, pools ...poolConfig) (*sandbox.Manager, []poolCo
|
||||||
return m, out
|
return m, out
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupManagerForPool(t *testing.T, pc *poolConfig) *sandbox.Manager {
|
func setupManagerForNode(t *testing.T, pc *nodeConfig) *sandbox.Manager {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
m, registered := setupManager(t, *pc)
|
m, registered := setupManager(t, *pc)
|
||||||
*pc = registered[0]
|
*pc = registered[0]
|
||||||
|
|
@ -289,38 +289,38 @@ func setupManagerForPool(t *testing.T, pc *poolConfig) *sandbox.Manager {
|
||||||
|
|
||||||
// setupManagerWithWorkspace creates a sandbox Manager and returns
|
// setupManagerWithWorkspace creates a sandbox Manager and returns
|
||||||
// the global workspace.Manager (which uses the registry for client lookups).
|
// 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()
|
t.Helper()
|
||||||
sbm := setupManagerForPool(t, pc)
|
sbm := setupManagerForNode(t, pc)
|
||||||
return sbm, workspace.M()
|
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()
|
t.Helper()
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := m.EnsureImage(ctx, pool, testImage(), sandbox.ImagePullOptions{}); err != nil {
|
if err := m.EnsureImage(ctx, nodeID, testImage(), sandbox.ImagePullOptions{}); err != nil {
|
||||||
t.Fatalf("EnsureImage(%s, %s): %v", pool, testImage(), err)
|
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()
|
t.Helper()
|
||||||
co := sandbox.CreateOptions{
|
co := sandbox.CreateOptions{
|
||||||
Image: testImage(),
|
Image: testImage(),
|
||||||
Owner: "test-user",
|
Owner: "test-user",
|
||||||
Pool: pc.TaiID,
|
NodeID: pc.TaiID,
|
||||||
}
|
}
|
||||||
for _, fn := range opts {
|
for _, fn := range opts {
|
||||||
fn(&co)
|
fn(&co)
|
||||||
}
|
}
|
||||||
|
|
||||||
pool := co.Pool
|
nodeID := co.NodeID
|
||||||
if pool == "" {
|
if nodeID == "" {
|
||||||
pools := m.Pools()
|
nodes := m.Nodes()
|
||||||
if len(pools) > 0 {
|
if len(nodes) > 0 {
|
||||||
pool = pools[0].TaiID
|
nodeID = nodes[0].TaiID
|
||||||
co.Pool = pool
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if pool != "" {
|
if nodeID != "" {
|
||||||
if err := m.EnsureImage(ctx, pool, co.Image, sandbox.ImagePullOptions{}); err != nil {
|
if err := m.EnsureImage(ctx, nodeID, co.Image, sandbox.ImagePullOptions{}); err != nil {
|
||||||
if isK8s {
|
if isK8s {
|
||||||
<-k8sSem
|
<-k8sSem
|
||||||
}
|
}
|
||||||
t.Fatalf("EnsureImage(%s, %s): %v", pool, co.Image, err)
|
t.Fatalf("EnsureImage(%s, %s): %v", nodeID, co.Image, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ type Computer interface {
|
||||||
// ComputerInfo holds identity and registry information for a Computer.
|
// ComputerInfo holds identity and registry information for a Computer.
|
||||||
type ComputerInfo struct {
|
type ComputerInfo struct {
|
||||||
Kind string // "box" | "host"
|
Kind string // "box" | "host"
|
||||||
Pool string
|
NodeID string
|
||||||
TaiID string
|
TaiID string
|
||||||
MachineID string
|
MachineID string
|
||||||
Version string
|
Version string
|
||||||
|
|
@ -84,7 +84,7 @@ type CreateOptions struct {
|
||||||
ID string
|
ID string
|
||||||
Owner string
|
Owner string
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
Pool string
|
NodeID string
|
||||||
Image string
|
Image string
|
||||||
WorkDir string
|
WorkDir string
|
||||||
User string
|
User string
|
||||||
|
|
@ -105,7 +105,7 @@ type CreateOptions struct {
|
||||||
|
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
Owner string
|
Owner string
|
||||||
Pool string
|
NodeID string
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +211,7 @@ type ServiceConn struct {
|
||||||
type BoxInfo struct {
|
type BoxInfo struct {
|
||||||
ID string
|
ID string
|
||||||
ContainerID string
|
ContainerID string
|
||||||
Pool string
|
NodeID string
|
||||||
Owner string
|
Owner string
|
||||||
Status string
|
Status string
|
||||||
Policy LifecyclePolicy
|
Policy LifecyclePolicy
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ type TaiNode struct {
|
||||||
Status string // "online" | "offline" | "connecting"
|
Status string // "online" | "offline" | "connecting"
|
||||||
ConnectedAt time.Time
|
ConnectedAt time.Time
|
||||||
LastPing time.Time
|
LastPing time.Time
|
||||||
PoolName string
|
DisplayName string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ type NodeSnapshot struct {
|
||||||
Capabilities map[string]bool
|
Capabilities map[string]bool
|
||||||
Status string
|
Status string
|
||||||
ConnectedAt, LastPing time.Time
|
ConnectedAt, LastPing time.Time
|
||||||
PoolName string
|
DisplayName string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ type TaiNode struct {
|
||||||
Status string // "online" | "offline" | "connecting"
|
Status string // "online" | "offline" | "connecting"
|
||||||
ConnectedAt time.Time
|
ConnectedAt time.Time
|
||||||
LastPing 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
|
client any // *tai.Client; stored as any to avoid import cycle
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ type NodeSnapshot struct {
|
||||||
Status string
|
Status string
|
||||||
ConnectedAt time.Time
|
ConnectedAt time.Time
|
||||||
LastPing time.Time
|
LastPing time.Time
|
||||||
PoolName string
|
DisplayName string
|
||||||
client any
|
client any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,8 +83,8 @@ func (n *TaiNode) snapshot() NodeSnapshot {
|
||||||
Mode: n.Mode, Addr: n.Addr, YaoBase: n.YaoBase,
|
Mode: n.Mode, Addr: n.Addr, YaoBase: n.YaoBase,
|
||||||
Ports: ports, Capabilities: caps,
|
Ports: ports, Capabilities: caps,
|
||||||
Status: n.Status, ConnectedAt: n.ConnectedAt, LastPing: n.LastPing,
|
Status: n.Status, ConnectedAt: n.ConnectedAt, LastPing: n.LastPing,
|
||||||
PoolName: n.PoolName,
|
DisplayName: n.DisplayName,
|
||||||
client: n.client,
|
client: n.client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue