X Tutup
Skip to content

Commit 06e04bc

Browse files
authored
Merge pull request containerd#2830 from Ace-Tang/support_cr_without_image
cr: support checkpoint/restore without image
2 parents 9b366b2 + d4ecb00 commit 06e04bc

File tree

15 files changed

+855
-103
lines changed

15 files changed

+855
-103
lines changed

cmd/ctr/commands/tasks/checkpoint.go

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ var checkpointCommand = cli.Command{
3636
Name: "exit",
3737
Usage: "stop the container after the checkpoint",
3838
},
39+
cli.StringFlag{
40+
Name: "image-path",
41+
Usage: "path to criu image files",
42+
},
43+
cli.StringFlag{
44+
Name: "work-path",
45+
Usage: "path to criu work files and logs",
46+
},
3947
},
4048
Action: func(context *cli.Context) error {
4149
id := context.Args().First()
@@ -59,40 +67,55 @@ var checkpointCommand = cli.Command{
5967
if err != nil {
6068
return err
6169
}
62-
var opts []containerd.CheckpointTaskOpts
63-
if context.Bool("exit") {
64-
opts = append(opts, withExit(info.Runtime.Name))
65-
}
70+
opts := []containerd.CheckpointTaskOpts{withCheckpointOpts(info.Runtime.Name, context)}
6671
checkpoint, err := task.Checkpoint(ctx, opts...)
6772
if err != nil {
6873
return err
6974
}
70-
fmt.Println(checkpoint.Name())
75+
if context.String("image-path") == "" {
76+
fmt.Println(checkpoint.Name())
77+
}
7178
return nil
7279
},
7380
}
7481

