- Rename and enhance the Docker instance startup steps for Tai in CI workflows, improving clarity and readiness checks for both HTTP and gRPC services. - Update health check logic to ensure accurate reporting of service readiness, including specific error messages for failures. - Modify environment variable configurations to streamline the setup for K8s and Docker instances, ensuring consistent port usage across tests. These changes improve the reliability and clarity of the CI processes for the Tai service, enhancing overall testing and deployment workflows.
188 lines
4.8 KiB
Go
188 lines
4.8 KiB
Go
package sandbox_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
sandbox "github.com/yaoapp/yao/sandbox/v2"
|
|
"github.com/yaoapp/yao/tai"
|
|
"github.com/yaoapp/yao/tai/volume"
|
|
"github.com/yaoapp/yao/workspace"
|
|
)
|
|
|
|
type poolConfig struct {
|
|
Name string
|
|
Addr string
|
|
Options []tai.Option
|
|
}
|
|
|
|
// testPools returns all available pool configurations for multi-mode testing.
|
|
// - local: always present (direct Docker daemon)
|
|
// - remote: when SANDBOX_TEST_REMOTE_ADDR is set (Tai proxy → Docker)
|
|
// - k8s: when TAI_TEST_K8S_HOST + TAI_TEST_KUBECONFIG are set (Tai proxy → K8s)
|
|
func testPools() []poolConfig {
|
|
pools := []poolConfig{
|
|
{Name: "local", Addr: testLocalAddr()},
|
|
}
|
|
if addr := os.Getenv("SANDBOX_TEST_REMOTE_ADDR"); addr != "" {
|
|
pools = append(pools, poolConfig{Name: "remote", Addr: addr})
|
|
}
|
|
if host := os.Getenv("TAI_TEST_K8S_HOST"); host != "" {
|
|
kubeconfig := os.Getenv("TAI_TEST_KUBECONFIG")
|
|
if kubeconfig == "" {
|
|
return pools
|
|
}
|
|
grpcPort := envPort("TAI_TEST_K8S_GRPC_PORT", envPort("TAI_TEST_GRPC_PORT", 9100))
|
|
addr := fmt.Sprintf("tai://%s:%d", host, grpcPort)
|
|
opts := []tai.Option{
|
|
tai.K8s,
|
|
tai.WithKubeConfig(kubeconfig),
|
|
tai.WithPorts(tai.Ports{
|
|
K8s: envPort("TAI_TEST_K8S_PORT", 6443),
|
|
GRPC: grpcPort,
|
|
}),
|
|
}
|
|
if ns := os.Getenv("TAI_TEST_K8S_NAMESPACE"); ns != "" {
|
|
opts = append(opts, tai.WithNamespace(ns))
|
|
}
|
|
pools = append(pools, poolConfig{Name: "k8s", Addr: addr, Options: opts})
|
|
}
|
|
return pools
|
|
}
|
|
|
|
func skipIfNoDocker(t *testing.T) {
|
|
t.Helper()
|
|
addr := testLocalAddr()
|
|
if addr == "" {
|
|
t.Skip("SANDBOX_TEST_LOCAL_ADDR not set, skipping Docker tests")
|
|
}
|
|
}
|
|
|
|
func skipIfNoTai(t *testing.T) {
|
|
t.Helper()
|
|
if os.Getenv("SANDBOX_TEST_REMOTE_ADDR") == "" {
|
|
t.Skip("SANDBOX_TEST_REMOTE_ADDR not set, skipping Tai proxy tests")
|
|
}
|
|
}
|
|
|
|
func testLocalAddr() string {
|
|
if addr := os.Getenv("SANDBOX_TEST_LOCAL_ADDR"); addr != "" {
|
|
return addr
|
|
}
|
|
return "local"
|
|
}
|
|
|
|
func testImage() string {
|
|
if img := os.Getenv("SANDBOX_TEST_IMAGE"); img != "" {
|
|
return img
|
|
}
|
|
return "alpine:latest"
|
|
}
|
|
|
|
func envPort(key string, fallback int) int {
|
|
if v := os.Getenv(key); v != "" {
|
|
if p, err := strconv.Atoi(v); err == nil {
|
|
return p
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func setupManager(t *testing.T, pools ...sandbox.Pool) *sandbox.Manager {
|
|
t.Helper()
|
|
cfg := sandbox.Config{Pool: pools}
|
|
if err := sandbox.Init(cfg); err != nil {
|
|
t.Fatalf("Init: %v", err)
|
|
}
|
|
m := sandbox.M()
|
|
t.Cleanup(func() {
|
|
m.Close()
|
|
})
|
|
return m
|
|
}
|
|
|
|
func setupManagerForPool(t *testing.T, pc poolConfig, mutators ...func(*sandbox.Pool)) *sandbox.Manager {
|
|
t.Helper()
|
|
pool := sandbox.Pool{Name: pc.Name, Addr: pc.Addr, Options: pc.Options}
|
|
for _, fn := range mutators {
|
|
fn(&pool)
|
|
}
|
|
return setupManager(t, pool)
|
|
}
|
|
|
|
// setupManagerWithWorkspace creates a sandbox Manager with a linked workspace Manager.
|
|
// Returns both managers and a helper to create workspaces on the given pool's node.
|
|
func setupManagerWithWorkspace(t *testing.T, pc poolConfig) (*sandbox.Manager, *workspace.Manager) {
|
|
t.Helper()
|
|
sbm := setupManagerForPool(t, pc)
|
|
|
|
var wsClient *tai.Client
|
|
var err error
|
|
if pc.Addr == "local" || pc.Addr == "" {
|
|
dataDir := t.TempDir()
|
|
vol := volume.NewLocal(dataDir)
|
|
wsClient, err = tai.New("local", tai.WithVolume(vol), tai.WithDataDir(dataDir))
|
|
} else {
|
|
wsClient, err = tai.New(pc.Addr, pc.Options...)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("tai.New for workspace: %v", err)
|
|
}
|
|
t.Cleanup(func() { wsClient.Close() })
|
|
|
|
wsm := workspace.NewManager(map[string]*tai.Client{pc.Name: wsClient})
|
|
sbm.SetWorkspaceManager(wsm)
|
|
return sbm, wsm
|
|
}
|
|
|
|
// ensureTestImage guarantees testImage() is available on the given pool before
|
|
// container creation. Safe for all modes (Docker pull; K8s no-op).
|
|
func ensureTestImage(t *testing.T, m *sandbox.Manager, pool string) {
|
|
t.Helper()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
|
defer cancel()
|
|
if err := m.EnsureImage(ctx, pool, testImage(), sandbox.ImagePullOptions{}); err != nil {
|
|
t.Fatalf("EnsureImage(%s, %s): %v", pool, testImage(), err)
|
|
}
|
|
}
|
|
|
|
func createTestBox(t *testing.T, m *sandbox.Manager, opts ...func(*sandbox.CreateOptions)) *sandbox.Box {
|
|
t.Helper()
|
|
co := sandbox.CreateOptions{
|
|
Image: testImage(),
|
|
Owner: "test-user",
|
|
}
|
|
for _, fn := range opts {
|
|
fn(&co)
|
|
}
|
|
|
|
pool := co.Pool
|
|
if pool == "" {
|
|
pools := m.Pools()
|
|
if len(pools) > 0 {
|
|
pool = pools[0].Name
|
|
}
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
|
defer cancel()
|
|
|
|
if pool != "" {
|
|
if err := m.EnsureImage(ctx, pool, co.Image, sandbox.ImagePullOptions{}); err != nil {
|
|
t.Fatalf("EnsureImage(%s, %s): %v", pool, co.Image, err)
|
|
}
|
|
}
|
|
|
|
box, err := m.Create(ctx, co)
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
m.Remove(context.Background(), box.ID())
|
|
})
|
|
return box
|
|
}
|