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