mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-09 00:34:10 -04:00
Merge pull request #135573 from brejman/issue-129733-score-update
Update scoring function for balanced allocation to consider change to the node's balance
This commit is contained in:
commit
efc15394a1
8 changed files with 312 additions and 506 deletions
|
|
@ -191,14 +191,30 @@ func NewBalancedAllocation(_ context.Context, baArgs runtime.Object, h fwk.Handl
|
|||
}, nil
|
||||
}
|
||||
|
||||
func balancedResourceScorer(requested, allocable []int64) int64 {
|
||||
func balancedResourceScorer(requested, allocated, allocatable []int64) int64 {
|
||||
// Compute the score for the node with pending pod requests
|
||||
scoreWithPod := balancedResourceScore(requested, allocatable)
|
||||
// Compute the score for the node without pending pod requests
|
||||
scoreWithoutPod := balancedResourceScore(allocated, allocatable)
|
||||
// Score based on the improvement to the node balance (before vs after adding the pod).
|
||||
// Scores with/without pod are in range [MaxNodeScore/2, MaxNodeScore].
|
||||
// Therefore, their difference will be in range [-MaxNodeScore/2, MaxNodeScore/2].
|
||||
// This operation brings the range back to [MaxNodeScore/2, MaxNodeScore]
|
||||
// to be closer to the original implementation that only considered the score after adding the pod.
|
||||
// If the difference to the balance is small, the score will be around 75.
|
||||
// If the balance is improved, the score will move towards 100.
|
||||
// If the balance is decreased, the score will move towards 50.
|
||||
return fwk.MaxNodeScore/2 + (fwk.MaxNodeScore/2+scoreWithPod-scoreWithoutPod)/2
|
||||
}
|
||||
|
||||
func balancedResourceScore(requested, allocatable []int64) int64 {
|
||||
var resourceToFractions []float64
|
||||
var totalFraction float64
|
||||
for i := range requested {
|
||||
if allocable[i] == 0 {
|
||||
if allocatable[i] == 0 {
|
||||
continue
|
||||
}
|
||||
fraction := float64(requested[i]) / float64(allocable[i])
|
||||
fraction := float64(requested[i]) / float64(allocatable[i])
|
||||
if fraction > 1 {
|
||||
fraction = 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
apiruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
|
|
@ -41,234 +40,162 @@ func TestNodeResourcesBalancedAllocation(t *testing.T) {
|
|||
testNodeResourcesBalancedAllocation(ktesting.Init(t))
|
||||
}
|
||||
func testNodeResourcesBalancedAllocation(tCtx ktesting.TContext) {
|
||||
cpuAndMemoryAndGPU := v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("1000m"),
|
||||
v1.ResourceMemory: resource.MustParse("2000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("2000m"),
|
||||
v1.ResourceMemory: resource.MustParse("3000"),
|
||||
"nvidia.com/gpu": resource.MustParse("3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NodeName: "node1",
|
||||
}
|
||||
cpuOnly := v1.PodSpec{
|
||||
NodeName: "node1",
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("1000m"),
|
||||
v1.ResourceMemory: resource.MustParse("0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("2000m"),
|
||||
v1.ResourceMemory: resource.MustParse("0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
cpuOnly2 := cpuOnly
|
||||
cpuOnly2.NodeName = "node2"
|
||||
cpuAndMemory := v1.PodSpec{
|
||||
NodeName: "node2",
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("1000m"),
|
||||
v1.ResourceMemory: resource.MustParse("2000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse("2000m"),
|
||||
v1.ResourceMemory: resource.MustParse("3000"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defaultResourceBalancedAllocationSet := []config.ResourceSpec{
|
||||
{Name: string(v1.ResourceCPU), Weight: 1},
|
||||
{Name: string(v1.ResourceMemory), Weight: 1},
|
||||
}
|
||||
scalarResource := map[string]int64{
|
||||
"nvidia.com/gpu": 8,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
pod *v1.Pod
|
||||
pods []*v1.Pod
|
||||
nodes []*v1.Node
|
||||
expectedList fwk.NodeScoreList
|
||||
name string
|
||||
args config.NodeResourcesBalancedAllocationArgs
|
||||
runPreScore bool
|
||||
wantPreScoreStatusCode fwk.Code
|
||||
draObjects []apiruntime.Object
|
||||
name string
|
||||
requestedPod *v1.Pod
|
||||
nodes []*v1.Node
|
||||
existingPods []*v1.Pod
|
||||
expectedScores fwk.NodeScoreList
|
||||
args config.NodeResourcesBalancedAllocationArgs
|
||||
runPreScore bool
|
||||
expectedPreScoreStatusCode fwk.Code
|
||||
draObjects []apiruntime.Object
|
||||
}{
|
||||
{
|
||||
// bestEffort pods, skip in PreScore
|
||||
pod: st.MakePod().Obj(),
|
||||
nodes: []*v1.Node{makeNode("node1", 4000, 10000, nil), makeNode("node2", 4000, 10000, nil)},
|
||||
name: "nothing scheduled, nothing requested, skip in PreScore",
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
wantPreScoreStatusCode: fwk.Skip,
|
||||
name: "nothing scheduled, nothing requested, skip in PreScore",
|
||||
requestedPod: st.MakePod().Obj(),
|
||||
nodes: []*v1.Node{makeNode("node1", 4000, 10000, nil), makeNode("node2", 4000, 10000, nil)},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
expectedPreScoreStatusCode: fwk.Skip,
|
||||
},
|
||||
{
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 4000= 75%
|
||||
// Memory Fraction: 5000 / 10000 = 50%
|
||||
// Node1 std: (0.75 - 0.5) / 2 = 0.125
|
||||
// Node1 Score: (1 - 0.125)*MaxNodeScore = 87
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 6000= 50%
|
||||
// Memory Fraction: 5000/10000 = 50%
|
||||
// Node2 std: 0
|
||||
// Node2 Score: (1-0) * MaxNodeScore = MaxNodeScore
|
||||
pod: &v1.Pod{Spec: cpuAndMemory},
|
||||
nodes: []*v1.Node{makeNode("node1", 4000, 10000, nil), makeNode("node2", 6000, 10000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 87}, {Name: "node2", Score: fwk.MaxNodeScore}},
|
||||
// Node1
|
||||
// CPU: 0 -> 3000/4000 (0% -> 75%)
|
||||
// Memory: 0 -> 5000/10000 (0% -> 50%)
|
||||
// Score: 68 (100 -> 87)
|
||||
// Node2
|
||||
// CPU: 0 -> 3000/6000 (0% -> 50%)
|
||||
// Memory: 0 -> 5000/10000 (0% -> 50%)
|
||||
// Score: 75 (100 -> 100)
|
||||
name: "nothing scheduled, resources requested, differently sized nodes",
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 4000, 10000, nil),
|
||||
makeNode("node2", 6000, 10000, nil)},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 68}, {Name: "node2", Score: 75}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
},
|
||||
{
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 10000 = 60%
|
||||
// Memory Fraction: 5000 / 20000 = 25%
|
||||
// Node1 std: (0.6 - 0.25) / 2 = 0.175
|
||||
// Node1 Score: (1 - 0.175)*MaxNodeScore = 82
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 10000 = 60%
|
||||
// Memory Fraction: 10000 / 20000 = 50%
|
||||
// Node2 std: (0.6 - 0.5) / 2 = 0.05
|
||||
// Node2 Score: (1 - 0.05)*MaxNodeScore = 95
|
||||
pod: &v1.Pod{Spec: cpuAndMemory},
|
||||
nodes: []*v1.Node{makeNode("node1", 10000, 20000, nil), makeNode("node2", 10000, 20000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 82}, {Name: "node2", Score: 95}},
|
||||
// Node1
|
||||
// CPU: 3000 -> 6000/10000 (30% -> 60%)
|
||||
// Memory: 0 -> 5000/20000 (0% -> 25%)
|
||||
// Score: 73 (85 -> 82)
|
||||
// Node2
|
||||
// CPU: 3000 -> 6000/10000 (30% -> 60%)
|
||||
// Memory: 5000 -> 10000/20000 (25% -> 50%)
|
||||
// Score: 74 (97 -> 95)
|
||||
name: "resources requested, pods scheduled with resources",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: cpuOnly},
|
||||
{Spec: cpuAndMemory},
|
||||
requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 10000, 20000, nil),
|
||||
makeNode("node2", 10000, 20000, nil),
|
||||
},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 73}, {Name: "node2", Score: 74}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
},
|
||||
{
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 10000 = 60%
|
||||
// Memory Fraction: 5000 / 20000 = 25%
|
||||
// Node1 std: (0.6 - 0.25) / 2 = 0.175
|
||||
// Node1 Score: (1 - 0.175)*MaxNodeScore = 82
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 10000 = 60%
|
||||
// Memory Fraction: 10000 / 50000 = 20%
|
||||
// Node2 std: (0.6 - 0.2) / 2 = 0.2
|
||||
// Node2 Score: (1 - 0.2)*MaxNodeScore = 80
|
||||
pod: &v1.Pod{Spec: cpuAndMemory},
|
||||
nodes: []*v1.Node{makeNode("node1", 10000, 20000, nil), makeNode("node2", 10000, 50000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 82}, {Name: "node2", Score: 80}},
|
||||
// Node1
|
||||
// CPU: 3000 -> 6000/10000 (30% -> 60%)
|
||||
// Memory: 0 -> 5000/20000 (0% -> 25%)
|
||||
// Score: 73 (85 -> 82)
|
||||
// Node2
|
||||
// CPU: 3000 -> 6000/10000 (30% -> 60%)
|
||||
// Memory: 5000 -> 10000/50000 (10% -> 20%)
|
||||
// Score: 70 (90 -> 80)
|
||||
name: "resources requested, pods scheduled with resources, differently sized nodes",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: cpuOnly},
|
||||
{Spec: cpuAndMemory},
|
||||
requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 10000, 20000, nil),
|
||||
makeNode("node2", 10000, 50000, nil),
|
||||
},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 73}, {Name: "node2", Score: 70}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
},
|
||||
{
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 6000 = 1
|
||||
// Memory Fraction: 0 / 10000 = 0
|
||||
// Node1 std: (1 - 0) / 2 = 0.5
|
||||
// Node1 Score: (1 - 0.5)*MaxNodeScore = 50
|
||||
// Node1 Score: MaxNodeScore - (1 - 0) * MaxNodeScore = 0
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 6000 = 1
|
||||
// Memory Fraction 5000 / 10000 = 50%
|
||||
// Node2 std: (1 - 0.5) / 2 = 0.25
|
||||
// Node2 Score: (1 - 0.25)*MaxNodeScore = 75
|
||||
pod: &v1.Pod{Spec: cpuOnly},
|
||||
nodes: []*v1.Node{makeNode("node1", 6000, 10000, nil), makeNode("node2", 6000, 10000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 75}},
|
||||
// Node1
|
||||
// CPU: 3000 -> 3000/3000 (100% -> 100%)
|
||||
// Memory: 0 -> 5000/5000 (0% -> 100%)
|
||||
// Score: 100 (50 -> 100)
|
||||
// Node2
|
||||
// CPU: 0 -> 0/10000 (0% -> 0%)
|
||||
// Memory: 0 -> 5000/5000 (0% -> 100%)
|
||||
// Score: 50 (100 -> 50)
|
||||
name: "resources requested, pods scheduled with resources, nodes to reach min/max score",
|
||||
requestedPod: st.MakePod().Req(map[v1.ResourceName]string{"memory": "2000"}).Req(map[v1.ResourceName]string{"memory": "3000"}).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 3000, 5000, nil),
|
||||
makeNode("node2", 3000, 5000, nil),
|
||||
},
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 100}, {Name: "node2", Score: 50}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
},
|
||||
{
|
||||
// Node1
|
||||
// CPU: 3000 -> 6000/6000 (50% -> 100%)
|
||||
// Memory: 0 -> 0/10000 (0% -> 0%)
|
||||
// Score: 62 (75 -> 50)
|
||||
// Node2
|
||||
// CPU: 3000 -> 6000/6000 (50% -> 100%)
|
||||
// Memory: 5000 -> 5000/10000 (50% -> 50%)
|
||||
// Score: 62 (100 -> 75)
|
||||
name: "requested resources at node capacity",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: cpuOnly},
|
||||
{Spec: cpuAndMemory},
|
||||
requestedPod: st.MakePod().Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 6000, 10000, nil),
|
||||
makeNode("node2", 6000, 10000, nil),
|
||||
},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
},
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 3500 = 85.71%
|
||||
// Memory Fraction: 5000 / 40000 = 12.5%
|
||||
// GPU Fraction: 4 / 8 = 0.5%
|
||||
// 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.3002)*MaxNodeScore = 70
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 3500 = 85.71%
|
||||
// Memory Fraction: 5000 / 40000 = 12.5%
|
||||
// GPU Fraction: 1 / 8 = 12.5%
|
||||
// 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 = 65
|
||||
{
|
||||
pod: st.MakePod().Req(map[v1.ResourceName]string{
|
||||
v1.ResourceMemory: "0",
|
||||
"nvidia.com/gpu": "1",
|
||||
}).Obj(),
|
||||
nodes: []*v1.Node{makeNode("node1", 3500, 40000, scalarResource), makeNode("node2", 3500, 40000, scalarResource)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 70}, {Name: "node2", Score: 65}},
|
||||
name: "include scalar resource on a node for balanced resource allocation",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: cpuAndMemory},
|
||||
{Spec: cpuAndMemoryAndGPU},
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{
|
||||
{Name: string(v1.ResourceCPU), Weight: 1},
|
||||
{Name: string(v1.ResourceMemory), Weight: 1},
|
||||
{Name: "nvidia.com/gpu", Weight: 1},
|
||||
}},
|
||||
runPreScore: true,
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 62}, {Name: "node2", Score: 62}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: true,
|
||||
},
|
||||
// Only one node (node1) has the scalar resource, pod doesn't request the scalar resource and the scalar resource should be skipped for consideration.
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 3500 = 85.71%
|
||||
// Memory Fraction: 5000 / 40000 = 12.5%
|
||||
// Node1 std: (0.8571 - 0.125) / 2 = 0.36605
|
||||
// Node1 Score: (1 - 0.22705)*MaxNodeScore = 63
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 3500 = 85.71%
|
||||
// Memory Fraction: 5000 / 40000 = 12.5%
|
||||
// Node2 std: (0.8571 - 0.125) / 2 = 0.36605
|
||||
// Node2 Score: (1 - 0.22705)*MaxNodeScore = 63
|
||||
{
|
||||
pod: &v1.Pod{Spec: cpuAndMemory},
|
||||
nodes: []*v1.Node{makeNode("node1", 3500, 40000, scalarResource), makeNode("node2", 3500, 40000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 63}, {Name: "node2", Score: 63}},
|
||||
name: "node without the scalar resource should skip the scalar resource",
|
||||
pods: []*v1.Pod{},
|
||||
// Node1
|
||||
// CPU: 3000 -> 3000/3500 (85.7% -> 85.7%)
|
||||
// Memory: 5000 -> 5000/40000 (12.5% -> 12.5%)
|
||||
// GPU: 3 -> 4/8 (37.5% -> 50%)
|
||||
// Score: 75 (69 -> 70)
|
||||
// Node2
|
||||
// CPU: 3000 -> 3000/3500 (85.7% -> 85.7%)
|
||||
// Memory: 5000 -> 5000/40000 (12.5% -> 12.5%)
|
||||
// GPU: 0 -> 1/8 (0% -> 12.5%)
|
||||
// Score: 76 (62 -> 65)
|
||||
name: "scalar resource is included in the score computation if pod requests the scalar resource",
|
||||
requestedPod: st.MakePod().Req(cpuAndMemoryAndGpu("0", "0", "1")).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 3500, 40000, gpu(8)),
|
||||
makeNode("node2", 3500, 40000, gpu(8)),
|
||||
},
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemoryAndGpu("2000m", "3000", "3")).Obj(),
|
||||
st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 75}, {Name: "node2", Score: 76}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{
|
||||
{Name: string(v1.ResourceCPU), Weight: 1},
|
||||
{Name: string(v1.ResourceMemory), Weight: 1},
|
||||
|
|
@ -277,48 +204,73 @@ func testNodeResourcesBalancedAllocation(tCtx ktesting.TContext) {
|
|||
runPreScore: true,
|
||||
},
|
||||
{
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 10000 = 60%
|
||||
// Memory Fraction: 5000 / 20000 = 25%
|
||||
// Node1 std: (0.6 - 0.25) / 2 = 0.175
|
||||
// Node1 Score: (1 - 0.175)*MaxNodeScore = 82
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 6000 / 10000 = 60%
|
||||
// Memory Fraction: 10000 / 20000 = 50%
|
||||
// Node2 std: (0.6 - 0.5) / 2 = 0.05
|
||||
// Node2 Score: (1 - 0.05)*MaxNodeScore = 95
|
||||
pod: &v1.Pod{Spec: cpuAndMemory},
|
||||
nodes: []*v1.Node{makeNode("node1", 10000, 20000, nil), makeNode("node2", 10000, 20000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 82}, {Name: "node2", Score: 95}},
|
||||
// Node1
|
||||
// CPU: 0 -> 3000/3500 (0% -> 85.7%)
|
||||
// Memory: 0 -> 5000/40000 (0% -> 12.5%)
|
||||
// GPU: 0 -> 0/8 (scalar resource not requested by pod, ignored)
|
||||
// Score: 56 (100 -> 63)
|
||||
// Node2
|
||||
// CPU: 0 -> 3000/3500 (0% -> 85.7%)
|
||||
// Memory: 0 -> 5000/40000 (0% -> 12.5%)
|
||||
// Score: 56 (100 -> 63)
|
||||
name: "scalar resource is not included in the score computation if pod doesn't request the scalar resource",
|
||||
requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 3500, 40000, gpu(8)),
|
||||
makeNode("node2", 3500, 40000, nil),
|
||||
},
|
||||
existingPods: []*v1.Pod{},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 56}, {Name: "node2", Score: 56}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{
|
||||
{Name: string(v1.ResourceCPU), Weight: 1},
|
||||
{Name: string(v1.ResourceMemory), Weight: 1},
|
||||
{Name: "nvidia.com/gpu", Weight: 1},
|
||||
}},
|
||||
runPreScore: true,
|
||||
},
|
||||
{
|
||||
// Whether or not prescore was called, the end result should be the same.
|
||||
// Node1
|
||||
// CPU: 3000 -> 6000/10000 (30% -> 60%)
|
||||
// Memory: 0 -> 5000/20000 (0% -> 25%)
|
||||
// Score: 73 (85 -> 82)
|
||||
// Node2
|
||||
// CPU: 3000 -> 6000/10000 (30% -> 60%)
|
||||
// Memory: 5000 -> 10000/20000 (25% -> 50%)
|
||||
// Score: 74 (97 -> 95)
|
||||
name: "resources requested, pods scheduled with resources if PreScore not called",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: cpuOnly},
|
||||
{Spec: cpuAndMemory},
|
||||
requestedPod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
nodes: []*v1.Node{
|
||||
makeNode("node1", 10000, 20000, nil),
|
||||
makeNode("node2", 10000, 20000, nil),
|
||||
},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: false,
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 73}, {Name: "node2", Score: 74}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: defaultResourceBalancedAllocationSet},
|
||||
runPreScore: false,
|
||||
},
|
||||
{
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 3500 = 0.8571
|
||||
// Memory Fraction: 0 / 40000 = 0
|
||||
// DRA Fraction: 1 / 8 = 0.125
|
||||
// Fraction mean: (0.8571 + 0 + 0.125) / 3 = 0.3274
|
||||
// Node1 std: sqrt(((0.8571 - 0.3274)**2 + (0- 0.3274)**2 + (0.125 - 0.3274)**2) / 3) = 0.378
|
||||
// Node1 Score: (1 - 0.378)*MaxNodeScore = 62
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 3500 = 0.8571
|
||||
// Memory Fraction: 5000 / 40000 = 0.125
|
||||
// Node2 std: (0.8571 - 0.125) / 2 = 0.36605
|
||||
// Node2 Score: (1 - 0.36605)*MaxNodeScore = 63
|
||||
pod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(),
|
||||
nodes: []*v1.Node{makeNode("node1", 3500, 40000, nil), makeNode("node2", 3500, 40000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 62}, {Name: "node2", Score: 63}},
|
||||
// Node1
|
||||
// CPU: 3000 -> 3000/3500 (86% -> 86%)
|
||||
// Memory: 0 -> 0/40000 (0% -> 0%)
|
||||
// DRA: 0 -> 1/8 (0% -> 12%)
|
||||
// Score: 76 (60 -> 62)
|
||||
// Node2
|
||||
// CPU: 3000 -> 3000/3500 (86% -> 86%)
|
||||
// Memory: 5000 -> 5000/40000 (12% -> 12%)
|
||||
// DRA: unsatisfiable
|
||||
// Score: 75 (63 -> 63)
|
||||
name: "include DRA resource on a node for balanced resource allocation",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: cpuOnly},
|
||||
{Spec: cpuAndMemory},
|
||||
requestedPod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(),
|
||||
nodes: []*v1.Node{makeNode("node1", 3500, 40000, nil), makeNode("node2", 3500, 40000, nil)},
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 76}, {Name: "node2", Score: 75}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{
|
||||
{Name: string(v1.ResourceCPU), Weight: 1},
|
||||
{Name: string(v1.ResourceMemory), Weight: 1},
|
||||
|
|
@ -331,26 +283,24 @@ func testNodeResourcesBalancedAllocation(tCtx ktesting.TContext) {
|
|||
runPreScore: true,
|
||||
},
|
||||
{
|
||||
// Node1 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 35000 = 0.8571
|
||||
// Memory Fraction: 0 / 40000 = 0
|
||||
// DRA Fraction: 1 / 8 = 0.125
|
||||
// Fraction mean: (0.8571 + 0 + 0.125) / 3 = 0.3274
|
||||
// Node1 std: sqrt(((0.8571 - 0.3274)**2 + (0- 0.3274)**2 + (0.125 - 0.3274)**2) / 3) = 0.378
|
||||
// Node1 Score: (1 - 0.378)*MaxNodeScore = 62
|
||||
// Node2 scores on 0-MaxNodeScore scale
|
||||
// CPU Fraction: 3000 / 35000 = 0.8571
|
||||
// Memory Fraction: 5000 / 40000 = 0.125
|
||||
// Node2 std: (0.8571 - 0.125) / 2 = 0.36605
|
||||
// Node2 Score: (1 - 0.36605)*MaxNodeScore = 63
|
||||
pod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(),
|
||||
nodes: []*v1.Node{makeNode("node1", 3500, 40000, nil), makeNode("node2", 3500, 40000, nil)},
|
||||
expectedList: []fwk.NodeScore{{Name: "node1", Score: 62}, {Name: "node2", Score: 63}},
|
||||
// Node1
|
||||
// CPU: 3000 -> 3000/3500 (86% -> 86%)
|
||||
// Memory: 0 -> 0/40000 (0% -> 0%)
|
||||
// DRA: 0 -> 1/8 (0% -> 12%)
|
||||
// Score: 76 (60 -> 62)
|
||||
// Node2
|
||||
// CPU: 3000 -> 3000/3500 (86% -> 86%)
|
||||
// Memory: 5000 -> 5000/40000 (12% -> 12%)
|
||||
// DRA: unsatisfiable
|
||||
// Score: 75 (63 -> 63)
|
||||
name: "include DRA resource on a node for balanced resource allocation if PreScore not called",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: cpuOnly},
|
||||
{Spec: cpuAndMemory},
|
||||
requestedPod: st.MakePod().Req(map[v1.ResourceName]string{extendedResourceDRA: "1"}).Obj(),
|
||||
nodes: []*v1.Node{makeNode("node1", 3500, 40000, nil), makeNode("node2", 3500, 40000, nil)},
|
||||
existingPods: []*v1.Pod{
|
||||
st.MakePod().Node("node1").Req(cpuOnly("1000m")).Req(cpuOnly("2000m")).Obj(),
|
||||
st.MakePod().Node("node2").Req(cpuAndMemory("1000m", "2000")).Req(cpuAndMemory("2000m", "3000")).Obj(),
|
||||
},
|
||||
expectedScores: []fwk.NodeScore{{Name: "node1", Score: 76}, {Name: "node2", Score: 75}},
|
||||
args: config.NodeResourcesBalancedAllocationArgs{Resources: []config.ResourceSpec{
|
||||
{Name: string(v1.ResourceCPU), Weight: 1},
|
||||
{Name: string(v1.ResourceMemory), Weight: 1},
|
||||
|
|
@ -367,7 +317,7 @@ func testNodeResourcesBalancedAllocation(tCtx ktesting.TContext) {
|
|||
for _, test := range tests {
|
||||
tCtx.SyncTest(test.name, func(tCtx ktesting.TContext) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, test.draObjects != nil)
|
||||
snapshot := cache.NewSnapshot(test.pods, test.nodes)
|
||||
snapshot := cache.NewSnapshot(test.existingPods, test.nodes)
|
||||
fh, _ := runtime.NewFramework(tCtx, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
|
||||
defer func() {
|
||||
tCtx.Cancel("test has completed")
|
||||
|
|
@ -382,9 +332,9 @@ func testNodeResourcesBalancedAllocation(tCtx ktesting.TContext) {
|
|||
|
||||
state := framework.NewCycleState()
|
||||
if test.runPreScore {
|
||||
status := p.(fwk.PreScorePlugin).PreScore(tCtx, state, test.pod, tf.BuildNodeInfos(test.nodes))
|
||||
if status.Code() != test.wantPreScoreStatusCode {
|
||||
tCtx.Errorf("unexpected status code, want: %v, got: %v", test.wantPreScoreStatusCode, status.Code())
|
||||
status := p.(fwk.PreScorePlugin).PreScore(tCtx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes))
|
||||
if status.Code() != test.expectedPreScoreStatusCode {
|
||||
tCtx.Errorf("unexpected status code, want: %v, got: %v", test.expectedPreScoreStatusCode, status.Code())
|
||||
}
|
||||
if status.Code() == fwk.Skip {
|
||||
tCtx.Log("skipping score test as PreScore returned skip")
|
||||
|
|
@ -396,11 +346,11 @@ func testNodeResourcesBalancedAllocation(tCtx ktesting.TContext) {
|
|||
if err != nil {
|
||||
tCtx.Errorf("failed to get node %q from snapshot: %v", test.nodes[i].Name, err)
|
||||
}
|
||||
hostResult, status := p.(fwk.ScorePlugin).Score(tCtx, state, test.pod, nodeInfo)
|
||||
hostResult, status := p.(fwk.ScorePlugin).Score(tCtx, state, test.requestedPod, nodeInfo)
|
||||
if !status.IsSuccess() {
|
||||
tCtx.Errorf("Score is expected to return success, but didn't. Got status: %v", status)
|
||||
}
|
||||
if diff := cmp.Diff(test.expectedList[i].Score, hostResult); diff != "" {
|
||||
if diff := cmp.Diff(test.expectedScores[i].Score, hostResult); diff != "" {
|
||||
tCtx.Errorf("unexpected score for host %v (-want,+got):\n%s", test.nodes[i].Name, diff)
|
||||
}
|
||||
}
|
||||
|
|
@ -417,16 +367,11 @@ func TestBalancedAllocationSignPod(t *testing.T) {
|
|||
expectedStatusCode fwk.Code
|
||||
}{
|
||||
"pod with CPU and memory requests": {
|
||||
pod: st.MakePod().Req(map[v1.ResourceName]string{
|
||||
v1.ResourceCPU: "1000m",
|
||||
v1.ResourceMemory: "2000",
|
||||
}).Obj(),
|
||||
pod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Obj(),
|
||||
enableDRAExtendedResource: false,
|
||||
expectedFragments: []fwk.SignFragment{
|
||||
{Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod().Req(map[v1.ResourceName]string{
|
||||
v1.ResourceCPU: "1000m",
|
||||
v1.ResourceMemory: "2000",
|
||||
}).Obj(), ResourceRequestsOptions{})},
|
||||
{Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(
|
||||
st.MakePod().Req(cpuAndMemory("1000m", "2000")).Obj(), ResourceRequestsOptions{})},
|
||||
},
|
||||
expectedStatusCode: fwk.Success,
|
||||
},
|
||||
|
|
@ -439,30 +384,19 @@ func TestBalancedAllocationSignPod(t *testing.T) {
|
|||
expectedStatusCode: fwk.Success,
|
||||
},
|
||||
"pod with multiple containers": {
|
||||
pod: st.MakePod().Container("container1").Req(map[v1.ResourceName]string{
|
||||
v1.ResourceCPU: "500m",
|
||||
v1.ResourceMemory: "1000",
|
||||
}).Container("container2").Req(map[v1.ResourceName]string{
|
||||
v1.ResourceCPU: "1500m",
|
||||
v1.ResourceMemory: "3000",
|
||||
}).Obj(),
|
||||
pod: st.MakePod().
|
||||
Container("container1").Req(cpuAndMemory("500m", "1000")).
|
||||
Container("container2").Req(cpuAndMemory("1500m", "3000")).Obj(),
|
||||
enableDRAExtendedResource: false,
|
||||
expectedFragments: []fwk.SignFragment{
|
||||
{Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod().Container("container1").Req(map[v1.ResourceName]string{
|
||||
v1.ResourceCPU: "500m",
|
||||
v1.ResourceMemory: "1000",
|
||||
}).Container("container2").Req(map[v1.ResourceName]string{
|
||||
v1.ResourceCPU: "1500m",
|
||||
v1.ResourceMemory: "3000",
|
||||
}).Obj(), ResourceRequestsOptions{})},
|
||||
{Key: fwk.ResourcesSignerName, Value: computePodResourceRequest(st.MakePod().
|
||||
Container("container1").Req(cpuAndMemory("500m", "1000")).
|
||||
Container("container2").Req(cpuAndMemory("1500m", "3000")).Obj(), ResourceRequestsOptions{})},
|
||||
},
|
||||
expectedStatusCode: fwk.Success,
|
||||
},
|
||||
"DRA extended resource enabled - returns unschedulable": {
|
||||
pod: st.MakePod().Req(map[v1.ResourceName]string{
|
||||
v1.ResourceCPU: "1000m",
|
||||
v1.ResourceMemory: "2000",
|
||||
}).Obj(),
|
||||
pod: st.MakePod().Req(cpuAndMemory("1000m", "2000")).Obj(),
|
||||
enableDRAExtendedResource: true,
|
||||
expectedFragments: nil,
|
||||
expectedStatusCode: fwk.Unschedulable,
|
||||
|
|
@ -495,3 +429,16 @@ func TestBalancedAllocationSignPod(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func cpuOnly(req string) map[v1.ResourceName]string {
|
||||
return map[v1.ResourceName]string{v1.ResourceCPU: req}
|
||||
}
|
||||
func cpuAndMemory(cpuReq, memoryReq string) map[v1.ResourceName]string {
|
||||
return map[v1.ResourceName]string{v1.ResourceCPU: cpuReq, v1.ResourceMemory: memoryReq}
|
||||
}
|
||||
func cpuAndMemoryAndGpu(cpuReq, memoryReq, gpuReq string) map[v1.ResourceName]string {
|
||||
return map[v1.ResourceName]string{v1.ResourceCPU: cpuReq, v1.ResourceMemory: memoryReq, "nvidia.com/gpu": gpuReq}
|
||||
}
|
||||
func gpu(count int64) map[string]int64 {
|
||||
return map[string]int64{"nvidia.com/gpu": count}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ import (
|
|||
//
|
||||
// Details:
|
||||
// (cpu((capacity-requested)*MaxNodeScore*cpuWeight/capacity) + memory((capacity-requested)*MaxNodeScore*memoryWeight/capacity) + ...)/weightSum
|
||||
func leastResourceScorer(resources []config.ResourceSpec) func([]int64, []int64) int64 {
|
||||
return func(requested, allocable []int64) int64 {
|
||||
func leastResourceScorer(resources []config.ResourceSpec) func([]int64, []int64, []int64) int64 {
|
||||
return func(requested, _, allocable []int64) int64 {
|
||||
var nodeScore, weightSum int64
|
||||
for i := range requested {
|
||||
if allocable[i] == 0 {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ import (
|
|||
//
|
||||
// Details:
|
||||
// (cpu(MaxNodeScore * requested * cpuWeight / capacity) + memory(MaxNodeScore * requested * memoryWeight / capacity) + ...) / weightSum
|
||||
func mostResourceScorer(resources []config.ResourceSpec) func(requested, allocable []int64) int64 {
|
||||
return func(requested, allocable []int64) int64 {
|
||||
func mostResourceScorer(resources []config.ResourceSpec) func(requested, _, allocable []int64) int64 {
|
||||
return func(requested, _, allocable []int64) int64 {
|
||||
var nodeScore, weightSum int64
|
||||
for i := range requested {
|
||||
if allocable[i] == 0 {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const maxUtilization = 100
|
|||
|
||||
// buildRequestedToCapacityRatioScorerFunction allows users to apply bin packing
|
||||
// on core resources like CPU, Memory as well as extended resources like accelerators.
|
||||
func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.FunctionShape, resources []config.ResourceSpec) func([]int64, []int64) int64 {
|
||||
func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.FunctionShape, resources []config.ResourceSpec) func([]int64, []int64, []int64) int64 {
|
||||
rawScoringFunction := helper.BuildBrokenLinearFunction(scoringFunctionShape)
|
||||
resourceScoringFunction := func(requested, capacity int64) int64 {
|
||||
if capacity == 0 || requested > capacity {
|
||||
|
|
@ -37,7 +37,7 @@ func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.Fun
|
|||
|
||||
return rawScoringFunction(requested * maxUtilization / capacity)
|
||||
}
|
||||
return func(requested, allocable []int64) int64 {
|
||||
return func(requested, _, allocable []int64) int64 {
|
||||
var nodeScore, weightSum int64
|
||||
for i := range requested {
|
||||
if allocable[i] == 0 {
|
||||
|
|
@ -57,7 +57,7 @@ func buildRequestedToCapacityRatioScorerFunction(scoringFunctionShape helper.Fun
|
|||
}
|
||||
}
|
||||
|
||||
func requestedToCapacityRatioScorer(resources []config.ResourceSpec, shape []config.UtilizationShapePoint) func([]int64, []int64) int64 {
|
||||
func requestedToCapacityRatioScorer(resources []config.ResourceSpec, shape []config.UtilizationShapePoint) func([]int64, []int64, []int64) int64 {
|
||||
shapes := make([]helper.FunctionShapePoint, 0, len(shape))
|
||||
for _, point := range shape {
|
||||
shapes = append(shapes, helper.FunctionShapePoint{
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ type resourceAllocationScorer struct {
|
|||
// used to decide whether to use Requested or NonZeroRequested for
|
||||
// cpu and memory.
|
||||
useRequested bool
|
||||
scorer func(requested, allocable []int64) int64
|
||||
scorer func(requested, allocated, allocatable []int64) int64
|
||||
resources []config.ResourceSpec
|
||||
draFeatures structured.Features
|
||||
draManager fwk.SharedDRAManager
|
||||
|
|
@ -150,19 +150,27 @@ func (r *resourceAllocationScorer) score(
|
|||
return 0, fwk.NewStatus(fwk.Error, "resources not found")
|
||||
}
|
||||
|
||||
allocated := make([]int64, len(r.resources))
|
||||
requested := make([]int64, len(r.resources))
|
||||
allocatable := make([]int64, len(r.resources))
|
||||
for i := range r.resources {
|
||||
alloc, req := r.calculateResourceAllocatableRequest(ctx, nodeInfo, v1.ResourceName(r.resources[i].Name), podRequests[i], draPreScoreState)
|
||||
// Only fill the extended resource entry when it's non-zero.
|
||||
if alloc == 0 {
|
||||
resource := v1.ResourceName(r.resources[i].Name)
|
||||
// If it's an extended resource, and the pod doesn't request it.
|
||||
// We don't fill the resource entry as an implication to bypass scoring on it.
|
||||
if podRequests[i] == 0 && schedutil.IsScalarResourceName(resource) {
|
||||
continue
|
||||
}
|
||||
allocatable[i] = alloc
|
||||
requested[i] = req
|
||||
nodeAllocatable, nodeAllocated := r.calculateResourceAllocatableRequest(ctx, nodeInfo, resource, draPreScoreState)
|
||||
// Only fill the extended resource entry when it's non-zero.
|
||||
if nodeAllocatable == 0 {
|
||||
continue
|
||||
}
|
||||
allocatable[i] = nodeAllocatable
|
||||
allocated[i] = nodeAllocated
|
||||
requested[i] = allocated[i] + podRequests[i]
|
||||
}
|
||||
|
||||
score := r.scorer(requested, allocatable)
|
||||
score := r.scorer(requested, allocated, allocatable)
|
||||
|
||||
if loggerV := logger.V(10); loggerV.Enabled() { // Serializing these maps is costly.
|
||||
loggerV.Info("Listed internal info for allocatable resources, requested resources and score", "pod",
|
||||
|
|
@ -177,12 +185,10 @@ func (r *resourceAllocationScorer) score(
|
|||
// calculateResourceAllocatableRequest returns 2 parameters:
|
||||
// - 1st param: quantity of allocatable 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.
|
||||
func (r *resourceAllocationScorer) calculateResourceAllocatableRequest(
|
||||
ctx context.Context,
|
||||
nodeInfo fwk.NodeInfo,
|
||||
resource v1.ResourceName,
|
||||
podRequest int64,
|
||||
draPreScoreState *draPreScoreState,
|
||||
) (int64, int64) {
|
||||
requested := nodeInfo.GetNonZeroRequested()
|
||||
|
|
@ -190,18 +196,13 @@ func (r *resourceAllocationScorer) calculateResourceAllocatableRequest(
|
|||
requested = nodeInfo.GetRequested()
|
||||
}
|
||||
|
||||
// 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.
|
||||
if podRequest == 0 && schedutil.IsScalarResourceName(resource) {
|
||||
return 0, 0
|
||||
}
|
||||
switch resource {
|
||||
case v1.ResourceCPU:
|
||||
return nodeInfo.GetAllocatable().GetMilliCPU(), (requested.GetMilliCPU() + podRequest)
|
||||
return nodeInfo.GetAllocatable().GetMilliCPU(), requested.GetMilliCPU()
|
||||
case v1.ResourceMemory:
|
||||
return nodeInfo.GetAllocatable().GetMemory(), (requested.GetMemory() + podRequest)
|
||||
return nodeInfo.GetAllocatable().GetMemory(), requested.GetMemory()
|
||||
case v1.ResourceEphemeralStorage:
|
||||
return nodeInfo.GetAllocatable().GetEphemeralStorage(), (nodeInfo.GetRequested().GetEphemeralStorage() + podRequest)
|
||||
return nodeInfo.GetAllocatable().GetEphemeralStorage(), nodeInfo.GetRequested().GetEphemeralStorage()
|
||||
default:
|
||||
allocatable, exists := nodeInfo.GetAllocatable().GetScalarResources()[resource]
|
||||
if allocatable == 0 && r.enableDRAExtendedResource && draPreScoreState != nil {
|
||||
|
|
@ -209,11 +210,11 @@ func (r *resourceAllocationScorer) calculateResourceAllocatableRequest(
|
|||
// Calculate allocatable and requested for resources backed by DRA.
|
||||
allocatable, allocated := r.calculateDRAExtendedResourceAllocatableRequest(ctx, nodeInfo.Node(), resource, draPreScoreState)
|
||||
if allocatable > 0 {
|
||||
return allocatable, allocated + podRequest
|
||||
return allocatable, allocated
|
||||
}
|
||||
}
|
||||
if exists {
|
||||
return allocatable, (nodeInfo.GetRequested().GetScalarResources()[resource] + podRequest)
|
||||
return allocatable, nodeInfo.GetRequested().GetScalarResources()[resource]
|
||||
}
|
||||
}
|
||||
klog.FromContext(ctx).V(10).Info("Requested resource is omitted for node score calculation", "resourceName", resource)
|
||||
|
|
|
|||
|
|
@ -286,44 +286,40 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
enableDRAExtendedResource bool
|
||||
node *v1.Node
|
||||
extendedResource v1.ResourceName
|
||||
objects []apiruntime.Object
|
||||
podRequest int64
|
||||
draObjects []apiruntime.Object
|
||||
expectedAllocatable int64
|
||||
expectedRequested int64
|
||||
expectedAllocated int64
|
||||
}{
|
||||
"device-plugin-resource-feature-disabled": {
|
||||
enableDRAExtendedResource: false,
|
||||
node: st.MakeNode().Name(nodeName).Capacity(map[v1.ResourceName]string{explicitExtendedResource: "4"}).Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 4,
|
||||
expectedRequested: 1,
|
||||
expectedAllocated: 0,
|
||||
},
|
||||
"device-plugin-resource-feature-enabled": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Capacity(map[v1.ResourceName]string{explicitExtendedResource: "4"}).Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 4,
|
||||
expectedRequested: 1,
|
||||
expectedAllocated: 0,
|
||||
},
|
||||
"DRA-backed-resource-explicit": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
objects: []apiruntime.Object{
|
||||
draObjects: []apiruntime.Object{
|
||||
deviceClassWithExtendResourceName,
|
||||
st.MakeResourceSlice(nodeName, driverName).Device("device-1").Obj(),
|
||||
},
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 1,
|
||||
expectedRequested: 1,
|
||||
expectedAllocatable: 1, // 1 device `device-1`
|
||||
expectedAllocated: 0,
|
||||
},
|
||||
"DRA-backed-resource-implicit": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Obj(),
|
||||
extendedResource: implicitExtendedResource,
|
||||
objects: []apiruntime.Object{
|
||||
draObjects: []apiruntime.Object{
|
||||
&resourceapi.DeviceClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: deviceClassName,
|
||||
|
|
@ -331,24 +327,24 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
},
|
||||
st.MakeResourceSlice(nodeName, driverName).Device("device-1").Obj(),
|
||||
},
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 1,
|
||||
expectedRequested: 1,
|
||||
expectedAllocatable: 1, // 1 device `device-1`
|
||||
expectedAllocated: 0,
|
||||
},
|
||||
"DRA-backed-resource-no-slices": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
objects: []apiruntime.Object{deviceClassWithExtendResourceName},
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 0,
|
||||
expectedRequested: 0,
|
||||
draObjects: []apiruntime.Object{
|
||||
deviceClassWithExtendResourceName,
|
||||
},
|
||||
expectedAllocatable: 0,
|
||||
expectedAllocated: 0,
|
||||
},
|
||||
"DRA-backed-resource-with-allocated-device": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
objects: []apiruntime.Object{
|
||||
draObjects: []apiruntime.Object{
|
||||
deviceClassWithExtendResourceName,
|
||||
st.MakeResourceSlice(nodeName, driverName).Devices("device-1", "device-2").Obj(),
|
||||
// Create a resource claim that fully allocates device-1
|
||||
|
|
@ -369,15 +365,14 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
}).
|
||||
Obj(),
|
||||
},
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 2,
|
||||
expectedRequested: 2, // 1 allocated + 1 requested
|
||||
expectedAllocatable: 2, // 2 allocatable devices `device-1`, `device-2`
|
||||
expectedAllocated: 1, // 1 allocated by `testClaim`
|
||||
},
|
||||
"DRA-backed-resource-with-shared-device-allocation": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
objects: []apiruntime.Object{
|
||||
draObjects: []apiruntime.Object{
|
||||
deviceClassWithExtendResourceName,
|
||||
st.MakeResourceSlice(nodeName, driverName).Devices("device-1", "device-2").Obj(),
|
||||
// Create a resource claim with shared device allocation (consumable capacity)
|
||||
|
|
@ -399,15 +394,14 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
}).
|
||||
Obj(),
|
||||
},
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 2,
|
||||
expectedRequested: 2, // 1 allocated (shared) + 1 requested
|
||||
expectedAllocatable: 2, // 2 allocatable devices `device-1`, `device-2`
|
||||
expectedAllocated: 1, // 1 allocated (shared) by `testClaim`
|
||||
},
|
||||
"DRA-backed-resource-multiple-devices-mixed-allocation": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
objects: []apiruntime.Object{
|
||||
draObjects: []apiruntime.Object{
|
||||
deviceClassWithExtendResourceName,
|
||||
st.MakeResourceSlice(nodeName, driverName).Devices("device-1", "device-2", "device-3").Obj(),
|
||||
// Mix of fully allocated and shared device allocations
|
||||
|
|
@ -447,15 +441,14 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
Obj(),
|
||||
// device-3 remains unallocated
|
||||
},
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 3,
|
||||
expectedRequested: 3, // 2 allocated (1 full + 1 shared) + 1 requested
|
||||
expectedAllocatable: 3, // 3 allocatable devices `device-1`, `device-2`, `device-3`
|
||||
expectedAllocated: 2, // 2 allocated (1 full `test-claim-1` + 1 shared `test-claim-2`)
|
||||
},
|
||||
"DRA-backed-resource-with-per-device-node-selection": {
|
||||
enableDRAExtendedResource: true,
|
||||
node: st.MakeNode().Name(nodeName).Label("zone", "us-east-1a").Obj(),
|
||||
extendedResource: explicitExtendedResource,
|
||||
objects: []apiruntime.Object{
|
||||
draObjects: []apiruntime.Object{
|
||||
&resourceapi.DeviceClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: deviceClassName,
|
||||
|
|
@ -545,9 +538,8 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
}).
|
||||
Obj(),
|
||||
},
|
||||
podRequest: 1,
|
||||
expectedAllocatable: 2, // device-1 matches the test node and device-3 matches via its selector
|
||||
expectedRequested: 2, // 1 allocated (device-1) + 1 requested
|
||||
expectedAllocated: 1, // 1 allocated (device-1)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -556,7 +548,7 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
// Setup environment, create required objects
|
||||
featuregatetesting.SetFeatureGateDuringTest(tCtx, utilfeature.DefaultFeatureGate, features.DRAExtendedResource, tc.enableDRAExtendedResource)
|
||||
|
||||
draManager := newTestDRAManager(tCtx, tc.objects...)
|
||||
draManager := newTestDRAManager(tCtx, tc.draObjects...)
|
||||
|
||||
nodeInfo := framework.NewNodeInfo()
|
||||
nodeInfo.SetNode(tc.node)
|
||||
|
|
@ -580,12 +572,12 @@ func testCalculateResourceAllocatableRequest(tCtx ktesting.TContext) {
|
|||
}
|
||||
|
||||
// Test calculateResourceAllocatableRequest API
|
||||
allocatable, requested := scorer.calculateResourceAllocatableRequest(tCtx, nodeInfo, tc.extendedResource, tc.podRequest, draPreScoreState)
|
||||
allocatable, allocated := scorer.calculateResourceAllocatableRequest(tCtx, nodeInfo, tc.extendedResource, draPreScoreState)
|
||||
if !cmp.Equal(allocatable, tc.expectedAllocatable) {
|
||||
tCtx.Errorf("Expected allocatable=%v, but got allocatable=%v", tc.expectedAllocatable, allocatable)
|
||||
tCtx.Errorf("Expected allocatable=%v, but got %v", tc.expectedAllocatable, allocatable)
|
||||
}
|
||||
if !cmp.Equal(requested, tc.expectedRequested) {
|
||||
tCtx.Errorf("Expected requested=%v, but got requested=%v", tc.expectedRequested, requested)
|
||||
if !cmp.Equal(allocated, tc.expectedAllocated) {
|
||||
tCtx.Errorf("Expected allocated=%v, but got %v", tc.expectedAllocated, allocated)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3747,156 +3747,6 @@ func TestFindFitPredicateCallCounts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// The point of this test is to show that you:
|
||||
// - get the same priority for a zero-request pod as for a pod with the defaults requests,
|
||||
// both when the zero-request pod is already on the node and when the zero-request pod
|
||||
// is the one being scheduled.
|
||||
// - don't get the same score no matter what we schedule.
|
||||
func TestZeroRequest(t *testing.T) {
|
||||
// A pod with no resources. We expect spreading to count it as having the default resources.
|
||||
noResources := v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{},
|
||||
},
|
||||
}
|
||||
noResources1 := noResources
|
||||
noResources1.NodeName = "node1"
|
||||
// A pod with the same resources as a 0-request pod gets by default as its resources (for spreading).
|
||||
small := v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse(
|
||||
strconv.FormatInt(schedutil.DefaultMilliCPURequest, 10) + "m"),
|
||||
v1.ResourceMemory: resource.MustParse(
|
||||
strconv.FormatInt(schedutil.DefaultMemoryRequest, 10)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
small2 := small
|
||||
small2.NodeName = "node2"
|
||||
// A larger pod.
|
||||
large := v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceCPU: resource.MustParse(
|
||||
strconv.FormatInt(schedutil.DefaultMilliCPURequest*3, 10) + "m"),
|
||||
v1.ResourceMemory: resource.MustParse(
|
||||
strconv.FormatInt(schedutil.DefaultMemoryRequest*3, 10)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
large1 := large
|
||||
large1.NodeName = "node1"
|
||||
large2 := large
|
||||
large2.NodeName = "node2"
|
||||
tests := []struct {
|
||||
pod *v1.Pod
|
||||
pods []*v1.Pod
|
||||
nodes []*v1.Node
|
||||
name string
|
||||
expectedScore int64
|
||||
}{
|
||||
// The point of these next two tests is to show you get the same priority for a zero-request pod
|
||||
// as for a pod with the defaults requests, both when the zero-request pod is already on the node
|
||||
// and when the zero-request pod is the one being scheduled.
|
||||
{
|
||||
pod: &v1.Pod{Spec: noResources},
|
||||
nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)},
|
||||
name: "test priority of zero-request pod with node with zero-request pod",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: large1}, {Spec: noResources1},
|
||||
{Spec: large2}, {Spec: small2},
|
||||
},
|
||||
expectedScore: 50,
|
||||
},
|
||||
{
|
||||
pod: &v1.Pod{Spec: small},
|
||||
nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)},
|
||||
name: "test priority of nonzero-request pod with node with zero-request pod",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: large1}, {Spec: noResources1},
|
||||
{Spec: large2}, {Spec: small2},
|
||||
},
|
||||
expectedScore: 150,
|
||||
},
|
||||
// The point of this test is to verify that we're not just getting the same score no matter what we schedule.
|
||||
{
|
||||
pod: &v1.Pod{Spec: large},
|
||||
nodes: []*v1.Node{makeNode("node1", 1000, schedutil.DefaultMemoryRequest*10), makeNode("node2", 1000, schedutil.DefaultMemoryRequest*10)},
|
||||
name: "test priority of larger pod with node with zero-request pod",
|
||||
pods: []*v1.Pod{
|
||||
{Spec: large1}, {Spec: noResources1},
|
||||
{Spec: large2}, {Spec: small2},
|
||||
},
|
||||
expectedScore: 130,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
client := clientsetfake.NewClientset()
|
||||
informerFactory := informers.NewSharedInformerFactory(client, 0)
|
||||
|
||||
snapshot := internalcache.NewSnapshot(test.pods, test.nodes)
|
||||
fts := feature.Features{}
|
||||
pluginRegistrations := []tf.RegisterPluginFunc{
|
||||
tf.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
|
||||
tf.RegisterScorePlugin(noderesources.Name, frameworkruntime.FactoryAdapter(fts, noderesources.NewFit), 1),
|
||||
tf.RegisterScorePlugin(noderesources.BalancedAllocationName, frameworkruntime.FactoryAdapter(fts, noderesources.NewBalancedAllocation), 1),
|
||||
tf.RegisterBindPlugin(defaultbinder.Name, defaultbinder.New),
|
||||
}
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
fwk, err := tf.NewFramework(
|
||||
ctx,
|
||||
pluginRegistrations, "",
|
||||
frameworkruntime.WithInformerFactory(informerFactory),
|
||||
frameworkruntime.WithSnapshotSharedLister(snapshot),
|
||||
frameworkruntime.WithClientSet(client),
|
||||
frameworkruntime.WithPodNominator(internalqueue.NewSchedulingQueue(nil, informerFactory)),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating framework: %+v", err)
|
||||
}
|
||||
|
||||
sched := &Scheduler{
|
||||
nodeInfoSnapshot: snapshot,
|
||||
percentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore,
|
||||
}
|
||||
sched.applyDefaultHandlers()
|
||||
|
||||
state := framework.NewCycleState()
|
||||
_, _, _, _, err = sched.findNodesThatFitPod(ctx, fwk, state, test.pod)
|
||||
if err != nil {
|
||||
t.Fatalf("error filtering nodes: %+v", err)
|
||||
}
|
||||
nodeInfos, err := snapshot.NodeInfos().List()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list node from snapshot: %v", err)
|
||||
}
|
||||
fwk.RunPreScorePlugins(ctx, state, test.pod, nodeInfos)
|
||||
list, err := prioritizeNodes(ctx, nil, fwk, state, test.pod, nodeInfos)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
for _, hp := range list {
|
||||
if hp.TotalScore != test.expectedScore {
|
||||
t.Errorf("expected %d for all priorities, got list %#v", test.expectedScore, list)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_prioritizeNodes(t *testing.T) {
|
||||
imageStatus1 := []v1.ContainerImage{
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue