diff --git a/.travis.yml b/.travis.yml index 92b620f24..fcbf729f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,9 +46,9 @@ install: - sudo PATH=$PATH GOPATH=$GOPATH script/setup/install-runc - sudo PATH=$PATH GOPATH=$GOPATH script/setup/install-cni - sudo PATH=$PATH GOPATH=$GOPATH script/setup/install-critools - - wget https://github.com/xemul/criu/archive/v3.0.tar.gz -O /tmp/criu.tar.gz + - wget https://github.com/checkpoint-restore/criu/archive/v3.7.tar.gz -O /tmp/criu.tar.gz - tar -C /tmp/ -zxf /tmp/criu.tar.gz - - cd /tmp/criu-3.0 && sudo make install-criu + - cd /tmp/criu-3.7 && sudo make install-criu - cd $TRAVIS_BUILD_DIR script: diff --git a/cmd/ctr/commands/tasks/tasks_unix.go b/cmd/ctr/commands/tasks/tasks_unix.go index ef563eec8..f7b111410 100644 --- a/cmd/ctr/commands/tasks/tasks_unix.go +++ b/cmd/ctr/commands/tasks/tasks_unix.go @@ -69,25 +69,24 @@ func HandleConsoleResize(ctx gocontext.Context, task resizer, con console.Consol // NewTask creates a new task func NewTask(ctx gocontext.Context, client *containerd.Client, container containerd.Container, checkpoint string, tty, nullIO bool, ioOpts []cio.Opt, opts ...containerd.NewTaskOpts) (containerd.Task, error) { stdio := cio.NewCreator(append([]cio.Opt{cio.WithStdio}, ioOpts...)...) - if checkpoint == "" { - ioCreator := stdio + if checkpoint != "" { + im, err := client.GetImage(ctx, checkpoint) + if err != nil { + return nil, err + } + opts = append(opts, containerd.WithTaskCheckpoint(im)) + } + ioCreator := stdio + if tty { + ioCreator = cio.NewCreator(append([]cio.Opt{cio.WithStdio, cio.WithTerminal}, ioOpts...)...) + } + if nullIO { if tty { - ioCreator = cio.NewCreator(append([]cio.Opt{cio.WithStdio, cio.WithTerminal}, ioOpts...)...) + return nil, errors.New("tty and null-io cannot be used together") } - if nullIO { - if tty { - return nil, errors.New("tty and null-io cannot be used together") - } - ioCreator = cio.NullIO - } - return container.NewTask(ctx, ioCreator, opts...) + ioCreator = cio.NullIO } - im, err := client.GetImage(ctx, checkpoint) - if err != nil { - return nil, err - } - opts = append(opts, containerd.WithTaskCheckpoint(im)) - return container.NewTask(ctx, stdio, opts...) + return container.NewTask(ctx, ioCreator, opts...) } func getNewTaskOpts(context *cli.Context) []containerd.NewTaskOpts { diff --git a/container_checkpoint_test.go b/container_checkpoint_test.go index df26dfd15..6b1858afa 100644 --- a/container_checkpoint_test.go +++ b/container_checkpoint_test.go @@ -1,4 +1,4 @@ -// +build !windows +// +build linux /* Copyright The containerd Authors. @@ -19,12 +19,122 @@ package containerd import ( + "bytes" + "fmt" + "io" + "strings" + "sync" "syscall" "testing" "github.com/containerd/containerd/oci" ) +func TestCheckpointRestorePTY(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() + ) + defer cancel() + + image, err := client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) + } + container, err := client.NewContainer(ctx, id, + WithNewSpec(oci.WithImageConfig(image), + oci.WithProcessArgs("sh", "-c", "read A; echo z${A}z"), + oci.WithTTY), + WithNewSnapshot(id, image)) + if err != nil { + t.Fatal(err) + } + defer container.Delete(ctx, WithSnapshotCleanup) + + direct, err := newDirectIOWithTerminal(ctx) + if err != nil { + t.Fatal(err) + } + defer direct.Delete() + + task, err := container.NewTask(ctx, direct.IOCreate) + if err != nil { + t.Fatal(err) + } + defer task.Delete(ctx) + + statusC, err := task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + + if err := task.Start(ctx); err != nil { + t.Fatal(err) + } + + checkpoint, err := task.Checkpoint(ctx, WithExit) + if err != nil { + t.Fatal(err) + } + + <-statusC + + if _, err := task.Delete(ctx); err != nil { + t.Fatal(err) + } + direct.Delete() + direct, err = newDirectIOWithTerminal(ctx) + if err != nil { + t.Fatal(err) + } + + var ( + wg sync.WaitGroup + buf = bytes.NewBuffer(nil) + ) + wg.Add(1) + go func() { + defer wg.Done() + io.Copy(buf, direct.Stdout) + }() + + if task, err = container.NewTask(ctx, direct.IOCreate, + WithTaskCheckpoint(checkpoint)); 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) + } + + direct.Stdin.Write([]byte("hello\n")) + <-statusC + wg.Wait() + + if err := direct.Close(); err != nil { + t.Error(err) + } + + out := buf.String() + if !strings.Contains(fmt.Sprintf("%#q", out), `zhelloz`) { + t.Fatalf(`expected \x00 in output: %s`, out) + } +} + func TestCheckpointRestore(t *testing.T) { if !supportsCriu { t.Skip("system does not have criu installed") diff --git a/linux/proc/init_state.go b/linux/proc/init_state.go index 8944d6192..0cbdc7ac1 100644 --- a/linux/proc/init_state.go +++ b/linux/proc/init_state.go @@ -194,10 +194,23 @@ func (s *createdCheckpointState) Start(ctx context.Context) error { s.p.mu.Lock() defer s.p.mu.Unlock() p := s.p + sio := p.stdio + + var ( + err error + socket *runc.Socket + ) + if sio.Terminal { + if socket, err = runc.NewTempConsoleSocket(); err != nil { + return errors.Wrap(err, "failed to create OCI runtime console socket") + } + defer socket.Close() + s.opts.ConsoleSocket = socket + } + if _, err := s.p.runtime.Restore(ctx, p.id, p.bundle, s.opts); err != nil { return p.runtimeError(err, "OCI runtime restore failed") } - sio := p.stdio if sio.Stdin != "" { sc, err := fifo.OpenFifo(ctx, sio.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) if err != nil { @@ -207,7 +220,17 @@ func (s *createdCheckpointState) Start(ctx context.Context) error { p.closers = append(p.closers, sc) } var copyWaitGroup sync.WaitGroup - if !sio.IsNull() { + if socket != nil { + console, err := socket.ReceiveMaster() + if err != nil { + return errors.Wrap(err, "failed to retrieve console master") + } + console, err = p.platform.CopyConsole(ctx, console, sio.Stdin, sio.Stdout, sio.Stderr, &p.wg, ©WaitGroup) + if err != nil { + return errors.Wrap(err, "failed to start console copy") + } + p.console = console + } else if !sio.IsNull() { if err := copyPipes(ctx, p.io, sio.Stdin, sio.Stdout, sio.Stderr, &p.wg, ©WaitGroup); err != nil { return errors.Wrap(err, "failed to start io pipe copy") } @@ -219,7 +242,6 @@ func (s *createdCheckpointState) Start(ctx context.Context) error { return errors.Wrap(err, "failed to retrieve OCI runtime container pid") } p.pid = pid - return s.transition("running") } diff --git a/vendor.conf b/vendor.conf index ec53b1d8c..60b972316 100644 --- a/vendor.conf +++ b/vendor.conf @@ -1,4 +1,4 @@ -github.com/containerd/go-runc bcb223a061a3dd7de1a89c0b402a60f4dd9bd307 +github.com/containerd/go-runc f271fa2021de855d4d918dbef83c5fe19db1bdd5 github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925 github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130 github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788 diff --git a/vendor/github.com/containerd/go-runc/console.go b/vendor/github.com/containerd/go-runc/console.go index e81730075..dc7b82ea0 100644 --- a/vendor/github.com/containerd/go-runc/console.go +++ b/vendor/github.com/containerd/go-runc/console.go @@ -43,7 +43,7 @@ func NewConsoleSocket(path string) (*Socket, error) { return nil, err } return &Socket{ - l: l, + l: l, }, nil } diff --git a/vendor/github.com/containerd/go-runc/runc.go b/vendor/github.com/containerd/go-runc/runc.go index 74d77c192..e4cf98e8e 100644 --- a/vendor/github.com/containerd/go-runc/runc.go +++ b/vendor/github.com/containerd/go-runc/runc.go @@ -512,10 +512,11 @@ type RestoreOpts struct { CheckpointOpts IO - Detach bool - PidFile string - NoSubreaper bool - NoPivot bool + Detach bool + PidFile string + NoSubreaper bool + NoPivot bool + ConsoleSocket ConsoleSocket } func (o *RestoreOpts) args() ([]string, error) { @@ -530,6 +531,9 @@ func (o *RestoreOpts) args() ([]string, error) { } out = append(out, "--pid-file", abs) } + if o.ConsoleSocket != nil { + out = append(out, "--console-socket", o.ConsoleSocket.Path()) + } if o.NoPivot { out = append(out, "--no-pivot") }