Add diff snapshots for re-pause to avoid UFFD fault-in storm
Use Firecracker's Diff snapshot type when re-pausing a previously
resumed sandbox, capturing only dirty pages instead of a full memory
dump. Chains up to 10 incremental generations before collapsing back
to a Full snapshot. Multi-generation diff files (memfile.{buildID})
are supported alongside the legacy single-file format in resume,
template creation, and snapshot existence checks.
This commit is contained in:
@ -7,6 +7,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,11 +30,17 @@ func SnapPath(baseDir, name string) string {
|
||||
return filepath.Join(DirPath(baseDir, name), SnapFileName)
|
||||
}
|
||||
|
||||
// MemDiffPath returns the path to the compact memory diff file.
|
||||
// MemDiffPath returns the path to the compact memory diff file (legacy single-generation).
|
||||
func MemDiffPath(baseDir, name string) string {
|
||||
return filepath.Join(DirPath(baseDir, name), MemDiffName)
|
||||
}
|
||||
|
||||
// MemDiffPathForBuild returns the path to a specific generation's diff file.
|
||||
// Format: memfile.{buildID}
|
||||
func MemDiffPathForBuild(baseDir, name string, buildID uuid.UUID) string {
|
||||
return filepath.Join(DirPath(baseDir, name), fmt.Sprintf("memfile.%s", buildID.String()))
|
||||
}
|
||||
|
||||
// MemHeaderPath returns the path to the memory mapping header file.
|
||||
func MemHeaderPath(baseDir, name string) string {
|
||||
return filepath.Join(DirPath(baseDir, name), MemHeaderName)
|
||||
@ -85,17 +93,38 @@ func ReadMeta(baseDir, name string) (*RootfsMeta, error) {
|
||||
|
||||
// Exists reports whether a complete snapshot exists (all required files present).
|
||||
// Supports both legacy (rootfs.ext4) and CoW-based (rootfs.cow + rootfs.meta) snapshots.
|
||||
// Memory diff files can be either legacy "memfile" or generation-specific "memfile.{uuid}".
|
||||
func Exists(baseDir, name string) bool {
|
||||
dir := DirPath(baseDir, name)
|
||||
|
||||
// Common files required by both formats.
|
||||
for _, f := range []string{SnapFileName, MemDiffName, MemHeaderName} {
|
||||
// snapfile and header are always required.
|
||||
for _, f := range []string{SnapFileName, MemHeaderName} {
|
||||
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Accept either rootfs.ext4 (legacy) or rootfs.cow + rootfs.meta (dm-snapshot).
|
||||
// Check that at least one memfile exists (legacy or generation-specific).
|
||||
// We verify by reading the header and checking that referenced diff files exist.
|
||||
// Fall back to checking for the legacy memfile name if header can't be read.
|
||||
if _, err := os.Stat(filepath.Join(dir, MemDiffName)); err != nil {
|
||||
// No legacy memfile — check if any memfile.{uuid} exists by
|
||||
// looking for files matching the pattern.
|
||||
matches, _ := filepath.Glob(filepath.Join(dir, "memfile.*"))
|
||||
hasGenDiff := false
|
||||
for _, m := range matches {
|
||||
base := filepath.Base(m)
|
||||
if base != MemHeaderName {
|
||||
hasGenDiff = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasGenDiff {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Accept either rootfs.ext4 (legacy/template) or rootfs.cow + rootfs.meta (dm-snapshot).
|
||||
if _, err := os.Stat(filepath.Join(dir, RootfsFileName)); err == nil {
|
||||
return true
|
||||
}
|
||||
@ -127,6 +156,37 @@ func HasCow(baseDir, name string) bool {
|
||||
return cowErr == nil && metaErr == nil
|
||||
}
|
||||
|
||||
// ListDiffFiles returns a map of build ID → file path for all memory diff files
|
||||
// referenced by the given header. Handles both the legacy "memfile" name
|
||||
// (single-generation) and generation-specific "memfile.{uuid}" names.
|
||||
func ListDiffFiles(baseDir, name string, header *Header) (map[string]string, error) {
|
||||
dir := DirPath(baseDir, name)
|
||||
result := make(map[string]string)
|
||||
|
||||
for _, m := range header.Mapping {
|
||||
if m.BuildID == uuid.Nil {
|
||||
continue // zero-fill, no file needed
|
||||
}
|
||||
idStr := m.BuildID.String()
|
||||
if _, exists := result[idStr]; exists {
|
||||
continue
|
||||
}
|
||||
// Try generation-specific path first, fall back to legacy.
|
||||
genPath := filepath.Join(dir, fmt.Sprintf("memfile.%s", idStr))
|
||||
if _, err := os.Stat(genPath); err == nil {
|
||||
result[idStr] = genPath
|
||||
continue
|
||||
}
|
||||
legacyPath := filepath.Join(dir, MemDiffName)
|
||||
if _, err := os.Stat(legacyPath); err == nil {
|
||||
result[idStr] = legacyPath
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("diff file not found for build %s", idStr)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EnsureDir creates the snapshot directory if it doesn't exist.
|
||||
func EnsureDir(baseDir, name string) error {
|
||||
dir := DirPath(baseDir, name)
|
||||
|
||||
Reference in New Issue
Block a user