k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/interpodaffinity/scoring_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 interpodaffinity 18 19 import ( 20 "context" 21 "reflect" 22 "strings" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/klog/v2/ktesting" 30 "k8s.io/kubernetes/pkg/scheduler/apis/config" 31 "k8s.io/kubernetes/pkg/scheduler/framework" 32 plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing" 33 "k8s.io/kubernetes/pkg/scheduler/internal/cache" 34 tf "k8s.io/kubernetes/pkg/scheduler/testing/framework" 35 ) 36 37 var nsLabelT1 = map[string]string{"team": "team1"} 38 var nsLabelT2 = map[string]string{"team": "team2"} 39 var namespaces = []runtime.Object{ 40 &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "subteam1.team1", Labels: nsLabelT1}}, 41 &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "subteam2.team1", Labels: nsLabelT1}}, 42 &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "subteam1.team2", Labels: nsLabelT2}}, 43 &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "subteam2.team2", Labels: nsLabelT2}}, 44 } 45 46 func TestPreferredAffinity(t *testing.T) { 47 labelRgChina := map[string]string{ 48 "region": "China", 49 } 50 labelRgIndia := map[string]string{ 51 "region": "India", 52 } 53 labelAzAz1 := map[string]string{ 54 "az": "az1", 55 } 56 labelAzAz2 := map[string]string{ 57 "az": "az2", 58 } 59 labelRgChinaAzAz1 := map[string]string{ 60 "region": "China", 61 "az": "az1", 62 } 63 podLabelSecurityS1 := map[string]string{ 64 "security": "S1", 65 } 66 podLabelSecurityS2 := map[string]string{ 67 "security": "S2", 68 } 69 // considered only preferredDuringSchedulingIgnoredDuringExecution in pod affinity 70 stayWithS1InRegion := &v1.Affinity{ 71 PodAffinity: &v1.PodAffinity{ 72 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 73 { 74 Weight: 5, 75 PodAffinityTerm: v1.PodAffinityTerm{ 76 LabelSelector: &metav1.LabelSelector{ 77 MatchExpressions: []metav1.LabelSelectorRequirement{ 78 { 79 Key: "security", 80 Operator: metav1.LabelSelectorOpIn, 81 Values: []string{"S1"}, 82 }, 83 }, 84 }, 85 TopologyKey: "region", 86 }, 87 }, 88 }, 89 }, 90 } 91 stayWithS2InRegion := &v1.Affinity{ 92 PodAffinity: &v1.PodAffinity{ 93 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 94 { 95 Weight: 6, 96 PodAffinityTerm: v1.PodAffinityTerm{ 97 LabelSelector: &metav1.LabelSelector{ 98 MatchExpressions: []metav1.LabelSelectorRequirement{ 99 { 100 Key: "security", 101 Operator: metav1.LabelSelectorOpIn, 102 Values: []string{"S2"}, 103 }, 104 }, 105 }, 106 TopologyKey: "region", 107 }, 108 }, 109 }, 110 }, 111 } 112 affinity3 := &v1.Affinity{ 113 PodAffinity: &v1.PodAffinity{ 114 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 115 { 116 Weight: 8, 117 PodAffinityTerm: v1.PodAffinityTerm{ 118 LabelSelector: &metav1.LabelSelector{ 119 MatchExpressions: []metav1.LabelSelectorRequirement{ 120 { 121 Key: "security", 122 Operator: metav1.LabelSelectorOpNotIn, 123 Values: []string{"S1"}, 124 }, { 125 Key: "security", 126 Operator: metav1.LabelSelectorOpIn, 127 Values: []string{"S2"}, 128 }, 129 }, 130 }, 131 TopologyKey: "region", 132 }, 133 }, { 134 Weight: 2, 135 PodAffinityTerm: v1.PodAffinityTerm{ 136 LabelSelector: &metav1.LabelSelector{ 137 MatchExpressions: []metav1.LabelSelectorRequirement{ 138 { 139 Key: "security", 140 Operator: metav1.LabelSelectorOpExists, 141 }, { 142 Key: "wrongkey", 143 Operator: metav1.LabelSelectorOpDoesNotExist, 144 }, 145 }, 146 }, 147 TopologyKey: "region", 148 }, 149 }, 150 }, 151 }, 152 } 153 hardAffinity := &v1.Affinity{ 154 PodAffinity: &v1.PodAffinity{ 155 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 156 { 157 LabelSelector: &metav1.LabelSelector{ 158 MatchExpressions: []metav1.LabelSelectorRequirement{ 159 { 160 Key: "security", 161 Operator: metav1.LabelSelectorOpIn, 162 Values: []string{"S1", "value2"}, 163 }, 164 }, 165 }, 166 TopologyKey: "region", 167 }, { 168 LabelSelector: &metav1.LabelSelector{ 169 MatchExpressions: []metav1.LabelSelectorRequirement{ 170 { 171 Key: "security", 172 Operator: metav1.LabelSelectorOpExists, 173 }, { 174 Key: "wrongkey", 175 Operator: metav1.LabelSelectorOpDoesNotExist, 176 }, 177 }, 178 }, 179 TopologyKey: "region", 180 }, 181 }, 182 }, 183 } 184 awayFromS1InAz := &v1.Affinity{ 185 PodAntiAffinity: &v1.PodAntiAffinity{ 186 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 187 { 188 Weight: 5, 189 PodAffinityTerm: v1.PodAffinityTerm{ 190 LabelSelector: &metav1.LabelSelector{ 191 MatchExpressions: []metav1.LabelSelectorRequirement{ 192 { 193 Key: "security", 194 Operator: metav1.LabelSelectorOpIn, 195 Values: []string{"S1"}, 196 }, 197 }, 198 }, 199 TopologyKey: "az", 200 }, 201 }, 202 }, 203 }, 204 } 205 // to stay away from security S2 in any az. 206 awayFromS2InAz := &v1.Affinity{ 207 PodAntiAffinity: &v1.PodAntiAffinity{ 208 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 209 { 210 Weight: 5, 211 PodAffinityTerm: v1.PodAffinityTerm{ 212 LabelSelector: &metav1.LabelSelector{ 213 MatchExpressions: []metav1.LabelSelectorRequirement{ 214 { 215 Key: "security", 216 Operator: metav1.LabelSelectorOpIn, 217 Values: []string{"S2"}, 218 }, 219 }, 220 }, 221 TopologyKey: "az", 222 }, 223 }, 224 }, 225 }, 226 } 227 // to stay with security S1 in same region, stay away from security S2 in any az. 228 stayWithS1InRegionAwayFromS2InAz := &v1.Affinity{ 229 PodAffinity: &v1.PodAffinity{ 230 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 231 { 232 Weight: 8, 233 PodAffinityTerm: v1.PodAffinityTerm{ 234 LabelSelector: &metav1.LabelSelector{ 235 MatchExpressions: []metav1.LabelSelectorRequirement{ 236 { 237 Key: "security", 238 Operator: metav1.LabelSelectorOpIn, 239 Values: []string{"S1"}, 240 }, 241 }, 242 }, 243 TopologyKey: "region", 244 }, 245 }, 246 }, 247 }, 248 PodAntiAffinity: &v1.PodAntiAffinity{ 249 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 250 { 251 Weight: 5, 252 PodAffinityTerm: v1.PodAffinityTerm{ 253 LabelSelector: &metav1.LabelSelector{ 254 MatchExpressions: []metav1.LabelSelectorRequirement{ 255 { 256 Key: "security", 257 Operator: metav1.LabelSelectorOpIn, 258 Values: []string{"S2"}, 259 }, 260 }, 261 }, 262 TopologyKey: "az", 263 }, 264 }, 265 }, 266 }, 267 } 268 269 affinityNamespaceSelector := &v1.Affinity{ 270 PodAffinity: &v1.PodAffinity{ 271 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 272 { 273 Weight: 5, 274 PodAffinityTerm: v1.PodAffinityTerm{ 275 LabelSelector: &metav1.LabelSelector{ 276 MatchExpressions: []metav1.LabelSelectorRequirement{ 277 { 278 Key: "security", 279 Operator: metav1.LabelSelectorOpIn, 280 Values: []string{"S1"}, 281 }, 282 }, 283 }, 284 TopologyKey: "region", 285 Namespaces: []string{"subteam2.team2"}, 286 NamespaceSelector: &metav1.LabelSelector{ 287 MatchExpressions: []metav1.LabelSelectorRequirement{ 288 { 289 Key: "team", 290 Operator: metav1.LabelSelectorOpIn, 291 Values: []string{"team1"}, 292 }, 293 }, 294 }, 295 }, 296 }, 297 }, 298 }, 299 } 300 antiAffinityNamespaceSelector := &v1.Affinity{ 301 PodAntiAffinity: &v1.PodAntiAffinity{ 302 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 303 { 304 Weight: 5, 305 PodAffinityTerm: v1.PodAffinityTerm{ 306 LabelSelector: &metav1.LabelSelector{ 307 MatchExpressions: []metav1.LabelSelectorRequirement{ 308 { 309 Key: "security", 310 Operator: metav1.LabelSelectorOpIn, 311 Values: []string{"S1"}, 312 }, 313 }, 314 }, 315 TopologyKey: "region", 316 Namespaces: []string{"subteam2.team2"}, 317 NamespaceSelector: &metav1.LabelSelector{ 318 MatchExpressions: []metav1.LabelSelectorRequirement{ 319 { 320 Key: "team", 321 Operator: metav1.LabelSelectorOpIn, 322 Values: []string{"team1"}, 323 }, 324 }, 325 }, 326 }, 327 }, 328 }, 329 }, 330 } 331 invalidAffinityLabels := &v1.Affinity{ 332 PodAffinity: &v1.PodAffinity{ 333 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 334 { 335 Weight: 8, 336 PodAffinityTerm: v1.PodAffinityTerm{ 337 LabelSelector: &metav1.LabelSelector{ 338 MatchExpressions: []metav1.LabelSelectorRequirement{ 339 { 340 Key: "security", 341 Operator: metav1.LabelSelectorOpIn, 342 Values: []string{"{{.bad-value.}}"}, 343 }, 344 }, 345 }, 346 TopologyKey: "region", 347 }, 348 }, 349 }, 350 }, 351 } 352 invalidAntiAffinityLabels := &v1.Affinity{ 353 PodAntiAffinity: &v1.PodAntiAffinity{ 354 PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{ 355 { 356 Weight: 5, 357 PodAffinityTerm: v1.PodAffinityTerm{ 358 LabelSelector: &metav1.LabelSelector{ 359 MatchExpressions: []metav1.LabelSelectorRequirement{ 360 { 361 Key: "security", 362 Operator: metav1.LabelSelectorOpIn, 363 Values: []string{"{{.bad-value.}}"}, 364 }, 365 }, 366 }, 367 TopologyKey: "az", 368 }, 369 }, 370 }, 371 }, 372 } 373 374 tests := []struct { 375 pod *v1.Pod 376 pods []*v1.Pod 377 nodes []*v1.Node 378 expectedList framework.NodeScoreList 379 name string 380 ignorePreferredTermsOfExistingPods bool 381 wantStatus *framework.Status 382 }{ 383 { 384 name: "all nodes are same priority as Affinity is nil", 385 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 386 nodes: []*v1.Node{ 387 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 388 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 389 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 390 }, 391 wantStatus: framework.NewStatus(framework.Skip), 392 }, 393 // the node(node1) that have the label {"region": "China"} (match the topology key) and that have existing pods that match the labelSelector get high score 394 // the node(node3) that don't have the label {"region": "whatever the value is"} (mismatch the topology key) but that have existing pods that match the labelSelector get low score 395 // the node(node2) that have the label {"region": "China"} (match the topology key) but that have existing pods that mismatch the labelSelector get low score 396 { 397 name: "Affinity: pod that matches topology key & pods in nodes will get high score comparing to others" + 398 "which doesn't match either pods in nodes or in topology key", 399 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: stayWithS1InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 400 pods: []*v1.Pod{ 401 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 402 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 403 {Spec: v1.PodSpec{NodeName: "node3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 404 }, 405 nodes: []*v1.Node{ 406 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 407 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 408 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 409 }, 410 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}, {Name: "node3", Score: 0}}, 411 }, 412 // the node1(node1) that have the label {"region": "China"} (match the topology key) and that have existing pods that match the labelSelector get high score 413 // the node2(node2) that have the label {"region": "China"}, match the topology key and have the same label value with node1, get the same high score with node1 414 // the node3(node3) that have the label {"region": "India"}, match the topology key but have a different label value, don't have existing pods that match the labelSelector, 415 // get a low score. 416 { 417 name: "All the nodes that have the same topology key & label value with one of them has an existing pod that match the affinity rules, have the same score", 418 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: stayWithS1InRegion}}, 419 pods: []*v1.Pod{ 420 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 421 }, 422 nodes: []*v1.Node{ 423 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 424 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgChinaAzAz1}}, 425 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelRgIndia}}, 426 }, 427 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: 0}}, 428 }, 429 // there are 2 regions, say regionChina(node1,node3,node4) and regionIndia(node2,node5), both regions have nodes that match the preference. 430 // But there are more nodes(actually more existing pods) in regionChina that match the preference than regionIndia. 431 // Then, nodes in regionChina get higher score than nodes in regionIndia, and all the nodes in regionChina should get a same score(high score), 432 // while all the nodes in regionIndia should get another same score(low score). 433 { 434 name: "Affinity: nodes in one region has more matching pods comparing to other region, so the region which has more matches will get high score", 435 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 436 pods: []*v1.Pod{ 437 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 438 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 439 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 440 {Spec: v1.PodSpec{NodeName: "node3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 441 {Spec: v1.PodSpec{NodeName: "node4"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 442 {Spec: v1.PodSpec{NodeName: "node5"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 443 }, 444 nodes: []*v1.Node{ 445 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 446 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 447 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelRgChina}}, 448 {ObjectMeta: metav1.ObjectMeta{Name: "node4", Labels: labelRgChina}}, 449 {ObjectMeta: metav1.ObjectMeta{Name: "node5", Labels: labelRgIndia}}, 450 }, 451 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}, {Name: "node3", Score: framework.MaxNodeScore}, {Name: "node4", Score: framework.MaxNodeScore}, {Name: "node5", Score: 0}}, 452 }, 453 // Test with the different operators and values for pod affinity scheduling preference, including some match failures. 454 { 455 name: "Affinity: different Label operators and values for pod affinity scheduling preference, including some match failures ", 456 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: affinity3}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 457 pods: []*v1.Pod{ 458 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 459 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 460 {Spec: v1.PodSpec{NodeName: "node3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 461 }, 462 nodes: []*v1.Node{ 463 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 464 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 465 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 466 }, 467 expectedList: []framework.NodeScore{{Name: "node1", Score: 20}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: 0}}, 468 }, 469 // Test the symmetry cases for affinity, the difference between affinity and symmetry is not the pod wants to run together with some existing pods, 470 // but the existing pods have the inter pod affinity preference while the pod to schedule satisfy the preference. 471 { 472 name: "Affinity symmetry: considered only the preferredDuringSchedulingIgnoredDuringExecution in pod affinity symmetry", 473 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 474 pods: []*v1.Pod{ 475 {Spec: v1.PodSpec{NodeName: "node1", Affinity: stayWithS1InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 476 {Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 477 }, 478 nodes: []*v1.Node{ 479 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 480 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 481 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 482 }, 483 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: 0}}, 484 }, 485 { 486 name: "Affinity symmetry with namespace selector", 487 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 488 pods: []*v1.Pod{ 489 {Spec: v1.PodSpec{NodeName: "node1", Affinity: affinityNamespaceSelector}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 490 {Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 491 }, 492 nodes: []*v1.Node{ 493 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 494 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 495 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 496 }, 497 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}, {Name: "node3", Score: 0}}, 498 }, 499 { 500 name: "AntiAffinity symmetry with namespace selector", 501 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 502 pods: []*v1.Pod{ 503 {Spec: v1.PodSpec{NodeName: "node1", Affinity: antiAffinityNamespaceSelector}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 504 {Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 505 }, 506 nodes: []*v1.Node{ 507 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 508 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 509 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 510 }, 511 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: framework.MaxNodeScore}}, 512 }, 513 { 514 name: "Affinity symmetry: considered RequiredDuringSchedulingIgnoredDuringExecution in pod affinity symmetry", 515 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 516 pods: []*v1.Pod{ 517 {Spec: v1.PodSpec{NodeName: "node1", Affinity: hardAffinity}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 518 {Spec: v1.PodSpec{NodeName: "node2", Affinity: hardAffinity}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 519 }, 520 nodes: []*v1.Node{ 521 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 522 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 523 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 524 }, 525 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: 0}}, 526 }, 527 528 // The pod to schedule prefer to stay away from some existing pods at node level using the pod anti affinity. 529 // the nodes that have the label {"node": "bar"} (match the topology key) and that have existing pods that match the labelSelector get low score 530 // the nodes that don't have the label {"node": "whatever the value is"} (mismatch the topology key) but that have existing pods that match the labelSelector get high score 531 // the nodes that have the label {"node": "bar"} (match the topology key) but that have existing pods that mismatch the labelSelector get high score 532 // there are 2 nodes, say node1 and node2, both nodes have pods that match the labelSelector and have topology-key in node.Labels. 533 // But there are more pods on node1 that match the preference than node2. Then, node1 get a lower score than node2. 534 { 535 name: "Anti Affinity: pod that does not match existing pods in node will get high score ", 536 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: awayFromS1InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 537 pods: []*v1.Pod{ 538 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 539 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 540 }, 541 nodes: []*v1.Node{ 542 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelAzAz1}}, 543 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgChina}}, 544 }, 545 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}}, 546 }, 547 { 548 name: "Anti Affinity: pod that does not match topology key & match the pods in nodes will get higher score comparing to others ", 549 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: awayFromS1InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 550 pods: []*v1.Pod{ 551 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 552 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 553 }, 554 nodes: []*v1.Node{ 555 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelAzAz1}}, 556 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgChina}}, 557 }, 558 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}}, 559 }, 560 { 561 name: "Anti Affinity: one node has more matching pods comparing to other node, so the node which has more unmatches will get high score", 562 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: awayFromS1InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 563 pods: []*v1.Pod{ 564 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 565 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 566 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 567 }, 568 nodes: []*v1.Node{ 569 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelAzAz1}}, 570 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 571 }, 572 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}}, 573 }, 574 // Test the symmetry cases for anti affinity 575 { 576 name: "Anti Affinity symmetry: the existing pods in node which has anti affinity match will get high score", 577 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 578 pods: []*v1.Pod{ 579 {Spec: v1.PodSpec{NodeName: "node1", Affinity: awayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 580 {Spec: v1.PodSpec{NodeName: "node2", Affinity: awayFromS1InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 581 }, 582 nodes: []*v1.Node{ 583 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelAzAz1}}, 584 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelAzAz2}}, 585 }, 586 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}}, 587 }, 588 // Test both affinity and anti-affinity 589 { 590 name: "Affinity and Anti Affinity: considered only preferredDuringSchedulingIgnoredDuringExecution in both pod affinity & anti affinity", 591 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: stayWithS1InRegionAwayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 592 pods: []*v1.Pod{ 593 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 594 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 595 }, 596 nodes: []*v1.Node{ 597 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 598 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelAzAz1}}, 599 }, 600 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}}, 601 }, 602 // Combined cases considering both affinity and anti-affinity, the pod to schedule and existing pods have the same labels (they are in the same RC/service), 603 // the pod prefer to run together with its brother pods in the same region, but wants to stay away from them at node level, 604 // so that all the pods of a RC/service can stay in a same region but trying to separate with each other 605 // node-1,node-3,node-4 are in ChinaRegion others node-2,node-5 are in IndiaRegion 606 { 607 name: "Affinity and Anti Affinity: considering both affinity and anti-affinity, the pod to schedule and existing pods have the same labels", 608 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: stayWithS1InRegionAwayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 609 pods: []*v1.Pod{ 610 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 611 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 612 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 613 {Spec: v1.PodSpec{NodeName: "node3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 614 {Spec: v1.PodSpec{NodeName: "node3"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 615 {Spec: v1.PodSpec{NodeName: "node4"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 616 {Spec: v1.PodSpec{NodeName: "node5"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 617 }, 618 nodes: []*v1.Node{ 619 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChinaAzAz1}}, 620 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 621 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelRgChina}}, 622 {ObjectMeta: metav1.ObjectMeta{Name: "node4", Labels: labelRgChina}}, 623 {ObjectMeta: metav1.ObjectMeta{Name: "node5", Labels: labelRgIndia}}, 624 }, 625 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}, {Name: "node3", Score: framework.MaxNodeScore}, {Name: "node4", Score: framework.MaxNodeScore}, {Name: "node5", Score: 0}}, 626 }, 627 // Consider Affinity, Anti Affinity and symmetry together. 628 // for Affinity, the weights are: 8, 0, 0, 0 629 // for Anti Affinity, the weights are: 0, -5, 0, 0 630 // for Affinity symmetry, the weights are: 0, 0, 8, 0 631 // for Anti Affinity symmetry, the weights are: 0, 0, 0, -5 632 { 633 name: "Affinity and Anti Affinity and symmetry: considered only preferredDuringSchedulingIgnoredDuringExecution in both pod affinity & anti affinity & symmetry", 634 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: stayWithS1InRegionAwayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 635 pods: []*v1.Pod{ 636 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 637 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 638 {Spec: v1.PodSpec{NodeName: "node3", Affinity: stayWithS1InRegionAwayFromS2InAz}}, 639 {Spec: v1.PodSpec{NodeName: "node4", Affinity: awayFromS1InAz}}, 640 }, 641 nodes: []*v1.Node{ 642 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 643 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelAzAz1}}, 644 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelRgIndia}}, 645 {ObjectMeta: metav1.ObjectMeta{Name: "node4", Labels: labelAzAz2}}, 646 }, 647 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}, {Name: "node3", Score: framework.MaxNodeScore}, {Name: "node4", Score: 0}}, 648 }, 649 // Cover https://github.com/kubernetes/kubernetes/issues/82796 which panics upon: 650 // 1. Some nodes in a topology don't have pods with affinity, but other nodes in the same topology have. 651 // 2. The incoming pod doesn't have affinity. 652 { 653 name: "Avoid panic when partial nodes in a topology don't have pods with affinity", 654 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 655 pods: []*v1.Pod{ 656 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 657 {Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS1InRegionAwayFromS2InAz}}, 658 }, 659 nodes: []*v1.Node{ 660 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 661 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgChina}}, 662 }, 663 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: 0}}, 664 }, 665 { 666 name: "invalid Affinity fails PreScore", 667 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: invalidAffinityLabels}}, 668 wantStatus: framework.NewStatus(framework.Error, `Invalid value: "{{.bad-value.}}"`), 669 nodes: []*v1.Node{ 670 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 671 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgChina}}, 672 }, 673 }, 674 { 675 name: "invalid AntiAffinity fails PreScore", 676 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: "", Affinity: invalidAntiAffinityLabels}}, 677 wantStatus: framework.NewStatus(framework.Error, `Invalid value: "{{.bad-value.}}"`), 678 nodes: []*v1.Node{ 679 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 680 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgChina}}, 681 }, 682 }, 683 { 684 name: "Affinity with pods matching NamespaceSelector", 685 pod: &v1.Pod{Spec: v1.PodSpec{Affinity: affinityNamespaceSelector}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 686 pods: []*v1.Pod{ 687 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 688 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 689 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team2", Labels: podLabelSecurityS1}}, 690 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam2.team1", Labels: podLabelSecurityS1}}, 691 }, 692 nodes: []*v1.Node{ 693 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 694 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 695 }, 696 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}}, 697 }, 698 { 699 name: "Affinity with pods matching both NamespaceSelector and Namespaces fields", 700 pod: &v1.Pod{Spec: v1.PodSpec{Affinity: affinityNamespaceSelector}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 701 pods: []*v1.Pod{ 702 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 703 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 704 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam2.team2", Labels: podLabelSecurityS1}}, 705 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam2.team1", Labels: podLabelSecurityS1}}, 706 }, 707 nodes: []*v1.Node{ 708 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 709 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 710 }, 711 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}}, 712 }, 713 { 714 name: "Affinity with pods matching NamespaceSelector", 715 pod: &v1.Pod{Spec: v1.PodSpec{Affinity: antiAffinityNamespaceSelector}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 716 pods: []*v1.Pod{ 717 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 718 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 719 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team2", Labels: podLabelSecurityS1}}, 720 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam2.team1", Labels: podLabelSecurityS1}}, 721 }, 722 nodes: []*v1.Node{ 723 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 724 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 725 }, 726 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}}, 727 }, 728 { 729 name: "Affinity with pods matching both NamespaceSelector and Namespaces fields", 730 pod: &v1.Pod{Spec: v1.PodSpec{Affinity: antiAffinityNamespaceSelector}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 731 pods: []*v1.Pod{ 732 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 733 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelSecurityS1}}, 734 {Spec: v1.PodSpec{NodeName: "node1"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam2.team2", Labels: podLabelSecurityS1}}, 735 {Spec: v1.PodSpec{NodeName: "node2"}, ObjectMeta: metav1.ObjectMeta{Namespace: "subteam2.team1", Labels: podLabelSecurityS1}}, 736 }, 737 nodes: []*v1.Node{ 738 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 739 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 740 }, 741 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}}, 742 }, 743 { 744 name: "Ignore preferred terms of existing pods", 745 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 746 pods: []*v1.Pod{ 747 {Spec: v1.PodSpec{NodeName: "node1", Affinity: stayWithS1InRegionAwayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 748 {Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 749 }, 750 nodes: []*v1.Node{ 751 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 752 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 753 }, 754 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: 0}}, 755 wantStatus: framework.NewStatus(framework.Skip), 756 ignorePreferredTermsOfExistingPods: true, 757 }, 758 { 759 name: "Do not ignore preferred terms of existing pods", 760 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 761 pods: []*v1.Pod{ 762 {Spec: v1.PodSpec{NodeName: "node1", Affinity: stayWithS1InRegionAwayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}}, 763 {Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 764 }, 765 nodes: []*v1.Node{ 766 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 767 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 768 }, 769 expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}}, 770 ignorePreferredTermsOfExistingPods: false, 771 }, 772 { 773 name: "No nodes to score", 774 pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}}, 775 pods: []*v1.Pod{}, 776 nodes: []*v1.Node{}, 777 wantStatus: framework.NewStatus(framework.Skip), 778 }, 779 } 780 for _, test := range tests { 781 t.Run(test.name, func(t *testing.T) { 782 _, ctx := ktesting.NewTestContext(t) 783 ctx, cancel := context.WithCancel(ctx) 784 defer cancel() 785 state := framework.NewCycleState() 786 p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: 1, IgnorePreferredTermsOfExistingPods: test.ignorePreferredTermsOfExistingPods}, cache.NewSnapshot(test.pods, test.nodes), namespaces) 787 status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, tf.BuildNodeInfos(test.nodes)) 788 789 if !status.IsSuccess() { 790 if status.Code() != test.wantStatus.Code() { 791 t.Errorf("InterPodAffinity#PreScore() returned unexpected status.Code got: %v, want: %v", status.Code(), test.wantStatus.Code()) 792 } 793 794 if !strings.Contains(status.Message(), test.wantStatus.Message()) { 795 t.Errorf("InterPodAffinity#PreScore() returned unexpected status.Message got: %v, want: %v", status.Message(), test.wantStatus.Message()) 796 } 797 return 798 } 799 800 var gotList framework.NodeScoreList 801 for _, n := range test.nodes { 802 nodeName := n.ObjectMeta.Name 803 score, status := p.(framework.ScorePlugin).Score(ctx, state, test.pod, nodeName) 804 if !status.IsSuccess() { 805 t.Errorf("unexpected error from Score: %v", status) 806 } 807 gotList = append(gotList, framework.NodeScore{Name: nodeName, Score: score}) 808 } 809 810 status = p.(framework.ScorePlugin).ScoreExtensions().NormalizeScore(ctx, state, test.pod, gotList) 811 if !status.IsSuccess() { 812 t.Errorf("unexpected error from NormalizeScore: %v", status) 813 } 814 815 if diff := cmp.Diff(test.expectedList, gotList); diff != "" { 816 t.Errorf("node score list doesn't match (-want,+got): \n %s", diff) 817 } 818 }) 819 } 820 } 821 822 func TestPreferredAffinityWithHardPodAffinitySymmetricWeight(t *testing.T) { 823 podLabelServiceS1 := map[string]string{ 824 "service": "S1", 825 } 826 labelRgChina := map[string]string{ 827 "region": "China", 828 } 829 labelRgIndia := map[string]string{ 830 "region": "India", 831 } 832 labelAzAz1 := map[string]string{ 833 "az": "az1", 834 } 835 hardPodAffinity := &v1.Affinity{ 836 PodAffinity: &v1.PodAffinity{ 837 RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ 838 { 839 LabelSelector: &metav1.LabelSelector{ 840 MatchExpressions: []metav1.LabelSelectorRequirement{ 841 { 842 Key: "service", 843 Operator: metav1.LabelSelectorOpIn, 844 Values: []string{"S1"}, 845 }, 846 }, 847 }, 848 Namespaces: []string{"", "subteam2.team2"}, 849 NamespaceSelector: &metav1.LabelSelector{ 850 MatchExpressions: []metav1.LabelSelectorRequirement{ 851 { 852 Key: "team", 853 Operator: metav1.LabelSelectorOpIn, 854 Values: []string{"team1"}, 855 }, 856 }, 857 }, 858 TopologyKey: "region", 859 }, 860 }, 861 }, 862 } 863 tests := []struct { 864 pod *v1.Pod 865 pods []*v1.Pod 866 nodes []*v1.Node 867 hardPodAffinityWeight int32 868 expectedList framework.NodeScoreList 869 name string 870 wantStatus *framework.Status 871 }{ 872 { 873 name: "with default weight", 874 pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: podLabelServiceS1}}, 875 pods: []*v1.Pod{ 876 {Spec: v1.PodSpec{NodeName: "node1", Affinity: hardPodAffinity}}, 877 {Spec: v1.PodSpec{NodeName: "node2", Affinity: hardPodAffinity}}, 878 }, 879 nodes: []*v1.Node{ 880 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 881 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 882 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 883 }, 884 hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, 885 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: 0}}, 886 }, 887 { 888 name: "with zero weight", 889 pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Labels: podLabelServiceS1}}, 890 pods: []*v1.Pod{ 891 {Spec: v1.PodSpec{NodeName: "node1", Affinity: hardPodAffinity}}, 892 {Spec: v1.PodSpec{NodeName: "node2", Affinity: hardPodAffinity}}, 893 }, 894 nodes: []*v1.Node{ 895 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 896 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 897 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 898 }, 899 hardPodAffinityWeight: 0, 900 wantStatus: framework.NewStatus(framework.Skip), 901 }, 902 { 903 name: "with no matching namespace", 904 pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team2", Labels: podLabelServiceS1}}, 905 pods: []*v1.Pod{ 906 {Spec: v1.PodSpec{NodeName: "node1", Affinity: hardPodAffinity}}, 907 {Spec: v1.PodSpec{NodeName: "node2", Affinity: hardPodAffinity}}, 908 }, 909 nodes: []*v1.Node{ 910 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 911 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 912 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 913 }, 914 hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, 915 wantStatus: framework.NewStatus(framework.Skip), 916 }, 917 { 918 name: "with matching NamespaceSelector", 919 pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "subteam1.team1", Labels: podLabelServiceS1}}, 920 pods: []*v1.Pod{ 921 {Spec: v1.PodSpec{NodeName: "node1", Affinity: hardPodAffinity}}, 922 {Spec: v1.PodSpec{NodeName: "node2", Affinity: hardPodAffinity}}, 923 }, 924 nodes: []*v1.Node{ 925 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 926 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 927 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 928 }, 929 hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, 930 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: 0}}, 931 }, 932 { 933 name: "with matching Namespaces", 934 pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "subteam2.team2", Labels: podLabelServiceS1}}, 935 pods: []*v1.Pod{ 936 {Spec: v1.PodSpec{NodeName: "node1", Affinity: hardPodAffinity}}, 937 {Spec: v1.PodSpec{NodeName: "node2", Affinity: hardPodAffinity}}, 938 }, 939 nodes: []*v1.Node{ 940 {ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}}, 941 {ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}}, 942 {ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: labelAzAz1}}, 943 }, 944 hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, 945 expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: framework.MaxNodeScore}, {Name: "node3", Score: 0}}, 946 }, 947 } 948 for _, test := range tests { 949 t.Run(test.name, func(t *testing.T) { 950 _, ctx := ktesting.NewTestContext(t) 951 ctx, cancel := context.WithCancel(ctx) 952 defer cancel() 953 state := framework.NewCycleState() 954 p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: test.hardPodAffinityWeight}, cache.NewSnapshot(test.pods, test.nodes), namespaces) 955 status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, tf.BuildNodeInfos(test.nodes)) 956 if !test.wantStatus.Equal(status) { 957 t.Errorf("InterPodAffinity#PreScore() returned unexpected status.Code got: %v, want: %v", status.Code(), test.wantStatus.Code()) 958 } 959 if !status.IsSuccess() { 960 return 961 } 962 963 var gotList framework.NodeScoreList 964 for _, n := range test.nodes { 965 nodeName := n.ObjectMeta.Name 966 score, status := p.(framework.ScorePlugin).Score(ctx, state, test.pod, nodeName) 967 if !status.IsSuccess() { 968 t.Errorf("unexpected error: %v", status) 969 } 970 gotList = append(gotList, framework.NodeScore{Name: nodeName, Score: score}) 971 } 972 973 status = p.(framework.ScorePlugin).ScoreExtensions().NormalizeScore(ctx, state, test.pod, gotList) 974 if !status.IsSuccess() { 975 t.Errorf("unexpected error: %v", status) 976 } 977 978 if !reflect.DeepEqual(test.expectedList, gotList) { 979 t.Errorf("expected:\n\t%+v,\ngot:\n\t%+v", test.expectedList, gotList) 980 } 981 }) 982 } 983 }