Account for node allocatable claims in PodRequests

This commit updates the PodRequests and AggregateContainerRequests functions to include resources from node allocatable claims when calculating the total resource requests for a pod.
This commit is contained in:
Praveen Krishna
2026-02-02 20:15:19 +00:00
parent ab5335d780
commit 0f815fd0e5
2 changed files with 404 additions and 0 deletions

View File

@@ -60,6 +60,8 @@ type PodResourcesOptions struct {
SkipPodLevelResources bool
// SkipContainerLevelResources
SkipContainerLevelResources bool
// Use node allocatable resource claim information from pod status to compute the effective pod resource request.
UseDRANodeAllocatableResourceClaimStatus bool
}
var supportedPodLevelResources = sets.New(v1.ResourceCPU, v1.ResourceMemory)
@@ -275,6 +277,16 @@ func AggregateContainerRequests(pod *v1.Pod, opts PodResourcesOptions) v1.Resour
}
maxResourceList(reqs, initContainerReqs)
// Add resources from node allocatable ResourceClaims
if opts.UseDRANodeAllocatableResourceClaimStatus && len(pod.Status.NodeAllocatableResourceClaimStatuses) > 0 {
for _, claimStatus := range pod.Status.NodeAllocatableResourceClaimStatuses {
for resName, resQty := range claimStatus.Resources {
addResourceList(reqs, v1.ResourceList{resName: resQty})
}
}
}
return reqs
}

View File

