Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 67 additions & 22 deletions api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
"github.com/docker/libcontainer"
"github.com/docker/libnetwork/portallocator"
)

Expand Down Expand Up @@ -1286,6 +1287,48 @@ func (s *Server) postContainersCopy(version version.Version, w http.ResponseWrit
return nil
}

func (s *Server) postContainersCheckpoint(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
}

criuOpts := &libcontainer.CriuOpts{}
if err := json.NewDecoder(r.Body).Decode(criuOpts); err != nil {
return err
}

if err := s.daemon.ContainerCheckpoint(vars["name"], criuOpts); err != nil {
return err
}

w.WriteHeader(http.StatusNoContent)
return nil
}

func (s *Server) postContainersRestore(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
}

restoreOpts := runconfig.RestoreConfig{}
if err := json.NewDecoder(r.Body).Decode(&restoreOpts); err != nil {
return err
}

if err := s.daemon.ContainerRestore(vars["name"], &restoreOpts.CriuOpts, restoreOpts.ForceRestore); err != nil {
return err
}

w.WriteHeader(http.StatusNoContent)
return nil
}

func (s *Server) postContainerExecCreate(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 @@ -1488,28 +1531,30 @@ func createRouter(s *Server) *mux.Router {
"/exec/{id:.*}/json": s.getExecByID,
},
"POST": {
"/auth": s.postAuth,
"/commit": s.postCommit,
"/build": s.postBuild,
"/images/create": s.postImagesCreate,
"/images/load": s.postImagesLoad,
"/images/{name:.*}/push": s.postImagesPush,
"/images/{name:.*}/tag": s.postImagesTag,
"/containers/create": s.postContainersCreate,
"/containers/{name:.*}/kill": s.postContainersKill,
"/containers/{name:.*}/pause": s.postContainersPause,
"/containers/{name:.*}/unpause": s.postContainersUnpause,
"/containers/{name:.*}/restart": s.postContainersRestart,
"/containers/{name:.*}/start": s.postContainersStart,
"/containers/{name:.*}/stop": s.postContainersStop,
"/containers/{name:.*}/wait": s.postContainersWait,
"/containers/{name:.*}/resize": s.postContainersResize,
"/containers/{name:.*}/attach": s.postContainersAttach,
"/containers/{name:.*}/copy": s.postContainersCopy,
"/containers/{name:.*}/exec": s.postContainerExecCreate,
"/exec/{name:.*}/start": s.postContainerExecStart,
"/exec/{name:.*}/resize": s.postContainerExecResize,
"/containers/{name:.*}/rename": s.postContainerRename,
"/auth": s.postAuth,
"/commit": s.postCommit,
"/build": s.postBuild,
"/images/create": s.postImagesCreate,
"/images/load": s.postImagesLoad,
"/images/{name:.*}/push": s.postImagesPush,
"/images/{name:.*}/tag": s.postImagesTag,
"/containers/create": s.postContainersCreate,
"/containers/{name:.*}/kill": s.postContainersKill,
"/containers/{name:.*}/pause": s.postContainersPause,
"/containers/{name:.*}/unpause": s.postContainersUnpause,
"/containers/{name:.*}/restart": s.postContainersRestart,
"/containers/{name:.*}/start": s.postContainersStart,
"/containers/{name:.*}/stop": s.postContainersStop,
"/containers/{name:.*}/wait": s.postContainersWait,
"/containers/{name:.*}/resize": s.postContainersResize,
"/containers/{name:.*}/attach": s.postContainersAttach,
"/containers/{name:.*}/copy": s.postContainersCopy,
"/containers/{name:.*}/exec": s.postContainerExecCreate,
"/exec/{name:.*}/start": s.postContainerExecStart,
"/exec/{name:.*}/resize": s.postContainerExecResize,
"/containers/{name:.*}/rename": s.postContainerRename,
"/containers/{name:.*}/checkpoint": s.postContainersCheckpoint,
"/containers/{name:.*}/restore": s.postContainersRestore,
},
"DELETE": {
"/containers/{name:.*}": s.deleteContainers,
Expand Down
56 changes: 56 additions & 0 deletions daemon/checkpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package daemon

import (
"fmt"

"github.com/docker/libcontainer"
)

// Checkpoint a running container.
func (daemon *Daemon) ContainerCheckpoint(name string, opts *libcontainer.CriuOpts) error {
container, err := daemon.Get(name)
if err != nil {
return err
}
if !container.IsRunning() {
return fmt.Errorf("Container %s not running", name)
}
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(name string, opts *libcontainer.CriuOpts, forceRestore bool) error {
container, err := daemon.Get(name)
if err != nil {
return err
}

if !forceRestore {
// TODO: It's possible we only want to bypass the checkpointed check,
// I'm not sure how this will work if the container is already running
if container.IsRunning() {
return fmt.Errorf("Container %s already running", name)
}

if !container.IsCheckpointed() {
return fmt.Errorf("Container %s is not checkpointed", name)
}
} else {
if !container.HasBeenCheckpointed() && opts.ImagesDirectory == "" {
return fmt.Errorf("You must specify an image directory to restore from %s", name)
}
}

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

container.LogEvent("restore")
return nil
}
78 changes: 76 additions & 2 deletions daemon/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"syscall"
"time"

"github.com/docker/libcontainer"
"github.com/docker/libcontainer/label"

"github.com/Sirupsen/logrus"
Expand Down Expand Up @@ -255,7 +256,7 @@ func (container *Container) Start() (err error) {
if err := container.Mount(); err != nil {
return err
}
if err := container.initializeNetworking(); err != nil {
if err := container.initializeNetworking(false); err != nil {
return err
}
container.verifyDaemonSettings()
Expand Down Expand Up @@ -342,7 +343,11 @@ func (container *Container) isNetworkAllocated() bool {
// cleanup releases any network resources allocated to the container along with any rules
// around how containers are linked together. It also unmounts the container's root filesystem.
func (container *Container) cleanup() {
container.ReleaseNetwork()
if container.IsCheckpointed() {
logrus.Debugf("not calling ReleaseNetwork() for checkpointed container %s", container.ID)
} else {
container.ReleaseNetwork()
}

disableAllActiveLinks(container)

Expand Down Expand Up @@ -564,6 +569,55 @@ func validateID(id string) error {
return nil
}

func (container *Container) Checkpoint(opts *libcontainer.CriuOpts) error {
if err := container.daemon.Checkpoint(container, opts); err != nil {
return err
}

if opts.LeaveRunning == false {
container.ReleaseNetwork()
}
return nil
}

func (container *Container) Restore(opts *libcontainer.CriuOpts, forceRestore bool) error {
var err error
container.Lock()
defer container.Unlock()

defer func() {
if err != nil {
container.cleanup()
}
}()
if err := container.Mount(); err != nil {
return err
}
if err = container.initializeNetworking(true); err != nil {
return err
}
container.verifyDaemonSettings()

linkedEnv, err := container.setupLinkedContainers()
if err != nil {
return err
}
if err = container.setupWorkingDirectory(); err != nil {
return err
}

env := container.createDaemonEnvironment(linkedEnv)
if err = populateCommand(container, env); err != nil {
return err
}

if err = container.setupMounts(); err != nil {
return err
}

return container.waitForRestore(opts, forceRestore)
}

func (container *Container) Copy(resource string) (io.ReadCloser, error) {
container.Lock()
defer container.Unlock()
Expand Down Expand Up @@ -709,6 +763,26 @@ func (container *Container) waitForStart() error {
return nil
}

func (container *Container) waitForRestore(opts *libcontainer.CriuOpts, forceRestore bool) error {
container.monitor = newContainerMonitor(container, container.hostConfig.RestartPolicy)

// After calling promise.Go() we'll have two goroutines:
// - The current goroutine that will block in the select
// below until restore is done.
// - A new goroutine that will restore the container and
// wait for it to exit.
select {
case <-container.monitor.restoreSignal:
if container.ExitCode != 0 {
return fmt.Errorf("restore process failed")
}
case err := <-promise.Go(func() error { return container.monitor.Restore(opts, forceRestore) }):
return err
}

return nil
}

func (container *Container) GetProcessLabel() string {
// even if we have a process label return "" if we are running
// in privileged mode
Expand Down
18 changes: 13 additions & 5 deletions daemon/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ func (container *Container) UpdateNetwork() error {
return nil
}

func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointOption, error) {
func (container *Container) buildCreateEndpointOptions(restoring bool) ([]libnetwork.EndpointOption, error) {
var (
portSpecs = make(nat.PortSet)
bindings = make(nat.PortMap)
Expand Down Expand Up @@ -718,10 +718,18 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO
createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(genericOption))
}

/*if restoring && container.NetworkSettings.IPAddress != "" {
genericOption := options.Generic{
netlabel.IPAddress: net.ParseIP(container.NetworkSettings.IPAddress),
}

createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(genericOption))
}*/

return createOptions, nil
}

func (container *Container) AllocateNetwork() error {
func (container *Container) AllocateNetwork(restoring bool) error {
mode := container.hostConfig.NetworkMode
if container.Config.NetworkDisabled || mode.IsContainer() {
return nil
Expand All @@ -734,7 +742,7 @@ func (container *Container) AllocateNetwork() error {
return fmt.Errorf("error locating network with name %s: %v", string(mode), err)
}

createOptions, err := container.buildCreateEndpointOptions()
createOptions, err := container.buildCreateEndpointOptions(restoring)
if err != nil {
return err
}
Expand Down Expand Up @@ -768,7 +776,7 @@ func (container *Container) AllocateNetwork() error {
return nil
}

func (container *Container) initializeNetworking() error {
func (container *Container) initializeNetworking(restoring bool) error {
var err error

// Make sure NetworkMode has an acceptable value before
Expand Down Expand Up @@ -809,7 +817,7 @@ func (container *Container) initializeNetworking() error {

}

if err := container.AllocateNetwork(); err != nil {
if err := container.AllocateNetwork(restoring); err != nil {
return err
}

Expand Down
32 changes: 32 additions & 0 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"sync"
"time"

"github.com/docker/libcontainer"
"github.com/docker/libcontainer/label"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/netlabel"
Expand Down Expand Up @@ -279,6 +280,18 @@ func (daemon *Daemon) restore() error {
logrus.Debugf("Loaded container %v", container.ID)

containers[container.ID] = container

// If the container was checkpointed, we need to reserve
// the IP address that it was using.
//
// XXX We should also reserve host ports (if any).
if container.IsCheckpointed() {
/*err = bridge.ReserveIP(container.ID, container.NetworkSettings.IPAddress)
if err != nil {
log.Errorf("Failed to reserve IP %s for container %s",
container.ID, container.NetworkSettings.IPAddress)
}*/
}
} else {
logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID)
}
Expand Down Expand Up @@ -1048,6 +1061,25 @@ func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback e
return daemon.execDriver.Run(c.command, pipes, startCallback)
}

func (daemon *Daemon) Checkpoint(c *Container, opts *libcontainer.CriuOpts) error {
if err := daemon.execDriver.Checkpoint(c.command, opts); err != nil {
return err
}
c.SetCheckpointed(opts.LeaveRunning)
return nil
}

func (daemon *Daemon) Restore(c *Container, pipes *execdriver.Pipes, restoreCallback execdriver.RestoreCallback, opts *libcontainer.CriuOpts, forceRestore bool) (execdriver.ExitStatus, error) {
// Mount the container's filesystem (daemon/graphdriver/aufs/aufs.go).
_, err := daemon.driver.Get(c.ID, c.GetMountLabel())
if err != nil {
return execdriver.ExitStatus{ExitCode: 0}, err
}

exitCode, err := daemon.execDriver.Restore(c.command, pipes, restoreCallback, opts, forceRestore)
return exitCode, err
}

func (daemon *Daemon) Kill(c *Container, sig int) error {
return daemon.execDriver.Kill(c.command, sig)
}
Expand Down
Loading