gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/cgroup/cgroup_test.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cgroup 16 17 import ( 18 "encoding/json" 19 "io/ioutil" 20 "os" 21 "path/filepath" 22 "strings" 23 "testing" 24 25 specs "github.com/opencontainers/runtime-spec/specs-go" 26 "gvisor.dev/gvisor/pkg/test/testutil" 27 ) 28 29 var debianMountinfo = ` 30 35 24 0:30 / /sys/fs/cgroup ro shared:9 - tmpfs tmpfs ro 31 36 35 0:31 / /sys/fs/cgroup/unified rw shared:10 - cgroup2 cgroup2 rw 32 37 35 0:32 / /sys/fs/cgroup/systemd rw - cgroup cgroup rw,name=systemd 33 41 35 0:36 / /sys/fs/cgroup/cpu,cpuacct rw shared:16 - cgroup cgroup rw,cpu,cpuacct 34 42 35 0:37 / /sys/fs/cgroup/freezer rw shared:17 - cgroup cgroup rw,freezer 35 43 35 0:38 / /sys/fs/cgroup/hugetlb rw shared:18 - cgroup cgroup rw,hugetlb 36 44 35 0:39 / /sys/fs/cgroup/cpuset rw shared:19 - cgroup cgroup rw,cpuset 37 45 35 0:40 / /sys/fs/cgroup/net_cls,net_prio rw shared:20 - cgroup cgroup rw,net_cls,net_prio 38 46 35 0:41 / /sys/fs/cgroup/pids rw shared:21 - cgroup cgroup rw,pids 39 47 35 0:42 / /sys/fs/cgroup/perf_event rw shared:22 - cgroup cgroup rw,perf_event 40 48 35 0:43 / /sys/fs/cgroup/memory rw shared:23 - cgroup cgroup rw,memory 41 49 35 0:44 / /sys/fs/cgroup/blkio rw shared:24 - cgroup cgroup rw,blkio 42 50 35 0:45 / /sys/fs/cgroup/devices rw shared:25 - cgroup cgroup rw,devices 43 51 35 0:46 / /sys/fs/cgroup/rdma rw shared:26 - cgroup cgroup rw,rdma 44 ` 45 46 var dindMountinfo = ` 47 05 04 0:64 / /sys/fs/cgroup rw - tmpfs tmpfs rw,mode=755 48 06 05 0:32 /docker/136 /sys/fs/cgroup/systemd ro master:11 - cgroup cgroup rw,xattr,name=systemd 49 07 05 0:36 /docker/136 /sys/fs/cgroup/cpu,cpuacct ro master:16 - cgroup cgroup rw,cpu,cpuacct 50 08 05 0:37 /docker/136 /sys/fs/cgroup/freezer ro master:17 - cgroup cgroup rw,freezer 51 09 05 0:38 /docker/136 /sys/fs/cgroup/hugetlb ro master:18 - cgroup cgroup rw,hugetlb 52 10 05 0:39 /docker/136 /sys/fs/cgroup/cpuset ro master:19 - cgroup cgroup rw,cpuset 53 11 05 0:40 /docker/136 /sys/fs/cgroup/net_cls,net_prio ro master:20 - cgroup cgroup rw,net_cls,net_prio 54 12 05 0:41 /docker/136 /sys/fs/cgroup/pids ro master:21 - cgroup cgroup rw,pids 55 13 05 0:42 /docker/136 /sys/fs/cgroup/perf_event ro master:22 - cgroup cgroup rw,perf_event 56 14 05 0:43 /docker/136 /sys/fs/cgroup/memory ro master:23 - cgroup cgroup rw,memory 57 16 05 0:44 /docker/136 /sys/fs/cgroup/blkio ro master:24 - cgroup cgroup rw,blkio 58 17 05 0:45 /docker/136 /sys/fs/cgroup/devices ro master:25 - cgroup cgroup rw,devices 59 18 05 0:46 / /sys/fs/cgroup/rdma ro master:26 - cgroup cgroup rw,rdma 60 ` 61 62 func TestUninstallEnoent(t *testing.T) { 63 c := cgroupV1{ 64 // Use a non-existent name. 65 Name: "runsc-test-uninstall-656e6f656e740a", 66 Own: make(map[string]bool), 67 } 68 for key := range controllers { 69 c.Own[key] = true 70 } 71 if err := c.Uninstall(); err != nil { 72 t.Errorf("Uninstall() failed: %v", err) 73 } 74 } 75 76 func TestCountCpuset(t *testing.T) { 77 for _, tc := range []struct { 78 str string 79 want int 80 error bool 81 }{ 82 {str: "0", want: 1}, 83 {str: "0,1,2,8,9,10", want: 6}, 84 {str: "0-1", want: 2}, 85 {str: "0-7", want: 8}, 86 {str: "0-7,16,32-39,64,65", want: 19}, 87 {str: "a", error: true}, 88 {str: "5-a", error: true}, 89 {str: "a-5", error: true}, 90 {str: "-10", error: true}, 91 {str: "15-", error: true}, 92 {str: "-", error: true}, 93 {str: "--", error: true}, 94 } { 95 t.Run(tc.str, func(t *testing.T) { 96 got, err := countCpuset(tc.str) 97 if tc.error { 98 if err == nil { 99 t.Errorf("countCpuset(%q) should have failed", tc.str) 100 } 101 } else { 102 if err != nil { 103 t.Errorf("countCpuset(%q) failed: %v", tc.str, err) 104 } 105 if tc.want != got { 106 t.Errorf("countCpuset(%q) want: %d, got: %d", tc.str, tc.want, got) 107 } 108 } 109 }) 110 } 111 } 112 113 func uint16Ptr(v uint16) *uint16 { 114 return &v 115 } 116 117 func uint32Ptr(v uint32) *uint32 { 118 return &v 119 } 120 121 func int64Ptr(v int64) *int64 { 122 return &v 123 } 124 125 func uint64Ptr(v uint64) *uint64 { 126 return &v 127 } 128 129 func boolPtr(v bool) *bool { 130 return &v 131 } 132 133 func createDir(dir string, contents map[string]string) error { 134 for name := range contents { 135 path := filepath.Join(dir, name) 136 f, err := os.Create(path) 137 if err != nil { 138 return err 139 } 140 f.Close() 141 } 142 return nil 143 } 144 145 func checkDir(t *testing.T, dir string, contents map[string]string) { 146 all, err := ioutil.ReadDir(dir) 147 if err != nil { 148 t.Fatalf("ReadDir(%q): %v", dir, err) 149 } 150 fileCount := 0 151 for _, file := range all { 152 if file.IsDir() { 153 // Only want to compare files. 154 continue 155 } 156 fileCount++ 157 158 want, ok := contents[file.Name()] 159 if !ok { 160 t.Errorf("file not expected: %q", file.Name()) 161 continue 162 } 163 gotBytes, err := ioutil.ReadFile(filepath.Join(dir, file.Name())) 164 if err != nil { 165 t.Fatal(err.Error()) 166 } 167 got := strings.TrimSuffix(string(gotBytes), "\n") 168 if got != want { 169 t.Errorf("wrong file content, file: %q, want: %q, got: %q", file.Name(), want, got) 170 } 171 } 172 if fileCount != len(contents) { 173 t.Errorf("file is missing, want: %v, got: %v", contents, all) 174 } 175 } 176 177 func makeLinuxWeightDevice(major, minor int64, weight, leafWeight *uint16) specs.LinuxWeightDevice { 178 rv := specs.LinuxWeightDevice{ 179 Weight: weight, 180 LeafWeight: leafWeight, 181 } 182 rv.Major = major 183 rv.Minor = minor 184 return rv 185 } 186 187 func makeLinuxThrottleDevice(major, minor int64, rate uint64) specs.LinuxThrottleDevice { 188 rv := specs.LinuxThrottleDevice{ 189 Rate: rate, 190 } 191 rv.Major = major 192 rv.Minor = minor 193 return rv 194 } 195 196 func TestBlockIO(t *testing.T) { 197 for _, tc := range []struct { 198 name string 199 spec *specs.LinuxBlockIO 200 wants map[string]string 201 }{ 202 { 203 name: "simple", 204 spec: &specs.LinuxBlockIO{ 205 Weight: uint16Ptr(1), 206 LeafWeight: uint16Ptr(2), 207 }, 208 wants: map[string]string{ 209 "blkio.weight": "1", 210 "blkio.leaf_weight": "2", 211 }, 212 }, 213 { 214 name: "weight_device", 215 spec: &specs.LinuxBlockIO{ 216 WeightDevice: []specs.LinuxWeightDevice{ 217 makeLinuxWeightDevice(1, 2, uint16Ptr(3), uint16Ptr(4)), 218 }, 219 }, 220 wants: map[string]string{ 221 "blkio.weight_device": "1:2 3", 222 "blkio.leaf_weight_device": "1:2 4", 223 }, 224 }, 225 { 226 name: "weight_device_nil_values", 227 spec: &specs.LinuxBlockIO{ 228 WeightDevice: []specs.LinuxWeightDevice{ 229 makeLinuxWeightDevice(1, 2, nil, nil), 230 }, 231 }, 232 }, 233 { 234 name: "throttle", 235 spec: &specs.LinuxBlockIO{ 236 ThrottleReadBpsDevice: []specs.LinuxThrottleDevice{ 237 makeLinuxThrottleDevice(1, 2, 3), 238 }, 239 ThrottleReadIOPSDevice: []specs.LinuxThrottleDevice{ 240 makeLinuxThrottleDevice(4, 5, 6), 241 }, 242 ThrottleWriteBpsDevice: []specs.LinuxThrottleDevice{ 243 makeLinuxThrottleDevice(7, 8, 9), 244 }, 245 ThrottleWriteIOPSDevice: []specs.LinuxThrottleDevice{ 246 makeLinuxThrottleDevice(10, 11, 12), 247 }, 248 }, 249 wants: map[string]string{ 250 "blkio.throttle.read_bps_device": "1:2 3", 251 "blkio.throttle.read_iops_device": "4:5 6", 252 "blkio.throttle.write_bps_device": "7:8 9", 253 "blkio.throttle.write_iops_device": "10:11 12", 254 }, 255 }, 256 { 257 name: "nil_values", 258 spec: &specs.LinuxBlockIO{}, 259 }, 260 { 261 name: "nil", 262 }, 263 } { 264 t.Run(tc.name, func(t *testing.T) { 265 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 266 if err != nil { 267 t.Fatalf("error creating temporary directory: %v", err) 268 } 269 defer os.RemoveAll(dir) 270 if err := createDir(dir, tc.wants); err != nil { 271 t.Fatalf("createDir(): %v", err) 272 } 273 274 spec := &specs.LinuxResources{ 275 BlockIO: tc.spec, 276 } 277 ctrlr := blockIO{} 278 if err := ctrlr.set(spec, dir); err != nil { 279 t.Fatalf("ctrlr.set(): %v", err) 280 } 281 checkDir(t, dir, tc.wants) 282 }) 283 } 284 } 285 286 func TestCPU(t *testing.T) { 287 for _, tc := range []struct { 288 name string 289 spec *specs.LinuxCPU 290 wants map[string]string 291 }{ 292 { 293 name: "all", 294 spec: &specs.LinuxCPU{ 295 Shares: uint64Ptr(1), 296 Quota: int64Ptr(2), 297 Period: uint64Ptr(3), 298 RealtimeRuntime: int64Ptr(4), 299 RealtimePeriod: uint64Ptr(5), 300 }, 301 wants: map[string]string{ 302 "cpu.shares": "1", 303 "cpu.cfs_quota_us": "2", 304 "cpu.cfs_period_us": "3", 305 "cpu.rt_runtime_us": "4", 306 "cpu.rt_period_us": "5", 307 }, 308 }, 309 { 310 name: "nil_values", 311 spec: &specs.LinuxCPU{}, 312 }, 313 { 314 name: "nil", 315 }, 316 } { 317 t.Run(tc.name, func(t *testing.T) { 318 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 319 if err != nil { 320 t.Fatalf("error creating temporary directory: %v", err) 321 } 322 defer os.RemoveAll(dir) 323 if err := createDir(dir, tc.wants); err != nil { 324 t.Fatalf("createDir(): %v", err) 325 } 326 327 spec := &specs.LinuxResources{ 328 CPU: tc.spec, 329 } 330 ctrlr := cpu{} 331 if err := ctrlr.set(spec, dir); err != nil { 332 t.Fatalf("ctrlr.set(): %v", err) 333 } 334 checkDir(t, dir, tc.wants) 335 }) 336 } 337 } 338 339 func TestCPUSet(t *testing.T) { 340 for _, tc := range []struct { 341 name string 342 spec *specs.LinuxCPU 343 wants map[string]string 344 }{ 345 { 346 name: "all", 347 spec: &specs.LinuxCPU{ 348 Cpus: "foo", 349 Mems: "bar", 350 }, 351 wants: map[string]string{ 352 "cpuset.cpus": "foo", 353 "cpuset.mems": "bar", 354 }, 355 }, 356 // Don't test nil values because they are copied from the parent. 357 // See TestCPUSetAncestor(). 358 } { 359 t.Run(tc.name, func(t *testing.T) { 360 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 361 if err != nil { 362 t.Fatalf("error creating temporary directory: %v", err) 363 } 364 defer os.RemoveAll(dir) 365 if err := createDir(dir, tc.wants); err != nil { 366 t.Fatalf("createDir(): %v", err) 367 } 368 369 spec := &specs.LinuxResources{ 370 CPU: tc.spec, 371 } 372 ctrlr := cpuSet{} 373 if err := ctrlr.set(spec, dir); err != nil { 374 t.Fatalf("ctrlr.set(): %v", err) 375 } 376 checkDir(t, dir, tc.wants) 377 }) 378 } 379 } 380 381 // TestCPUSetAncestor checks that, when not available, value is read from 382 // parent directory. 383 func TestCPUSetAncestor(t *testing.T) { 384 // Prepare master directory with cgroup files that will be propagated to 385 // children. 386 grandpa, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 387 if err != nil { 388 t.Fatalf("error creating temporary directory: %v", err) 389 } 390 defer os.RemoveAll(grandpa) 391 392 if err := ioutil.WriteFile(filepath.Join(grandpa, "cpuset.cpus"), []byte("parent-cpus"), 0666); err != nil { 393 t.Fatalf("ioutil.WriteFile(): %v", err) 394 } 395 if err := ioutil.WriteFile(filepath.Join(grandpa, "cpuset.mems"), []byte("parent-mems"), 0666); err != nil { 396 t.Fatalf("ioutil.WriteFile(): %v", err) 397 } 398 399 for _, tc := range []struct { 400 name string 401 spec *specs.LinuxCPU 402 }{ 403 { 404 name: "nil_values", 405 spec: &specs.LinuxCPU{}, 406 }, 407 { 408 name: "nil", 409 }, 410 } { 411 t.Run(tc.name, func(t *testing.T) { 412 // Create empty files in intermediate directory. They should be ignored 413 // when reading, and then populated from parent. 414 parent, err := ioutil.TempDir(grandpa, "parent") 415 if err != nil { 416 t.Fatalf("error creating temporary directory: %v", err) 417 } 418 defer os.RemoveAll(parent) 419 if _, err := os.Create(filepath.Join(parent, "cpuset.cpus")); err != nil { 420 t.Fatalf("os.Create(): %v", err) 421 } 422 if _, err := os.Create(filepath.Join(parent, "cpuset.mems")); err != nil { 423 t.Fatalf("os.Create(): %v", err) 424 } 425 426 // cgroup files mmust exist. 427 dir, err := ioutil.TempDir(parent, "child") 428 if err != nil { 429 t.Fatalf("error creating temporary directory: %v", err) 430 } 431 if _, err := os.Create(filepath.Join(dir, "cpuset.cpus")); err != nil { 432 t.Fatalf("os.Create(): %v", err) 433 } 434 if _, err := os.Create(filepath.Join(dir, "cpuset.mems")); err != nil { 435 t.Fatalf("os.Create(): %v", err) 436 } 437 438 spec := &specs.LinuxResources{ 439 CPU: tc.spec, 440 } 441 ctrlr := cpuSet{} 442 if err := ctrlr.set(spec, dir); err != nil { 443 t.Fatalf("ctrlr.set(): %v", err) 444 } 445 want := map[string]string{ 446 "cpuset.cpus": "parent-cpus", 447 "cpuset.mems": "parent-mems", 448 } 449 // Both path and dir must have been populated from grandpa. 450 checkDir(t, parent, want) 451 checkDir(t, dir, want) 452 }) 453 } 454 } 455 456 func TestHugeTlb(t *testing.T) { 457 for _, tc := range []struct { 458 name string 459 spec []specs.LinuxHugepageLimit 460 wants map[string]string 461 }{ 462 { 463 name: "single", 464 spec: []specs.LinuxHugepageLimit{ 465 { 466 Pagesize: "1G", 467 Limit: 123, 468 }, 469 }, 470 wants: map[string]string{ 471 "hugetlb.1G.limit_in_bytes": "123", 472 }, 473 }, 474 { 475 name: "multiple", 476 spec: []specs.LinuxHugepageLimit{ 477 { 478 Pagesize: "1G", 479 Limit: 123, 480 }, 481 { 482 Pagesize: "2G", 483 Limit: 456, 484 }, 485 { 486 Pagesize: "1P", 487 Limit: 789, 488 }, 489 }, 490 wants: map[string]string{ 491 "hugetlb.1G.limit_in_bytes": "123", 492 "hugetlb.2G.limit_in_bytes": "456", 493 "hugetlb.1P.limit_in_bytes": "789", 494 }, 495 }, 496 { 497 name: "nil", 498 }, 499 } { 500 t.Run(tc.name, func(t *testing.T) { 501 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 502 if err != nil { 503 t.Fatalf("error creating temporary directory: %v", err) 504 } 505 defer os.RemoveAll(dir) 506 if err := createDir(dir, tc.wants); err != nil { 507 t.Fatalf("createDir(): %v", err) 508 } 509 510 spec := &specs.LinuxResources{ 511 HugepageLimits: tc.spec, 512 } 513 ctrlr := hugeTLB{} 514 if err := ctrlr.set(spec, dir); err != nil { 515 t.Fatalf("ctrlr.set(): %v", err) 516 } 517 checkDir(t, dir, tc.wants) 518 }) 519 } 520 } 521 522 func TestMemory(t *testing.T) { 523 for _, tc := range []struct { 524 name string 525 spec *specs.LinuxMemory 526 wants map[string]string 527 }{ 528 { 529 name: "all", 530 spec: &specs.LinuxMemory{ 531 Limit: int64Ptr(1), 532 Reservation: int64Ptr(2), 533 Swap: int64Ptr(3), 534 Kernel: int64Ptr(4), 535 KernelTCP: int64Ptr(5), 536 Swappiness: uint64Ptr(6), 537 DisableOOMKiller: boolPtr(true), 538 }, 539 wants: map[string]string{ 540 "memory.limit_in_bytes": "1", 541 "memory.soft_limit_in_bytes": "2", 542 "memory.memsw.limit_in_bytes": "3", 543 "memory.kmem.limit_in_bytes": "4", 544 "memory.kmem.tcp.limit_in_bytes": "5", 545 "memory.swappiness": "6", 546 "memory.oom_control": "1", 547 }, 548 }, 549 { 550 // Disable OOM killer should only write when set to true. 551 name: "oomkiller", 552 spec: &specs.LinuxMemory{ 553 DisableOOMKiller: boolPtr(false), 554 }, 555 }, 556 { 557 name: "nil_values", 558 spec: &specs.LinuxMemory{}, 559 }, 560 { 561 name: "nil", 562 }, 563 } { 564 t.Run(tc.name, func(t *testing.T) { 565 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 566 if err != nil { 567 t.Fatalf("error creating temporary directory: %v", err) 568 } 569 defer os.RemoveAll(dir) 570 if err := createDir(dir, tc.wants); err != nil { 571 t.Fatalf("createDir(): %v", err) 572 } 573 574 spec := &specs.LinuxResources{ 575 Memory: tc.spec, 576 } 577 ctrlr := memory{} 578 if err := ctrlr.set(spec, dir); err != nil { 579 t.Fatalf("ctrlr.set(): %v", err) 580 } 581 checkDir(t, dir, tc.wants) 582 }) 583 } 584 } 585 586 func TestNetworkClass(t *testing.T) { 587 for _, tc := range []struct { 588 name string 589 spec *specs.LinuxNetwork 590 wants map[string]string 591 }{ 592 { 593 name: "all", 594 spec: &specs.LinuxNetwork{ 595 ClassID: uint32Ptr(1), 596 }, 597 wants: map[string]string{ 598 "net_cls.classid": "1", 599 }, 600 }, 601 { 602 name: "nil_values", 603 spec: &specs.LinuxNetwork{}, 604 }, 605 { 606 name: "nil", 607 }, 608 } { 609 t.Run(tc.name, func(t *testing.T) { 610 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 611 if err != nil { 612 t.Fatalf("error creating temporary directory: %v", err) 613 } 614 defer os.RemoveAll(dir) 615 if err := createDir(dir, tc.wants); err != nil { 616 t.Fatalf("createDir(): %v", err) 617 } 618 619 spec := &specs.LinuxResources{ 620 Network: tc.spec, 621 } 622 ctrlr := networkClass{} 623 if err := ctrlr.set(spec, dir); err != nil { 624 t.Fatalf("ctrlr.set(): %v", err) 625 } 626 checkDir(t, dir, tc.wants) 627 }) 628 } 629 } 630 631 func TestNetworkPriority(t *testing.T) { 632 for _, tc := range []struct { 633 name string 634 spec *specs.LinuxNetwork 635 wants map[string]string 636 }{ 637 { 638 name: "all", 639 spec: &specs.LinuxNetwork{ 640 Priorities: []specs.LinuxInterfacePriority{ 641 { 642 Name: "foo", 643 Priority: 1, 644 }, 645 }, 646 }, 647 wants: map[string]string{ 648 "net_prio.ifpriomap": "foo 1", 649 }, 650 }, 651 { 652 name: "nil_values", 653 spec: &specs.LinuxNetwork{}, 654 }, 655 { 656 name: "nil", 657 }, 658 } { 659 t.Run(tc.name, func(t *testing.T) { 660 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 661 if err != nil { 662 t.Fatalf("error creating temporary directory: %v", err) 663 } 664 defer os.RemoveAll(dir) 665 if err := createDir(dir, tc.wants); err != nil { 666 t.Fatalf("createDir(): %v", err) 667 } 668 669 spec := &specs.LinuxResources{ 670 Network: tc.spec, 671 } 672 ctrlr := networkPrio{} 673 if err := ctrlr.set(spec, dir); err != nil { 674 t.Fatalf("ctrlr.set(): %v", err) 675 } 676 checkDir(t, dir, tc.wants) 677 }) 678 } 679 } 680 681 func TestPids(t *testing.T) { 682 for _, tc := range []struct { 683 name string 684 spec *specs.LinuxPids 685 wants map[string]string 686 }{ 687 { 688 name: "all", 689 spec: &specs.LinuxPids{Limit: 1}, 690 wants: map[string]string{ 691 "pids.max": "1", 692 }, 693 }, 694 { 695 name: "nil_values", 696 spec: &specs.LinuxPids{}, 697 }, 698 { 699 name: "nil", 700 }, 701 } { 702 t.Run(tc.name, func(t *testing.T) { 703 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 704 if err != nil { 705 t.Fatalf("error creating temporary directory: %v", err) 706 } 707 defer os.RemoveAll(dir) 708 if err := createDir(dir, tc.wants); err != nil { 709 t.Fatalf("createDir(): %v", err) 710 } 711 712 spec := &specs.LinuxResources{ 713 Pids: tc.spec, 714 } 715 ctrlr := pids{} 716 if err := ctrlr.set(spec, dir); err != nil { 717 t.Fatalf("ctrlr.set(): %v", err) 718 } 719 checkDir(t, dir, tc.wants) 720 }) 721 } 722 } 723 724 func TestLoadPaths(t *testing.T) { 725 for _, tc := range []struct { 726 name string 727 cgroups string 728 mountinfo string 729 want map[string]string 730 err string 731 }{ 732 { 733 name: "empty", 734 mountinfo: debianMountinfo, 735 }, 736 { 737 name: "abs-path", 738 cgroups: "0:cpu:/path", 739 mountinfo: debianMountinfo, 740 want: map[string]string{"cpu": "/path"}, 741 }, 742 { 743 name: "rel-path", 744 cgroups: "0:cpu:rel-path", 745 mountinfo: debianMountinfo, 746 want: map[string]string{"cpu": "rel-path"}, 747 }, 748 { 749 name: "non-controller", 750 cgroups: "0:name=systemd:/path", 751 mountinfo: debianMountinfo, 752 want: map[string]string{"systemd": "/path"}, 753 }, 754 { 755 name: "unknown-controller", 756 cgroups: "0:ctr:/path", 757 mountinfo: debianMountinfo, 758 want: map[string]string{}, 759 }, 760 { 761 name: "multiple", 762 cgroups: "0:cpu:/path0\n" + 763 "1:memory:/path1\n" + 764 "2::/empty\n", 765 mountinfo: debianMountinfo, 766 want: map[string]string{ 767 "cpu": "/path0", 768 "memory": "/path1", 769 }, 770 }, 771 { 772 name: "missing-field", 773 cgroups: "0:nopath\n", 774 mountinfo: debianMountinfo, 775 err: "invalid cgroups file", 776 }, 777 { 778 name: "too-many-fields", 779 cgroups: "0:ctr:/path:extra\n", 780 mountinfo: debianMountinfo, 781 err: "invalid cgroups file", 782 }, 783 { 784 name: "multiple-malformed", 785 cgroups: "0:ctr0:/path0\n" + 786 "1:ctr1:/path1\n" + 787 "2:\n", 788 mountinfo: debianMountinfo, 789 err: "invalid cgroups file", 790 }, 791 { 792 name: "nested-cgroup", 793 cgroups: "9:memory:/docker/136\n" + 794 "2:cpu,cpuacct:/docker/136\n" + 795 "1:name=systemd:/docker/136\n" + 796 "0::/system.slice/containerd.service\n", 797 mountinfo: dindMountinfo, 798 // we want relative path to /sys/fs/cgroup inside the nested container. 799 // Subcroup inside the container will be created at /sys/fs/cgroup/cpu 800 // This will be /sys/fs/cgroup/cpu/docker/136/CGROUP_NAME 801 // outside the container 802 want: map[string]string{ 803 "memory": ".", 804 "cpu": ".", 805 "cpuacct": ".", 806 "systemd": ".", 807 }, 808 }, 809 { 810 name: "nested-cgroup-submount", 811 cgroups: "9:memory:/docker/136/test", 812 mountinfo: dindMountinfo, 813 want: map[string]string{ 814 "memory": "test", 815 }, 816 }, 817 { 818 name: "invalid-mount-info", 819 cgroups: "0:memory:/path", 820 mountinfo: "41 35 0:36 / /sys/fs/cgroup/memory rw shared:16 - invalid", 821 want: map[string]string{ 822 "memory": "/path", 823 }, 824 }, 825 { 826 name: "invalid-rel-path-in-proc-cgroup", 827 cgroups: "9:memory:invalid", 828 mountinfo: dindMountinfo, 829 err: "can't make invalid relative to /docker/136", 830 }, 831 } { 832 t.Run(tc.name, func(t *testing.T) { 833 r := strings.NewReader(tc.cgroups) 834 mountinfo := strings.NewReader(tc.mountinfo) 835 got, err := loadPathsHelper(r, mountinfo, false) 836 if len(tc.err) == 0 { 837 if err != nil { 838 t.Fatalf("Unexpected error: %v", err) 839 } 840 } else if err == nil || !strings.Contains(err.Error(), tc.err) { 841 t.Fatalf("Wrong error message, want: *%s*, got: %v", tc.err, err) 842 } 843 for key, vWant := range tc.want { 844 vGot, ok := got[key] 845 if !ok { 846 t.Errorf("Missing controller %q", key) 847 } 848 if vWant != vGot { 849 t.Errorf("Wrong controller %q value, want: %q, got: %q", key, vWant, vGot) 850 } 851 delete(got, key) 852 } 853 for k, v := range got { 854 t.Errorf("Unexpected controller %q: %q", k, v) 855 } 856 }) 857 } 858 } 859 860 func TestOptional(t *testing.T) { 861 for _, tc := range []struct { 862 name string 863 ctrlr controller 864 spec *specs.LinuxResources 865 err string 866 }{ 867 { 868 name: "pids", 869 ctrlr: &pids{}, 870 spec: &specs.LinuxResources{Pids: &specs.LinuxPids{Limit: 1}}, 871 err: "Pids.Limit set but pids cgroup controller not found", 872 }, 873 { 874 name: "net-cls", 875 ctrlr: &networkClass{}, 876 spec: &specs.LinuxResources{Network: &specs.LinuxNetwork{ClassID: uint32Ptr(1)}}, 877 err: "Network.ClassID set but net_cls cgroup controller not found", 878 }, 879 { 880 name: "net-prio", 881 ctrlr: &networkPrio{}, 882 spec: &specs.LinuxResources{Network: &specs.LinuxNetwork{ 883 Priorities: []specs.LinuxInterfacePriority{ 884 {Name: "foo", Priority: 1}, 885 }, 886 }}, 887 err: "Network.Priorities set but net_prio cgroup controller not found", 888 }, 889 { 890 name: "hugetlb", 891 ctrlr: &hugeTLB{}, 892 spec: &specs.LinuxResources{HugepageLimits: []specs.LinuxHugepageLimit{ 893 {Pagesize: "1", Limit: 2}, 894 }}, 895 err: "HugepageLimits set but hugetlb cgroup controller not found", 896 }, 897 } { 898 t.Run(tc.name, func(t *testing.T) { 899 err := tc.ctrlr.skip(tc.spec) 900 if err == nil { 901 t.Fatalf("ctrlr.skip() didn't fail") 902 } 903 if !strings.Contains(err.Error(), tc.err) { 904 t.Errorf("ctrlr.skip() want: *%s*, got: %q", tc.err, err) 905 } 906 }) 907 } 908 } 909 910 func TestJSON(t *testing.T) { 911 for _, tc := range []struct { 912 cg Cgroup 913 }{ 914 { 915 cg: &cgroupV1{ 916 Name: "foobar", 917 Parents: map[string]string{"hello": "world"}, 918 Own: map[string]bool{"parent": true}, 919 }, 920 }, 921 { 922 cg: &cgroupV2{ 923 Mountpoint: "foobar", 924 Path: "a/path/here", 925 Controllers: []string{"test", "controllers"}, 926 Own: []string{"I", "own", "this"}, 927 }, 928 }, 929 { 930 cg: CreateMockSystemdCgroup(), 931 }, 932 { 933 cg: nil, 934 }, 935 } { 936 in := &CgroupJSON{Cgroup: tc.cg} 937 data, err := json.Marshal(in) 938 if err != nil { 939 t.Fatalf("could not serialize %v to JSON: %v", in, err) 940 } 941 out := &CgroupJSON{} 942 if err := json.Unmarshal(data, out); err != nil { 943 t.Fatalf("could not deserialize %v from JSON: %v", data, err) 944 } 945 switch tc.cg.(type) { 946 case *cgroupSystemd: 947 if _, ok := out.Cgroup.(*cgroupSystemd); !ok { 948 t.Errorf("cgroup incorrectly deserialized from JSON: got %v, want %v", out.Cgroup, tc.cg) 949 } 950 case *cgroupV1: 951 if _, ok := out.Cgroup.(*cgroupV1); !ok { 952 t.Errorf("cgroup incorrectly deserialized from JSON: got %v, want %v", out.Cgroup, tc.cg) 953 } 954 case *cgroupV2: 955 if _, ok := out.Cgroup.(*cgroupV2); !ok { 956 t.Errorf("cgroup incorrectly deserialized from JSON: got %v, want %v", out.Cgroup, tc.cg) 957 } 958 } 959 } 960 }