mirror of
https://github.com/clearlinux/docker.git
synced 2026-10-03 23:38:24 +00:00
Add ability to pause/unpause containers via cgroups freeze
This patch adds pause/unpause to the command line, api, and drivers for use on containers. This is implemented using the cgroups/freeze utility in libcontainer and lxc freeze/unfreeze. Co-Authored-By: Eric Windisch <ewindisch@docker.com> Co-Authored-By: Chris Alfonso <calfonso@redhat.com> Docker-DCO-1.1-Signed-off-by: Ian Main <imain@redhat.com> (github: imain)
This commit is contained in:
@@ -544,6 +544,26 @@ func (container *Container) KillSig(sig int) error {
|
||||
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
|
||||
@@ -574,6 +594,11 @@ func (container *Container) Stop(seconds int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 1. Send a SIGTERM
|
||||
if err := container.KillSig(15); err != nil {
|
||||
log.Print("Failed to send SIGTERM to the process, force killing")
|
||||
|
||||
@@ -1014,6 +1014,24 @@ 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 {
|
||||
err := daemon.execDriver.Pause(c.command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.State.SetPaused()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Unpause(c *Container) error {
|
||||
err := daemon.execDriver.Unpause(c.command)
|
||||
if 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)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ var actions = map[string]Action{
|
||||
"cgroups.memory_reservation": memoryReservation, // set the memory reservation
|
||||
"cgroups.memory_swap": memorySwap, // set the memory swap limit
|
||||
"cgroups.cpuset.cpus": cpusetCpus, // set the cpus used
|
||||
"cgroups.freezer": freezer, // set the frozen/thaw state
|
||||
|
||||
"systemd.slice": systemdSlice, // set parent Slice used for systemd unit
|
||||
|
||||
@@ -35,6 +36,16 @@ var actions = map[string]Action{
|
||||
"fs.readonly": readonlyFs, // make the rootfs of the container read only
|
||||
}
|
||||
|
||||
func freezer(container *libcontainer.Container, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set cgroups when they are disabled")
|
||||
}
|
||||
|
||||
container.Cgroups.Freezer = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cpusetCpus(container *libcontainer.Container, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set cgroups when they are disabled")
|
||||
|
||||
@@ -145,6 +145,32 @@ 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]
|
||||
active.container.Cgroups.Freezer = "FROZEN"
|
||||
pid := c.Process.Pid
|
||||
|
||||
if systemd.UseSystemd() {
|
||||
_, err := systemd.Apply(active.container.Cgroups, pid)
|
||||
return err
|
||||
}
|
||||
_, err := fs.Apply(active.container.Cgroups, pid)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
active := d.activeContainers[c.ID]
|
||||
active.container.Cgroups.Freezer = "THAWED"
|
||||
pid := c.Process.Pid
|
||||
|
||||
if systemd.UseSystemd() {
|
||||
_, err := systemd.Apply(active.container.Cgroups, pid)
|
||||
return err
|
||||
}
|
||||
_, err := fs.Apply(active.container.Cgroups, pid)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user