From e20ba5fa51e5a7bcd38965513317a1dd6b7dfac9 Mon Sep 17 00:00:00 2001 From: Ace-Tang Date: Fri, 23 Nov 2018 20:45:38 +0800 Subject: [PATCH] test: add test for c/r without image add test for both v1, v2 runtime Signed-off-by: Ace-Tang --- container_checkpoint_test.go | 125 ++++++++++++++++++++++++++++++++++- task_opts.go | 61 +++++++++++++++++ 2 files changed, 185 insertions(+), 1 deletion(-) diff --git a/container_checkpoint_test.go b/container_checkpoint_test.go index a120ce4c4..2e5203a5a 100644 --- a/container_checkpoint_test.go +++ b/container_checkpoint_test.go @@ -22,16 +22,19 @@ import ( "bytes" "fmt" "io" + "io/ioutil" + "os" + "path/filepath" "strings" "sync" "syscall" "testing" + "github.com/containerd/containerd/cio" "github.com/containerd/containerd/oci" ) const ( - v1runtime = "io.containerd.runtime.v1.linux" testCheckpointName = "checkpoint-test:latest" ) @@ -408,3 +411,123 @@ func TestCheckpointLeaveRunning(t *testing.T) { <-statusC } + +func TestCRWithImagePath(t *testing.T) { + if !supportsCriu { + t.Skip("system does not have criu installed") + } + + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + ctx, cancel = testContext() + id = t.Name() + "-checkpoint" + ) + defer cancel() + + image, err := client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) + } + + container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("top"))) + if err != nil { + t.Fatal(err) + } + defer container.Delete(ctx, WithSnapshotCleanup) + + task, err := container.NewTask(ctx, empty()) + if err != nil { + t.Fatal(err) + } + statusC, err := task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + if err := task.Start(ctx); err != nil { + t.Fatal(err) + } + + // create image path store criu image files + crDir, err := ioutil.TempDir("", "test-cr") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(crDir) + imagePath := filepath.Join(crDir, "cr") + // checkpoint task + if _, err := task.Checkpoint(ctx, WithCheckpointImagePath(client.runtime, imagePath)); err != nil { + t.Fatal(err) + } + + if err := task.Kill(ctx, syscall.SIGKILL); err != nil { + t.Fatal(err) + } + <-statusC + task.Delete(ctx) + + // check image files have been dumped into image path + if files, err := ioutil.ReadDir(imagePath); err != nil || len(files) == 0 { + t.Fatal("failed to checkpoint with image path set") + } + + // restore task with same container image and checkpoint directory, + // the restore process should finish in millisecond level + id = t.Name() + "-restore" + ncontainer, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image))) + if err != nil { + t.Fatal(err) + } + defer ncontainer.Delete(ctx, WithSnapshotCleanup) + + ntask, err := ncontainer.NewTask(ctx, empty(), WithRestoreImagePath(client.runtime, imagePath)) + if err != nil { + t.Fatal(err) + } + statusC, err = ntask.Wait(ctx) + if err != nil { + t.Fatal(err) + } + if err := ntask.Start(ctx); err != nil { + t.Fatal(err) + } + + // check top process is existed in restored container + spec, err := container.Spec(ctx) + if err != nil { + t.Fatal(err) + } + + stdout := bytes.NewBuffer(nil) + spec.Process.Args = []string{"ps", "-ef"} + process, err := ntask.Exec(ctx, t.Name()+"_exec", spec.Process, cio.NewCreator(withByteBuffers(stdout))) + if err != nil { + t.Fatal(err) + } + processStatusC, err := process.Wait(ctx) + if err != nil { + t.Fatal(err) + } + if err := process.Start(ctx); err != nil { + t.Fatal(err) + } + <-processStatusC + if _, err := process.Delete(ctx); err != nil { + t.Fatal(err) + } + + if !strings.Contains(stdout.String(), "top") { + t.Errorf("except top process exists in restored container but not, got output %s", stdout.String()) + } + + // we wrote the same thing after attach + if err := ntask.Kill(ctx, syscall.SIGKILL); err != nil { + t.Fatal(err) + } + <-statusC + ntask.Delete(ctx) +} diff --git a/task_opts.go b/task_opts.go index ce861ea51..714a152c5 100644 --- a/task_opts.go +++ b/task_opts.go @@ -27,11 +27,18 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/runtime/linux/runctypes" + "github.com/containerd/containerd/runtime/v2/runc/options" imagespec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) +const ( + v1runtime = "io.containerd.runtime.v1.linux" + v2runtime = "io.containerd.runc.v1" +) + // NewTaskOpts allows the caller to set options on a new task type NewTaskOpts func(context.Context, *Client, *TaskInfo) error @@ -89,6 +96,60 @@ func WithCheckpointName(name string) CheckpointTaskOpts { } } +// WithCheckpointImagePath sets image path for checkpoint option +func WithCheckpointImagePath(rt, path string) CheckpointTaskOpts { + return func(r *CheckpointTaskInfo) error { + switch rt { + case v1runtime: + if r.Options == nil { + r.Options = &runctypes.CheckpointOptions{} + } + opts, ok := r.Options.(*runctypes.CheckpointOptions) + if !ok { + return errors.New("invalid v1 checkpoint options format") + } + opts.ImagePath = path + case v2runtime: + if r.Options == nil { + r.Options = &options.CheckpointOptions{} + } + opts, ok := r.Options.(*options.CheckpointOptions) + if !ok { + return errors.New("invalid v2 checkpoint options format") + } + opts.ImagePath = path + } + return nil + } +} + +// WithRestoreImagePath sets image path for create option +func WithRestoreImagePath(rt, path string) NewTaskOpts { + return func(ctx context.Context, c *Client, ti *TaskInfo) error { + switch rt { + case v1runtime: + if ti.Options == nil { + ti.Options = &runctypes.CreateOptions{} + } + opts, ok := ti.Options.(*runctypes.CreateOptions) + if !ok { + return errors.New("invalid v1 create options format") + } + opts.CriuImagePath = path + case v2runtime: + if ti.Options == nil { + ti.Options = &options.Options{} + } + opts, ok := ti.Options.(*options.Options) + if !ok { + return errors.New("invalid v2 create options format") + } + opts.CriuImagePath = path + } + return nil + } +} + // ProcessDeleteOpts allows the caller to set options for the deletion of a task type ProcessDeleteOpts func(context.Context, Process) error