Make the Firecracker and bangerd processes outlive short-lived CLI request contexts so vm create no longer kills the VMM or daemon as soon as the RPC returns. Fix fresh-VM SSH by flattening the seeded /root work disk when the copied home tree lands under a nested root/ directory, and write a guest sshd override to keep root pubkey auth explicit while debugging. Harden teardown and smoke diagnostics: verify.sh now reports early Firecracker exit and delete failures directly, while dm snapshot cleanup tolerates already-gone handles and retries busy mapper removal long enough for Firecracker to release the device. Validation: go test ./..., make build, bash -n verify.sh, direct SSH against a fresh VM, and a live ./verify.sh run that now completes with [verify] ok.
114 lines
3.5 KiB
Go
114 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 TestBuildProcessRunnerUsesSudoWrapper(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)
|
|
}
|
|
if want := "umask 000 && exec '/repo/firecracker' --api-sock '/tmp/fc.sock' --id 'vm-1'"; 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())
|
|
}
|
|
}
|