Browse code

Add `--cpus` support for `docker update`

This fix tries to address the issue raised in 31032 where it was
not possible to specify `--cpus` for `docker update`.

This fix adds `--cpus` support for `docker update`. In case both
`--cpus` and `--cpu-period/--cpu-quota` have been specified,
an error will be returned.

Related docs has been updated.

Integration tests have been added.

This fix fixes 31032.

This fix is related to 27921, 27958.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2017/02/18 15:04:37
Showing 6 changed files
... ...
@@ -28,6 +28,7 @@ type updateOptions struct {
28 28
 	memorySwap         opts.MemSwapBytes
29 29
 	kernelMemory       opts.MemBytes
30 30
 	restartPolicy      string
31
+	cpus               opts.NanoCPUs
31 32
 
32 33
 	nFlag int
33 34
 
... ...
@@ -66,6 +67,9 @@ func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
66 66
 	flags.Var(&opts.kernelMemory, "kernel-memory", "Kernel memory limit")
67 67
 	flags.StringVar(&opts.restartPolicy, "restart", "", "Restart policy to apply when a container exits")
68 68
 
69
+	flags.Var(&opts.cpus, "cpus", "Number of CPUs")
70
+	flags.SetAnnotation("cpus", "version", []string{"1.29"})
71
+
69 72
 	return cmd
70 73
 }
71 74
 
... ...
@@ -97,6 +101,7 @@ func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error {
97 97
 		CPUQuota:           opts.cpuQuota,
98 98
 		CPURealtimePeriod:  opts.cpuRealtimePeriod,
99 99
 		CPURealtimeRuntime: opts.cpuRealtimeRuntime,
100
+		NanoCPUs:           opts.cpus.Value(),
100 101
 	}
101 102
 
102 103
 	updateConfig := containertypes.UpdateConfig{
... ...
@@ -286,12 +286,33 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
286 286
 	// update resources of container
287 287
 	resources := hostConfig.Resources
288 288
 	cResources := &container.HostConfig.Resources
289
+
290
+	// validate NanoCPUs, CPUPeriod, and CPUQuota
291
+	// Becuase NanoCPU effectively updates CPUPeriod/CPUQuota,
292
+	// once NanoCPU is already set, updating CPUPeriod/CPUQuota will be blocked, and vice versa.
293
+	// In the following we make sure the intended update (resources) does not conflict with the existing (cResource).
294
+	if resources.NanoCPUs > 0 && cResources.CPUPeriod > 0 {
295
+		return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Period has already been set")
296
+	}
297
+	if resources.NanoCPUs > 0 && cResources.CPUQuota > 0 {
298
+		return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Quota has already been set")
299
+	}
300
+	if resources.CPUPeriod > 0 && cResources.NanoCPUs > 0 {
301
+		return fmt.Errorf("Conflicting options: CPU Period cannot be updated as NanoCPUs has already been set")
302
+	}
303
+	if resources.CPUQuota > 0 && cResources.NanoCPUs > 0 {
304
+		return fmt.Errorf("Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set")
305
+	}
306
+
289 307
 	if resources.BlkioWeight != 0 {
290 308
 		cResources.BlkioWeight = resources.BlkioWeight
291 309
 	}
292 310
 	if resources.CPUShares != 0 {
293 311
 		cResources.CPUShares = resources.CPUShares
294 312
 	}
313
+	if resources.NanoCPUs != 0 {
314
+		cResources.NanoCPUs = resources.NanoCPUs
315
+	}
295 316
 	if resources.CPUPeriod != 0 {
296 317
 		cResources.CPUPeriod = resources.CPUPeriod
297 318
 	}
... ...
@@ -3,6 +3,8 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
+	"time"
7
+
6 8
 	"github.com/docker/docker/api/types/container"
7 9
 	"github.com/docker/docker/libcontainerd"
8 10
 )
... ...
@@ -11,8 +13,13 @@ func toContainerdResources(resources container.Resources) libcontainerd.Resource
11 11
 	var r libcontainerd.Resources
12 12
 	r.BlkioWeight = uint64(resources.BlkioWeight)
13 13
 	r.CpuShares = uint64(resources.CPUShares)
14
-	r.CpuPeriod = uint64(resources.CPUPeriod)
15
-	r.CpuQuota = uint64(resources.CPUQuota)
14
+	if resources.NanoCPUs != 0 {
15
+		r.CpuPeriod = uint64(100 * time.Millisecond / time.Microsecond)
16
+		r.CpuQuota = uint64(resources.NanoCPUs) * r.CpuPeriod / 1e9
17
+	} else {
18
+		r.CpuPeriod = uint64(resources.CPUPeriod)
19
+		r.CpuQuota = uint64(resources.CPUQuota)
20
+	}
16 21
 	r.CpusetCpus = resources.CpusetCpus
