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
14 changes: 9 additions & 5 deletions lib/hypervisor/firecracker/binaries.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,23 @@ func resolveBinaryPath(p *paths.Paths, version string) (string, error) {
return "", fmt.Errorf("paths are required when using embedded firecracker binaries")
}

return extractBinary(p, parseVersion(version))
parsedVersion, err := parseVersion(version)
if err != nil {
return "", err
}
return extractBinary(p, parsedVersion)
}

func parseVersion(version string) Version {
func parseVersion(version string) (Version, error) {
if version == "" {
return defaultVersion
return defaultVersion, nil
}
for _, supported := range supportedVersions {
if version == string(supported) {
return supported
return supported, nil
}
}
return defaultVersion
return "", fmt.Errorf("unsupported firecracker version %q", version)
}

func extractBinary(p *paths.Paths, version Version) (string, error) {
Expand Down
21 changes: 15 additions & 6 deletions lib/hypervisor/firecracker/binaries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,21 @@ func TestResolveBinaryPathInvalidCustomPath(t *testing.T) {
assert.Contains(t, err.Error(), "invalid firecracker custom binary path")
}

func TestParseVersionFallback(t *testing.T) {
assert.Equal(t, V1_16_1, defaultVersion)
assert.Equal(t, defaultVersion, parseVersion(""))
assert.Equal(t, defaultVersion, parseVersion("unknown"))
assert.Equal(t, V1_14_2, parseVersion("v1.14.2"))
assert.Equal(t, V1_16_1, parseVersion("v1.16.1"))
func TestParseVersion(t *testing.T) {
version, err := parseVersion("")
require.NoError(t, err)
assert.Equal(t, defaultVersion, version)

_, err = parseVersion("unknown")
require.ErrorContains(t, err, "unsupported firecracker version")

version, err = parseVersion("v1.14.2")
require.NoError(t, err)
assert.Equal(t, V1_14_2, version)

version, err = parseVersion("v1.16.1")
require.NoError(t, err)
assert.Equal(t, V1_16_1, version)
}

func TestResolveEmbeddedBinaryVersions(t *testing.T) {
Expand Down
3 changes: 1 addition & 2 deletions lib/images/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -203,7 +202,7 @@ func TestRecoverInterruptedCredentialedPullFailsForFreshRetry(t *testing.T) {
assert.Equal(t, ErrBorrowedCredentialsExpired.Error(), *stored.Error)
assert.Zero(t, m.queue.QueueLength())

data, err := os.ReadFile(filepath.Join(p.ImageDigestDir(repository, strings.TrimPrefix(digest, "sha256:")), "metadata.json"))
data, err := os.ReadFile(p.ImageContentMetadata(strings.TrimPrefix(digest, "sha256:")))
require.NoError(t, err)
assert.NotContains(t, string(data), "password")
}
81 changes: 75 additions & 6 deletions lib/images/disk_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"syscall"
)

// totalReadyImageBytesFromMetadata sums ready image sizes directly from metadata.json files.
Expand All @@ -14,6 +15,7 @@ import (
// files found in the digest directory so we do not undercount host disk usage.
func totalReadyImageBytesFromMetadata(imagesDir string) (int64, error) {
var total int64
seenRootfs := make(map[rootfsIdentity]struct{})

err := filepath.Walk(imagesDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
Expand All @@ -28,7 +30,7 @@ func totalReadyImageBytesFromMetadata(imagesDir string) (int64, error) {

data, err := os.ReadFile(path)
if err != nil {
rootfsBytes, fallbackErr := totalRootfsBytesInDigestDir(filepath.Dir(path))
rootfsBytes, fallbackErr := totalUniqueRootfsBytesInDigestDir(filepath.Dir(path), seenRootfs)
if fallbackErr == nil {
total += rootfsBytes
return nil
Expand All @@ -38,18 +40,33 @@ func totalReadyImageBytesFromMetadata(imagesDir string) (int64, error) {

var meta imageMetadata
if err := json.Unmarshal(data, &meta); err != nil {
rootfsBytes, fallbackErr := totalRootfsBytesInDigestDir(filepath.Dir(path))
rootfsBytes, fallbackErr := totalUniqueRootfsBytesInDigestDir(filepath.Dir(path), seenRootfs)
if fallbackErr == nil {
total += rootfsBytes
return nil
}
return fmt.Errorf("unmarshal image metadata %s: %w", path, err)
}
if meta.Status == StatusReady && meta.SizeBytes > 0 {
total += meta.SizeBytes
return nil
}
if meta.Status == StatusReady {
rootfsPaths, globErr := filepath.Glob(filepath.Join(filepath.Dir(path), "rootfs.*"))
if globErr != nil {
return fmt.Errorf("find ready image rootfs for %s: %w", path, globErr)
}
for _, rootfsPath := range rootfsPaths {
rootfsInfo, statErr := os.Stat(rootfsPath)
if statErr != nil {
continue
}
if !markUniqueRootfs(rootfsInfo, seenRootfs) {
return nil
}
break
}

if meta.SizeBytes > 0 {
total += meta.SizeBytes
return nil
}
rootfsBytes, err := totalRootfsBytesInDigestDir(filepath.Dir(path))
if err != nil {
return fmt.Errorf("stat ready image rootfs for %s: %w", path, err)
Expand Down Expand Up @@ -172,3 +189,55 @@ func totalRootfsBytesInDigestDir(digestDir string) (int64, error) {
}
return total, nil
}

type rootfsIdentity struct {
dev uint64
ino uint64
}

func markUniqueRootfs(info os.FileInfo, seen map[rootfsIdentity]struct{}) bool {
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return true
}
identity := rootfsIdentity{dev: uint64(stat.Dev), ino: uint64(stat.Ino)}
if _, exists := seen[identity]; exists {
return false
}
seen[identity] = struct{}{}
return true
}

func totalUniqueRootfsBytesInDigestDir(digestDir string, seen map[rootfsIdentity]struct{}) (int64, error) {
rootfsPaths, err := filepath.Glob(filepath.Join(digestDir, "rootfs.*"))
if err != nil {
return 0, err
}
if len(rootfsPaths) == 0 {
return 0, os.ErrNotExist
}

var total int64
found := false
for _, rootfsPath := range rootfsPaths {
info, err := os.Stat(rootfsPath)
if err != nil {
if os.IsNotExist(err) {
continue
}
return 0, err
}
if info.IsDir() {
continue
}
found = true
if !markUniqueRootfs(info, seen) {
continue
}
total += info.Size()
}
if !found {
return 0, os.ErrNotExist
}
return total, nil
}
42 changes: 38 additions & 4 deletions lib/images/disk_usage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
)

func TestTotalReadyImageBytesFromMetadata_UsesRootfsFallbackForMalformedMetadata(t *testing.T) {
t.Parallel()

imagesDir := t.TempDir()
digestDir := filepath.Join(imagesDir, "docker.io", "library", "alpine", "sha256deadbeef")
require.NoError(t, os.MkdirAll(digestDir, 0o755))
Expand All @@ -22,9 +20,45 @@ func TestTotalReadyImageBytesFromMetadata_UsesRootfsFallbackForMalformedMetadata
require.Equal(t, int64(len("rootfs-data")), total)
}

func TestTotalReadyImageBytesFromMetadata_UsesRootfsFallbackForReadyImageWithoutSize(t *testing.T) {
t.Parallel()
func TestTotalReadyImageBytesFromMetadata_DeduplicatesHardLinkedAliases(t *testing.T) {
imagesDir := t.TempDir()
sourceDir := filepath.Join(imagesDir, "source", "digest")
targetDir := filepath.Join(imagesDir, "target", "digest")
require.NoError(t, os.MkdirAll(sourceDir, 0o755))
require.NoError(t, os.MkdirAll(targetDir, 0o755))

sourceRootfs := filepath.Join(sourceDir, "rootfs.erofs")
targetRootfs := filepath.Join(targetDir, "rootfs.erofs")
require.NoError(t, os.WriteFile(sourceRootfs, []byte("shared-rootfs"), 0o644))
require.NoError(t, os.Link(sourceRootfs, targetRootfs))
metadata := []byte(`{"status":"ready","size_bytes":13}`)
require.NoError(t, os.WriteFile(filepath.Join(sourceDir, "metadata.json"), metadata, 0o644))
require.NoError(t, os.WriteFile(filepath.Join(targetDir, "metadata.json"), metadata, 0o644))

total, err := totalReadyImageBytesFromMetadata(imagesDir)
require.NoError(t, err)
require.Equal(t, int64(len("shared-rootfs")), total)
}

func TestTotalReadyImageBytesFromMetadata_DeduplicatesMalformedAliases(t *testing.T) {
imagesDir := t.TempDir()
malformedDir := filepath.Join(imagesDir, "a-malformed", "digest")
validDir := filepath.Join(imagesDir, "b-valid", "digest")
require.NoError(t, os.MkdirAll(malformedDir, 0o755))
require.NoError(t, os.MkdirAll(validDir, 0o755))

rootfs := filepath.Join(malformedDir, "rootfs.erofs")
require.NoError(t, os.WriteFile(rootfs, []byte("shared-rootfs"), 0o644))
require.NoError(t, os.Link(rootfs, filepath.Join(validDir, "rootfs.erofs")))
require.NoError(t, os.WriteFile(filepath.Join(malformedDir, "metadata.json"), []byte("{not-json"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(validDir, "metadata.json"), []byte(`{"status":"ready","size_bytes":13}`), 0o644))

total, err := totalReadyImageBytesFromMetadata(imagesDir)
require.NoError(t, err)
require.Equal(t, int64(len("shared-rootfs")), total)
}

func TestTotalReadyImageBytesFromMetadata_UsesRootfsFallbackForReadyImageWithoutSize(t *testing.T) {
imagesDir := t.TempDir()
digestDir := filepath.Join(imagesDir, "docker.io", "library", "alpine", "sha256deadbeef")
require.NoError(t, os.MkdirAll(digestDir, 0o755))
Expand Down
Loading
Loading