Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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