Browse code

Ensure CPU quota/period updates are sent to runc

Fixes an issue where if cpu quota/period is sent via the update API, the
values are updated in the stored container data but not actually sent to
the running container.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2018/01/17 01:35:04
Showing 2 changed files
... ...
@@ -30,6 +30,13 @@ func toContainerdResources(resources container.Resources) *libcontainerd.Resourc
30 30
 		period = uint64(100 * time.Millisecond / time.Microsecond)
31 31
 		quota = resources.NanoCPUs * int64(period) / 1e9
32 32
 	}
33
+	if quota == 0 && resources.CPUQuota != 0 {
34
+		quota = resources.CPUQuota
35
+	}
36
+	if period == 0 && resources.CPUPeriod != 0 {
37
+		period = uint64(resources.CPUPeriod)
38
+	}
39
+
33 40
 	r.CPU.Period = &period
34 41
 	r.CPU.Quota = &quota
35 42
 
36 43
new file mode 100644
... ...
@@ -0,0 +1,108 @@
0
+package container
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"fmt"
6
+	"strconv"
7
+	"strings"
8
+	"testing"
9
+	"time"
10
+
11
+	"github.com/docker/docker/api/types"
12
+	"github.com/docker/docker/api/types/container"
13
+	"github.com/docker/docker/integration/util/request"
14
+	"github.com/docker/docker/pkg/stdcopy"
15
+)
16
+
17
+func TestUpdateCPUQUota(t *testing.T) {
18
+	t.Parallel()
19
+
20
+	client := request.NewAPIClient(t)
21
+	ctx := context.Background()
22
+
23
+	c, err := client.ContainerCreate(ctx, &container.Config{
24
+		Image: "busybox",
25
+		Cmd:   []string{"top"},
26
+	}, nil, nil, "")
27
+	if err != nil {
28
+		t.Fatal(err)
29
+	}
30
+	defer func() {
31
+		if err := client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil {
32
+			panic(fmt.Sprintf("failed to clean up after test: %v", err))
33
+		}
34
+	}()
35
+
36
+	if err := client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}); err != nil {
37
+		t.Fatal(err)
38
+	}
39
+
40
+	for _, test := range []struct {
41
+		desc   string
42
+		update int64
43
+	}{
44
+		{desc: "some random value", update: 15000},
45
+		{desc: "a higher value", update: 20000},
46
+		{desc: "a lower value", update: 10000},
47
+		{desc: "unset value", update: -1},
48
+	} {
49
+		if _, err := client.ContainerUpdate(ctx, c.ID, container.UpdateConfig{
50
+			Resources: container.Resources{
51
+				CPUQuota: test.update,
52
+			},
53
+		}); err != nil {
54
+			t.Fatal(err)
55
+		}
56
+
57
+		inspect, err := client.ContainerInspect(ctx, c.ID)
58
+		if err != nil {
59
+			t.Fatal(err)
60
+		}
61
+
62
+		if inspect.HostConfig.CPUQuota != test.update {
63
+			t.Fatalf("quota not updated in the API, expected %d, got: %d", test.update, inspect.HostConfig.CPUQuota)
64
+		}
65
+
66
+		execCreate, err := client.ContainerExecCreate(ctx, c.ID, types.ExecConfig{
67
+			Cmd:          []string{"/bin/cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"},
68
+			AttachStdout: true,
69
+			AttachStderr: true,
70
+		})
71
+		if err != nil {
72
+			t.Fatal(err)
73
+		}
74
+
75
+		attach, err := client.ContainerExecAttach(ctx, execCreate.ID, types.ExecStartCheck{})
76
+		if err != nil {
77
+			t.Fatal(err)
78
+		}
79
+
80
+		if err := client.ContainerExecStart(ctx, execCreate.ID, types.ExecStartCheck{}); err != nil {
81
+			t.Fatal(err)
82
+		}
83
+
84
+		buf := bytes.NewBuffer(nil)
85
+		ready := make(chan error)
86
+
87
+		go func() {
88
+			_, err := stdcopy.StdCopy(buf, buf, attach.Reader)
89
+			ready <- err
90
+		}()
91
+
92
+		select {
93
+		case <-time.After(60 * time.Second):
94
+			t.Fatal("timeout waiting for exec to complete")
95
+		case err := <-ready:
96
+			if err != nil {
97
+				t.Fatal(err)
98
+			}
99
+		}
100
+
101
+		actual := strings.TrimSpace(buf.String())
102
+		if actual != strconv.Itoa(int(test.update)) {
103
+			t.Fatalf("expected cgroup value %d, got: %s", test.update, actual)
104
+		}
105
+	}
106
+
107
+}