k8s.io/kubernetes@v1.29.3/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2018 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package kuberuntime 21 22 import ( 23 "context" 24 "fmt" 25 "math" 26 "os" 27 "reflect" 28 "strconv" 29 "testing" 30 31 "k8s.io/kubernetes/pkg/kubelet/cm" 32 "k8s.io/kubernetes/pkg/kubelet/types" 33 34 "github.com/google/go-cmp/cmp" 35 libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups" 36 "github.com/stretchr/testify/assert" 37 v1 "k8s.io/api/core/v1" 38 "k8s.io/apimachinery/pkg/api/resource" 39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 40 utilfeature "k8s.io/apiserver/pkg/util/feature" 41 featuregatetesting "k8s.io/component-base/featuregate/testing" 42 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 43 "k8s.io/kubernetes/pkg/features" 44 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 45 ) 46 47 func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerIndex int, enforceMemoryQoS bool) *runtimeapi.ContainerConfig { 48 ctx := context.Background() 49 container := &pod.Spec.Containers[containerIndex] 50 podIP := "" 51 restartCount := 0 52 opts, _, _ := m.runtimeHelper.GenerateRunContainerOptions(ctx, pod, container, podIP, []string{podIP}) 53 containerLogsPath := buildContainerLogsPath(container.Name, restartCount) 54 restartCountUint32 := uint32(restartCount) 55 envs := make([]*runtimeapi.KeyValue, len(opts.Envs)) 56 57 l, _ := m.generateLinuxContainerConfig(container, pod, new(int64), "", nil, enforceMemoryQoS) 58 59 expectedConfig := &runtimeapi.ContainerConfig{ 60 Metadata: &runtimeapi.ContainerMetadata{ 61 Name: container.Name, 62 Attempt: restartCountUint32, 63 }, 64 Image: &runtimeapi.ImageSpec{Image: container.Image, UserSpecifiedImage: container.Image}, 65 Command: container.Command, 66 Args: []string(nil), 67 WorkingDir: container.WorkingDir, 68 Labels: newContainerLabels(container, pod), 69 Annotations: newContainerAnnotations(container, pod, restartCount, opts), 70 Devices: makeDevices(opts), 71 Mounts: m.makeMounts(opts, container), 72 LogPath: containerLogsPath, 73 Stdin: container.Stdin, 74 StdinOnce: container.StdinOnce, 75 Tty: container.TTY, 76 Linux: l, 77 Envs: envs, 78 CDIDevices: makeCDIDevices(opts), 79 } 80 return expectedConfig 81 } 82 83 func TestGenerateContainerConfig(t *testing.T) { 84 ctx := context.Background() 85 _, imageService, m, err := createTestRuntimeManager() 86 assert.NoError(t, err) 87 88 runAsUser := int64(1000) 89 runAsGroup := int64(2000) 90 pod := &v1.Pod{ 91 ObjectMeta: metav1.ObjectMeta{ 92 UID: "12345678", 93 Name: "bar", 94 Namespace: "new", 95 }, 96 Spec: v1.PodSpec{ 97 Containers: []v1.Container{ 98 { 99 Name: "foo", 100 Image: "busybox", 101 ImagePullPolicy: v1.PullIfNotPresent, 102 Command: []string{"testCommand"}, 103 WorkingDir: "testWorkingDir", 104 SecurityContext: &v1.SecurityContext{ 105 RunAsUser: &runAsUser, 106 RunAsGroup: &runAsGroup, 107 }, 108 }, 109 }, 110 }, 111 } 112 113 expectedConfig := makeExpectedConfig(m, pod, 0, false) 114 containerConfig, _, err := m.generateContainerConfig(ctx, &pod.Spec.Containers[0], pod, 0, "", pod.Spec.Containers[0].Image, []string{}, nil) 115 assert.NoError(t, err) 116 assert.Equal(t, expectedConfig, containerConfig, "generate container config for kubelet runtime v1.") 117 assert.Equal(t, runAsUser, containerConfig.GetLinux().GetSecurityContext().GetRunAsUser().GetValue(), "RunAsUser should be set") 118 assert.Equal(t, runAsGroup, containerConfig.GetLinux().GetSecurityContext().GetRunAsGroup().GetValue(), "RunAsGroup should be set") 119 120 runAsRoot := int64(0) 121 runAsNonRootTrue := true 122 podWithContainerSecurityContext := &v1.Pod{ 123 ObjectMeta: metav1.ObjectMeta{ 124 UID: "12345678", 125 Name: "bar", 126 Namespace: "new", 127 }, 128 Spec: v1.PodSpec{ 129 Containers: []v1.Container{ 130 { 131 Name: "foo", 132 Image: "busybox", 133 ImagePullPolicy: v1.PullIfNotPresent, 134 Command: []string{"testCommand"}, 135 WorkingDir: "testWorkingDir", 136 SecurityContext: &v1.SecurityContext{ 137 RunAsNonRoot: &runAsNonRootTrue, 138 RunAsUser: &runAsRoot, 139 }, 140 }, 141 }, 142 }, 143 } 144 145 _, _, err = m.generateContainerConfig(ctx, &podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil) 146 assert.Error(t, err) 147 148 imageID, _ := imageService.PullImage(ctx, &runtimeapi.ImageSpec{Image: "busybox"}, nil, nil) 149 resp, _ := imageService.ImageStatus(ctx, &runtimeapi.ImageSpec{Image: imageID}, false) 150 151 resp.Image.Uid = nil 152 resp.Image.Username = "test" 153 154 podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsUser = nil 155 podWithContainerSecurityContext.Spec.Containers[0].SecurityContext.RunAsNonRoot = &runAsNonRootTrue 156 157 _, _, err = m.generateContainerConfig(ctx, &podWithContainerSecurityContext.Spec.Containers[0], podWithContainerSecurityContext, 0, "", podWithContainerSecurityContext.Spec.Containers[0].Image, []string{}, nil) 158 assert.Error(t, err, "RunAsNonRoot should fail for non-numeric username") 159 } 160 161 func TestGenerateLinuxContainerConfigResources(t *testing.T) { 162 _, _, m, err := createTestRuntimeManager() 163 m.cpuCFSQuota = true 164 165 assert.NoError(t, err) 166 167 tests := []struct { 168 name string 169 podResources v1.ResourceRequirements 170 expected *runtimeapi.LinuxContainerResources 171 }{ 172 { 173 name: "Request 128M/1C, Limit 256M/3C", 174 podResources: v1.ResourceRequirements{ 175 Requests: v1.ResourceList{ 176 v1.ResourceMemory: resource.MustParse("128Mi"), 177 v1.ResourceCPU: resource.MustParse("1"), 178 }, 179 Limits: v1.ResourceList{ 180 v1.ResourceMemory: resource.MustParse("256Mi"), 181 v1.ResourceCPU: resource.MustParse("3"), 182 }, 183 }, 184 expected: &runtimeapi.LinuxContainerResources{ 185 CpuPeriod: 100000, 186 CpuQuota: 300000, 187 CpuShares: 1024, 188 MemoryLimitInBytes: 256 * 1024 * 1024, 189 }, 190 }, 191 { 192 name: "Request 128M/2C, No Limit", 193 podResources: v1.ResourceRequirements{ 194 Requests: v1.ResourceList{ 195 v1.ResourceMemory: resource.MustParse("128Mi"), 196 v1.ResourceCPU: resource.MustParse("2"), 197 }, 198 }, 199 expected: &runtimeapi.LinuxContainerResources{ 200 CpuPeriod: 100000, 201 CpuQuota: 0, 202 CpuShares: 2048, 203 MemoryLimitInBytes: 0, 204 }, 205 }, 206 } 207 208 for _, test := range tests { 209 pod := &v1.Pod{ 210 ObjectMeta: metav1.ObjectMeta{ 211 UID: "12345678", 212 Name: "bar", 213 Namespace: "new", 214 }, 215 Spec: v1.PodSpec{ 216 Containers: []v1.Container{ 217 { 218 Name: "foo", 219 Image: "busybox", 220 ImagePullPolicy: v1.PullIfNotPresent, 221 Command: []string{"testCommand"}, 222 WorkingDir: "testWorkingDir", 223 Resources: test.podResources, 224 }, 225 }, 226 }, 227 } 228 229 linuxConfig, err := m.generateLinuxContainerConfig(&pod.Spec.Containers[0], pod, new(int64), "", nil, false) 230 assert.NoError(t, err) 231 assert.Equal(t, test.expected.CpuPeriod, linuxConfig.GetResources().CpuPeriod, test.name) 232 assert.Equal(t, test.expected.CpuQuota, linuxConfig.GetResources().CpuQuota, test.name) 233 assert.Equal(t, test.expected.CpuShares, linuxConfig.GetResources().CpuShares, test.name) 234 assert.Equal(t, test.expected.MemoryLimitInBytes, linuxConfig.GetResources().MemoryLimitInBytes, test.name) 235 } 236 } 237 238 func TestCalculateLinuxResources(t *testing.T) { 239 _, _, m, err := createTestRuntimeManager() 240 m.cpuCFSQuota = true 241 242 assert.NoError(t, err) 243 244 generateResourceQuantity := func(str string) *resource.Quantity { 245 quantity := resource.MustParse(str) 246 return &quantity 247 } 248 249 tests := []struct { 250 name string 251 cpuReq *resource.Quantity 252 cpuLim *resource.Quantity 253 memLim *resource.Quantity 254 expected *runtimeapi.LinuxContainerResources 255 cgroupVersion CgroupVersion 256 }{ 257 { 258 name: "Request128MBLimit256MB", 259 cpuReq: generateResourceQuantity("1"), 260 cpuLim: generateResourceQuantity("2"), 261 memLim: generateResourceQuantity("128Mi"), 262 expected: &runtimeapi.LinuxContainerResources{ 263 CpuPeriod: 100000, 264 CpuQuota: 200000, 265 CpuShares: 1024, 266 MemoryLimitInBytes: 134217728, 267 }, 268 cgroupVersion: cgroupV1, 269 }, 270 { 271 name: "RequestNoMemory", 272 cpuReq: generateResourceQuantity("2"), 273 cpuLim: generateResourceQuantity("8"), 274 memLim: generateResourceQuantity("0"), 275 expected: &runtimeapi.LinuxContainerResources{ 276 CpuPeriod: 100000, 277 CpuQuota: 800000, 278 CpuShares: 2048, 279 MemoryLimitInBytes: 0, 280 }, 281 cgroupVersion: cgroupV1, 282 }, 283 { 284 name: "RequestNilCPU", 285 cpuLim: generateResourceQuantity("2"), 286 memLim: generateResourceQuantity("0"), 287 expected: &runtimeapi.LinuxContainerResources{ 288 CpuPeriod: 100000, 289 CpuQuota: 200000, 290 CpuShares: 2048, 291 MemoryLimitInBytes: 0, 292 }, 293 cgroupVersion: cgroupV1, 294 }, 295 { 296 name: "RequestZeroCPU", 297 cpuReq: generateResourceQuantity("0"), 298 cpuLim: generateResourceQuantity("2"), 299 memLim: generateResourceQuantity("0"), 300 expected: &runtimeapi.LinuxContainerResources{ 301 CpuPeriod: 100000, 302 CpuQuota: 200000, 303 CpuShares: 2, 304 MemoryLimitInBytes: 0, 305 }, 306 cgroupVersion: cgroupV1, 307 }, 308 { 309 name: "Request128MBLimit256MB", 310 cpuReq: generateResourceQuantity("1"), 311 cpuLim: generateResourceQuantity("2"), 312 memLim: generateResourceQuantity("128Mi"), 313 expected: &runtimeapi.LinuxContainerResources{ 314 CpuPeriod: 100000, 315 CpuQuota: 200000, 316 CpuShares: 1024, 317 MemoryLimitInBytes: 134217728, 318 Unified: map[string]string{"memory.oom.group": "1"}, 319 }, 320 cgroupVersion: cgroupV2, 321 }, 322 { 323 name: "RequestNoMemory", 324 cpuReq: generateResourceQuantity("2"), 325 cpuLim: generateResourceQuantity("8"), 326 memLim: generateResourceQuantity("0"), 327 expected: &runtimeapi.LinuxContainerResources{ 328 CpuPeriod: 100000, 329 CpuQuota: 800000, 330 CpuShares: 2048, 331 MemoryLimitInBytes: 0, 332 Unified: map[string]string{"memory.oom.group": "1"}, 333 }, 334 cgroupVersion: cgroupV2, 335 }, 336 { 337 name: "RequestNilCPU", 338 cpuLim: generateResourceQuantity("2"), 339 memLim: generateResourceQuantity("0"), 340 expected: &runtimeapi.LinuxContainerResources{ 341 CpuPeriod: 100000, 342 CpuQuota: 200000, 343 CpuShares: 2048, 344 MemoryLimitInBytes: 0, 345 Unified: map[string]string{"memory.oom.group": "1"}, 346 }, 347 cgroupVersion: cgroupV2, 348 }, 349 { 350 name: "RequestZeroCPU", 351 cpuReq: generateResourceQuantity("0"), 352 cpuLim: generateResourceQuantity("2"), 353 memLim: generateResourceQuantity("0"), 354 expected: &runtimeapi.LinuxContainerResources{ 355 CpuPeriod: 100000, 356 CpuQuota: 200000, 357 CpuShares: 2, 358 MemoryLimitInBytes: 0, 359 Unified: map[string]string{"memory.oom.group": "1"}, 360 }, 361 cgroupVersion: cgroupV2, 362 }, 363 } 364 for _, test := range tests { 365 setCgroupVersionDuringTest(test.cgroupVersion) 366 linuxContainerResources := m.calculateLinuxResources(test.cpuReq, test.cpuLim, test.memLim) 367 assert.Equal(t, test.expected, linuxContainerResources) 368 } 369 } 370 371 func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { 372 _, _, m, err := createTestRuntimeManager() 373 assert.NoError(t, err) 374 375 podRequestMemory := resource.MustParse("128Mi") 376 pod1LimitMemory := resource.MustParse("256Mi") 377 pod1 := &v1.Pod{ 378 ObjectMeta: metav1.ObjectMeta{ 379 UID: "12345678", 380 Name: "bar", 381 Namespace: "new", 382 }, 383 Spec: v1.PodSpec{ 384 Containers: []v1.Container{ 385 { 386 Name: "foo", 387 Image: "busybox", 388 ImagePullPolicy: v1.PullIfNotPresent, 389 Command: []string{"testCommand"}, 390 WorkingDir: "testWorkingDir", 391 Resources: v1.ResourceRequirements{ 392 Requests: v1.ResourceList{ 393 v1.ResourceMemory: podRequestMemory, 394 }, 395 Limits: v1.ResourceList{ 396 v1.ResourceMemory: pod1LimitMemory, 397 }, 398 }, 399 }, 400 }, 401 }, 402 } 403 404 pod2 := &v1.Pod{ 405 ObjectMeta: metav1.ObjectMeta{ 406 UID: "12345678", 407 Name: "bar", 408 Namespace: "new", 409 }, 410 Spec: v1.PodSpec{ 411 Containers: []v1.Container{ 412 { 413 Name: "foo", 414 Image: "busybox", 415 ImagePullPolicy: v1.PullIfNotPresent, 416 Command: []string{"testCommand"}, 417 WorkingDir: "testWorkingDir", 418 Resources: v1.ResourceRequirements{ 419 Requests: v1.ResourceList{ 420 v1.ResourceMemory: podRequestMemory, 421 }, 422 }, 423 }, 424 }, 425 }, 426 } 427 pageSize := int64(os.Getpagesize()) 428 memoryNodeAllocatable := resource.MustParse(fakeNodeAllocatableMemory) 429 pod1MemoryHigh := int64(math.Floor( 430 float64(podRequestMemory.Value())+ 431 (float64(pod1LimitMemory.Value())-float64(podRequestMemory.Value()))*float64(m.memoryThrottlingFactor))/float64(pageSize)) * pageSize 432 pod2MemoryHigh := int64(math.Floor( 433 float64(podRequestMemory.Value())+ 434 (float64(memoryNodeAllocatable.Value())-float64(podRequestMemory.Value()))*float64(m.memoryThrottlingFactor))/float64(pageSize)) * pageSize 435 436 type expectedResult struct { 437 containerConfig *runtimeapi.LinuxContainerConfig 438 memoryLow int64 439 memoryHigh int64 440 } 441 l1, _ := m.generateLinuxContainerConfig(&pod1.Spec.Containers[0], pod1, new(int64), "", nil, true) 442 l2, _ := m.generateLinuxContainerConfig(&pod2.Spec.Containers[0], pod2, new(int64), "", nil, true) 443 tests := []struct { 444 name string 445 pod *v1.Pod 446 expected *expectedResult 447 }{ 448 { 449 name: "Request128MBLimit256MB", 450 pod: pod1, 451 expected: &expectedResult{ 452 l1, 453 128 * 1024 * 1024, 454 int64(pod1MemoryHigh), 455 }, 456 }, 457 { 458 name: "Request128MBWithoutLimit", 459 pod: pod2, 460 expected: &expectedResult{ 461 l2, 462 128 * 1024 * 1024, 463 int64(pod2MemoryHigh), 464 }, 465 }, 466 } 467 468 for _, test := range tests { 469 linuxConfig, err := m.generateLinuxContainerConfig(&test.pod.Spec.Containers[0], test.pod, new(int64), "", nil, true) 470 assert.NoError(t, err) 471 assert.Equal(t, test.expected.containerConfig, linuxConfig, test.name) 472 assert.Equal(t, linuxConfig.GetResources().GetUnified()["memory.min"], strconv.FormatInt(test.expected.memoryLow, 10), test.name) 473 assert.Equal(t, linuxConfig.GetResources().GetUnified()["memory.high"], strconv.FormatInt(test.expected.memoryHigh, 10), test.name) 474 } 475 } 476 477 func TestGetHugepageLimitsFromResources(t *testing.T) { 478 var baseHugepage []*runtimeapi.HugepageLimit 479 480 // For each page size, limit to 0. 481 for _, pageSize := range libcontainercgroups.HugePageSizes() { 482 baseHugepage = append(baseHugepage, &runtimeapi.HugepageLimit{ 483 PageSize: pageSize, 484 Limit: uint64(0), 485 }) 486 } 487 488 tests := []struct { 489 name string 490 resources v1.ResourceRequirements 491 expected []*runtimeapi.HugepageLimit 492 }{ 493 { 494 name: "Success2MB", 495 resources: v1.ResourceRequirements{ 496 Limits: v1.ResourceList{ 497 "hugepages-2Mi": resource.MustParse("2Mi"), 498 }, 499 }, 500 expected: []*runtimeapi.HugepageLimit{ 501 { 502 PageSize: "2MB", 503 Limit: 2097152, 504 }, 505 }, 506 }, 507 { 508 name: "Success1GB", 509 resources: v1.ResourceRequirements{ 510 Limits: v1.ResourceList{ 511 "hugepages-1Gi": resource.MustParse("2Gi"), 512 }, 513 }, 514 expected: []*runtimeapi.HugepageLimit{ 515 { 516 PageSize: "1GB", 517 Limit: 2147483648, 518 }, 519 }, 520 }, 521 { 522 name: "Skip2MB", 523 resources: v1.ResourceRequirements{ 524 Limits: v1.ResourceList{ 525 "hugepages-2MB": resource.MustParse("2Mi"), 526 }, 527 }, 528 expected: []*runtimeapi.HugepageLimit{ 529 { 530 PageSize: "2MB", 531 Limit: 0, 532 }, 533 }, 534 }, 535 { 536 name: "Skip1GB", 537 resources: v1.ResourceRequirements{ 538 Limits: v1.ResourceList{ 539 "hugepages-1GB": resource.MustParse("2Gi"), 540 }, 541 }, 542 expected: []*runtimeapi.HugepageLimit{ 543 { 544 PageSize: "1GB", 545 Limit: 0, 546 }, 547 }, 548 }, 549 { 550 name: "Success2MBand1GB", 551 resources: v1.ResourceRequirements{ 552 Limits: v1.ResourceList{ 553 v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"), 554 "hugepages-2Mi": resource.MustParse("2Mi"), 555 "hugepages-1Gi": resource.MustParse("2Gi"), 556 }, 557 }, 558 expected: []*runtimeapi.HugepageLimit{ 559 { 560 PageSize: "2MB", 561 Limit: 2097152, 562 }, 563 { 564 PageSize: "1GB", 565 Limit: 2147483648, 566 }, 567 }, 568 }, 569 { 570 name: "Skip2MBand1GB", 571 resources: v1.ResourceRequirements{ 572 Limits: v1.ResourceList{ 573 v1.ResourceName(v1.ResourceCPU): resource.MustParse("0"), 574 "hugepages-2MB": resource.MustParse("2Mi"), 575 "hugepages-1GB": resource.MustParse("2Gi"), 576 }, 577 }, 578 expected: []*runtimeapi.HugepageLimit{ 579 { 580 PageSize: "2MB", 581 Limit: 0, 582 }, 583 { 584 PageSize: "1GB", 585 Limit: 0, 586 }, 587 }, 588 }, 589 } 590 591 for _, test := range tests { 592 // Validate if machine supports hugepage size that used in test case. 593 machineHugepageSupport := true 594 for _, hugepageLimit := range test.expected { 595 hugepageSupport := false 596 for _, pageSize := range libcontainercgroups.HugePageSizes() { 597 if pageSize == hugepageLimit.PageSize { 598 hugepageSupport = true 599 break 600 } 601 } 602 603 if !hugepageSupport { 604 machineHugepageSupport = false 605 break 606 } 607 } 608 609 // Case of machine can't support hugepage size 610 if !machineHugepageSupport { 611 continue 612 } 613 614 expectedHugepages := baseHugepage 615 for _, hugepage := range test.expected { 616 for _, expectedHugepage := range expectedHugepages { 617 if expectedHugepage.PageSize == hugepage.PageSize { 618 expectedHugepage.Limit = hugepage.Limit 619 } 620 } 621 } 622 623 results := GetHugepageLimitsFromResources(test.resources) 624 if !reflect.DeepEqual(expectedHugepages, results) { 625 t.Errorf("%s test failed. Expected %v but got %v", test.name, expectedHugepages, results) 626 } 627 628 for _, hugepage := range baseHugepage { 629 hugepage.Limit = uint64(0) 630 } 631 } 632 } 633 634 func TestGenerateLinuxContainerConfigNamespaces(t *testing.T) { 635 _, _, m, err := createTestRuntimeManager() 636 if err != nil { 637 t.Fatalf("error creating test RuntimeManager: %v", err) 638 } 639 640 for _, tc := range []struct { 641 name string 642 pod *v1.Pod 643 target *kubecontainer.ContainerID 644 want *runtimeapi.NamespaceOption 645 }{ 646 { 647 "Default namespaces", 648 &v1.Pod{ 649 Spec: v1.PodSpec{ 650 Containers: []v1.Container{ 651 {Name: "test"}, 652 }, 653 }, 654 }, 655 nil, 656 &runtimeapi.NamespaceOption{ 657 Pid: runtimeapi.NamespaceMode_CONTAINER, 658 }, 659 }, 660 { 661 "PID Namespace POD", 662 &v1.Pod{ 663 Spec: v1.PodSpec{ 664 Containers: []v1.Container{ 665 {Name: "test"}, 666 }, 667 ShareProcessNamespace: &[]bool{true}[0], 668 }, 669 }, 670 nil, 671 &runtimeapi.NamespaceOption{ 672 Pid: runtimeapi.NamespaceMode_POD, 673 }, 674 }, 675 { 676 "PID Namespace TARGET", 677 &v1.Pod{ 678 Spec: v1.PodSpec{ 679 Containers: []v1.Container{ 680 {Name: "test"}, 681 }, 682 }, 683 }, 684 &kubecontainer.ContainerID{Type: "docker", ID: "really-long-id-string"}, 685 &runtimeapi.NamespaceOption{ 686 Pid: runtimeapi.NamespaceMode_TARGET, 687 TargetId: "really-long-id-string", 688 }, 689 }, 690 } { 691 t.Run(tc.name, func(t *testing.T) { 692 got, err := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", tc.target, false) 693 assert.NoError(t, err) 694 if diff := cmp.Diff(tc.want, got.SecurityContext.NamespaceOptions); diff != "" { 695 t.Errorf("%v: diff (-want +got):\n%v", t.Name(), diff) 696 } 697 }) 698 } 699 } 700 701 func TestGenerateLinuxContainerResources(t *testing.T) { 702 _, _, m, err := createTestRuntimeManager() 703 assert.NoError(t, err) 704 m.machineInfo.MemoryCapacity = 17179860387 // 16GB 705 706 pod := &v1.Pod{ 707 ObjectMeta: metav1.ObjectMeta{ 708 UID: "12345678", 709 Name: "foo", 710 Namespace: "bar", 711 }, 712 Spec: v1.PodSpec{ 713 Containers: []v1.Container{ 714 { 715 Name: "c1", 716 Image: "busybox", 717 }, 718 }, 719 }, 720 Status: v1.PodStatus{}, 721 } 722 723 for _, tc := range []struct { 724 name string 725 scalingFg bool 726 limits v1.ResourceList 727 requests v1.ResourceList 728 cStatus []v1.ContainerStatus 729 expected *runtimeapi.LinuxContainerResources 730 }{ 731 { 732 "requests & limits, cpu & memory, guaranteed qos - no container status", 733 true, 734 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 735 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 736 []v1.ContainerStatus{}, 737 &runtimeapi.LinuxContainerResources{CpuShares: 256, MemoryLimitInBytes: 524288000, OomScoreAdj: -997}, 738 }, 739 { 740 "requests & limits, cpu & memory, burstable qos - no container status", 741 true, 742 v1.ResourceList{v1.ResourceCPU: resource.MustParse("500m"), v1.ResourceMemory: resource.MustParse("750Mi")}, 743 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 744 []v1.ContainerStatus{}, 745 &runtimeapi.LinuxContainerResources{CpuShares: 256, MemoryLimitInBytes: 786432000, OomScoreAdj: 970}, 746 }, 747 { 748 "best-effort qos - no container status", 749 true, 750 nil, 751 nil, 752 []v1.ContainerStatus{}, 753 &runtimeapi.LinuxContainerResources{CpuShares: 2, OomScoreAdj: 1000}, 754 }, 755 { 756 "requests & limits, cpu & memory, guaranteed qos - empty resources container status", 757 true, 758 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 759 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 760 []v1.ContainerStatus{{Name: "c1"}}, 761 &runtimeapi.LinuxContainerResources{CpuShares: 256, MemoryLimitInBytes: 524288000, OomScoreAdj: -997}, 762 }, 763 { 764 "requests & limits, cpu & memory, burstable qos - empty resources container status", 765 true, 766 v1.ResourceList{v1.ResourceCPU: resource.MustParse("500m"), v1.ResourceMemory: resource.MustParse("750Mi")}, 767 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 768 []v1.ContainerStatus{{Name: "c1"}}, 769 &runtimeapi.LinuxContainerResources{CpuShares: 256, MemoryLimitInBytes: 786432000, OomScoreAdj: 999}, 770 }, 771 { 772 "best-effort qos - empty resources container status", 773 true, 774 nil, 775 nil, 776 []v1.ContainerStatus{{Name: "c1"}}, 777 &runtimeapi.LinuxContainerResources{CpuShares: 2, OomScoreAdj: 1000}, 778 }, 779 { 780 "requests & limits, cpu & memory, guaranteed qos - container status with allocatedResources", 781 true, 782 v1.ResourceList{v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 783 v1.ResourceList{v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 784 []v1.ContainerStatus{ 785 { 786 Name: "c1", 787 AllocatedResources: v1.ResourceList{v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 788 }, 789 }, 790 &runtimeapi.LinuxContainerResources{CpuShares: 204, MemoryLimitInBytes: 524288000, OomScoreAdj: -997}, 791 }, 792 { 793 "requests & limits, cpu & memory, burstable qos - container status with allocatedResources", 794 true, 795 v1.ResourceList{v1.ResourceCPU: resource.MustParse("500m"), v1.ResourceMemory: resource.MustParse("750Mi")}, 796 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 797 []v1.ContainerStatus{ 798 { 799 Name: "c1", 800 AllocatedResources: v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 801 }, 802 }, 803 &runtimeapi.LinuxContainerResources{CpuShares: 256, MemoryLimitInBytes: 786432000, OomScoreAdj: 970}, 804 }, 805 { 806 "requests & limits, cpu & memory, guaranteed qos - no container status", 807 false, 808 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 809 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 810 []v1.ContainerStatus{}, 811 &runtimeapi.LinuxContainerResources{CpuShares: 256, MemoryLimitInBytes: 524288000, OomScoreAdj: -997}, 812 }, 813 { 814 "requests & limits, cpu & memory, burstable qos - container status with allocatedResources", 815 false, 816 v1.ResourceList{v1.ResourceCPU: resource.MustParse("500m"), v1.ResourceMemory: resource.MustParse("750Mi")}, 817 v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 818 []v1.ContainerStatus{ 819 { 820 Name: "c1", 821 AllocatedResources: v1.ResourceList{v1.ResourceCPU: resource.MustParse("250m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 822 }, 823 }, 824 &runtimeapi.LinuxContainerResources{CpuShares: 256, MemoryLimitInBytes: 786432000, OomScoreAdj: 970}, 825 }, 826 { 827 "requests & limits, cpu & memory, guaranteed qos - container status with allocatedResources", 828 false, 829 v1.ResourceList{v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 830 v1.ResourceList{v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 831 []v1.ContainerStatus{ 832 { 833 Name: "c1", 834 AllocatedResources: v1.ResourceList{v1.ResourceCPU: resource.MustParse("200m"), v1.ResourceMemory: resource.MustParse("500Mi")}, 835 }, 836 }, 837 &runtimeapi.LinuxContainerResources{CpuShares: 204, MemoryLimitInBytes: 524288000, OomScoreAdj: -997}, 838 }, 839 { 840 "best-effort qos - no container status", 841 false, 842 nil, 843 nil, 844 []v1.ContainerStatus{}, 845 &runtimeapi.LinuxContainerResources{CpuShares: 2, OomScoreAdj: 1000}, 846 }, 847 } { 848 t.Run(tc.name, func(t *testing.T) { 849 defer setSwapControllerAvailableDuringTest(false)() 850 if tc.scalingFg { 851 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true)() 852 } 853 854 setCgroupVersionDuringTest(cgroupV1) 855 856 pod.Spec.Containers[0].Resources = v1.ResourceRequirements{Limits: tc.limits, Requests: tc.requests} 857 if len(tc.cStatus) > 0 { 858 pod.Status.ContainerStatuses = tc.cStatus 859 } 860 resources := m.generateLinuxContainerResources(pod, &pod.Spec.Containers[0], false) 861 tc.expected.HugepageLimits = resources.HugepageLimits 862 if !cmp.Equal(resources, tc.expected) { 863 t.Errorf("Test %s: expected resources %+v, but got %+v", tc.name, tc.expected, resources) 864 } 865 }) 866 } 867 //TODO(vinaykul,InPlacePodVerticalScaling): Add unit tests for cgroup v1 & v2 868 } 869 870 func TestGenerateLinuxContainerResourcesWithSwap(t *testing.T) { 871 _, _, m, err := createTestRuntimeManager() 872 assert.NoError(t, err) 873 m.machineInfo.MemoryCapacity = 42949672960 // 40Gb == 40 * 1024^3 874 m.machineInfo.SwapCapacity = 5368709120 // 5Gb == 5 * 1024^3 875 876 pod := &v1.Pod{ 877 ObjectMeta: metav1.ObjectMeta{ 878 UID: "12345678", 879 Name: "foo", 880 Namespace: "bar", 881 }, 882 Spec: v1.PodSpec{ 883 Containers: []v1.Container{ 884 { 885 Name: "c1", 886 }, 887 { 888 Name: "c2", 889 }, 890 }, 891 }, 892 Status: v1.PodStatus{}, 893 } 894 895 expectSwapDisabled := func(cgroupVersion CgroupVersion, resources ...*runtimeapi.LinuxContainerResources) { 896 const msg = "container is expected to not have swap configured" 897 898 for _, r := range resources { 899 switch cgroupVersion { 900 case cgroupV1: 901 assert.Equal(t, int64(0), r.MemorySwapLimitInBytes, msg) 902 case cgroupV2: 903 assert.NotContains(t, r.Unified, cm.Cgroup2MaxSwapFilename, msg) 904 } 905 } 906 } 907 908 expectNoSwap := func(cgroupVersion CgroupVersion, resources ...*runtimeapi.LinuxContainerResources) { 909 const msg = "container is expected to not have swap access" 910 911 for _, r := range resources { 912 switch cgroupVersion { 913 case cgroupV1: 914 assert.Equal(t, r.MemoryLimitInBytes, r.MemorySwapLimitInBytes, msg) 915 case cgroupV2: 916 assert.Equal(t, "0", r.Unified[cm.Cgroup2MaxSwapFilename], msg) 917 } 918 } 919 } 920 921 expectUnlimitedSwap := func(cgroupVersion CgroupVersion, resources ...*runtimeapi.LinuxContainerResources) { 922 const msg = "container is expected to have unlimited swap access" 923 924 for _, r := range resources { 925 switch cgroupVersion { 926 case cgroupV1: 927 assert.Equal(t, int64(-1), r.MemorySwapLimitInBytes, msg) 928 case cgroupV2: 929 assert.Equal(t, "max", r.Unified[cm.Cgroup2MaxSwapFilename], msg) 930 } 931 } 932 } 933 934 expectSwap := func(cgroupVersion CgroupVersion, swapBytesExpected int64, resources *runtimeapi.LinuxContainerResources) { 935 msg := fmt.Sprintf("container swap is expected to be limited by %d bytes", swapBytesExpected) 936 937 switch cgroupVersion { 938 case cgroupV1: 939 assert.Equal(t, resources.MemoryLimitInBytes+swapBytesExpected, resources.MemorySwapLimitInBytes, msg) 940 case cgroupV2: 941 assert.Equal(t, fmt.Sprintf("%d", swapBytesExpected), resources.Unified[cm.Cgroup2MaxSwapFilename], msg) 942 } 943 } 944 945 calcSwapForBurstablePods := func(containerMemoryRequest int64) int64 { 946 swapSize, err := calcSwapForBurstablePods(containerMemoryRequest, int64(m.machineInfo.MemoryCapacity), int64(m.machineInfo.SwapCapacity)) 947 assert.NoError(t, err) 948 949 return swapSize 950 } 951 952 for _, tc := range []struct { 953 name string 954 cgroupVersion CgroupVersion 955 qosClass v1.PodQOSClass 956 swapDisabledOnNode bool 957 nodeSwapFeatureGateEnabled bool 958 swapBehavior string 959 addContainerWithoutRequests bool 960 addGuaranteedContainer bool 961 }{ 962 // With cgroup v1 963 { 964 name: "cgroups v1, LimitedSwap, Burstable QoS", 965 cgroupVersion: cgroupV1, 966 qosClass: v1.PodQOSBurstable, 967 nodeSwapFeatureGateEnabled: true, 968 swapBehavior: types.LimitedSwap, 969 }, 970 { 971 name: "cgroups v1, UnlimitedSwap, Burstable QoS", 972 cgroupVersion: cgroupV1, 973 qosClass: v1.PodQOSBurstable, 974 nodeSwapFeatureGateEnabled: true, 975 swapBehavior: types.UnlimitedSwap, 976 }, 977 { 978 name: "cgroups v1, LimitedSwap, Best-effort QoS", 979 cgroupVersion: cgroupV1, 980 qosClass: v1.PodQOSBestEffort, 981 nodeSwapFeatureGateEnabled: true, 982 swapBehavior: types.LimitedSwap, 983 }, 984 985 // With feature gate turned off 986 { 987 name: "NodeSwap feature gate turned off, cgroups v2, LimitedSwap", 988 cgroupVersion: cgroupV2, 989 qosClass: v1.PodQOSBurstable, 990 nodeSwapFeatureGateEnabled: false, 991 swapBehavior: types.LimitedSwap, 992 }, 993 { 994 name: "NodeSwap feature gate turned off, cgroups v2, UnlimitedSwap", 995 cgroupVersion: cgroupV2, 996 qosClass: v1.PodQOSBurstable, 997 nodeSwapFeatureGateEnabled: false, 998 swapBehavior: types.UnlimitedSwap, 999 }, 1000 1001 // With no swapBehavior, UnlimitedSwap should be the default 1002 { 1003 name: "With no swapBehavior - UnlimitedSwap should be the default", 1004 cgroupVersion: cgroupV2, 1005 qosClass: v1.PodQOSBestEffort, 1006 nodeSwapFeatureGateEnabled: true, 1007 swapBehavior: "", 1008 }, 1009 1010 // With Guaranteed and Best-effort QoS 1011 { 1012 name: "Best-effort QoS, cgroups v2, LimitedSwap", 1013 cgroupVersion: cgroupV2, 1014 qosClass: v1.PodQOSBurstable, 1015 nodeSwapFeatureGateEnabled: true, 1016 swapBehavior: types.LimitedSwap, 1017 }, 1018 { 1019 name: "Best-effort QoS, cgroups v2, UnlimitedSwap", 1020 cgroupVersion: cgroupV2, 1021 qosClass: v1.PodQOSBurstable, 1022 nodeSwapFeatureGateEnabled: true, 1023 swapBehavior: types.UnlimitedSwap, 1024 }, 1025 { 1026 name: "Guaranteed QoS, cgroups v2, LimitedSwap", 1027 cgroupVersion: cgroupV2, 1028 qosClass: v1.PodQOSGuaranteed, 1029 nodeSwapFeatureGateEnabled: true, 1030 swapBehavior: types.LimitedSwap, 1031 }, 1032 { 1033 name: "Guaranteed QoS, cgroups v2, UnlimitedSwap", 1034 cgroupVersion: cgroupV2, 1035 qosClass: v1.PodQOSGuaranteed, 1036 nodeSwapFeatureGateEnabled: true, 1037 swapBehavior: types.UnlimitedSwap, 1038 }, 1039 1040 // With a "guaranteed" container (when memory requests equal to limits) 1041 { 1042 name: "Burstable QoS, cgroups v2, LimitedSwap, with a guaranteed container", 1043 cgroupVersion: cgroupV2, 1044 qosClass: v1.PodQOSBurstable, 1045 nodeSwapFeatureGateEnabled: true, 1046 swapBehavior: types.LimitedSwap, 1047 addContainerWithoutRequests: false, 1048 addGuaranteedContainer: true, 1049 }, 1050 { 1051 name: "Burstable QoS, cgroups v2, UnlimitedSwap, with a guaranteed container", 1052 cgroupVersion: cgroupV2, 1053 qosClass: v1.PodQOSBurstable, 1054 nodeSwapFeatureGateEnabled: true, 1055 swapBehavior: types.UnlimitedSwap, 1056 addContainerWithoutRequests: false, 1057 addGuaranteedContainer: true, 1058 }, 1059 1060 // Swap is expected to be allocated 1061 { 1062 name: "Burstable QoS, cgroups v2, LimitedSwap", 1063 cgroupVersion: cgroupV2, 1064 qosClass: v1.PodQOSBurstable, 1065 nodeSwapFeatureGateEnabled: true, 1066 swapBehavior: types.LimitedSwap, 1067 addContainerWithoutRequests: false, 1068 addGuaranteedContainer: false, 1069 }, 1070 { 1071 name: "Burstable QoS, cgroups v2, UnlimitedSwap", 1072 cgroupVersion: cgroupV2, 1073 qosClass: v1.PodQOSBurstable, 1074 nodeSwapFeatureGateEnabled: true, 1075 swapBehavior: types.UnlimitedSwap, 1076 addContainerWithoutRequests: false, 1077 addGuaranteedContainer: false, 1078 }, 1079 { 1080 name: "Burstable QoS, cgroups v2, LimitedSwap, with a container with no requests", 1081 cgroupVersion: cgroupV2, 1082 qosClass: v1.PodQOSBurstable, 1083 nodeSwapFeatureGateEnabled: true, 1084 swapBehavior: types.LimitedSwap, 1085 addContainerWithoutRequests: true, 1086 addGuaranteedContainer: false, 1087 }, 1088 { 1089 name: "Burstable QoS, cgroups v2, UnlimitedSwap, with a container with no requests", 1090 cgroupVersion: cgroupV2, 1091 qosClass: v1.PodQOSBurstable, 1092 nodeSwapFeatureGateEnabled: true, 1093 swapBehavior: types.UnlimitedSwap, 1094 addContainerWithoutRequests: true, 1095 addGuaranteedContainer: false, 1096 }, 1097 // All the above examples with Swap disabled on node 1098 { 1099 name: "Swap disabled on node, cgroups v1, LimitedSwap, Burstable QoS", 1100 swapDisabledOnNode: true, 1101 cgroupVersion: cgroupV1, 1102 qosClass: v1.PodQOSBurstable, 1103 nodeSwapFeatureGateEnabled: true, 1104 swapBehavior: types.LimitedSwap, 1105 }, 1106 { 1107 name: "Swap disabled on node, cgroups v1, UnlimitedSwap, Burstable QoS", 1108 swapDisabledOnNode: true, 1109 cgroupVersion: cgroupV1, 1110 qosClass: v1.PodQOSBurstable, 1111 nodeSwapFeatureGateEnabled: true, 1112 swapBehavior: types.UnlimitedSwap, 1113 }, 1114 { 1115 name: "Swap disabled on node, cgroups v1, LimitedSwap, Best-effort QoS", 1116 swapDisabledOnNode: true, 1117 cgroupVersion: cgroupV1, 1118 qosClass: v1.PodQOSBestEffort, 1119 nodeSwapFeatureGateEnabled: true, 1120 swapBehavior: types.LimitedSwap, 1121 }, 1122 1123 // With feature gate turned off 1124 { 1125 name: "Swap disabled on node, NodeSwap feature gate turned off, cgroups v2, LimitedSwap", 1126 swapDisabledOnNode: true, 1127 cgroupVersion: cgroupV2, 1128 qosClass: v1.PodQOSBurstable, 1129 nodeSwapFeatureGateEnabled: false, 1130 swapBehavior: types.LimitedSwap, 1131 }, 1132 { 1133 name: "Swap disabled on node, NodeSwap feature gate turned off, cgroups v2, UnlimitedSwap", 1134 swapDisabledOnNode: true, 1135 cgroupVersion: cgroupV2, 1136 qosClass: v1.PodQOSBurstable, 1137 nodeSwapFeatureGateEnabled: false, 1138 swapBehavior: types.UnlimitedSwap, 1139 }, 1140 1141 // With no swapBehavior, UnlimitedSwap should be the default 1142 { 1143 name: "Swap disabled on node, With no swapBehavior - UnlimitedSwap should be the default", 1144 swapDisabledOnNode: true, 1145 cgroupVersion: cgroupV2, 1146 qosClass: v1.PodQOSBestEffort, 1147 nodeSwapFeatureGateEnabled: true, 1148 swapBehavior: "", 1149 }, 1150 1151 // With Guaranteed and Best-effort QoS 1152 { 1153 name: "Swap disabled on node, Best-effort QoS, cgroups v2, LimitedSwap", 1154 swapDisabledOnNode: true, 1155 cgroupVersion: cgroupV2, 1156 qosClass: v1.PodQOSBurstable, 1157 nodeSwapFeatureGateEnabled: true, 1158 swapBehavior: types.LimitedSwap, 1159 }, 1160 { 1161 name: "Swap disabled on node, Best-effort QoS, cgroups v2, UnlimitedSwap", 1162 swapDisabledOnNode: true, 1163 cgroupVersion: cgroupV2, 1164 qosClass: v1.PodQOSBurstable, 1165 nodeSwapFeatureGateEnabled: true, 1166 swapBehavior: types.UnlimitedSwap, 1167 }, 1168 { 1169 name: "Swap disabled on node, Guaranteed QoS, cgroups v2, LimitedSwap", 1170 swapDisabledOnNode: true, 1171 cgroupVersion: cgroupV2, 1172 qosClass: v1.PodQOSGuaranteed, 1173 nodeSwapFeatureGateEnabled: true, 1174 swapBehavior: types.LimitedSwap, 1175 }, 1176 { 1177 name: "Swap disabled on node, Guaranteed QoS, cgroups v2, UnlimitedSwap", 1178 swapDisabledOnNode: true, 1179 cgroupVersion: cgroupV2, 1180 qosClass: v1.PodQOSGuaranteed, 1181 nodeSwapFeatureGateEnabled: true, 1182 swapBehavior: types.UnlimitedSwap, 1183 }, 1184 1185 // With a "guaranteed" container (when memory requests equal to limits) 1186 { 1187 name: "Swap disabled on node, Burstable QoS, cgroups v2, LimitedSwap, with a guaranteed container", 1188 swapDisabledOnNode: true, 1189 cgroupVersion: cgroupV2, 1190 qosClass: v1.PodQOSBurstable, 1191 nodeSwapFeatureGateEnabled: true, 1192 swapBehavior: types.LimitedSwap, 1193 addContainerWithoutRequests: false, 1194 addGuaranteedContainer: true, 1195 }, 1196 { 1197 name: "Swap disabled on node, Burstable QoS, cgroups v2, UnlimitedSwap, with a guaranteed container", 1198 swapDisabledOnNode: true, 1199 cgroupVersion: cgroupV2, 1200 qosClass: v1.PodQOSBurstable, 1201 nodeSwapFeatureGateEnabled: true, 1202 swapBehavior: types.UnlimitedSwap, 1203 addContainerWithoutRequests: false, 1204 addGuaranteedContainer: true, 1205 }, 1206 1207 // Swap is expected to be allocated 1208 { 1209 name: "Swap disabled on node, Burstable QoS, cgroups v2, LimitedSwap", 1210 swapDisabledOnNode: true, 1211 cgroupVersion: cgroupV2, 1212 qosClass: v1.PodQOSBurstable, 1213 nodeSwapFeatureGateEnabled: true, 1214 swapBehavior: types.LimitedSwap, 1215 addContainerWithoutRequests: false, 1216 addGuaranteedContainer: false, 1217 }, 1218 { 1219 name: "Swap disabled on node, Burstable QoS, cgroups v2, UnlimitedSwap", 1220 swapDisabledOnNode: true, 1221 cgroupVersion: cgroupV2, 1222 qosClass: v1.PodQOSBurstable, 1223 nodeSwapFeatureGateEnabled: true, 1224 swapBehavior: types.UnlimitedSwap, 1225 addContainerWithoutRequests: false, 1226 addGuaranteedContainer: false, 1227 }, 1228 { 1229 name: "Swap disabled on node, Burstable QoS, cgroups v2, LimitedSwap, with a container with no requests", 1230 swapDisabledOnNode: true, 1231 cgroupVersion: cgroupV2, 1232 qosClass: v1.PodQOSBurstable, 1233 nodeSwapFeatureGateEnabled: true, 1234 swapBehavior: types.LimitedSwap, 1235 addContainerWithoutRequests: true, 1236 addGuaranteedContainer: false, 1237 }, 1238 { 1239 name: "Swap disabled on node, Burstable QoS, cgroups v2, UnlimitedSwap, with a container with no requests", 1240 swapDisabledOnNode: true, 1241 cgroupVersion: cgroupV2, 1242 qosClass: v1.PodQOSBurstable, 1243 nodeSwapFeatureGateEnabled: true, 1244 swapBehavior: types.UnlimitedSwap, 1245 addContainerWithoutRequests: true, 1246 addGuaranteedContainer: false, 1247 }, 1248 } { 1249 t.Run(tc.name, func(t *testing.T) { 1250 setCgroupVersionDuringTest(tc.cgroupVersion) 1251 defer setSwapControllerAvailableDuringTest(!tc.swapDisabledOnNode)() 1252 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeSwap, tc.nodeSwapFeatureGateEnabled)() 1253 m.memorySwapBehavior = tc.swapBehavior 1254 1255 var resourceReqsC1, resourceReqsC2 v1.ResourceRequirements 1256 switch tc.qosClass { 1257 case v1.PodQOSBurstable: 1258 resourceReqsC1 = v1.ResourceRequirements{ 1259 Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("1Gi")}, 1260 } 1261 1262 if !tc.addContainerWithoutRequests { 1263 resourceReqsC2 = v1.ResourceRequirements{ 1264 Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Gi")}, 1265 } 1266 1267 if tc.addGuaranteedContainer { 1268 resourceReqsC2.Limits = v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Gi")} 1269 } 1270 } 1271 case v1.PodQOSGuaranteed: 1272 resourceReqsC1 = v1.ResourceRequirements{ 1273 Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("1Gi"), v1.ResourceCPU: resource.MustParse("1")}, 1274 Limits: v1.ResourceList{v1.ResourceMemory: resource.MustParse("1Gi"), v1.ResourceCPU: resource.MustParse("1")}, 1275 } 1276 resourceReqsC2 = v1.ResourceRequirements{ 1277 Requests: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Gi"), v1.ResourceCPU: resource.MustParse("1")}, 1278 Limits: v1.ResourceList{v1.ResourceMemory: resource.MustParse("2Gi"), v1.ResourceCPU: resource.MustParse("1")}, 1279 } 1280 } 1281 pod.Spec.Containers[0].Resources = resourceReqsC1 1282 pod.Spec.Containers[1].Resources = resourceReqsC2 1283 1284 resourcesC1 := m.generateLinuxContainerResources(pod, &pod.Spec.Containers[0], false) 1285 resourcesC2 := m.generateLinuxContainerResources(pod, &pod.Spec.Containers[1], false) 1286 1287 if tc.swapDisabledOnNode { 1288 expectSwapDisabled(tc.cgroupVersion, resourcesC1, resourcesC2) 1289 return 1290 } 1291 1292 if !tc.nodeSwapFeatureGateEnabled || tc.cgroupVersion == cgroupV1 || (tc.swapBehavior == types.LimitedSwap && tc.qosClass != v1.PodQOSBurstable) { 1293 expectNoSwap(tc.cgroupVersion, resourcesC1, resourcesC2) 1294 return 1295 } 1296 1297 if tc.swapBehavior == types.UnlimitedSwap || tc.swapBehavior == "" { 1298 expectUnlimitedSwap(tc.cgroupVersion, resourcesC1, resourcesC2) 1299 return 1300 } 1301 1302 c1ExpectedSwap := calcSwapForBurstablePods(resourceReqsC1.Requests.Memory().Value()) 1303 c2ExpectedSwap := int64(0) 1304 if !tc.addContainerWithoutRequests && !tc.addGuaranteedContainer { 1305 c2ExpectedSwap = calcSwapForBurstablePods(resourceReqsC2.Requests.Memory().Value()) 1306 } 1307 1308 expectSwap(tc.cgroupVersion, c1ExpectedSwap, resourcesC1) 1309 expectSwap(tc.cgroupVersion, c2ExpectedSwap, resourcesC2) 1310 }) 1311 } 1312 } 1313 1314 type CgroupVersion string 1315 1316 const ( 1317 cgroupV1 CgroupVersion = "v1" 1318 cgroupV2 CgroupVersion = "v2" 1319 ) 1320 1321 func setCgroupVersionDuringTest(version CgroupVersion) { 1322 isCgroup2UnifiedMode = func() bool { 1323 return version == cgroupV2 1324 } 1325 } 1326 1327 func setSwapControllerAvailableDuringTest(available bool) func() { 1328 original := swapControllerAvailable 1329 swapControllerAvailable = func() bool { 1330 return available 1331 } 1332 1333 return func() { 1334 swapControllerAvailable = original 1335 } 1336 }