Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
60 changes: 60 additions & 0 deletions api/client/checkpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package client

import (
"fmt"

"github.com/docker/libcontainer"
)

/*
type CriuOpts struct {
ImagesDirectory string // directory for storing image files
PreviousImagesDirectory string // path to images from previous dump (relative to --images-directory)
LeaveRunning Bool // leave container in running state after checkpoint
TcpEstablished bool // checkpoint/restore established TCP connections
ExternalUnixConnections bool // allow external unix connections
ShellJob bool // allow to dump and restore shell jobs
}*/

func (cli *DockerCli) CmdCheckpoint(args ...string) error {
cmd := cli.Subcmd("checkpoint", "CONTAINER [CONTAINER...]", "Checkpoint one or more running containers", true)

var (
flImgDir = cmd.String([]string{"-image-dir"}, "", "(optional) directory for storing checkpoint image files")
flPrevImgDir = cmd.String([]string{"-prev-image-dir"}, "", "path to images from previous dump (relative to --checkpoint-image-dir)")
flLeaveRunning = cmd.Bool([]string{"-leave-running"}, false, "leave the container running after checkpointing")
flCheckTcp = cmd.Bool([]string{"-allow-tcp"}, false, "allow checkpointing established tcp connections")
flExtUnix = cmd.Bool([]string{"-allow-ext-unix"}, false, "allow checkpointing external unix connections")
flShell = cmd.Bool([]string{"-allow-shell"}, false, "allow checkpointing shell jobs")
)

if err := cmd.ParseFlags(args, true); err != nil {
return err
}

if cmd.NArg() < 1 {
cmd.Usage()
return nil
}

criuOpts := &libcontainer.CriuOpts{
ImagesDirectory: *flImgDir,
PreviousImagesDirectory: *flPrevImgDir,
LeaveRunning: *flLeaveRunning,
TcpEstablished: *flCheckTcp,
ExternalUnixConnections: *flExtUnix,
ShellJob: *flShell,
}

var encounteredError error
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/checkpoint", criuOpts, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to checkpoint one or more containers")
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return encounteredError
}
46 changes: 46 additions & 0 deletions api/client/restore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package client

import (
"fmt"

"github.com/docker/libcontainer"
)

func (cli *DockerCli) CmdRestore(args ...string) error {
cmd := cli.Subcmd("restore", "CONTAINER [CONTAINER...]", "Restore one or more checkpointed containers", true)

var (
flImgDir = cmd.String([]string{"-image-dir"}, "", "(optional) directory to restore image files from")
flCheckTcp = cmd.Bool([]string{"-allow-tcp"}, false, "allow restoring tcp connections")
flExtUnix = cmd.Bool([]string{"-allow-ext-unix"}, false, "allow restoring external unix connections")
flShell = cmd.Bool([]string{"-allow-shell"}, false, "allow restoring shell jobs")
)

if err := cmd.ParseFlags(args, true); err != nil {
return err
}

if cmd.NArg() < 1 {
cmd.Usage()
return nil
}

criuOpts := &libcontainer.CriuOpts{
ImagesDirectory: *flImgDir,
TcpEstablished: *flCheckTcp,
ExternalUnixConnections: *flExtUnix,
ShellJob: *flShell,
}

var encounteredError error
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restore", criuOpts, nil))
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to restore one or more containers")
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return encounteredError
}
86 changes: 64 additions & 22 deletions api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,46 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
return nil
}

func postContainersCheckpoint(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
job := eng.Job("checkpoint", vars["name"])

if err := job.DecodeEnv(r.Body); err != nil {
return err
}

if err := job.Run(); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
return nil
}

func postContainersRestore(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
job := eng.Job("restore", vars["name"])

if err := job.DecodeEnv(r.Body); err != nil {
return err
}

if err := job.Run(); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
return nil
}

