package integration import ( "bytes" "os" "os/exec" "path/filepath" "regexp" "strings" "testing" "github.com/opencontainers/runc/libcontainer" "golang.org/x/sys/unix" ) func criuFeature(feature string) bool { return exec.Command("criu", "check", "--feature", feature).Run() == nil } func TestUsernsCheckpoint(t *testing.T) { testCheckpoint(t, true) } func TestCheckpoint(t *testing.T) { testCheckpoint(t, false) } func testCheckpoint(t *testing.T, userns bool) { if testing.Short() { return } if _, err := exec.LookPath("criu"); err != nil { t.Skipf("criu binary not found: %v", err) } // Workaround for https://github.com/opencontainers/runc/issues/3532. out, err := exec.Command("rpm", "-q", "criu").CombinedOutput() if err == nil && regexp.MustCompile(`^criu-3\.17-[123]\.el9`).Match(out) { t.Skip("Test requires criu >= 3.17-4 on CentOS Stream 9.") } if userns && !criuFeature("userns") { 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) } }