gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/cgroup/cgroup_v2_test.go (about) 1 // Copyright The runc Authors. 2 // Copyright 2021 The gVisor Authors. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // https://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 package cgroup 16 17 import ( 18 "io/ioutil" 19 "os" 20 "path/filepath" 21 "strconv" 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 ( 30 cgroupv2MountInfo = `29 22 0:26 / /sys/fs/cgroup rw shared:4 - cgroup2 cgroup2 rw,seclabel,nsdelegate` 31 multipleCg2MountInfo = `34 28 0:29 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:8 - cgroup2 cgroup2 rw 32 1479 28 0:29 / /run/some/module/cgroupv2 rw,relatime shared:650 - cgroup2 none rw 33 ` 34 ) 35 36 func TestIO(t *testing.T) { 37 for _, tc := range []struct { 38 name string 39 spec *specs.LinuxBlockIO 40 path string 41 wants string 42 }{ 43 { 44 name: "simple", 45 spec: &specs.LinuxBlockIO{ 46 Weight: uint16Ptr(1), 47 }, 48 path: "io.weight", 49 wants: strconv.FormatUint(convertBlkIOToIOWeightValue(1), 10), 50 }, 51 { 52 name: "throttlereadbps", 53 spec: &specs.LinuxBlockIO{ 54 ThrottleReadBpsDevice: []specs.LinuxThrottleDevice{ 55 makeLinuxThrottleDevice(1, 2, 3), 56 }, 57 }, 58 path: "io.max", 59 wants: "1:2 rbps=3", 60 }, 61 { 62 name: "throttlewritebps", 63 spec: &specs.LinuxBlockIO{ 64 ThrottleWriteBpsDevice: []specs.LinuxThrottleDevice{ 65 makeLinuxThrottleDevice(4, 5, 6), 66 }, 67 }, 68 path: "io.max", 69 wants: "4:5 wbps=6", 70 }, 71 { 72 name: "throttlereadiops", 73 spec: &specs.LinuxBlockIO{ 74 ThrottleReadIOPSDevice: []specs.LinuxThrottleDevice{ 75 makeLinuxThrottleDevice(7, 8, 9), 76 }, 77 }, 78 path: "io.max", 79 wants: "7:8 riops=9", 80 }, 81 { 82 name: "throttlewriteiops", 83 spec: &specs.LinuxBlockIO{ 84 ThrottleWriteIOPSDevice: []specs.LinuxThrottleDevice{ 85 makeLinuxThrottleDevice(10, 11, 12), 86 }, 87 }, 88 path: "io.max", 89 wants: "10:11 wiops=12", 90 }, 91 { 92 name: "nil_values", 93 spec: &specs.LinuxBlockIO{}, 94 path: "not_used", 95 wants: "", 96 }, 97 } { 98 t.Run(tc.name, func(t *testing.T) { 99 testutil.TmpDir() 100 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 101 if err != nil { 102 t.Fatalf("error creating temporary directory: %v", err) 103 } 104 defer os.RemoveAll(dir) 105 106 fd, err := os.Create(filepath.Join(dir, tc.path)) 107 if err != nil { 108 t.Fatalf("os.CreatTemp(): %v", err) 109 } 110 fd.Close() 111 112 spec := &specs.LinuxResources{ 113 BlockIO: tc.spec, 114 } 115 ctrlr := io2{} 116 if err := ctrlr.set(spec, dir); err != nil { 117 t.Fatalf("ctrlr.set(): %v", err) 118 } 119 120 gotBytes, err := ioutil.ReadFile(filepath.Join(dir, tc.path)) 121 if err != nil { 122 t.Fatal(err.Error()) 123 } 124 got := strings.TrimSuffix(string(gotBytes), "\n") 125 if got != tc.wants { 126 t.Errorf("wrong file content, file: %q, want: %q, got: %q", tc.path, tc.wants, got) 127 } 128 }) 129 } 130 } 131 132 func TestLoadPathsCgroupv2(t *testing.T) { 133 for _, tc := range []struct { 134 name string 135 cgroups string 136 mountinfo string 137 want map[string]string 138 err string 139 }{ 140 { 141 name: "cgroupv2", 142 cgroups: "0::/docker/123", 143 mountinfo: cgroupv2MountInfo, 144 want: map[string]string{ 145 "cgroup2": "docker/123", 146 }, 147 }, 148 149 { 150 name: "cgroupv2-nested", 151 cgroups: "0::/", 152 mountinfo: cgroupv2MountInfo, 153 want: map[string]string{ 154 "cgroup2": ".", 155 }, 156 }, 157 { 158 name: "multiple-cgv2", 159 cgroups: "0::/system.slice/containerd.service\n", 160 mountinfo: multipleCg2MountInfo, 161 want: map[string]string{ 162 "cgroup2": "system.slice/containerd.service", 163 }, 164 }, 165 } { 166 t.Run(tc.name, func(t *testing.T) { 167 r := strings.NewReader(tc.cgroups) 168 mountinfo := strings.NewReader(tc.mountinfo) 169 got, err := loadPathsHelper(r, mountinfo, true) 170 if len(tc.err) == 0 { 171 if err != nil { 172 t.Fatalf("Unexpected error: %v", err) 173 } 174 } else if !strings.Contains(err.Error(), tc.err) { 175 t.Fatalf("Wrong error message, want: *%s*, got: %v", tc.err, err) 176 } 177 for key, vWant := range tc.want { 178 vGot, ok := got[key] 179 if !ok { 180 t.Errorf("Missing controller %q", key) 181 } 182 if vWant != vGot { 183 t.Errorf("Wrong controller %q value, want: %q, got: %q", key, vWant, vGot) 184 } 185 delete(got, key) 186 } 187 for k, v := range got { 188 t.Errorf("Unexpected controller %q: %q", k, v) 189 } 190 }) 191 } 192 } 193 194 func TestGetLimits(t *testing.T) { 195 for _, tc := range []struct { 196 name string 197 mem string 198 cpu string 199 expMem uint64 200 expCPU int 201 limitPath string 202 path string 203 }{ 204 { 205 name: "get limit from parent cgroup", 206 mem: "150", 207 cpu: "100 50", 208 limitPath: "user.slice", 209 path: "user.slice/container.scope", 210 expMem: 150, 211 expCPU: 2, 212 }, 213 { 214 name: "get limit from leaf cgroup", 215 mem: "150", 216 cpu: "100 50", 217 limitPath: "user.slice/container.scope", 218 path: "user.slice/container.scope", 219 expMem: 150, 220 expCPU: 2, 221 }, 222 } { 223 t.Run(tc.name, func(t *testing.T) { 224 testutil.TmpDir() 225 dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") 226 if err != nil { 227 t.Fatalf("error creating temporary directory: %v", err) 228 } 229 defer os.RemoveAll(dir) 230 231 fullPath := filepath.Join(dir, tc.path) 232 if err := os.MkdirAll(fullPath, 0o777); err != nil { 233 t.Fatalf("os.MkdirAll(): %v", err) 234 } 235 cg := cgroupV2{ 236 Mountpoint: dir, 237 Path: tc.path, 238 } 239 240 if err := os.WriteFile(filepath.Join(dir, tc.path, "memory.max"), []byte("max"), 0o777); err != nil { 241 t.Fatalf("os.WriteFile(): %v", err) 242 } 243 if err := os.WriteFile(filepath.Join(dir, tc.path, "cpu.max"), []byte("max max"), 0o777); err != nil { 244 t.Fatalf("os.WriteFile(): %v", err) 245 } 246 if err := os.WriteFile(filepath.Join(dir, tc.limitPath, "memory.max"), []byte(tc.mem), 0o655); err != nil { 247 t.Fatalf("os.WriteFile(): %v", err) 248 } 249 if err := os.WriteFile(filepath.Join(dir, tc.limitPath, "cpu.max"), []byte(tc.cpu), 0o655); err != nil { 250 t.Fatalf("os.WriteFile(): %v", err) 251 } 252 253 quota, err := cg.CPUQuota() 254 if err != nil { 255 t.Fatalf("cg.CPUQuota(): %v", err) 256 } 257 if int(quota) != tc.expCPU { 258 t.Errorf("cg.CPUQuota() = %v, want %v", quota, tc.expCPU) 259 } 260 mem, err := cg.MemoryLimit() 261 if err != nil { 262 t.Fatalf("cg.MemoryLimit(): %v", err) 263 } 264 if mem != tc.expMem { 265 t.Errorf("cg.MemoryLimit() = %v, want %v", mem, tc.expMem) 266 } 267 }) 268 } 269 } 270 271 func TestNumToStr(t *testing.T) { 272 cases := map[int64]string{ 273 0: "", 274 -1: "max", 275 10: "10", 276 } 277 for i, expected := range cases { 278 got := numToStr(i) 279 if got != expected { 280 t.Errorf("expected numToStr(%d) to be %q, got %q", i, expected, got) 281 } 282 } 283 } 284 285 func TestConvertBlkIOToIOWeightValue(t *testing.T) { 286 cases := map[uint16]uint64{ 287 0: 0, 288 10: 1, 289 1000: 10000, 290 } 291 for i, expected := range cases { 292 got := convertBlkIOToIOWeightValue(i) 293 if got != expected { 294 t.Errorf("expected ConvertBlkIOToIOWeightValue(%d) to be %d, got %d", i, expected, got) 295 } 296 } 297 } 298 299 func TestConvertCPUSharesToCgroupV2Value(t *testing.T) { 300 cases := map[uint64]uint64{ 301 0: 0, 302 2: 1, 303 262144: 10000, 304 } 305 for i, expected := range cases { 306 got := convertCPUSharesToCgroupV2Value(i) 307 if got != expected { 308 t.Errorf("expected ConvertCPUSharesToCgroupV2Value(%d) to be %d, got %d", i, expected, got) 309 } 310 } 311 } 312 313 func TestConvertMemorySwapToCgroupV2Value(t *testing.T) { 314 cases := []struct { 315 memswap, memory int64 316 expected int64 317 expErr bool 318 }{ 319 { 320 memswap: 0, 321 memory: 0, 322 expected: 0, 323 }, 324 { 325 memswap: -1, 326 memory: 0, 327 expected: -1, 328 }, 329 { 330 memswap: -1, 331 memory: -1, 332 expected: -1, 333 }, 334 { 335 memswap: -2, 336 memory: 0, 337 expErr: true, 338 }, 339 { 340 memswap: -1, 341 memory: 1000, 342 expected: -1, 343 }, 344 { 345 memswap: 1000, 346 memory: 1000, 347 expected: 0, 348 }, 349 { 350 memswap: 500, 351 memory: 200, 352 expected: 300, 353 }, 354 { 355 memswap: 300, 356 memory: 400, 357 expErr: true, 358 }, 359 { 360 memswap: 300, 361 memory: 0, 362 expErr: true, 363 }, 364 { 365 memswap: 300, 366 memory: -300, 367 expErr: true, 368 }, 369 { 370 memswap: 300, 371 memory: -1, 372 expErr: true, 373 }, 374 } 375 376 for _, c := range cases { 377 swap, err := convertMemorySwapToCgroupV2Value(c.memswap, c.memory) 378 if c.expErr { 379 if err == nil { 380 t.Errorf("memswap: %d, memory %d, expected error, got %d, nil", c.memswap, c.memory, swap) 381 } 382 // no more checks 383 continue 384 } 385 if err != nil { 386 t.Errorf("memswap: %d, memory %d, expected success, got error %s", c.memswap, c.memory, err) 387 } 388 if swap != c.expected { 389 t.Errorf("memswap: %d, memory %d, expected %d, got %d", c.memswap, c.memory, c.expected, swap) 390 } 391 } 392 } 393 394 func TestParseCPUQuota(t *testing.T) { 395 cases := []struct { 396 quota string 397 expected float64 398 expErr bool 399 }{ 400 { 401 quota: "max 100000\n", 402 expected: -1, 403 }, 404 { 405 quota: "10000 100000", 406 expected: 0.1, 407 }, 408 { 409 quota: "20000 100000\n", 410 expected: 0.2, 411 }, 412 413 { 414 quota: "-1", 415 expected: -1, 416 expErr: true, 417 }, 418 } 419 420 for _, c := range cases { 421 res, err := parseCPUQuota(c.quota) 422 if c.expErr { 423 if err == nil { 424 t.Errorf("quota: %q, expected error, got %.2f, nil", c.quota, res) 425 } 426 continue 427 } 428 if err != nil { 429 t.Errorf("quota: %q, expected success, got error %s", c.quota, err) 430 } 431 if res != c.expected { 432 t.Errorf("quota: %q, expected %.2f, got error %.2f", c.quota, c.expected, res) 433 } 434 } 435 }