mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
e4aca0db13
Expose a dedicated container metrics endpoint and surface current CPU, memory, and root filesystem usage in the bot container view. This gives operators a quick health snapshot while degrading cleanly on unsupported backends.
297 lines
8.5 KiB
Go
297 lines
8.5 KiB
Go
package workspace
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/containerd/errdefs"
|
|
|
|
"github.com/memohai/memoh/internal/config"
|
|
ctr "github.com/memohai/memoh/internal/containerd"
|
|
"github.com/memohai/memoh/internal/workspace/bridge"
|
|
)
|
|
|
|
type legacyRouteTestService struct {
|
|
container ctr.ContainerInfo
|
|
created bool
|
|
byLabel []ctr.ContainerInfo
|
|
|
|
createCalls int
|
|
startCalls int
|
|
deleteCalls int
|
|
removeNet int
|
|
deleteTask int
|
|
setupNet int
|
|
|
|
getContainerBeforeCreateErr error
|
|
setupNetworkResults []ctr.NetworkResult
|
|
setupNetworkErrs []error
|
|
}
|
|
|
|
func (*legacyRouteTestService) PullImage(context.Context, string, *ctr.PullImageOptions) (ctr.ImageInfo, error) {
|
|
return ctr.ImageInfo{}, nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) GetImage(context.Context, string) (ctr.ImageInfo, error) {
|
|
return ctr.ImageInfo{}, nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) ListImages(context.Context) ([]ctr.ImageInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) DeleteImage(context.Context, string, *ctr.DeleteImageOptions) error {
|
|
return nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) ResolveRemoteDigest(context.Context, string) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) CreateContainer(_ context.Context, req ctr.CreateContainerRequest) (ctr.ContainerInfo, error) {
|
|
s.createCalls++
|
|
s.created = true
|
|
s.container = ctr.ContainerInfo{
|
|
ID: req.ID,
|
|
Image: req.ImageRef,
|
|
Labels: req.Labels,
|
|
Snapshotter: req.Snapshotter,
|
|
SnapshotKey: req.ID,
|
|
}
|
|
return s.container, nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) GetContainer(context.Context, string) (ctr.ContainerInfo, error) {
|
|
if !s.created {
|
|
if s.getContainerBeforeCreateErr != nil {
|
|
return ctr.ContainerInfo{}, s.getContainerBeforeCreateErr
|
|
}
|
|
return ctr.ContainerInfo{}, errdefs.ErrNotFound
|
|
}
|
|
return s.container, nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) ListContainers(context.Context) ([]ctr.ContainerInfo, error) {
|
|
if !s.created {
|
|
return nil, nil
|
|
}
|
|
return []ctr.ContainerInfo{s.container}, nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) DeleteContainer(context.Context, string, *ctr.DeleteContainerOptions) error {
|
|
s.deleteCalls++
|
|
s.created = false
|
|
return nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) ListContainersByLabel(context.Context, string, string) ([]ctr.ContainerInfo, error) {
|
|
return s.byLabel, nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) StartContainer(context.Context, string, *ctr.StartTaskOptions) error {
|
|
s.startCalls++
|
|
return nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) StopContainer(context.Context, string, *ctr.StopTaskOptions) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) DeleteTask(context.Context, string, *ctr.DeleteTaskOptions) error {
|
|
s.deleteTask++
|
|
return nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) GetTaskInfo(context.Context, string) (ctr.TaskInfo, error) {
|
|
return ctr.TaskInfo{}, errdefs.ErrNotFound
|
|
}
|
|
|
|
func (*legacyRouteTestService) GetContainerMetrics(context.Context, string) (ctr.ContainerMetrics, error) {
|
|
return ctr.ContainerMetrics{}, ctr.ErrNotSupported
|
|
}
|
|
|
|
func (*legacyRouteTestService) ListTasks(context.Context, *ctr.ListTasksOptions) ([]ctr.TaskInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) SetupNetwork(context.Context, ctr.NetworkSetupRequest) (ctr.NetworkResult, error) {
|
|
idx := s.setupNet
|
|
s.setupNet++
|
|
if idx < len(s.setupNetworkErrs) && s.setupNetworkErrs[idx] != nil {
|
|
return ctr.NetworkResult{}, s.setupNetworkErrs[idx]
|
|
}
|
|
if idx < len(s.setupNetworkResults) {
|
|
return s.setupNetworkResults[idx], nil
|
|
}
|
|
return ctr.NetworkResult{IP: "10.0.0.2"}, nil
|
|
}
|
|
|
|
func (s *legacyRouteTestService) RemoveNetwork(context.Context, ctr.NetworkSetupRequest) error {
|
|
s.removeNet++
|
|
return nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) CommitSnapshot(context.Context, string, string, string) error {
|
|
return nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) ListSnapshots(context.Context, string) ([]ctr.SnapshotInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) PrepareSnapshot(context.Context, string, string, string) error {
|
|
return nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) CreateContainerFromSnapshot(context.Context, ctr.CreateContainerRequest) (ctr.ContainerInfo, error) {
|
|
return ctr.ContainerInfo{}, nil
|
|
}
|
|
|
|
func (*legacyRouteTestService) SnapshotMounts(context.Context, string, string) ([]ctr.MountInfo, error) {
|
|
return nil, ctr.ErrNotSupported
|
|
}
|
|
|
|
func newLegacyRouteTestManager(t *testing.T, svc ctr.Service, cfg config.WorkspaceConfig) *Manager {
|
|
t.Helper()
|
|
logger := slog.New(slog.DiscardHandler)
|
|
m := &Manager{
|
|
service: svc,
|
|
cfg: cfg,
|
|
namespace: config.DefaultNamespace,
|
|
containerLocks: make(map[string]*sync.Mutex),
|
|
legacyIPs: make(map[string]string),
|
|
logger: logger,
|
|
}
|
|
m.grpcPool = bridge.NewPool(m.dialTarget)
|
|
return m
|
|
}
|
|
|
|
func TestStartWithImageClearsLegacyRouteForBridgeContainer(t *testing.T) {
|
|
dataRoot := t.TempDir()
|
|
runtimeDir := filepath.Join(dataRoot, "runtime")
|
|
if err := os.MkdirAll(runtimeDir, 0o750); err != nil {
|
|
t.Fatalf("mkdir runtime dir: %v", err)
|
|
}
|
|
|
|
svc := &legacyRouteTestService{}
|
|
m := newLegacyRouteTestManager(t, svc, config.WorkspaceConfig{
|
|
DataRoot: dataRoot,
|
|
RuntimeDir: runtimeDir,
|
|
Snapshotter: "overlayfs",
|
|
CNIBinaryDir: "/opt/cni/bin",
|
|
CNIConfigDir: "/etc/cni/net.d",
|
|
})
|
|
|
|
botID := "00000000-0000-0000-0000-000000000001"
|
|
m.SetLegacyIP(botID, "10.0.0.9")
|
|
|
|
if got := m.dialTarget(botID); got != "10.0.0.9:9090" {
|
|
t.Fatalf("expected legacy dial target before start, got %q", got)
|
|
}
|
|
|
|
if err := m.StartWithImage(context.Background(), botID, ""); err != nil {
|
|
t.Fatalf("StartWithImage failed: %v", err)
|
|
}
|
|
|
|
if got := m.dialTarget(botID); got != "unix://"+filepath.Join(dataRoot, "run", botID, "bridge.sock") {
|
|
t.Fatalf("expected unix dial target after bridge start, got %q", got)
|
|
}
|
|
if svc.createCalls != 1 || svc.startCalls != 1 {
|
|
t.Fatalf("expected create/start once, got create=%d start=%d", svc.createCalls, svc.startCalls)
|
|
}
|
|
}
|
|
|
|
func TestDeleteClearsLegacyRoute(t *testing.T) {
|
|
svc := &legacyRouteTestService{created: true, container: ctr.ContainerInfo{ID: "workspace-00000000-0000-0000-0000-000000000001"}}
|
|
m := newLegacyRouteTestManager(t, svc, config.WorkspaceConfig{
|
|
DataRoot: t.TempDir(),
|
|
Snapshotter: "overlayfs",
|
|
CNIBinaryDir: "/opt/cni/bin",
|
|
CNIConfigDir: "/etc/cni/net.d",
|
|
})
|
|
|
|
botID := "00000000-0000-0000-0000-000000000001"
|
|
m.SetLegacyIP(botID, "10.0.0.9")
|
|
|
|
if err := m.Delete(context.Background(), botID, false); err != nil {
|
|
t.Fatalf("Delete failed: %v", err)
|
|
}
|
|
|
|
if got := m.dialTarget(botID); got == "10.0.0.9:9090" {
|
|
t.Fatalf("expected legacy TCP target to be cleared, got %q", got)
|
|
}
|
|
if svc.removeNet != 1 || svc.deleteTask != 1 || svc.deleteCalls != 1 {
|
|
t.Fatalf("expected delete cleanup once, got removeNet=%d deleteTask=%d delete=%d", svc.removeNet, svc.deleteTask, svc.deleteCalls)
|
|
}
|
|
}
|
|
|
|
func TestSetupNetworkAndGetIPRejectsEmptyIP(t *testing.T) {
|
|
svc := &legacyRouteTestService{
|
|
setupNetworkResults: []ctr.NetworkResult{{IP: ""}, {IP: "10.0.0.3"}},
|
|
}
|
|
m := newLegacyRouteTestManager(t, svc, config.WorkspaceConfig{
|
|
CNIBinaryDir: "/opt/cni/bin",
|
|
CNIConfigDir: "/etc/cni/net.d",
|
|
})
|
|
|
|
ip, err := m.setupNetworkAndGetIP(context.Background(), "workspace-bot")
|
|
if err != nil {
|
|
t.Fatalf("setupNetworkAndGetIP failed: %v", err)
|
|
}
|
|
if ip != "10.0.0.3" {
|
|
t.Fatalf("expected retry IP, got %q", ip)
|
|
}
|
|
if svc.setupNet != 2 {
|
|
t.Fatalf("expected two network setup attempts, got %d", svc.setupNet)
|
|
}
|
|
}
|
|
|
|
func TestContainerIDPrefersCurrentLabelSearch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
botID := "00000000-0000-0000-0000-000000000001"
|
|
svc := &legacyRouteTestService{
|
|
byLabel: []ctr.ContainerInfo{{
|
|
ID: "workspace-from-label",
|
|
Labels: map[string]string{BotLabelKey: botID},
|
|
UpdatedAt: time.Now(),
|
|
}},
|
|
}
|
|
m := newLegacyRouteTestManager(t, svc, config.WorkspaceConfig{})
|
|
|
|
containerID, err := m.ContainerID(context.Background(), botID)
|
|
if err != nil {
|
|
t.Fatalf("ContainerID failed: %v", err)
|
|
}
|
|
if containerID != "workspace-from-label" {
|
|
t.Fatalf("expected label-resolved container ID, got %q", containerID)
|
|
}
|
|
}
|
|
|
|
func TestContainerIDFallsBackToNameInference(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
botID := "00000000-0000-0000-0000-000000000001"
|
|
svc := &legacyRouteTestService{
|
|
created: true,
|
|
container: ctr.ContainerInfo{
|
|
ID: ContainerPrefix + botID,
|
|
UpdatedAt: time.Now(),
|
|
},
|
|
}
|
|
m := newLegacyRouteTestManager(t, svc, config.WorkspaceConfig{})
|
|
|
|
containerID, err := m.ContainerID(context.Background(), botID)
|
|
if err != nil {
|
|
t.Fatalf("ContainerID failed: %v", err)
|
|
}
|
|
if containerID != ContainerPrefix+botID {
|
|
t.Fatalf("expected inferred container ID, got %q", containerID)
|
|
}
|
|
}
|