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:
Adrian Reber
2021-02-25 15:34:12 +00:00
parent bd819ef7dc
commit bf92e21113
4 changed files with 217 additions and 153 deletions

View File

@ -33,6 +33,7 @@ import (
"github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/libpod/events"
"github.com/containers/podman/v3/pkg/annotations" "github.com/containers/podman/v3/pkg/annotations"
"github.com/containers/podman/v3/pkg/cgroups" "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/criu"
"github.com/containers/podman/v3/pkg/lookup" "github.com/containers/podman/v3/pkg/lookup"
"github.com/containers/podman/v3/pkg/resolvconf" "github.com/containers/podman/v3/pkg/resolvconf"
@ -895,69 +896,20 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
includeFiles[0] = "pre-checkpoint" includeFiles[0] = "pre-checkpoint"
} }
// Get root file-system changes included in the checkpoint archive // Get root file-system changes included in the checkpoint archive
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar") var addToTarFiles []string
deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files")
if !options.IgnoreRootfs { if !options.IgnoreRootfs {
// To correctly track deleted files, let's go through the output of 'podman diff' // 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 { if err != nil {
return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath) return errors.Wrapf(err, "error exporting root file-system diff for %q", c.ID())
}
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
}
} }
if len(rootfsIncludeFiles) > 0 { addToTarFiles, err := crutils.CRCreateRootFsDiffTar(&rootFsChanges, c.state.Mountpoint, c.bundlePath())
rootfsTar, err := archive.TarWithOptions(c.state.Mountpoint, &archive.TarOptions{ if err != nil {
Compression: archive.Uncompressed, return err
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)
if err != nil {
return err
}
includeFiles = append(includeFiles, "rootfs-diff.tar")
} }
if len(deletedFiles) > 0 { includeFiles = append(includeFiles, addToTarFiles...)
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")
}
} }
// Folder containing archived volumes that will be included in the export // Folder containing archived volumes that will be included in the export
@ -1034,8 +986,9 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
return err return err
} }
os.Remove(rootfsDiffPath) for _, file := range addToTarFiles {
os.Remove(deleteFilesList) os.Remove(filepath.Join(c.bundlePath(), file))
}
if !options.IgnoreVolumes { if !options.IgnoreVolumes {
os.RemoveAll(expVolDir) os.RemoveAll(expVolDir)
@ -1054,23 +1007,6 @@ func (c *Container) checkpointRestoreSupported() error {
return nil 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 { func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) error {
if err := c.checkpointRestoreSupported(); err != nil { if err := c.checkpointRestoreSupported(); err != nil {
return err 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") 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 return err
} }
@ -1151,28 +1087,13 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
} }
func (c *Container) importCheckpoint(input string) error { func (c *Container) importCheckpoint(input string) error {
archiveFile, err := os.Open(input) if err := crutils.CRImportCheckpointWithoutConfig(c.bundlePath(), input); err != nil {
if err != nil { return err
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)
} }
// Make sure the newly created config.json exists on disk // Make sure the newly created config.json exists on disk
g := generate.Generator{Config: c.config.Spec} 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") 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") 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 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 // Before actually restarting the container, apply the root file-system changes
if !options.IgnoreRootfs { if !options.IgnoreRootfs {
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar") if err := crutils.CRApplyRootFsDiffTar(c.bundlePath(), c.state.Mountpoint); err != nil {
if _, err := os.Stat(rootfsDiffPath); err == nil { return err
// 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 { if err := crutils.CRRemoveDeletedFiles(c.ID(), c.bundlePath(), c.state.Mountpoint); err != nil {
var deletedFiles []string return err
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())
}
}
} }
} }

View File

@ -28,6 +28,7 @@ import (
"github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/logs" "github.com/containers/podman/v3/libpod/logs"
"github.com/containers/podman/v3/pkg/cgroups" "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/errorhandling"
"github.com/containers/podman/v3/pkg/lookup" "github.com/containers/podman/v3/pkg/lookup"
"github.com/containers/podman/v3/pkg/rootless" "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 // SupportsCheckpoint checks if the OCI runtime supports checkpointing
// containers. // containers.
func (r *ConmonOCIRuntime) SupportsCheckpoint() bool { func (r *ConmonOCIRuntime) SupportsCheckpoint() bool {
// Check if the runtime implements checkpointing. Currently only return crutils.CRRuntimeSupportsCheckpointRestore(r.path)
// 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
} }
// SupportsJSONErrors checks if the OCI runtime supports JSON-formatted error // SupportsJSONErrors checks if the OCI runtime supports JSON-formatted error

View File

@ -4,15 +4,14 @@ import (
"context" "context"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
metadata "github.com/checkpoint-restore/checkpointctl/lib"
"github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/libpod/image"
"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/errorhandling"
"github.com/containers/podman/v3/pkg/util" "github.com/containers/podman/v3/pkg/util"
"github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/archive"
jsoniter "github.com/json-iterator/go"
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"
@ -20,21 +19,6 @@ import (
// Prefixing the checkpoint/restore related functions with 'cr' // 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 // CRImportCheckpoint it the function which imports the information
// from checkpoint tarball and re-creates the container from that 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) { 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 // Load spec.dump from temporary directory
dumpSpec := new(spec.Spec) 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 return nil, err
} }
// Load config.dump from temporary directory // Load config.dump from temporary directory
config := new(libpod.ContainerConfig) 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 return nil, err
} }

View 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
}