func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return nil
Expand Down Expand Up @@ -1451,28 +1491,30 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders stri
"/exec/{id:.*}/json": getExecByID,
},
"POST": {
"/auth": postAuth,
"/commit": postCommit,
"/build": postBuild,
"/images/create": postImagesCreate,
"/images/load": postImagesLoad,
"/images/{name:.*}/push": postImagesPush,
"/images/{name:.*}/tag": postImagesTag,
"/containers/create": postContainersCreate,
"/containers/{name:.*}/kill": postContainersKill,
"/containers/{name:.*}/pause": postContainersPause,
"/containers/{name:.*}/unpause": postContainersUnpause,
"/containers/{name:.*}/restart": postContainersRestart,
"/containers/{name:.*}/start": postContainersStart,
"/containers/{name:.*}/stop": postContainersStop,
"/containers/{name:.*}/wait": postContainersWait,
"/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy,
"/containers/{name:.*}/exec": postContainerExecCreate,
"/exec/{name:.*}/start": postContainerExecStart,
"/exec/{name:.*}/resize": postContainerExecResize,
"/containers/{name:.*}/rename": postContainerRename,
"/auth": postAuth,
"/commit": postCommit,
"/build": postBuild,
"/images/create": postImagesCreate,
"/images/load": postImagesLoad,
"/images/{name:.*}/push": postImagesPush,
"/images/{name:.*}/tag": postImagesTag,
"/containers/create": postContainersCreate,
"/containers/{name:.*}/kill": postContainersKill,
"/containers/{name:.*}/pause": postContainersPause,
"/containers/{name:.*}/unpause": postContainersUnpause,
"/containers/{name:.*}/restart": postContainersRestart,
"/containers/{name:.*}/start": postContainersStart,
"/containers/{name:.*}/stop": postContainersStop,
"/containers/{name:.*}/wait": postContainersWait,
"/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy,
"/containers/{name:.*}/exec": postContainerExecCreate,
"/exec/{name:.*}/start": postContainerExecStart,
"/exec/{name:.*}/resize": postContainerExecResize,
"/containers/{name:.*}/rename": postContainerRename,
"/containers/{name:.*}/checkpoint": postContainersCheckpoint,
"/containers/{name:.*}/restore": postContainersRestore,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
Expand Down
99 changes: 99 additions & 0 deletions daemon/checkpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package daemon

import (
"fmt"

"github.com/docker/docker/engine"
"github.com/docker/libcontainer"
)

// Checkpoint a running container.
func (daemon *Daemon) ContainerCheckpoint(job *engine.Job) error {
if len(job.Args) != 1 {
return fmt.Errorf("Usage: %s CONTAINER\n", job.Name)
}

name := job.Args[0]
container, err := daemon.Get(name)
if err != nil {
return err
}
if !container.IsRunning() {
return fmt.Errorf("Container %s not running", name)
}

opts := &libcontainer.CriuOpts{}

if job.EnvExists("ImagesDirectory") {
opts.ImagesDirectory = job.Getenv("ImagesDirectory")
}
if job.EnvExists("PreviousImagesDirectory") {
opts.PreviousImagesDirectory = job.Getenv("PreviousImagesDirectory")
}
if job.EnvExists("LeaveRunning") {
opts.LeaveRunning = job.GetenvBool("LeaveRunning")
}
if job.EnvExists("TcpEstablished") {
opts.TcpEstablished = job.GetenvBool("TcpEstablished")
}
if job.EnvExists("ExternalUnixConnections") {
opts.ExternalUnixConnections = job.GetenvBool("ExternalUnixConnections")
}
if job.EnvExists("ShellJob") {
opts.ShellJob = job.GetenvBool("ShellJob")
}

if err := container.Checkpoint(opts); err != nil {
return fmt.Errorf("Cannot checkpoint container %s: %s", name, err)
}

container.LogEvent("checkpoint")
return nil
}

// Restore a checkpointed container.
func (daemon *Daemon) ContainerRestore(job *engine.Job) error {
if len(job.Args) != 1 {
return fmt.Errorf("Usage: %s CONTAINER\n", job.Name)
}

name := job.Args[0]
container, err := daemon.Get(name)
if err != nil {
return err
}

if container.IsRunning() {
return fmt.Errorf("Container %s already running", name)
}

// TODO: how should we handle the notion of checkpoint and keep running?
// right now, having ever been checkpointed is sufficient for our desires
// still requires manually calling stop before you are able to then restore
if !container.HasBeenCheckpointed() {
return fmt.Errorf("Container %s is not checkpointed", name)
}

opts := &libcontainer.CriuOpts{}

if job.EnvExists("ImagesDirectory") {
opts.ImagesDirectory = job.Getenv("ImagesDirectory")
}
if job.EnvExists("TcpEstablished") {
opts.TcpEstablished = job.GetenvBool("TcpEstablished")
}
if job.EnvExists("ExternalUnixConnections") {
opts.ExternalUnixConnections = job.GetenvBool("ExternalUnixConnections")
}
if job.EnvExists("ShellJob") {
opts.ShellJob = job.GetenvBool("ShellJob")
}

if err = container.Restore(opts); err != nil {
container.LogEvent("die")
return fmt.Errorf("Cannot restore container %s: %s", name, err)
}

container.LogEvent("restore")
return nil
}
Loading