podman
234 строки · 7.5 Кб
1//go:build !remote
2
3package checkpoint
4
5import (
6"context"
7"errors"
8"fmt"
9"os"
10
11metadata "github.com/checkpoint-restore/checkpointctl/lib"
12"github.com/containers/common/libimage"
13"github.com/containers/common/pkg/config"
14"github.com/containers/podman/v5/libpod"
15ann "github.com/containers/podman/v5/pkg/annotations"
16"github.com/containers/podman/v5/pkg/checkpoint/crutils"
17"github.com/containers/podman/v5/pkg/criu"
18"github.com/containers/podman/v5/pkg/domain/entities"
19"github.com/containers/podman/v5/pkg/specgen/generate"
20"github.com/containers/podman/v5/pkg/specgenutil"
21spec "github.com/opencontainers/runtime-spec/specs-go"
22"github.com/sirupsen/logrus"
23)
24
25// Prefixing the checkpoint/restore related functions with 'cr'
26
27func CRImportCheckpointTar(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
28// First get the container definition from the
29// tarball to a temporary directory
30dir, err := os.MkdirTemp("", "checkpoint")
31if err != nil {
32return nil, err
33}
34defer func() {
35if err := os.RemoveAll(dir); err != nil {
36logrus.Errorf("Could not recursively remove %s: %q", dir, err)
37}
38}()
39if err := crutils.CRImportCheckpointConfigOnly(dir, restoreOptions.Import); err != nil {
40return nil, err
41}
42return CRImportCheckpoint(ctx, runtime, restoreOptions, dir)
43}
44
45// CRImportCheckpoint it the function which imports the information
46// from checkpoint tarball and re-creates the container from that information
47func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions, dir string) ([]*libpod.Container, error) {
48// Load spec.dump from temporary directory
49dumpSpec := new(spec.Spec)
50if _, err := metadata.ReadJSONFile(dumpSpec, dir, metadata.SpecDumpFile); err != nil {
51return nil, err
52}
53
54// Load config.dump from temporary directory
55ctrConfig := new(libpod.ContainerConfig)
56if _, err := metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil {
57return nil, err
58}
59
60if ctrConfig.Pod != "" && restoreOptions.Pod == "" {
61return nil, errors.New("cannot restore pod container without --pod")
62}
63
64if ctrConfig.Pod == "" && restoreOptions.Pod != "" {
65return nil, errors.New("cannot restore non pod container into pod")
66}
67
68// This should not happen as checkpoints with these options are not exported.
69if len(ctrConfig.Dependencies) > 0 {
70return nil, errors.New("cannot import checkpoints of containers with dependencies")
71}
72
73// Volumes included in the checkpoint should not exist
74if !restoreOptions.IgnoreVolumes {
75for _, vol := range ctrConfig.NamedVolumes {
76exists, err := runtime.HasVolume(vol.Name)
77if err != nil {
78return nil, err
79}
80if exists {
81return nil, fmt.Errorf("volume with name %s already exists. Use --ignore-volumes to not restore content of volumes", vol.Name)
82}
83}
84}
85
86ctrID := ctrConfig.ID
87newName := false
88
89// Check if the restored container gets a new name
90if restoreOptions.Name != "" {
91ctrConfig.ID = ""
92ctrConfig.Name = restoreOptions.Name
93newName = true
94}
95
96if restoreOptions.Pod != "" {
97// Restoring into a Pod requires much newer versions of CRIU
98if err := criu.CheckForCriu(criu.PodCriuVersion); err != nil {
99return nil, fmt.Errorf("restoring containers into pod: %w", err)
100}
101// The runtime also has to support it
102if !crutils.CRRuntimeSupportsPodCheckpointRestore(runtime.GetOCIRuntimePath()) {
103return nil, fmt.Errorf("runtime %s does not support pod restore", runtime.GetOCIRuntimePath())
104}
105// Restoring into an existing Pod
106ctrConfig.Pod = restoreOptions.Pod
107
108// According to podman pod create a pod can share the following namespaces:
109// cgroup, ipc, net, pid, uts
110// Let's make sure we are restoring into a pod with the same shared namespaces.
111pod, err := runtime.LookupPod(ctrConfig.Pod)
112if err != nil {
113return nil, fmt.Errorf("pod %q cannot be retrieved: %w", ctrConfig.Pod, err)
114}
115
116infraContainer, err := pod.InfraContainer()
117if err != nil {
118return nil, fmt.Errorf("cannot retrieve infra container from pod %q: %w", ctrConfig.Pod, err)
119}
120
121// If a namespace was shared (!= "") it needs to be set to the new infrastructure container.
122// If the infrastructure container does not share the same namespaces as the to be restored
123// container we abort.
124if ctrConfig.IPCNsCtr != "" {
125if !pod.SharesIPC() {
126return nil, fmt.Errorf("pod %s does not share the IPC namespace", ctrConfig.Pod)
127}
128ctrConfig.IPCNsCtr = infraContainer.ID()
129}
130
131if ctrConfig.NetNsCtr != "" {
132if !pod.SharesNet() {
133return nil, fmt.Errorf("pod %s does not share the network namespace", ctrConfig.Pod)
134}
135ctrConfig.NetNsCtr = infraContainer.ID()
136for net, opts := range ctrConfig.Networks {
137opts.StaticIPs = nil
138opts.StaticMAC = nil
139ctrConfig.Networks[net] = opts
140}
141ctrConfig.StaticIP = nil
142ctrConfig.StaticMAC = nil
143}
144
145if ctrConfig.PIDNsCtr != "" {
146if !pod.SharesPID() {
147return nil, fmt.Errorf("pod %s does not share the PID namespace", ctrConfig.Pod)
148}
149ctrConfig.PIDNsCtr = infraContainer.ID()
150}
151
152if ctrConfig.UTSNsCtr != "" {
153if !pod.SharesUTS() {
154return nil, fmt.Errorf("pod %s does not share the UTS namespace", ctrConfig.Pod)
155}
156ctrConfig.UTSNsCtr = infraContainer.ID()
157}
158
159if ctrConfig.CgroupNsCtr != "" {
160if !pod.SharesCgroup() {
161return nil, fmt.Errorf("pod %s does not share the cgroup namespace", ctrConfig.Pod)
162}
163ctrConfig.CgroupNsCtr = infraContainer.ID()
164}
165
166// Change SELinux labels to infrastructure container labels
167ctrConfig.MountLabel = infraContainer.MountLabel()
168ctrConfig.ProcessLabel = infraContainer.ProcessLabel()
169
170// Fix parent cgroup
171cgroupPath, err := pod.CgroupPath()
172if err != nil {
173return nil, fmt.Errorf("cannot retrieve cgroup path from pod %q: %w", ctrConfig.Pod, err)
174}
175ctrConfig.CgroupParent = cgroupPath
176
177oldPodID := dumpSpec.Annotations[ann.SandboxID]
178// Fix up SandboxID in the annotations
179dumpSpec.Annotations[ann.SandboxID] = ctrConfig.Pod
180// Fix up CreateCommand
181for i, c := range ctrConfig.CreateCommand {
182if c == oldPodID {
183ctrConfig.CreateCommand[i] = ctrConfig.Pod
184}
185}
186}
187
188if len(restoreOptions.PublishPorts) > 0 {
189pubPorts, err := specgenutil.CreatePortBindings(restoreOptions.PublishPorts)
190if err != nil {
191return nil, err
192}
193
194ports, err := generate.ParsePortMapping(pubPorts, nil)
195if err != nil {
196return nil, err
197}
198ctrConfig.PortMappings = ports
199}
200
201pullOptions := &libimage.PullOptions{}
202pullOptions.Writer = os.Stderr
203if _, err := runtime.LibimageRuntime().Pull(ctx, ctrConfig.RootfsImageName, config.PullPolicyMissing, pullOptions); err != nil {
204return nil, err
205}
206
207// Now create a new container from the just loaded information
208container, err := runtime.RestoreContainer(ctx, dumpSpec, ctrConfig)
209if err != nil {
210return nil, err
211}
212
213var containers []*libpod.Container
214if container == nil {
215return nil, nil
216}
217
218containerConfig := container.Config()
219ctrName := ctrConfig.Name
220if containerConfig.Name != ctrName {
221return nil, fmt.Errorf("name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName)
222}
223
224if !newName {
225// Only check ID for a restore with the same name.
226// Using -n to request a new name for the restored container, will also create a new ID
227if containerConfig.ID != ctrID {
228return nil, fmt.Errorf("ID of restored container (%s) does not match requested ID (%s)", containerConfig.ID, ctrID)
229}
230}
231
232containers = append(containers, container)
233return containers, nil
234}
235