The initial implementation followed the Swarm API, where
PidsLimit is located in ContainerSpec. This is not the
desired place for this property, so moving the field to
TaskTemplate.Resources in our API.
A similar change should be made in the SwarmKit API (likely
keeping the old field for backward compatibility, because
it was merged some releases back)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -97,10 +97,13 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
|
| 97 | 97 |
} |
| 98 | 98 |
if versions.LessThan(cliVersion, "1.41") {
|
| 99 | 99 |
if service.TaskTemplate.ContainerSpec != nil {
|
| 100 |
- // Capabilities and PidsLimit for docker swarm services weren't |
|
| 100 |
+ // Capabilities for docker swarm services weren't |
|
| 101 | 101 |
// supported before API version 1.41 |
| 102 | 102 |
service.TaskTemplate.ContainerSpec.Capabilities = nil |
| 103 |
- service.TaskTemplate.ContainerSpec.PidsLimit = 0 |
|
| 103 |
+ } |
|
| 104 |
+ if service.TaskTemplate.Resources != nil && service.TaskTemplate.Resources.Limits != nil {
|
|
| 105 |
+ // Limits.Pids not supported before API version 1.41 |
|
| 106 |
+ service.TaskTemplate.Resources.Limits.Pids = 0 |
|
| 104 | 107 |
} |
| 105 | 108 |
|
| 106 | 109 |
// jobs were only introduced in API version 1.41. Nil out both Job |
| ... | ... |
@@ -17,8 +17,7 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
| 17 | 17 |
spec := &swarm.ServiceSpec{
|
| 18 | 18 |
TaskTemplate: swarm.TaskSpec{
|
| 19 | 19 |
ContainerSpec: &swarm.ContainerSpec{
|
| 20 |
- Sysctls: expectedSysctls, |
|
| 21 |
- PidsLimit: 300, |
|
| 20 |
+ Sysctls: expectedSysctls, |
|
| 22 | 21 |
Privileges: &swarm.Privileges{
|
| 23 | 22 |
CredentialSpec: &swarm.CredentialSpec{
|
| 24 | 23 |
Config: "someconfig", |
| ... | ... |
@@ -44,6 +43,11 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
| 44 | 44 |
Placement: &swarm.Placement{
|
| 45 | 45 |
MaxReplicas: 222, |
| 46 | 46 |
}, |
| 47 |
+ Resources: &swarm.ResourceRequirements{
|
|
| 48 |
+ Limits: &swarm.Limit{
|
|
| 49 |
+ Pids: 300, |
|
| 50 |
+ }, |
|
| 51 |
+ }, |
|
| 47 | 52 |
}, |
| 48 | 53 |
} |
| 49 | 54 |
|
| ... | ... |
@@ -55,10 +59,10 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
| 55 | 55 |
t.Error("Sysctls was stripped from spec")
|
| 56 | 56 |
} |
| 57 | 57 |
|
| 58 |
- if spec.TaskTemplate.ContainerSpec.PidsLimit == 0 {
|
|
| 58 |
+ if spec.TaskTemplate.Resources.Limits.Pids == 0 {
|
|
| 59 | 59 |
t.Error("PidsLimit was stripped from spec")
|
| 60 | 60 |
} |
| 61 |
- if spec.TaskTemplate.ContainerSpec.PidsLimit != 300 {
|
|
| 61 |
+ if spec.TaskTemplate.Resources.Limits.Pids != 300 {
|
|
| 62 | 62 |
t.Error("PidsLimit did not preserve the value from spec")
|
| 63 | 63 |
} |
| 64 | 64 |
|
| ... | ... |
@@ -80,7 +84,7 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
| 80 | 80 |
t.Error("Sysctls was not stripped from spec")
|
| 81 | 81 |
} |
| 82 | 82 |
|
| 83 |
- if spec.TaskTemplate.ContainerSpec.PidsLimit != 0 {
|
|
| 83 |
+ if spec.TaskTemplate.Resources.Limits.Pids != 0 {
|
|
| 84 | 84 |
t.Error("PidsLimit was not stripped from spec")
|
| 85 | 85 |
} |
| 86 | 86 |
|
| ... | ... |
@@ -638,6 +638,13 @@ definitions: |
| 638 | 638 |
type: "integer" |
| 639 | 639 |
format: "int64" |
| 640 | 640 |
example: 8272408576 |
| 641 |
+ Pids: |
|
| 642 |
+ description: | |
|
| 643 |
+ Limits the maximum number of PIDs in the container. Set `0` for unlimited. |
|
| 644 |
+ type: "integer" |
|
| 645 |
+ format: "int64" |
|
| 646 |
+ default: 0 |
|
| 647 |
+ example: 100 |
|
| 641 | 648 |
|
| 642 | 649 |
ResourceObject: |
| 643 | 650 |
description: | |
| ... | ... |
@@ -3220,13 +3227,6 @@ definitions: |
| 3220 | 3220 |
configured on the daemon) is used. |
| 3221 | 3221 |
type: "boolean" |
| 3222 | 3222 |
x-nullable: true |
| 3223 |
- PidsLimit: |
|
| 3224 |
- description: | |
|
| 3225 |
- Tune a container's PIDs limit. Set `0` for unlimited. |
|
| 3226 |
- type: "integer" |
|
| 3227 |
- format: "int64" |
|
| 3228 |
- default: 0 |
|
| 3229 |
- example: 100 |
|
| 3230 | 3223 |
Sysctls: |
| 3231 | 3224 |
description: | |
| 3232 | 3225 |
Set kernel namedspaced parameters (sysctls) in the container. |
| ... | ... |
@@ -72,7 +72,6 @@ type ContainerSpec struct {
|
| 72 | 72 |
Secrets []*SecretReference `json:",omitempty"` |
| 73 | 73 |
Configs []*ConfigReference `json:",omitempty"` |
| 74 | 74 |
Isolation container.Isolation `json:",omitempty"` |
| 75 |
- PidsLimit int64 `json:",omitempty"` |
|
| 76 | 75 |
Sysctls map[string]string `json:",omitempty"` |
| 77 | 76 |
Capabilities []string `json:",omitempty"` |
| 78 | 77 |
} |
| ... | ... |
@@ -36,7 +36,6 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
|
| 36 | 36 |
Configs: configReferencesFromGRPC(c.Configs), |
| 37 | 37 |
Isolation: IsolationFromGRPC(c.Isolation), |
| 38 | 38 |
Init: initFromGRPC(c.Init), |
| 39 |
- PidsLimit: c.PidsLimit, |
|
| 40 | 39 |
Sysctls: c.Sysctls, |
| 41 | 40 |
Capabilities: c.Capabilities, |
| 42 | 41 |
} |
| ... | ... |
@@ -264,7 +263,6 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
| 264 | 264 |
Secrets: secretReferencesToGRPC(c.Secrets), |
| 265 | 265 |
Isolation: isolationToGRPC(c.Isolation), |
| 266 | 266 |
Init: initToGRPC(c.Init), |
| 267 |
- PidsLimit: c.PidsLimit, |
|
| 268 | 267 |
Sysctls: c.Sysctls, |
| 269 | 268 |
Capabilities: c.Capabilities, |
| 270 | 269 |
} |
| ... | ... |
@@ -193,6 +193,10 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
|
| 193 | 193 |
if err != nil {
|
| 194 | 194 |
return swarmapi.ServiceSpec{}, err
|
| 195 | 195 |
} |
| 196 |
+ if s.TaskTemplate.Resources != nil && s.TaskTemplate.Resources.Limits != nil {
|
|
| 197 |
+ // TODO remove this (or keep for backward compat) once SwarmKit API moved PidsLimit into Resources |
|
| 198 |
+ containerSpec.PidsLimit = s.TaskTemplate.Resources.Limits.Pids |
|
| 199 |
+ } |
|
| 196 | 200 |
spec.Task.Runtime = &swarmapi.TaskSpec_Container{Container: containerSpec}
|
| 197 | 201 |
} else {
|
| 198 | 202 |
// If the ContainerSpec is nil, we can't set the task runtime |
| ... | ... |
@@ -396,15 +400,31 @@ func GenericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []types.Ge |
| 396 | 396 |
return generic |
| 397 | 397 |
} |
| 398 | 398 |
|
| 399 |
-func resourcesFromGRPC(res *swarmapi.ResourceRequirements) *types.ResourceRequirements {
|
|
| 399 |
+// resourcesFromGRPC creates a ResourceRequirements from the GRPC TaskSpec. |
|
| 400 |
+// We currently require the whole TaskSpec to be passed, because PidsLimit |
|
| 401 |
+// is returned as part of the container spec, instead of Resources |
|
| 402 |
+// TODO move PidsLimit to Resources in the Swarm API |
|
| 403 |
+func resourcesFromGRPC(ts *swarmapi.TaskSpec) *types.ResourceRequirements {
|
|
| 400 | 404 |
var resources *types.ResourceRequirements |
| 401 |
- if res != nil {
|
|
| 402 |
- resources = &types.ResourceRequirements{}
|
|
| 405 |
+ |
|
| 406 |
+ if cs := ts.GetContainer(); cs != nil && cs.PidsLimit != 0 {
|
|
| 407 |
+ resources = &types.ResourceRequirements{
|
|
| 408 |
+ Limits: &types.Limit{
|
|
| 409 |
+ Pids: cs.PidsLimit, |
|
| 410 |
+ }, |
|
| 411 |
+ } |
|
| 412 |
+ } |
|
| 413 |
+ if ts.Resources != nil {
|
|
| 414 |
+ if resources == nil {
|
|
| 415 |
+ resources = &types.ResourceRequirements{}
|
|
| 416 |
+ } |
|
| 417 |
+ res := ts.Resources |
|
| 403 | 418 |
if res.Limits != nil {
|
| 404 |
- resources.Limits = &types.Limit{
|
|
| 405 |
- NanoCPUs: res.Limits.NanoCPUs, |
|
| 406 |
- MemoryBytes: res.Limits.MemoryBytes, |
|
| 419 |
+ if resources.Limits == nil {
|
|
| 420 |
+ resources.Limits = &types.Limit{}
|
|
| 407 | 421 |
} |
| 422 |
+ resources.Limits.NanoCPUs = res.Limits.NanoCPUs |
|
| 423 |
+ resources.Limits.MemoryBytes = res.Limits.MemoryBytes |
|
| 408 | 424 |
} |
| 409 | 425 |
if res.Reservations != nil {
|
| 410 | 426 |
resources.Reservations = &types.Resources{
|
| ... | ... |
@@ -441,6 +461,7 @@ func resourcesToGRPC(res *types.ResourceRequirements) *swarmapi.ResourceRequirem |
| 441 | 441 |
if res != nil {
|
| 442 | 442 |
reqs = &swarmapi.ResourceRequirements{}
|
| 443 | 443 |
if res.Limits != nil {
|
| 444 |
+ // TODO add PidsLimit once Swarm API has been updated to move it into Limits |
|
| 444 | 445 |
reqs.Limits = &swarmapi.Resources{
|
| 445 | 446 |
NanoCPUs: res.Limits.NanoCPUs, |
| 446 | 447 |
MemoryBytes: res.Limits.MemoryBytes, |
| ... | ... |
@@ -657,7 +678,7 @@ func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
|
| 657 | 657 |
} |
| 658 | 658 |
|
| 659 | 659 |
t := types.TaskSpec{
|
| 660 |
- Resources: resourcesFromGRPC(taskSpec.Resources), |
|
| 660 |
+ Resources: resourcesFromGRPC(&taskSpec), |
|
| 661 | 661 |
RestartPolicy: restartPolicyFromGRPC(taskSpec.Restart), |
| 662 | 662 |
Placement: placementFromGRPC(taskSpec.Placement), |
| 663 | 663 |
LogDriver: driverFromGRPC(taskSpec.LogDriver), |
| ... | ... |
@@ -31,12 +31,13 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 31 | 31 |
* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
|
| 32 | 32 |
* `GET /tasks` now returns `Capabilities` as part of the `ContainerSpec`. |
| 33 | 33 |
* `GET /tasks/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
|
| 34 |
-* `GET /services` now returns `PidsLimit` as part of the `ContainerSpec`. |
|
| 35 |
-* `GET /services/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
|
|
| 36 |
-* `POST /services/create` now accepts `PidsLimit` as part of the `ContainerSpec`. |
|
| 37 |
-* `POST /services/{id}/update` now accepts `PidsLimit` as part of the `ContainerSpec`.
|
|
| 38 |
-* `GET /tasks` now returns `PidsLimit` as part of the `ContainerSpec`. |
|
| 39 |
-* `GET /tasks/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
|
|
| 34 |
+* `GET /services` now returns `Pids` in `TaskTemplate.Resources.Limits`. |
|
| 35 |
+* `GET /services/{id}` now returns `Pids` in `TaskTemplate.Resources.Limits`.
|
|
| 36 |
+* `POST /services/create` now accepts `Pids` in `TaskTemplate.Resources.Limits`. |
|
| 37 |
+* `POST /services/{id}/update` now accepts `Pids` in `TaskTemplate.Resources.Limits`
|
|
| 38 |
+ to limit the maximum number of PIDs. |
|
| 39 |
+* `GET /tasks` now returns `Pids` in `TaskTemplate.Resources.Limits`. |
|
| 40 |
+* `GET /tasks/{id}` now returns `Pids` in `TaskTemplate.Resources.Limits`.
|
|
| 40 | 41 |
* `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property. |
| 41 | 42 |
Set the property to `host` to create the container in the daemon's cgroup namespace, or |
| 42 | 43 |
`private` to create the container in its own private cgroup namespace. The per-daemon |
| ... | ... |
@@ -196,11 +196,11 @@ func ServiceWithCapabilities(Capabilities []string) ServiceSpecOpt {
|
| 196 | 196 |
} |
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 |
-// ServiceWithPidsLimit sets the PidsLimit option of the service's ContainerSpec. |
|
| 199 |
+// ServiceWithPidsLimit sets the PidsLimit option of the service's Resources.Limits. |
|
| 200 | 200 |
func ServiceWithPidsLimit(limit int64) ServiceSpecOpt {
|
| 201 | 201 |
return func(spec *swarmtypes.ServiceSpec) {
|
| 202 |
- ensureContainerSpec(spec) |
|
| 203 |
- spec.TaskTemplate.ContainerSpec.PidsLimit = limit |
|
| 202 |
+ ensureResources(spec) |
|
| 203 |
+ spec.TaskTemplate.Resources.Limits.Pids = limit |
|
| 204 | 204 |
} |
| 205 | 205 |
} |
| 206 | 206 |
|
| ... | ... |
@@ -235,6 +235,18 @@ func ExecTask(t *testing.T, d *daemon.Daemon, task swarmtypes.Task, config types |
| 235 | 235 |
return attach |
| 236 | 236 |
} |
| 237 | 237 |
|
| 238 |
+func ensureResources(spec *swarmtypes.ServiceSpec) {
|
|
| 239 |
+ if spec.TaskTemplate.Resources == nil {
|
|
| 240 |
+ spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{}
|
|
| 241 |
+ } |
|
| 242 |
+ if spec.TaskTemplate.Resources.Limits == nil {
|
|
| 243 |
+ spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{}
|
|
| 244 |
+ } |
|
| 245 |
+ if spec.TaskTemplate.Resources.Reservations == nil {
|
|
| 246 |
+ spec.TaskTemplate.Resources.Reservations = &swarmtypes.Resources{}
|
|
| 247 |
+ } |
|
| 248 |
+} |
|
| 249 |
+ |
|
| 238 | 250 |
func ensureContainerSpec(spec *swarmtypes.ServiceSpec) {
|
| 239 | 251 |
if spec.TaskTemplate.ContainerSpec == nil {
|
| 240 | 252 |
spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{}
|
| ... | ... |
@@ -295,7 +295,13 @@ func TestServiceUpdatePidsLimit(t *testing.T) {
|
| 295 | 295 |
serviceID = swarm.CreateService(t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit)) |
| 296 | 296 |
} else {
|
| 297 | 297 |
service = getService(t, cli, serviceID) |
| 298 |
- service.Spec.TaskTemplate.ContainerSpec.PidsLimit = tc.pidsLimit |
|
| 298 |
+ if service.Spec.TaskTemplate.Resources == nil {
|
|
| 299 |
+ service.Spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{}
|
|
| 300 |
+ } |
|
| 301 |
+ if service.Spec.TaskTemplate.Resources.Limits == nil {
|
|
| 302 |
+ service.Spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{}
|
|
| 303 |
+ } |
|
| 304 |
+ service.Spec.TaskTemplate.Resources.Limits.Pids = tc.pidsLimit |
|
| 299 | 305 |
_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
| 300 | 306 |
assert.NilError(t, err) |
| 301 | 307 |
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll) |
| ... | ... |
@@ -304,7 +310,7 @@ func TestServiceUpdatePidsLimit(t *testing.T) {
|
| 304 | 304 |
poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, 1), swarm.ServicePoll) |
| 305 | 305 |
service = getService(t, cli, serviceID) |
| 306 | 306 |
container := getServiceTaskContainer(ctx, t, cli, serviceID) |
| 307 |
- assert.Equal(t, service.Spec.TaskTemplate.ContainerSpec.PidsLimit, tc.expected) |
|
| 307 |
+ assert.Equal(t, service.Spec.TaskTemplate.Resources.Limits.Pids, tc.expected) |
|
| 308 | 308 |
if tc.expected == 0 {
|
| 309 | 309 |
if container.HostConfig.Resources.PidsLimit != nil {
|
| 310 | 310 |
t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil")
|