use original requests in NodeResourcesBalancedAllocation instead of NonZero (#105845)

This commit is contained in:
Ahmad Diaa 2021-10-30 03:04:14 +01:00 committed by GitHub
parent 324eff5b75
commit a2c37bfd09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 36 deletions

View File

@ -89,6 +89,7 @@ func NewBalancedAllocation(baArgs runtime.Object, h framework.Handle, fts featur
resourceAllocationScorer: resourceAllocationScorer{ resourceAllocationScorer: resourceAllocationScorer{
Name: BalancedAllocationName, Name: BalancedAllocationName,
scorer: balancedResourceScorer, scorer: balancedResourceScorer,
useRequested: true,
resourceToWeightMap: resToWeightMap, resourceToWeightMap: resToWeightMap,
enablePodOverhead: fts.EnablePodOverhead, enablePodOverhead: fts.EnablePodOverhead,
}, },

View File

@ -196,18 +196,18 @@ func TestNodeResourcesBalancedAllocation(t *testing.T) {
}, },
{ {
// Node1 scores on 0-MaxNodeScore scale // Node1 scores on 0-MaxNodeScore scale
// CPU Fraction: 300 / 250 = 100% // CPU Fraction: 0 / 250 = 0%
// Memory Fraction: 600 / 10000 = 60% // Memory Fraction: 0 / 1000 = 0%
// Node1 std: (1 - 0.6) / 2 = 0.2 // Node1 std: (0 - 0) / 2 = 0
// Node1 Score: (1 - 0.2)*MaxNodeScore = 80 // Node1 Score: (1 - 0)*MaxNodeScore = 100
// Node2 scores on 0-MaxNodeScore scale // Node2 scores on 0-MaxNodeScore scale
// CPU Fraction: 100 / 250 = 40% // CPU Fraction: 0 / 250 = 0%
// Memory Fraction: 200 / 10000 = 20% // Memory Fraction: 0 / 1000 = 0%
// Node2 std: (0.4 - 0.2) / 2 = 0.1 // Node2 std: (0 - 0) / 2 = 0
// Node2 Score: (1 - 0.1)*MaxNodeScore = 90 // Node2 Score: (1 - 0)*MaxNodeScore = 100
pod: &v1.Pod{Spec: nonZeroContainer}, pod: &v1.Pod{Spec: nonZeroContainer},
nodes: []*v1.Node{makeNode("machine1", 250, 1000*1024*1024), makeNode("machine2", 250, 1000*1024*1024)}, nodes: []*v1.Node{makeNode("machine1", 250, 1000*1024*1024), makeNode("machine2", 250, 1000*1024*1024)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 80}, {Name: "machine2", Score: 90}}, expectedList: []framework.NodeScore{{Name: "machine1", Score: 100}, {Name: "machine2", Score: 100}},
name: "no resources requested, pods with container scheduled", name: "no resources requested, pods with container scheduled",
pods: []*v1.Pod{ pods: []*v1.Pod{
{Spec: nonZeroContainer1}, {Spec: nonZeroContainer1},
@ -314,17 +314,17 @@ func TestNodeResourcesBalancedAllocation(t *testing.T) {
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet}, args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
}, },
// Node1 scores on 0-MaxNodeScore scale // Node1 scores on 0-MaxNodeScore scale
// CPU Fraction: 3100 / 3500 = 88.57% // CPU Fraction: 3000 / 3500 = 85.71%
// Memory Fraction: 5000 / 40000 = 12.5% // Memory Fraction: 5000 / 40000 = 12.5%
// GPU Fraction: 4 / 8 = 0.5% // GPU Fraction: 4 / 8 = 0.5%
// Node1 std: sqrt(((0.8857 - 0.503) * (0.8857 - 0.503) + (0.503 - 0.125) * (0.503 - 0.125) + (0.503 - 0.5) * (0.503 - 0.5)) / 3) = 0.3105 // Node1 std: sqrt(((0.8571 - 0.503) * (0.8571 - 0.503) + (0.503 - 0.125) * (0.503 - 0.125) + (0.503 - 0.5) * (0.503 - 0.5)) / 3) = 0.3002
// Node1 Score: (1 - 0.3105)*MaxNodeScore = 68 // Node1 Score: (1 - 0.3002)*MaxNodeScore = 70
// Node2 scores on 0-MaxNodeScore scale // Node2 scores on 0-MaxNodeScore scale
// CPU Fraction: 3100 / 3500 = 88.57% // CPU Fraction: 3000 / 3500 = 85.71%
// Memory Fraction: 5000 / 40000 = 12.5% // Memory Fraction: 5000 / 40000 = 12.5%
// GPU Fraction: 1 / 8 = 12.5% // GPU Fraction: 1 / 8 = 12.5%
// Node2 std: sqrt(((0.8875 - 0.378) * (0.8875 - 0.378) + (0.378 - 0.125) * (0.378 - 0.125)) + (0.378 - 0.125) * (0.378 - 0.125)) / 3) = 0.358 // Node2 std: sqrt(((0.8571 - 0.378) * (0.8571 - 0.378) + (0.378 - 0.125) * (0.378 - 0.125)) + (0.378 - 0.125) * (0.378 - 0.125)) / 3) = 0.345
// Node2 Score: (1 - 0.358)*MaxNodeScore = 64 // Node2 Score: (1 - 0.358)*MaxNodeScore = 65
{ {
pod: &v1.Pod{ pod: &v1.Pod{
Spec: v1.PodSpec{ Spec: v1.PodSpec{
@ -341,7 +341,7 @@ func TestNodeResourcesBalancedAllocation(t *testing.T) {
}, },
}, },
nodes: []*v1.Node{makeNodeWithExtendedResource("machine1", 3500, 40000, scalarResource), makeNodeWithExtendedResource("machine2", 3500, 40000, scalarResource)}, nodes: []*v1.Node{makeNodeWithExtendedResource("machine1", 3500, 40000, scalarResource), makeNodeWithExtendedResource("machine2", 3500, 40000, scalarResource)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 68}, {Name: "machine2", Score: 64}}, expectedList: []framework.NodeScore{{Name: "machine1", Score: 70}, {Name: "machine2", Score: 65}},
name: "include scalar resource on a node for balanced resource allocation", name: "include scalar resource on a node for balanced resource allocation",
pods: []*v1.Pod{ pods: []*v1.Pod{
{Spec: cpuAndMemory}, {Spec: cpuAndMemory},

View File

@ -32,7 +32,10 @@ type scorer func(args *config.NodeResourcesFitArgs) *resourceAllocationScorer
// resourceAllocationScorer contains information to calculate resource allocation score. // resourceAllocationScorer contains information to calculate resource allocation score.
type resourceAllocationScorer struct { type resourceAllocationScorer struct {
Name string Name string
// used to decide whether to use Requested or NonZeroRequested for
// cpu and memory.
useRequested bool
scorer func(requested, allocable resourceToValueMap) int64 scorer func(requested, allocable resourceToValueMap) int64
resourceToWeightMap resourceToWeightMap resourceToWeightMap resourceToWeightMap
@ -53,10 +56,11 @@ func (r *resourceAllocationScorer) score(
if r.resourceToWeightMap == nil { if r.resourceToWeightMap == nil {
return 0, framework.NewStatus(framework.Error, "resources not found") return 0, framework.NewStatus(framework.Error, "resources not found")
} }
requested := make(resourceToValueMap) requested := make(resourceToValueMap)
allocatable := make(resourceToValueMap) allocatable := make(resourceToValueMap)
for resource := range r.resourceToWeightMap { for resource := range r.resourceToWeightMap {
alloc, req := calculateResourceAllocatableRequest(nodeInfo, pod, resource, r.enablePodOverhead) alloc, req := r.calculateResourceAllocatableRequest(nodeInfo, pod, resource)
if alloc != 0 { if alloc != 0 {
// Only fill the extended resource entry when it's non-zero. // Only fill the extended resource entry when it's non-zero.
allocatable[resource], requested[resource] = alloc, req allocatable[resource], requested[resource] = alloc, req
@ -80,8 +84,13 @@ func (r *resourceAllocationScorer) score(
// - 1st param: quantity of allocatable resource on the node. // - 1st param: quantity of allocatable resource on the node.
// - 2nd param: aggregated quantity of requested resource on the node. // - 2nd param: aggregated quantity of requested resource on the node.
// Note: if it's an extended resource, and the pod doesn't request it, (0, 0) is returned. // Note: if it's an extended resource, and the pod doesn't request it, (0, 0) is returned.
func calculateResourceAllocatableRequest(nodeInfo *framework.NodeInfo, pod *v1.Pod, resource v1.ResourceName, enablePodOverhead bool) (int64, int64) { func (r *resourceAllocationScorer) calculateResourceAllocatableRequest(nodeInfo *framework.NodeInfo, pod *v1.Pod, resource v1.ResourceName) (int64, int64) {
podRequest := calculatePodResourceRequest(pod, resource, enablePodOverhead) requested := nodeInfo.NonZeroRequested
if r.useRequested {
requested = nodeInfo.Requested
}
podRequest := r.calculatePodResourceRequest(pod, resource)
// If it's an extended resource, and the pod doesn't request it. We return (0, 0) // If it's an extended resource, and the pod doesn't request it. We return (0, 0)
// as an implication to bypass scoring on this resource. // as an implication to bypass scoring on this resource.
if podRequest == 0 && schedutil.IsScalarResourceName(resource) { if podRequest == 0 && schedutil.IsScalarResourceName(resource) {
@ -89,9 +98,9 @@ func calculateResourceAllocatableRequest(nodeInfo *framework.NodeInfo, pod *v1.P
} }
switch resource { switch resource {
case v1.ResourceCPU: case v1.ResourceCPU:
return nodeInfo.Allocatable.MilliCPU, (nodeInfo.NonZeroRequested.MilliCPU + podRequest) return nodeInfo.Allocatable.MilliCPU, (requested.MilliCPU + podRequest)
case v1.ResourceMemory: case v1.ResourceMemory:
return nodeInfo.Allocatable.Memory, (nodeInfo.NonZeroRequested.Memory + podRequest) return nodeInfo.Allocatable.Memory, (requested.Memory + podRequest)
case v1.ResourceEphemeralStorage: case v1.ResourceEphemeralStorage:
return nodeInfo.Allocatable.EphemeralStorage, (nodeInfo.Requested.EphemeralStorage + podRequest) return nodeInfo.Allocatable.EphemeralStorage, (nodeInfo.Requested.EphemeralStorage + podRequest)
default: default:
@ -108,24 +117,24 @@ func calculateResourceAllocatableRequest(nodeInfo *framework.NodeInfo, pod *v1.P
// calculatePodResourceRequest returns the total non-zero requests. If Overhead is defined for the pod and the // calculatePodResourceRequest returns the total non-zero requests. If Overhead is defined for the pod and the
// PodOverhead feature is enabled, the Overhead is added to the result. // PodOverhead feature is enabled, the Overhead is added to the result.
// podResourceRequest = max(sum(podSpec.Containers), podSpec.InitContainers) + overHead // podResourceRequest = max(sum(podSpec.Containers), podSpec.InitContainers) + overHead
func calculatePodResourceRequest(pod *v1.Pod, resource v1.ResourceName, enablePodOverhead bool) int64 { func (r *resourceAllocationScorer) calculatePodResourceRequest(pod *v1.Pod, resource v1.ResourceName) int64 {
var podRequest int64 var podRequest int64
for i := range pod.Spec.Containers { for i := range pod.Spec.Containers {
container := &pod.Spec.Containers[i] container := &pod.Spec.Containers[i]
value := schedutil.GetNonzeroRequestForResource(resource, &container.Resources.Requests) value := schedutil.GetRequestForResource(resource, &container.Resources.Requests, !r.useRequested)
podRequest += value podRequest += value
} }
for i := range pod.Spec.InitContainers { for i := range pod.Spec.InitContainers {
initContainer := &pod.Spec.InitContainers[i] initContainer := &pod.Spec.InitContainers[i]
value := schedutil.GetNonzeroRequestForResource(resource, &initContainer.Resources.Requests) value := schedutil.GetRequestForResource(resource, &initContainer.Resources.Requests, !r.useRequested)
if podRequest < value { if podRequest < value {
podRequest = value podRequest = value
} }
} }
// If Overhead is being utilized, add to the total requests for the pod // If Overhead is being utilized, add to the total requests for the pod
if pod.Spec.Overhead != nil && enablePodOverhead { if pod.Spec.Overhead != nil && r.enablePodOverhead {
if quantity, found := pod.Spec.Overhead[resource]; found { if quantity, found := pod.Spec.Overhead[resource]; found {
podRequest += quantity.Value() podRequest += quantity.Value()
} }

View File

@ -40,26 +40,27 @@ const (
// GetNonzeroRequests returns the default cpu and memory resource request if none is found or // GetNonzeroRequests returns the default cpu and memory resource request if none is found or
// what is provided on the request. // what is provided on the request.
func GetNonzeroRequests(requests *v1.ResourceList) (int64, int64) { func GetNonzeroRequests(requests *v1.ResourceList) (int64, int64) {
return GetNonzeroRequestForResource(v1.ResourceCPU, requests), return GetRequestForResource(v1.ResourceCPU, requests, true),
GetNonzeroRequestForResource(v1.ResourceMemory, requests) GetRequestForResource(v1.ResourceMemory, requests, true)
} }
// GetNonzeroRequestForResource returns the default resource request if none is found or // GetRequestForResource returns the requested values unless nonZero is true and there is no defined request
// what is provided on the request. // for CPU and memory.
func GetNonzeroRequestForResource(resource v1.ResourceName, requests *v1.ResourceList) int64 { // If nonZero is true and the resource has no defined request for CPU or memory, it returns a default value.
func GetRequestForResource(resource v1.ResourceName, requests *v1.ResourceList, nonZero bool) int64 {
if requests == nil { if requests == nil {
return 0 return 0
} }
switch resource { switch resource {
case v1.ResourceCPU: case v1.ResourceCPU:
// Override if un-set, but not if explicitly set to zero // Override if un-set, but not if explicitly set to zero
if _, found := (*requests)[v1.ResourceCPU]; !found { if _, found := (*requests)[v1.ResourceCPU]; !found && nonZero {
return DefaultMilliCPURequest return DefaultMilliCPURequest
} }
return requests.Cpu().MilliValue() return requests.Cpu().MilliValue()
case v1.ResourceMemory: case v1.ResourceMemory:
// Override if un-set, but not if explicitly set to zero // Override if un-set, but not if explicitly set to zero
if _, found := (*requests)[v1.ResourceMemory]; !found { if _, found := (*requests)[v1.ResourceMemory]; !found && nonZero {
return DefaultMemoryRequest return DefaultMemoryRequest
} }
return requests.Memory().Value() return requests.Memory().Value()

View File

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
) )
@ -74,18 +74,20 @@ func TestGetNonZeroRequest(t *testing.T) {
} }
} }
func TestGetLeastRequestResource(t *testing.T) { func TestGetRequestForResource(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
requests v1.ResourceList requests v1.ResourceList
resource v1.ResourceName resource v1.ResourceName
expectedQuantity int64 expectedQuantity int64
nonZero bool
}{ }{
{ {
"extended_resource_not_found", "extended_resource_not_found",
v1.ResourceList{}, v1.ResourceList{},
v1.ResourceName("intel.com/foo"), v1.ResourceName("intel.com/foo"),
0, 0,
true,
}, },
{ {
"extended_resource_found", "extended_resource_found",
@ -94,18 +96,21 @@ func TestGetLeastRequestResource(t *testing.T) {
}, },
v1.ResourceName("intel.com/foo"), v1.ResourceName("intel.com/foo"),
4, 4,
true,
}, },
{ {
"cpu_not_found", "cpu_not_found",
v1.ResourceList{}, v1.ResourceList{},
v1.ResourceCPU, v1.ResourceCPU,
DefaultMilliCPURequest, DefaultMilliCPURequest,
true,
}, },
{ {
"memory_not_found", "memory_not_found",
v1.ResourceList{}, v1.ResourceList{},
v1.ResourceMemory, v1.ResourceMemory,
DefaultMemoryRequest, DefaultMemoryRequest,
true,
}, },
{ {
"cpu_exist", "cpu_exist",
@ -114,6 +119,7 @@ func TestGetLeastRequestResource(t *testing.T) {
}, },
v1.ResourceCPU, v1.ResourceCPU,
200, 200,
true,
}, },
{ {
"memory_exist", "memory_exist",
@ -122,6 +128,7 @@ func TestGetLeastRequestResource(t *testing.T) {
}, },
v1.ResourceMemory, v1.ResourceMemory,
400 * 1024 * 1024, 400 * 1024 * 1024,
true,
}, },
{ {
"ephemeralStorage_exist", "ephemeralStorage_exist",
@ -130,18 +137,34 @@ func TestGetLeastRequestResource(t *testing.T) {
}, },
v1.ResourceEphemeralStorage, v1.ResourceEphemeralStorage,
400 * 1024 * 1024, 400 * 1024 * 1024,
true,
}, },
{ {
"ephemeralStorage_not_found", "ephemeralStorage_not_found",
v1.ResourceList{}, v1.ResourceList{},
v1.ResourceEphemeralStorage, v1.ResourceEphemeralStorage,
0, 0,
true,
},
{
"cpu_not_found, useRequested is true",
v1.ResourceList{},
v1.ResourceCPU,
0,
false,
},
{
"memory_not_found, useRequested is true",
v1.ResourceList{},
v1.ResourceMemory,
0,
false,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
realQuantity := GetNonzeroRequestForResource(test.resource, &test.requests) realQuantity := GetRequestForResource(test.resource, &test.requests, test.nonZero)
assert.EqualValuesf(t, test.expectedQuantity, realQuantity, "Failed to test: %s", test.name) assert.EqualValuesf(t, test.expectedQuantity, realQuantity, "Failed to test: %s", test.name)
}) })
} }