Skip to content

Commit d8fef66

Browse files
committed
Initial implementation of containerd Checkpoint API.
Signed-off-by: boucher <[email protected]>
1 parent e345d67 commit d8fef66

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+658
-37
lines changed

Dockerfile

+13
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,17 @@ RUN apt-get update && apt-get install -y \
5757
libapparmor-dev \
5858
libcap-dev \
5959
libltdl-dev \
60+
libnl-3-dev \
61+
libprotobuf-c0-dev \
62+
libprotobuf-dev \
6063
libsqlite3-dev \
6164
libsystemd-journal-dev \
6265
libtool \
6366
mercurial \
6467
net-tools \
6568
pkg-config \
69+
protobuf-compiler \
70+
protobuf-c-compiler \
6671
python-dev \
6772
python-mock \
6873
python-pip \
@@ -145,6 +150,14 @@ RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint
145150
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
146151
&& go install -v github.com/golang/lint/golint
147152

153+
# Install CRIU for checkpoint/restore support
154+
ENV CRIU_VERSION 2.2
155+
RUN mkdir -p /usr/src/criu \
156+
&& curl -sSL https://github.com/xemul/criu/archive/v${CRIU_VERSION}.tar.gz | tar -v -C /usr/src/criu/ -xz --strip-components=1 \
157+
&& cd /usr/src/criu \
158+
&& make \
159+
&& make install-criu
160+
148161
# Install two versions of the registry. The first is an older version that
149162
# only supports schema1 manifests. The second is a newer version that supports
150163
# both. This allows integration-cli tests to cover push/pull with both schema1
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// +build experimental
2+
3+
package checkpoint
4+
5+
import "github.com/docker/docker/api/types"
6+
7+
// Backend for Checkpoint
8+
type Backend interface {
9+
CheckpointCreate(container string, config types.CheckpointCreateOptions) error
10+
CheckpointDelete(container string, checkpointID string) error
11+
CheckpointList(container string) ([]types.Checkpoint, error)
12+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package checkpoint
2+
3+
import (
4+
"github.com/docker/docker/api/server/httputils"
5+
"github.com/docker/docker/api/server/router"
6+
)
7+
8+
// checkpointRouter is a router to talk with the checkpoint controller
9+
type checkpointRouter struct {
10+
backend Backend
11+
decoder httputils.ContainerDecoder
12+
routes []router.Route
13+
}
14+
15+
// NewRouter initializes a new checkpoint router
16+
func NewRouter(b Backend, decoder httputils.ContainerDecoder) router.Router {
17+
r := &checkpointRouter{
18+
backend: b,
19+
decoder: decoder,
20+
}
21+
r.initRoutes()
22+
return r
23+
}
24+
25+
// Routes returns the available routers to the checkpoint controller
26+
func (r *checkpointRouter) Routes() []router.Route {
27+
return r.routes
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// +build experimental
2+
3+
package checkpoint
4+
5+
import (
6+
"github.com/docker/docker/api/server/router"
7+
)
8+
9+
func (r *checkpointRouter) initRoutes() {
10+
r.routes = []router.Route{
11+
router.NewGetRoute("/containers/{name:.*}/checkpoints", r.getContainerCheckpoints),
12+
router.NewPostRoute("/containers/{name:.*}/checkpoints", r.postContainerCheckpoint),
13+
router.NewDeleteRoute("/containers/{name:.*}/checkpoints/{checkpoint:.*}", r.deleteContainerCheckpoint),
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// +build !experimental
2+
3+
package checkpoint
4+
5+
func (r *checkpointRouter) initRoutes() {}
6+
7+
// Backend is empty so that the package can compile in non-experimental
8+
type Backend interface{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// +build experimental
2+
3+
package checkpoint
4+
5+
import (
6+
"encoding/json"
7+
"net/http"
8+
9+
"github.com/docker/docker/api/server/httputils"
10+
"github.com/docker/docker/api/types"
11+
"golang.org/x/net/context"
12+
)
13+
14+
func (s *checkpointRouter) postContainerCheckpoint(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
15+
if err := httputils.ParseForm(r); err != nil {
16+
return err
17+
}
18+
19+
var options types.CheckpointCreateOptions
20+
21+
decoder := json.NewDecoder(r.Body)
22+
if err := decoder.Decode(&options); err != nil {
23+
return err
24+
}
25+
26+
err := s.backend.CheckpointCreate(vars["name"], options)
27+
if err != nil {
28+
return err
29+
}
30+
31+
w.WriteHeader(http.StatusNoContent)
32+
return nil
33+
}
34+
35+
func (s *checkpointRouter) getContainerCheckpoints(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
36+
if err := httputils.ParseForm(r); err != nil {
37+
return err
38+
}
39+
40+
checkpoints, err := s.backend.CheckpointList(vars["name"])
41+
if err != nil {
42+
return err
43+
}
44+
45+
return httputils.WriteJSON(w, http.StatusOK, checkpoints)
46+
}
47+
48+
func (s *checkpointRouter) deleteContainerCheckpoint(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
49+
if err := httputils.ParseForm(r); err != nil {
50+
return err
51+
}
52+
53+
err := s.backend.CheckpointDelete(vars["name"], vars["checkpoint"])
54+
if err != nil {
55+
return err
56+
}
57+
58+
w.WriteHeader(http.StatusNoContent)
59+
return nil
60+
}

api/server/router/container/backend.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type stateBackend interface {
3939
ContainerResize(name string, height, width int) error
4040
ContainerRestart(name string, seconds int) error
4141
ContainerRm(name string, config *types.ContainerRmConfig) error
42-
ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool) error
42+
ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
4343
ContainerStop(name string, seconds int) error
4444
ContainerUnpause(name string) error
4545
ContainerUpdate(name string, hostConfig *container.HostConfig, validateHostname bool) (types.ContainerUpdateResponse, error)

api/server/router/container/container_routes.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,16 @@ func (s *containerRouter) postContainersStart(ctx context.Context, w http.Respon
151151
hostConfig = c
152152
}
153153

154+
if err := httputils.ParseForm(r); err != nil {
155+
return err
156+
}
157+
158+
checkpoint := r.Form.Get("checkpoint")
154159
validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
155-
if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname); err != nil {
160+
if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname, checkpoint); err != nil {
156161
return err
157162
}
163+
158164
w.WriteHeader(http.StatusNoContent)
159165
return nil
160166
}

builder/builder.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,19 @@ type Backend interface {
124124
// ContainerKill stops the container execution abruptly.
125125
ContainerKill(containerID string, sig uint64) error
126126
// ContainerStart starts a new container
127-
ContainerStart(containerID string, hostConfig *container.HostConfig, validateHostname bool) error
127+
ContainerStart(containerID string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
128128
// ContainerWait stops processing until the given container is stopped.
129129
ContainerWait(containerID string, timeout time.Duration) (int, error)
130130
// ContainerUpdateCmdOnBuild updates container.Path and container.Args
131131
ContainerUpdateCmdOnBuild(containerID string, cmd []string) error
132132

133+
// CheckpointCreate checkpoints a running container
134+
CheckpointCreate(container string, config types.CheckpointCreateOptions) error
135+
// CheckpointDelete deletes a container's checkpoint
136+
CheckpointDelete(container string, checkpoint string) error
137+
// CheckpointList lists the available checkpoints for a container
138+
CheckpointList(container string) ([]types.Checkpoint, error)
139+
133140
// ContainerCopy copies/extracts a source FileInfo to a destination path inside a container
134141
// specified by a container object.
135142
// TODO: make an Extract method instead of passing `decompress`

builder/dockerfile/internals.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ func (b *Builder) run(cID string) (err error) {
555555
}
556556
}()
557557

558-
if err := b.docker.ContainerStart(cID, nil, true); err != nil {
558+
if err := b.docker.ContainerStart(cID, nil, true, ""); err != nil {
559559
return err
560560
}
561561

cli/command/checkpoint/cmd.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// +build !experimental
2+
3+
package checkpoint
4+
5+
import (
6+
"github.com/docker/docker/cli/command"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// NewCheckpointCommand returns a cobra command for `checkpoint` subcommands
11+
func NewCheckpointCommand(rootCmd *cobra.Command, dockerCli *command.DockerCli) {
12+
}
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// +build experimental
2+
3+
package checkpoint
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/spf13/cobra"
9+
10+
"github.com/docker/docker/cli"
11+
"github.com/docker/docker/cli/command"
12+
)
13+
14+
// NewCheckpointCommand returns a cobra command for `checkpoint` subcommands
15+
func NewCheckpointCommand(rootCmd *cobra.Command, dockerCli *command.DockerCli) {
16+
cmd := &cobra.Command{
17+
Use: "checkpoint",
18+
Short: "Manage Container Checkpoints",
19+
Args: cli.NoArgs,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
22+
},
23+
}
24+
cmd.AddCommand(
25+
newCreateCommand(dockerCli),
26+
newListCommand(dockerCli),
27+
newRemoveCommand(dockerCli),
28+
)
29+
30+
rootCmd.AddCommand(cmd)
31+
}

cli/command/checkpoint/create.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// +build experimental
2+
3+
package checkpoint
4+
5+
import (
6+
"golang.org/x/net/context"
7+
8+
"github.com/docker/docker/api/types"
9+
"github.com/docker/docker/cli"
10+
"github.com/docker/docker/cli/command"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
type createOptions struct {
15+
container string
16+
checkpoint string
17+
leaveRunning bool
18+
}
19+
20+
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
21+
var opts createOptions
22+
23+
cmd := &cobra.Command{
24+
Use: "create CONTAINER CHECKPOINT",
25+
Short: "Create a checkpoint from a running container",
26+
Args: cli.ExactArgs(2),
27+
RunE: func(cmd *cobra.Command, args []string) error {
28+
opts.container = args[0]
29+
opts.checkpoint = args[1]
30+
return runCreate(dockerCli, opts)
31+
},
32+
}
33+
34+
flags := cmd.Flags()
35+
flags.BoolVar(&opts.leaveRunning, "leave-running", false, "leave the container running after checkpoing")
36+
37+
return cmd
38+
}
39+
40+
func runCreate(dockerCli *command.DockerCli, opts createOptions) error {
41+
client := dockerCli.Client()
42+
43+
checkpointOpts := types.CheckpointCreateOptions{
44+
CheckpointID: opts.checkpoint,
45+
Exit: !opts.leaveRunning,
46+
}
47+
48+
err := client.CheckpointCreate(context.Background(), opts.container, checkpointOpts)
49+
if err != nil {
50+
return err
51+
}
52+
53+
return nil
54+
}

cli/command/checkpoint/list.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// +build experimental
2+
3+
package checkpoint
4+
5+
import (
6+
"fmt"
7+
"text/tabwriter"
8+
9+
"golang.org/x/net/context"
10+
11+
"github.com/docker/docker/cli"
12+
"github.com/docker/docker/cli/command"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
17+
return &cobra.Command{
18+
Use: "ls CONTAINER",
19+
Aliases: []string{"list"},
20+
Short: "List checkpoints for a container",
21+
Args: cli.ExactArgs(1),
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
return runList(dockerCli, args[0])
24+
},
25+
}
26+
}
27+
28+
func runList(dockerCli *command.DockerCli, container string) error {
29+
client := dockerCli.Client()
30+
31+
checkpoints, err := client.CheckpointList(context.Background(), container)
32+
if err != nil {
33+
return err
34+
}
35+
36+
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
37+
fmt.Fprintf(w, "CHECKPOINT NAME")
38+
fmt.Fprintf(w, "\n")
39+
40+
for _, checkpoint := range checkpoints {
41+
fmt.Fprintf(w, "%s\t", checkpoint.Name)
42+
fmt.Fprint(w, "\n")
43+
}
44+
45+
w.Flush()
46+
return nil
47+
}

cli/command/checkpoint/remove.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// +build experimental
2+
3+
package checkpoint
4+
5+
import (
6+
"golang.org/x/net/context"
7+
8+
"github.com/docker/docker/cli"
9+
"github.com/docker/docker/cli/command"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
14+
return &cobra.Command{
15+
Use: "rm CONTAINER CHECKPOINT",
16+
Aliases: []string{"remove"},
17+
Short: "Remove a checkpoint",
18+
Args: cli.ExactArgs(2),
19+
RunE: func(cmd *cobra.Command, args []string) error {
20+
return runRemove(dockerCli, args[0], args[1])
21+
},
22+
}
23+
}
24+
25+
func runRemove(dockerCli *command.DockerCli, container string, checkpoint string) error {
26+
client := dockerCli.Client()
27+
return client.CheckpointDelete(context.Background(), container, checkpoint)
28+
}

0 commit comments

Comments
 (0)