Merge pull request #2307 from avagin/tty
Allow to checkpoint and restore a container with console
This commit is contained in:
commit
4219f7ba3a
@ -46,9 +46,9 @@ install:
|
|||||||
- sudo PATH=$PATH GOPATH=$GOPATH script/setup/install-runc
|
- 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-cni
|
||||||
- sudo PATH=$PATH GOPATH=$GOPATH script/setup/install-critools
|
- 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
|
- 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
|
- cd $TRAVIS_BUILD_DIR
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
@ -69,25 +69,24 @@ func HandleConsoleResize(ctx gocontext.Context, task resizer, con console.Consol
|
|||||||
// NewTask creates a new task
|
// 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) {
|
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...)...)
|
stdio := cio.NewCreator(append([]cio.Opt{cio.WithStdio}, ioOpts...)...)
|
||||||
if checkpoint == "" {
|
if checkpoint != "" {
|
||||||
ioCreator := stdio
|
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 {
|
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 {
|
ioCreator = cio.NullIO
|
||||||
if tty {
|
|
||||||
return nil, errors.New("tty and null-io cannot be used together")
|
|
||||||
}
|
|
||||||
ioCreator = cio.NullIO
|
|
||||||
}
|
|
||||||
return container.NewTask(ctx, ioCreator, opts...)
|
|
||||||
}
|
}
|
||||||
im, err := client.GetImage(ctx, checkpoint)
|
return container.NewTask(ctx, ioCreator, opts...)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
opts = append(opts, containerd.WithTaskCheckpoint(im))
|
|
||||||
return container.NewTask(ctx, stdio, opts...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNewTaskOpts(context *cli.Context) []containerd.NewTaskOpts {
|
func getNewTaskOpts(context *cli.Context) []containerd.NewTaskOpts {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
// +build linux
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
@ -19,12 +19,122 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containerd/containerd/oci"
|
"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) {
|
func TestCheckpointRestore(t *testing.T) {
|
||||||
if !supportsCriu {
|
if !supportsCriu {
|
||||||
t.Skip("system does not have criu installed")
|
t.Skip("system does not have criu installed")
|
||||||
|
@ -194,10 +194,23 @@ func (s *createdCheckpointState) Start(ctx context.Context) error {
|
|||||||
s.p.mu.Lock()
|
s.p.mu.Lock()
|
||||||
defer s.p.mu.Unlock()
|
defer s.p.mu.Unlock()
|
||||||
p := s.p
|
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 {
|
if _, err := s.p.runtime.Restore(ctx, p.id, p.bundle, s.opts); err != nil {
|
||||||
return p.runtimeError(err, "OCI runtime restore failed")
|
return p.runtimeError(err, "OCI runtime restore failed")
|
||||||
}
|
}
|
||||||
sio := p.stdio
|
|
||||||
if sio.Stdin != "" {
|
if sio.Stdin != "" {
|
||||||
sc, err := fifo.OpenFifo(ctx, sio.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
|
sc, err := fifo.OpenFifo(ctx, sio.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -207,7 +220,17 @@ func (s *createdCheckpointState) Start(ctx context.Context) error {
|
|||||||
p.closers = append(p.closers, sc)
|
p.closers = append(p.closers, sc)
|
||||||
}
|
}
|
||||||
var copyWaitGroup sync.WaitGroup
|
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 {
|
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")
|
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")
|
return errors.Wrap(err, "failed to retrieve OCI runtime container pid")
|
||||||
}
|
}
|
||||||
p.pid = pid
|
p.pid = pid
|
||||||
|
|
||||||
return s.transition("running")
|
return s.transition("running")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
github.com/containerd/go-runc bcb223a061a3dd7de1a89c0b402a60f4dd9bd307
|
github.com/containerd/go-runc f271fa2021de855d4d918dbef83c5fe19db1bdd5
|
||||||
github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925
|
github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925
|
||||||
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
|
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
|
||||||
github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788
|
github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788
|
||||||
|
2
vendor/github.com/containerd/go-runc/console.go
generated
vendored
2
vendor/github.com/containerd/go-runc/console.go
generated
vendored
@ -43,7 +43,7 @@ func NewConsoleSocket(path string) (*Socket, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Socket{
|
return &Socket{
|
||||||
l: l,
|
l: l,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
vendor/github.com/containerd/go-runc/runc.go
generated
vendored
12
vendor/github.com/containerd/go-runc/runc.go
generated
vendored
@ -512,10 +512,11 @@ type RestoreOpts struct {
|
|||||||
CheckpointOpts
|
CheckpointOpts
|
||||||
IO
|
IO
|
||||||
|
|
||||||
Detach bool
|
Detach bool
|
||||||
PidFile string
|
PidFile string
|
||||||
NoSubreaper bool
|
NoSubreaper bool
|
||||||
NoPivot bool
|
NoPivot bool
|
||||||
|
ConsoleSocket ConsoleSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *RestoreOpts) args() ([]string, error) {
|
func (o *RestoreOpts) args() ([]string, error) {
|
||||||
@ -530,6 +531,9 @@ func (o *RestoreOpts) args() ([]string, error) {
|
|||||||
}
|
}
|
||||||
out = append(out, "--pid-file", abs)
|
out = append(out, "--pid-file", abs)
|
||||||
}
|
}
|
||||||
|
if o.ConsoleSocket != nil {
|
||||||
|
out = append(out, "--console-socket", o.ConsoleSocket.Path())
|
||||||
|
}
|
||||||
if o.NoPivot {
|
if o.NoPivot {
|
||||||
out = append(out, "--no-pivot")
|
out = append(out, "--no-pivot")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user