aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2021-11-22 15:44:10 +0100
committerGitHub <noreply@github.com>2021-11-22 15:44:10 +0100
commit26b45a1564fb01090f6a10776922654641a76681 (patch)
tree84dc1b3a6ab425b201c0e56360dfe3c20d61afd2
parent2f6cdd353f50e6c26b34f0b1bff028e8393d2580 (diff)
parentced0ffbe8f6dae9031a1ae3b48d4f24bca93b5bd (diff)
downloadpodman-26b45a1564fb01090f6a10776922654641a76681.tar.gz
podman-26b45a1564fb01090f6a10776922654641a76681.tar.bz2
podman-26b45a1564fb01090f6a10776922654641a76681.zip
Merge pull request #12351 from adrianreber/2021-11-18-restore-runtime-verification
Restore runtime verification
-rw-r--r--cmd/podman/root.go43
-rw-r--r--docs/source/markdown/podman-container-restore.1.md6
-rw-r--r--pkg/checkpoint/checkpoint_restore.go26
-rw-r--r--pkg/checkpoint/crutils/checkpoint_restore_utils.go55
-rw-r--r--test/e2e/checkpoint_test.go173
5 files changed, 279 insertions, 24 deletions
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 9e4c8d24d..bccc559ce 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -15,6 +15,7 @@ import (
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/cmd/podman/validate"
"github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/pkg/checkpoint/crutils"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/parallel"
"github.com/containers/podman/v3/pkg/rootless"
@@ -114,6 +115,48 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
cfg := registry.PodmanConfig()
+ // Currently it is only possible to restore a container with the same runtime
+ // as used for checkpointing. It should be possible to make crun and runc
+ // compatible to restore a container with another runtime then checkpointed.
+ // Currently that does not work.
+ // To make it easier for users we will look into the checkpoint archive and
+ // set the runtime to the one used during checkpointing.
+ if !registry.IsRemote() && cmd.Name() == "restore" {
+ if cmd.Flag("import").Changed {
+ runtime, err := crutils.CRGetRuntimeFromArchive(cmd.Flag("import").Value.String())
+ if err != nil {
+ return errors.Wrapf(
+ err,
+ "failed extracting runtime information from %s",
+ cmd.Flag("import").Value.String(),
+ )
+ }
+ if cfg.RuntimePath == "" {
+ // If the user did not select a runtime, this takes the one from
+ // the checkpoint archives and tells Podman to use it for the restore.
+ runtimeFlag := cmd.Root().Flags().Lookup("runtime")
+ if runtimeFlag == nil {
+ return errors.Errorf(
+ "Unexcpected error setting runtime to '%s' for restore",
+ *runtime,
+ )
+ }
+ runtimeFlag.Value.Set(*runtime)
+ runtimeFlag.Changed = true
+ logrus.Debugf("Checkpoint was created using '%s'. Restore will use the same runtime", *runtime)
+ } else if cfg.RuntimePath != *runtime {
+ // If the user selected a runtime on the command-line this checks if
+ // it is the same then during checkpointing and errors out if not.
+ return errors.Errorf(
+ "checkpoint archive %s was created with runtime '%s' and cannot be restored with runtime '%s'",
+ cmd.Flag("import").Value.String(),
+ *runtime,
+ cfg.RuntimePath,
+ )
+ }
+ }
+ }
+
// --connection is not as "special" as --remote so we can wait and process it here
conn := cmd.Root().LocalFlags().Lookup("connection")
if conn != nil && conn.Changed {
diff --git a/docs/source/markdown/podman-container-restore.1.md b/docs/source/markdown/podman-container-restore.1.md
index 10477fc77..a4630dedf 100644
--- a/docs/source/markdown/podman-container-restore.1.md
+++ b/docs/source/markdown/podman-container-restore.1.md
@@ -77,6 +77,12 @@ Import a checkpoint tar.gz file, which was exported by Podman. This can be used
to import a checkpointed *container* from another host.\
*IMPORTANT: This OPTION does not need a container name or ID as input argument.*
+During the import of a checkpoint file Podman will select the same container runtime
+which was used during checkpointing. This is especially important if a specific
+(non-default) container runtime was specified during container creation. Podman will
+also abort the restore if the container runtime specified during restore does
+not much the container runtime used for container creation.
+
#### **--import-previous**=*file*
Import a pre-checkpoint tar.gz file which was exported by Podman. This option
diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go
index 85fe6a77e..c371adf5b 100644
--- a/pkg/checkpoint/checkpoint_restore.go
+++ b/pkg/checkpoint/checkpoint_restore.go
@@ -6,7 +6,6 @@ import (
"os"
metadata "github.com/checkpoint-restore/checkpointctl/lib"
- "github.com/checkpoint-restore/go-criu/v5/stats"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod"
@@ -14,10 +13,8 @@ import (
"github.com/containers/podman/v3/pkg/checkpoint/crutils"
"github.com/containers/podman/v3/pkg/criu"
"github.com/containers/podman/v3/pkg/domain/entities"
- "github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/specgen/generate"
"github.com/containers/podman/v3/pkg/specgenutil"
- "github.com/containers/storage/pkg/archive"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -30,24 +27,6 @@ import (
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
// First get the container definition from the
// tarball to a temporary directory
- archiveFile, err := os.Open(restoreOptions.Import)
- if err != nil {
- return nil, errors.Wrap(err, "failed to open checkpoint archive for import")
- }
- defer errorhandling.CloseQuiet(archiveFile)
- options := &archive.TarOptions{
- // Here we only need the files config.dump and spec.dump
- ExcludePatterns: []string{
- "volumes",
- "ctr.log",
- "artifacts",
- stats.StatsDump,
- metadata.RootFsDiffTar,
- metadata.DeletedFilesFile,
- metadata.NetworkStatusFile,
- metadata.CheckpointDirectory,
- },
- }
dir, err := ioutil.TempDir("", "checkpoint")
if err != nil {
return nil, err
@@ -57,9 +36,8 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
logrus.Errorf("Could not recursively remove %s: %q", dir, err)
}
}()
- err = archive.Untar(archiveFile, dir, options)
- if err != nil {
- return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", restoreOptions.Import)
+ if err := crutils.CRImportCheckpointConfigOnly(dir, restoreOptions.Import); err != nil {
+ return nil, err
}
// Load spec.dump from temporary directory
diff --git a/pkg/checkpoint/crutils/checkpoint_restore_utils.go b/pkg/checkpoint/crutils/checkpoint_restore_utils.go
index 3b77368bb..2765d18e8 100644
--- a/pkg/checkpoint/crutils/checkpoint_restore_utils.go
+++ b/pkg/checkpoint/crutils/checkpoint_restore_utils.go
@@ -3,11 +3,13 @@ package crutils
import (
"bytes"
"io"
+ "io/ioutil"
"os"
"os/exec"
"path/filepath"
metadata "github.com/checkpoint-restore/checkpointctl/lib"
+ "github.com/checkpoint-restore/go-criu/v5/stats"
"github.com/containers/storage/pkg/archive"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
@@ -39,6 +41,36 @@ func CRImportCheckpointWithoutConfig(destination, input string) error {
return nil
}
+// CRImportCheckpointConfigOnly only imports the checkpoint configuration
+// from the checkpoint archive (input) into the directory destination.
+// Only the files "config.dump" and "spec.dump" are extracted.
+func CRImportCheckpointConfigOnly(destination, input string) error {
+ archiveFile, err := os.Open(input)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input)
+ }
+
+ defer archiveFile.Close()
+ options := &archive.TarOptions{
+ // Here we only need the files config.dump and spec.dump
+ ExcludePatterns: []string{
+ "volumes",
+ "ctr.log",
+ "artifacts",
+ stats.StatsDump,
+ metadata.RootFsDiffTar,
+ metadata.DeletedFilesFile,
+ metadata.NetworkStatusFile,
+ metadata.CheckpointDirectory,
+ },
+ }
+ if err = archive.Untar(archiveFile, destination, options); err != nil {
+ return errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input)
+ }
+
+ return nil
+}
+
// CRRemoveDeletedFiles loads the list of deleted files and if
// it exists deletes all files listed.
func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error {
@@ -200,3 +232,26 @@ func CRRuntimeSupportsPodCheckpointRestore(runtimePath string) bool {
out, _ := cmd.CombinedOutput()
return bytes.Contains(out, []byte("flag needs an argument"))
}
+
+// CRGetRuntimeFromArchive extracts the checkpoint metadata from the
+// given checkpoint archive and returns the runtime used to create
+// the given checkpoint archive.
+func CRGetRuntimeFromArchive(input string) (*string, error) {
+ dir, err := ioutil.TempDir("", "checkpoint")
+ if err != nil {
+ return nil, err
+ }
+ defer os.RemoveAll(dir)
+
+ if err := CRImportCheckpointConfigOnly(dir, input); err != nil {
+ return nil, err
+ }
+
+ // Load config.dump from temporary directory
+ ctrConfig := new(metadata.ContainerConfig)
+ if _, err = metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil {
+ return nil, err
+ }
+
+ return &ctrConfig.OCIRuntime, nil
+}
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index 6b9a96e9f..e34c07d49 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -1377,4 +1377,177 @@ var _ = Describe("Podman checkpoint", func() {
Expect(result).Should(Exit(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
})
+
+ It("podman checkpoint container with export and verify runtime", func() {
+ SkipIfRemote("podman-remote does not support --runtime flag")
+ localRunString := getRunString([]string{
+ "--rm",
+ ALPINE,
+ "top",
+ })
+ session := podmanTest.Podman(localRunString)
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ cid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{
+ "inspect",
+ "--format",
+ "{{.OCIRuntime}}",
+ cid,
+ })
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ runtime := session.OutputToString()
+
+ fileName := "/tmp/checkpoint-" + cid + ".tar.gz"
+
+ result := podmanTest.Podman([]string{
+ "container",
+ "checkpoint",
+ cid, "-e",
+ fileName,
+ })
+ result.WaitWithDefaultTimeout()
+
+ // As the container has been started with '--rm' it will be completely
+ // cleaned up after checkpointing.
+ Expect(result).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainers()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{
+ "container",
+ "restore",
+ "-i",
+ fileName,
+ })
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+
+ // The restored container should have the same runtime as the original container
+ result = podmanTest.Podman([]string{
+ "inspect",
+ "--format",
+ "{{.OCIRuntime}}",
+ cid,
+ })
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(session.OutputToString()).To(Equal(runtime))
+
+ // Remove exported checkpoint
+ os.Remove(fileName)
+ })
+
+ It("podman checkpoint container with export and try to change the runtime", func() {
+ SkipIfRemote("podman-remote does not support --runtime flag")
+ // This test will only run if runc and crun both exist
+ if !strings.Contains(podmanTest.OCIRuntime, "crun") {
+ Skip("Test requires crun and runc")
+ }
+ cmd := exec.Command("runc")
+ if err := cmd.Start(); err != nil {
+ Skip("Test requires crun and runc")
+ }
+ if err := cmd.Wait(); err != nil {
+ Skip("Test requires crun and runc")
+ }
+ localRunString := getRunString([]string{
+ "--rm",
+ ALPINE,
+ "top",
+ })
+ // Let's start a container with runc and try to restore it with crun (expected to fail)
+ localRunString = append(
+ []string{
+ "--runtime",
+ "runc",
+ },
+ localRunString...,
+ )
+ session := podmanTest.Podman(localRunString)
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ cid := session.OutputToString()
+
+ session = podmanTest.Podman([]string{
+ "inspect",
+ "--format",
+ "{{.OCIRuntime}}",
+ cid,
+ })
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ runtime := session.OutputToString()
+
+ fileName := "/tmp/checkpoint-" + cid + ".tar.gz"
+
+ result := podmanTest.Podman([]string{
+ "container",
+ "checkpoint",
+ cid, "-e",
+ fileName,
+ })
+ result.WaitWithDefaultTimeout()
+
+ // As the container has been started with '--rm' it will be completely
+ // cleaned up after checkpointing.
+ Expect(result).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainers()).To(Equal(0))
+
+ // This should fail as the container was checkpointed with runc
+ result = podmanTest.Podman([]string{
+ "--runtime",
+ "crun",
+ "container",
+ "restore",
+ "-i",
+ fileName,
+ })
+ result.WaitWithDefaultTimeout()
+
+ Expect(result).Should(Exit(125))
+ Expect(result.ErrorToString()).To(
+ ContainSubstring("and cannot be restored with runtime"),
+ )
+
+ result = podmanTest.Podman([]string{
+ "--runtime",
+ "runc",
+ "container",
+ "restore",
+ "-i",
+ fileName,
+ })
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+
+ result = podmanTest.Podman([]string{
+ "inspect",
+ "--format",
+ "{{.OCIRuntime}}",
+ cid,
+ })
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(result.OutputToString()).To(Equal(runtime))
+
+ result = podmanTest.Podman([]string{
+ "--runtime",
+ "runc",
+ "rm",
+ "-fa",
+ })
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ // Remove exported checkpoint
+ os.Remove(fileName)
+ })
})