Add an EROFS test in container tests

This patch adds an EROFS test which checks that the checksums we get
from the target directory in container are identical with the ones
got from the source directory that we used to create the EROFS images.

erofs-utils is also added to the default image and will be used to
build the EROFS images during the test.

Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
This commit is contained in:
Tiwei Bie
2023-10-06 01:00:30 +08:00
parent 8246598313
commit 2f01e3fb90
3 changed files with 153 additions and 5 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y curl gnupg2 git \
apt-transport-https ca-certificates gnupg-agent \
software-properties-common \
pkg-config libffi-dev patch diffutils libssl-dev iptables kmod \
clang crossbuild-essential-amd64
clang crossbuild-essential-amd64 erofs-utils
# Install Docker client for the website build.
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
+133
View File
@@ -21,6 +21,7 @@ import (
"io/ioutil"
"math"
"os"
"os/exec"
"path"
"path/filepath"
"reflect"
@@ -3135,3 +3136,135 @@ func TestOverlayByMountAnnotation(t *testing.T) {
t.Fatalf("overlay filestore at %q was not deleted after container.Destroy()", filestoreFile)
}
}
// TestMountEROFS checks that the checksums from the target directory in container
// are identical with the ones from the source directory on host.
func TestMountEROFS(t *testing.T) {
// Skip this test if mkfs.erofs is not available.
mkfs, err := exec.LookPath("mkfs.erofs")
if err != nil {
t.Skipf("mkfs.erofs is not available: %v", err)
}
// Create a temporary directory to save the test files.
assetsDir, err := ioutil.TempDir(testutil.TmpDir(), "erofs-assets")
if err != nil {
t.Fatalf("ioutil.TempDir() failed: %v", err)
}
// Create a temporary directory with some random files in it, which will
// be used as the source directory to create the EROFS images.
sourceDir := filepath.Join(assetsDir, "source")
if err := os.Mkdir(sourceDir, 0755); err != nil {
t.Fatalf("os.Mkdir() failed: %v", err)
}
testApp, err := testutil.FindFile("test/cmd/test_app/test_app")
if err != nil {
t.Fatalf("error finding test_app: %v", err)
}
// Source directory is a small directory. Let's create a big directory in it.
// So we can cover both cases.
cmd := fmt.Sprintf("%s fsTreeCreate --target-dir=%s --create-symlink --depth=1 --file-per-level=500 --file-size=5000", testApp, filepath.Join(sourceDir, "big-directory"))
if out, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput(); err != nil {
t.Fatalf("exec: sh -c %q, err: %v, out: %s", cmd, err, out)
}
// Create a test script which can be used to get the checksums
// from a specified directory.
scriptFile := filepath.Join(assetsDir, "test-script")
if err := os.WriteFile(scriptFile, []byte(`#!/bin/bash
set -u -e -o pipefail
dir=$1
find $dir -printf "%P\n" | sort | md5sum
find $dir -type l | sort | xargs -L 1 readlink | md5sum
find $dir -type l -o -type f | sort | xargs cat | md5sum`), 0755); err != nil {
t.Fatalf("os.WriteFile() failed: %v", err)
}
// Get the checksums from the source directory on host.
var checksums string
if out, err := exec.Command(scriptFile, sourceDir).CombinedOutput(); err != nil {
t.Fatalf("exec: %s %s, err: %v, out: %s", scriptFile, sourceDir, err, out)
} else {
checksums = string(out)
}
images := []struct {
name string
options string
}{
{
// Generate extended inodes. Inline regular files if possible.
name: "image1",
options: "-E force-inode-extended",
},
{
// Generate extended inodes. Do not inline regular files.
name: "image2",
options: "-E force-inode-extended -E noinline_data",
},
{
// Generate compact inodes. Inline regular files if possible.
name: "image3",
options: "-E force-inode-compact",
},
{
// Generate compact inodes. Do not inline regular files.
name: "image4",
options: "-E force-inode-compact -E noinline_data",
},
}
// Create the EROFS images.
for _, i := range images {
cmd := fmt.Sprintf("%s %s %s %s", mkfs, i.options, filepath.Join(assetsDir, i.name), sourceDir)
if out, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput(); err != nil {
t.Fatalf("exec: sh -c %q, err: %v, out: %s", cmd, err, out)
}
}
spec, _ := sleepSpecConf(t)
conf := testutil.TestConfig(t)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()
// Create and start the container.
args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
c, err := New(conf, args)
if err != nil {
t.Fatalf("error creating container: %v", err)
}
defer c.Destroy()
if err := c.Start(conf); err != nil {
t.Fatalf("error starting container: %v", err)
}
targetDir := "/mnt"
for _, i := range images {
// Mount the EROFS image in container.
imageFile := filepath.Join(assetsDir, i.name)
if err := c.Sandbox.Mount(c.ID, "erofs", imageFile, targetDir); err != nil {
t.Fatalf("error mounting EROFS image %q to %q, err: %v", imageFile, targetDir, err)
}
// Get the checksums from the target directory in container, and check if they are
// identical with the ones got from the source directory on host.
if out, err := executeCombinedOutput(conf, c, nil, scriptFile, targetDir); err != nil {
t.Fatalf("exec: %s %s, err: %v, out: %s", scriptFile, targetDir, err, out)
} else if checksums != string(out) {
t.Errorf("checksums do not match, got: %s from %s, expected: %s", out, imageFile, checksums)
}
// Unmount the EROFS image in container.
if out, err := executeCombinedOutput(conf, c, nil, "/bin/umount", targetDir); err != nil {
t.Fatalf("exec: umount %q, err: %v, out: %s", targetDir, err, out)
}
}
}
+19 -4
View File
@@ -64,6 +64,8 @@ type fsTreeCreator struct {
depth uint
numFilesPerLevel uint
fileSize uint
targetDir string
createSymlink bool
}
// Name implements subcommands.Command.Name.
@@ -73,7 +75,7 @@ func (*fsTreeCreator) Name() string {
// Synopsis implements subcommands.Command.Synopsys.
func (*fsTreeCreator) Synopsis() string {
return "creates a filesystem tree of a certain depth, with a certain number of files on each level and each file with a certain size. Some randomization is added on top of this"
return "creates a filesystem tree of a certain depth, with a certain number of files on each level and each file with a certain size and type, under a certain directory. Some randomization is added on top of this"
}
// Usage implements subcommands.Command.Usage.
@@ -86,6 +88,8 @@ func (c *fsTreeCreator) SetFlags(f *flag.FlagSet) {
f.UintVar(&c.depth, "depth", 10, "number of levels to create")
f.UintVar(&c.numFilesPerLevel, "file-per-level", 10, "number of files to create per level")
f.UintVar(&c.fileSize, "file-size", 4096, "size of each file")
f.StringVar(&c.targetDir, "target-dir", "/", "directory under which to create the filesystem tree")
f.BoolVar(&c.createSymlink, "create-symlink", false, "create symlinks other than the first file per level")
}
// Execute implements subcommands.Command.Execute.
@@ -93,15 +97,26 @@ func (c *fsTreeCreator) Execute(ctx context.Context, f *flag.FlagSet, args ...an
depth := c.depth + uint(rand.Uint32())%c.depth
numFilesPerLevel := c.numFilesPerLevel + uint(rand.Uint32())%c.numFilesPerLevel
fileSize := c.fileSize + uint(rand.Uint32())%c.fileSize
curDir := c.targetDir
if _, err := os.Stat(curDir); os.IsNotExist(err) {
if err := os.MkdirAll(curDir, 0777); err != nil {
log.Fatalf("error creating directory %q: %v", curDir, err)
}
}
curDir := "/"
data := make([]byte, fileSize)
rand.Read(data)
for i := uint(0); i < depth; i++ {
for j := uint(0); j < numFilesPerLevel; j++ {
filePath := filepath.Join(curDir, fmt.Sprintf("file%d", j))
if err := os.WriteFile(filePath, data, 0666); err != nil {
log.Fatalf("error writing file %q: %v", filePath, err)
if c.createSymlink && j > 0 {
if err := os.Symlink("file0", filePath); err != nil {
log.Fatalf("error creating symlink %q: %v", filePath, err)
}
} else {
if err := os.WriteFile(filePath, data, 0666); err != nil {
log.Fatalf("error writing file %q: %v", filePath, err)
}
}
}
nextDir := filepath.Join(curDir, "dir")