Skip to content
This repository was archived by the owner on Apr 13, 2026. It is now read-only.

Commit 41ae5f8

Browse files
committed
feat: add Firecracker support as alternative VM runtime
1 parent 76a098d commit 41ae5f8

File tree

16 files changed

+1673
-56
lines changed

16 files changed

+1673
-56
lines changed

core/config/runtime.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
package config
22

3+
// RuntimeType specifies which VM runtime to use.
4+
type RuntimeType string
5+
6+
const (
7+
RuntimeTypeCloudHypervisor RuntimeType = "cloudhypervisor"
8+
RuntimeTypeFirecracker RuntimeType = "firecracker"
9+
)
10+
311
type RuntimeConfig struct {
4-
CloudHypervisorBinary string `json:"cloud_hypervisor_binary" toml:"cloud_hypervisor_binary"`
5-
JailerBinary string `json:"jailer_binary" toml:"jailer_binary"`
6-
InitBinary string `json:"init_binary" toml:"init_binary"`
7-
LinuxKernel string `json:"linux_kernel" toml:"linux_kernel"`
8-
ZFSPool string `json:"zfs_pool" toml:"zfs_pool"`
12+
// RuntimeType specifies which VM runtime to use: "cloudhypervisor" (default) or "firecracker"
13+
RuntimeType RuntimeType `json:"runtime_type" toml:"runtime_type"`
14+
CloudHypervisorBinary string `json:"cloud_hypervisor_binary" toml:"cloud_hypervisor_binary"`
15+
FirecrackerBinary string `json:"firecracker_binary" toml:"firecracker_binary"`
16+
JailerBinary string `json:"jailer_binary" toml:"jailer_binary"`
17+
InitBinary string `json:"init_binary" toml:"init_binary"`
18+
LinuxKernel string `json:"linux_kernel" toml:"linux_kernel"`
19+
ZFSPool string `json:"zfs_pool" toml:"zfs_pool"`
20+
}
21+
22+
// GetRuntimeType returns the runtime type, defaulting to CloudHypervisor if not specified.
23+
func (c *RuntimeConfig) GetRuntimeType() RuntimeType {
24+
if c.RuntimeType == "" {
25+
return RuntimeTypeCloudHypervisor
26+
}
27+
return c.RuntimeType
928
}

