diff --git a/e2e/actions/actions.go b/e2e/actions/actions.go index 2249f86ee3..b0464c1270 100644 --- a/e2e/actions/actions.go +++ b/e2e/actions/actions.go @@ -2584,14 +2584,15 @@ func E2ETests(env e2e.TestEnv) testhelper.Tests { // // OCI Runtime Mode // - "ociRun": c.actionOciRun, // singularity run --oci - "ociExec": c.actionOciExec, // singularity exec --oci - "ociShell": c.actionOciShell, // singularity shell --oci - "ociNetwork": c.actionOciNetwork, // singularity exec --oci --net - "ociBinds": c.actionOciBinds, // singularity exec --oci --bind / --mount - "ociCdi": c.actionOciCdi, // singularity exec --oci --cdi - "ociIDMaps": c.actionOciIDMaps, // check uid/gid mapping on host for --oci as user / --fakeroot - "ociCompat": np(c.actionOciCompat), // --oci equivalence to native mode --compat - "ociOverlay": (c.actionOciOverlay), // --overlay in OCI mode + "ociRun": c.actionOciRun, // singularity run --oci + "ociExec": c.actionOciExec, // singularity exec --oci + "ociShell": c.actionOciShell, // singularity shell --oci + "ociNetwork": c.actionOciNetwork, // singularity exec --oci --net + "ociBinds": c.actionOciBinds, // singularity exec --oci --bind / --mount + "ociCdi": c.actionOciCdi, // singularity exec --oci --cdi + "ociIDMaps": c.actionOciIDMaps, // check uid/gid mapping on host for --oci as user / --fakeroot + "ociCompat": np(c.actionOciCompat), // --oci equivalence to native mode --compat + "ociOverlay": (c.actionOciOverlay), // --overlay in OCI mode + "ociOverlayTeardown": np(c.actionOciOverlayTeardown), // proper overlay unmounting in OCI mode } } diff --git a/e2e/actions/oci.go b/e2e/actions/oci.go index 92869f362f..c04953c627 100644 --- a/e2e/actions/oci.go +++ b/e2e/actions/oci.go @@ -6,6 +6,7 @@ package actions import ( + "bufio" "encoding/json" "fmt" "os" @@ -19,6 +20,7 @@ import ( cdispecs "github.com/container-orchestrated-devices/container-device-interface/specs-go" "github.com/sylabs/singularity/e2e/internal/e2e" "github.com/sylabs/singularity/internal/pkg/util/fs" + "gotest.tools/v3/assert" ) func (c actionTests) actionOciRun(t *testing.T) { @@ -1074,3 +1076,56 @@ func (c actionTests) actionOciOverlay(t *testing.T) { }) } } + +// actionOciOverlayTeardown checks that OCI-mode overlays are correctly +// unmounted even in root mode (i.e., when user namespaces are not involved). +func (c actionTests) actionOciOverlayTeardown(t *testing.T) { + e2e.EnsureOCIArchive(t, c.env) + imageRef := "oci-archive:" + c.env.OCIArchivePath + + mountInfoPath := "/proc/self/mountinfo" + numMountLinesPre, err := countLines(mountInfoPath) + if err != nil { + t.Fatal(err) + } + + tmpDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "oci_overlay_teardown-", "") + t.Cleanup(func() { + if !t.Failed() { + cleanup(t) + } + }) + + c.env.RunSingularity( + t, + e2e.WithProfile(e2e.OCIRootProfile), + e2e.WithCommand("exec"), + e2e.WithArgs("--overlay", tmpDir+":ro", imageRef, "/bin/true"), + e2e.ExpectExit(0), + ) + + numMountLinesPost, err := countLines(mountInfoPath) + if err != nil { + t.Fatal(err) + } + + assert.Equal( + t, numMountLinesPost, numMountLinesPre, + "Number of mounts after running in OCI-mode with overlays (%d) does not match the number before the run (%d)", numMountLinesPost, numMountLinesPre) +} + +func countLines(path string) (int, error) { + file, err := os.Open(path) + if err != nil { + return -1, err + } + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + lines := 0 + for scanner.Scan() { + lines++ + } + file.Close() + + return lines, nil +}