Skip to content

Commit 157c53c

Browse files
committed
Add API support for PidsLimit on services
Support for PidsLimit was added to SwarmKit in moby/swarmkit/pull/2415, but never exposed through the Docker remove API. This patch exposes the feature in the repote API. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent f4b0673 commit 157c53c

File tree

9 files changed

+134
-4
lines changed

9 files changed

+134
-4
lines changed

api/server/router/swarm/helpers.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,10 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
9797
}
9898
if versions.LessThan(cliVersion, "1.41") {
9999
if service.TaskTemplate.ContainerSpec != nil {
100-
// Capabilities for docker swarm services weren't supported before
101-
// API version 1.41
100+
// Capabilities and PidsLimit for docker swarm services weren't
101+
// supported before API version 1.41
102102
service.TaskTemplate.ContainerSpec.Capabilities = nil
103+
service.TaskTemplate.ContainerSpec.PidsLimit = 0
103104
}
104105

105106
// jobs were only introduced in API version 1.41. Nil out both Job

api/server/router/swarm/helpers_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ func TestAdjustForAPIVersion(t *testing.T) {
1717
spec := &swarm.ServiceSpec{
1818
TaskTemplate: swarm.TaskSpec{
1919
ContainerSpec: &swarm.ContainerSpec{
20-
Sysctls: expectedSysctls,
20+
Sysctls: expectedSysctls,
21+
PidsLimit: 300,
2122
Privileges: &swarm.Privileges{
2223
CredentialSpec: &swarm.CredentialSpec{
2324
Config: "someconfig",
@@ -49,11 +50,18 @@ func TestAdjustForAPIVersion(t *testing.T) {
4950
// first, does calling this with a later version correctly NOT strip
5051
// fields? do the later version first, so we can reuse this spec in the
5152
// next test.
52-
adjustForAPIVersion("1.40", spec)
53+
adjustForAPIVersion("1.41", spec)
5354
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls) {
5455
t.Error("Sysctls was stripped from spec")
5556
}
5657

58+
if spec.TaskTemplate.ContainerSpec.PidsLimit == 0 {
59+
t.Error("PidsLimit was stripped from spec")
60+
}
61+
if spec.TaskTemplate.ContainerSpec.PidsLimit != 300 {
62+
t.Error("PidsLimit did not preserve the value from spec")
63+
}
64+
5765
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "someconfig" {
5866
t.Error("CredentialSpec.Config field was stripped from spec")
5967
}
@@ -72,6 +80,10 @@ func TestAdjustForAPIVersion(t *testing.T) {
7280
t.Error("Sysctls was not stripped from spec")
7381
}
7482

83+
if spec.TaskTemplate.ContainerSpec.PidsLimit != 0 {
84+
t.Error("PidsLimit was not stripped from spec")
85+
}
86+
7587
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "" {
7688
t.Error("CredentialSpec.Config field was not stripped from spec")
7789
}

api/swagger.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,6 +2967,13 @@ definitions:
29672967
description: "Run an init inside the container that forwards signals and reaps processes. This field is omitted if empty, and the default (as configured on the daemon) is used."
29682968
type: "boolean"
29692969
x-nullable: true
2970+
PidsLimit:
2971+
description: |
2972+
Tune a container's PIDs limit. Set `0` for unlimited.
2973+
type: "integer"
2974+
format: "int64"
2975+
default: 0
2976+
example: 100
29702977
Sysctls:
29712978
description: |
29722979
Set kernel namedspaced parameters (sysctls) in the container.

api/types/swarm/container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type ContainerSpec struct {
7272
Secrets []*SecretReference `json:",omitempty"`
7373
Configs []*ConfigReference `json:",omitempty"`
7474
Isolation container.Isolation `json:",omitempty"`
75+
PidsLimit int64 `json:",omitempty"`
7576
Sysctls map[string]string `json:",omitempty"`
7677
Capabilities []string `json:",omitempty"`
7778
}

daemon/cluster/convert/container.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
3636
Configs: configReferencesFromGRPC(c.Configs),
3737
Isolation: IsolationFromGRPC(c.Isolation),
3838
Init: initFromGRPC(c.Init),
39+
PidsLimit: c.PidsLimit,
3940
Sysctls: c.Sysctls,
4041
Capabilities: c.Capabilities,
4142
}
@@ -263,6 +264,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
263264
Secrets: secretReferencesToGRPC(c.Secrets),
264265
Isolation: isolationToGRPC(c.Isolation),
265266
Init: initToGRPC(c.Init),
267+
PidsLimit: c.PidsLimit,
266268
Sysctls: c.Sysctls,
267269
Capabilities: c.Capabilities,
268270
}

daemon/cluster/executor/container/container.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@ func (c *containerConfig) volumeCreateRequest(mount *api.Mount) *volumetypes.Vol
431431
func (c *containerConfig) resources() enginecontainer.Resources {
432432
resources := enginecontainer.Resources{}
433433

434+
// set pids limit
435+
pidsLimit := c.spec().PidsLimit
436+
if pidsLimit > 0 {
437+
resources.PidsLimit = &pidsLimit
438+
}
439+
434440
// If no limits are specified let the engine use its defaults.
435441
//
436442
// TODO(aluzzardi): We might want to set some limits anyway otherwise

docs/api/version-history.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ keywords: "API, Docker, rcli, REST, documentation"
3030
* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
3131
* `GET /tasks` now returns `Capabilities` as part of the `ContainerSpec`.
3232
* `GET /tasks/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
33+
* `GET /services` now returns `PidsLimit` as part of the `ContainerSpec`.
34+
* `GET /services/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
35+
* `POST /services/create` now accepts `PidsLimit` as part of the `ContainerSpec`.
36+
* `POST /services/{id}/update` now accepts `PidsLimit` as part of the `ContainerSpec`.
37+
* `GET /tasks` now returns `PidsLimit` as part of the `ContainerSpec`.
38+
* `GET /tasks/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
3339
* `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property.
3440
Set the property to `host` to create the container in the daemon's cgroup namespace, or
3541
`private` to create the container in its own private cgroup namespace. The per-daemon

integration/internal/swarm/service.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ func ServiceWithCapabilities(Capabilities []string) ServiceSpecOpt {
196196
}
197197
}
198198

199+
// ServiceWithPidsLimit sets the PidsLimit option of the service's ContainerSpec.
200+
func ServiceWithPidsLimit(limit int64) ServiceSpecOpt {
201+
return func(spec *swarmtypes.ServiceSpec) {
202+
ensureContainerSpec(spec)
203+
spec.TaskTemplate.ContainerSpec.PidsLimit = limit
204+
}
205+
}
206+
199207
// GetRunningTasks gets the list of running tasks for a service
200208
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
201209
t.Helper()

integration/service/update_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"testing"
66

77
"github.com/docker/docker/api/types"
8+
"github.com/docker/docker/api/types/filters"
89
swarmtypes "github.com/docker/docker/api/types/swarm"
10+
"github.com/docker/docker/api/types/versions"
911
"github.com/docker/docker/client"
1012
"github.com/docker/docker/integration/internal/network"
1113
"github.com/docker/docker/integration/internal/swarm"
@@ -248,6 +250,91 @@ func TestServiceUpdateNetwork(t *testing.T) {
248250
assert.NilError(t, err)
249251
}
250252

253+
// TestServiceUpdatePidsLimit tests creating and updating a service with PidsLimit
254+
func TestServiceUpdatePidsLimit(t *testing.T) {
255+
skip.If(
256+
t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
257+
"setting pidslimit for services is not supported before api v1.41",
258+
)
259+
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
260+
tests := []struct {
261+
name string
262+
pidsLimit int64
263+
expected int64
264+
}{
265+
{
266+
name: "create service with PidsLimit 300",
267+
pidsLimit: 300,
268+
expected: 300,
269+
},
270+
{
271+
name: "unset PidsLimit to 0",
272+
pidsLimit: 0,
273+
expected: 0,
274+
},
275+
{
276+
name: "update PidsLimit to 100",
277+
pidsLimit: 100,
278+
expected: 100,
279+
},
280+
}
281+
282+
defer setupTest(t)()
283+
d := swarm.NewSwarm(t, testEnv)
284+
defer d.Stop(t)
285+
cli := d.NewClientT(t)
286+
defer func() { _ = cli.Close() }()
287+
ctx := context.Background()
288+
var (
289+
serviceID string
290+
service swarmtypes.Service
291+
)
292+
for i, tc := range tests {
293+
t.Run(tc.name, func(t *testing.T) {
294+
if i == 0 {
295+
serviceID = swarm.CreateService(t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit))
296+
} else {
297+
service = getService(t, cli, serviceID)
298+
service.Spec.TaskTemplate.ContainerSpec.PidsLimit = tc.pidsLimit
299+
_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
300+
assert.NilError(t, err)
301+
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
302+
}
303+
304+
poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, 1), swarm.ServicePoll)
305+
service = getService(t, cli, serviceID)
306+
container := getServiceTaskContainer(ctx, t, cli, serviceID)
307+
assert.Equal(t, service.Spec.TaskTemplate.ContainerSpec.PidsLimit, tc.expected)
308+
if tc.expected == 0 {
309+
if container.HostConfig.Resources.PidsLimit != nil {
310+
t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil")
311+
}
312+
} else {
313+
assert.Assert(t, container.HostConfig.Resources.PidsLimit != nil)
314+
assert.Equal(t, *container.HostConfig.Resources.PidsLimit, tc.expected)
315+
}
316+
})
317+
}
318+
319+
err := cli.ServiceRemove(ctx, serviceID)
320+
assert.NilError(t, err)
321+
}
322+
323+
func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) types.ContainerJSON {
324+
t.Helper()
325+
filter := filters.NewArgs()
326+
filter.Add("service", serviceID)
327+
filter.Add("desired-state", "running")
328+
tasks, err := cli.TaskList(ctx, types.TaskListOptions{Filters: filter})
329+
assert.NilError(t, err)
330+
assert.Assert(t, len(tasks) > 0)
331+
332+
ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
333+
assert.NilError(t, err)
334+
assert.Equal(t, ctr.State.Running, true)
335+
return ctr
336+
}
337+
251338
func getService(t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
252339
t.Helper()
253340
service, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})

0 commit comments

Comments
 (0)