Files
podman/pkg/adapter/checkpoint_restore.go
Adrian Reber 225c7ae6c9 Correctly export the root file-system changes
When doing a checkpoint with --export the root file-system diff was not
working as expected. Instead of getting the changes from the running
container to the highest storage layer it got the changes from the
highest layer to that parent's layer. For a one layer container this
could mean that the complete root file-system is part of the checkpoint.

With this commit this changes to use the same functionality as 'podman
diff'. This actually enables to correctly diff the root file-system
including tracking deleted files.

This also removes the non-working helper functions from libpod/diff.go.

Signed-off-by: Adrian Reber <areber@redhat.com>
2019-12-09 13:29:36 +01:00

154 lines
4.7 KiB
Go

// +build !remoteclient
package adapter
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/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"
)
// 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 {
jsonFile, err := os.Open(filePath)
if err != nil {
return errors.Wrapf(err, "Failed to open container definition %s for restore", filePath)
}
defer errorhandling.CloseQuiet(jsonFile)
content, err := ioutil.ReadAll(jsonFile)
if err != nil {
return errors.Wrapf(err, "Failed to read container definition %s for restore", filePath)
}
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, input string, name string) ([]*libpod.Container, error) {
// First get the container definition from the
// tarball to a temporary directory
archiveFile, err := os.Open(input)
if err != nil {
return nil, errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input)
}
defer errorhandling.CloseQuiet(archiveFile)
options := &archive.TarOptions{
// Here we only need the files config.dump and spec.dump
ExcludePatterns: []string{
"checkpoint",
"artifacts",
"ctr.log",
"rootfs-diff.tar",
"network.status",
"deleted.files",
},
}
dir, err := ioutil.TempDir("", "checkpoint")
if err != nil {
return nil, err
}
defer func() {
if err := os.RemoveAll(dir); err != nil {
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", input)
}
// Load spec.dump from temporary directory
dumpSpec := new(spec.Spec)
if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), dumpSpec); 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 {
return nil, err
}
// This should not happen as checkpoints with these options are not exported.
if (len(config.Dependencies) > 0) || (len(config.NamedVolumes) > 0) {
return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies")
}
ctrID := config.ID
newName := false
// Check if the restored container gets a new name
if name != "" {
config.ID = ""
config.Name = name
newName = true
}
ctrName := config.Name
// The code to load the images is copied from create.go
// In create.go this only set if '--quiet' does not exist.
writer := os.Stderr
rtc, err := runtime.GetConfig()
if err != nil {
return nil, err
}
_, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing)
if err != nil {
return nil, err
}
// Now create a new container from the just loaded information
container, err := runtime.RestoreContainer(ctx, dumpSpec, config)
if err != nil {
return nil, err
}
var containers []*libpod.Container
if container == nil {
return nil, nil
}
containerConfig := container.Config()
if containerConfig.Name != ctrName {
return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName)
}
if !newName {
// Only check ID for a restore with the same name.
// Using -n to request a new name for the restored container, will also create a new ID
if containerConfig.ID != ctrID {
return nil, errors.Errorf("ID of restored container (%s) does not match requested ID (%s)", containerConfig.ID, ctrID)
}
}
// Check if the ExitCommand points to the correct container ID
if containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1] != containerConfig.ID {
return nil, errors.Errorf("'ExitCommandID' uses ID %s instead of container ID %s", containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1], containerConfig.ID)
}
containers = append(containers, container)
return containers, nil
}