test: add test for c/r without image

add test for both v1, v2 runtime

Signed-off-by: Ace-Tang <aceapril@126.com>
This commit is contained in:
Ace-Tang 2018-11-23 20:45:38 +08:00
parent 6593399e9f
commit e20ba5fa51
2 changed files with 185 additions and 1 deletions

View File

@ -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)
}

View File

@ -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