k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/noderesources/most_allocated_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package noderesources 18 19 import ( 20 "context" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 v1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/util/validation/field" 26 "k8s.io/klog/v2/ktesting" 27 "k8s.io/kubernetes/pkg/scheduler/apis/config" 28 "k8s.io/kubernetes/pkg/scheduler/framework" 29 plfeature "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature" 30 "k8s.io/kubernetes/pkg/scheduler/framework/runtime" 31 "k8s.io/kubernetes/pkg/scheduler/internal/cache" 32 st "k8s.io/kubernetes/pkg/scheduler/testing" 33 tf "k8s.io/kubernetes/pkg/scheduler/testing/framework" 34 ) 35 36 func TestMostAllocatedScoringStrategy(t *testing.T) { 37 tests := []struct { 38 name string 39 requestedPod *v1.Pod 40 nodes []*v1.Node 41 existingPods []*v1.Pod 42 expectedScores framework.NodeScoreList 43 resources []config.ResourceSpec 44 wantErrs field.ErrorList 45 wantStatusCode framework.Code 46 }{ 47 { 48 // Node1 scores (used resources) on 0-MaxNodeScore scale 49 // CPU Score: (0 * MaxNodeScore) / 4000 = 0 50 // Memory Score: (0 * MaxNodeScore) / 10000 = 0 51 // Node1 Score: (0 + 0) / 2 = 0 52 // Node2 scores (used resources) on 0-MaxNodeScore scale 53 // CPU Score: (0 * MaxNodeScore) / 4000 = 0 54 // Memory Score: (0 * MaxNodeScore) / 10000 = 0 55 // Node2 Score: (0 + 0) / 2 = 0 56 name: "nothing scheduled, nothing requested", 57 requestedPod: st.MakePod().Obj(), 58 nodes: []*v1.Node{ 59 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 60 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 61 }, 62 existingPods: nil, 63 expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MinNodeScore}, {Name: "node2", Score: framework.MinNodeScore}}, 64 resources: defaultResources, 65 }, 66 { 67 // Node1 scores on 0-MaxNodeScore scale 68 // CPU Score: (3000 * MaxNodeScore) / 4000 = 75 69 // Memory Score: (5000 * MaxNodeScore) / 10000 = 50 70 // Node1 Score: (75 + 50) / 2 = 62 71 // Node2 scores on 0-MaxNodeScore scale 72 // CPU Score: (3000 * MaxNodeScore) / 6000 = 50 73 // Memory Score: (5000 * MaxNodeScore) / 10000 = 50 74 // Node2 Score: (50 + 50) / 2 = 50 75 name: "nothing scheduled, resources requested, differently sized nodes", 76 requestedPod: st.MakePod(). 77 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 78 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 79 Obj(), 80 nodes: []*v1.Node{ 81 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 82 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(), 83 }, 84 existingPods: nil, 85 expectedScores: []framework.NodeScore{{Name: "node1", Score: 62}, {Name: "node2", Score: 50}}, 86 resources: defaultResources, 87 }, 88 { 89 name: "Resources not set, pods scheduled with error", 90 requestedPod: st.MakePod(). 91 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 92 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 93 Obj(), 94 nodes: []*v1.Node{ 95 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 96 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(), 97 }, 98 existingPods: nil, 99 expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MinNodeScore}, {Name: "node2", Score: framework.MinNodeScore}}, 100 resources: nil, 101 wantStatusCode: framework.Error, 102 }, 103 { 104 // Node1 scores on 0-MaxNodeScore scale 105 // CPU Score: (6000 * MaxNodeScore) / 10000 = 60 106 // Memory Score: (0 * MaxNodeScore) / 20000 = 0 107 // Node1 Score: (60 + 0) / 2 = 30 108 // Node2 scores on 0-MaxNodeScore scale 109 // CPU Score: (6000 * MaxNodeScore) / 10000 = 60 110 // Memory Score: (5000 * MaxNodeScore) / 20000 = 25 111 // Node2 Score: (60 + 25) / 2 = 42 112 name: "no resources requested, pods scheduled with resources", 113 requestedPod: st.MakePod().Obj(), 114 nodes: []*v1.Node{ 115 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(), 116 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(), 117 }, 118 existingPods: []*v1.Pod{ 119 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(), 120 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(), 121 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(), 122 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(), 123 }, 124 expectedScores: []framework.NodeScore{{Name: "node1", Score: 30}, {Name: "node2", Score: 42}}, 125 resources: defaultResources, 126 }, 127 { 128 // Node1 scores on 0-MaxNodeScore scale 129 // CPU Score: (6000 * MaxNodeScore) / 10000 = 60 130 // Memory Score: (5000 * MaxNodeScore) / 20000 = 25 131 // Node1 Score: (60 + 25) / 2 = 42 132 // Node2 scores on 0-MaxNodeScore scale 133 // CPU Score: (6000 * MaxNodeScore) / 10000 = 60 134 // Memory Score: (10000 * MaxNodeScore) / 20000 = 50 135 // Node2 Score: (60 + 50) / 2 = 55 136 name: "resources requested, pods scheduled with resources", 137 requestedPod: st.MakePod(). 138 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 139 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 140 Obj(), 141 nodes: []*v1.Node{ 142 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(), 143 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(), 144 }, 145 existingPods: []*v1.Pod{ 146 st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(), 147 st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(), 148 }, 149 expectedScores: []framework.NodeScore{{Name: "node1", Score: 42}, {Name: "node2", Score: 55}}, 150 resources: defaultResources, 151 }, 152 { 153 // Node1 scores on 0-MaxNodeScore scale 154 // CPU Score: 5000 * MaxNodeScore / 5000 return 100 155 // Memory Score: (9000 * MaxNodeScore) / 10000 = 90 156 // Node1 Score: (100 + 90) / 2 = 95 157 // Node2 scores on 0-MaxNodeScore scale 158 // CPU Score: (5000 * MaxNodeScore) / 10000 = 50 159 // Memory Score: 9000 * MaxNodeScore / 9000 return 100 160 // Node2 Score: (50 + 100) / 2 = 75 161 name: "resources requested equal node capacity", 162 requestedPod: st.MakePod(). 163 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "4000"}). 164 Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}). 165 Obj(), 166 nodes: []*v1.Node{ 167 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "5000", "memory": "10000"}).Obj(), 168 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "9000"}).Obj(), 169 }, 170 existingPods: nil, 171 expectedScores: []framework.NodeScore{{Name: "node1", Score: 95}, {Name: "node2", Score: 75}}, 172 resources: defaultResources, 173 }, 174 { 175 // CPU Score: (3000 *100) / 4000 = 75 176 // Memory Score: (5000 *100) / 10000 = 50 177 // Node1 Score: (75 * 1 + 50 * 2) / (1 + 2) = 58 178 // CPU Score: (3000 *100) / 6000 = 50 179 // Memory Score: (5000 *100) / 10000 = 50 180 // Node2 Score: (50 * 1 + 50 * 2) / (1 + 2) = 50 181 name: "nothing scheduled, resources requested, differently sized nodes", 182 requestedPod: st.MakePod(). 183 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 184 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 185 Obj(), 186 nodes: []*v1.Node{ 187 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 188 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(), 189 }, 190 existingPods: nil, 191 expectedScores: []framework.NodeScore{{Name: "node1", Score: 58}, {Name: "node2", Score: 50}}, 192 resources: []config.ResourceSpec{ 193 {Name: "memory", Weight: 2}, 194 {Name: "cpu", Weight: 1}, 195 }, 196 }, 197 { 198 // Node1 scores on 0-MaxNodeScore scale 199 // CPU Fraction: 300 / 250 = 100% 200 // Memory Fraction: 600 / 1000 = 60% 201 // Node1 Score: (100 + 60) / 2 = 80 202 // Node2 scores on 0-MaxNodeScore scale 203 // CPU Fraction: 100 / 250 = 40% 204 // Memory Fraction: 200 / 1000 = 20% 205 // Node2 Score: (20 + 40) / 2 = 30 206 name: "no resources requested, pods scheduled, nonzero request for resource", 207 requestedPod: st.MakePod().Container("container").Obj(), 208 nodes: []*v1.Node{ 209 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "250m", "memory": "1000Mi"}).Obj(), 210 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "250m", "memory": "1000Mi"}).Obj(), 211 }, 212 existingPods: []*v1.Pod{ 213 st.MakePod().Node("node1").Container("container").Obj(), 214 st.MakePod().Node("node1").Container("container").Obj(), 215 }, 216 expectedScores: []framework.NodeScore{{Name: "node1", Score: 80}, {Name: "node2", Score: 30}}, 217 resources: defaultResources, 218 }, 219 { 220 // resource with negative weight is not allowed 221 name: "resource with negative weight", 222 requestedPod: st.MakePod(). 223 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 224 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 225 Obj(), 226 nodes: []*v1.Node{ 227 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 228 }, 229 resources: []config.ResourceSpec{ 230 {Name: "memory", Weight: -1}, 231 {Name: "cpu", Weight: 1}, 232 }, 233 wantErrs: field.ErrorList{ 234 &field.Error{ 235 Type: field.ErrorTypeInvalid, 236 Field: "scoringStrategy.resources[0].weight", 237 }, 238 }, 239 }, 240 { 241 // resource with zero weight is not allowed 242 name: "resource with zero weight", 243 requestedPod: st.MakePod(). 244 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 245 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 246 Obj(), 247 nodes: []*v1.Node{ 248 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 249 }, 250 resources: []config.ResourceSpec{ 251 {Name: "memory", Weight: 1}, 252 {Name: "cpu", Weight: 0}, 253 }, 254 wantErrs: field.ErrorList{ 255 &field.Error{ 256 Type: field.ErrorTypeInvalid, 257 Field: "scoringStrategy.resources[1].weight", 258 }, 259 }, 260 }, 261 { 262 // resource weight should be less than MaxNodeScore 263 name: "resource weight larger than MaxNodeScore", 264 requestedPod: st.MakePod(). 265 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 266 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 267 Obj(), 268 nodes: []*v1.Node{ 269 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(), 270 }, 271 resources: []config.ResourceSpec{ 272 {Name: "memory", Weight: 101}, 273 }, 274 wantErrs: field.ErrorList{ 275 &field.Error{ 276 Type: field.ErrorTypeInvalid, 277 Field: "scoringStrategy.resources[0].weight", 278 }, 279 }, 280 }, 281 { 282 // Bypass extended resource if the pod does not request. 283 // For both nodes: cpuScore and memScore are 50 284 // Given that extended resource score are intentionally bypassed, 285 // the final scores are: 286 // - node1: (50 + 50) / 2 = 50 287 // - node2: (50 + 50) / 2 = 50 288 name: "bypass extended resource if the pod does not request", 289 requestedPod: st.MakePod(). 290 Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}). 291 Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}). 292 Obj(), 293 nodes: []*v1.Node{ 294 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(), 295 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(), 296 }, 297 resources: extendedResourceSet, 298 existingPods: nil, 299 expectedScores: []framework.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 50}}, 300 }, 301 { 302 // Honor extended resource if the pod requests. 303 // For both nodes: cpuScore and memScore are 50. 304 // In terms of extended resource score: 305 // - node1 get: 2 / 4 * 100 = 50 306 // - node2 get: 2 / 10 * 100 = 20 307 // So the final scores are: 308 // - node1: (50 + 50 + 50) / 3 = 50 309 // - node2: (50 + 50 + 20) / 3 = 40 310 name: "honor extended resource if the pod request", 311 requestedPod: st.MakePod().Node("node1"). 312 Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000", v1.ResourceName(extendedRes): "2"}). 313 Obj(), 314 nodes: []*v1.Node{ 315 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(), 316 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "10"}).Obj(), 317 }, 318 resources: extendedResourceSet, 319 existingPods: nil, 320 expectedScores: []framework.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 40}}, 321 }, 322 { 323 // If the node doesn't have a resource 324 // CPU Score: (3000 * 100) / 6000 = 50 325 // Memory Score: (4000 * 100) / 10000 = 40 326 // Node1 Score: (50 * 1 + 40 * 1) / (1 + 1) = 45 327 // Node2 Score: (50 * 1 + 40 * 1) / (1 + 1) = 45 328 name: "if the node doesn't have a resource", 329 requestedPod: st.MakePod().Node("node1"). 330 Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "4000"}). 331 Obj(), 332 nodes: []*v1.Node{ 333 st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(), 334 st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(), 335 }, 336 expectedScores: []framework.NodeScore{{Name: "node1", Score: 45}, {Name: "node2", Score: 45}}, 337 resources: []config.ResourceSpec{ 338 {Name: extendedRes, Weight: 2}, 339 {Name: string(v1.ResourceCPU), Weight: 1}, 340 {Name: string(v1.ResourceMemory), Weight: 1}, 341 }, 342 }, 343 } 344 345 for _, test := range tests { 346 t.Run(test.name, func(t *testing.T) { 347 _, ctx := ktesting.NewTestContext(t) 348 ctx, cancel := context.WithCancel(ctx) 349 defer cancel() 350 351 state := framework.NewCycleState() 352 snapshot := cache.NewSnapshot(test.existingPods, test.nodes) 353 fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot)) 354 355 p, err := NewFit(ctx, 356 &config.NodeResourcesFitArgs{ 357 ScoringStrategy: &config.ScoringStrategy{ 358 Type: config.MostAllocated, 359 Resources: test.resources, 360 }, 361 }, fh, plfeature.Features{}) 362 363 if diff := cmp.Diff(test.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" { 364 t.Fatalf("got err (-want,+got):\n%s", diff) 365 } 366 if err != nil { 367 return 368 } 369 370 status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes)) 371 if !status.IsSuccess() { 372 t.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status) 373 } 374 375 var gotScores framework.NodeScoreList 376 for _, n := range test.nodes { 377 score, status := p.(framework.ScorePlugin).Score(ctx, state, test.requestedPod, n.Name) 378 if status.Code() != test.wantStatusCode { 379 t.Errorf("unexpected status code, want: %v, got: %v", test.wantStatusCode, status.Code()) 380 } 381 gotScores = append(gotScores, framework.NodeScore{Name: n.Name, Score: score}) 382 } 383 384 if diff := cmp.Diff(test.expectedScores, gotScores); diff != "" { 385 t.Errorf("Unexpected scores (-want,+got):\n%s", diff) 386 } 387 }) 388 } 389 }