Browse code

Windows: Add cpu count option

Signed-off-by: Darren Stahl <darst@microsoft.com>

Darren Stahl authored on 2016/11/02 05:02:46
Showing 12 changed files
... ...
@@ -27,10 +27,13 @@ import (
27 27
 )
28 28
 
29 29
 const (
30
-	defaultNetworkSpace = "172.16.0.0/12"
31
-	platformSupported   = true
32
-	windowsMinCPUShares = 1
33
-	windowsMaxCPUShares = 10000
30
+	defaultNetworkSpace  = "172.16.0.0/12"
31
+	platformSupported    = true
32
+	windowsMinCPUShares  = 1
33
+	windowsMaxCPUShares  = 10000
34
+	windowsMinCPUPercent = 1
35
+	windowsMaxCPUPercent = 100
36
+	windowsMinCPUCount   = 1
34 37
 )
35 38
 
36 39
 func getBlkioWeightDevices(config *containertypes.HostConfig) ([]blkiodev.WeightDevice, error) {
... ...
@@ -80,6 +83,15 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
80 80
 		return nil
81 81
 	}
82 82
 
83
+	numCPU := int64(sysinfo.NumCPU())
84
+	if hostConfig.CPUCount < 0 {
85
+		logrus.Warnf("Changing requested CPUCount of %d to minimum allowed of %d", hostConfig.CPUCount, windowsMinCPUCount)
86
+		hostConfig.CPUCount = windowsMinCPUCount
87
+	} else if hostConfig.CPUCount > numCPU {
88
+		logrus.Warnf("Changing requested CPUCount of %d to current number of processors, %d", hostConfig.CPUCount, numCPU)
89
+		hostConfig.CPUCount = numCPU
90
+	}
91
+
83 92
 	if hostConfig.CPUShares < 0 {
84 93
 		logrus.Warnf("Changing requested CPUShares of %d to minimum allowed of %d", hostConfig.CPUShares, windowsMinCPUShares)
85 94
 		hostConfig.CPUShares = windowsMinCPUShares
... ...
@@ -88,19 +100,42 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
88 88
 		hostConfig.CPUShares = windowsMaxCPUShares
89 89
 	}
90 90
 
91
+	if hostConfig.CPUPercent < 0 {
92
+		logrus.Warnf("Changing requested CPUPercent of %d to minimum allowed of %d", hostConfig.CPUPercent, windowsMinCPUPercent)
93
+		hostConfig.CPUPercent = windowsMinCPUPercent
94
+	} else if hostConfig.CPUPercent > windowsMaxCPUPercent {
95
+		logrus.Warnf("Changing requested CPUPercent of %d to maximum allowed of %d", hostConfig.CPUPercent, windowsMaxCPUPercent)
96
+		hostConfig.CPUPercent = windowsMaxCPUPercent
97
+	}
98
+
91 99
 	return nil
92 100
 }
93 101
 
