Files
podman/pkg/adapter/checkpoint_restore.go
Adrian Reber bef83c42ea migration: add possibility to restore a container with a new name
The option to restore a container from an external checkpoint archive
(podman container restore -i /tmp/checkpoint.tar.gz) restores a
container with the same name and same ID as id had before checkpointing.

This commit adds the option '--name,-n' to 'podman container restore'.
With this option the restored container gets the name specified after
'--name,-n' and a new ID. This way it is possible to restore one
container multiple times.

If a container is restored with a new name Podman will not try to
request the same IP address for the container as it had during
checkpointing. This implicitly assumes that if a container is restored
from a checkpoint archive with a different name, that it will be
restored multiple times and restoring a container multiple times with
the same IP address will fail as each IP address can only be used once.

Signed-off-by: Adrian Reber <areber@redhat.com>
2019-06-04 14:02:51 +02:00

146 lines
4.4 KiB
Go

// +build !remoteclient
package adapter
import (
"context"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/storage/pkg/archive"
jsoniter "github.com/json-iterator/go"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"io"
"io/ioutil"
"os"
"path/filepath"
)
// 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 jsonFile.Close()
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([]byte(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 archiveFile.Close()
options := &archive.TarOptions{
// Here we only need the files config.dump and spec.dump
ExcludePatterns: []string{
"checkpoint",
"artifacts",
"ctr.log",
"network.status",
},
}
dir, err := ioutil.TempDir("", "checkpoint")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir)
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
spec := new(spec.Spec)
if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), spec); 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
var writer io.Writer
// 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{}, false, nil)
if err != nil {
return nil, err
}
// Now create a new container from the just loaded information
container, err := runtime.RestoreContainer(ctx, spec, 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 == false {
// 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
}