aboutsummaryrefslogtreecommitdiff
path: root/pkg/checkpoint/checkpoint_restore.go
blob: da82c9745f5815a3de3342792f1153207d9f7357 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package checkpoint

import (
	"context"
	"io/ioutil"
	"os"

	metadata "github.com/checkpoint-restore/checkpointctl/lib"
	"github.com/checkpoint-restore/go-criu/v5/stats"
	"github.com/containers/common/libimage"
	"github.com/containers/common/pkg/config"
	"github.com/containers/podman/v3/libpod"
	ann "github.com/containers/podman/v3/pkg/annotations"
	"github.com/containers/podman/v3/pkg/checkpoint/crutils"
	"github.com/containers/podman/v3/pkg/criu"
	"github.com/containers/podman/v3/pkg/domain/entities"
	"github.com/containers/podman/v3/pkg/errorhandling"
	"github.com/containers/podman/v3/pkg/specgen/generate"
	"github.com/containers/storage/pkg/archive"
	spec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// Prefixing the checkpoint/restore related functions with 'cr'

// 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, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
	// First get the container definition from the
	// tarball to a temporary directory
	archiveFile, err := os.Open(restoreOptions.Import)
	if err != nil {
		return nil, errors.Wrap(err, "failed to open checkpoint archive for import")
	}
	defer errorhandling.CloseQuiet(archiveFile)
	options := &archive.TarOptions{
		// Here we only need the files config.dump and spec.dump
		ExcludePatterns: []string{
			"volumes",
			"ctr.log",
			"artifacts",
			stats.StatsDump,
			metadata.RootFsDiffTar,
			metadata.DeletedFilesFile,
			metadata.NetworkStatusFile,
			metadata.CheckpointDirectory,
		},
	}
	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", restoreOptions.Import)
	}

	// Load spec.dump from temporary directory
	dumpSpec := new(spec.Spec)
	if _, err := metadata.ReadJSONFile(dumpSpec, dir, metadata.SpecDumpFile); err != nil {
		return nil, err
	}

	// Load config.dump from temporary directory
	ctrConfig := new(libpod.ContainerConfig)
	if _, err = metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil {
		return nil, err
	}

	if ctrConfig.Pod != "" && restoreOptions.Pod == "" {
		return nil, errors.New("cannot restore pod container without --pod")
	}

	if ctrConfig.Pod == "" && restoreOptions.Pod != "" {
		return nil, errors.New("cannot restore non pod container into pod")
	}

	// This should not happen as checkpoints with these options are not exported.
	if len(ctrConfig.Dependencies) > 0 {
		return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies")
	}

	// Volumes included in the checkpoint should not exist
	if !restoreOptions.IgnoreVolumes {
		for _, vol := range ctrConfig.NamedVolumes {
			exists, err := runtime.HasVolume(vol.Name)
			if err != nil {
				return nil, err
			}
			if exists {
				return nil, errors.Errorf("volume with name %s already exists. Use --ignore-volumes to not restore content of volumes", vol.Name)
			}
		}
	}

	ctrID := ctrConfig.ID
	newName := false

	// Check if the restored container gets a new name
	if restoreOptions.Name != "" {
		ctrConfig.ID = ""
		ctrConfig.Name = restoreOptions.Name
		newName = true
	}

	if restoreOptions.Pod != "" {
		// Restoring into a Pod requires much newer versions of CRIU
		if !criu.CheckForCriu(criu.PodCriuVersion) {
			return nil, errors.Errorf("restoring containers into pods requires at least CRIU %d", criu.PodCriuVersion)
		}
		// The runtime also has to support it
		if !crutils.CRRuntimeSupportsPodCheckpointRestore(runtime.GetOCIRuntimePath()) {
			return nil, errors.Errorf("runtime %s does not support pod restore", runtime.GetOCIRuntimePath())
		}
		// Restoring into an existing Pod
		ctrConfig.Pod = restoreOptions.Pod

		// According to podman pod create a pod can share the following namespaces:
		// cgroup, ipc, net, pid, uts
		// Let's make sure we a restoring into a pod with the same shared namespaces.
		pod, err := runtime.LookupPod(ctrConfig.Pod)
		if err != nil {
			return nil, errors.Wrapf(err, "pod %q cannot be retrieved", ctrConfig.Pod)
		}

		infraContainer, err := pod.InfraContainer()
		if err != nil {
			return nil, errors.Wrapf(err, "cannot retrieve infra container from pod %q", ctrConfig.Pod)
		}

		// If a namespaces was shared (!= "") it needs to be set to the new infrastructure container
		// If the infrastructure container does not share the same namespaces as the to be restored
		// container we abort.
		if ctrConfig.IPCNsCtr != "" {
			if !pod.SharesIPC() {
				return nil, errors.Errorf("pod %s does not share the IPC namespace", ctrConfig.Pod)
			}
			ctrConfig.IPCNsCtr = infraContainer.ID()
		}

		if ctrConfig.NetNsCtr != "" {
			if !pod.SharesNet() {
				return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod)
			}
			ctrConfig.NetNsCtr = infraContainer.ID()
		}

		if ctrConfig.PIDNsCtr != "" {
			if !pod.SharesPID() {
				return nil, errors.Errorf("pod %s does not share the PID namespace", ctrConfig.Pod)
			}
			ctrConfig.PIDNsCtr = infraContainer.ID()
		}

		if ctrConfig.UTSNsCtr != "" {
			if !pod.SharesUTS() {
				return nil, errors.Errorf("pod %s does not share the UTS namespace", ctrConfig.Pod)
			}
			ctrConfig.UTSNsCtr = infraContainer.ID()
		}

		if ctrConfig.CgroupNsCtr != "" {
			if !pod.SharesCgroup() {
				return nil, errors.Errorf("pod %s does not share the cgroup namespace", ctrConfig.Pod)
			}
			ctrConfig.CgroupNsCtr = infraContainer.ID()
		}

		// Change SELinux labels to infrastructure container labels
		ctrConfig.MountLabel = infraContainer.MountLabel()
		ctrConfig.ProcessLabel = infraContainer.ProcessLabel()

		// Fix parent cgroup
		cgroupPath, err := pod.CgroupPath()
		if err != nil {
			return nil, errors.Wrapf(err, "cannot retrieve cgroup path from pod %q", ctrConfig.Pod)
		}
		ctrConfig.CgroupParent = cgroupPath

		oldPodID := dumpSpec.Annotations[ann.SandboxID]
		// Fix up SandboxID in the annotations
		dumpSpec.Annotations[ann.SandboxID] = ctrConfig.Pod
		// Fix up CreateCommand
		for i, c := range ctrConfig.CreateCommand {
			if c == oldPodID {
				ctrConfig.CreateCommand[i] = ctrConfig.Pod
			}
		}
	}

	if len(restoreOptions.PublishPorts) > 0 {
		ports, err := generate.ParsePortMapping(restoreOptions.PublishPorts, nil)
		if err != nil {
			return nil, err
		}
		ctrConfig.PortMappings = ports
	}

	pullOptions := &libimage.PullOptions{}
	pullOptions.Writer = os.Stderr
	if _, err := runtime.LibimageRuntime().Pull(ctx, ctrConfig.RootfsImageName, config.PullPolicyMissing, pullOptions); err != nil {
		return nil, err
	}

	// Now create a new container from the just loaded information
	container, err := runtime.RestoreContainer(ctx, dumpSpec, ctrConfig)
	if err != nil {
		return nil, err
	}

	var containers []*libpod.Container
	if container == nil {
		return nil, nil
	}

	containerConfig := container.Config()
	ctrName := ctrConfig.Name
	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
}