diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7a16ba74d36..4634cc0eb02 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -429,6 +429,10 @@ jobs: matrix: mode: ["rootful", "rootless"] runs-on: ubuntu-24.04 + env: + MODE: ${{ matrix.mode }} + # FIXME: this is only necessary to access the build cache. To remove with build cleanup. + CONTAINERD_VERSION: v2.0.3 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -490,20 +494,44 @@ jobs: echo "DOCKER_HOST=${DOCKER_HOST}" >>$GITHUB_ENV docker info docker version + - name: "Expose GitHub Runtime variables for gha" + uses: crazy-max/ghaction-github-runtime@b3a9207c0e1ef41f4cf215303c976869d0c2c1c4 # v3.0.0 - name: "Prepare integration tests" - env: - MODE: ${{ matrix.mode }} run: | set -eux - TARGET=test-integration - [ "$MODE" = "rootless" ] && TARGET=test-integration-rootless - docker build -t test-integration --target "${TARGET}" . - # losetup not working as expected on EL8? - # > === FAIL: cmd/nerdctl/container TestRunDevice (0.44s) - # > container_run_cgroup_linux_test.go:236: assertion failed: error is not nil: loopback setup failed ([losetup --find --show /tmp/containerd-test-loopback3931357228]): - # > stdout="", stderr="losetup: /tmp/containerd-test-loopback3931357228: failed to set up loop device: No such file or directory\n": exit status 1 - # https://github.com/containerd/nerdctl/pull/3904#issuecomment-2670917820 + sudo losetup -Dv sudo losetup -lv + + TARGET=test-integration + [ "$MODE" = "rootless" ] && TARGET=test-integration-rootless + docker buildx create --name with-gha --use + docker buildx build \ + --output=type=docker \ + --cache-from type=gha,scope=amd64-${CONTAINERD_VERSION} \ + -t test-integration --target "${TARGET}" \ + . + - name: "Run integration tests" - run: docker run -t --rm --privileged test-integration + # Presumably, something is broken with the way docker exposes /dev to the container, as it appears to only + # randomly work. Mounting /dev does workaround the issue. + # This might be due to the old kernel shipped with Alma (4.18), or something else between centos/docker. + run: | + set -eux + [ "$MODE" = "rootless" ] && { + echo "rootless" + docker run -t -v /dev:/dev --rm --privileged test-integration /test-integration-rootless.sh ./hack/test-integration.sh -test.only-flaky=false + } || { + echo "rootful" + docker run -t -v /dev:/dev --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=false + } + - name: "Run integration tests (flaky)" + run: | + set -eux + [ "$MODE" = "rootless" ] && { + echo "rootless" + docker run -t -v /dev:/dev --rm --privileged test-integration /test-integration-rootless.sh ./hack/test-integration.sh -test.only-flaky=true + } || { + echo "rootful" + docker run -t -v /dev:/dev --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=true + } diff --git a/cmd/nerdctl/container/container_run_cgroup_linux_test.go b/cmd/nerdctl/container/container_run_cgroup_linux_test.go index b70c8936824..edf42711730 100644 --- a/cmd/nerdctl/container/container_run_cgroup_linux_test.go +++ b/cmd/nerdctl/container/container_run_cgroup_linux_test.go @@ -22,20 +22,20 @@ import ( "fmt" "os" "path/filepath" - "strings" + "strconv" "testing" - "github.com/moby/sys/userns" "gotest.tools/v3/assert" "github.com/containerd/cgroups/v3" containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/continuity/testutil/loopback" + "github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/require" + "github.com/containerd/nerdctl/mod/tigron/test" "github.com/containerd/nerdctl/v2/pkg/cmd/container" "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" - "github.com/containerd/nerdctl/v2/pkg/infoutil" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) @@ -224,53 +224,99 @@ func TestIssue3781(t *testing.T) { } func TestRunDevice(t *testing.T) { - if os.Geteuid() != 0 || userns.RunningInUserNS() { - t.Skip("test requires the root in the initial user namespace") - } + testCase := nerdtest.Setup() - if unameR := infoutil.UnameR(); strings.Contains(unameR, ".el8") { - t.Logf("Assuming to be running on EL8 (kernel release %q)", unameR) - t.Skip("FIXME: loopback.New fails on EL8 (when the test is executed inside a container) https://github.com/containerd/nerdctl/pull/3904#issuecomment-2670917820") - // > === FAIL: cmd/nerdctl/container TestRunDevice (0.44s) - // > container_run_cgroup_linux_test.go:236: assertion failed: error is not nil: loopback setup failed ([losetup --find --show /tmp/containerd-test-loopback3931357228]): - // > stdout="", stderr="losetup: /tmp/containerd-test-loopback3931357228: failed to set up loop device: No such file or directory\n": exit status 1 - } + testCase.Require = nerdtest.Rootful const n = 3 lo := make([]*loopback.Loopback, n) - loContent := make([]string, n) - for i := 0; i < n; i++ { - var err error - lo[i], err = loopback.New(4096) - assert.NilError(t, err) - t.Logf("lo[%d] = %+v", i, lo[i]) - defer lo[i].Close() - loContent[i] = fmt.Sprintf("lo%d-content", i) - assert.NilError(t, os.WriteFile(lo[i].Device, []byte(loContent[i]), 0700)) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + + for i := 0; i < n; i++ { + var err error + lo[i], err = loopback.New(4096) + assert.NilError(t, err) + t.Logf("lo[%d] = %+v", i, lo[i]) + loContent := fmt.Sprintf("lo%d-content", i) + assert.NilError(t, os.WriteFile(lo[i].Device, []byte(loContent), 0o700)) + data.Set("loContent"+strconv.Itoa(i), loContent) + } + + // lo0 is readable but not writable. + // lo1 is readable and writable + // lo2 is not accessible. + helpers.Ensure("run", + "-d", + "--name", data.Identifier(), + "--device", lo[0].Device+":r", + "--device", lo[1].Device, + testutil.AlpineImage, "sleep", nerdtest.Infinity) + data.Set("id", data.Identifier()) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + for i := 0; i < n; i++ { + if lo[i] != nil { + _ = lo[i].Close() + } + } + helpers.Anyhow("rm", "-f", data.Identifier()) } - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - defer base.Cmd("rm", "-f", containerName).AssertOK() - // lo0 is readable but not writable. - // lo1 is readable and writable - // lo2 is not accessible. - base.Cmd("run", - "-d", - "--name", containerName, - "--device", lo[0].Device+":r", - "--device", lo[1].Device, - testutil.AlpineImage, "sleep", nerdtest.Infinity).Run() - - base.Cmd("exec", containerName, "cat", lo[0].Device).AssertOutContains(loContent[0]) - base.Cmd("exec", containerName, "cat", lo[1].Device).AssertOutContains(loContent[1]) - base.Cmd("exec", containerName, "cat", lo[2].Device).AssertFail() - base.Cmd("exec", containerName, "sh", "-ec", "echo -n \"overwritten-lo0-content\">"+lo[0].Device).AssertFail() - base.Cmd("exec", containerName, "sh", "-ec", "echo -n \"overwritten-lo1-content\">"+lo[1].Device).AssertOK() - lo1Read, err := os.ReadFile(lo[1].Device) - assert.NilError(t, err) - assert.Equal(t, string(bytes.Trim(lo1Read, "\x00")), "overwritten-lo1-content") + testCase.SubTests = []*test.Case{ + { + Description: "can read lo0", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("exec", data.Get("id"), "cat", lo[0].Device) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: expect.Contains(data.Get("locontent0")), + } + }, + }, + { + Description: "cannot write lo0", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("exec", data.Get("id"), "sh", "-ec", "echo -n \"overwritten-lo1-content\">"+lo[0].Device) + }, + Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), + }, + { + Description: "cannot read lo2", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("exec", data.Get("id"), "cat", lo[2].Device) + }, + Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), + }, + { + Description: "can read lo1", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("exec", data.Get("id"), "cat", lo[1].Device) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: expect.Contains(data.Get("locontent1")), + } + }, + }, + { + Description: "can write lo1 and read back updated value", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("exec", data.Get("id"), "sh", "-ec", "echo -n \"overwritten-lo1-content\">"+lo[1].Device) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, info string, t *testing.T) { + lo1Read, err := os.ReadFile(lo[1].Device) + assert.NilError(t, err) + assert.Equal(t, string(bytes.Trim(lo1Read, "\x00")), "overwritten-lo1-content") + }), + }, + } + + testCase.Run(t) } func TestParseDevice(t *testing.T) { diff --git a/cmd/nerdctl/container/container_stats_test.go b/cmd/nerdctl/container/container_stats_test.go index 897a108228f..0648d502d94 100644 --- a/cmd/nerdctl/container/container_stats_test.go +++ b/cmd/nerdctl/container/container_stats_test.go @@ -18,14 +18,12 @@ package container import ( "runtime" - "strings" "testing" "github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/test" - "github.com/containerd/nerdctl/v2/pkg/infoutil" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) @@ -45,11 +43,6 @@ func TestStats(t *testing.T) { ) } - if unameR := infoutil.UnameR(); strings.Contains(unameR, ".el8") { - t.Logf("Assuming to be running on EL8 (kernel release %q)", unameR) - t.Skip("FIXME: the test seems to hang on EL8: https://github.com/containerd/nerdctl/pull/3904#issuecomment-2693931822") - } - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { helpers.Anyhow("rm", "-f", data.Identifier("container")) helpers.Anyhow("rm", "-f", data.Identifier("memlimited"))