functions

The Fool guy's FAAS
git clone git://git.lair.cx/functions
Log | Files | Refs | README

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 }