Skip to content

Commit 5c7491b

Browse files
authored
accept rounded container cpu limits in container cgroup tests (kubernetes#131059)
1 parent 83bb5d5 commit 5c7491b

File tree

4 files changed

+140
-27
lines changed

4 files changed

+140
-27
lines changed

test/e2e/common/node/pod_level_resources.go

+5-8
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,9 @@ func verifyPodCgroups(ctx context.Context, f *framework.Framework, pod *v1.Pod,
233233
}
234234

235235
cpuLimCgPath := fmt.Sprintf("%s/%s", podCgPath, cgroupv2CPULimit)
236-
cpuQuota := kubecm.MilliCPUToQuota(expectedResources.Limits.Cpu().MilliValue(), kubecm.QuotaPeriod)
237-
expectedCPULimit := strconv.FormatInt(cpuQuota, 10)
238-
expectedCPULimit = fmt.Sprintf("%s %s", expectedCPULimit, CPUPeriod)
239-
err = e2epod.VerifyCgroupValue(f, pod, pod.Spec.Containers[0].Name, cpuLimCgPath, expectedCPULimit)
236+
expectedCPULimits := e2epod.GetCPULimitCgroupExpectations(expectedResources.Limits.Cpu())
237+
238+
err = e2epod.VerifyCgroupValue(f, pod, pod.Spec.Containers[0].Name, cpuLimCgPath, expectedCPULimits...)
240239
if err != nil {
241240
errs = append(errs, fmt.Errorf("failed to verify cpu limit cgroup value: %w", err))
242241
}
@@ -395,10 +394,8 @@ func verifyContainersCgroupLimits(f *framework.Framework, pod *v1.Pod) error {
395394

396395
if pod.Spec.Resources != nil && pod.Spec.Resources.Limits.Cpu() != nil &&
397396
container.Resources.Limits.Cpu() == nil {
398-
cpuQuota := kubecm.MilliCPUToQuota(pod.Spec.Resources.Limits.Cpu().MilliValue(), kubecm.QuotaPeriod)
399-
expectedCPULimit := strconv.FormatInt(cpuQuota, 10)
400-
expectedCPULimit = fmt.Sprintf("%s %s", expectedCPULimit, CPUPeriod)
401-
err := e2epod.VerifyCgroupValue(f, pod, container.Name, fmt.Sprintf("%s/%s", cgroupFsPath, cgroupv2CPULimit), expectedCPULimit)
397+
expectedCPULimits := e2epod.GetCPULimitCgroupExpectations(pod.Spec.Resources.Limits.Cpu())
398+
err := e2epod.VerifyCgroupValue(f, pod, container.Name, fmt.Sprintf("%s/%s", cgroupFsPath, cgroupv2CPULimit), expectedCPULimits...)
402399
if err != nil {
403400
errs = append(errs, fmt.Errorf("failed to verify cpu limit cgroup value: %w", err))
404401
}

test/e2e/framework/pod/resize.go

+5-11
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func VerifyPodContainersCgroupValues(ctx context.Context, f *framework.Framework
321321
tc := makeResizableContainer(ci)
322322
if tc.Resources.Limits != nil || tc.Resources.Requests != nil {
323323
var expectedCPUShares int64
324-
var expectedCPULimitString, expectedMemLimitString string
324+
var expectedMemLimitString string
325325
expectedMemLimitInBytes := tc.Resources.Limits.Memory().Value()
326326
cpuRequest := tc.Resources.Requests.Cpu()
327327
cpuLimit := tc.Resources.Limits.Cpu()
@@ -330,28 +330,22 @@ func VerifyPodContainersCgroupValues(ctx context.Context, f *framework.Framework
330330
} else {
331331
expectedCPUShares = int64(kubecm.MilliCPUToShares(cpuRequest.MilliValue()))
332332
}
333-
cpuQuota := kubecm.MilliCPUToQuota(cpuLimit.MilliValue(), kubecm.QuotaPeriod)
334-
if cpuLimit.IsZero() {
335-
cpuQuota = -1
336-
}
337-
expectedCPULimitString = strconv.FormatInt(cpuQuota, 10)
333+
334+
expectedCPULimits := GetCPULimitCgroupExpectations(cpuLimit)
338335
expectedMemLimitString = strconv.FormatInt(expectedMemLimitInBytes, 10)
339336
if *podOnCgroupv2Node {
340-
if expectedCPULimitString == "-1" {
341-
expectedCPULimitString = "max"
342-
}
343-
expectedCPULimitString = fmt.Sprintf("%s %s", expectedCPULimitString, CPUPeriod)
344337
if expectedMemLimitString == "0" {
345338
expectedMemLimitString = "max"
346339
}
347340
// convert cgroup v1 cpu.shares value to cgroup v2 cpu.weight value
348341
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2254-cgroup-v2#phase-1-convert-from-cgroups-v1-settings-to-v2
349342
expectedCPUShares = int64(1 + ((expectedCPUShares-2)*9999)/262142)
350343
}
344+
351345
if expectedMemLimitString != "0" {
352346
errs = append(errs, VerifyCgroupValue(f, pod, ci.Name, cgroupMemLimit, expectedMemLimitString))
353347
}
354-
errs = append(errs, VerifyCgroupValue(f, pod, ci.Name, cgroupCPULimit, expectedCPULimitString))
348+
errs = append(errs, VerifyCgroupValue(f, pod, ci.Name, cgroupCPULimit, expectedCPULimits...))
355349
errs = append(errs, VerifyCgroupValue(f, pod, ci.Name, cgroupCPURequest, strconv.FormatInt(expectedCPUShares, 10)))
356350
// TODO(vinaykul,InPlacePodVerticalScaling): Verify oom_score_adj when runc adds support for updating it
357351
// See https://github.com/opencontainers/runc/pull/4669

test/e2e/framework/pod/utils.go

+46-8
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ package pod
1818

1919
import (
2020
"fmt"
21+
"strconv"
2122
"strings"
2223

2324
"github.com/onsi/ginkgo/v2"
2425
"github.com/onsi/gomega"
2526

2627
v1 "k8s.io/api/core/v1"
28+
"k8s.io/apimachinery/pkg/api/resource"
29+
kubecm "k8s.io/kubernetes/pkg/kubelet/cm"
2730
"k8s.io/kubernetes/test/e2e/framework"
2831
imageutils "k8s.io/kubernetes/test/utils/image"
2932
psaapi "k8s.io/pod-security-admission/api"
@@ -292,20 +295,23 @@ func FindContainerStatusInPod(pod *v1.Pod, containerName string) *v1.ContainerSt
292295
}
293296

294297
// VerifyCgroupValue verifies that the given cgroup path has the expected value in
295-
// the specified container of the pod. It execs into the container to retrive the
296-
// cgroup value and compares it against the expected value.
297-
func VerifyCgroupValue(f *framework.Framework, pod *v1.Pod, cName, cgPath, expectedCgValue string) error {
298+
// the specified container of the pod. It execs into the container to retrieve the
299+
// cgroup value, and ensures that the retrieved cgroup value is equivalent to at
300+
// least one of the values in expectedCgValues.
301+
func VerifyCgroupValue(f *framework.Framework, pod *v1.Pod, cName, cgPath string, expectedCgValues ...string) error {
298302
cmd := fmt.Sprintf("head -n 1 %s", cgPath)
299-
framework.Logf("Namespace %s Pod %s Container %s - looking for cgroup value %s in path %s",
300-
pod.Namespace, pod.Name, cName, expectedCgValue, cgPath)
303+
framework.Logf("Namespace %s Pod %s Container %s - looking for one of the expected cgroup values %s in path %s",
304+
pod.Namespace, pod.Name, cName, expectedCgValues, cgPath)
301305
cgValue, _, err := ExecCommandInContainerWithFullOutput(f, pod.Name, cName, "/bin/sh", "-c", cmd)
302306
if err != nil {
303-
return fmt.Errorf("failed to find expected value %q in container cgroup %q", expectedCgValue, cgPath)
307+
return fmt.Errorf("failed to find one of the expected cgroup values %q in container cgroup %q", expectedCgValues, cgPath)
304308
}
305309
cgValue = strings.Trim(cgValue, "\n")
306-
if cgValue != expectedCgValue {
307-
return fmt.Errorf("cgroup value %q not equal to expected %q", cgValue, expectedCgValue)
310+
311+
if err := framework.Gomega().Expect(cgValue).To(gomega.BeElementOf(expectedCgValues)); err != nil {
312+
return fmt.Errorf("value of cgroup %q for container %q should match one of the expectations: %w", cgPath, cName, err)
308313
}
314+
309315
return nil
310316
}
311317

@@ -338,3 +344,35 @@ func IsPodOnCgroupv2Node(f *framework.Framework, pod *v1.Pod) bool {
338344
}
339345
return len(out) != 0
340346
}
347+
348+
// TODO: Remove the rounded cpu limit values when https://github.com/opencontainers/runc/issues/4622
349+
// is fixed.
350+
func GetCPULimitCgroupExpectations(cpuLimit *resource.Quantity) []string {
351+
var expectedCPULimits []string
352+
milliCPULimit := cpuLimit.MilliValue()
353+
354+
cpuQuota := kubecm.MilliCPUToQuota(milliCPULimit, kubecm.QuotaPeriod)
355+
if cpuLimit.IsZero() {
356+
cpuQuota = -1
357+
}
358+
expectedCPULimits = append(expectedCPULimits, getExpectedCPULimitFromCPUQuota(cpuQuota))
359+
360+
if milliCPULimit%10 != 0 && cpuQuota != -1 {
361+
roundedCPULimit := (milliCPULimit/10 + 1) * 10
362+
cpuQuotaRounded := kubecm.MilliCPUToQuota(roundedCPULimit, kubecm.QuotaPeriod)
363+
expectedCPULimits = append(expectedCPULimits, getExpectedCPULimitFromCPUQuota(cpuQuotaRounded))
364+
}
365+
366+
return expectedCPULimits
367+
}
368+
369+
func getExpectedCPULimitFromCPUQuota(cpuQuota int64) string {
370+
expectedCPULimitString := strconv.FormatInt(cpuQuota, 10)
371+
if *podOnCgroupv2Node {
372+
if expectedCPULimitString == "-1" {
373+
expectedCPULimitString = "max"
374+
}
375+
expectedCPULimitString = fmt.Sprintf("%s %s", expectedCPULimitString, CPUPeriod)
376+
}
377+
return expectedCPULimitString
378+
}
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//go:build linux
2+
// +build linux
3+
4+
/*
5+
Copyright 2025 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 pod
21+
22+
import (
23+
"testing"
24+
25+
"github.com/stretchr/testify/assert"
26+
"k8s.io/apimachinery/pkg/api/resource"
27+
)
28+
29+
func TestGetCPULimitCgroupExpectations(t *testing.T) {
30+
testCases := []struct {
31+
name string
32+
cpuLimit *resource.Quantity
33+
podOnCgroupv2Node bool
34+
expected []string
35+
}{
36+
{
37+
name: "rounding required, podOnCGroupv2Node=true",
38+
cpuLimit: resource.NewMilliQuantity(15, resource.DecimalSI),
39+
podOnCgroupv2Node: true,
40+
expected: []string{"1500 100000", "2000 100000"},
41+
},
42+
{
43+
name: "rounding not required, podOnCGroupv2Node=true",
44+
cpuLimit: resource.NewMilliQuantity(20, resource.DecimalSI),
45+
podOnCgroupv2Node: true,
46+
expected: []string{"2000 100000"},
47+
},
48+
{
49+
name: "rounding required, podOnCGroupv2Node=false",
50+
cpuLimit: resource.NewMilliQuantity(15, resource.DecimalSI),
51+
podOnCgroupv2Node: false,
52+
expected: []string{"1500", "2000"},
53+
},
54+
{
55+
name: "rounding not required, podOnCGroupv2Node=false",
56+
cpuLimit: resource.NewMilliQuantity(20, resource.DecimalSI),
57+
podOnCgroupv2Node: false,
58+
expected: []string{"2000"},
59+
},
60+
{
61+
name: "cpuQuota=0, podOnCGroupv2Node=true",
62+
cpuLimit: resource.NewMilliQuantity(0, resource.DecimalSI),
63+
podOnCgroupv2Node: true,
64+
expected: []string{"max 100000"},
65+
},
66+
{
67+
name: "cpuQuota=0, podOnCGroupv2Node=false",
68+
cpuLimit: resource.NewMilliQuantity(0, resource.DecimalSI),
69+
podOnCgroupv2Node: false,
70+
expected: []string{"-1"},
71+
},
72+
}
73+
74+
originalPodOnCgroupv2Node := podOnCgroupv2Node
75+
t.Cleanup(func() { podOnCgroupv2Node = originalPodOnCgroupv2Node })
76+
77+
for _, tc := range testCases {
78+
t.Run(tc.name, func(t *testing.T) {
79+
podOnCgroupv2Node = &tc.podOnCgroupv2Node
80+
actual := GetCPULimitCgroupExpectations(tc.cpuLimit)
81+
assert.Equal(t, tc.expected, actual)
82+
})
83+
}
84+
}

0 commit comments

Comments
 (0)