94
-func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysinfo.SysInfo) ([]string, error) {
102
+func verifyContainerResources(resources *containertypes.Resources, isHyperv bool) ([]string, error) {
95 103
 	warnings := []string{}
96 104
 
97
-	// cpu subsystem checks and adjustments
98
-	if resources.CPUPercent < 0 || resources.CPUPercent > 100 {
99
-		return warnings, fmt.Errorf("Range of CPU percent is from 1 to 100")
100
-	}
101
-
102
-	if resources.CPUPercent > 0 && resources.CPUShares > 0 {
103
-		return warnings, fmt.Errorf("Conflicting options: CPU Shares and CPU Percent cannot both be set")
105
+	if !isHyperv {
106
+		// The processor resource controls are mutually exclusive on
107
+		// Windows Server Containers, the order of precedence is
108
+		// CPUCount first, then CPUShares, and CPUPercent last.
109
+		if resources.CPUCount > 0 {
110
+			if resources.CPUShares > 0 {
111
+				warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
112
+				logrus.Warn("Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
113
+				resources.CPUShares = 0
114
+			}
115
+			if resources.CPUPercent > 0 {
116
+				warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
117
+				logrus.Warn("Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
118
+				resources.CPUPercent = 0
119
+			}
120
+		} else if resources.CPUShares > 0 {
121
+			if resources.CPUPercent > 0 {
122
+				warnings = append(warnings, "Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
123
+				logrus.Warn("Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
124
+				resources.CPUPercent = 0
125
+			}
126
+		}
104 127
 	}
105 128
 
106 129
 	if resources.NanoCPUs > 0 && resources.CPUPercent > 0 {
... ...
@@ -154,7 +189,7 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi
154 154
 func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) {
155 155
 	warnings := []string{}
156 156
 
157
-	w, err := verifyContainerResources(&hostConfig.Resources, nil)
157
+	w, err := verifyContainerResources(&hostConfig.Resources, daemon.runAsHyperVContainer(hostConfig))
158 158
 	warnings = append(warnings, w...)
159 159
 	if err != nil {
160 160
 		return warnings, err
... ...
@@ -388,14 +423,14 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error
388 388
 }
389 389
 
390 390
 // runasHyperVContainer returns true if we are going to run as a Hyper-V container
391
-func (daemon *Daemon) runAsHyperVContainer(container *container.Container) bool {
392
-	if container.HostConfig.Isolation.IsDefault() {
391
+func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig) bool {
392
+	if hostConfig.Isolation.IsDefault() {
393 393
 		// Container is set to use the default, so take the default from the daemon configuration
394 394
 		return daemon.defaultIsolation.IsHyperV()
395 395
 	}
396 396
 
397 397
 	// Container is requesting an isolation mode. Honour it.
398
-	return container.HostConfig.Isolation.IsHyperV()
398
+	return hostConfig.Isolation.IsHyperV()
399 399
 
400 400
 }
401 401
 
... ...
@@ -403,7 +438,7 @@ func (daemon *Daemon) runAsHyperVContainer(container *container.Container) bool
403 403
 // container start to call mount.
404 404
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
405 405
 	// We do not mount if a Hyper-V container
406
-	if !daemon.runAsHyperVContainer(container) {
406
+	if !daemon.runAsHyperVContainer(container.HostConfig) {
407 407
 		return daemon.Mount(container)
408 408
 	}
409 409
 	return nil
... ...
@@ -413,7 +448,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
413 413
 // during the cleanup of a container to unmount.
414 414
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
415 415
 	// We do not unmount if a Hyper-V container
416
-	if !daemon.runAsHyperVContainer(container) {
416
+	if !daemon.runAsHyperVContainer(container.HostConfig) {
417 417
 		return daemon.Unmount(container)
418 418
 	}
419 419
 	return nil
... ...
@@ -86,11 +86,13 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
86 86
 	if c.HostConfig.NanoCPUs > 0 {
87 87
 		cpuPercent = uint8(c.HostConfig.NanoCPUs * 100 / int64(sysinfo.NumCPU()) / 1e9)
88 88
 	}
89
+	cpuCount := uint64(c.HostConfig.CPUCount)
89 90
 	memoryLimit := uint64(c.HostConfig.Memory)
90 91
 	s.Windows.Resources = &specs.WindowsResources{
91 92
 		CPU: &specs.WindowsCPUResources{
92 93
 			Percent: &cpuPercent,
93 94
 			Shares:  &cpuShares,
95
+			Count:   &cpuCount,
94 96
 		},
95 97
 		Memory: &specs.WindowsMemoryResources{
96 98
 			Limit: &memoryLimit,
... ...
@@ -166,6 +166,7 @@ This section lists each version from latest to oldest.  Each listing includes a
166 166
 * `GET /nodes` and `GET /node/(id or name)` now return `Addr` as part of a node's `Status`, which is the address that that node connects to the manager from.
167 167
 * The `HostConfig` field now includes `NanoCPUs` that represents CPU quota in units of 10<sup>-9</sup> CPUs.
168 168
 * `GET /info` now returns more structured information about security options.
169
+* The `HostConfig` field now includes `CpuCount` that represents the number of CPUs available for execution by the container. Windows daemon only.
169 170
 
170 171
 ### v1.24 API changes
171 172
 
... ...
@@ -303,6 +303,7 @@ Create a container
303 303
              "MemoryReservation": 0,
304 304
              "KernelMemory": 0,
305 305
              "NanoCPUs": 500000,
306
+             "CpuCount": 4,
306 307
              "CpuPercent": 80,
307 308
              "CpuShares": 512,
308 309
              "CpuPeriod": 100000,
... ...
@@ -427,7 +428,14 @@ Create a container
427 427
     -   **MemoryReservation** - Memory soft limit in bytes.
428 428
     -   **KernelMemory** - Kernel memory limit in bytes.
429 429
     -   **NanoCPUs** - CPU quota in units of 10<sup>-9</sup> CPUs.
430
-    -   **CpuPercent** - An integer value containing the usable percentage of the available CPUs. (Windows daemon only)
430
+    -   **CpuCount** - An integer value containing the number of usable CPUs.
431
+          Windows daemon only. On Windows Server containers,
432
+          the processor resource controls are mutually exclusive, the order of precedence
433
+          is CPUCount first, then CPUShares, and CPUPercent last.
434
+    -   **CpuPercent** - An integer value containing the usable percentage of
435
+          the available CPUs. Windows daemon only. On Windows Server containers,
436
+          the processor resource controls are mutually exclusive, the order of precedence
437
+          is CPUCount first, then CPUShares, and CPUPercent last.
431 438
     -   **CpuShares** - An integer value containing the container's CPU Shares
432 439
           (ie. the relative weight vs other containers).
433 440
     -   **CpuPeriod** - The length of a CPU period in microseconds.
... ...
@@ -623,6 +631,7 @@ Return low-level information on the container `id`
623 623
 			"ContainerIDFile": "",
624 624
 			"CpusetCpus": "",
625 625
 			"CpusetMems": "",
626
+			"CpuCount": 4,
626 627
 			"CpuPercent": 80,
627 628
 			"CpuShares": 0,
628 629
 			"CpuPeriod": 100000,
... ...
@@ -31,6 +31,9 @@ Options:
31 31
       --cap-drop value              Drop Linux capabilities (default [])
32 32
       --cgroup-parent string        Optional parent cgroup for the container
33 33
       --cidfile string              Write the container ID to the file
34
+      --cpu-count int               The number of CPUs available for execution by the container.
35
+                                    Windows daemon only. On Windows Server containers, this is
36
+                                    approximated as a percentage of total CPU usage.
34 37
       --cpu-percent int             CPU percent (Windows only)
35 38
       --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
36 39
       --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota
... ...
@@ -29,7 +29,14 @@ Options:
29 29
       --cap-drop value              Drop Linux capabilities (default [])
30 30
       --cgroup-parent string        Optional parent cgroup for the container
31 31
       --cidfile string              Write the container ID to the file
32
-      --cpu-percent int             CPU percent (Windows only)
32
+      --cpu-count int               The number of CPUs available for execution by the container.
33
+                                    Windows daemon only. On Windows Server containers, this is
34
+                                    approximated as a percentage of total CPU usage.
35
+      --cpu-percent int             Limit percentage of CPU available for execution
36
+                                    by the container. Windows daemon only.
37
+                                    The processor resource controls are mutually
38
+                                    exclusive, the order of precedence is CPUCount
39
+                                    first, then CPUShares, and CPUPercent last.
33 40
       --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
34 41
       --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota
35 42
   -c, --cpu-shares int              CPU shares (relative weight)
... ...
@@ -4766,3 +4766,67 @@ func (s *DockerSuite) TestRunMount(c *check.C) {
4766 4766
 		}
4767 4767
 	}
4768 4768
 }
4769
+
4770
+func (s *DockerSuite) TestRunWindowsWithCPUCount(c *check.C) {
4771
+	testRequires(c, DaemonIsWindows)
4772
+
4773
+	out, _ := dockerCmd(c, "run", "--cpu-count=1", "--name", "test", "busybox", "echo", "testing")
4774
+	c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
4775
+
4776
+	out = inspectField(c, "test", "HostConfig.CPUCount")
4777
+	c.Assert(out, check.Equals, "1")
4778
+}
4779
+
4780
+func (s *DockerSuite) TestRunWindowsWithCPUShares(c *check.C) {
4781
+	testRequires(c, DaemonIsWindows)
4782
+
4783
+	out, _ := dockerCmd(c, "run", "--cpu-shares=1000", "--name", "test", "busybox", "echo", "testing")
4784
+	c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
4785
+
4786
+	out = inspectField(c, "test", "HostConfig.CPUShares")
4787
+	c.Assert(out, check.Equals, "1000")
4788
+}
4789
+
4790
+func (s *DockerSuite) TestRunWindowsWithCPUPercent(c *check.C) {
4791
+	testRequires(c, DaemonIsWindows)
4792
+
4793
+	out, _ := dockerCmd(c, "run", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
4794
+	c.Assert(strings.TrimSpace(out), checker.Equals, "testing")
4795
+
4796
+	out = inspectField(c, "test", "HostConfig.CPUPercent")
4797
+	c.Assert(out, check.Equals, "80")
4798
+}
4799
+
4800
+func (s *DockerSuite) TestRunProcessIsolationWithCPUCountCPUSharesAndCPUPercent(c *check.C) {
4801
+	testRequires(c, DaemonIsWindows, IsolationIsProcess)
4802
+
4803
+	out, _ := dockerCmd(c, "run", "--cpu-count=1", "--cpu-shares=1000", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
4804
+	c.Assert(strings.TrimSpace(out), checker.Contains, "WARNING: Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
4805
+	c.Assert(strings.TrimSpace(out), checker.Contains, "WARNING: Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
4806
+	c.Assert(strings.TrimSpace(out), checker.Contains, "testing")
4807
+
4808
+	out = inspectField(c, "test", "HostConfig.CPUCount")
4809
+	c.Assert(out, check.Equals, "1")
4810
+
4811
+	out = inspectField(c, "test", "HostConfig.CPUShares")
4812
+	c.Assert(out, check.Equals, "0")
4813
+
4814
+	out = inspectField(c, "test", "HostConfig.CPUPercent")
4815
+	c.Assert(out, check.Equals, "0")
4816
+}
4817
+
4818
+func (s *DockerSuite) TestRunHypervIsolationWithCPUCountCPUSharesAndCPUPercent(c *check.C) {
4819
+	testRequires(c, DaemonIsWindows, IsolationIsHyperv)
4820
+
4821
+	out, _ := dockerCmd(c, "run", "--cpu-count=1", "--cpu-shares=1000", "--cpu-percent=80", "--name", "test", "busybox", "echo", "testing")
4822
+	c.Assert(strings.TrimSpace(out), checker.Contains, "testing")
4823
+
4824
+	out = inspectField(c, "test", "HostConfig.CPUCount")
4825
+	c.Assert(out, check.Equals, "1")
4826
+
4827
+	out = inspectField(c, "test", "HostConfig.CPUShares")
4828
+	c.Assert(out, check.Equals, "1000")
4829
+
4830
+	out = inspectField(c, "test", "HostConfig.CPUPercent")
4831
+	c.Assert(out, check.Equals, "80")
4832
+}
... ...
@@ -218,6 +218,18 @@ var (
218 218
 		},
219 219
 		"Test requires containers are not pausable.",
220 220
 	}
221
+	IsolationIsHyperv = testRequirement{
222
+		func() bool {
223
+			return daemonPlatform == "windows" && isolation == "hyperv"
224
+		},
225
+		"Test requires a Windows daemon running default isolation mode of hyperv.",
226
+	}
227
+	IsolationIsProcess = testRequirement{
228
+		func() bool {
229
+			return daemonPlatform == "windows" && isolation == "process"
230
+		},
231
+		"Test requires a Windows daemon running default isolation mode of process.",
232
+	}
221 233
 )
222 234
 
223 235
 // testRequires checks if the environment satisfies the requirements
... ...
@@ -110,6 +110,9 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
110 110
 
111 111
 	if spec.Windows.Resources != nil {
112 112
 		if spec.Windows.Resources.CPU != nil {
113
+			if spec.Windows.Resources.CPU.Count != nil {
114
+				configuration.ProcessorCount = uint32(*spec.Windows.Resources.CPU.Count)
115
+			}
113 116
 			if spec.Windows.Resources.CPU.Shares != nil {
114 117
 				configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares)
115 118
 			}
... ...
@@ -15,6 +15,8 @@ docker-create - Create a new container
15 15
 [**--cap-drop**[=*[]*]]
16 16
 [**--cgroup-parent**[=*CGROUP-PATH*]]
17 17
 [**--cidfile**[=*CIDFILE*]]
18
+[**--cpu-count**[=*0*]]
19
+[**--cpu-percent**[=*0*]]
18 20
 [**--cpu-period**[=*0*]]
19 21
 [**--cpu-quota**[=*0*]]
20 22
 [**--cpu-rt-period**[=*0*]]
... ...
@@ -124,6 +126,18 @@ The initial status of the container created with **docker create** is 'created'.
124 124
 **--cidfile**=""
125 125
    Write the container ID to the file
126 126
 
127
+**--cpu-count**=*0*
128
+    Limit the number of CPUs available for execution by the container.
129
+    
130
+    On Windows Server containers, this is approximated as a percentage of total CPU usage.
131
+
132
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
133
+
134
+**--cpu-percent**=*0*
135
+    Limit the percentage of CPU available for execution by a container running on a Windows daemon.
136
+
137
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
138
+
127 139
 **--cpu-period**=*0*
128 140
     Limit the CPU CFS (Completely Fair Scheduler) period
129 141
 
... ...
@@ -15,6 +15,8 @@ docker-run - Run a command in a new container
15 15
 [**--cap-drop**[=*[]*]]
16 16
 [**--cgroup-parent**[=*CGROUP-PATH*]]
17 17
 [**--cidfile**[=*CIDFILE*]]
18
+[**--cpu-count**[=*0*]]
19
+[**--cpu-percent**[=*0*]]
18 20
 [**--cpu-period**[=*0*]]
19 21
 [**--cpu-quota**[=*0*]]
20 22
 [**--cpu-rt-period**[=*0*]]
... ...
@@ -174,6 +176,18 @@ division of CPU shares:
174 174
 **--cidfile**=""
175 175
    Write the container ID to the file
176 176
 
177
+**--cpu-count**=*0*
178
+    Limit the number of CPUs available for execution by the container.
179
+    
180
+    On Windows Server containers, this is approximated as a percentage of total CPU usage.
181
+
182
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
183
+
184
+**--cpu-percent**=*0*
185
+    Limit the percentage of CPU available for execution by a container running on a Windows daemon.
186
+
187
+    On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
188
+
177 189
 **--cpu-period**=*0*
178 190
    Limit the CPU CFS (Completely Fair Scheduler) period
179 191
 
... ...
@@ -73,6 +73,7 @@ type ContainerOptions struct {
73 73
 	kernelMemory       string
74 74
 	user               string
75 75
 	workingDir         string
76
+	cpuCount           int64
76 77
 	cpuShares          int64
77 78
 	cpuPercent         int64
78 79
 	cpuPeriod          int64
... ...
@@ -227,6 +228,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
227 227
 	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
228 228
 	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
229 229
 	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
230
+	flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
230 231
 	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
231 232
 	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
232 233
 	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
... ...
@@ -529,6 +531,7 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
529 529
 		KernelMemory:         kernelMemory,
530 530
 		OomKillDisable:       &copts.oomKillDisable,
531 531
 		NanoCPUs:             copts.cpus.Value(),
532
+		CPUCount:             copts.cpuCount,
532 533
 		CPUPercent:           copts.cpuPercent,
533 534
 		CPUShares:            copts.cpuShares,
534 535
 		CPUPeriod:            copts.cpuPeriod,