75-
func withExit(rt string) containerd.CheckpointTaskOpts {
82+
// withCheckpointOpts only suitable for runc runtime now
83+
func withCheckpointOpts(rt string, context *cli.Context) containerd.CheckpointTaskOpts {
7684
return func(r *containerd.CheckpointTaskInfo) error {
85+
imagePath := context.String("image-path")
86+
workPath := context.String("work-path")
87+
7788
switch rt {
7889
case "io.containerd.runc.v1":
7990
if r.Options == nil {
80-
r.Options = &options.CheckpointOptions{
81-
Exit: true,
82-
}
83-
} else {
84-
opts, _ := r.Options.(*options.CheckpointOptions)
91+
r.Options = &options.CheckpointOptions{}
92+
}
93+
opts, _ := r.Options.(*options.CheckpointOptions)
94+
95+
if context.Bool("exit") {
8596
opts.Exit = true
8697
}
87-
default:
98+
if imagePath != "" {
99+
opts.ImagePath = imagePath
100+
}
101+
if workPath != "" {
102+
opts.WorkPath = workPath
103+
}
104+
case "io.containerd.runtime.v1.linux":
88105
if r.Options == nil {
89-
r.Options = &runctypes.CheckpointOptions{
90-
Exit: true,
91-
}
92-
} else {
93-
opts, _ := r.Options.(*runctypes.CheckpointOptions)
106+
r.Options = &runctypes.CheckpointOptions{}
107+
}
108+
opts, _ := r.Options.(*runctypes.CheckpointOptions)
109+
110+
if context.Bool("exit") {
94111
opts.Exit = true
95112
}
113+
if imagePath != "" {
114+
opts.ImagePath = imagePath
115+
}
116+
if workPath != "" {
117+
opts.WorkPath = workPath
118+
}
96119
}
97120
return nil
98121
}

container_checkpoint_test.go

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@ import (
2222
"bytes"
2323
"fmt"
2424
"io"
25+
"io/ioutil"
26+
"os"
27+
"path/filepath"
2528
"strings"
2629
"sync"
2730
"syscall"
2831
"testing"
2932

33+
"github.com/containerd/containerd/cio"
3034
"github.com/containerd/containerd/oci"
3135
)
3236

3337
const (
34-
v1runtime = "io.containerd.runtime.v1.linux"
3538
testCheckpointName = "checkpoint-test:latest"
3639
)
3740

@@ -408,3 +411,123 @@ func TestCheckpointLeaveRunning(t *testing.T) {
408411

409412
<-statusC
410413
}
414+
415+
func TestCRWithImagePath(t *testing.T) {
416+
if !supportsCriu {
417+
t.Skip("system does not have criu installed")
418+
}
419+
420+
client, err := newClient(t, address)
421+
if err != nil {
422+
t.Fatal(err)
423+
}
424+
defer client.Close()
425+
426+
var (
427+
ctx, cancel = testContext()
428+
id = t.Name() + "-checkpoint"
429+
)
430+
defer cancel()
431+
432+
image, err := client.GetImage(ctx, testImage)
433+
if err != nil {
434+
t.Fatal(err)
435+
}
436+
437+
container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("top")))
438+
if err != nil {
439+
t.Fatal(err)
440+
}
441+
defer container.Delete(ctx, WithSnapshotCleanup)
442+
443+
task, err := container.NewTask(ctx, empty())
444+
if err != nil {
445+
t.Fatal(err)
446+
}
447+
statusC, err := task.Wait(ctx)
448+
if err != nil {
449+
t.Fatal(err)
450+
}
451+
if err := task.Start(ctx); err != nil {
452+
t.Fatal(err)
453+
}
454+
455+
// create image path store criu image files
456+
crDir, err := ioutil.TempDir("", "test-cr")
457+
if err != nil {
458+
t.Fatal(err)
459+
}
460+
defer os.RemoveAll(crDir)
461+
imagePath := filepath.Join(crDir, "cr")
462+
// checkpoint task
463+
if _, err := task.Checkpoint(ctx, WithCheckpointImagePath(client.runtime, imagePath)); err != nil {
464+
t.Fatal(err)
465+
}
466+
467+
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
468+
t.Fatal(err)
469+
}
470+
<-statusC
471+
task.Delete(ctx)
472+
473+
// check image files have been dumped into image path
474+
if files, err := ioutil.ReadDir(imagePath); err != nil || len(files) == 0 {
475+
t.Fatal("failed to checkpoint with image path set")
476+
}
477+
478+
// restore task with same container image and checkpoint directory,
479+
// the restore process should finish in millisecond level
480+
id = t.Name() + "-restore"
481+
ncontainer, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image)))
482+
if err != nil {
483+
t.Fatal(err)
484+
}
485+
defer ncontainer.Delete(ctx, WithSnapshotCleanup)
486+
487+
ntask, err := ncontainer.NewTask(ctx, empty(), WithRestoreImagePath(client.runtime, imagePath))
488+
if err != nil {
489+
t.Fatal(err)
490+
}
491+
statusC, err = ntask.Wait(ctx)
492+
if err != nil {
493+
t.Fatal(err)
494+
}
495+
if err := ntask.Start(ctx); err != nil {
496+
t.Fatal(err)
497+
}
498+
499+
// check top process is existed in restored container
500+
spec, err := container.Spec(ctx)
501+
if err != nil {
502+
t.Fatal(err)
503+
}
504+
505+
stdout := bytes.NewBuffer(nil)
506+
spec.Process.Args = []string{"ps", "-ef"}
507+
process, err := ntask.Exec(ctx, t.Name()+"_exec", spec.Process, cio.NewCreator(withByteBuffers(stdout)))
508+
if err != nil {
509+
t.Fatal(err)
510+
}
511+
processStatusC, err := process.Wait(ctx)
512+
if err != nil {
513+
t.Fatal(err)
514+
}
515+
if err := process.Start(ctx); err != nil {
516+
t.Fatal(err)
517+
}
518+
<-processStatusC
519+
if _, err := process.Delete(ctx); err != nil {
520+
t.Fatal(err)
521+
}
522+
523+
if !strings.Contains(stdout.String(), "top") {
524+
t.Errorf("except top process exists in restored container but not, got output %s", stdout.String())
525+
}
526+
527+
// we wrote the same thing after attach
528+
if err := ntask.Kill(ctx, syscall.SIGKILL); err != nil {
529+
t.Fatal(err)
530+
}
531+
<-statusC
532+
ntask.Delete(ctx)
533+
}

runtime/linux/runctypes/next.pb.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ file {
112112
type: TYPE_UINT32
113113
json_name: "ioGid"
114114
}
115+
field {
116+
name: "criu_work_path"
117+
number: 12
118+
label: LABEL_OPTIONAL
119+
type: TYPE_STRING
120+
json_name: "criuWorkPath"
121+
}
122+
field {
123+
name: "criu_image_path"
124+
number: 13
125+
label: LABEL_OPTIONAL
126+
type: TYPE_STRING
127+
json_name: "criuImagePath"
128+
}
115129
}
116130
message_type {
117131
name: "CheckpointOptions"
@@ -164,6 +178,20 @@ file {
164178
type: TYPE_STRING
165179
json_name: "cgroupsMode"
166180
}
181+
field {
182+
name: "work_path"
183+
number: 8
184+
label: LABEL_OPTIONAL
185+
type: TYPE_STRING
186+
json_name: "workPath"
187+
}
188+
field {
189+
name: "image_path"
190+
number: 9
191+
label: LABEL_OPTIONAL
192+
type: TYPE_STRING
193+
json_name: "imagePath"
194+
}
167195
}
168196
message_type {
169197
name: "ProcessDetails"

0 commit comments

Comments
 (0)
X Tutup