mirror of
https://github.com/getcompanion-ai/computer-host.git
synced 2026-04-15 12:03:48 +00:00
feat: nvme disk on m6
This commit is contained in:
parent
54a4c423a6
commit
eb9d2a76df
9 changed files with 240 additions and 22 deletions
|
|
@ -159,9 +159,11 @@ func (d *Daemon) buildMachineSpec(machineID contracthost.MachineID, artifact *mo
|
|||
drives := make([]firecracker.DriveSpec, 0, len(userVolumes))
|
||||
for i, volume := range userVolumes {
|
||||
drives = append(drives, firecracker.DriveSpec{
|
||||
ID: fmt.Sprintf("user-%d", i),
|
||||
Path: volume.Path,
|
||||
ReadOnly: false,
|
||||
ID: fmt.Sprintf("user-%d", i),
|
||||
Path: volume.Path,
|
||||
ReadOnly: false,
|
||||
CacheType: firecracker.DriveCacheTypeUnsafe,
|
||||
IOEngine: d.config.DriveIOEngine,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -179,9 +181,9 @@ func (d *Daemon) buildMachineSpec(machineID contracthost.MachineID, artifact *mo
|
|||
ID: "root_drive",
|
||||
Path: systemVolumePath,
|
||||
CacheType: firecracker.DriveCacheTypeUnsafe,
|
||||
IOEngine: firecracker.DriveIOEngineSync,
|
||||
IOEngine: d.config.DriveIOEngine,
|
||||
},
|
||||
KernelArgs: defaultGuestKernelArgs,
|
||||
KernelArgs: guestKernelArgs(d.config.EnablePCI),
|
||||
Drives: drives,
|
||||
MMDS: mmds,
|
||||
Vsock: guestVsockSpec(machineID),
|
||||
|
|
|
|||
|
|
@ -8,21 +8,22 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
contracthost "github.com/getcompanion-ai/computer-host/contract"
|
||||
appconfig "github.com/getcompanion-ai/computer-host/internal/config"
|
||||
"github.com/getcompanion-ai/computer-host/internal/firecracker"
|
||||
"github.com/getcompanion-ai/computer-host/internal/model"
|
||||
"github.com/getcompanion-ai/computer-host/internal/store"
|
||||
contracthost "github.com/getcompanion-ai/computer-host/contract"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultGuestKernelArgs = "console=ttyS0 reboot=k panic=1 pci=off"
|
||||
defaultGuestMemoryMiB = int64(3072)
|
||||
defaultGuestVCPUs = int64(2)
|
||||
defaultSSHPort = uint16(2222)
|
||||
defaultVNCPort = uint16(6080)
|
||||
defaultCopyBufferSize = 1024 * 1024
|
||||
defaultGuestDialTimeout = 500 * time.Millisecond
|
||||
defaultGuestKernelArgs = "console=ttyS0 reboot=k panic=1"
|
||||
defaultGuestKernelArgsNoPCI = defaultGuestKernelArgs + " pci=off"
|
||||
defaultGuestMemoryMiB = int64(3072)
|
||||
defaultGuestVCPUs = int64(2)
|
||||
defaultSSHPort = uint16(2222)
|
||||
defaultVNCPort = uint16(6080)
|
||||
defaultCopyBufferSize = 1024 * 1024
|
||||
defaultGuestDialTimeout = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
type Runtime interface {
|
||||
|
|
@ -73,6 +74,9 @@ func New(cfg appconfig.Config, store store.Store, runtime Runtime) (*Daemon, err
|
|||
return nil, fmt.Errorf("create daemon dir %q: %w", dir, err)
|
||||
}
|
||||
}
|
||||
if err := validateDiskCloneBackend(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
daemon := &Daemon{
|
||||
config: cfg,
|
||||
store: store,
|
||||
|
|
@ -127,3 +131,10 @@ func (d *Daemon) lockArtifact(key string) func() {
|
|||
lock.Lock()
|
||||
return lock.Unlock
|
||||
}
|
||||
|
||||
func guestKernelArgs(enablePCI bool) string {
|
||||
if enablePCI {
|
||||
return defaultGuestKernelArgs
|
||||
}
|
||||
return defaultGuestKernelArgsNoPCI
|
||||
}
|
||||
|
|
|
|||
|
|
@ -758,6 +758,7 @@ func testConfig(root string) appconfig.Config {
|
|||
SnapshotsDir: filepath.Join(root, "snapshots"),
|
||||
RuntimeDir: filepath.Join(root, "runtime"),
|
||||
DiskCloneMode: appconfig.DiskCloneModeCopy,
|
||||
DriveIOEngine: firecracker.DriveIOEngineSync,
|
||||
SocketPath: filepath.Join(root, "firecracker-host.sock"),
|
||||
EgressInterface: "eth0",
|
||||
FirecrackerBinaryPath: "/usr/bin/firecracker",
|
||||
|
|
@ -765,6 +766,22 @@ func testConfig(root string) appconfig.Config {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGuestKernelArgsDisablesPCIByDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := guestKernelArgs(false); !strings.Contains(got, "pci=off") {
|
||||
t.Fatalf("guestKernelArgs(false) = %q, want pci=off", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuestKernelArgsRemovesPCIOffWhenPCIEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := guestKernelArgs(true); strings.Contains(got, "pci=off") {
|
||||
t.Fatalf("guestKernelArgs(true) = %q, want no pci=off", got)
|
||||
}
|
||||
}
|
||||
|
||||
func stubGuestSSHPublicKeyReader(hostDaemon *Daemon) {
|
||||
hostDaemon.readGuestSSHPublicKey = func(context.Context, string) (string, error) {
|
||||
return "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO0j1AyW0mQm9a1G2rY0R4fP2G5+4Qx2V3FJ9P2mA6N3", nil
|
||||
|
|
|
|||
|
|
@ -97,7 +97,10 @@ func cloneFile(source string, target string) error {
|
|||
func cloneDiskFile(source string, target string, mode appconfig.DiskCloneMode) error {
|
||||
switch mode {
|
||||
case appconfig.DiskCloneModeReflink:
|
||||
return reflinkFile(source, target)
|
||||
if err := reflinkFile(source, target); err != nil {
|
||||
return reflinkRequiredError(err)
|
||||
}
|
||||
return nil
|
||||
case appconfig.DiskCloneModeCopy:
|
||||
return cloneFile(source, target)
|
||||
default:
|
||||
|
|
@ -105,6 +108,42 @@ func cloneDiskFile(source string, target string, mode appconfig.DiskCloneMode) e
|
|||
}
|
||||
}
|
||||
|
||||
func validateDiskCloneBackend(cfg appconfig.Config) error {
|
||||
if cfg.DiskCloneMode != appconfig.DiskCloneModeReflink {
|
||||
return nil
|
||||
}
|
||||
|
||||
sourceFile, err := os.CreateTemp(cfg.ArtifactsDir, ".reflink-probe-source-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create reflink probe source in %q: %w", cfg.ArtifactsDir, err)
|
||||
}
|
||||
sourcePath := sourceFile.Name()
|
||||
defer func() {
|
||||
_ = os.Remove(sourcePath)
|
||||
}()
|
||||
|
||||
if _, err := sourceFile.WriteString("reflink-probe"); err != nil {
|
||||
_ = sourceFile.Close()
|
||||
return fmt.Errorf("write reflink probe source %q: %w", sourcePath, err)
|
||||
}
|
||||
if err := sourceFile.Close(); err != nil {
|
||||
return fmt.Errorf("close reflink probe source %q: %w", sourcePath, err)
|
||||
}
|
||||
|
||||
targetPath := filepath.Join(cfg.MachineDisksDir, "."+filepath.Base(sourcePath)+".target")
|
||||
defer func() {
|
||||
_ = os.Remove(targetPath)
|
||||
}()
|
||||
if err := cloneDiskFile(sourcePath, targetPath, cfg.DiskCloneMode); err != nil {
|
||||
return fmt.Errorf("validate disk clone backend from artifacts dir %q to machine disks dir %q: %w", cfg.ArtifactsDir, cfg.MachineDisksDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reflinkRequiredError(err error) error {
|
||||
return fmt.Errorf("FIRECRACKER_HOST_DISK_CLONE_MODE=reflink requires a CoW filesystem with reflink support across artifacts and machine-disks; mount FIRECRACKER_HOST_ROOT_DIR on XFS with reflink=1 or Btrfs, preferably on local NVMe, or set FIRECRACKER_HOST_DISK_CLONE_MODE=copy for the slow full-copy fallback: %w", err)
|
||||
}
|
||||
|
||||
func reflinkFile(source string, target string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return fmt.Errorf("create target dir for %q: %w", target, err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue