Browse Source

Implemet docker update command

It's used for updating properties of one or more containers, we only
support resource configs for now. It can be extended in the future.

Signed-off-by: Qiang Huang <h.huangqiang@huawei.com>
Qiang Huang 9 years ago
parent
commit
8799c4fc0f

+ 1 - 0
api/client/client.go

@@ -43,6 +43,7 @@ type apiClient interface {
 	ContainerStop(containerID string, timeout int) error
 	ContainerTop(containerID string, arguments []string) (types.ContainerProcessList, error)
 	ContainerUnpause(containerID string) error
+	ContainerUpdate(containerID string, hostConfig container.HostConfig) error
 	ContainerWait(containerID string) (int, error)
 	CopyFromContainer(containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
 	CopyToContainer(options types.CopyToContainerOptions) error

+ 12 - 0
api/client/lib/container_update.go

@@ -0,0 +1,12 @@
+package lib
+
+import (
+	"github.com/docker/docker/api/types/container"
+)
+
+// ContainerUpdate updates resources of a container
+func (cli *Client) ContainerUpdate(containerID string, hostConfig container.HostConfig) error {
+	resp, err := cli.post("/containers/"+containerID+"/update", nil, hostConfig, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 104 - 0
api/client/update.go

@@ -0,0 +1,104 @@
+package client
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/types/container"
+	Cli "github.com/docker/docker/cli"
+	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/go-units"
+)
+
+// CmdUpdate updates resources of one or more containers.
+//
+// Usage: docker update [OPTIONS] CONTAINER [CONTAINER...]
+func (cli *DockerCli) CmdUpdate(args ...string) error {
+	cmd := Cli.Subcmd("update", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["update"].Description, true)
+	flBlkioWeight := cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
+	flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
+	flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
+	flCpusetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
+	flCpusetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
+	flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
+	flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
+	flMemoryReservation := cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
+	flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
+	flKernelMemory := cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
+
+	cmd.Require(flag.Min, 1)
+	cmd.ParseFlags(args, true)
+	if cmd.NFlag() == 0 {
+		return fmt.Errorf("You must provide one or more flags when using this command.")
+	}
+
+	var err error
+	var flMemory int64
+	if *flMemoryString != "" {
+		flMemory, err = units.RAMInBytes(*flMemoryString)
+		if err != nil {
+			return err
+		}
+	}
+
+	var memoryReservation int64
+	if *flMemoryReservation != "" {
+		memoryReservation, err = units.RAMInBytes(*flMemoryReservation)
+		if err != nil {
+			return err
+		}
+	}
+
+	var memorySwap int64
+	if *flMemorySwap != "" {
+		if *flMemorySwap == "-1" {
+			memorySwap = -1
+		} else {
+			memorySwap, err = units.RAMInBytes(*flMemorySwap)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	var kernelMemory int64
+	if *flKernelMemory != "" {
+		kernelMemory, err = units.RAMInBytes(*flKernelMemory)
+		if err != nil {
+			return err
+		}
+	}
+
+	resources := container.Resources{
+		BlkioWeight:       *flBlkioWeight,
+		CpusetCpus:        *flCpusetCpus,
+		CpusetMems:        *flCpusetMems,
+		CPUShares:         *flCPUShares,
+		Memory:            flMemory,
+		MemoryReservation: memoryReservation,
+		MemorySwap:        memorySwap,
+		KernelMemory:      kernelMemory,
+		CPUPeriod:         *flCPUPeriod,
+		CPUQuota:          *flCPUQuota,
+	}
+
+	hostConfig := container.HostConfig{
+		Resources: resources,
+	}
+
+	names := cmd.Args()
+	var errNames []string
+	for _, name := range names {
+		if err := cli.client.ContainerUpdate(name, hostConfig); err != nil {
+			fmt.Fprintf(cli.err, "%s\n", err)
+			errNames = append(errNames, name)
+		} else {
+			fmt.Fprintf(cli.out, "%s\n", name)
+		}
+	}
+
+	if len(errNames) > 0 {
+		return fmt.Errorf("Error: failed to update resources of containers: %v", errNames)
+	}
+
+	return nil
+}

+ 1 - 0
api/server/router/container/backend.go

@@ -42,6 +42,7 @@ type stateBackend interface {
 	ContainerStart(name string, hostConfig *container.HostConfig) error
 	ContainerStop(name string, seconds int) error
 	ContainerUnpause(name string) error
+	ContainerUpdate(name string, hostConfig *container.HostConfig) ([]string, error)
 	ContainerWait(name string, timeout time.Duration) (int, error)
 	Exists(id string) bool
 	IsPaused(id string) bool

+ 1 - 0
api/server/router/container/container.go

@@ -57,6 +57,7 @@ func (r *containerRouter) initRoutes() {
 		local.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
 		local.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
 		local.NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
+		local.NewPostRoute("/containers/{name:.*}/update", r.postContainerUpdate),
 		// PUT
 		local.NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
 		// DELETE

+ 24 - 0
api/server/router/container/container_routes.go

@@ -323,6 +323,30 @@ func (s *containerRouter) postContainerRename(ctx context.Context, w http.Respon
 	return nil
 }
 
+func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+	if err := httputils.CheckForJSON(r); err != nil {
+		return err
+	}
+
+	_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
+	if err != nil {
+		return err
+	}
+
+	name := vars["name"]
+	warnings, err := s.backend.ContainerUpdate(name, hostConfig)
+	if err != nil {
+		return err
+	}
+
+	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerUpdateResponse{
+		Warnings: warnings,
+	})
+}
+
 func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 		return err

+ 7 - 0
api/types/types.go

@@ -28,6 +28,13 @@ type ContainerExecCreateResponse struct {
 	ID string `json:"Id"`
 }
 
+// ContainerUpdateResponse contains response of Remote API:
+// POST /containers/{name:.*}/update
+type ContainerUpdateResponse struct {
+	// Warnings are any warnings encountered during the updating of the container.
+	Warnings []string `json:"Warnings"`
+}
+
 // AuthResponse contains response of Remote API:
 // POST "/auth"
 type AuthResponse struct {

+ 1 - 0
cli/common.go

@@ -64,6 +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"},
 	{"version", "Show the Docker version information"},
 	{"volume", "Manage Docker volumes"},
 	{"wait", "Block until a container stops, then print its exit code"},

+ 70 - 0
container/container_unix.go

@@ -13,6 +13,7 @@ import (
 	"syscall"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/daemon/execdriver"
 	derr "github.com/docker/docker/errors"
@@ -543,6 +544,75 @@ func (container *Container) IpcMounts() []execdriver.Mount {
 	return mounts
 }
 
+func updateCommand(c *execdriver.Command, resources container.Resources) {
+	c.Resources.BlkioWeight = resources.BlkioWeight
+	c.Resources.CPUShares = resources.CPUShares
+	c.Resources.CPUPeriod = resources.CPUPeriod
+	c.Resources.CPUQuota = resources.CPUQuota
+	c.Resources.CpusetCpus = resources.CpusetCpus
+	c.Resources.CpusetMems = resources.CpusetMems
+	c.Resources.Memory = resources.Memory
+	c.Resources.MemorySwap = resources.MemorySwap
+	c.Resources.MemoryReservation = resources.MemoryReservation
+	c.Resources.KernelMemory = resources.KernelMemory
+}
+
+// UpdateContainer updates resources of a container.
+func (container *Container) UpdateContainer(hostConfig *container.HostConfig) error {
+	container.Lock()
+
+	resources := hostConfig.Resources
+	cResources := &container.HostConfig.Resources
+	if resources.BlkioWeight != 0 {
+		cResources.BlkioWeight = resources.BlkioWeight
+	}
+	if resources.CPUShares != 0 {
+		cResources.CPUShares = resources.CPUShares
+	}
+	if resources.CPUPeriod != 0 {
+		cResources.CPUPeriod = resources.CPUPeriod
+	}
+	if resources.CPUQuota != 0 {
+		cResources.CPUQuota = resources.CPUQuota
+	}
+	if resources.CpusetCpus != "" {
+		cResources.CpusetCpus = resources.CpusetCpus
+	}
+	if resources.CpusetMems != "" {
+		cResources.CpusetMems = resources.CpusetMems
+	}
+	if resources.Memory != 0 {
+		cResources.Memory = resources.Memory
+	}
+	if resources.MemorySwap != 0 {
+		cResources.MemorySwap = resources.MemorySwap
+	}
+	if resources.MemoryReservation != 0 {
+		cResources.MemoryReservation = resources.MemoryReservation
+	}
+	if resources.KernelMemory != 0 {
+		cResources.KernelMemory = resources.KernelMemory
+	}
+	container.Unlock()
+
+	// 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
+	// the command so we can update configs to the real world.
+	if container.IsRunning() {
+		container.Lock()
+		updateCommand(container.Command, resources)
+		container.Unlock()
+	}
+
+	if err := container.ToDiskLocking(); err != nil {
+		logrus.Errorf("Error saving updated container: %v", err)
+		return err
+	}
+
+	return nil
+}
+
 func detachMounted(path string) error {
 	return syscall.Unmount(path, syscall.MNT_DETACH)
 }

+ 6 - 0
container/container_windows.go

@@ -3,6 +3,7 @@
 package container
 
 import (
+	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/volume"
 )
@@ -47,6 +48,11 @@ func (container *Container) TmpfsMounts() []execdriver.Mount {
 	return nil
 }
 
+// UpdateContainer updates resources of a container
+func (container *Container) UpdateContainer(hostConfig *container.HostConfig) error {
+	return nil
+}
+
 // appendNetworkMounts appends any network mounts to the array of mount points passed in.
 // Windows does not support network mounts (not to be confused with SMB network mounts), so
 // this is a no-op.

+ 3 - 0
daemon/execdriver/driver.go

@@ -91,6 +91,9 @@ type Driver interface {
 	// Stats returns resource stats for a running container
 	Stats(id string) (*ResourceStats, error)
 
+	// Update updates resource configs for a container
+	Update(c *Command) error
+
 	// SupportsHooks refers to the driver capability to exploit pre/post hook functionality
 	SupportsHooks() bool
 }

+ 20 - 0
daemon/execdriver/native/driver.go

@@ -398,6 +398,26 @@ func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
 	}, nil
 }
 
+// Update updates configs for a container
+func (d *Driver) Update(c *execdriver.Command) error {
+	d.Lock()
+	cont := d.activeContainers[c.ID]
+	d.Unlock()
+	if cont == nil {
+		return execdriver.ErrNotRunning
+	}
+	config := cont.Config()
+	if err := execdriver.SetupCgroups(&config, c); err != nil {
+		return err
+	}
+
+	if err := cont.Set(config); err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // TtyConsole implements the exec driver Terminal interface.
 type TtyConsole struct {
 	console libcontainer.Console

+ 14 - 0
daemon/execdriver/windows/update.go

@@ -0,0 +1,14 @@
+// +build windows
+
+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")
+}

+ 58 - 0
daemon/update.go

@@ -0,0 +1,58 @@
+package daemon
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/types/container"
+)
+
+// ContainerUpdate updates resources of the container
+func (daemon *Daemon) ContainerUpdate(name string, hostConfig *container.HostConfig) ([]string, error) {
+	var warnings []string
+
+	warnings, err := daemon.verifyContainerSettings(hostConfig, nil)
+	if err != nil {
+		return warnings, err
+	}
+
+	if err := daemon.update(name, hostConfig); err != nil {
+		return warnings, err
+	}
+
+	return warnings, nil
+}
+
+func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) error {
+	if hostConfig == nil {
+		return nil
+	}
+
+	container, err := daemon.GetContainer(name)
+	if err != nil {
+		return err
+	}
+
+	if container.RemovalInProgress || container.Dead {
+		return fmt.Errorf("Container is marked for removal and cannot be \"update\".")
+	}
+
+	if container.IsRunning() && hostConfig.KernelMemory != 0 {
+		return fmt.Errorf("Can not update kernel memory to a running container, please stop it first.")
+	}
+
+	if err := container.UpdateContainer(hostConfig); err != nil {
+		return err
+	}
+
+	// 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 err := daemon.execDriver.Update(container.Command); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 44 - 0
docs/reference/api/docker_remote_api_v1.22.md

@@ -1003,6 +1003,50 @@ Status Codes:
 -   **404** – no such container
 -   **500** – server error
 
+### Update a container
+
+`POST /containers/(id)/update`
+
+Update resource configs of one or more containers.
+
+**Example request**:
+
+       POST /containers/(id)/update HTTP/1.1
+       Content-Type: application/json
+
+       {
+           "HostConfig": {
+               "Resources": {
+                   "BlkioWeight": 300,
+                   "CpuShares": 512,
+                   "CpuPeriod": 100000,
+                   "CpuQuota": 50000,
+                   "CpusetCpus": "0,1",
+                   "CpusetMems": "0",
+                   "Memory": 314572800,
+                   "MemorySwap": 514288000,
+                   "MemoryReservation": 209715200,
+                   "KernelMemory": 52428800,
+               }
+           }
+       }
+
+**Example response**:
+
+       HTTP/1.1 200 OK
+       Content-Type: application/json
+
+       {
+           "Warnings": []
+       }
+
+Status Codes:
+
+-   **200** – no error
+-   **400** – bad parameter
+-   **404** – no such container
+-   **500** – server error
+
 ### Rename a container
 
 `POST /containers/(id)/rename`

File diff suppressed because it is too large
+ 0 - 0
docs/reference/api/images/event_state.gliffy


+ 1 - 0
docs/reference/commandline/index.md

@@ -59,6 +59,7 @@ You start the Docker daemon with the command line. How you start the daemon affe
 * [stop](stop.md)
 * [top](top.md)
 * [unpause](unpause.md)
+* [update](update.md)
 * [wait](wait.md)
 
 ### Hub and registry commands

+ 61 - 0
docs/reference/commandline/update.md

@@ -0,0 +1,61 @@
+<!--[metadata]>
++++
+title = "update"
+description = "The update command description and usage"
+keywords = ["resources, update, dynamically"]
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+## update
+
+    Usage: docker update [OPTIONS] CONTAINER [CONTAINER...]
+
+    Updates container resource limits
+
+      --help=false               Print usage
+      --blkio-weight=0           Block IO (relative weight), between 10 and 1000
+      --cpu-shares=0             CPU shares (relative weight)
+      --cpu-period=0             Limit the CPU CFS (Completely Fair Scheduler) period
+      --cpu-quota=0              Limit the CPU CFS (Completely Fair Scheduler) quota
+      --cpuset-cpus=""           CPUs in which to allow execution (0-3, 0,1)
+      --cpuset-mems=""           Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
+      -m, --memory=""            Memory limit
+      --memory-reservation=""    Memory soft limit
+      --memory-swap=""           Total memory (memory + swap), '-1' to disable swap
+      --kernel-memory=""         Kernel memory limit: container must be stopped
+
+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.
+
+With the exception of the `--kernel-memory` value, you can specify these
+options on a running or a stopped container. You can only update
+`--kernel-memory` on a stopped container. When you run `docker update` on
+stopped container, the next time you restart it, the container uses those
+values.
+
+## EXAMPLES
+
+The following sections illustrate ways to use this command.
+
+### Update a container with cpu-shares=512
+
+To limit a container's cpu-shares to 512, first identify the container
+name or ID. You can use **docker ps** to find these values. You can also
+use the ID returned from the **docker run** command.  Then, do the following:
+
+```bash
+$ docker update --cpu-shares 512 abebf7571666
+```
+
+### Update a container with cpu-shares and memory
+
+To update multiple resource configurations for multiple containers:
+
+```bash
+$ docker update --cpu-shares 512 -m 300M abebf7571666 hopeful_morse
+```

+ 43 - 0
integration-cli/docker_api_update_unix_test.go

@@ -0,0 +1,43 @@
+// +build !windows
+
+package main
+
+import (
+	"strings"
+
+	"github.com/docker/docker/pkg/integration/checker"
+	"github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestApiUpdateContainer(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, memoryLimitSupport)
+	testRequires(c, swapMemorySupport)
+
+	name := "apiUpdateContainer"
+	hostConfig := map[string]interface{}{
+		"Memory":     314572800,
+		"MemorySwap": 524288000,
+	}
+	dockerCmd(c, "run", "-d", "--name", name, "-m", "200M", "busybox", "top")
+	_, _, err := sockRequest("POST", "/containers/"+name+"/update", hostConfig)
+	c.Assert(err, check.IsNil)
+
+	memory, err := inspectField(name, "HostConfig.Memory")
+	c.Assert(err, check.IsNil)
+	if memory != "314572800" {
+		c.Fatalf("Got the wrong memory value, we got %d, expected 314572800(300M).", memory)
+	}
+	file := "/sys/fs/cgroup/memory/memory.limit_in_bytes"
+	out, _ := dockerCmd(c, "exec", name, "cat", file)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "314572800")
+
+	memorySwap, err := inspectField(name, "HostConfig.MemorySwap")
+	c.Assert(err, check.IsNil)
+	if memorySwap != "524288000" {
+		c.Fatalf("Got the wrong memorySwap value, we got %d, expected 524288000(500M).", memorySwap)
+	}
+	file = "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
+	out, _ = dockerCmd(c, "exec", name, "cat", file)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "524288000")
+}

+ 1 - 1
integration-cli/docker_cli_help_test.go

@@ -226,7 +226,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
 		}
 
 		// Number of commands for standard release and experimental release
-		standard := 40
+		standard := 41
 		experimental := 1
 		expected := standard + experimental
 		if isLocalDaemon {

+ 162 - 0
integration-cli/docker_cli_update_unix_test.go

@@ -0,0 +1,162 @@
+// +build !windows
+
+package main
+
+import (
+	"strings"
+
+	"github.com/docker/docker/pkg/integration/checker"
+	"github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestUpdateRunningContainer(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, memoryLimitSupport)
+
+	name := "test-update-container"
+	dockerCmd(c, "run", "-d", "--name", name, "-m", "300M", "busybox", "top")
+	dockerCmd(c, "update", "-m", "500M", name)
+
+	memory, err := inspectField(name, "HostConfig.Memory")
+	c.Assert(err, check.IsNil)
+	if memory != "524288000" {
+		c.Fatalf("Got the wrong memory value, we got %d, expected 524288000(500M).", memory)
+	}
+
+	file := "/sys/fs/cgroup/memory/memory.limit_in_bytes"
+	out, _ := dockerCmd(c, "exec", name, "cat", file)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "524288000")
+}
+
+func (s *DockerSuite) TestUpdateRunningContainerWithRestart(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, memoryLimitSupport)
+
+	name := "test-update-container"
+	dockerCmd(c, "run", "-d", "--name", name, "-m", "300M", "busybox", "top")
+	dockerCmd(c, "update", "-m", "500M", name)
+	dockerCmd(c, "restart", name)
+
+	memory, err := inspectField(name, "HostConfig.Memory")
+	c.Assert(err, check.IsNil)
+	if memory != "524288000" {
+		c.Fatalf("Got the wrong memory value, we got %d, expected 524288000(500M).", memory)
+	}
+
+	file := "/sys/fs/cgroup/memory/memory.limit_in_bytes"
+	out, _ := dockerCmd(c, "exec", name, "cat", file)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "524288000")
+}
+
+func (s *DockerSuite) TestUpdateStoppedContainer(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, memoryLimitSupport)
+
+	name := "test-update-container"
+	file := "/sys/fs/cgroup/memory/memory.limit_in_bytes"
+	dockerCmd(c, "run", "--name", name, "-m", "300M", "busybox", "cat", file)
+	dockerCmd(c, "update", "-m", "500M", name)
+
+	memory, err := inspectField(name, "HostConfig.Memory")
+	c.Assert(err, check.IsNil)
+	if memory != "524288000" {
+		c.Fatalf("Got the wrong memory value, we got %d, expected 524288000(500M).", memory)
+	}
+
+	out, _ := dockerCmd(c, "start", "-a", name)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "524288000")
+}
+
+func (s *DockerSuite) TestUpdatePausedContainer(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, cpuShare)
+
+	name := "test-update-container"
+	dockerCmd(c, "run", "-d", "--name", name, "--cpu-shares", "1000", "busybox", "top")
+	dockerCmd(c, "pause", name)
+	dockerCmd(c, "update", "--cpu-shares", "500", name)
+
+	out, err := inspectField(name, "HostConfig.CPUShares")
+	c.Assert(err, check.IsNil)
+	if out != "500" {
+		c.Fatalf("Got the wrong cpu shares value, we got %d, expected 500.", out)
+	}
+
+	dockerCmd(c, "unpause", name)
+	file := "/sys/fs/cgroup/cpu/cpu.shares"
+	out, _ = dockerCmd(c, "exec", name, "cat", file)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "500")
+}
+
+func (s *DockerSuite) TestUpdateWithUntouchedFields(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, memoryLimitSupport)
+	testRequires(c, cpuShare)
+
+	name := "test-update-container"
+	dockerCmd(c, "run", "-d", "--name", name, "-m", "300M", "--cpu-shares", "800", "busybox", "top")
+	dockerCmd(c, "update", "-m", "500M", name)
+
+	// Update memory and not touch cpus, `cpuset.cpus` should still have the old value
+	out, err := inspectField(name, "HostConfig.CPUShares")
+	c.Assert(err, check.IsNil)
+	c.Assert(out, check.Equals, "800")
+
+	file := "/sys/fs/cgroup/cpu/cpu.shares"
+	out, _ = dockerCmd(c, "exec", name, "cat", file)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "800")
+}
+
+func (s *DockerSuite) TestUpdateContainerInvalidValue(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, memoryLimitSupport)
+
+	name := "test-update-container"
+	dockerCmd(c, "run", "-d", "--name", name, "-m", "300M", "busybox", "true")
+	out, _, err := dockerCmdWithError("update", "-m", "2M", name)
+	c.Assert(err, check.NotNil)
+	expected := "Minimum memory limit allowed is 4MB"
+	c.Assert(out, checker.Contains, expected)
+}
+
+func (s *DockerSuite) TestUpdateContainerWithoutFlags(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, memoryLimitSupport)
+
+	name := "test-update-container"
+	dockerCmd(c, "run", "-d", "--name", name, "-m", "300M", "busybox", "true")
+	_, _, err := dockerCmdWithError("update", name)
+	c.Assert(err, check.NotNil)
+}
+
+func (s *DockerSuite) TestUpdateKernelMemory(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+	testRequires(c, kernelMemorySupport)
+
+	name := "test-update-container"
+	dockerCmd(c, "run", "-d", "--name", name, "--kernel-memory", "50M", "busybox", "top")
+	_, _, err := dockerCmdWithError("update", "--kernel-memory", "100M", name)
+	// Update kernel memory to a running container is not allowed.
+	c.Assert(err, check.NotNil)
+
+	out, err := inspectField(name, "HostConfig.KernelMemory")
+	c.Assert(err, check.IsNil)
+	// Update kernel memory to a running container with failure should not change HostConfig
+	if out != "52428800" {
+		c.Fatalf("Got the wrong memory value, we got %d, expected 52428800(50M).", out)
+	}
+
+	dockerCmd(c, "stop", name)
+	dockerCmd(c, "update", "--kernel-memory", "100M", name)
+	dockerCmd(c, "start", name)
+
+	out, err = inspectField(name, "HostConfig.KernelMemory")
+	c.Assert(err, check.IsNil)
+	if out != "104857600" {
+		c.Fatalf("Got the wrong memory value, we got %d, expected 104857600(100M).", out)
+	}
+
+	file := "/sys/fs/cgroup/memory/memory.kmem.limit_in_bytes"
+	out, _ = dockerCmd(c, "exec", name, "cat", file)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "104857600")
+}

