Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore runtime verification #12351

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions cmd/podman/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman-container-restore.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 2 additions & 24 deletions pkg/checkpoint/checkpoint_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@ 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"
ann "github.com/containers/podman/v3/pkg/annotations"
"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"
Expand All @@ -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
Expand All @@ -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
Expand Down
55 changes: 55 additions & 0 deletions pkg/checkpoint/crutils/checkpoint_restore_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
173 changes: 173 additions & 0 deletions test/e2e/checkpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
adrianreber marked this conversation as resolved.
Show resolved Hide resolved
"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)
})
})