From 634356acd655855992540813d2b3ba50973e8391 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Tue, 7 Apr 2026 20:48:17 +0000 Subject: [PATCH] chore: move vsock into runtime for entropy per VM --- internal/config/config.go | 216 ++++---------------------------- internal/firecracker/runtime.go | 157 ++++++++++++++++------- internal/firecracker/sdk.go | 23 ++-- internal/service/service.go | 72 +++-------- 4 files changed, 163 insertions(+), 305 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index dd772ea..380643c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,132 +3,29 @@ package config import ( "fmt" "os" - "strconv" "strings" "github.com/getcompanion-ai/computer-host/internal/firecracker" ) -const ( - defaultGuestKernelArgs = "console=ttyS0 reboot=k panic=1 pci=off" - defaultGuestMemoryMiB = 512 - defaultGuestVCPUs = 1 - defaultVSockCID = 3 - defaultVSockID = "vsock0" -) - // Config contains the minimum host-local settings required to boot a machine. type Config struct { - Runtime RuntimeConfig - Machine MachineConfig - VSock VSockConfig -} - -// RuntimeConfig contains host-local runtime settings. -type RuntimeConfig struct { RootDir string FirecrackerBinaryPath string JailerBinaryPath string - JailerUID int - JailerGID int - NumaNode int - NetworkCIDR string + KernelImagePath string + RootFSPath string } -// MachineConfig contains the default guest boot settings. -type MachineConfig struct { - KernelImagePath string - RootFSPath string - KernelArgs string - VCPUs int64 - MemoryMiB int64 -} - -// VSockConfig contains optional default vsock settings. -type VSockConfig struct { - Enabled bool - BaseDir string - ID string - CID uint32 -} - -// Load loads and validates the firecracker-host configuration from the -// environment. +// Load loads and validates the firecracker-host configuration from the environment. func Load() (Config, error) { cfg := Config{ - Runtime: RuntimeConfig{ - RootDir: strings.TrimSpace(os.Getenv("FIRECRACKER_HOST_ROOT_DIR")), - FirecrackerBinaryPath: strings.TrimSpace(os.Getenv("FIRECRACKER_BINARY_PATH")), - JailerBinaryPath: strings.TrimSpace(os.Getenv("JAILER_BINARY_PATH")), - JailerUID: os.Getuid(), - JailerGID: os.Getgid(), - NetworkCIDR: strings.TrimSpace(os.Getenv("FIRECRACKER_NETWORK_CIDR")), - }, - Machine: MachineConfig{ - KernelImagePath: strings.TrimSpace(os.Getenv("FIRECRACKER_GUEST_KERNEL_PATH")), - RootFSPath: strings.TrimSpace(os.Getenv("FIRECRACKER_GUEST_ROOTFS_PATH")), - KernelArgs: defaultGuestKernelArgs, - VCPUs: defaultGuestVCPUs, - MemoryMiB: defaultGuestMemoryMiB, - }, - VSock: VSockConfig{ - ID: defaultVSockID, - CID: defaultVSockCID, - }, + RootDir: strings.TrimSpace(os.Getenv("FIRECRACKER_HOST_ROOT_DIR")), + FirecrackerBinaryPath: strings.TrimSpace(os.Getenv("FIRECRACKER_BINARY_PATH")), + JailerBinaryPath: strings.TrimSpace(os.Getenv("JAILER_BINARY_PATH")), + KernelImagePath: strings.TrimSpace(os.Getenv("FIRECRACKER_GUEST_KERNEL_PATH")), + RootFSPath: strings.TrimSpace(os.Getenv("FIRECRACKER_GUEST_ROOTFS_PATH")), } - - if value := strings.TrimSpace(os.Getenv("FIRECRACKER_GUEST_KERNEL_ARGS")); value != "" { - cfg.Machine.KernelArgs = value - } - if value := strings.TrimSpace(os.Getenv("FIRECRACKER_BINARY_PATH")); value == "" { - cfg.Runtime.FirecrackerBinaryPath = "firecracker" - } - if value := strings.TrimSpace(os.Getenv("JAILER_BINARY_PATH")); value == "" { - cfg.Runtime.JailerBinaryPath = "jailer" - } - - if value, ok, err := lookupIntEnv("FIRECRACKER_JAILER_UID"); err != nil { - return Config{}, err - } else if ok { - cfg.Runtime.JailerUID = value - } - if value, ok, err := lookupIntEnv("FIRECRACKER_JAILER_GID"); err != nil { - return Config{}, err - } else if ok { - cfg.Runtime.JailerGID = value - } - if value, ok, err := lookupIntEnv("FIRECRACKER_NUMA_NODE"); err != nil { - return Config{}, err - } else if ok { - cfg.Runtime.NumaNode = value - } - if value, ok, err := lookupInt64Env("FIRECRACKER_GUEST_VCPUS"); err != nil { - return Config{}, err - } else if ok { - cfg.Machine.VCPUs = value - } - if value, ok, err := lookupInt64Env("FIRECRACKER_GUEST_MEMORY_MIB"); err != nil { - return Config{}, err - } else if ok { - cfg.Machine.MemoryMiB = value - } - if value, ok, err := lookupBoolEnv("FIRECRACKER_VSOCK_ENABLED"); err != nil { - return Config{}, err - } else if ok { - cfg.VSock.Enabled = value - } - if value := strings.TrimSpace(os.Getenv("FIRECRACKER_VSOCK_BASE_DIR")); value != "" { - cfg.VSock.BaseDir = value - } - if value := strings.TrimSpace(os.Getenv("FIRECRACKER_VSOCK_ID")); value != "" { - cfg.VSock.ID = value - } - if value, ok, err := lookupUint32Env("FIRECRACKER_VSOCK_CID"); err != nil { - return Config{}, err - } else if ok { - cfg.VSock.CID = value - } - if err := cfg.Validate(); err != nil { return Config{}, err } @@ -137,100 +34,29 @@ func Load() (Config, error) { // Validate reports whether the host configuration is usable. func (c Config) Validate() error { - if strings.TrimSpace(c.Runtime.RootDir) == "" { + if c.RootDir == "" { return fmt.Errorf("FIRECRACKER_HOST_ROOT_DIR is required") } - if strings.TrimSpace(c.Machine.KernelImagePath) == "" { + if c.FirecrackerBinaryPath == "" { + return fmt.Errorf("FIRECRACKER_BINARY_PATH is required") + } + if c.JailerBinaryPath == "" { + return fmt.Errorf("JAILER_BINARY_PATH is required") + } + if c.KernelImagePath == "" { return fmt.Errorf("FIRECRACKER_GUEST_KERNEL_PATH is required") } - if strings.TrimSpace(c.Machine.RootFSPath) == "" { + if c.RootFSPath == "" { return fmt.Errorf("FIRECRACKER_GUEST_ROOTFS_PATH is required") } - if c.Machine.VCPUs < 1 { - return fmt.Errorf("FIRECRACKER_GUEST_VCPUS must be at least 1") - } - if c.Machine.MemoryMiB < 1 { - return fmt.Errorf("FIRECRACKER_GUEST_MEMORY_MIB must be at least 1") - } - if c.Runtime.NumaNode < 0 { - return fmt.Errorf("FIRECRACKER_NUMA_NODE must be non-negative") - } - if c.VSock.Enabled { - if strings.TrimSpace(c.VSock.BaseDir) == "" { - return fmt.Errorf("FIRECRACKER_VSOCK_BASE_DIR is required when FIRECRACKER_VSOCK_ENABLED is true") - } - if c.VSock.CID == 0 { - return fmt.Errorf("FIRECRACKER_VSOCK_CID must be non-zero when FIRECRACKER_VSOCK_ENABLED is true") - } - if strings.TrimSpace(c.VSock.ID) == "" { - return fmt.Errorf("FIRECRACKER_VSOCK_ID is required when FIRECRACKER_VSOCK_ENABLED is true") - } - } return nil } -// FirecrackerRuntimeConfig converts the host config into the runtime wrapper's -// concrete runtime config. +// FirecrackerRuntimeConfig converts the host config into the runtime wrapper's concrete runtime config. func (c Config) FirecrackerRuntimeConfig() firecracker.RuntimeConfig { return firecracker.RuntimeConfig{ - RootDir: c.Runtime.RootDir, - FirecrackerBinaryPath: c.Runtime.FirecrackerBinaryPath, - JailerBinaryPath: c.Runtime.JailerBinaryPath, - JailerUID: c.Runtime.JailerUID, - JailerGID: c.Runtime.JailerGID, - NumaNode: c.Runtime.NumaNode, - NetworkCIDR: c.Runtime.NetworkCIDR, + RootDir: c.RootDir, + FirecrackerBinaryPath: c.FirecrackerBinaryPath, + JailerBinaryPath: c.JailerBinaryPath, } } - -func lookupBoolEnv(key string) (bool, bool, error) { - raw, ok := os.LookupEnv(key) - if !ok || strings.TrimSpace(raw) == "" { - return false, false, nil - } - - value, err := strconv.ParseBool(strings.TrimSpace(raw)) - if err != nil { - return false, false, fmt.Errorf("parse %s: %w", key, err) - } - return value, true, nil -} - -func lookupIntEnv(key string) (int, bool, error) { - raw, ok := os.LookupEnv(key) - if !ok || strings.TrimSpace(raw) == "" { - return 0, false, nil - } - - value, err := strconv.Atoi(strings.TrimSpace(raw)) - if err != nil { - return 0, false, fmt.Errorf("parse %s: %w", key, err) - } - return value, true, nil -} - -func lookupInt64Env(key string) (int64, bool, error) { - raw, ok := os.LookupEnv(key) - if !ok || strings.TrimSpace(raw) == "" { - return 0, false, nil - } - - value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64) - if err != nil { - return 0, false, fmt.Errorf("parse %s: %w", key, err) - } - return value, true, nil -} - -func lookupUint32Env(key string) (uint32, bool, error) { - raw, ok := os.LookupEnv(key) - if !ok || strings.TrimSpace(raw) == "" { - return 0, false, nil - } - - value, err := strconv.ParseUint(strings.TrimSpace(raw), 10, 32) - if err != nil { - return 0, false, fmt.Errorf("parse %s: %w", key, err) - } - return uint32(value), true, nil -} diff --git a/internal/firecracker/runtime.go b/internal/firecracker/runtime.go index a1067c8..b7e1f22 100644 --- a/internal/firecracker/runtime.go +++ b/internal/firecracker/runtime.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "sync" "time" @@ -20,22 +21,18 @@ type RuntimeConfig struct { RootDir string FirecrackerBinaryPath string JailerBinaryPath string - JailerUID int - JailerGID int - NumaNode int - NetworkCIDR string - NetworkAllocator *NetworkAllocator - NetworkProvisioner NetworkProvisioner } // Runtime manages local Firecracker machines on a single host. type Runtime struct { - config RuntimeConfig + rootDir string + firecrackerBinaryPath string + jailerBinaryPath string + networkAllocator *NetworkAllocator + networkProvisioner NetworkProvisioner mu sync.RWMutex machines map[MachineID]*managedMachine - - newMachine func(context.Context, sdk.Config) (*sdk.Machine, error) } type managedMachine struct { @@ -46,48 +43,48 @@ type managedMachine struct { state MachineState } +const ( + defaultVSockCIDStart = uint32(3) + defaultVSockDirName = "vsock" + defaultVSockID = "vsock0" +) + // NewRuntime creates a new host-local Firecracker runtime wrapper. func NewRuntime(cfg RuntimeConfig) (*Runtime, error) { - cfg.RootDir = filepath.Clean(cfg.RootDir) - if cfg.RootDir == "." || cfg.RootDir == "" { + rootDir := filepath.Clean(strings.TrimSpace(cfg.RootDir)) + if rootDir == "." || rootDir == "" { return nil, fmt.Errorf("runtime root dir is required") } - if err := os.MkdirAll(cfg.RootDir, 0o755); err != nil { - return nil, fmt.Errorf("create runtime root dir %q: %w", cfg.RootDir, err) + + firecrackerBinaryPath := strings.TrimSpace(cfg.FirecrackerBinaryPath) + if firecrackerBinaryPath == "" { + return nil, fmt.Errorf("firecracker binary path is required") } - if cfg.FirecrackerBinaryPath == "" { - cfg.FirecrackerBinaryPath = "firecracker" + jailerBinaryPath := strings.TrimSpace(cfg.JailerBinaryPath) + if jailerBinaryPath == "" { + return nil, fmt.Errorf("jailer binary path is required") } - if cfg.JailerBinaryPath == "" { - cfg.JailerBinaryPath = "jailer" + + if err := os.MkdirAll(rootDir, 0o755); err != nil { + return nil, fmt.Errorf("create runtime root dir %q: %w", rootDir, err) } - if cfg.JailerUID == 0 { - cfg.JailerUID = os.Getuid() + if err := os.MkdirAll(filepath.Join(rootDir, defaultVSockDirName), 0o755); err != nil { + return nil, fmt.Errorf("create runtime vsock dir: %w", err) } - if cfg.JailerGID == 0 { - cfg.JailerGID = os.Getgid() - } - if cfg.NumaNode < 0 { - cfg.NumaNode = 0 - } - if cfg.NetworkAllocator == nil { - allocator, err := NewNetworkAllocator(cfg.NetworkCIDR) - if err != nil { - return nil, err - } - cfg.NetworkAllocator = allocator - } - if cfg.NetworkProvisioner == nil { - cfg.NetworkProvisioner = NewIPTapProvisioner() + + allocator, err := NewNetworkAllocator(defaultNetworkCIDR) + if err != nil { + return nil, err } return &Runtime{ - config: cfg, - machines: make(map[MachineID]*managedMachine), - newMachine: func(ctx context.Context, cfg sdk.Config) (*sdk.Machine, error) { - return sdk.NewMachine(ctx, cfg) - }, + rootDir: rootDir, + firecrackerBinaryPath: firecrackerBinaryPath, + jailerBinaryPath: jailerBinaryPath, + networkAllocator: allocator, + networkProvisioner: NewIPTapProvisioner(), + machines: make(map[MachineID]*managedMachine), }, nil } @@ -102,12 +99,25 @@ func (r *Runtime) Boot(ctx context.Context, spec MachineSpec) (*MachineState, er r.mu.Unlock() return nil, fmt.Errorf("machine %q already exists", spec.ID) } + usedNetworks := make([]NetworkAllocation, 0, len(r.machines)) + usedVSockCIDs := make(map[uint32]struct{}, len(r.machines)) for _, machine := range r.machines { - if machine != nil { - usedNetworks = append(usedNetworks, machine.network) + if machine == nil { + continue + } + usedNetworks = append(usedNetworks, machine.network) + if machine.spec.Vsock != nil { + usedVSockCIDs[machine.spec.Vsock.CID] = struct{}{} } } + + spec, err := r.resolveVSock(spec, usedVSockCIDs) + if err != nil { + r.mu.Unlock() + return nil, err + } + r.machines[spec.ID] = &managedMachine{ spec: spec, state: MachineState{ @@ -118,7 +128,8 @@ func (r *Runtime) Boot(ctx context.Context, spec MachineSpec) (*MachineState, er r.mu.Unlock() cleanup := func(network NetworkAllocation, paths machinePaths) { - _ = r.config.NetworkProvisioner.Remove(context.Background(), network) + _ = r.networkProvisioner.Remove(context.Background(), network) + _ = removeIfExists(vsockPath(spec)) if paths.BaseDir != "" { _ = os.RemoveAll(paths.BaseDir) } @@ -127,13 +138,13 @@ func (r *Runtime) Boot(ctx context.Context, spec MachineSpec) (*MachineState, er r.mu.Unlock() } - network, err := r.config.NetworkAllocator.Allocate(usedNetworks) + network, err := r.networkAllocator.Allocate(usedNetworks) if err != nil { cleanup(NetworkAllocation{}, machinePaths{}) return nil, err } - paths, err := buildMachinePaths(r.config.RootDir, spec.ID, r.config.FirecrackerBinaryPath) + paths, err := buildMachinePaths(r.rootDir, spec.ID, r.firecrackerBinaryPath) if err != nil { cleanup(network, machinePaths{}) return nil, err @@ -142,18 +153,18 @@ func (r *Runtime) Boot(ctx context.Context, spec MachineSpec) (*MachineState, er cleanup(network, paths) return nil, fmt.Errorf("create machine jailer dir %q: %w", paths.JailerBaseDir, err) } - if err := r.config.NetworkProvisioner.Ensure(ctx, network); err != nil { + if err := r.networkProvisioner.Ensure(ctx, network); err != nil { cleanup(network, paths) return nil, err } - cfg, err := buildSDKConfig(spec, paths, network, r.config) + cfg, err := buildSDKConfig(spec, paths, network, r.firecrackerBinaryPath, r.jailerBinaryPath) if err != nil { cleanup(network, paths) return nil, err } - machine, err := r.newMachine(ctx, cfg) + machine, err := sdk.NewMachine(ctx, cfg) if err != nil { cleanup(network, paths) return nil, fmt.Errorf("create firecracker machine: %w", err) @@ -263,7 +274,10 @@ func (r *Runtime) Delete(ctx context.Context, id MachineID) error { return err } } - if err := r.config.NetworkProvisioner.Remove(ctx, entry.network); err != nil { + if err := r.networkProvisioner.Remove(ctx, entry.network); err != nil { + return err + } + if err := removeIfExists(vsockPath(entry.spec)); err != nil { return err } if err := os.RemoveAll(entry.paths.BaseDir); err != nil { @@ -299,3 +313,50 @@ func (r *Runtime) watchMachine(id MachineID, machine *sdk.Machine) { } entry.state.Error = "" } + +func (r *Runtime) resolveVSock(spec MachineSpec, used map[uint32]struct{}) (MachineSpec, error) { + if spec.Vsock != nil { + if _, exists := used[spec.Vsock.CID]; exists { + return MachineSpec{}, fmt.Errorf("vsock cid %d already in use", spec.Vsock.CID) + } + return spec, nil + } + + cid, err := nextVSockCID(used) + if err != nil { + return MachineSpec{}, err + } + + spec.Vsock = &VsockSpec{ + ID: defaultVSockID, + CID: cid, + Path: filepath.Join(r.rootDir, defaultVSockDirName, string(spec.ID)+".sock"), + } + return spec, nil +} + +func nextVSockCID(used map[uint32]struct{}) (uint32, error) { + for cid := defaultVSockCIDStart; cid != 0; cid++ { + if _, exists := used[cid]; !exists { + return cid, nil + } + } + return 0, fmt.Errorf("vsock cid space exhausted") +} + +func vsockPath(spec MachineSpec) string { + if spec.Vsock == nil { + return "" + } + return strings.TrimSpace(spec.Vsock.Path) +} + +func removeIfExists(path string) error { + if path == "" { + return nil + } + if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("remove path %q: %w", path, err) + } + return nil +} diff --git a/internal/firecracker/sdk.go b/internal/firecracker/sdk.go index 4ccc995..86bcb20 100644 --- a/internal/firecracker/sdk.go +++ b/internal/firecracker/sdk.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "net/netip" + "os" "path/filepath" "strings" @@ -11,15 +12,23 @@ import ( models "github.com/firecracker-microvm/firecracker-go-sdk/client/models" ) -func buildSDKConfig(spec MachineSpec, paths machinePaths, network NetworkAllocation, runtime RuntimeConfig) (sdk.Config, error) { +const defaultNumaNode = 0 + +func buildSDKConfig(spec MachineSpec, paths machinePaths, network NetworkAllocation, firecrackerBinaryPath string, jailerBinaryPath string) (sdk.Config, error) { if err := spec.Validate(); err != nil { return sdk.Config{}, err } - if runtime.FirecrackerBinaryPath == "" { + firecrackerBinaryPath = strings.TrimSpace(firecrackerBinaryPath) + if firecrackerBinaryPath == "" { return sdk.Config{}, fmt.Errorf("firecracker binary path is required") } + jailerBinaryPath = strings.TrimSpace(jailerBinaryPath) + if jailerBinaryPath == "" { + return sdk.Config{}, fmt.Errorf("jailer binary path is required") + } + drives := sdk.NewDrivesBuilder(spec.RootFSPath) for _, drive := range spec.Drives { drives = drives.AddDrive( @@ -51,12 +60,12 @@ func buildSDKConfig(spec MachineSpec, paths machinePaths, network NetworkAllocat Smt: sdk.Bool(false), }, JailerCfg: &sdk.JailerConfig{ - GID: sdk.Int(runtime.JailerGID), - UID: sdk.Int(runtime.JailerUID), + GID: sdk.Int(os.Getgid()), + UID: sdk.Int(os.Getuid()), ID: string(spec.ID), - NumaNode: sdk.Int(runtime.NumaNode), - ExecFile: runtime.FirecrackerBinaryPath, - JailerBinary: runtime.JailerBinaryPath, + NumaNode: sdk.Int(defaultNumaNode), + ExecFile: firecrackerBinaryPath, + JailerBinary: jailerBinaryPath, ChrootBaseDir: paths.JailerBaseDir, ChrootStrategy: sdk.NewNaiveChrootStrategy(filepath.Clean(spec.KernelImagePath)), }, diff --git a/internal/service/service.go b/internal/service/service.go index 5080b56..f85737f 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -3,8 +3,6 @@ package service import ( "context" "fmt" - "os" - "path/filepath" "strings" appconfig "github.com/getcompanion-ai/computer-host/internal/config" @@ -25,29 +23,22 @@ type Service struct { runtime MachineRuntime } -// CreateMachineRequest contains the minimum machine creation inputs above the -// raw runtime layer. +// CreateMachineRequest contains the minimum machine creation inputs above the raw runtime layer. type CreateMachineRequest struct { - ID firecracker.MachineID - KernelImagePath string - RootFSPath string - KernelArgs string - VCPUs int64 - MemoryMiB int64 - Drives []firecracker.DriveSpec - VSock *firecracker.VsockSpec + ID firecracker.MachineID } +const ( + defaultGuestKernelArgs = "console=ttyS0 reboot=k panic=1 pci=off" + defaultGuestMemoryMiB = int64(512) + defaultGuestVCPUs = int64(1) +) + // New constructs a new host-local service from the app config. func New(cfg appconfig.Config) (*Service, error) { if err := cfg.Validate(); err != nil { return nil, err } - if cfg.VSock.Enabled { - if err := os.MkdirAll(cfg.VSock.BaseDir, 0o755); err != nil { - return nil, fmt.Errorf("create vsock base dir %q: %w", cfg.VSock.BaseDir, err) - } - } runtime, err := firecracker.NewRuntime(cfg.FirecrackerRuntimeConfig()) if err != nil { @@ -60,8 +51,7 @@ func New(cfg appconfig.Config) (*Service, error) { }, nil } -// CreateMachine boots a new local machine using config defaults plus request -// overrides. +// CreateMachine boots a new local machine from the single supported host default shape. func (s *Service) CreateMachine(ctx context.Context, req CreateMachineRequest) (*firecracker.MachineState, error) { spec, err := s.buildMachineSpec(req) if err != nil { @@ -86,49 +76,21 @@ func (s *Service) DeleteMachine(ctx context.Context, id firecracker.MachineID) e } func (s *Service) buildMachineSpec(req CreateMachineRequest) (firecracker.MachineSpec, error) { - if strings.TrimSpace(string(req.ID)) == "" { - return firecracker.MachineSpec{}, fmt.Errorf("machine id is required") - } if s == nil { return firecracker.MachineSpec{}, fmt.Errorf("service is required") } + if strings.TrimSpace(string(req.ID)) == "" { + return firecracker.MachineSpec{}, fmt.Errorf("machine id is required") + } spec := firecracker.MachineSpec{ ID: req.ID, - VCPUs: s.config.Machine.VCPUs, - MemoryMiB: s.config.Machine.MemoryMiB, - KernelImagePath: s.config.Machine.KernelImagePath, - RootFSPath: s.config.Machine.RootFSPath, - KernelArgs: s.config.Machine.KernelArgs, - Drives: append([]firecracker.DriveSpec(nil), req.Drives...), + VCPUs: defaultGuestVCPUs, + MemoryMiB: defaultGuestMemoryMiB, + KernelImagePath: s.config.KernelImagePath, + RootFSPath: s.config.RootFSPath, + KernelArgs: defaultGuestKernelArgs, } - - if value := strings.TrimSpace(req.KernelImagePath); value != "" { - spec.KernelImagePath = value - } - if value := strings.TrimSpace(req.RootFSPath); value != "" { - spec.RootFSPath = value - } - if value := strings.TrimSpace(req.KernelArgs); value != "" { - spec.KernelArgs = value - } - if req.VCPUs > 0 { - spec.VCPUs = req.VCPUs - } - if req.MemoryMiB > 0 { - spec.MemoryMiB = req.MemoryMiB - } - if req.VSock != nil { - vsock := *req.VSock - spec.Vsock = &vsock - } else if s.config.VSock.Enabled { - spec.Vsock = &firecracker.VsockSpec{ - ID: s.config.VSock.ID, - CID: s.config.VSock.CID, - Path: filepath.Join(s.config.VSock.BaseDir, string(req.ID)+".sock"), - } - } - if err := spec.Validate(); err != nil { return firecracker.MachineSpec{}, err }