+ 93 - 0
man/docker-update.1.md

@@ -0,0 +1,93 @@
+% DOCKER(1) Docker User Manuals
+% Docker Community
+% JUNE 2014
+# NAME
+docker-update - Update resource configs of one or more containers
+
+# SYNOPSIS
+**docker update**
+[**--blkio-weight**[=*[BLKIO-WEIGHT]*]]
+[**--cpu-shares**[=*0*]]
+[**--cpu-period**[=*0*]]
+[**--cpu-quota**[=*0*]]
+[**--cpuset-cpus**[=*CPUSET-CPUS*]]
+[**--cpuset-mems**[=*CPUSET-MEMS*]]
+[**--help**]
+[**--kernel-memory**[=*KERNEL-MEMORY*]]
+[**-m**|**--memory**[=*MEMORY*]]
+[**--memory-reservation**[=*MEMORY-RESERVATION*]]
+[**--memory-swap**[=*MEMORY-SWAP*]]
+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.
+
+With the exception of the `--kernel-memory` value, you can specify these
+options on a running or a stopped container. You can only update
+`--kernel-memory` on a stopped container. When you run `docker update` on
+stopped container, the next time you restart it, the container uses those
+values.
+
+# OPTIONS
+**--blkio-weight**=0
+   Block IO weight (relative weight) accepts a weight value between 10 and 1000.
+
+**--cpu-shares**=0
+   CPU shares (relative weight)
+
+**--cpu-period**=0
+   Limit the CPU CFS (Completely Fair Scheduler) period
+
+**--cpu-quota**=0
+   Limit the CPU CFS (Completely Fair Scheduler) quota
+
+**--cpuset-cpus**=""
+   CPUs in which to allow execution (0-3, 0,1)
+
+**--cpuset-mems**=""
+   Memory nodes(MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
+
+**--help**
+   Print usage statement
+
+**--kernel-memory**=""
+   Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)
+
+   Note that you can not update kernel memory to a running container, it can only
+be updated to a stopped container, and affect after it's started.
+
+**-m**, **--memory**=""
+   Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
+
+**--memory-reservation**=""
+   Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)
+
+**--memory-swap**=""
+   Total memory limit (memory + swap)
+
+# EXAMPLES
+
+The following sections illustrate ways to use this command.
+
+### Update a container with cpu-shares=512
+
+To limit a container's cpu-shares to 512, first identify the container
+name or ID. You can use **docker ps** to find these values. You can also
+use the ID returned from the **docker run** command.  Then, do the following:
+
+```bash
+$ docker update --cpu-shares 512 abebf7571666
+```
+
+### Update a container with cpu-shares and memory
+
+To update multiple resource configurations for multiple containers:
+
+```bash
+$ docker update --cpu-shares 512 -m 300M abebf7571666 hopeful_morse
+```

Some files were not shown because too many files changed in this diff