banger/internal/firecracker/client_test.go
Thales Maciel 787b234029
Fix VM startup regressions after shell-out cleanup
The shell-out reduction pass introduced two linked startup regressions in the hot path for vm create.

Make flattenNestedWorkHome repair the temporary nested /root tree without trying to read a root-owned 0700 directory as the calling user: chmod the scratch directory under sudo, then copy each child entry individually before removing it. Add a regression test for that overlap/permission case.

Restore the Firecracker launch wrapper that sets umask 000 before exec. Firecracker was creating the API socket, but the SDK could not use it during machine.Start after the direct sudo launch, so vm create timed out waiting on a socket that already existed.

Validated with go test ./... and make build.
2026-03-18 12:18:34 -03:00

115 lines
3.5 KiB
Go

package firecracker
import (
"bytes"
"log/slog"
"strings"
"testing"
)
func TestBuildConfig(t *testing.T) {
cfg := buildConfig(MachineConfig{
VMID: "vm-1",
SocketPath: "/tmp/fc.sock",
LogPath: "/tmp/fc.log",
MetricsPath: "/tmp/fc.metrics",
KernelImagePath: "/kernel",
InitrdPath: "/initrd",
KernelArgs: "console=ttyS0",
RootDrivePath: "/dev/mapper/root",
WorkDrivePath: "/var/lib/banger/root.ext4",
TapDevice: "tap-fc-1",
VCPUCount: 4,
MemoryMiB: 2048,
})
if cfg.SocketPath != "/tmp/fc.sock" {
t.Fatalf("socket path = %q", cfg.SocketPath)
}
if cfg.LogPath != "/tmp/fc.log" || cfg.MetricsPath != "/tmp/fc.metrics" {
t.Fatalf("unexpected log or metrics path: %+v", cfg)
}
if cfg.KernelImagePath != "/kernel" || cfg.InitrdPath != "/initrd" {
t.Fatalf("unexpected kernel paths: %+v", cfg)
}
if len(cfg.Drives) != 2 {
t.Fatalf("drive count = %d, want 2", len(cfg.Drives))
}
if cfg.Drives[0].DriveID == nil || *cfg.Drives[0].DriveID != "work" {
t.Fatalf("work drive id = %v", cfg.Drives[0].DriveID)
}
if cfg.Drives[1].DriveID == nil || *cfg.Drives[1].DriveID != "rootfs" {
t.Fatalf("root drive id = %v", cfg.Drives[1].DriveID)
}
if len(cfg.NetworkInterfaces) != 1 {
t.Fatalf("interface count = %d, want 1", len(cfg.NetworkInterfaces))
}
if got := cfg.NetworkInterfaces[0].StaticConfiguration.HostDevName; got != "tap-fc-1" {
t.Fatalf("host dev name = %q", got)
}
if cfg.MachineCfg.VcpuCount == nil || *cfg.MachineCfg.VcpuCount != 4 {
t.Fatalf("vcpu = %v", cfg.MachineCfg.VcpuCount)
}
if cfg.MachineCfg.MemSizeMib == nil || *cfg.MachineCfg.MemSizeMib != 2048 {
t.Fatalf("memory = %v", cfg.MachineCfg.MemSizeMib)
}
if cfg.MachineCfg.Smt == nil || *cfg.MachineCfg.Smt {
t.Fatalf("smt = %v, want false", cfg.MachineCfg.Smt)
}
}
func TestBuildProcessRunnerUsesSudoShellWrapper(t *testing.T) {
cmd := buildProcessRunner(MachineConfig{
BinaryPath: "/repo/firecracker",
SocketPath: "/tmp/fc.sock",
VMID: "vm-1",
}, nil)
if cmd.Path != "/usr/bin/sudo" && cmd.Path != "sudo" {
t.Fatalf("command path = %q", cmd.Path)
}
if len(cmd.Args) != 5 {
t.Fatalf("args = %v", cmd.Args)
}
if cmd.Args[1] != "-n" || cmd.Args[2] != "sh" || cmd.Args[3] != "-c" {
t.Fatalf("args = %v", cmd.Args)
}
want := "umask 000 && exec '/repo/firecracker' --api-sock '/tmp/fc.sock' --id 'vm-1'"
if cmd.Args[4] != want {
t.Fatalf("script = %q, want %q", cmd.Args[4], want)
}
if cmd.Cancel != nil {
t.Fatal("process runner should not be tied to a request context")
}
}
func TestSDKLoggerBridgeEmitsStructuredDebugLogs(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
entry := newLogger(logger)
entry.WithField("vm_id", "vm-1").Info("sdk ready")
output := buf.String()
if !strings.Contains(output, `"component":"firecracker_sdk"`) {
t.Fatalf("output = %q, want firecracker_sdk component", output)
}
if !strings.Contains(output, `"vm_id":"vm-1"`) {
t.Fatalf("output = %q, want vm_id field", output)
}
if !strings.Contains(output, `"msg":"sdk ready"`) {
t.Fatalf("output = %q, want sdk message", output)
}
}
func TestSDKLoggerBridgeSuppressesDebugAtInfoLevel(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}))
entry := newLogger(logger)
entry.Info("sdk hidden at info")
if buf.Len() != 0 {
t.Fatalf("expected info-level logger to suppress sdk debug chatter, got %q", buf.String())
}
}