1 package systemd
2
3 import (
4 "bufio"
5 "bytes"
6 "os"
7 "os/exec"
8 "strings"
9 "testing"
10
11 "github.com/opencontainers/runc/libcontainer/cgroups"
12 "github.com/opencontainers/runc/libcontainer/configs"
13 "github.com/opencontainers/runc/libcontainer/devices"
14 )
15
16 func newManager(t *testing.T, config *configs.Cgroup) (m cgroups.Manager) {
17 t.Helper()
18 var err error
19
20 if cgroups.IsCgroup2UnifiedMode() {
21 m, err = NewUnifiedManager(config, "")
22 } else {
23 m, err = NewLegacyManager(config, nil)
24 }
25 if err != nil {
26 t.Fatal(err)
27 }
28 t.Cleanup(func() { _ = m.Destroy() })
29
30 return m
31 }
32
33 func TestSystemdVersion(t *testing.T) {
34 systemdVersionTests := []struct {
35 verStr string
36 expectedVer int
37 expectErr bool
38 }{
39 {`"219"`, 219, false},
40 {`"v245.4-1.fc32"`, 245, false},
41 {`"241-1"`, 241, false},
42 {`"v241-1"`, 241, false},
43 {"NaN", 0, true},
44 {"", 0, true},
45 }
46 for _, sdTest := range systemdVersionTests {
47 ver, err := systemdVersionAtoi(sdTest.verStr)
48 if !sdTest.expectErr && err != nil {
49 t.Errorf("systemdVersionAtoi(%s); want nil; got %v", sdTest.verStr, err)
50 }
51 if sdTest.expectErr && err == nil {
52 t.Errorf("systemdVersionAtoi(%s); wanted failure; got nil", sdTest.verStr)
53 }
54 if ver != sdTest.expectedVer {
55 t.Errorf("systemdVersionAtoi(%s); want %d; got %d", sdTest.verStr, sdTest.expectedVer, ver)
56 }
57 }
58 }
59
60 func TestValidUnitTypes(t *testing.T) {
61 testCases := []struct {
62 unitName string
63 expectedUnitType string
64 }{
65 {"system.slice", "Slice"},
66 {"kubepods.slice", "Slice"},
67 {"testing-container:ab.scope", "Scope"},
68 }
69 for _, sdTest := range testCases {
70 unitType := getUnitType(sdTest.unitName)
71 if unitType != sdTest.expectedUnitType {
72 t.Errorf("getUnitType(%s); want %q; got %q", sdTest.unitName, sdTest.expectedUnitType, unitType)
73 }
74 }
75 }
76
77
78
79
80
81
82 func TestPodSkipDevicesUpdate(t *testing.T) {
83 if !IsRunningSystemd() {
84 t.Skip("Test requires systemd.")
85 }
86 if os.Geteuid() != 0 {
87 t.Skip("Test requires root.")
88 }
89
90 podName := "system-runc_test_pod" + t.Name() + ".slice"
91 podConfig := &configs.Cgroup{
92 Systemd: true,
93 Parent: "system.slice",
94 Name: podName,
95 Resources: &configs.Resources{
96 PidsLimit: 42,
97 Memory: 32 * 1024 * 1024,
98 SkipDevices: true,
99 },
100 }
101
102 pm := newManager(t, podConfig)
103 if err := pm.Apply(-1); err != nil {
104 t.Fatal(err)
105 }
106 if err := pm.Set(podConfig.Resources); err != nil {
107 t.Fatal(err)
108 }
109
110 containerConfig := &configs.Cgroup{
111 Parent: podName,
112 ScopePrefix: "test",
113 Name: "PodSkipDevicesUpdate",
114 Resources: &configs.Resources{
115 Devices: []*devices.Rule{
116
117 {
118 Type: devices.CharDevice,
119 Major: 1,
120 Minor: 3,
121 Permissions: "rwm",
122 Allow: true,
123 },
124 },
125 },
126 }
127
128
129
130 cmd := exec.Command("sleep", "infinity")
131 cmd.Env = append(os.Environ(), "LANG=C")
132 var stderr bytes.Buffer
133 cmd.Stderr = &stderr
134 if err := cmd.Start(); err != nil {
135 t.Fatal(err)
136 }
137
138 defer func() {
139
140 _ = cmd.Process.Kill()
141 _ = cmd.Wait()
142 }()
143
144
145 cm := newManager(t, containerConfig)
146 if err := cm.Apply(cmd.Process.Pid); err != nil {
147 t.Fatal(err)
148 }
149
150 if !strings.HasPrefix(cm.Path("devices"), pm.Path("devices")) {
151 t.Fatalf("expected container cgroup path %q to be under pod cgroup path %q",
152 cm.Path("devices"), pm.Path("devices"))
153 }
154 if err := cm.Set(containerConfig.Resources); err != nil {
155 t.Fatal(err)
156 }
157
158
159 for i := 0; i < 42; i++ {
160 podConfig.Resources.PidsLimit++
161 podConfig.Resources.Memory += 1024 * 1024
162 if err := pm.Set(podConfig.Resources); err != nil {
163 t.Fatal(err)
164 }
165 }
166
167 if err := cmd.Process.Kill(); err != nil {
168 t.Fatal(err)
169 }
170
171 _ = cmd.Wait()
172
173
174 if stderr.Len() != 0 {
175 t.Fatalf("container stderr not empty: %s", stderr.String())
176 }
177 }
178
179 func testSkipDevices(t *testing.T, skipDevices bool, expected []string) {
180 if !IsRunningSystemd() {
181 t.Skip("Test requires systemd.")
182 }
183 if os.Geteuid() != 0 {
184 t.Skip("Test requires root.")
185 }
186
187 centosVer, _ := exec.Command("rpm", "-q", "--qf", "%{version}", "centos-release").CombinedOutput()
188 if string(centosVer) == "7" {
189 t.Skip("Flaky on CentOS 7")
190 }
191
192 podConfig := &configs.Cgroup{
193 Parent: "system.slice",
194 Name: "system-runc_test_pods.slice",
195 Resources: &configs.Resources{
196 SkipDevices: skipDevices,
197 },
198 }
199
200 pm := newManager(t, podConfig)
201 if err := pm.Apply(-1); err != nil {
202 t.Fatal(err)
203 }
204 if err := pm.Set(podConfig.Resources); err != nil {
205 t.Fatal(err)
206 }
207
208 config := &configs.Cgroup{
209 Parent: "system-runc_test_pods.slice",
210 ScopePrefix: "test",
211 Name: "SkipDevices",
212 Resources: &configs.Resources{
213 Devices: []*devices.Rule{
214
215 {
216 Type: devices.CharDevice,
217 Major: 1,
218 Minor: 7,
219 Permissions: "rwm",
220 Allow: true,
221 },
222 },
223 },
224 }
225
226
227
228 cmd := exec.Command("bash", "-c", "read; echo > /dev/full; cat /dev/null; true")
229 cmd.Env = append(os.Environ(), "LANG=C")
230 stdinR, stdinW, err := os.Pipe()
231 if err != nil {
232 t.Fatal(err)
233 }
234 cmd.Stdin = stdinR
235 var stderr bytes.Buffer
236 cmd.Stderr = &stderr
237 err = cmd.Start()
238 stdinR.Close()
239 defer stdinW.Close()
240 if err != nil {
241 t.Fatal(err)
242 }
243
244 defer func() {
245
246 _, _ = stdinW.WriteString("hey\n")
247 _ = cmd.Wait()
248 }()
249
250
251 m := newManager(t, config)
252 if err := m.Apply(cmd.Process.Pid); err != nil {
253 t.Fatal(err)
254 }
255
256 if !strings.HasPrefix(m.Path("devices"), pm.Path("devices")) {
257 t.Fatalf("expected container cgroup path %q to be under pod cgroup path %q",
258 m.Path("devices"), pm.Path("devices"))
259 }
260 if err := m.Set(config.Resources); err != nil {
261
262 if skipDevices == false && strings.HasSuffix(err.Error(), "/devices.allow: operation not permitted") {
263
264
265
266
267 return
268 }
269 t.Fatal(err)
270 }
271
272
273 if _, err := stdinW.WriteString("wow\n"); err != nil {
274 t.Fatal(err)
275 }
276 if err := cmd.Wait(); err != nil {
277 t.Fatal(err)
278 }
279 for _, exp := range expected {
280 if !strings.Contains(stderr.String(), exp) {
281 t.Errorf("expected %q, got: %s", exp, stderr.String())
282 }
283 }
284 }
285
286 func TestSkipDevicesTrue(t *testing.T) {
287 testSkipDevices(t, true, []string{
288 "echo: write error: No space left on device",
289 "cat: /dev/null: Operation not permitted",
290 })
291 }
292
293 func TestSkipDevicesFalse(t *testing.T) {
294
295
296
297
298 testSkipDevices(t, false, []string{
299 "/dev/full: Operation not permitted",
300 "cat: /dev/null: Operation not permitted",
301 })
302 }
303
304 func TestUnitExistsIgnored(t *testing.T) {
305 if !IsRunningSystemd() {
306 t.Skip("Test requires systemd.")
307 }
308 if os.Geteuid() != 0 {
309 t.Skip("Test requires root.")
310 }
311
312 podConfig := &configs.Cgroup{
313 Parent: "system.slice",
314 Name: "system-runc_test_exists.slice",
315 Resources: &configs.Resources{},
316 }
317
318 pm := newManager(t, podConfig)
319
320
321 for i := 0; i < 2; i++ {
322 if err := pm.Apply(-1); err != nil {
323 t.Fatal(err)
324 }
325 }
326 }
327
328 func TestFreezePodCgroup(t *testing.T) {
329 if !IsRunningSystemd() {
330 t.Skip("Test requires systemd.")
331 }
332 if os.Geteuid() != 0 {
333 t.Skip("Test requires root.")
334 }
335
336 podConfig := &configs.Cgroup{
337 Parent: "system.slice",
338 Name: "system-runc_test_pod.slice",
339 Resources: &configs.Resources{
340 SkipDevices: true,
341 Freezer: configs.Frozen,
342 },
343 }
344
345
346 pm := newManager(t, podConfig)
347 if err := pm.Apply(-1); err != nil {
348 t.Fatal(err)
349 }
350
351 if err := pm.Set(podConfig.Resources); err != nil {
352 t.Fatal(err)
353 }
354
355
356 pf, err := pm.GetFreezerState()
357 if err != nil {
358 t.Fatal(err)
359 }
360 if pf != configs.Frozen {
361 t.Fatalf("expected pod to be frozen, got %v", pf)
362 }
363
364
365
366 containerConfig := &configs.Cgroup{
367 Parent: "system-runc_test_pod.slice",
368 ScopePrefix: "test",
369 Name: "inner-container",
370 Resources: &configs.Resources{},
371 }
372
373 cmd := exec.Command("bash", "-c", "while read; do echo $REPLY; done")
374 cmd.Env = append(os.Environ(), "LANG=C")
375
376
377 stdinR, stdinW, err := os.Pipe()
378 if err != nil {
379 t.Fatal(err)
380 }
381 cmd.Stdin = stdinR
382
383
384 stdoutR, stdoutW, err := os.Pipe()
385 if err != nil {
386 t.Fatal(err)
387 }
388 cmd.Stdout = stdoutW
389 rdr := bufio.NewReader(stdoutR)
390
391
392 var stderr bytes.Buffer
393 cmd.Stderr = &stderr
394
395 err = cmd.Start()
396 stdinR.Close()
397 stdoutW.Close()
398 defer func() {
399 _ = stdinW.Close()
400 _ = stdoutR.Close()
401 }()
402 if err != nil {
403 t.Fatal(err)
404 }
405
406 defer func() {
407
408 _ = cmd.Process.Kill()
409 _ = cmd.Wait()
410 }()
411
412
413 cm := newManager(t, containerConfig)
414
415 if err := cm.Apply(cmd.Process.Pid); err != nil {
416 t.Fatal(err)
417 }
418 if err := cm.Set(containerConfig.Resources); err != nil {
419 t.Fatal(err)
420 }
421
422 if !strings.HasPrefix(cm.Path("freezer"), pm.Path("freezer")) {
423 t.Fatalf("expected container cgroup path %q to be under pod cgroup path %q",
424 cm.Path("freezer"), pm.Path("freezer"))
425 }
426
427 cf, err := cm.GetFreezerState()
428 if err != nil {
429 t.Fatal(err)
430 }
431 if cf != configs.Thawed {
432 t.Fatalf("expected container to be thawed, got %v", cf)
433 }
434
435
436 if err := pm.Freeze(configs.Thawed); err != nil {
437 t.Fatal(err)
438 }
439
440 cf, err = cm.GetFreezerState()
441 if err != nil {
442 t.Fatal(err)
443 }
444 if cf != configs.Thawed {
445 t.Fatalf("expected container to be thawed, got %v", cf)
446 }
447
448
449 marker := "one two\n"
450 _, err = stdinW.WriteString(marker)
451 if err != nil {
452 t.Fatal(err)
453 }
454 reply, err := rdr.ReadString('\n')
455 if err != nil {
456 t.Fatalf("reading from container: %v", err)
457 }
458 if reply != marker {
459 t.Fatalf("expected %q, got %q", marker, reply)
460 }
461 }
462
View as plain text