Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/vmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ func main() {

// ---- VM manager ----
maxRestores, _ := strconv.Atoi(envOrDefault("VMD_MAX_CONCURRENT_RESTORES", "100"))
uffdEnabled := envOrDefault("VMD_UFFD_ENABLED", "true") != "false"
uffdPrefetchEnabled := envOrDefault("VMD_UFFD_PREFETCH_ENABLED", "true") != "false"
uffdRecordMaxSeconds, _ := strconv.Atoi(envOrDefault("VMD_UFFD_RECORD_MAX_SECONDS", "10"))

mgr, err := vm.NewManager(vm.ManagerConfig{
FirecrackerBin: cfg.FirecrackerBin,
Expand All @@ -260,6 +263,9 @@ func main() {
BoxdBinaryPath: cfg.BoxdBinaryPath,
HostInterface: cfg.HostInterface,
MaxConcurrentRestores: maxRestores,
UffdEnabled: uffdEnabled,
UffdPrefetchEnabled: uffdPrefetchEnabled,
UffdRecordMaxSeconds: uffdRecordMaxSeconds,
}, netMgr, log)
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize VM manager")
Expand Down
17 changes: 17 additions & 0 deletions internal/vm/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ func (m *Manager) buildTemplateSync(ctx context.Context, buildVMID string, req B
return nil, fmt.Errorf("read build meta: %w", err)
}

// Best-effort: a missing access.log just means sandboxes fall back
// to sequential prefetch. The "build-" prefix must remain so isBuildVM
// skips persistence + reconciler for this throwaway VM.
if m.cfg.UffdEnabled && m.cfg.UffdPrefetchEnabled {
recordingVMID := "build-record-" + req.TemplateID
accessLogPath := filepath.Join(snapshotDir, "access.log")
recCfg := VMConfig{
VCPU: req.VCPU,
MemoryMiB: req.MemoryMiB,
BasePath: result.BasePath,
DeltaDir: snapshotDir,
}
if recErr := m.RecordAccessPattern(ctx, recordingVMID, result.SnapshotPath, result.MemFilePath, accessLogPath, recCfg, nil); recErr != nil {
log.Warn().Err(recErr).Msg("access-pattern recording failed (sandbox will fall back to sequential prefetch)")
}
}

log.Info().Dur("total", time.Since(buildStart)).Msg("template build complete")
return result, nil
}
Expand Down
41 changes: 41 additions & 0 deletions internal/vm/firecracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net"
"net/http"
"strings"
"time"

httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
Expand Down Expand Up @@ -326,3 +327,43 @@ func RestoreSnapshotWithOverrides(socketPath, snapshotPath, memPath, ifaceID, ta
}
return nil
}

// RestoreSnapshotUffdWithOverrides is the UFFD-backend variant of
// RestoreSnapshotWithOverrides. Instead of pointing Firecracker at the
// mem.snap file (File backend, which synchronously reads and CRC64-verifies
// the entire snapshot before returning), it points Firecracker at a Unix
// domain socket served by our in-process UFFD handler.
//
// LoadSnapshot returns in milliseconds because pages are not read upfront.
// As the guest touches pages, the kernel forwards faults to our handler,
// which serves them from a memory-mapped mem.snap.
//
// uffdSocketPath must be a bound Unix socket; the caller is responsible for
// starting the handler goroutine before invoking this function.
func RestoreSnapshotUffdWithOverrides(socketPath, snapshotPath, uffdSocketPath, ifaceID, tapDevice, blockDeltaDir string) error {
// Bound LoadSnapshot so a hung Firecracker doesn't wedge vmd.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
fc := newFCClient(socketPath)
if _, err := fc.Operations.LoadSnapshot(&operations.LoadSnapshotParams{
Context: ctx,
Body: &models.SnapshotLoadParams{
SnapshotPath: &snapshotPath,
MemBackend: &models.MemoryBackend{
BackendType: strPtr(models.MemoryBackendBackendTypeUffd),
BackendPath: &uffdSocketPath,
},
ResumeVM: true,
NetworkOverrides: []*models.NetworkOverride{
{IfaceID: &ifaceID, HostDevName: &tapDevice},
},
BlockDeltaDir: blockDeltaDir,
},
}); err != nil {
if isTornSnapshotErr(err) {
return fmt.Errorf("load snapshot (uffd): %w: %v", ErrTornSnapshot, err)
}
return fmt.Errorf("load snapshot (uffd): %w", err)
}
return nil
}
Loading
Loading