diff --git a/api/client/update.go b/api/client/update.go index b33ed476a6..0a576e7c73 100644 --- a/api/client/update.go +++ b/api/client/update.go @@ -6,6 +6,7 @@ import ( Cli "github.com/docker/docker/cli" flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/runconfig/opts" "github.com/docker/engine-api/types/container" "github.com/docker/go-units" ) @@ -25,6 +26,7 @@ func (cli *DockerCli) CmdUpdate(args ...string) error { flMemoryReservation := cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit") flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") flKernelMemory := cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit") + flRestartPolicy := cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits") cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) @@ -69,6 +71,14 @@ func (cli *DockerCli) CmdUpdate(args ...string) error { } } + var restartPolicy container.RestartPolicy + if *flRestartPolicy != "" { + restartPolicy, err = opts.ParseRestartPolicy(*flRestartPolicy) + if err != nil { + return err + } + } + resources := container.Resources{ BlkioWeight: *flBlkioWeight, CpusetCpus: *flCpusetCpus, @@ -83,7 +93,8 @@ func (cli *DockerCli) CmdUpdate(args ...string) error { } updateConfig := container.UpdateConfig{ - Resources: resources, + Resources: resources, + RestartPolicy: restartPolicy, } names := cmd.Args() diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index 07d40168be..34ad48f0c4 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -322,7 +322,8 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon } hostConfig := &container.HostConfig{ - Resources: updateConfig.Resources, + Resources: updateConfig.Resources, + RestartPolicy: updateConfig.RestartPolicy, } name := vars["name"] diff --git a/cli/common.go b/cli/common.go index 880ef6c80a..d2fa93d882 100644 --- a/cli/common.go +++ b/cli/common.go @@ -64,7 +64,7 @@ var dockerCommands = []Command{ {"tag", "Tag an image into a repository"}, {"top", "Display the running processes of a container"}, {"unpause", "Unpause all processes within a container"}, - {"update", "Update resources of one or more containers"}, + {"update", "Update configuration of one or more containers"}, {"version", "Show the Docker version information"}, {"volume", "Manage Docker volumes"}, {"wait", "Block until a container stops, then print its exit code"}, diff --git a/container/container.go b/container/container.go index c92e3de4d1..526810e5cb 100644 --- a/container/container.go +++ b/container/container.go @@ -594,3 +594,20 @@ func (container *Container) InitDNSHostConfig() { container.HostConfig.DNSOptions = make([]string, 0) } } + +// UpdateMonitor updates monitor configure for running container +func (container *Container) UpdateMonitor(restartPolicy containertypes.RestartPolicy) { + monitor := container.monitor + // No need to update monitor if container hasn't got one + // monitor will be generated correctly according to container + if monitor == nil { + return + } + + monitor.mux.Lock() + // to check whether restart policy has changed. + if restartPolicy.Name != "" && !monitor.restartPolicy.IsSame(&restartPolicy) { + monitor.restartPolicy = restartPolicy + } + monitor.mux.Unlock() +} diff --git a/container/container_unix.go b/container/container_unix.go index 3d85e32e48..02408d25fc 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -564,10 +564,11 @@ func updateCommand(c *execdriver.Command, resources containertypes.Resources) { c.Resources.KernelMemory = resources.KernelMemory } -// UpdateContainer updates resources of a container. +// UpdateContainer updates configuration of a container. func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error { container.Lock() + // update resources of container resources := hostConfig.Resources cResources := &container.HostConfig.Resources if resources.BlkioWeight != 0 { @@ -600,6 +601,11 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi if resources.KernelMemory != 0 { cResources.KernelMemory = resources.KernelMemory } + + // update HostConfig of container + if hostConfig.RestartPolicy.Name != "" { + container.HostConfig.RestartPolicy = hostConfig.RestartPolicy + } container.Unlock() // If container is not running, update hostConfig struct is enough, diff --git a/container/container_windows.go b/container/container_windows.go index 61a8994244..a0ca88b3d3 100644 --- a/container/container_windows.go +++ b/container/container_windows.go @@ -3,6 +3,7 @@ package container import ( + "fmt" "os" "path/filepath" @@ -45,8 +46,22 @@ func (container *Container) TmpfsMounts() []execdriver.Mount { return nil } -// UpdateContainer updates resources of a container +// UpdateContainer updates configuration of a container func (container *Container) UpdateContainer(hostConfig *container.HostConfig) error { + container.Lock() + defer container.Unlock() + resources := hostConfig.Resources + if resources.BlkioWeight != 0 || resources.CPUShares != 0 || + resources.CPUPeriod != 0 || resources.CPUQuota != 0 || + resources.CpusetCpus != "" || resources.CpusetMems != "" || + resources.Memory != 0 || resources.MemorySwap != 0 || + resources.MemoryReservation != 0 || resources.KernelMemory != 0 { + return fmt.Errorf("Resource updating isn't supported on Windows") + } + // update HostConfig of container + if hostConfig.RestartPolicy.Name != "" { + container.HostConfig.RestartPolicy = hostConfig.RestartPolicy + } return nil } diff --git a/container/monitor.go b/container/monitor.go index a292d8ce3f..043ee2fe80 100644 --- a/container/monitor.go +++ b/container/monitor.go @@ -79,11 +79,11 @@ type containerMonitor struct { // StartMonitor initializes a containerMonitor for this container with the provided supervisor and restart policy // and starts the container's process. -func (container *Container) StartMonitor(s supervisor, policy container.RestartPolicy) error { +func (container *Container) StartMonitor(s supervisor) error { container.monitor = &containerMonitor{ supervisor: s, container: container, - restartPolicy: policy, + restartPolicy: container.HostConfig.RestartPolicy, timeIncrement: defaultTimeIncrement, stopChan: make(chan struct{}), startSignal: make(chan struct{}), diff --git a/container/state.go b/container/state.go index 4a923aa968..aa2b26722b 100644 --- a/container/state.go +++ b/container/state.go @@ -119,11 +119,11 @@ func wait(waitChan <-chan struct{}, timeout time.Duration) error { } } -// waitRunning waits until state is running. If state is already +// WaitRunning waits until state is running. If state is already // running it returns immediately. If you want wait forever you must // supply negative timeout. Returns pid, that was passed to // SetRunning. -func (s *State) waitRunning(timeout time.Duration) (int, error) { +func (s *State) WaitRunning(timeout time.Duration) (int, error) { s.Lock() if s.Running { pid := s.Pid diff --git a/container/state_test.go b/container/state_test.go index 00c45f1324..75028168d4 100644 --- a/container/state_test.go +++ b/container/state_test.go @@ -14,7 +14,7 @@ func TestStateRunStop(t *testing.T) { started := make(chan struct{}) var pid int64 go func() { - runPid, _ := s.waitRunning(-1 * time.Second) + runPid, _ := s.WaitRunning(-1 * time.Second) atomic.StoreInt64(&pid, int64(runPid)) close(started) }() @@ -41,8 +41,8 @@ func TestStateRunStop(t *testing.T) { if runPid != i+100 { t.Fatalf("Pid %v, expected %v", runPid, i+100) } - if pid, err := s.waitRunning(-1 * time.Second); err != nil || pid != i+100 { - t.Fatalf("waitRunning returned pid: %v, err: %v, expected pid: %v, err: %v", pid, err, i+100, nil) + if pid, err := s.WaitRunning(-1 * time.Second); err != nil || pid != i+100 { + t.Fatalf("WaitRunning returned pid: %v, err: %v, expected pid: %v, err: %v", pid, err, i+100, nil) } stopped := make(chan struct{}) @@ -82,7 +82,7 @@ func TestStateTimeoutWait(t *testing.T) { s := NewState() started := make(chan struct{}) go func() { - s.waitRunning(100 * time.Millisecond) + s.WaitRunning(100 * time.Millisecond) close(started) }() select { @@ -98,7 +98,7 @@ func TestStateTimeoutWait(t *testing.T) { stopped := make(chan struct{}) go func() { - s.waitRunning(100 * time.Millisecond) + s.WaitRunning(100 * time.Millisecond) close(stopped) }() select { diff --git a/daemon/execdriver/windows/update.go b/daemon/execdriver/windows/update.go index 33c0b9ef1a..a4c42a6b08 100644 --- a/daemon/execdriver/windows/update.go +++ b/daemon/execdriver/windows/update.go @@ -3,12 +3,12 @@ package windows import ( - "fmt" - "github.com/docker/docker/daemon/execdriver" ) // Update updates resource configs for a container. func (d *Driver) Update(c *execdriver.Command) error { - return fmt.Errorf("Windows: Update not implemented") + // Updating resource isn't supported on Windows + // but we should return nil for enabling updating container + return nil } diff --git a/daemon/start.go b/daemon/start.go index 883e9d06d9..b467da6b6f 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -154,7 +154,7 @@ func (daemon *Daemon) containerStart(container *container.Container) (err error) } func (daemon *Daemon) waitForStart(container *container.Container) error { - return container.StartMonitor(daemon, container.HostConfig.RestartPolicy) + return container.StartMonitor(daemon) } // Cleanup releases any network resources allocated to the container along with any rules diff --git a/daemon/update.go b/daemon/update.go index 181b399c96..dab1a8ccb1 100644 --- a/daemon/update.go +++ b/daemon/update.go @@ -2,12 +2,13 @@ package daemon import ( "fmt" + "time" derr "github.com/docker/docker/errors" "github.com/docker/engine-api/types/container" ) -// ContainerUpdate updates resources of the container +// ContainerUpdate updates configuration of the container func (daemon *Daemon) ContainerUpdate(name string, hostConfig *container.HostConfig) ([]string, error) { var warnings []string @@ -58,11 +59,19 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro return derr.ErrorCodeCantUpdate.WithArgs(container.ID, err.Error()) } + // if Restart Policy changed, we need to update container monitor + container.UpdateMonitor(hostConfig.RestartPolicy) + + // if container is restarting, wait 5 seconds until it's running + if container.IsRestarting() { + container.WaitRunning(5 * time.Second) + } + // If container is not running, update hostConfig struct is enough, // resources will be updated when the container is started again. // If container is running (including paused), we need to update configs // to the real world. - if container.IsRunning() { + if container.IsRunning() && !container.IsRestarting() { if err := daemon.execDriver.Update(container.Command); err != nil { return derr.ErrorCodeCantUpdate.WithArgs(container.ID, err.Error()) } diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 30871d0875..06ce157d1c 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -117,7 +117,7 @@ This section lists each version from latest to oldest. Each listing includes a * `GET /containers/json` returns the state of the container, one of `created`, `restarting`, `running`, `paused`, `exited` or `dead`. * `GET /networks/(name)` now returns an `Internal` field showing whether the network is internal or not. - +* `POST /containers/(name)/update` now supports updating container's restart policy. ### v1.22 API changes diff --git a/docs/reference/api/docker_remote_api_v1.23.md b/docs/reference/api/docker_remote_api_v1.23.md index 422920f083..ebee12e68c 100644 --- a/docs/reference/api/docker_remote_api_v1.23.md +++ b/docs/reference/api/docker_remote_api_v1.23.md @@ -1031,7 +1031,7 @@ Status Codes: `POST /containers/(id)/update` -Update resource configs of one or more containers. +Update configuration of one or more containers. **Example request**: @@ -1049,6 +1049,10 @@ Update resource configs of one or more containers. "MemorySwap": 514288000, "MemoryReservation": 209715200, "KernelMemory": 52428800, + "RestartPolicy": { + "MaximumRetryCount": 4, + "Name": "on-failure" + }, } **Example response**: diff --git a/docs/reference/commandline/update.md b/docs/reference/commandline/update.md index bcbfab6ab9..24fb1f290d 100644 --- a/docs/reference/commandline/update.md +++ b/docs/reference/commandline/update.md @@ -12,7 +12,7 @@ parent = "smn_cli" Usage: docker update [OPTIONS] CONTAINER [CONTAINER...] - Updates container resource limits + Update configuration of one or more containers --help=false Print usage --blkio-weight=0 Block IO (relative weight), between 10 and 1000 @@ -25,11 +25,12 @@ parent = "smn_cli" --memory-reservation="" Memory soft limit --memory-swap="" A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap --kernel-memory="" Kernel memory limit: container must be stopped + --restart Restart policy to apply when a container exits -The `docker update` command dynamically updates container resources. Use this -command to prevent containers from consuming too many resources from their -Docker host. With a single command, you can place limits on a single -container or on many. To specify more than one container, provide +The `docker update` command dynamically updates container configuration. +You can use this command to prevent containers from consuming too many resources +from their Docker host. With a single command, you can place limits on +a single container or on many. To specify more than one container, provide space-separated list of container names or IDs. With the exception of the `--kernel-memory` value, you can specify these @@ -38,6 +39,10 @@ options on a running or a stopped container. You can only update stopped container, the next time you restart it, the container uses those values. +Another configuration you can change with this command is restart policy, +new restart policy will take effect instantly after you run `docker update` +on a container. + ## EXAMPLES The following sections illustrate ways to use this command. @@ -59,3 +64,10 @@ To update multiple resource configurations for multiple containers: ```bash $ docker update --cpu-shares 512 -m 300M abebf7571666 hopeful_morse ``` + +### Update a container's restart policy + +To update restart policy for one or more containers: +```bash +$ docker update --restart=on-failure:3 abebf7571666 hopeful_morse +``` diff --git a/integration-cli/docker_cli_update_test.go b/integration-cli/docker_cli_update_test.go new file mode 100644 index 0000000000..588d75d37c --- /dev/null +++ b/integration-cli/docker_cli_update_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "strings" + "time" + + "github.com/docker/docker/pkg/integration/checker" + "github.com/go-check/check" +) + +func (s *DockerSuite) TestUpdateRestartPolicy(c *check.C) { + out, _ := dockerCmd(c, "run", "-d", "--restart=on-failure:3", "busybox", "sh", "-c", "sleep 1 && false") + timeout := 60 * time.Second + if daemonPlatform == "windows" { + timeout = 100 * time.Second + } + + id := strings.TrimSpace(string(out)) + + // update restart policy to on-failure:5 + dockerCmd(c, "update", "--restart=on-failure:5", id) + + err := waitExited(id, timeout) + c.Assert(err, checker.IsNil) + + count := inspectField(c, id, "RestartCount") + c.Assert(count, checker.Equals, "5") + + maximumRetryCount := inspectField(c, id, "HostConfig.RestartPolicy.MaximumRetryCount") + c.Assert(maximumRetryCount, checker.Equals, "5") +} diff --git a/man/docker-update.1.md b/man/docker-update.1.md index a49fbd83d3..87849ef8d5 100644 --- a/man/docker-update.1.md +++ b/man/docker-update.1.md @@ -2,7 +2,7 @@ % Docker Community % JUNE 2014 # NAME -docker-update - Update resource configs of one or more containers +docker-update - Update configuration of one or more containers # SYNOPSIS **docker update** @@ -17,15 +17,16 @@ docker-update - Update resource configs of one or more containers [**-m**|**--memory**[=*MEMORY*]] [**--memory-reservation**[=*MEMORY-RESERVATION*]] [**--memory-swap**[=*MEMORY-SWAP*]] +[**--restart**[=*""*]] CONTAINER [CONTAINER...] # DESCRIPTION -The `docker update` command dynamically updates container resources. Use this -command to prevent containers from consuming too many resources from their -Docker host. With a single command, you can place limits on a single -container or on many. To specify more than one container, provide -space-separated list of container names or IDs. +The `docker update` command dynamically updates container configuration. +you can Use this command to prevent containers from consuming too many +resources from their Docker host. With a single command, you can place +limits on a single container or on many. To specify more than one container, +provide space-separated list of container names or IDs. With the exception of the `--kernel-memory` value, you can specify these options on a running or a stopped container. You can only update @@ -33,6 +34,10 @@ options on a running or a stopped container. You can only update stopped container, the next time you restart it, the container uses those values. +Another configuration you can change with this command is restart policy, +new restart policy will take effect instantly after you run `docker update` +on a container. + # OPTIONS **--blkio-weight**=0 Block IO weight (relative weight) accepts a weight value between 10 and 1000. @@ -70,6 +75,9 @@ be updated to a stopped container, and affect after it's started. **--memory-swap**="" Total memory limit (memory + swap) +**--restart**="" + Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped). + # EXAMPLES The following sections illustrate ways to use this command. @@ -91,3 +99,10 @@ To update multiple resource configurations for multiple containers: ```bash $ docker update --cpu-shares 512 -m 300M abebf7571666 hopeful_morse ``` + +### Update a container's restart policy + +To update restart policy for one or more containers: +```bash +$ docker update --restart=on-failure:3 abebf7571666 hopeful_morse +```