test: Add extended resource handling test coverage

Add unit test cases for extended resource handling in kubelet lifecycle
predicates and scheduler admission checks for resources backed up by
DRA and Device plugins.
This commit is contained in:
Ed Bartosh
2025-12-11 21:07:04 +02:00
parent c2361491f5
commit 882cd02253
2 changed files with 133 additions and 26 deletions

View File

@@ -29,6 +29,7 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/types"
schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework"
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity"
@@ -39,49 +40,51 @@ import (
)
var (
quantity = *resource.NewQuantity(1, resource.DecimalSI)
quantity = *resource.NewQuantity(1, resource.DecimalSI)
extendedResource = v1.ResourceName("foo.com/bar")
)
func TestRemoveMissingExtendedResources(t *testing.T) {
for _, test := range []struct {
desc string
pod *v1.Pod
node *v1.Node
desc string
pod *v1.Pod
node *v1.Node
enableDRAExtendedResource bool
expectedPod *v1.Pod
}{
{
desc: "requests in Limits should be ignored",
pod: makeTestPod(
v1.ResourceList{}, // Requests
v1.ResourceList{"foo.com/bar": quantity}, // Limits
v1.ResourceList{}, // Requests
v1.ResourceList{extendedResource: quantity}, // Limits
),
node: makeTestNode(
v1.ResourceList{"foo.com/baz": quantity}, // Allocatable
),
expectedPod: makeTestPod(
v1.ResourceList{}, // Requests
v1.ResourceList{"foo.com/bar": quantity}, // Limits
v1.ResourceList{}, // Requests
v1.ResourceList{extendedResource: quantity}, // Limits
),
},
{
desc: "requests for resources available in node should not be removed",
pod: makeTestPod(
v1.ResourceList{"foo.com/bar": quantity}, // Requests
v1.ResourceList{}, // Limits
v1.ResourceList{extendedResource: quantity}, // Requests
v1.ResourceList{}, // Limits
),
node: makeTestNode(
v1.ResourceList{"foo.com/bar": quantity}, // Allocatable
v1.ResourceList{extendedResource: quantity}, // Allocatable
),
expectedPod: makeTestPod(
v1.ResourceList{"foo.com/bar": quantity}, // Requests
v1.ResourceList{}), // Limits
v1.ResourceList{extendedResource: quantity}, // Requests
v1.ResourceList{}), // Limits
},
{
desc: "requests for resources unavailable in node should be removed",
pod: makeTestPod(
v1.ResourceList{"foo.com/bar": quantity}, // Requests
v1.ResourceList{}, // Limits
v1.ResourceList{extendedResource: quantity}, // Requests
v1.ResourceList{}, // Limits
),
node: makeTestNode(
v1.ResourceList{"foo.com/baz": quantity}, // Allocatable
@@ -91,13 +94,62 @@ func TestRemoveMissingExtendedResources(t *testing.T) {
v1.ResourceList{}, // Limits
),
},
{
desc: "requests for extended resources with Allocatable 0 should not be removed",
pod: makeTestPod(
v1.ResourceList{extendedResource: quantity}, // Requests
v1.ResourceList{extendedResource: quantity}, // Limits
),
node: makeTestNode(
v1.ResourceList{extendedResource: *resource.NewQuantity(0, resource.DecimalSI)}, // Allocatable = 0
),
expectedPod: makeTestPod(
v1.ResourceList{extendedResource: quantity}, // Requests
v1.ResourceList{extendedResource: quantity}, // Limits
),
},
{
desc: "requests for extended resources that are not available in node should be removed",
pod: makeTestPod(
v1.ResourceList{extendedResource: quantity}, // Requests
v1.ResourceList{extendedResource: quantity}, // Limits
),
node: makeTestNode(
v1.ResourceList{}, // Allocatable is absent for foo.com/bar
),
expectedPod: makeTestPod(
v1.ResourceList{}, // Requests are filtered
v1.ResourceList{extendedResource: quantity}, // Limits
),
},
{
desc: "request for extended resource backed by DRA should be removed if Allocatable is 0",
pod: makeTestPodWithDRAResource(extendedResource, quantity),
node: makeTestNode(
v1.ResourceList{extendedResource: *resource.NewQuantity(0, resource.DecimalSI)}, // Allocatable = 0
),
enableDRAExtendedResource: true,
expectedPod: emptyRequests(makeTestPodWithDRAResource(extendedResource, quantity)),
},
{
desc: "request for extended resource backed by DRA should be removed if Allocatable is absent",
pod: makeTestPodWithDRAResource(extendedResource, quantity),
node: makeTestNode(
v1.ResourceList{}, // Allocatable is absent for foo.com/bar
),
enableDRAExtendedResource: true,
expectedPod: emptyRequests(makeTestPodWithDRAResource(extendedResource, quantity)),
},
} {
nodeInfo := schedulerframework.NewNodeInfo()
nodeInfo.SetNode(test.node)
pod := removeMissingExtendedResources(test.pod, nodeInfo)
if diff := cmp.Diff(test.expectedPod, pod); diff != "" {
t.Errorf("unexpected pod (-want, +got):\n%s", diff)
}
t.Run(test.desc, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.enableDRAExtendedResource)
nodeInfo := schedulerframework.NewNodeInfo()
nodeInfo.SetNode(test.node)
pod := removeMissingExtendedResources(test.pod, nodeInfo)
if diff := cmp.Diff(test.expectedPod, pod); diff != "" {
t.Errorf("unexpected pod (-want, +got):\n%s", diff)
}
})
}
}
@@ -124,6 +176,44 @@ func makeTestPod(requests, limits v1.ResourceList) *v1.Pod {
}
}
func makeTestPodWithDRAResource(resourceName v1.ResourceName, quantity resource.Quantity) *v1.Pod {
pod := &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "test-container",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{resourceName: quantity},
Limits: v1.ResourceList{resourceName: quantity},
},
},
},
},
}
pod.Status = v1.PodStatus{
ExtendedResourceClaimStatus: &v1.PodExtendedResourceClaimStatus{
RequestMappings: []v1.ContainerExtendedResourceRequest{
{
ResourceName: string(resourceName),
ContainerName: pod.Spec.Containers[0].Name,
},
},
},
}
return pod
}
func emptyRequests(pod *v1.Pod) *v1.Pod {
newPod := pod.DeepCopy()
for i := range newPod.Spec.Containers {
newPod.Spec.Containers[i].Resources.Requests = v1.ResourceList{}
}
for i := range newPod.Spec.InitContainers {
newPod.Spec.InitContainers[i].Resources.Requests = v1.ResourceList{}
}
return newPod
}
func makeTestNode(allocatable v1.ResourceList) *v1.Node {
return &v1.Node{
Status: v1.NodeStatus{

View File

@@ -697,13 +697,16 @@ func TestAdmissionCheck(t *testing.T) {
nodenameError := AdmissionResult{Name: nodename.Name, Reason: nodename.ErrReason}
nodeportsError := AdmissionResult{Name: nodeports.Name, Reason: nodeports.ErrReason}
podOverheadError := AdmissionResult{InsufficientResource: &noderesources.InsufficientResource{ResourceName: v1.ResourceCPU, Reason: "Insufficient cpu", Requested: 2000, Used: 7000, Capacity: 8000}}
extendedResourceError := AdmissionResult{InsufficientResource: &noderesources.InsufficientResource{ResourceName: "foo.com/bar", Reason: "Insufficient foo.com/bar", Requested: 1, Unresolvable: true}}
cpu := map[v1.ResourceName]string{v1.ResourceCPU: "8"}
extendedResource := map[v1.ResourceName]string{"foo.com/bar": "1"}
tests := []struct {
name string
node *v1.Node
existingPods []*v1.Pod
pod *v1.Pod
wantAdmissionResults [][]AdmissionResult
name string
node *v1.Node
existingPods []*v1.Pod
pod *v1.Pod
wantAdmissionResults [][]AdmissionResult
enableDRAExtendedResource bool
}{
{
name: "check nodeAffinity and nodeports, nodeAffinity need fail quickly if includeAllFailures is false",
@@ -732,9 +735,23 @@ func TestAdmissionCheck(t *testing.T) {
},
wantAdmissionResults: [][]AdmissionResult{{nodenameError, nodeportsError}, {nodenameError}},
},
{
name: "check extended resource handling when node Allocatable doesn't have the resource",
node: st.MakeNode().Name("fake-node").Obj(),
pod: st.MakePod().Name("pod1").Req(extendedResource).Obj(),
wantAdmissionResults: [][]AdmissionResult{{extendedResourceError}, {extendedResourceError}},
},
{
name: "check extended resource handling when node Allocatable doesn't have the resource and DRAExtendedResource is enabled",
node: st.MakeNode().Name("fake-node").Obj(),
pod: st.MakePod().Name("pod1").Req(extendedResource).Obj(),
wantAdmissionResults: [][]AdmissionResult{{extendedResourceError}, {extendedResourceError}},
enableDRAExtendedResource: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tt.enableDRAExtendedResource)
nodeInfo := framework.NewNodeInfo(tt.existingPods...)
nodeInfo.SetNode(tt.node)