|
|
|
@@ -0,0 +1,408 @@
|
|
|
|
|
/*
|
|
|
|
|
Copyright 2024 The Kubernetes Authors.
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package node
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/onsi/ginkgo/v2"
|
|
|
|
|
"github.com/onsi/gomega"
|
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
|
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
|
|
|
|
kubecm "k8s.io/kubernetes/pkg/kubelet/cm"
|
|
|
|
|
"k8s.io/kubernetes/test/e2e/feature"
|
|
|
|
|
"k8s.io/kubernetes/test/e2e/framework"
|
|
|
|
|
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
|
|
|
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
|
|
|
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
|
|
|
imageutils "k8s.io/kubernetes/test/utils/image"
|
|
|
|
|
admissionapi "k8s.io/pod-security-admission/api"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
cgroupv2CPUWeight string = "cpu.weight"
|
|
|
|
|
cgroupv2CPULimit string = "cpu.max"
|
|
|
|
|
cgroupv2MemLimit string = "memory.max"
|
|
|
|
|
cgroupFsPath string = "/sys/fs/cgroup"
|
|
|
|
|
CPUPeriod string = "100000"
|
|
|
|
|
mountPath string = "/sysfscgroup"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
cmd = []string{"/bin/sh", "-c", "sleep 1d"}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var _ = SIGDescribe("Pod Level Resources", framework.WithSerial(), feature.PodLevelResources, "[NodeAlphaFeature:PodLevelResources]", func() {
|
|
|
|
|
f := framework.NewDefaultFramework("pod-level-resources-tests")
|
|
|
|
|
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
|
|
|
|
|
|
|
|
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
|
|
|
_, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
|
|
|
|
|
framework.ExpectNoError(err)
|
|
|
|
|
|
|
|
|
|
if framework.NodeOSDistroIs("windows") {
|
|
|
|
|
e2eskipper.Skipf("not supported on windows -- skipping")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// skip the test on nodes with cgroupv2 not enabled.
|
|
|
|
|
if !isCgroupv2Node(f, ctx) {
|
|
|
|
|
e2eskipper.Skipf("not supported on cgroupv1 -- skipping")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
podLevelResourcesTests(f)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// isCgroupv2Node creates a small pod and check if it is running on a node
|
|
|
|
|
// with cgroupv2 enabled.
|
|
|
|
|
// TODO: refactor to mark this test with cgroupv2 label, and rather check
|
|
|
|
|
// the label in the test job, to tun this test on a node with cgroupv2.
|
|
|
|
|
func isCgroupv2Node(f *framework.Framework, ctx context.Context) bool {
|
|
|
|
|
podClient := e2epod.NewPodClient(f)
|
|
|
|
|
cgroupv2Testpod := &v1.Pod{
|
|
|
|
|
ObjectMeta: makeObjectMetadata("cgroupv2-check", f.Namespace.Name),
|
|
|
|
|
Spec: v1.PodSpec{
|
|
|
|
|
Containers: []v1.Container{
|
|
|
|
|
{
|
|
|
|
|
Name: "cgroupv2-check",
|
|
|
|
|
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
|
|
|
|
Command: cmd,
|
|
|
|
|
Resources: getResourceRequirements(&resourceInfo{CPULim: "1m", MemReq: "1Mi"}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pod := podClient.CreateSync(ctx, cgroupv2Testpod)
|
|
|
|
|
defer func() {
|
|
|
|
|
framework.Logf("Deleting %q pod", cgroupv2Testpod.Name)
|
|
|
|
|
delErr := e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
|
|
|
|
|
framework.ExpectNoError(delErr, "failed to delete pod %s", delErr)
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
return e2epod.IsPodOnCgroupv2Node(f, pod)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func makeObjectMetadata(name, namespace string) metav1.ObjectMeta {
|
|
|
|
|
return metav1.ObjectMeta{
|
|
|
|
|
Name: "testpod", Namespace: namespace,
|
|
|
|
|
Labels: map[string]string{"time": strconv.Itoa(time.Now().Nanosecond())},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type containerInfo struct {
|
|
|
|
|
Name string
|
|
|
|
|
Resources *resourceInfo
|
|
|
|
|
}
|
|
|
|
|
type resourceInfo struct {
|
|
|
|
|
CPUReq string
|
|
|
|
|
CPULim string
|
|
|
|
|
MemReq string
|
|
|
|
|
MemLim string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func makeContainer(info containerInfo) v1.Container {
|
|
|
|
|
cmd := []string{"/bin/sh", "-c", "sleep 1d"}
|
|
|
|
|
res := getResourceRequirements(info.Resources)
|
|
|
|
|
return v1.Container{
|
|
|
|
|
Name: info.Name,
|
|
|
|
|
Command: cmd,
|
|
|
|
|
Resources: res,
|
|
|
|
|
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
|
|
|
|
VolumeMounts: []v1.VolumeMount{
|
|
|
|
|
{
|
|
|
|
|
Name: "sysfscgroup",
|
|
|
|
|
MountPath: mountPath,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getResourceRequirements(info *resourceInfo) v1.ResourceRequirements {
|
|
|
|
|
var res v1.ResourceRequirements
|
|
|
|
|
if info != nil {
|
|
|
|
|
if info.CPUReq != "" || info.MemReq != "" {
|
|
|
|
|
res.Requests = make(v1.ResourceList)
|
|
|
|
|
}
|
|
|
|
|
if info.CPUReq != "" {
|
|
|
|
|
res.Requests[v1.ResourceCPU] = resource.MustParse(info.CPUReq)
|
|
|
|
|
}
|
|
|
|
|
if info.MemReq != "" {
|
|
|
|
|
res.Requests[v1.ResourceMemory] = resource.MustParse(info.MemReq)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.CPULim != "" || info.MemLim != "" {
|
|
|
|
|
res.Limits = make(v1.ResourceList)
|
|
|
|
|
}
|
|
|
|
|
if info.CPULim != "" {
|
|
|
|
|
res.Limits[v1.ResourceCPU] = resource.MustParse(info.CPULim)
|
|
|
|
|
}
|
|
|
|
|
if info.MemLim != "" {
|
|
|
|
|
res.Limits[v1.ResourceMemory] = resource.MustParse(info.MemLim)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func makePod(metadata *metav1.ObjectMeta, podResources *resourceInfo, containers []containerInfo) *v1.Pod {
|
|
|
|
|
var testContainers []v1.Container
|
|
|
|
|
for _, container := range containers {
|
|
|
|
|
testContainers = append(testContainers, makeContainer(container))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pod := &v1.Pod{
|
|
|
|
|
ObjectMeta: *metadata,
|
|
|
|
|
|
|
|
|
|
Spec: v1.PodSpec{
|
|
|
|
|
Containers: testContainers,
|
|
|
|
|
Volumes: []v1.Volume{
|
|
|
|
|
{
|
|
|
|
|
Name: "sysfscgroup",
|
|
|
|
|
VolumeSource: v1.VolumeSource{
|
|
|
|
|
HostPath: &v1.HostPathVolumeSource{Path: cgroupFsPath},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if podResources != nil {
|
|
|
|
|
res := getResourceRequirements(podResources)
|
|
|
|
|
pod.Spec.Resources = &res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pod
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyPodResources(gotPod v1.Pod, inputInfo, expectedInfo *resourceInfo) {
|
|
|
|
|
ginkgo.GinkgoHelper()
|
|
|
|
|
var expectedResources *v1.ResourceRequirements
|
|
|
|
|
// expectedResources will be nil if pod-level resources are not set in the test
|
|
|
|
|
// case input.
|
|
|
|
|
if inputInfo != nil {
|
|
|
|
|
resourceInfo := getResourceRequirements(expectedInfo)
|
|
|
|
|
expectedResources = &resourceInfo
|
|
|
|
|
}
|
|
|
|
|
gomega.Expect(expectedResources).To(gomega.Equal(gotPod.Spec.Resources))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyQoS(gotPod v1.Pod, expectedQoS v1.PodQOSClass) {
|
|
|
|
|
ginkgo.GinkgoHelper()
|
|
|
|
|
gomega.Expect(expectedQoS).To(gomega.Equal(gotPod.Status.QOSClass))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(ndixita): dedup the conversion logic in pod resize test and move to helpers/utils.
|
|
|
|
|
func verifyPodCgroups(ctx context.Context, f *framework.Framework, pod *v1.Pod, info *resourceInfo) error {
|
|
|
|
|
ginkgo.GinkgoHelper()
|
|
|
|
|
cmd := fmt.Sprintf("find %s -name '*%s*'", mountPath, strings.ReplaceAll(string(pod.UID), "-", "_"))
|
|
|
|
|
framework.Logf("Namespace %s Pod %s - looking for Pod cgroup directory path: %q", f.Namespace, pod.Name, cmd)
|
|
|
|
|
podCgPath, stderr, err := e2epod.ExecCommandInContainerWithFullOutput(f, pod.Name, pod.Spec.Containers[0].Name, []string{"/bin/sh", "-c", cmd}...)
|
|
|
|
|
if err != nil || len(stderr) > 0 {
|
|
|
|
|
return fmt.Errorf("encountered error while running command: %q, \nerr: %w \nstdErr: %q", cmd, err, stderr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expectedResources := getResourceRequirements(info)
|
|
|
|
|
cpuWeightCgPath := fmt.Sprintf("%s/%s", podCgPath, cgroupv2CPUWeight)
|
|
|
|
|
expectedCPUShares := int64(kubecm.MilliCPUToShares(expectedResources.Requests.Cpu().MilliValue()))
|
|
|
|
|
expectedCPUShares = int64(1 + ((expectedCPUShares-2)*9999)/262142)
|
|
|
|
|
// convert cgroup v1 cpu.shares value to cgroup v2 cpu.weight value
|
|
|
|
|
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2254-cgroup-v2#phase-1-convert-from-cgroups-v1-settings-to-v2
|
|
|
|
|
var errs []error
|
|
|
|
|
err = e2epod.VerifyCgroupValue(f, pod, pod.Spec.Containers[0].Name, cpuWeightCgPath, strconv.FormatInt(expectedCPUShares, 10))
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, fmt.Errorf("failed to verify cpu request cgroup value: %w", err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cpuLimCgPath := fmt.Sprintf("%s/%s", podCgPath, cgroupv2CPULimit)
|
|
|
|
|
cpuQuota := kubecm.MilliCPUToQuota(expectedResources.Limits.Cpu().MilliValue(), kubecm.QuotaPeriod)
|
|
|
|
|
expectedCPULimit := strconv.FormatInt(cpuQuota, 10)
|
|
|
|
|
expectedCPULimit = fmt.Sprintf("%s %s", expectedCPULimit, CPUPeriod)
|
|
|
|
|
err = e2epod.VerifyCgroupValue(f, pod, pod.Spec.Containers[0].Name, cpuLimCgPath, expectedCPULimit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, fmt.Errorf("failed to verify cpu limit cgroup value: %w", err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memLimCgPath := fmt.Sprintf("%s/%s", podCgPath, cgroupv2MemLimit)
|
|
|
|
|
expectedMemLim := strconv.FormatInt(expectedResources.Limits.Memory().Value(), 10)
|
|
|
|
|
err = e2epod.VerifyCgroupValue(f, pod, pod.Spec.Containers[0].Name, memLimCgPath, expectedMemLim)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, fmt.Errorf("failed to verify memory limit cgroup value: %w", err))
|
|
|
|
|
}
|
|
|
|
|
return utilerrors.NewAggregate(errs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func podLevelResourcesTests(f *framework.Framework) {
|
|
|
|
|
type expectedPodConfig struct {
|
|
|
|
|
qos v1.PodQOSClass
|
|
|
|
|
// totalPodResources represents the aggregate resource requests
|
|
|
|
|
// and limits for the pod. If pod-level resource specifications
|
|
|
|
|
// are specified, totalPodResources is equal to pod-level resources.
|
|
|
|
|
// Otherwise, it is calculated by aggregating resource requests and
|
|
|
|
|
// limits from all containers within the pod..
|
|
|
|
|
totalPodResources *resourceInfo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type testCase struct {
|
|
|
|
|
name string
|
|
|
|
|
podResources *resourceInfo
|
|
|
|
|
containers []containerInfo
|
|
|
|
|
expected expectedPodConfig
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := []testCase{
|
|
|
|
|
{
|
|
|
|
|
name: "Guaranteed QoS pod with container resources",
|
|
|
|
|
containers: []containerInfo{
|
|
|
|
|
{Name: "c1", Resources: &resourceInfo{CPUReq: "50m", CPULim: "50m", MemReq: "70Mi", MemLim: "70Mi"}},
|
|
|
|
|
{Name: "c2", Resources: &resourceInfo{CPUReq: "70m", CPULim: "70m", MemReq: "50Mi", MemLim: "50Mi"}},
|
|
|
|
|
},
|
|
|
|
|
expected: expectedPodConfig{
|
|
|
|
|
qos: v1.PodQOSGuaranteed,
|
|
|
|
|
totalPodResources: &resourceInfo{CPUReq: "120m", CPULim: "120m", MemReq: "120Mi", MemLim: "120Mi"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Guaranteed QoS pod, no container resources",
|
|
|
|
|
podResources: &resourceInfo{CPUReq: "100m", CPULim: "100m", MemReq: "100Mi", MemLim: "100Mi"},
|
|
|
|
|
containers: []containerInfo{{Name: "c1"}, {Name: "c2"}},
|
|
|
|
|
expected: expectedPodConfig{
|
|
|
|
|
qos: v1.PodQOSGuaranteed,
|
|
|
|
|
totalPodResources: &resourceInfo{CPUReq: "100m", CPULim: "100m", MemReq: "100Mi", MemLim: "100Mi"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Guaranteed QoS pod with container resources",
|
|
|
|
|
podResources: &resourceInfo{CPUReq: "100m", CPULim: "100m", MemReq: "100Mi", MemLim: "100Mi"},
|
|
|
|
|
containers: []containerInfo{
|
|
|
|
|
{Name: "c1", Resources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"}},
|
|
|
|
|
{Name: "c2", Resources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"}},
|
|
|
|
|
},
|
|
|
|
|
expected: expectedPodConfig{
|
|
|
|
|
qos: v1.PodQOSGuaranteed,
|
|
|
|
|
totalPodResources: &resourceInfo{CPUReq: "100m", CPULim: "100m", MemReq: "100Mi", MemLim: "100Mi"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Guaranteed QoS pod, 1 container with resources",
|
|
|
|
|
podResources: &resourceInfo{CPUReq: "100m", CPULim: "100m", MemReq: "100Mi", MemLim: "100Mi"},
|
|
|
|
|
containers: []containerInfo{
|
|
|
|
|
{Name: "c1", Resources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"}},
|
|
|
|
|
{Name: "c2"},
|
|
|
|
|
},
|
|
|
|
|
expected: expectedPodConfig{
|
|
|
|
|
qos: v1.PodQOSGuaranteed,
|
|
|
|
|
totalPodResources: &resourceInfo{CPUReq: "100m", CPULim: "100m", MemReq: "100Mi", MemLim: "100Mi"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Burstable QoS pod, no container resources",
|
|
|
|
|
podResources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"},
|
|
|
|
|
containers: []containerInfo{
|
|
|
|
|
{Name: "c1"},
|
|
|
|
|
{Name: "c2"},
|
|
|
|
|
},
|
|
|
|
|
expected: expectedPodConfig{
|
|
|
|
|
qos: v1.PodQOSBurstable,
|
|
|
|
|
totalPodResources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Burstable QoS pod with container resources",
|
|
|
|
|
podResources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"},
|
|
|
|
|
containers: []containerInfo{
|
|
|
|
|
{Name: "c1", Resources: &resourceInfo{CPUReq: "20m", CPULim: "100m", MemReq: "20Mi", MemLim: "100Mi"}},
|
|
|
|
|
{Name: "c2", Resources: &resourceInfo{CPUReq: "30m", CPULim: "100m", MemReq: "30Mi", MemLim: "100Mi"}},
|
|
|
|
|
},
|
|
|
|
|
expected: expectedPodConfig{
|
|
|
|
|
qos: v1.PodQOSBurstable,
|
|
|
|
|
totalPodResources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Burstable QoS pod, 1 container with resources",
|
|
|
|
|
podResources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"},
|
|
|
|
|
containers: []containerInfo{
|
|
|
|
|
{Name: "c1", Resources: &resourceInfo{CPUReq: "20m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"}},
|
|
|
|
|
{Name: "c2"},
|
|
|
|
|
},
|
|
|
|
|
expected: expectedPodConfig{
|
|
|
|
|
qos: v1.PodQOSBurstable,
|
|
|
|
|
totalPodResources: &resourceInfo{CPUReq: "50m", CPULim: "100m", MemReq: "50Mi", MemLim: "100Mi"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
|
ginkgo.It(tc.name, func(ctx context.Context) {
|
|
|
|
|
podMetadata := makeObjectMetadata("testpod", f.Namespace.Name)
|
|
|
|
|
testPod := makePod(&podMetadata, tc.podResources, tc.containers)
|
|
|
|
|
|
|
|
|
|
ginkgo.By("creating pods")
|
|
|
|
|
podClient := e2epod.NewPodClient(f)
|
|
|
|
|
pod := podClient.CreateSync(ctx, testPod)
|
|
|
|
|
|
|
|
|
|
ginkgo.By("verifying pod resources are as expected")
|
|
|
|
|
verifyPodResources(*pod, tc.podResources, tc.expected.totalPodResources)
|
|
|
|
|
|
|
|
|
|
ginkgo.By("verifying pod QoS as expected")
|
|
|
|
|
verifyQoS(*pod, tc.expected.qos)
|
|
|
|
|
|
|
|
|
|
ginkgo.By("verifying pod cgroup values")
|
|
|
|
|
err := verifyPodCgroups(ctx, f, pod, tc.expected.totalPodResources)
|
|
|
|
|
framework.ExpectNoError(err, "failed to verify pod's cgroup values: %v", err)
|
|
|
|
|
|
|
|
|
|
ginkgo.By("verifying containers cgroup limits are same as pod container's cgroup limits")
|
|
|
|
|
err = verifyContainersCgroupLimits(f, pod)
|
|
|
|
|
framework.ExpectNoError(err, "failed to verify containers cgroup values: %v", err)
|
|
|
|
|
|
|
|
|
|
ginkgo.By("deleting pods")
|
|
|
|
|
delErr := e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
|
|
|
|
|
framework.ExpectNoError(delErr, "failed to delete pod %s", delErr)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func verifyContainersCgroupLimits(f *framework.Framework, pod *v1.Pod) error {
|
|
|
|
|
var errs []error
|
|
|
|
|
for _, container := range pod.Spec.Containers {
|
|
|
|
|
if pod.Spec.Resources != nil && pod.Spec.Resources.Limits.Memory() != nil &&
|
|
|
|
|
container.Resources.Limits.Memory() == nil {
|
|
|
|
|
expectedCgroupMemLimit := strconv.FormatInt(pod.Spec.Resources.Limits.Memory().Value(), 10)
|
|
|
|
|
err := e2epod.VerifyCgroupValue(f, pod, container.Name, fmt.Sprintf("%s/%s", cgroupFsPath, cgroupv2MemLimit), expectedCgroupMemLimit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, fmt.Errorf("failed to verify memory limit cgroup value: %w", err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if pod.Spec.Resources != nil && pod.Spec.Resources.Limits.Cpu() != nil &&
|
|
|
|
|
container.Resources.Limits.Cpu() == nil {
|
|
|
|
|
cpuQuota := kubecm.MilliCPUToQuota(pod.Spec.Resources.Limits.Cpu().MilliValue(), kubecm.QuotaPeriod)
|
|
|
|
|
expectedCPULimit := strconv.FormatInt(cpuQuota, 10)
|
|
|
|
|
expectedCPULimit = fmt.Sprintf("%s %s", expectedCPULimit, CPUPeriod)
|
|
|
|
|
err := e2epod.VerifyCgroupValue(f, pod, container.Name, fmt.Sprintf("%s/%s", cgroupFsPath, cgroupv2CPULimit), expectedCPULimit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, fmt.Errorf("failed to verify cpu limit cgroup value: %w", err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return utilerrors.NewAggregate(errs)
|
|
|
|
|
}
|