feat: add structured logging, --dev flag, and snapshot network_overrides
- Add --dev flag to main that enables logrus caller info (file:line) for easier debugging without changing production log output - Wire firecracker SDK logger (WithLogger) and FIFO log file to both the golden VM and each clone machine so Firecracker's own logs are surfaced - Log the exact shell commands being run (cp --reflink, ip tuntap, ip link, firecracker binary) at Info level before each syscall/exec, making it straightforward to reproduce steps manually - Extract snapshot.go with loadSnapshotWithNetworkOverride: a direct PUT /snapshot/load call over the Unix socket that includes network_overrides, remapping the stored tap to the per-clone tap name (Firecracker v1.15+ feature not yet exposed by SDK v1.0.0) - Use firecracker.WithSnapshot + a Handlers.FcInit.Swap to replace the SDK's LoadSnapshotHandler with the above when Bridge != "none" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
71
orchestrator/snapshot.go
Normal file
71
orchestrator/snapshot.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type networkOverride struct {
|
||||
IfaceID string `json:"iface_id"`
|
||||
HostDevName string `json:"host_dev_name"`
|
||||
}
|
||||
|
||||
type snapshotLoadRequest struct {
|
||||
MemFilePath string `json:"mem_file_path"`
|
||||
SnapshotPath string `json:"snapshot_path"`
|
||||
ResumeVM bool `json:"resume_vm,omitempty"`
|
||||
NetworkOverrides []networkOverride `json:"network_overrides,omitempty"`
|
||||
}
|
||||
|
||||
// loadSnapshotWithNetworkOverride calls PUT /snapshot/load on the Firecracker
|
||||
// Unix socket, remapping the first network interface to tapName.
|
||||
// This bypasses the SDK's LoadSnapshotHandler which doesn't expose
|
||||
// network_overrides (added in Firecracker v1.15, SDK v1.0.0 omits it).
|
||||
func loadSnapshotWithNetworkOverride(ctx context.Context, sockPath, memPath, vmstatePath, tapName string) error {
|
||||
payload := snapshotLoadRequest{
|
||||
MemFilePath: memPath,
|
||||
SnapshotPath: vmstatePath,
|
||||
ResumeVM: true,
|
||||
NetworkOverrides: []networkOverride{
|
||||
{IfaceID: "1", HostDevName: tapName},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal snapshot load params: %w", err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return net.Dial("unix", sockPath)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut,
|
||||
"http://localhost/snapshot/load", bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("build snapshot load request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("snapshot load request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("snapshot load failed (%d): %s", resp.StatusCode, body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user