pkg/firecracker/client.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package firecracker
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"net"
10+
"net/http"
11+
)
12+
13+
// Client is a Firecracker HTTP API client.
14+
type Client struct {
15+
httpClient *http.Client
16+
baseURL string
17+
}
18+
19+
// NewClient creates a new Firecracker API client connected via Unix socket.
20+
func NewClient(socketPath string) *Client {
21+
httpClient := &http.Client{
22+
Transport: &http.Transport{
23+
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
24+
return net.Dial("unix", socketPath)
25+
},
26+
},
27+
}
28+
29+
return &Client{
30+
httpClient: httpClient,
31+
baseURL: "http://localhost",
32+
}
33+
}
34+
35+
// doRequest performs an HTTP request to the Firecracker API.
36+
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
37+
var bodyReader io.Reader
38+
if body != nil {
39+
jsonBody, err := json.Marshal(body)
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to marshal request body: %w", err)
42+
}
43+
bodyReader = bytes.NewReader(jsonBody)
44+
}
45+
46+
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bodyReader)
47+
if err != nil {
48+
return nil, fmt.Errorf("failed to create request: %w", err)
49+
}
50+
51+
if body != nil {
52+
req.Header.Set("Content-Type", "application/json")
53+
}
54+
req.Header.Set("Accept", "application/json")
55+
56+
return c.httpClient.Do(req)
57+
}
58+
59+
// checkResponse checks if the response indicates success.
60+
func checkResponse(resp *http.Response) error {
61+
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
62+
return nil
63+
}
64+
65+
body, _ := io.ReadAll(resp.Body)
66+
var apiErr Error
67+
if err := json.Unmarshal(body, &apiErr); err == nil && apiErr.FaultMessage != "" {
68+
return fmt.Errorf("firecracker API error: %s", apiErr.FaultMessage)
69+
}
70+
71+
return fmt.Errorf("firecracker API error: status %d, body: %s", resp.StatusCode, string(body))
72+
}
73+
74+
// GetInstanceInfo retrieves information about the instance.
75+
func (c *Client) GetInstanceInfo(ctx context.Context) (*InstanceInfo, error) {
76+
resp, err := c.doRequest(ctx, http.MethodGet, "/", nil)
77+
if err != nil {
78+
return nil, err
79+
}
80+
defer resp.Body.Close()
81+
82+
if err := checkResponse(resp); err != nil {
83+
return nil, err
84+
}
85+
86+
var info InstanceInfo
87+
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
88+
return nil, fmt.Errorf("failed to decode response: %w", err)
89+
}
90+
91+
return &info, nil
92+
}
93+
94+
// PutMachineConfig sets the machine configuration.
95+
func (c *Client) PutMachineConfig(ctx context.Context, config MachineConfig) error {
96+
resp, err := c.doRequest(ctx, http.MethodPut, "/machine-config", config)
97+
if err != nil {
98+
return err
99+
}
100+
defer resp.Body.Close()
101+
102+
return checkResponse(resp)
103+
}
104+
105+
// PutBootSource sets the boot source configuration.
106+
func (c *Client) PutBootSource(ctx context.Context, bootSource BootSource) error {
107+
resp, err := c.doRequest(ctx, http.MethodPut, "/boot-source", bootSource)
108+
if err != nil {
109+
return err
110+
}
111+
defer resp.Body.Close()
112+
113+
return checkResponse(resp)
114+
}
115+
116+
// PutDrive adds or updates a block device.
117+
func (c *Client) PutDrive(ctx context.Context, drive Drive) error {
118+
resp, err := c.doRequest(ctx, http.MethodPut, "/drives/"+drive.DriveID, drive)
119+
if err != nil {
120+
return err
121+
}
122+
defer resp.Body.Close()
123+
124+
return checkResponse(resp)
125+
}
126+
127+
// PutNetworkInterface adds or updates a network interface.
128+
func (c *Client) PutNetworkInterface(ctx context.Context, iface NetworkInterface) error {
129+
resp, err := c.doRequest(ctx, http.MethodPut, "/network-interfaces/"+iface.IfaceID, iface)
130+
if err != nil {
131+
return err
132+
}
133+
defer resp.Body.Close()
134+
135+
return checkResponse(resp)
136+
}
137+
138+
// PutVsock sets the vsock device configuration.
139+
func (c *Client) PutVsock(ctx context.Context, vsock VsockDevice) error {
140+
resp, err := c.doRequest(ctx, http.MethodPut, "/vsock", vsock)
141+
if err != nil {
142+
return err
143+
}
144+
defer resp.Body.Close()
145+
146+
return checkResponse(resp)
147+
}
148+
149+
// CreateAction performs an instance action (e.g., InstanceStart).
150+
func (c *Client) CreateAction(ctx context.Context, action InstanceActionInfo) error {
151+
resp, err := c.doRequest(ctx, http.MethodPut, "/actions", action)
152+
if err != nil {
153+
return err
154+
}
155+
defer resp.Body.Close()
156+
157+
return checkResponse(resp)
158+
}
159+
160+
// PatchVMState changes the VM state (Paused or Resumed).
161+
func (c *Client) PatchVMState(ctx context.Context, state VMState) error {
162+
resp, err := c.doRequest(ctx, http.MethodPatch, "/vm", state)
163+
if err != nil {
164+
return err
165+
}
166+
defer resp.Body.Close()
167+
168+
return checkResponse(resp)
169+
}
170+
171+
// CreateSnapshot creates a snapshot of the microVM.
172+
func (c *Client) CreateSnapshot(ctx context.Context, params SnapshotCreateParams) error {
173+
resp, err := c.doRequest(ctx, http.MethodPut, "/snapshot/create", params)
174+
if err != nil {
175+
return err
176+
}
177+
defer resp.Body.Close()
178+
179+
return checkResponse(resp)
180+
}
181+
182+
// LoadSnapshot loads a snapshot into the microVM.
183+
func (c *Client) LoadSnapshot(ctx context.Context, params SnapshotLoadParams) error {
184+
resp, err := c.doRequest(ctx, http.MethodPut, "/snapshot/load", params)
185+
if err != nil {
186+
return err
187+
}
188+
defer resp.Body.Close()
189+
190+
return checkResponse(resp)
191+
}

