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