Skip to content

Commit 2fa4e9c

Browse files
committed
cri: add support for configuring swap
Signed-off-by: Danielle Lancashire <dani@builds.terrible.systems>
1 parent 591d709 commit 2fa4e9c

File tree

2 files changed

+168
-1
lines changed

2 files changed

+168
-1
lines changed

integration/container_update_resources_test.go

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020
package integration
2121

2222
import (
23+
"bytes"
24+
"io/ioutil"
25+
"strings"
2326
"testing"
2427

2528
"github.com/containerd/cgroups"
29+
cgroupsv2 "github.com/containerd/cgroups/v2"
30+
"github.com/containerd/containerd"
2631
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
2732
"github.com/stretchr/testify/assert"
2833
"github.com/stretchr/testify/require"
@@ -39,7 +44,164 @@ func checkMemoryLimit(t *testing.T, spec *runtimespec.Spec, memLimit int64) {
3944
assert.Equal(t, memLimit, *spec.Linux.Resources.Memory.Limit)
4045
}
4146

42-
func TestUpdateContainerResources(t *testing.T) {
47+
func checkMemorySwapLimit(t *testing.T, spec *runtimespec.Spec, memLimit *int64) {
48+
require.NotNil(t, spec)
49+
require.NotNil(t, spec.Linux)
50+
require.NotNil(t, spec.Linux.Resources)
51+
require.NotNil(t, spec.Linux.Resources.Memory)
52+
if memLimit == nil {
53+
require.Nil(t, spec.Linux.Resources.Memory.Swap)
54+
} else {
55+
require.NotNil(t, spec.Linux.Resources.Memory.Swap)
56+
assert.Equal(t, *memLimit, *spec.Linux.Resources.Memory.Swap)
57+
}
58+
}
59+
60+
func getCgroupSwapLimitForTask(t *testing.T, task containerd.Task) uint64 {
61+
if cgroups.Mode() == cgroups.Unified {
62+
groupPath, err := cgroupsv2.PidGroupPath(int(task.Pid()))
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
cgroup2, err := cgroupsv2.LoadManager("/sys/fs/cgroup", groupPath)
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
stat, err := cgroup2.Stat()
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
return stat.Memory.SwapLimit
75+
}
76+
cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid())))
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
stat, err := cgroup.Stat(cgroups.IgnoreNotExist)
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
return stat.Memory.HierarchicalSwapLimit
85+
}
86+
87+
func getCgroupMemoryLimitForTask(t *testing.T, task containerd.Task) uint64 {
88+
if cgroups.Mode() == cgroups.Unified {
89+
groupPath, err := cgroupsv2.PidGroupPath(int(task.Pid()))
90+
if err != nil {
91+
t.Fatal(err)
92+
}
93+
cgroup2, err := cgroupsv2.LoadManager("/sys/fs/cgroup", groupPath)
94+
if err != nil {
95+
t.Fatal(err)
96+
}
97+
stat, err := cgroup2.Stat()
98+
if err != nil {
99+
t.Fatal(err)
100+
}
101+
return stat.Memory.UsageLimit
102+
}
103+
104+
cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid())))
105+
if err != nil {
106+
t.Fatal(err)
107+
}
108+
stat, err := cgroup.Stat(cgroups.IgnoreNotExist)
109+
if err != nil {
110+
t.Fatal(err)
111+
}
112+
return stat.Memory.Usage.Limit
113+
}
114+
115+
func isSwapLikelyEnabled() bool {
116+
// Check whether swap is enabled.
117+
swapFile := "/proc/swaps"
118+
swapData, err := ioutil.ReadFile(swapFile)
119+
if err != nil {
120+
// We can't read the file or it doesn't exist, assume we don't have swap.
121+
return false
122+
}
123+
124+
swapData = bytes.TrimSpace(swapData) // extra trailing \n
125+
swapLines := strings.Split(string(swapData), "\n")
126+
// If there is more than one line (table headers) in /proc/swaps, swap is enabled
127+
return len(swapLines) > 1
128+
}
129+
130+
func TestUpdateContainerResources_MemorySwap(t *testing.T) {
131+
if !isSwapLikelyEnabled() {
132+
t.Skipf("Swap is not enabled, or /proc/swaps is not readable. Swap is required for this test")
133+
return
134+
}
135+
136+
t.Log("Create a sandbox")
137+
sb, sbConfig := PodSandboxConfigWithCleanup(t, "sandbox", "update-container-swap-resources")
138+
139+
EnsureImageExists(t, pauseImage)
140+
141+
memoryLimit := int64(128 * 1024 * 1024)
142+
baseSwapLimit := int64(200 * 1024 * 1024)
143+
increasedSwapLimit := int64(256 * 1024 * 1024)
144+
145+
t.Log("Create a container with memory limit but no swap")
146+
cnConfig := ContainerConfig(
147+
"container",
148+
pauseImage,
149+
WithResources(&runtime.LinuxContainerResources{
150+
MemoryLimitInBytes: memoryLimit,
151+
MemorySwapLimitInBytes: baseSwapLimit,
152+
}),
153+
)
154+
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
155+
require.NoError(t, err)
156+
157+
t.Log("Check memory limit in container OCI spec")
158+
container, err := containerdClient.LoadContainer(context.Background(), cn)
159+
require.NoError(t, err)
160+
spec, err := container.Spec(context.Background())
161+
require.NoError(t, err)
162+
checkMemoryLimit(t, spec, memoryLimit)
163+
checkMemorySwapLimit(t, spec, &baseSwapLimit)
164+
165+
t.Log("Update container swap limit after created")
166+
err = runtimeService.UpdateContainerResources(cn, &runtime.LinuxContainerResources{
167+
MemorySwapLimitInBytes: baseSwapLimit,
168+
}, nil)
169+
require.NoError(t, err)
170+
171+
t.Log("Check memory limit in container OCI spec")
172+
spec, err = container.Spec(context.Background())
173+
require.NoError(t, err)
174+
sw1 := baseSwapLimit
175+
checkMemorySwapLimit(t, spec, &sw1)
176+
177+
t.Log("Start the container")
178+
require.NoError(t, runtimeService.StartContainer(cn))
179+
task, err := container.Task(context.Background(), nil)
180+
require.NoError(t, err)
181+
182+
t.Log("Check memory limit in cgroup")
183+
memLimit := getCgroupMemoryLimitForTask(t, task)
184+
swapLimit := getCgroupSwapLimitForTask(t, task)
185+
assert.Equal(t, uint64(memoryLimit), memLimit)
186+
assert.Equal(t, uint64(baseSwapLimit-memoryLimit), swapLimit)
187+
188+
t.Log("Update container memory limit after started")
189+
err = runtimeService.UpdateContainerResources(cn, &runtime.LinuxContainerResources{
190+
MemorySwapLimitInBytes: increasedSwapLimit,
191+
}, nil)
192+
require.NoError(t, err)
193+
194+
t.Log("Check memory limit in container OCI spec")
195+
spec, err = container.Spec(context.Background())
196+
require.NoError(t, err)
197+
checkMemorySwapLimit(t, spec, &increasedSwapLimit)
198+
199+
t.Log("Check memory limit in cgroup")
200+
swapLimit = getCgroupSwapLimitForTask(t, task)
201+
assert.Equal(t, uint64(increasedSwapLimit-memoryLimit), swapLimit)
202+
}
203+
204+
func TestUpdateContainerResources_MemoryLimit(t *testing.T) {
43205
// TODO(claudiub): Make this test work once https://github.com/microsoft/hcsshim/pull/931 merges.
44206
t.Log("Create a sandbox")
45207
sb, sbConfig := PodSandboxConfigWithCleanup(t, "sandbox", "update-container-resources")

pkg/cri/opts/spec_linux.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ func WithResources(resources *runtime.LinuxContainerResources, tolerateMissingHu
450450
q = resources.GetCpuQuota()
451451
shares = uint64(resources.GetCpuShares())
452452
limit = resources.GetMemoryLimitInBytes()
453+
swapLimit = resources.GetMemorySwapLimitInBytes()
453454
hugepages = resources.GetHugepageLimits()
454455
)
455456

@@ -471,6 +472,10 @@ func WithResources(resources *runtime.LinuxContainerResources, tolerateMissingHu
471472
if limit != 0 {
472473
s.Linux.Resources.Memory.Limit = &limit
473474
}
475+
if swapLimit != 0 {
476+
s.Linux.Resources.Memory.Swap = &swapLimit
477+
}
478+
474479
if !disableHugetlbController {
475480
if isHugetlbControllerPresent() {
476481
for _, limit := range hugepages {

0 commit comments

Comments
 (0)