Browse code

API: swarm: move PidsLimit to TaskTemplate.Resources

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>

Sebastiaan van Stijn authored on 2020/05/09 04:11:51
Showing 10 changed files
... ...
@@ -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
 }
... ...
@@ -103,6 +103,7 @@ type Resources struct {
103 103
 type Limit struct {
104 104
 	NanoCPUs    int64 `json:",omitempty"`
105 105
 	MemoryBytes int64 `json:",omitempty"`
106
+	Pids        int64 `json:",omitempty"`
106 107
 }
107 108
 
108 109
 // GenericResource represents a "user defined" resource which can
... ...
@@ -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")