builder.go (5543B)
1 // Pacakge vm provides the VM-related features 2 package vmutils 3 4 import ( 5 "context" 6 "fmt" 7 "io" 8 "os/exec" 9 "sync" 10 11 firecracker "github.com/firecracker-microvm/firecracker-go-sdk" 12 "github.com/firecracker-microvm/firecracker-go-sdk/client/models" 13 "go.lair.cx/functions/configs" 14 ) 15 16 type MachineBuilder struct { 17 mutex sync.Mutex 18 19 socketPath string 20 kernelPath string 21 kernelArgs string 22 rootfsPath string 23 firecrackerLogPath string 24 25 networkEnabled bool 26 networkName string 27 28 vsockEnabled bool 29 vsockPath string 30 31 vCpuCount int 32 memSize int 33 34 verbose bool 35 36 stdin io.Reader 37 stdout io.Writer 38 stderr io.Writer 39 40 machine *firecracker.Machine 41 } 42 43 // BuildMachine builds a new firecracker machine. 44 func BuildMachine( 45 kernelPath string, 46 rootfsPath string, 47 configs ...func(*MachineBuilder), 48 ) (*firecracker.Machine, error) { 49 builder := &MachineBuilder{ 50 kernelPath: kernelPath, 51 rootfsPath: rootfsPath, 52 53 socketPath: DefaultSocketPath, 54 kernelArgs: DefaultKernelArgs, 55 firecrackerLogPath: DefaultFirecrackerLogPath, 56 57 networkEnabled: false, 58 networkName: "", 59 60 vsockEnabled: false, 61 vsockPath: "", 62 63 vCpuCount: DefaultVCpuCount, 64 memSize: DefaultMemSize, 65 } 66 67 for _, config := range configs { 68 config(builder) 69 } 70 71 machine, err := builder.build(context.Background()) 72 if err != nil { 73 return nil, err 74 75 } 76 77 if !builder.verbose { 78 machine.Logger().Logger.SetOutput(io.Discard) 79 } 80 81 return machine, nil 82 } 83 84 func WithSocketPath(socketPath string) func(*MachineBuilder) { 85 return func(builder *MachineBuilder) { 86 builder.socketPath = socketPath 87 } 88 } 89 90 func WithKernelArgs(kernelArgs string) func(*MachineBuilder) { 91 return func(builder *MachineBuilder) { 92 builder.kernelArgs = kernelArgs 93 } 94 } 95 96 func WithNetwork(cniNetworkName string) func(*MachineBuilder) { 97 return func(builder *MachineBuilder) { 98 builder.networkEnabled = true 99 builder.networkName = cniNetworkName 100 } 101 } 102 103 func WithVsock(vsockPath string) func(*MachineBuilder) { 104 return func(builder *MachineBuilder) { 105 builder.vsockEnabled = true 106 builder.vsockPath = vsockPath 107 } 108 } 109 110 func WithFirecrackerLogPath(firecrackerLogPath string) func(*MachineBuilder) { 111 return func(builder *MachineBuilder) { 112 builder.firecrackerLogPath = firecrackerLogPath 113 } 114 } 115 116 func WithVCpuCount(vCpuCount int) func(*MachineBuilder) { 117 return func(builder *MachineBuilder) { 118 builder.vCpuCount = vCpuCount 119 } 120 } 121 122 func WithMemSize(memSize int) func(*MachineBuilder) { 123 return func(builder *MachineBuilder) { 124 builder.memSize = memSize 125 } 126 } 127 128 func WithVerbose(verbose bool) func(*MachineBuilder) { 129 return func(builder *MachineBuilder) { 130 builder.verbose = verbose 131 } 132 } 133 134 func WithStdin(stdin io.Reader) func(*MachineBuilder) { 135 return func(builder *MachineBuilder) { 136 builder.stdin = stdin 137 } 138 } 139 140 func WithStdout(stdout io.Writer) func(*MachineBuilder) { 141 return func(builder *MachineBuilder) { 142 builder.stdout = stdout 143 } 144 } 145 146 func WithStderr(stderr io.Writer) func(*MachineBuilder) { 147 return func(builder *MachineBuilder) { 148 builder.stderr = stderr 149 } 150 } 151 152 func (vm *MachineBuilder) build(ctx context.Context) (*firecracker.Machine, error) { 153 if vm.machine != nil { 154 return vm.machine, nil 155 } 156 157 machineOpts, err := vm.buildMachineOpts(ctx) 158 if err != nil { 159 return nil, fmt.Errorf("failed to build machine options: %w", err) 160 } 161 162 firecrackerConfig := vm.buildFirecrackerConfig() 163 164 m, err := firecracker.NewMachine( 165 ctx, 166 firecrackerConfig, 167 machineOpts..., 168 ) 169 if err != nil { 170 return nil, fmt.Errorf("failed to create new machine: %w", err) 171 } 172 173 vm.machine = m 174 return vm.machine, nil 175 } 176 177 func (vm *MachineBuilder) buildMachineOpts(ctx context.Context) ([]firecracker.Opt, error) { 178 executablePath, err := exec.LookPath("firecracker") 179 if err != nil { 180 return nil, fmt.Errorf("failed to find firecracker binary: %w", err) 181 } 182 183 var machineOpts []firecracker.Opt 184 185 // Build the firecracker VM command 186 cmdBuilder := firecracker.VMCommandBuilder{}. 187 WithBin(executablePath). 188 WithSocketPath(vm.socketPath) 189 190 if vm.stdin != nil { 191 cmdBuilder = cmdBuilder.WithStdin(vm.stdin) 192 } 193 194 if vm.stdout != nil { 195 cmdBuilder = cmdBuilder.WithStdout(vm.stdout) 196 } 197 198 if vm.stderr != nil { 199 cmdBuilder = cmdBuilder.WithStderr(vm.stderr) 200 } 201 202 // TODO: Add jailer support 203 machineOpts = append( 204 machineOpts, 205 firecracker.WithProcessRunner( 206 cmdBuilder.Build(ctx), 207 ), 208 ) 209 210 return machineOpts, nil 211 } 212 213 func (vm *MachineBuilder) buildNetworkInterfacesConfig() firecracker.NetworkInterfaces { 214 if !vm.networkEnabled { 215 return nil 216 } 217 218 return firecracker.NetworkInterfaces{{ 219 CNIConfiguration: &firecracker.CNIConfiguration{ 220 NetworkName: vm.networkName, 221 IfName: NetworkInterfaceName, 222 }, 223 }} 224 } 225 226 func (vm *MachineBuilder) buildVsockDevicesConfig() []firecracker.VsockDevice { 227 if !vm.vsockEnabled { 228 return nil 229 } 230 231 return []firecracker.VsockDevice{{ 232 Path: vm.vsockPath, 233 CID: 3, 234 }} 235 } 236 237 func (vm *MachineBuilder) buildFirecrackerConfig() firecracker.Config { 238 return firecracker.Config{ 239 SocketPath: vm.socketPath, 240 LogPath: vm.firecrackerLogPath, 241 KernelImagePath: vm.kernelPath, 242 KernelArgs: vm.kernelArgs, 243 Drives: firecracker.NewDrivesBuilder(vm.rootfsPath). 244 Build(), 245 MachineCfg: models.MachineConfiguration{ 246 VcpuCount: firecracker.Int64(int64(vm.vCpuCount)), 247 MemSizeMib: firecracker.Int64(int64(vm.memSize)), 248 Smt: firecracker.Bool(configs.EnableSMT), 249 }, 250 NetworkInterfaces: vm.buildNetworkInterfacesConfig(), 251 VsockDevices: vm.buildVsockDevicesConfig(), 252 } 253 }