github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/checkpoint/checkpoint_restore.go (about)

     1  package checkpoint
     2  
     3  import (
     4  	"context"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/containers/podman/v2/libpod"
    10  	"github.com/containers/podman/v2/libpod/image"
    11  	"github.com/containers/podman/v2/pkg/errorhandling"
    12  	"github.com/containers/podman/v2/pkg/util"
    13  	"github.com/containers/storage/pkg/archive"
    14  	jsoniter "github.com/json-iterator/go"
    15  	spec "github.com/opencontainers/runtime-spec/specs-go"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  // Prefixing the checkpoint/restore related functions with 'cr'
    21  
    22  // crImportFromJSON imports the JSON files stored in the exported
    23  // checkpoint tarball
    24  func crImportFromJSON(filePath string, v interface{}) error {
    25  	content, err := ioutil.ReadFile(filePath)
    26  	if err != nil {
    27  		return errors.Wrap(err, "failed to read container definition for restore")
    28  	}
    29  	json := jsoniter.ConfigCompatibleWithStandardLibrary
    30  	if err = json.Unmarshal(content, v); err != nil {
    31  		return errors.Wrapf(err, "failed to unmarshal container definition %s for restore", filePath)
    32  	}
    33  
    34  	return nil
    35  }
    36  
    37  // CRImportCheckpoint it the function which imports the information
    38  // from checkpoint tarball and re-creates the container from that information
    39  func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) {
    40  	// First get the container definition from the
    41  	// tarball to a temporary directory
    42  	archiveFile, err := os.Open(input)
    43  	if err != nil {
    44  		return nil, errors.Wrap(err, "failed to open checkpoint archive for import")
    45  	}
    46  	defer errorhandling.CloseQuiet(archiveFile)
    47  	options := &archive.TarOptions{
    48  		// Here we only need the files config.dump and spec.dump
    49  		ExcludePatterns: []string{
    50  			"checkpoint",
    51  			"artifacts",
    52  			"ctr.log",
    53  			"rootfs-diff.tar",
    54  			"network.status",
    55  			"deleted.files",
    56  		},
    57  	}
    58  	dir, err := ioutil.TempDir("", "checkpoint")
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	defer func() {
    63  		if err := os.RemoveAll(dir); err != nil {
    64  			logrus.Errorf("could not recursively remove %s: %q", dir, err)
    65  		}
    66  	}()
    67  	err = archive.Untar(archiveFile, dir, options)
    68  	if err != nil {
    69  		return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input)
    70  	}
    71  
    72  	// Load spec.dump from temporary directory
    73  	dumpSpec := new(spec.Spec)
    74  	if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), dumpSpec); err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	// Load config.dump from temporary directory
    79  	config := new(libpod.ContainerConfig)
    80  	if err = crImportFromJSON(filepath.Join(dir, "config.dump"), config); err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	// This should not happen as checkpoints with these options are not exported.
    85  	if (len(config.Dependencies) > 0) || (len(config.NamedVolumes) > 0) {
    86  		return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies")
    87  	}
    88  
    89  	ctrID := config.ID
    90  	newName := false
    91  
    92  	// Check if the restored container gets a new name
    93  	if name != "" {
    94  		config.ID = ""
    95  		config.Name = name
    96  		newName = true
    97  	}
    98  
    99  	ctrName := config.Name
   100  
   101  	// The code to load the images is copied from create.go
   102  	// In create.go this only set if '--quiet' does not exist.
   103  	writer := os.Stderr
   104  	rtc, err := runtime.GetConfig()
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	_, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.Engine.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	// Now create a new container from the just loaded information
   115  	container, err := runtime.RestoreContainer(ctx, dumpSpec, config)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var containers []*libpod.Container
   121  	if container == nil {
   122  		return nil, nil
   123  	}
   124  
   125  	containerConfig := container.Config()
   126  	if containerConfig.Name != ctrName {
   127  		return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName)
   128  	}
   129  
   130  	if !newName {
   131  		// Only check ID for a restore with the same name.
   132  		// Using -n to request a new name for the restored container, will also create a new ID
   133  		if containerConfig.ID != ctrID {
   134  			return nil, errors.Errorf("ID of restored container (%s) does not match requested ID (%s)", containerConfig.ID, ctrID)
   135  		}
   136  	}
   137  
   138  	// Check if the ExitCommand points to the correct container ID
   139  	if containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1] != containerConfig.ID {
   140  		return nil, errors.Errorf("'ExitCommandID' uses ID %s instead of container ID %s", containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1], containerConfig.ID)
   141  	}
   142  
   143  	containers = append(containers, container)
   144  	return containers, nil
   145  }