- Load snapshot with ResumeVM: false so MMDS data can be written while VM is paused - Call ResumeVM explicitly after configureMmds succeeds - Skip PUT /mmds/config on restored VMs (Firecracker rejects it with 400) - Strip JSON quotes from MMDS values with tr -d '"' in net-init script - Add 169.254.169.2/32 link-local addr and flush eth0 before applying new IP Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
72 lines
2.0 KiB
Go
72 lines
2.0 KiB
Go
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: false, // Changed: We pause here so MMDS can be configured BEFORE Resume.
|
|
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
|
|
}
|