feat: nvme disk on m6

This commit is contained in:
Harivansh Rathi 2026-04-10 04:04:08 +00:00
parent 54a4c423a6
commit eb9d2a76df
9 changed files with 240 additions and 22 deletions

View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/getcompanion-ai/computer-host/internal/firecracker"
@ -21,6 +22,8 @@ type Config struct {
SnapshotsDir string
RuntimeDir string
DiskCloneMode DiskCloneMode
DriveIOEngine firecracker.DriveIOEngine
EnablePCI bool
SocketPath string
HTTPAddr string
EgressInterface string
@ -42,6 +45,10 @@ const (
// Load loads and validates the firecracker-host daemon configuration from the environment.
func Load() (Config, error) {
rootDir := filepath.Clean(strings.TrimSpace(os.Getenv("FIRECRACKER_HOST_ROOT_DIR")))
enablePCI, err := loadBool("FIRECRACKER_HOST_ENABLE_PCI")
if err != nil {
return Config{}, err
}
cfg := Config{
RootDir: rootDir,
StatePath: filepath.Join(rootDir, "state", "state.json"),
@ -51,6 +58,8 @@ func Load() (Config, error) {
SnapshotsDir: filepath.Join(rootDir, "snapshots"),
RuntimeDir: filepath.Join(rootDir, "runtime"),
DiskCloneMode: loadDiskCloneMode(os.Getenv("FIRECRACKER_HOST_DISK_CLONE_MODE")),
DriveIOEngine: loadDriveIOEngine(os.Getenv("FIRECRACKER_HOST_DRIVE_IO_ENGINE")),
EnablePCI: enablePCI,
SocketPath: filepath.Join(rootDir, defaultSocketName),
HTTPAddr: strings.TrimSpace(os.Getenv("FIRECRACKER_HOST_HTTP_ADDR")),
EgressInterface: strings.TrimSpace(os.Getenv("FIRECRACKER_HOST_EGRESS_INTERFACE")),
@ -96,6 +105,9 @@ func (c Config) Validate() error {
if err := c.DiskCloneMode.Validate(); err != nil {
return err
}
if err := validateDriveIOEngine(c.DriveIOEngine); err != nil {
return err
}
if strings.TrimSpace(c.SocketPath) == "" {
return fmt.Errorf("socket path is required")
}
@ -112,6 +124,7 @@ func (c Config) FirecrackerRuntimeConfig() firecracker.RuntimeConfig {
EgressInterface: c.EgressInterface,
FirecrackerBinaryPath: c.FirecrackerBinaryPath,
JailerBinaryPath: c.JailerBinaryPath,
EnablePCI: c.EnablePCI,
}
}
@ -132,3 +145,32 @@ func (m DiskCloneMode) Validate() error {
return fmt.Errorf("FIRECRACKER_HOST_DISK_CLONE_MODE must be %q or %q", DiskCloneModeReflink, DiskCloneModeCopy)
}
}
func loadDriveIOEngine(raw string) firecracker.DriveIOEngine {
value := strings.TrimSpace(raw)
if value == "" {
return firecracker.DriveIOEngineSync
}
return firecracker.DriveIOEngine(value)
}
func validateDriveIOEngine(engine firecracker.DriveIOEngine) error {
switch engine {
case firecracker.DriveIOEngineSync, firecracker.DriveIOEngineAsync:
return nil
default:
return fmt.Errorf("FIRECRACKER_HOST_DRIVE_IO_ENGINE must be %q or %q", firecracker.DriveIOEngineSync, firecracker.DriveIOEngineAsync)
}
}
func loadBool(name string) (bool, error) {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return false, nil
}
parsed, err := strconv.ParseBool(value)
if err != nil {
return false, fmt.Errorf("%s must be a boolean: %w", name, err)
}
return parsed, nil
}

View file

@ -1,6 +1,10 @@
package config
import "testing"
import (
"testing"
"github.com/getcompanion-ai/computer-host/internal/firecracker"
)
func TestLoadDiskCloneModeDefaultsToReflink(t *testing.T) {
t.Parallel()
@ -38,3 +42,73 @@ func TestDiskCloneModeValidate(t *testing.T) {
})
}
}
func TestLoadDriveIOEngineDefaultsToSync(t *testing.T) {
t.Parallel()
if got := loadDriveIOEngine(""); got != firecracker.DriveIOEngineSync {
t.Fatalf("drive io engine = %q, want %q", got, firecracker.DriveIOEngineSync)
}
}
func TestValidateDriveIOEngine(t *testing.T) {
t.Parallel()
tests := []struct {
name string
engine firecracker.DriveIOEngine
wantErr bool
}{
{name: "sync", engine: firecracker.DriveIOEngineSync},
{name: "async", engine: firecracker.DriveIOEngineAsync},
{name: "empty", engine: "", wantErr: true},
{name: "unknown", engine: "Aio", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := validateDriveIOEngine(tt.engine)
if tt.wantErr && err == nil {
t.Fatal("validateDriveIOEngine() error = nil, want error")
}
if !tt.wantErr && err != nil {
t.Fatalf("validateDriveIOEngine() error = %v, want nil", err)
}
})
}
}
func TestLoadBool(t *testing.T) {
tests := []struct {
name string
value string
want bool
wantErr bool
}{
{name: "default"},
{name: "true", value: "true", want: true},
{name: "false", value: "false"},
{name: "one", value: "1", want: true},
{name: "invalid", value: "enabled", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
envName := "TEST_FIRECRACKER_BOOL"
t.Setenv(envName, tt.value)
got, err := loadBool(envName)
if tt.wantErr && err == nil {
t.Fatal("loadBool() error = nil, want error")
}
if !tt.wantErr && err != nil {
t.Fatalf("loadBool() error = %v, want nil", err)
}
if got != tt.want {
t.Fatalf("loadBool() = %v, want %v", got, tt.want)
}
})
}
}