mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
Move checkpoint/restore code to pkg/checkpoint/crutils
To be able to reuse common checkpoint/restore functions this commit moves code to pkg/checkpoint/crutils. This commit has not functional changes. It only moves code around. [NO TESTS NEEDED] - only moving code around Signed-off-by: Adrian Reber <areber@redhat.com>
This commit is contained in:
@ -33,6 +33,7 @@ import (
|
||||
"github.com/containers/podman/v3/libpod/events"
|
||||
"github.com/containers/podman/v3/pkg/annotations"
|
||||
"github.com/containers/podman/v3/pkg/cgroups"
|
||||
"github.com/containers/podman/v3/pkg/checkpoint/crutils"
|
||||
"github.com/containers/podman/v3/pkg/criu"
|
||||
"github.com/containers/podman/v3/pkg/lookup"
|
||||
"github.com/containers/podman/v3/pkg/resolvconf"
|
||||
@ -895,69 +896,20 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
|
||||
includeFiles[0] = "pre-checkpoint"
|
||||
}
|
||||
// Get root file-system changes included in the checkpoint archive
|
||||
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
|
||||
deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files")
|
||||
var addToTarFiles []string
|
||||
if !options.IgnoreRootfs {
|
||||
// To correctly track deleted files, let's go through the output of 'podman diff'
|
||||
tarFiles, err := c.runtime.GetDiff("", c.ID())
|
||||
rootFsChanges, err := c.runtime.GetDiff("", c.ID())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
|
||||
}
|
||||
var rootfsIncludeFiles []string
|
||||
var deletedFiles []string
|
||||
|
||||
for _, file := range tarFiles {
|
||||
if file.Kind == archive.ChangeAdd {
|
||||
rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
|
||||
continue
|
||||
}
|
||||
if file.Kind == archive.ChangeDelete {
|
||||
deletedFiles = append(deletedFiles, file.Path)
|
||||
continue
|
||||
}
|
||||
fileName, err := os.Stat(file.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !fileName.IsDir() && file.Kind == archive.ChangeModify {
|
||||
rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
|
||||
continue
|
||||
}
|
||||
return errors.Wrapf(err, "error exporting root file-system diff for %q", c.ID())
|
||||
}
|
||||
|
||||
if len(rootfsIncludeFiles) > 0 {
|
||||
rootfsTar, err := archive.TarWithOptions(c.state.Mountpoint, &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
IncludeSourceDir: true,
|
||||
IncludeFiles: rootfsIncludeFiles,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
|
||||
}
|
||||
rootfsDiffFile, err := os.Create(rootfsDiffPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath)
|
||||
}
|
||||
defer rootfsDiffFile.Close()
|
||||
_, err = io.Copy(rootfsDiffFile, rootfsTar)
|
||||
addToTarFiles, err := crutils.CRCreateRootFsDiffTar(&rootFsChanges, c.state.Mountpoint, c.bundlePath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
includeFiles = append(includeFiles, "rootfs-diff.tar")
|
||||
}
|
||||
|
||||
if len(deletedFiles) > 0 {
|
||||
formatJSON, err := json.MarshalIndent(deletedFiles, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating delete files list file %q", deleteFilesList)
|
||||
}
|
||||
if err := ioutil.WriteFile(deleteFilesList, formatJSON, 0600); err != nil {
|
||||
return errors.Wrap(err, "error creating delete files list file")
|
||||
}
|
||||
|
||||
includeFiles = append(includeFiles, "deleted.files")
|
||||
}
|
||||
includeFiles = append(includeFiles, addToTarFiles...)
|
||||
}
|
||||
|
||||
// Folder containing archived volumes that will be included in the export
|
||||
@ -1034,8 +986,9 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
os.Remove(rootfsDiffPath)
|
||||
os.Remove(deleteFilesList)
|
||||
for _, file := range addToTarFiles {
|
||||
os.Remove(filepath.Join(c.bundlePath(), file))
|
||||
}
|
||||
|
||||
if !options.IgnoreVolumes {
|
||||
os.RemoveAll(expVolDir)
|
||||
@ -1054,23 +1007,6 @@ func (c *Container) checkpointRestoreSupported() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) checkpointRestoreLabelLog(fileName string) error {
|
||||
// Create the CRIU log file and label it
|
||||
dumpLog := filepath.Join(c.bundlePath(), fileName)
|
||||
|
||||
logFile, err := os.OpenFile(dumpLog, os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create CRIU log file")
|
||||
}
|
||||
if err := logFile.Close(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err = label.SetFileLabel(dumpLog, c.MountLabel()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) error {
|
||||
if err := c.checkpointRestoreSupported(); err != nil {
|
||||
return err
|
||||
@ -1084,7 +1020,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
|
||||
return errors.Errorf("cannot checkpoint containers that have been started with '--rm' unless '--export' is used")
|
||||
}
|
||||
|
||||
if err := c.checkpointRestoreLabelLog("dump.log"); err != nil {
|
||||
if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "dump.log", c.MountLabel()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1151,28 +1087,13 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
|
||||
}
|
||||
|
||||
func (c *Container) importCheckpoint(input string) error {
|
||||
archiveFile, err := os.Open(input)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open checkpoint archive for import")
|
||||
}
|
||||
|
||||
defer archiveFile.Close()
|
||||
options := &archive.TarOptions{
|
||||
ExcludePatterns: []string{
|
||||
// config.dump and spec.dump are only required
|
||||
// container creation
|
||||
"config.dump",
|
||||
"spec.dump",
|
||||
},
|
||||
}
|
||||
err = archive.Untar(archiveFile, c.bundlePath(), options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unpacking of checkpoint archive %s failed", input)
|
||||
if err := crutils.CRImportCheckpointWithoutConfig(c.bundlePath(), input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure the newly created config.json exists on disk
|
||||
g := generate.Generator{Config: c.config.Spec}
|
||||
if err = c.saveSpec(g.Config); err != nil {
|
||||
if err := c.saveSpec(g.Config); err != nil {
|
||||
return errors.Wrap(err, "saving imported container specification for restore failed")
|
||||
}
|
||||
|
||||
@ -1221,7 +1142,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
||||
return errors.Wrapf(err, "a complete checkpoint for this container cannot be found, cannot restore")
|
||||
}
|
||||
|
||||
if err := c.checkpointRestoreLabelLog("restore.log"); err != nil {
|
||||
if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "restore.log", c.MountLabel()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1398,36 +1319,12 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
||||
|
||||
// Before actually restarting the container, apply the root file-system changes
|
||||
if !options.IgnoreRootfs {
|
||||
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
|
||||
if _, err := os.Stat(rootfsDiffPath); err == nil {
|
||||
// Only do this if a rootfs-diff.tar actually exists
|
||||
rootfsDiffFile, err := os.Open(rootfsDiffPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open root file-system diff file")
|
||||
}
|
||||
defer rootfsDiffFile.Close()
|
||||
if err := c.runtime.ApplyDiffTarStream(c.ID(), rootfsDiffFile); err != nil {
|
||||
return errors.Wrapf(err, "failed to apply root file-system diff file %s", rootfsDiffPath)
|
||||
}
|
||||
}
|
||||
deletedFilesPath := filepath.Join(c.bundlePath(), "deleted.files")
|
||||
if _, err := os.Stat(deletedFilesPath); err == nil {
|
||||
var deletedFiles []string
|
||||
deletedFilesJSON, err := ioutil.ReadFile(deletedFilesPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read deleted files file")
|
||||
}
|
||||
if err := json.Unmarshal(deletedFilesJSON, &deletedFiles); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal deleted files file %s", deletedFilesPath)
|
||||
}
|
||||
for _, deleteFile := range deletedFiles {
|
||||
// Using RemoveAll as deletedFiles, which is generated from 'podman diff'
|
||||
// lists completely deleted directories as a single entry: 'D /root'.
|
||||
err = os.RemoveAll(filepath.Join(c.state.Mountpoint, deleteFile))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete files from container %s during restore", c.ID())
|
||||
}
|
||||
if err := crutils.CRApplyRootFsDiffTar(c.bundlePath(), c.state.Mountpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := crutils.CRRemoveDeletedFiles(c.ID(), c.bundlePath(), c.state.Mountpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
"github.com/containers/podman/v3/libpod/logs"
|
||||
"github.com/containers/podman/v3/pkg/cgroups"
|
||||
"github.com/containers/podman/v3/pkg/checkpoint/crutils"
|
||||
"github.com/containers/podman/v3/pkg/errorhandling"
|
||||
"github.com/containers/podman/v3/pkg/lookup"
|
||||
"github.com/containers/podman/v3/pkg/rootless"
|
||||
@ -837,16 +838,7 @@ func (r *ConmonOCIRuntime) CheckConmonRunning(ctr *Container) (bool, error) {
|
||||
// SupportsCheckpoint checks if the OCI runtime supports checkpointing
|
||||
// containers.
|
||||
func (r *ConmonOCIRuntime) SupportsCheckpoint() bool {
|
||||
// Check if the runtime implements checkpointing. Currently only
|
||||
// runc's checkpoint/restore implementation is supported.
|
||||
cmd := exec.Command(r.path, "checkpoint", "--help")
|
||||
if err := cmd.Start(); err != nil {
|
||||
return false
|
||||
}
|
||||
if err := cmd.Wait(); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return crutils.CRRuntimeSupportsCheckpointRestore(r.path)
|
||||
}
|
||||
|
||||
// SupportsJSONErrors checks if the OCI runtime supports JSON-formatted error
|
||||
|
@ -4,15 +4,14 @@ import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
metadata "github.com/checkpoint-restore/checkpointctl/lib"
|
||||
"github.com/containers/podman/v3/libpod"
|
||||
"github.com/containers/podman/v3/libpod/image"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/errorhandling"
|
||||
"github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -20,21 +19,6 @@ import (
|
||||
|
||||
// Prefixing the checkpoint/restore related functions with 'cr'
|
||||
|
||||
// crImportFromJSON imports the JSON files stored in the exported
|
||||
// checkpoint tarball
|
||||
func crImportFromJSON(filePath string, v interface{}) error {
|
||||
content, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read container definition for restore")
|
||||
}
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err = json.Unmarshal(content, v); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal container definition %s for restore", filePath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CRImportCheckpoint it the function which imports the information
|
||||
// from checkpoint tarball and re-creates the container from that information
|
||||
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
|
||||
@ -73,13 +57,13 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
|
||||
|
||||
// Load spec.dump from temporary directory
|
||||
dumpSpec := new(spec.Spec)
|
||||
if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), dumpSpec); err != nil {
|
||||
if _, err := metadata.ReadJSONFile(dumpSpec, dir, metadata.SpecDumpFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load config.dump from temporary directory
|
||||
config := new(libpod.ContainerConfig)
|
||||
if err = crImportFromJSON(filepath.Join(dir, "config.dump"), config); err != nil {
|
||||
if _, err = metadata.ReadJSONFile(config, dir, metadata.ConfigDumpFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
191
pkg/checkpoint/crutils/checkpoint_restore_utils.go
Normal file
191
pkg/checkpoint/crutils/checkpoint_restore_utils.go
Normal file
@ -0,0 +1,191 @@
|
||||
package crutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
metadata "github.com/checkpoint-restore/checkpointctl/lib"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// This file mainly exist to make the checkpoint/restore functions
|
||||
// available for other users. One possible candidate would be CRI-O.
|
||||
|
||||
// CRImportCheckpointWithoutConfig imports the checkpoint archive (input)
|
||||
// into the directory destination without "config.dump" and "spec.dump"
|
||||
func CRImportCheckpointWithoutConfig(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{
|
||||
ExcludePatterns: []string{
|
||||
// Import everything else besides the container config
|
||||
metadata.ConfigDumpFile,
|
||||
metadata.SpecDumpFile,
|
||||
},
|
||||
}
|
||||
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 {
|
||||
deletedFiles, _, err := metadata.ReadContainerCheckpointDeletedFiles(baseDirectory)
|
||||
if os.IsNotExist(errors.Unwrap(errors.Unwrap(err))) {
|
||||
// No files to delete. Just return
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read deleted files file")
|
||||
}
|
||||
|
||||
for _, deleteFile := range deletedFiles {
|
||||
// Using RemoveAll as deletedFiles, which is generated from 'podman diff'
|
||||
// lists completely deleted directories as a single entry: 'D /root'.
|
||||
if err := os.RemoveAll(filepath.Join(containerRootDirectory, deleteFile)); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete files from container %s during restore", id)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CRApplyRootFsDiffTar applies the tar archive found in baseDirectory with the
|
||||
// root file system changes on top of containerRootDirectory
|
||||
func CRApplyRootFsDiffTar(baseDirectory, containerRootDirectory string) error {
|
||||
rootfsDiffPath := filepath.Join(baseDirectory, metadata.RootFsDiffTar)
|
||||
if _, err := os.Stat(rootfsDiffPath); err != nil {
|
||||
// Only do this if a rootfs-diff.tar actually exists
|
||||
return nil
|
||||
}
|
||||
|
||||
rootfsDiffFile, err := os.Open(rootfsDiffPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open root file-system diff file")
|
||||
}
|
||||
defer rootfsDiffFile.Close()
|
||||
|
||||
if err := archive.Untar(rootfsDiffFile, containerRootDirectory, nil); err != nil {
|
||||
return errors.Wrapf(err, "failed to apply root file-system diff file %s", rootfsDiffPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CRCreateRootFsDiffTar goes through the 'changes' and can create two files:
|
||||
// * metadata.RootFsDiffTar will contain all new and changed files
|
||||
// * metadata.DeletedFilesFile will contain a list of deleted files
|
||||
// With these two files it is possible to restore the container file system to the same
|
||||
// state it was during checkpointing.
|
||||
// Changes to directories (owner, mode) are not handled.
|
||||
func CRCreateRootFsDiffTar(changes *[]archive.Change, mountPoint, destination string) (includeFiles []string, err error) {
|
||||
if len(*changes) == 0 {
|
||||
return includeFiles, nil
|
||||
}
|
||||
|
||||
var rootfsIncludeFiles []string
|
||||
var deletedFiles []string
|
||||
|
||||
rootfsDiffPath := filepath.Join(destination, metadata.RootFsDiffTar)
|
||||
|
||||
for _, file := range *changes {
|
||||
if file.Kind == archive.ChangeAdd {
|
||||
rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
|
||||
continue
|
||||
}
|
||||
if file.Kind == archive.ChangeDelete {
|
||||
deletedFiles = append(deletedFiles, file.Path)
|
||||
continue
|
||||
}
|
||||
fileName, err := os.Stat(file.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !fileName.IsDir() && file.Kind == archive.ChangeModify {
|
||||
rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(rootfsIncludeFiles) > 0 {
|
||||
rootfsTar, err := archive.TarWithOptions(mountPoint, &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
IncludeSourceDir: true,
|
||||
IncludeFiles: rootfsIncludeFiles,
|
||||
})
|
||||
if err != nil {
|
||||
return includeFiles, errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
|
||||
}
|
||||
rootfsDiffFile, err := os.Create(rootfsDiffPath)
|
||||
if err != nil {
|
||||
return includeFiles, errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath)
|
||||
}
|
||||
defer rootfsDiffFile.Close()
|
||||
if _, err = io.Copy(rootfsDiffFile, rootfsTar); err != nil {
|
||||
return includeFiles, err
|
||||
}
|
||||
|
||||
includeFiles = append(includeFiles, metadata.RootFsDiffTar)
|
||||
}
|
||||
|
||||
if len(deletedFiles) == 0 {
|
||||
return includeFiles, nil
|
||||
}
|
||||
|
||||
if _, err := metadata.WriteJSONFile(deletedFiles, destination, metadata.DeletedFilesFile); err != nil {
|
||||
return includeFiles, nil
|
||||
}
|
||||
|
||||
includeFiles = append(includeFiles, metadata.DeletedFilesFile)
|
||||
|
||||
return includeFiles, nil
|
||||
}
|
||||
|
||||
// CRCreateFileWithLabel creates an empty file and sets the corresponding ('fileLabel')
|
||||
// SELinux label on the file.
|
||||
// This is necessary for CRIU log files because CRIU infects the processes in
|
||||
// the container with a 'parasite' and this will also try to write to the log files
|
||||
// from the context of the container processes.
|
||||
func CRCreateFileWithLabel(directory, fileName, fileLabel string) error {
|
||||
logFileName := filepath.Join(directory, fileName)
|
||||
|
||||
logFile, err := os.OpenFile(logFileName, os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create file %q", logFileName)
|
||||
}
|
||||
defer logFile.Close()
|
||||
if err = label.SetFileLabel(logFileName, fileLabel); err != nil {
|
||||
return errors.Wrapf(err, "failed to label file %q", logFileName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CRRuntimeSupportsCheckpointRestore tests if the given runtime at 'runtimePath'
|
||||
// supports checkpointing. The checkpoint restore interface has no definition
|
||||
// but crun implements all commands just as runc does. Whathh runc does it the
|
||||
// official definition of the checkpoint/restore interface.
|
||||
func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool {
|
||||
// Check if the runtime implements checkpointing. Currently only
|
||||
// runc's and crun's checkpoint/restore implementation is supported.
|
||||
cmd := exec.Command(runtimePath, "checkpoint", "--help")
|
||||
if err := cmd.Start(); err != nil {
|
||||
return false
|
||||
}
|
||||
if err := cmd.Wait(); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
Reference in New Issue
Block a user