diff --git a/plugins/snapshots/erofs/erofs_linux_test.go b/plugins/snapshots/erofs/erofs_linux_test.go index 195cc41c93..b5dcb0a0dd 100644 --- a/plugins/snapshots/erofs/erofs_linux_test.go +++ b/plugins/snapshots/erofs/erofs_linux_test.go @@ -18,17 +18,31 @@ package erofs import ( "context" + "io" "os" "os/exec" "path/filepath" "testing" + "time" + "github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/v2/core/mount" "github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/v2/core/snapshots/storage" "github.com/containerd/containerd/v2/core/snapshots/testsuite" + "github.com/containerd/containerd/v2/internal/erofsutils" "github.com/containerd/containerd/v2/internal/fsverity" + "github.com/containerd/containerd/v2/pkg/archive/tartest" "github.com/containerd/containerd/v2/pkg/testutil" + "github.com/containerd/containerd/v2/plugins/content/local" + erofsdiffer "github.com/containerd/containerd/v2/plugins/diff/erofs" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +const ( + testFileContent = "Hello, this is content for testing the EROFS Snapshotter!" + testNestedFileContent = "Nested file content" ) func newSnapshotter(t *testing.T) func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) { @@ -131,3 +145,177 @@ func TestErofsFsverity(t *testing.T) { t.Fatal("Expected direct write to fsverity-enabled layer to fail") } } + +func TestErofsDifferWithTarIndexMode(t *testing.T) { + testutil.RequiresRoot(t) + ctx := context.Background() + + if !findErofs() { + t.Skip("check for erofs kernel support failed, skipping test") + } + + // Check if mkfs.erofs supports tar index mode + supported, err := erofsutils.SupportGenerateFromTar() + if err != nil || !supported { + t.Skip("mkfs.erofs does not support tar mode, skipping tar index test") + } + + tempDir := t.TempDir() + + // Create content store for the differ + contentStore, err := local.NewStore(filepath.Join(tempDir, "content")) + if err != nil { + t.Fatal(err) + } + + // Create EROFS differ with tar index mode enabled + differ := erofsdiffer.NewErofsDiffer(contentStore, erofsdiffer.WithTarIndexMode()) + + // Create EROFS snapshotter + snapshotRoot := filepath.Join(tempDir, "snapshots") + s, err := NewSnapshotter(snapshotRoot) + if err != nil { + t.Fatal(err) + } + defer s.Close() + + // Create test tar content + tarReader := createTestTarContent() + defer tarReader.Close() + + // Read the tar content into a buffer for digest calculation and writing + tarContent, err := io.ReadAll(tarReader) + if err != nil { + t.Fatal(err) + } + + // Write tar content to content store + desc := ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageLayerGzip, + Digest: digest.FromBytes(tarContent), + Size: int64(len(tarContent)), + } + + writer, err := contentStore.Writer(ctx, + content.WithRef("test-layer"), + content.WithDescriptor(desc)) + if err != nil { + t.Fatal(err) + } + + if _, err := writer.Write(tarContent); err != nil { + writer.Close() + t.Fatal(err) + } + + if err := writer.Commit(ctx, desc.Size, desc.Digest); err != nil { + writer.Close() + t.Fatal(err) + } + writer.Close() + + // Prepare a snapshot using the snapshotter + snapshotKey := "test-snapshot" + mounts, err := s.Prepare(ctx, snapshotKey, "") + if err != nil { + t.Fatal(err) + } + + // Apply the tar content using the EROFS differ with tar index mode + appliedDesc, err := differ.Apply(ctx, desc, mounts) + if err != nil { + t.Fatal(err) + } + + t.Logf("Applied layer using EROFS differ with tar index mode:") + t.Logf(" Original: %s (%d bytes)", desc.Digest, desc.Size) + t.Logf(" Applied: %s (%d bytes)", appliedDesc.Digest, appliedDesc.Size) + t.Logf(" MediaType: %s", appliedDesc.MediaType) + + // Commit the snapshot to finalize the EROFS layer creation + commitKey := "test-commit" + if err := s.Commit(ctx, commitKey, snapshotKey); err != nil { + t.Fatal(err) + } + + // Get the internal snapshot ID to check the EROFS layer file + snap := s.(*snapshotter) + var id string + if err := snap.ms.WithTransaction(ctx, false, func(ctx context.Context) error { + id, _, _, err = storage.GetInfo(ctx, commitKey) + return err + }); err != nil { + t.Fatal(err) + } + + // Verify the EROFS layer file was created + layerPath := snap.layerBlobPath(id) + if _, err := os.Stat(layerPath); err != nil { + t.Fatalf("EROFS layer file should exist: %v", err) + } + + // Verify the layer file is not empty + stat, err := os.Stat(layerPath) + if err != nil { + t.Fatal(err) + } + if stat.Size() == 0 { + t.Fatal("EROFS layer file should not be empty") + } + + t.Logf("EROFS layer file created with tar index mode: %s (%d bytes)", layerPath, stat.Size()) + + // Create a view to verify the content + viewKey := "test-view" + viewMounts, err := s.View(ctx, viewKey, commitKey) + if err != nil { + t.Fatal(err) + } + + viewTarget := filepath.Join(tempDir, viewKey) + if err := os.MkdirAll(viewTarget, 0755); err != nil { + t.Fatal(err) + } + if err := mount.All(viewMounts, viewTarget); err != nil { + t.Fatal(err) + } + defer testutil.Unmount(t, viewTarget) + + // Verify we can read the original test data + testData, err := os.ReadFile(filepath.Join(viewTarget, "test-file.txt")) + if err != nil { + t.Fatal(err) + } + expected := testFileContent + if string(testData) != expected { + t.Fatalf("Expected %q, got %q", expected, string(testData)) + } + + // Verify nested file + nestedData, err := os.ReadFile(filepath.Join(viewTarget, "testdir", "nested.txt")) + if err != nil { + t.Fatal(err) + } + expectedNested := testNestedFileContent + if string(nestedData) != expectedNested { + t.Fatalf("Expected %q, got %q", expectedNested, string(nestedData)) + } + + t.Logf("Successfully verified EROFS Snapshotter using the differ with tar index mode") +} + +// Helper function to create test tar content using tartest +func createTestTarContent() io.ReadCloser { + // Create a tar context with current time for consistency + tc := tartest.TarContext{}.WithModTime(time.Now()) + + // Create the tar with our test files and directories + tarWriter := tartest.TarAll( + tc.File("test-file.txt", []byte(testFileContent), 0644), + tc.Dir("testdir", 0755), + tc.File("testdir/nested.txt", []byte(testNestedFileContent), 0644), + ) + + // Return the tar as a ReadCloser + return tartest.TarFromWriterTo(tarWriter) +}