@@ -2017,6 +2017,398 @@ func TestIsSupportedPodLevelResource(t *testing.T) {
}
}
var hugePageResource1Gi = v1.ResourceName(v1.ResourceHugePagesPrefix + "1Gi")
func TestPodResourceRequestsWithDRANodeAllocatableClaims(t *testing.T) {
testCases := []struct {
description string
pod *v1.Pod
options PodResourcesOptions
expectedRequests v1.ResourceList
}{
{
description: "No claims",
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("100m"),
v1.ResourceMemory: resource.MustParse("1Gi"),
},
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: true},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("100m"),
v1.ResourceMemory: resource.MustParse("1Gi"),
},
},
{
description: "Single claim on one container",
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("100m"),
v1.ResourceMemory: resource.MustParse("1Gi"),
hugePageResource1Gi: resource.MustParse("2"),
},
Claims: []v1.ResourceClaim{
{Name: "claim-1"},
},
},
},
},
ResourceClaims: []v1.PodResourceClaim{
{
Name: "claim-1",
ResourceClaimName: ptr.To("node-allocatable-claim-1"),
},
},
},
Status: v1.PodStatus{
NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{
{
ResourceClaimName: "node-allocatable-claim-1",
Containers: []string{"c1"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("50m"),
hugePageResource1Gi: resource.MustParse("1"),
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: true},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("150m"), // 100m + 50m
v1.ResourceMemory: resource.MustParse("1Gi"),
hugePageResource1Gi: resource.MustParse("3"), // 2 + 1
},
},
{
description: "Multiple claims on one container",
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("100m"),
v1.ResourceMemory: resource.MustParse("1Gi"),
},
Claims: []v1.ResourceClaim{
{Name: "claim-1"},
{Name: "claim-2"},
},
},
},
},
ResourceClaims: []v1.PodResourceClaim{
{
Name: "claim-1",
ResourceClaimName: ptr.To("node-allocatable-claim-1"),
},
{
Name: "claim-2",
ResourceClaimName: ptr.To("node-allocatable-claim-2"),
},
},
},
Status: v1.PodStatus{
NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{
{
ResourceClaimName: "node-allocatable-claim-1",
Containers: []string{"c1"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("50m"),
},
},
{
ResourceClaimName: "node-allocatable-claim-2",
Containers: []string{"c1"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("25m"),
v1.ResourceMemory: resource.MustParse("512Mi"),
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: true},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("175m"), // 100m + 50m + 25m
v1.ResourceMemory: resource.MustParse("1.5Gi"), // 1Gi + 512Mi
},
},
{
description: "Same claim referenced in multiple containers",
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("100m")},
Claims: []v1.ResourceClaim{
{Name: "claim-1"},
},
},
},
{
Name: "c2",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("200m")},
Claims: []v1.ResourceClaim{
{Name: "claim-1"},
},
},
},
},
ResourceClaims: []v1.PodResourceClaim{
{
Name: "claim-1",
ResourceClaimName: ptr.To("node-allocatable-claim-1"),
},
},
},
Status: v1.PodStatus{
NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{
{
ResourceClaimName: "node-allocatable-claim-1",
Containers: []string{"c1", "c2"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("50m"),
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: true},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("350m"), // 100m + 200m + 50m (claim counted once)
},
},
{
description: "Different claims on multiple containers",
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("100m"),
hugePageResource1Gi: resource.MustParse("1"),
},
Claims: []v1.ResourceClaim{
{Name: "claim-1"},
},
},
},
{
Name: "c2",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("200m"),
v1.ResourceMemory: resource.MustParse("1Gi"),
hugePageResource1Gi: resource.MustParse("2"),
},
Claims: []v1.ResourceClaim{
{Name: "claim-2"},
},
},
},
},
ResourceClaims: []v1.PodResourceClaim{
{
Name: "claim-1",
ResourceClaimName: ptr.To("node-allocatable-claim-1"),
},
{
Name: "claim-2",
ResourceClaimName: ptr.To("node-allocatable-claim-2"),
},
},
},
Status: v1.PodStatus{
NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{
{
ResourceClaimName: "node-allocatable-claim-1",
Containers: []string{"c1"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("50m"),
hugePageResource1Gi: resource.MustParse("1"),
},
},
{
ResourceClaimName: "node-allocatable-claim-2",
Containers: []string{"c2"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("25m"),
v1.ResourceMemory: resource.MustParse("512Mi"),
hugePageResource1Gi: resource.MustParse("2"),
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: true},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("375m"), // 100m + 200m + 50m + 25m
v1.ResourceMemory: resource.MustParse("1.5Gi"), // 1Gi + 1Gi + 1Gi + 512Mi
hugePageResource1Gi: resource.MustParse("6"), // 1 + 2 + 1 + 2
},
},
{
description: "Claim on init container",
pod: &v1.Pod{
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Name: "ic1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("100m")},
Claims: []v1.ResourceClaim{
{Name: "claim-1"},
},
},
},
},
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{v1.ResourceCPU: resource.MustParse("50m")},
},
},
},
ResourceClaims: []v1.PodResourceClaim{
{
Name: "claim-1",
ResourceClaimName: ptr.To("node-allocatable-claim"),
},
},
},
Status: v1.PodStatus{
NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{
{
ResourceClaimName: "node-allocatable-claim",
Containers: []string{"ic1"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("25m"),
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: true},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("125m"), // max(100m, 50m) + 25m
},
},
{
description: "UseDRANodeAllocatableResourceClaimStatus flag disabled",
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("100m"),
},
Claims: []v1.ResourceClaim{
{Name: "claim-1"},
},
},
},
},
ResourceClaims: []v1.PodResourceClaim{
{
Name: "claim-1",
ResourceClaimName: ptr.To("node-allocatable-claim"),
},
},
},
Status: v1.PodStatus{
NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{
{
ResourceClaimName: "node-allocatable-claim",
Containers: []string{"c1"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("50m"),
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: false},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("100m"),
},
},
{
description: "Pod Level Resources overrides the claim request",
pod: &v1.Pod{
Spec: v1.PodSpec{
Resources: &v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("2"),
},
},
Containers: []v1.Container{
{
Name: "c1",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("1"),
v1.ResourceMemory: resource.MustParse("1Gi"),
},
},
},
},
},
Status: v1.PodStatus{
NodeAllocatableResourceClaimStatuses: []v1.NodeAllocatableResourceClaimStatus{
{
ResourceClaimName: "node-allocatable-claim",
Containers: []string{"c1"},
Resources: map[v1.ResourceName]resource.Quantity{
v1.ResourceCPU: resource.MustParse("3"),
},
},
},
},
},
options: PodResourcesOptions{UseDRANodeAllocatableResourceClaimStatus: true, SkipPodLevelResources: false},
expectedRequests: v1.ResourceList{
v1.ResourceCPU: resource.MustParse("2"), // Pod level resource should not be overridden by claim request (3)
v1.ResourceMemory: resource.MustParse("1Gi"), // Container level resource should be included
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
request := PodRequests(tc.pod, tc.options)
if diff := diff.Diff(tc.expectedRequests, request); diff != "" {
t.Errorf("PodRequests() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestAggregateContainerRequestsAndLimits(t *testing.T) {
restartAlways := v1.ContainerRestartPolicyAlways
cases := []struct {