diff --git a/.cirrus.yml b/.cirrus.yml index e63ef51cf1a..bf1757d183c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -79,7 +79,7 @@ task: CIRRUS_WORKING_DIR: /home/runc GO_VERSION: "1.23" BATS_VERSION: "v1.9.0" - RPMS: gcc git iptables jq glibc-static libseccomp-devel make criu fuse-sshfs container-selinux + RPMS: gcc git iptables jq glibc-static libseccomp-devel make fuse-sshfs container-selinux asciidoc gnutls-devel libaio-devel libasan libcap-devel libnet-devel libnl3-devel libselinux-devel make protobuf-c-devel protobuf-devel xmlto libdrm-devel # yamllint disable rule:key-duplicates matrix: DISTRO: almalinux-8 @@ -121,13 +121,17 @@ task: # Find out the latest minor release URL. filename=$(curl -fsSL "${PREFIX}?mode=json&include=all" | jq -r --arg Ver "go$GO_VERSION." '. | map(select(.version | contains($Ver))) | first | .files[] | select(.os == "linux" and .arch == "amd64" and .kind == "archive") | .filename') curl -fsSL "$PREFIX$filename" | tar Cxz /usr/local - # install bats + # Install bats cd /tmp git clone https://github.com/bats-core/bats-core cd bats-core git checkout $BATS_VERSION ./install.sh /usr/local cd - + # Testing https://github.com/checkpoint-restore/criu/pull/2545 + git clone https://github.com/kolyshkin/criu.git ~/criu + (cd ~/criu && git checkout freeze-kludges && sudo make install-criu) + rm -rf ~/criu # Add a user for rootless tests useradd -u2000 -m -d/home/rootless -s/bin/bash rootless # Allow root and rootless itself to execute `ssh rootless@localhost` in tests/rootless.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 754fd87f155..bc1dce42977 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,14 +29,6 @@ jobs: race: ["-race", ""] criu: ["", "criu-dev"] exclude: - # Disable most of criu-dev jobs, as they are expensive - # (need to compile criu) and don't add much value/coverage. - - criu: criu-dev - go-version: 1.22.x - - criu: criu-dev - rootless: rootless - - criu: criu-dev - race: -race - go-version: 1.22.x os: actuated-arm64-6cpu-8gb - race: "-race" @@ -124,8 +116,9 @@ jobs: sudo apt -qy install \ libcap-dev libnet1-dev libnl-3-dev \ libprotobuf-c-dev libprotobuf-dev protobuf-c-compiler protobuf-compiler - git clone https://github.com/checkpoint-restore/criu.git ~/criu - (cd ~/criu && git checkout ${{ matrix.criu }} && sudo make install-criu) + # Testing https://github.com/checkpoint-restore/criu/pull/2545 + git clone https://github.com/kolyshkin/criu.git ~/criu + (cd ~/criu && git checkout freeze-kludges && sudo make install-criu) rm -rf ~/criu - name: install go ${{ matrix.go-version }} diff --git a/Makefile b/Makefile index 39c1ef918d4..dbe86e47d68 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,7 @@ unittest: runcimage .PHONY: localunittest localunittest: test-binaries - $(GO) test -timeout 3m -tags "$(BUILDTAGS)" $(TESTFLAGS) -v ./... + $(GO) test -timeout 10m -tags "$(BUILDTAGS)" $(TESTFLAGS) -v ./... .PHONY: integration integration: runcimage diff --git a/libcontainer/integration/checkpoint_test.go b/libcontainer/integration/checkpoint_test.go index 8d4d6fe4751..03aafd697d9 100644 --- a/libcontainer/integration/checkpoint_test.go +++ b/libcontainer/integration/checkpoint_test.go @@ -6,6 +6,7 @@ import ( "os/exec" "path/filepath" "regexp" + "strconv" "strings" "testing" @@ -17,6 +18,8 @@ func criuFeature(feature string) bool { return exec.Command("criu", "check", "--feature", feature).Run() == nil } +const iter = 150 + func TestUsernsCheckpoint(t *testing.T) { testCheckpoint(t, true) } @@ -44,128 +47,132 @@ func testCheckpoint(t *testing.T, userns bool) { t.Skip("Test requires userns") } - config := newTemplateConfig(t, &tParam{userns: userns}) - stateDir := t.TempDir() - - container, err := libcontainer.Create(stateDir, "test", config) - ok(t, err) - defer destroyContainer(container) - - stdinR, stdinW, err := os.Pipe() - ok(t, err) - - var stdout bytes.Buffer - - pconfig := libcontainer.Process{ - Cwd: "/", - Args: []string{"cat"}, - Env: standardEnvironment, - Stdin: stdinR, - Stdout: &stdout, - Init: true, - } - - err = container.Run(&pconfig) - _ = stdinR.Close() - defer stdinW.Close() //nolint: errcheck - ok(t, err) - - pid, err := pconfig.Pid() - ok(t, err) - - process, err := os.FindProcess(pid) - ok(t, err) - - tmp := t.TempDir() - var parentImage string - - // Test pre-dump if mem_dirty_track is available. - if criuFeature("mem_dirty_track") { - parentImage = "../criu-parent" - parentDir := filepath.Join(tmp, "criu-parent") - preDumpOpts := &libcontainer.CriuOpts{ - ImagesDirectory: parentDir, - WorkDirectory: parentDir, - PreDump: true, - } - - if err := container.Checkpoint(preDumpOpts); err != nil { - t.Fatal(err) - } - - state, err := container.Status() - ok(t, err) - - if state != libcontainer.Running { - t.Fatal("Unexpected preDump state: ", state) - } - } - - imagesDir := filepath.Join(tmp, "criu") - - checkpointOpts := &libcontainer.CriuOpts{ - ImagesDirectory: imagesDir, - WorkDirectory: imagesDir, - ParentImage: parentImage, - } - - if err := container.Checkpoint(checkpointOpts); err != nil { - t.Fatal(err) - } - - state, err := container.Status() - ok(t, err) - - if state != libcontainer.Stopped { - t.Fatal("Unexpected state checkpoint: ", state) - } - - _ = stdinW.Close() - _, err = process.Wait() - ok(t, err) - - // reload the container - container, err = libcontainer.Load(stateDir, "test") - ok(t, err) - - restoreStdinR, restoreStdinW, err := os.Pipe() - ok(t, err) - - var restoreStdout bytes.Buffer - restoreProcessConfig := &libcontainer.Process{ - Cwd: "/", - Stdin: restoreStdinR, - Stdout: &restoreStdout, - Init: true, - } - - err = container.Restore(restoreProcessConfig, checkpointOpts) - _ = restoreStdinR.Close() - defer restoreStdinW.Close() //nolint: errcheck - if err != nil { - t.Fatal(err) - } - - state, err = container.Status() - ok(t, err) - if state != libcontainer.Running { - t.Fatal("Unexpected restore state: ", state) - } - - pid, err = restoreProcessConfig.Pid() - ok(t, err) - - err = unix.Kill(pid, 0) - ok(t, err) - - _, err = restoreStdinW.WriteString("Hello!") - ok(t, err) - - _ = restoreStdinW.Close() - waitProcess(restoreProcessConfig, t) - - output := restoreStdout.String() - if !strings.Contains(output, "Hello!") { - t.Fatal("Did not restore the pipe correctly:", output) + for i := range iter { + t.Run(strconv.Itoa(i), func(t *testing.T) { + config := newTemplateConfig(t, &tParam{userns: userns}) + stateDir := t.TempDir() + + container, err := libcontainer.Create(stateDir, "test", config) + ok(t, err) + defer destroyContainer(container) + + stdinR, stdinW, err := os.Pipe() + ok(t, err) + + var stdout bytes.Buffer + + pconfig := libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + Stdout: &stdout, + Init: true, + } + + err = container.Run(&pconfig) + _ = stdinR.Close() + defer stdinW.Close() //nolint: errcheck + ok(t, err) + + pid, err := pconfig.Pid() + ok(t, err) + + process, err := os.FindProcess(pid) + ok(t, err) + + tmp := t.TempDir() + var parentImage string + + // Test pre-dump if mem_dirty_track is available. + if criuFeature("mem_dirty_track") { + parentImage = "../criu-parent" + parentDir := filepath.Join(tmp, "criu-parent") + preDumpOpts := &libcontainer.CriuOpts{ + ImagesDirectory: parentDir, + WorkDirectory: parentDir, + PreDump: true, + } + + if err := container.Checkpoint(preDumpOpts); err != nil { + t.Fatal(err) + } + + state, err := container.Status() + ok(t, err) + + if state != libcontainer.Running { + t.Fatal("Unexpected preDump state: ", state) + } + } + + imagesDir := filepath.Join(tmp, "criu") + + checkpointOpts := &libcontainer.CriuOpts{ + ImagesDirectory: imagesDir, + WorkDirectory: imagesDir, + ParentImage: parentImage, + } + + if err := container.Checkpoint(checkpointOpts); err != nil { + t.Fatal(err) + } + + state, err := container.Status() + ok(t, err) + + if state != libcontainer.Stopped { + t.Fatal("Unexpected state checkpoint: ", state) + } + + _ = stdinW.Close() + _, err = process.Wait() + ok(t, err) + + // reload the container + container, err = libcontainer.Load(stateDir, "test") + ok(t, err) + + restoreStdinR, restoreStdinW, err := os.Pipe() + ok(t, err) + + var restoreStdout bytes.Buffer + restoreProcessConfig := &libcontainer.Process{ + Cwd: "/", + Stdin: restoreStdinR, + Stdout: &restoreStdout, + Init: true, + } + + err = container.Restore(restoreProcessConfig, checkpointOpts) + _ = restoreStdinR.Close() + defer restoreStdinW.Close() //nolint: errcheck + if err != nil { + t.Fatal(err) + } + + state, err = container.Status() + ok(t, err) + if state != libcontainer.Running { + t.Fatal("Unexpected restore state: ", state) + } + + pid, err = restoreProcessConfig.Pid() + ok(t, err) + + err = unix.Kill(pid, 0) + ok(t, err) + + _, err = restoreStdinW.WriteString("Hello!") + ok(t, err) + + _ = restoreStdinW.Close() + waitProcess(restoreProcessConfig, t) + + output := restoreStdout.String() + if !strings.Contains(output, "Hello!") { + t.Fatal("Did not restore the pipe correctly:", output) + } + }) } }