pkg/firecracker/types.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package firecracker
2+
3+
// MachineConfig specifies the microVM machine configuration.
4+
type MachineConfig struct {
5+
VcpuCount int `json:"vcpu_count"`
6+
MemSizeMib int `json:"mem_size_mib"`
7+
Smt bool `json:"smt,omitempty"`
8+
TrackDirtyPages bool `json:"track_dirty_pages,omitempty"`
9+
}
10+
11+
// BootSource specifies the boot source for the microVM.
12+
type BootSource struct {
13+
KernelImagePath string `json:"kernel_image_path"`
14+
BootArgs *string `json:"boot_args,omitempty"`
15+
InitrdPath *string `json:"initrd_path,omitempty"`
16+
}
17+
18+
// Drive specifies a block device configuration.
19+
type Drive struct {
20+
DriveID string `json:"drive_id"`
21+
PathOnHost string `json:"path_on_host"`
22+
IsRootDevice bool `json:"is_root_device"`
23+
IsReadOnly bool `json:"is_read_only"`
24+
}
25+
26+
// NetworkInterface specifies a network interface configuration.
27+
type NetworkInterface struct {
28+
IfaceID string `json:"iface_id"`
29+
GuestMAC *string `json:"guest_mac,omitempty"`
30+
HostDevName string `json:"host_dev_name"`
31+
}
32+
33+
// VsockDevice specifies a vsock device configuration.
34+
type VsockDevice struct {
35+
GuestCID int `json:"guest_cid"`
36+
UdsPath string `json:"uds_path"`
37+
}
38+
39+
// InstanceActionInfo describes an action to perform on the instance.
40+
type InstanceActionInfo struct {
41+
ActionType string `json:"action_type"` // InstanceStart, SendCtrlAltDel, FlushMetrics
42+
}
43+
44+
// SnapshotCreateParams specifies parameters for creating a snapshot.
45+
type SnapshotCreateParams struct {
46+
SnapshotPath string `json:"snapshot_path"`
47+
MemFilePath string `json:"mem_file_path"`
48+
SnapshotType *string `json:"snapshot_type,omitempty"` // Full or Diff
49+
}
50+
51+
// SnapshotLoadParams specifies parameters for loading a snapshot.
52+
type SnapshotLoadParams struct {
53+
SnapshotPath string `json:"snapshot_path"`
54+
MemFilePath string `json:"mem_file_path"`
55+
EnableDiffSnapshots bool `json:"enable_diff_snapshots,omitempty"`
56+
ResumeVM bool `json:"resume_vm,omitempty"`
57+
}
58+
59+
// VMState represents the state of the VM for pause/resume.
60+
type VMState struct {
61+
State string `json:"state"` // Paused or Resumed
62+
}
63+
64+
// InstanceInfo provides information about the instance state.
65+
type InstanceInfo struct {
66+
ID string `json:"id"`
67+
State string `json:"state"` // Not started, Running, Paused
68+
VMState string `json:"vmm_version"`
69+
AppName string `json:"app_name"`
70+
}
71+
72+
// Error represents a Firecracker API error response.
73+
type Error struct {
74+
FaultMessage string `json:"fault_message"`
75+
}

0 commit comments

Comments
 (0)