mirror of
https://github.com/containers/podman.git
synced 2025-06-24 03:08:13 +08:00
Merge pull request #12351 from adrianreber/2021-11-18-restore-runtime-verification
Restore runtime verification
This commit is contained in:
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user