Merge pull request #6178 from calfonso/freezer-pr-integration
Freezer integration to support container pause/unpause
This commit is contained in:
commit
048833c246
9 changed files with 227 additions and 0 deletions
|
@ -65,6 +65,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|||
{"login", "Register or Login to the docker registry server"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
||||
{"pause", "Pause all processes within a container"},
|
||||
{"ps", "List containers"},
|
||||
{"pull", "Pull an image or a repository from the docker registry server"},
|
||||
{"push", "Push an image or a repository to the docker registry server"},
|
||||
|
@ -78,6 +79,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|||
{"stop", "Stop a running container"},
|
||||
{"tag", "Tag an image into a repository"},
|
||||
{"top", "Lookup the running processes of a container"},
|
||||
{"unpause", "Unpause a paused container"},
|
||||
{"version", "Show the docker version information"},
|
||||
{"wait", "Block until a container stops, then print its exit code"},
|
||||
} {
|
||||
|
@ -648,6 +650,52 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||
cmd := cli.Subcmd("unpause", "CONTAINER", "Unpause all processes within a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cmd.NArg() != 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
return encounteredError
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPause(args ...string) error {
|
||||
cmd := cli.Subcmd("pause", "CONTAINER", "Pause all processes within a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cmd.NArg() != 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to pause container named %s", name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
return encounteredError
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
|
||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.")
|
||||
|
|
|
@ -165,6 +165,36 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp
|
|||
return nil
|
||||
}
|
||||
|
||||
func postContainersPause(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("pause", vars["name"])
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersUnpause(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("unpause", vars["name"])
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersExport(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")
|
||||
|
@ -1087,6 +1117,8 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
|||
"/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,
|
||||
|
|
|
@ -538,12 +538,37 @@ func (container *Container) KillSig(sig int) error {
|
|||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
// We could unpause the container for them rather than returning this error
|
||||
if container.State.IsPaused() {
|
||||
return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID)
|
||||
}
|
||||
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
return container.daemon.Kill(container, sig)
|
||||
}
|
||||
|
||||
func (container *Container) Pause() error {
|
||||
if container.State.IsPaused() {
|
||||
return fmt.Errorf("Container %s is already paused", container.ID)
|
||||
}
|
||||
if !container.State.IsRunning() {
|
||||
return fmt.Errorf("Container %s is not running", container.ID)
|
||||
}
|
||||
return container.daemon.Pause(container)
|
||||
}
|
||||
|
||||
func (container *Container) Unpause() error {
|
||||
if !container.State.IsPaused() {
|
||||
return fmt.Errorf("Container %s is not paused", container.ID)
|
||||
}
|
||||
if !container.State.IsRunning() {
|
||||
return fmt.Errorf("Container %s is not running", container.ID)
|
||||
}
|
||||
return container.daemon.Unpause(container)
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
|
|
|
@ -1014,6 +1014,22 @@ func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback e
|
|||
return daemon.execDriver.Run(c.command, pipes, startCallback)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Pause(c *Container) error {
|
||||
if err := daemon.execDriver.Pause(c.command); err != nil {
|
||||
return err
|
||||
}
|
||||
c.State.SetPaused()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Unpause(c *Container) error {
|
||||
if err := daemon.execDriver.Unpause(c.command); err != nil {
|
||||
return err
|
||||
}
|
||||
c.State.SetUnpaused()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Kill(c *Container, sig int) error {
|
||||
return daemon.execDriver.Kill(c.command, sig)
|
||||
}
|
||||
|
|
|
@ -83,6 +83,8 @@ type TtyTerminal interface {
|
|||
type Driver interface {
|
||||
Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code
|
||||
Kill(c *Command, sig int) error
|
||||
Pause(c *Command) error
|
||||
Unpause(c *Command) error
|
||||
Name() string // Driver name
|
||||
Info(id string) Info // "temporary" hack (until we move state from core to plugins)
|
||||
GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container.
|
||||
|
|
|
@ -218,6 +218,30 @@ func (d *driver) Kill(c *execdriver.Command, sig int) error {
|
|||
return KillLxc(c.ID, sig)
|
||||
}
|
||||
|
||||
func (d *driver) Pause(c *execdriver.Command) error {
|
||||
_, err := exec.LookPath("lxc-freeze")
|
||||
if err == nil {
|
||||
output, errExec := exec.Command("lxc-freeze", "-n", c.ID).CombinedOutput()
|
||||
if errExec != nil {
|
||||
return fmt.Errorf("Err: %s Output: %s", errExec, output)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
_, err := exec.LookPath("lxc-unfreeze")
|
||||
if err == nil {
|
||||
output, errExec := exec.Command("lxc-unfreeze", "-n", c.ID).CombinedOutput()
|
||||
if errExec != nil {
|
||||
return fmt.Errorf("Err: %s Output: %s", errExec, output)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) Terminate(c *execdriver.Command) error {
|
||||
return KillLxc(c.ID, 9)
|
||||
}
|
||||
|
|
|
@ -145,6 +145,30 @@ func (d *driver) Kill(p *execdriver.Command, sig int) error {
|
|||
return syscall.Kill(p.Process.Pid, syscall.Signal(sig))
|
||||
}
|
||||
|
||||
func (d *driver) Pause(c *execdriver.Command) error {
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
active.container.Cgroups.Freezer = "FROZEN"
|
||||
if systemd.UseSystemd() {
|
||||
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
|
||||
func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
active.container.Cgroups.Freezer = "THAWED"
|
||||
if systemd.UseSystemd() {
|
||||
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
|
||||
func (d *driver) Terminate(p *execdriver.Command) error {
|
||||
// lets check the start time for the process
|
||||
started, err := d.readStartTime(p)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
type State struct {
|
||||
sync.RWMutex
|
||||
Running bool
|
||||
Paused bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
StartedAt time.Time
|
||||
|
@ -23,6 +24,9 @@ func (s *State) String() string {
|
|||
defer s.RUnlock()
|
||||
|
||||
if s.Running {
|
||||
if s.Paused {
|
||||
return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
if s.FinishedAt.IsZero() {
|
||||
|
@ -50,6 +54,7 @@ func (s *State) SetRunning(pid int) {
|
|||
defer s.Unlock()
|
||||
|
||||
s.Running = true
|
||||
s.Paused = false
|
||||
s.ExitCode = 0
|
||||
s.Pid = pid
|
||||
s.StartedAt = time.Now().UTC()
|
||||
|
@ -64,3 +69,22 @@ func (s *State) SetStopped(exitCode int) {
|
|||
s.FinishedAt = time.Now().UTC()
|
||||
s.ExitCode = exitCode
|
||||
}
|
||||
|
||||
func (s *State) SetPaused() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.Paused = true
|
||||
}
|
||||
|
||||
func (s *State) SetUnpaused() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.Paused = false
|
||||
}
|
||||
|
||||
func (s *State) IsPaused() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.Paused
|
||||
}
|
||||
|
|
|
@ -125,6 +125,8 @@ func InitServer(job *engine.Job) engine.Status {
|
|||
"restart": srv.ContainerRestart,
|
||||
"start": srv.ContainerStart,
|
||||
"kill": srv.ContainerKill,
|
||||
"pause": srv.ContainerPause,
|
||||
"unpause": srv.ContainerUnpause,
|
||||
"wait": srv.ContainerWait,
|
||||
"tag": srv.ImageTag, // FIXME merge with "image_tag"
|
||||
"resize": srv.ContainerResize,
|
||||
|
@ -168,6 +170,36 @@ func InitServer(job *engine.Job) engine.Status {
|
|||
return engine.StatusOK
|
||||
}
|
||||
|
||||
func (srv *Server) ContainerPause(job *engine.Job) engine.Status {
|
||||
if len(job.Args) != 1 {
|
||||
return job.Errorf("Usage: %s CONTAINER", job.Name)
|
||||
}
|
||||
name := job.Args[0]
|
||||
container := srv.daemon.Get(name)
|
||||
if container == nil {
|
||||
return job.Errorf("No such container: %s", name)
|
||||
}
|
||||
if err := container.Pause(); err != nil {
|
||||
return job.Errorf("Cannot pause container %s: %s", name, err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
||||
func (srv *Server) ContainerUnpause(job *engine.Job) engine.Status {
|
||||
if n := len(job.Args); n < 1 || n > 2 {
|
||||
return job.Errorf("Usage: %s CONTAINER", job.Name)
|
||||
}
|
||||
name := job.Args[0]
|
||||
container := srv.daemon.Get(name)
|
||||
if container == nil {
|
||||
return job.Errorf("No such container: %s", name)
|
||||
}
|
||||
if err := container.Unpause(); err != nil {
|
||||
return job.Errorf("Cannot unpause container %s: %s", name, err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
||||
// ContainerKill send signal to the container
|
||||
// If no signal is given (sig 0), then Kill with SIGKILL and wait
|
||||
// for the container to exit.
|
||||
|
|
Loading…
Reference in a new issue