17 22
 	r.CpusetMems = resources.CpusetMems
18 23
 	r.MemoryLimit = uint64(resources.Memory)
... ...
@@ -39,6 +39,7 @@ keywords: "API, Docker, rcli, REST, documentation"
39 39
 * `POST /services/create` and `POST /services/(id or name)/update` now accept a `rollback` value for `FailureAction`.
40 40
 * `POST /services/create` and `POST /services/(id or name)/update` now accept an optional `RollbackConfig` object which specifies rollback options.
41 41
 * `GET /services` now supports a `mode` filter to filter services based on the service mode (either `global` or `replicated`).
42
+* `POST /containers/(name)/update` now supports updating `NanoCPUs` that represents CPU quota in units of 10<sup>-9</sup> CPUs.
42 43
 
43 44
 ## v1.27 API changes
44 45
 
... ...
@@ -21,12 +21,13 @@ Usage:  docker update [OPTIONS] CONTAINER [CONTAINER...]
21 21
 Update configuration of one or more containers
22 22
 
23 23
 Options:
24
-      --blkio-weight value          Block IO (relative weight), between 10 and 1000
24
+      --blkio-weight uint16         Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)
25 25
       --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
26 26
       --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota
27
-  -c, --cpu-shares int              CPU shares (relative weight)
28 27
       --cpu-rt-period int           Limit the CPU real-time period in microseconds
29 28
       --cpu-rt-runtime int          Limit the CPU real-time runtime in microseconds
29
+  -c, --cpu-shares int              CPU shares (relative weight)
30
+      --cpus decimal                Number of CPUs (default 0.000)
30 31
       --cpuset-cpus string          CPUs in which to allow execution (0-3, 0,1)
31 32
       --cpuset-mems string          MEMs in which to allow execution (0-3, 0,1)
32 33
       --help                        Print usage
... ...
@@ -282,3 +282,38 @@ func (s *DockerSuite) TestUpdateNotAffectMonitorRestartPolicy(c *check.C) {
282 282
 	c.Assert(err, checker.IsNil)
283 283
 	c.Assert(waitRun(id), checker.IsNil)
284 284
 }
285
+
286
+func (s *DockerSuite) TestUpdateWithNanoCPUs(c *check.C) {
287
+	testRequires(c, cpuCfsQuota, cpuCfsPeriod)
288
+
289
+	file1 := "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
290
+	file2 := "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
291
+
292
+	out, _ := dockerCmd(c, "run", "-d", "--cpus", "0.5", "--name", "top", "busybox", "top")
293
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
294
+
295
+	out, _ = dockerCmd(c, "exec", "top", "sh", "-c", fmt.Sprintf("cat %s && cat %s", file1, file2))
296
+	c.Assert(strings.TrimSpace(out), checker.Equals, "50000\n100000")
297
+
298
+	out = inspectField(c, "top", "HostConfig.NanoCpus")
299
+	c.Assert(out, checker.Equals, "5e+08", check.Commentf("setting the Nano CPUs failed"))
300
+	out = inspectField(c, "top", "HostConfig.CpuQuota")
301
+	c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS quota should be 0"))
302
+	out = inspectField(c, "top", "HostConfig.CpuPeriod")
303
+	c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS period should be 0"))
304
+
305
+	out, _, err := dockerCmdWithError("update", "--cpu-quota", "80000", "top")
306
+	c.Assert(err, checker.NotNil)
307
+	c.Assert(out, checker.Contains, "Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set")
308
+
309
+	out, _ = dockerCmd(c, "update", "--cpus", "0.8", "top")
310
+	out = inspectField(c, "top", "HostConfig.NanoCpus")
311
+	c.Assert(out, checker.Equals, "8e+08", check.Commentf("updating the Nano CPUs failed"))
312
+	out = inspectField(c, "top", "HostConfig.CpuQuota")
313
+	c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS quota should be 0"))
314
+	out = inspectField(c, "top", "HostConfig.CpuPeriod")
315
+	c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS period should be 0"))
316
+
317
+	out, _ = dockerCmd(c, "exec", "top", "sh", "-c", fmt.Sprintf("cat %s && cat %s", file1, file2))
318
+	c.Assert(strings.TrimSpace(out), checker.Equals, "80000\n100000")
319
+}