mirror of
https://github.com/containers/podman.git
synced 2025-06-25 03:52:15 +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/registry"
|
||||||
"github.com/containers/podman/v3/cmd/podman/validate"
|
"github.com/containers/podman/v3/cmd/podman/validate"
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"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/domain/entities"
|
||||||
"github.com/containers/podman/v3/pkg/parallel"
|
"github.com/containers/podman/v3/pkg/parallel"
|
||||||
"github.com/containers/podman/v3/pkg/rootless"
|
"github.com/containers/podman/v3/pkg/rootless"
|
||||||
@ -114,6 +115,48 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
cfg := registry.PodmanConfig()
|
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
|
// --connection is not as "special" as --remote so we can wait and process it here
|
||||||
conn := cmd.Root().LocalFlags().Lookup("connection")
|
conn := cmd.Root().LocalFlags().Lookup("connection")
|
||||||
if conn != nil && conn.Changed {
|
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.\
|
to import a checkpointed *container* from another host.\
|
||||||
*IMPORTANT: This OPTION does not need a container name or ID as input argument.*
|
*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-previous**=*file*
|
||||||
|
|
||||||
Import a pre-checkpoint tar.gz file which was exported by Podman. This option
|
Import a pre-checkpoint tar.gz file which was exported by Podman. This option
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
metadata "github.com/checkpoint-restore/checkpointctl/lib"
|
metadata "github.com/checkpoint-restore/checkpointctl/lib"
|
||||||
"github.com/checkpoint-restore/go-criu/v5/stats"
|
|
||||||
"github.com/containers/common/libimage"
|
"github.com/containers/common/libimage"
|
||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/podman/v3/libpod"
|
"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/checkpoint/crutils"
|
||||||
"github.com/containers/podman/v3/pkg/criu"
|
"github.com/containers/podman/v3/pkg/criu"
|
||||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
"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/specgen/generate"
|
||||||
"github.com/containers/podman/v3/pkg/specgenutil"
|
"github.com/containers/podman/v3/pkg/specgenutil"
|
||||||
"github.com/containers/storage/pkg/archive"
|
|
||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -30,24 +27,6 @@ import (
|
|||||||
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
|
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
|
||||||
// First get the container definition from the
|
// First get the container definition from the
|
||||||
// tarball to a temporary directory
|
// 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")
|
dir, err := ioutil.TempDir("", "checkpoint")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
logrus.Errorf("Could not recursively remove %s: %q", dir, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = archive.Untar(archiveFile, dir, options)
|
if err := crutils.CRImportCheckpointConfigOnly(dir, restoreOptions.Import); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", restoreOptions.Import)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load spec.dump from temporary directory
|
// Load spec.dump from temporary directory
|
||||||
|
@ -3,11 +3,13 @@ package crutils
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
metadata "github.com/checkpoint-restore/checkpointctl/lib"
|
metadata "github.com/checkpoint-restore/checkpointctl/lib"
|
||||||
|
"github.com/checkpoint-restore/go-criu/v5/stats"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -39,6 +41,36 @@ func CRImportCheckpointWithoutConfig(destination, input string) error {
|
|||||||
return nil
|
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
|
// CRRemoveDeletedFiles loads the list of deleted files and if
|
||||||
// it exists deletes all files listed.
|
// it exists deletes all files listed.
|
||||||
func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error {
|
func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error {
|
||||||
@ -200,3 +232,26 @@ func CRRuntimeSupportsPodCheckpointRestore(runtimePath string) bool {
|
|||||||
out, _ := cmd.CombinedOutput()
|
out, _ := cmd.CombinedOutput()
|
||||||
return bytes.Contains(out, []byte("flag needs an argument"))
|
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(result).Should(Exit(0))
|
||||||
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(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