k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/podtopologyspread/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 podtopologyspread
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	appsv1 "k8s.io/api/apps/v1"
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  	"k8s.io/client-go/informers"
    31  	"k8s.io/client-go/kubernetes/fake"
    32  	"k8s.io/klog/v2/ktesting"
    33  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    34  	"k8s.io/kubernetes/pkg/scheduler/framework"
    35  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
    36  	plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing"
    37  	frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
    38  	"k8s.io/kubernetes/pkg/scheduler/internal/cache"
    39  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    40  	tf "k8s.io/kubernetes/pkg/scheduler/testing/framework"
    41  	"k8s.io/utils/ptr"
    42  )
    43  
    44  var podTopologySpreadFunc = frameworkruntime.FactoryAdapter(feature.Features{}, New)
    45  
    46  // TestPreScoreSkip tests the cases that TopologySpread#PreScore returns the Skip status.
    47  func TestPreScoreSkip(t *testing.T) {
    48  	tests := []struct {
    49  		name   string
    50  		pod    *v1.Pod
    51  		nodes  []*v1.Node
    52  		objs   []runtime.Object
    53  		config config.PodTopologySpreadArgs
    54  	}{
    55  		{
    56  			name: "the pod doesn't have soft topology spread Constraints",
    57  			pod:  st.MakePod().Name("p").Namespace("default").Obj(),
    58  			config: config.PodTopologySpreadArgs{
    59  				DefaultingType: config.ListDefaulting,
    60  			},
    61  			nodes: []*v1.Node{
    62  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
    63  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
    64  			},
    65  		},
    66  		{
    67  			name: "default constraints and a replicaset that doesn't match",
    68  			pod:  st.MakePod().Name("p").Namespace("default").Label("foo", "bar").Label("baz", "sup").OwnerReference("rs2", appsv1.SchemeGroupVersion.WithKind("ReplicaSet")).Obj(),
    69  			config: config.PodTopologySpreadArgs{
    70  				DefaultConstraints: []v1.TopologySpreadConstraint{
    71  					{
    72  						MaxSkew:           2,
    73  						TopologyKey:       "planet",
    74  						WhenUnsatisfiable: v1.ScheduleAnyway,
    75  					},
    76  				},
    77  				DefaultingType: config.ListDefaulting,
    78  			},
    79  			nodes: []*v1.Node{
    80  				st.MakeNode().Name("node-a").Label("planet", "mars").Obj(),
    81  			},
    82  			objs: []runtime.Object{
    83  				&appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "rs1"}, Spec: appsv1.ReplicaSetSpec{Selector: st.MakeLabelSelector().Exists("tar").Obj()}},
    84  			},
    85  		},
    86  	}
    87  	for _, tt := range tests {
    88  		t.Run(tt.name, func(t *testing.T) {
    89  			_, ctx := ktesting.NewTestContext(t)
    90  			ctx, cancel := context.WithCancel(ctx)
    91  			defer cancel()
    92  			informerFactory := informers.NewSharedInformerFactory(fake.NewSimpleClientset(tt.objs...), 0)
    93  			f, err := frameworkruntime.NewFramework(ctx, nil, nil,
    94  				frameworkruntime.WithSnapshotSharedLister(cache.NewSnapshot(nil, tt.nodes)),
    95  				frameworkruntime.WithInformerFactory(informerFactory))
    96  			if err != nil {
    97  				t.Fatalf("Failed creating framework runtime: %v", err)
    98  			}
    99  			pl, err := New(ctx, &tt.config, f, feature.Features{})
   100  			if err != nil {
   101  				t.Fatalf("Failed creating plugin: %v", err)
   102  			}
   103  			informerFactory.Start(ctx.Done())
   104  			informerFactory.WaitForCacheSync(ctx.Done())
   105  			p := pl.(*PodTopologySpread)
   106  			cs := framework.NewCycleState()
   107  			if s := p.PreScore(ctx, cs, tt.pod, tf.BuildNodeInfos(tt.nodes)); !s.IsSkip() {
   108  				t.Fatalf("Expected skip but got %v", s.AsError())
   109  			}
   110  		})
   111  	}
   112  }
   113  
   114  func TestPreScoreStateEmptyNodes(t *testing.T) {
   115  	tests := []struct {
   116  		name                      string
   117  		pod                       *v1.Pod
   118  		nodes                     []*v1.Node
   119  		objs                      []runtime.Object
   120  		config                    config.PodTopologySpreadArgs
   121  		want                      *preScoreState
   122  		enableNodeInclusionPolicy bool
   123  	}{
   124  		{
   125  			name: "normal case",
   126  			pod: st.MakePod().Name("p").Label("foo", "").
   127  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   128  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   129  				Obj(),
   130  			nodes: []*v1.Node{
   131  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
   132  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
   133  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   134  			},
   135  			config: config.PodTopologySpreadArgs{
   136  				DefaultingType: config.ListDefaulting,
   137  			},
   138  			want: &preScoreState{
   139  				Constraints: []topologySpreadConstraint{
   140  					{
   141  						MaxSkew:            1,
   142  						TopologyKey:        "zone",
   143  						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
   144  						MinDomains:         1,
   145  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   146  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   147  					},
   148  					{
   149  						MaxSkew:            1,
   150  						TopologyKey:        v1.LabelHostname,
   151  						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
   152  						MinDomains:         1,
   153  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   154  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   155  					},
   156  				},
   157  				IgnoredNodes: sets.New[string](),
   158  				TopologyPairToPodCounts: map[topologyPair]*int64{
   159  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   160  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   161  				},
   162  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2), topologyNormalizingWeight(3)},
   163  			},
   164  		},
   165  		{
   166  			name: "null selector",
   167  			pod: st.MakePod().Name("p").Label("foo", "").
   168  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, nil, nil, nil, nil, nil).
   169  				Obj(),
   170  			nodes: []*v1.Node{
   171  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
   172  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
   173  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   174  			},
   175  			config: config.PodTopologySpreadArgs{
   176  				DefaultingType: config.ListDefaulting,
   177  			},
   178  			want: &preScoreState{
   179  				Constraints: []topologySpreadConstraint{
   180  					{
   181  						MaxSkew:            1,
   182  						TopologyKey:        "zone",
   183  						Selector:           labels.Nothing(),
   184  						MinDomains:         1,
   185  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   186  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   187  					},
   188  				},
   189  				IgnoredNodes: sets.New[string](),
   190  				TopologyPairToPodCounts: map[topologyPair]*int64{
   191  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   192  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   193  				},
   194  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
   195  			},
   196  		},
   197  		{
   198  			name: "node-x doesn't have label zone",
   199  			pod: st.MakePod().Name("p").Label("foo", "").
   200  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   201  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
   202  				Obj(),
   203  			nodes: []*v1.Node{
   204  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
   205  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
   206  				st.MakeNode().Name("node-x").Label(v1.LabelHostname, "node-x").Obj(),
   207  			},
   208  			config: config.PodTopologySpreadArgs{
   209  				DefaultingType: config.ListDefaulting,
   210  			},
   211  			want: &preScoreState{
   212  				Constraints: []topologySpreadConstraint{
   213  					{
   214  						MaxSkew:            1,
   215  						TopologyKey:        "zone",
   216  						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
   217  						MinDomains:         1,
   218  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   219  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   220  					},
   221  					{
   222  						MaxSkew:            1,
   223  						TopologyKey:        v1.LabelHostname,
   224  						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
   225  						MinDomains:         1,
   226  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   227  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   228  					},
   229  				},
   230  				IgnoredNodes: sets.New("node-x"),
   231  				TopologyPairToPodCounts: map[topologyPair]*int64{
   232  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   233  				},
   234  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1), topologyNormalizingWeight(2)},
   235  			},
   236  		},
   237  		{
   238  			name: "system default constraints and a replicaset",
   239  			pod:  st.MakePod().Name("p").Namespace("default").Label("foo", "tar").Label("baz", "sup").OwnerReference("rs1", appsv1.SchemeGroupVersion.WithKind("ReplicaSet")).Obj(),
   240  			config: config.PodTopologySpreadArgs{
   241  				DefaultingType: config.SystemDefaulting,
   242  			},
   243  			nodes: []*v1.Node{
   244  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Label(v1.LabelTopologyZone, "mars").Obj(),
   245  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Label(v1.LabelTopologyZone, "mars").Obj(),
   246  				// Nodes with no zone are not excluded. They are considered a separate zone.
   247  				st.MakeNode().Name("node-c").Label(v1.LabelHostname, "node-c").Obj(),
   248  				st.MakeNode().Name("node-d").Label(v1.LabelHostname, "node-d").Obj(),
   249  			},
   250  			objs: []runtime.Object{
   251  				&appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "rs1"}, Spec: appsv1.ReplicaSetSpec{Selector: fooSelector}},
   252  			},
   253  			want: &preScoreState{
   254  				Constraints: []topologySpreadConstraint{
   255  					{
   256  						MaxSkew:            3,
   257  						TopologyKey:        v1.LabelHostname,
   258  						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
   259  						MinDomains:         1,
   260  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   261  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   262  					},
   263  					{
   264  						MaxSkew:            5,
   265  						TopologyKey:        v1.LabelTopologyZone,
   266  						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
   267  						MinDomains:         1,
   268  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   269  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   270  					},
   271  				},
   272  				IgnoredNodes: sets.New[string](),
   273  				TopologyPairToPodCounts: map[topologyPair]*int64{
   274  					{key: v1.LabelTopologyZone, value: "mars"}: ptr.To[int64](0),
   275  					{key: v1.LabelTopologyZone, value: ""}:     ptr.To[int64](0),
   276  				},
   277  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(4), topologyNormalizingWeight(2)},
   278  			},
   279  		},
   280  		{
   281  			name: "default constraints and a replicaset",
   282  			pod:  st.MakePod().Name("p").Namespace("default").Label("foo", "tar").Label("baz", "sup").OwnerReference("rs1", appsv1.SchemeGroupVersion.WithKind("ReplicaSet")).Obj(),
   283  			config: config.PodTopologySpreadArgs{
   284  				DefaultConstraints: []v1.TopologySpreadConstraint{
   285  					{
   286  						MaxSkew:           1,
   287  						TopologyKey:       v1.LabelHostname,
   288  						WhenUnsatisfiable: v1.ScheduleAnyway,
   289  					},
   290  					{MaxSkew: 2,
   291  						TopologyKey:       "rack",
   292  						WhenUnsatisfiable: v1.DoNotSchedule,
   293  					},
   294  					{MaxSkew: 2, TopologyKey: "planet", WhenUnsatisfiable: v1.ScheduleAnyway},
   295  				},
   296  				DefaultingType: config.ListDefaulting,
   297  			},
   298  			nodes: []*v1.Node{
   299  				st.MakeNode().Name("node-a").Label("rack", "rack1").Label(v1.LabelHostname, "node-a").Label("planet", "mars").Obj(),
   300  			},
   301  			objs: []runtime.Object{
   302  				&appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "rs1"}, Spec: appsv1.ReplicaSetSpec{Selector: fooSelector}},
   303  			},
   304  			want: &preScoreState{
   305  				Constraints: []topologySpreadConstraint{
   306  					{
   307  						MaxSkew:            1,
   308  						TopologyKey:        v1.LabelHostname,
   309  						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
   310  						MinDomains:         1,
   311  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   312  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   313  					},
   314  					{
   315  						MaxSkew:            2,
   316  						TopologyKey:        "planet",
   317  						Selector:           mustConvertLabelSelectorAsSelector(t, fooSelector),
   318  						MinDomains:         1,
   319  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   320  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   321  					},
   322  				},
   323  				IgnoredNodes: sets.New[string](),
   324  				TopologyPairToPodCounts: map[topologyPair]*int64{
   325  					{key: "planet", value: "mars"}: ptr.To[int64](0),
   326  				},
   327  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1), topologyNormalizingWeight(1)},
   328  			},
   329  		},
   330  		{
   331  			name: "default constraints and a replicaset, but pod has constraints",
   332  			pod: st.MakePod().Name("p").Namespace("default").Label("foo", "bar").Label("baz", "sup").
   333  				OwnerReference("rs1", appsv1.SchemeGroupVersion.WithKind("ReplicaSet")).
   334  				SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil).
   335  				SpreadConstraint(2, "planet", v1.ScheduleAnyway, st.MakeLabelSelector().Label("baz", "sup").Obj(), nil, nil, nil, nil).
   336  				Obj(),
   337  			config: config.PodTopologySpreadArgs{
   338  				DefaultConstraints: []v1.TopologySpreadConstraint{
   339  					{
   340  						MaxSkew:           2,
   341  						TopologyKey:       "galaxy",
   342  						WhenUnsatisfiable: v1.ScheduleAnyway,
   343  					},
   344  				},
   345  				DefaultingType: config.ListDefaulting,
   346  			},
   347  			nodes: []*v1.Node{
   348  				st.MakeNode().Name("node-a").Label("planet", "mars").Label("galaxy", "andromeda").Obj(),
   349  			},
   350  			objs: []runtime.Object{
   351  				&appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "rs1"}, Spec: appsv1.ReplicaSetSpec{Selector: fooSelector}},
   352  			},
   353  			want: &preScoreState{
   354  				Constraints: []topologySpreadConstraint{
   355  					{
   356  						MaxSkew:            2,
   357  						TopologyKey:        "planet",
   358  						Selector:           mustConvertLabelSelectorAsSelector(t, st.MakeLabelSelector().Label("baz", "sup").Obj()),
   359  						MinDomains:         1,
   360  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   361  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   362  					},
   363  				},
   364  				IgnoredNodes: sets.New[string](),
   365  				TopologyPairToPodCounts: map[topologyPair]*int64{
   366  					{"planet", "mars"}: ptr.To[int64](0),
   367  				},
   368  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1)},
   369  			},
   370  		},
   371  		{
   372  			name: "NodeAffinityPolicy honored with labelSelectors",
   373  			pod: st.MakePod().Name("p").Label("foo", "").
   374  				NodeSelector(map[string]string{"foo": ""}).
   375  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
   376  				Obj(),
   377  			nodes: []*v1.Node{
   378  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
   379  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-b").Obj(),
   380  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   381  			},
   382  			config: config.PodTopologySpreadArgs{
   383  				DefaultingType: config.ListDefaulting,
   384  			},
   385  			want: &preScoreState{
   386  				Constraints: []topologySpreadConstraint{
   387  					{
   388  						MaxSkew:            1,
   389  						TopologyKey:        "zone",
   390  						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
   391  						MinDomains:         1,
   392  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   393  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   394  					},
   395  				},
   396  				IgnoredNodes: sets.New[string](),
   397  				TopologyPairToPodCounts: map[topologyPair]*int64{
   398  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   399  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   400  				},
   401  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
   402  			},
   403  			enableNodeInclusionPolicy: true,
   404  		},
   405  		{
   406  			name: "NodeAffinityPolicy ignored with labelSelectors",
   407  			pod: st.MakePod().Name("p").Label("foo", "").
   408  				NodeSelector(map[string]string{"foo": ""}).
   409  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, &ignorePolicy, nil, nil).
   410  				Obj(),
   411  			nodes: []*v1.Node{
   412  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
   413  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-b").Obj(),
   414  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   415  			},
   416  			config: config.PodTopologySpreadArgs{
   417  				DefaultingType: config.ListDefaulting,
   418  			},
   419  			want: &preScoreState{
   420  				Constraints: []topologySpreadConstraint{
   421  					{
   422  						MaxSkew:            1,
   423  						TopologyKey:        "zone",
   424  						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
   425  						MinDomains:         1,
   426  						NodeAffinityPolicy: v1.NodeInclusionPolicyIgnore,
   427  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   428  					},
   429  				},
   430  				IgnoredNodes: sets.New[string](),
   431  				TopologyPairToPodCounts: map[topologyPair]*int64{
   432  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   433  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   434  				},
   435  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
   436  			},
   437  			enableNodeInclusionPolicy: true,
   438  		},
   439  		{
   440  			name: "NodeAffinityPolicy honored with nodeAffinity",
   441  			pod: st.MakePod().Name("p").Label("foo", "").
   442  				NodeAffinityIn("foo", []string{""}).
   443  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
   444  				Obj(),
   445  			nodes: []*v1.Node{
   446  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
   447  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-b").Obj(),
   448  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   449  			},
   450  			config: config.PodTopologySpreadArgs{
   451  				DefaultingType: config.ListDefaulting,
   452  			},
   453  			want: &preScoreState{
   454  				Constraints: []topologySpreadConstraint{
   455  					{
   456  						MaxSkew:            1,
   457  						TopologyKey:        "zone",
   458  						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
   459  						MinDomains:         1,
   460  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   461  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   462  					},
   463  				},
   464  				IgnoredNodes: sets.New[string](),
   465  				TopologyPairToPodCounts: map[topologyPair]*int64{
   466  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   467  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   468  				},
   469  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
   470  			},
   471  			enableNodeInclusionPolicy: true,
   472  		},
   473  		{
   474  			name: "NodeAffinityPolicy ignored with nodeAffinity",
   475  			pod: st.MakePod().Name("p").Label("foo", "").
   476  				NodeAffinityIn("foo", []string{""}).
   477  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, &ignorePolicy, nil, nil).
   478  				Obj(),
   479  			nodes: []*v1.Node{
   480  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
   481  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-b").Obj(),
   482  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   483  			},
   484  			config: config.PodTopologySpreadArgs{
   485  				DefaultingType: config.ListDefaulting,
   486  			},
   487  			want: &preScoreState{
   488  				Constraints: []topologySpreadConstraint{
   489  					{
   490  						MaxSkew:            1,
   491  						TopologyKey:        "zone",
   492  						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
   493  						MinDomains:         1,
   494  						NodeAffinityPolicy: v1.NodeInclusionPolicyIgnore,
   495  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   496  					},
   497  				},
   498  				IgnoredNodes: sets.New[string](),
   499  				TopologyPairToPodCounts: map[topologyPair]*int64{
   500  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   501  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   502  				},
   503  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
   504  			},
   505  			enableNodeInclusionPolicy: true,
   506  		},
   507  		{
   508  			name: "NodeTaintsPolicy honored",
   509  			pod: st.MakePod().Name("p").Label("foo", "").
   510  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, &honorPolicy, nil).
   511  				Obj(),
   512  			nodes: []*v1.Node{
   513  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
   514  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-b").Obj(),
   515  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Taints(taints).Obj(),
   516  			},
   517  			config: config.PodTopologySpreadArgs{
   518  				DefaultingType: config.ListDefaulting,
   519  			},
   520  			want: &preScoreState{
   521  				Constraints: []topologySpreadConstraint{
   522  					{
   523  						MaxSkew:            1,
   524  						TopologyKey:        "zone",
   525  						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
   526  						MinDomains:         1,
   527  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   528  						NodeTaintsPolicy:   v1.NodeInclusionPolicyHonor,
   529  					},
   530  				},
   531  				IgnoredNodes: sets.New[string](),
   532  				TopologyPairToPodCounts: map[topologyPair]*int64{
   533  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   534  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   535  				},
   536  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
   537  			},
   538  			enableNodeInclusionPolicy: true,
   539  		},
   540  		{
   541  			name: "NodeTaintsPolicy ignored",
   542  			pod: st.MakePod().Name("p").Label("foo", "").
   543  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
   544  				Obj(),
   545  			nodes: []*v1.Node{
   546  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-a").Obj(),
   547  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label("foo", "").Label(v1.LabelHostname, "node-b").Obj(),
   548  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Taints(taints).Obj(),
   549  			},
   550  			config: config.PodTopologySpreadArgs{
   551  				DefaultingType: config.ListDefaulting,
   552  			},
   553  			want: &preScoreState{
   554  				Constraints: []topologySpreadConstraint{
   555  					{
   556  						MaxSkew:            1,
   557  						TopologyKey:        "zone",
   558  						Selector:           mustConvertLabelSelectorAsSelector(t, barSelector),
   559  						MinDomains:         1,
   560  						NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
   561  						NodeTaintsPolicy:   v1.NodeInclusionPolicyIgnore,
   562  					},
   563  				},
   564  				IgnoredNodes: sets.New[string](),
   565  				TopologyPairToPodCounts: map[topologyPair]*int64{
   566  					{key: "zone", value: "zone1"}: ptr.To[int64](0),
   567  					{key: "zone", value: "zone2"}: ptr.To[int64](0),
   568  				},
   569  				TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)},
   570  			},
   571  			enableNodeInclusionPolicy: true,
   572  		},
   573  	}
   574  	for _, tt := range tests {
   575  		t.Run(tt.name, func(t *testing.T) {
   576  			_, ctx := ktesting.NewTestContext(t)
   577  			ctx, cancel := context.WithCancel(ctx)
   578  			defer cancel()
   579  			informerFactory := informers.NewSharedInformerFactory(fake.NewSimpleClientset(tt.objs...), 0)
   580  			f, err := frameworkruntime.NewFramework(ctx, nil, nil,
   581  				frameworkruntime.WithSnapshotSharedLister(cache.NewSnapshot(nil, tt.nodes)),
   582  				frameworkruntime.WithInformerFactory(informerFactory))
   583  			if err != nil {
   584  				t.Fatalf("Failed creating framework runtime: %v", err)
   585  			}
   586  			pl, err := New(ctx, &tt.config, f, feature.Features{EnableNodeInclusionPolicyInPodTopologySpread: tt.enableNodeInclusionPolicy})
   587  			if err != nil {
   588  				t.Fatalf("Failed creating plugin: %v", err)
   589  			}
   590  			informerFactory.Start(ctx.Done())
   591  			informerFactory.WaitForCacheSync(ctx.Done())
   592  			p := pl.(*PodTopologySpread)
   593  			cs := framework.NewCycleState()
   594  			if s := p.PreScore(ctx, cs, tt.pod, tf.BuildNodeInfos(tt.nodes)); !s.IsSuccess() {
   595  				t.Fatal(s.AsError())
   596  			}
   597  
   598  			got, err := getPreScoreState(cs)
   599  			if err != nil {
   600  				t.Fatal(err)
   601  			}
   602  			if diff := cmp.Diff(tt.want, got, cmpOpts...); diff != "" {
   603  				t.Errorf("PodTopologySpread#PreScore() returned (-want, +got):\n%s", diff)
   604  			}
   605  		})
   606  	}
   607  }
   608  
   609  func TestPodTopologySpreadScore(t *testing.T) {
   610  	tests := []struct {
   611  		name                      string
   612  		pod                       *v1.Pod
   613  		existingPods              []*v1.Pod
   614  		nodes                     []*v1.Node
   615  		failedNodes               []*v1.Node // nodes + failedNodes = all nodes
   616  		objs                      []runtime.Object
   617  		want                      framework.NodeScoreList
   618  		enableNodeInclusionPolicy bool
   619  		enableMatchLabelKeys      bool
   620  	}{
   621  		// Explanation on the Legend:
   622  		// a) X/Y means there are X matching pods on node1 and Y on node2, both nodes are candidates
   623  		//   (i.e. they have passed all predicates)
   624  		// b) X/~Y~ means there are X matching pods on node1 and Y on node2, but node Y is NOT a candidate
   625  		// c) X/?Y? means there are X matching pods on node1 and Y on node2, both nodes are candidates
   626  		//    but node2 doesn't have all required topologyKeys present.
   627  		{
   628  			name: "one constraint on node, no existing pods",
   629  			pod: st.MakePod().Name("p").Label("foo", "").
   630  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   631  				Obj(),
   632  			nodes: []*v1.Node{
   633  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   634  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   635  			},
   636  			want: []framework.NodeScore{
   637  				{Name: "node-a", Score: 100},
   638  				{Name: "node-b", Score: 100},
   639  			},
   640  		},
   641  		{
   642  			// if there is only one candidate node, it should be scored to 100
   643  			name: "one constraint on node, only one node is candidate",
   644  			pod: st.MakePod().Name("p").Label("foo", "").
   645  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   646  				Obj(),
   647  			existingPods: []*v1.Pod{
   648  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   649  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   650  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   651  			},
   652  			nodes: []*v1.Node{
   653  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   654  			},
   655  			failedNodes: []*v1.Node{
   656  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   657  			},
   658  			want: []framework.NodeScore{
   659  				{Name: "node-a", Score: 100},
   660  			},
   661  		},
   662  		{
   663  			name: "one constraint on node, all nodes have the same number of matching pods",
   664  			pod: st.MakePod().Name("p").Label("foo", "").
   665  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   666  				Obj(),
   667  			existingPods: []*v1.Pod{
   668  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   669  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   670  			},
   671  			nodes: []*v1.Node{
   672  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   673  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   674  			},
   675  			want: []framework.NodeScore{
   676  				{Name: "node-a", Score: 100},
   677  				{Name: "node-b", Score: 100},
   678  			},
   679  		},
   680  		{
   681  			// matching pods spread as 2/1/0/3.
   682  			name: "one constraint on node, all 4 nodes are candidates",
   683  			pod: st.MakePod().Name("p").Label("foo", "").
   684  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   685  				Obj(),
   686  			existingPods: []*v1.Pod{
   687  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   688  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   689  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   690  				st.MakePod().Name("p-d1").Node("node-d").Label("foo", "").Obj(),
   691  				st.MakePod().Name("p-d2").Node("node-d").Label("foo", "").Obj(),
   692  				st.MakePod().Name("p-d3").Node("node-d").Label("foo", "").Obj(),
   693  			},
   694  			nodes: []*v1.Node{
   695  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   696  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   697  				st.MakeNode().Name("node-c").Label(v1.LabelHostname, "node-c").Obj(),
   698  				st.MakeNode().Name("node-d").Label(v1.LabelHostname, "node-d").Obj(),
   699  			},
   700  			failedNodes: []*v1.Node{},
   701  			want: []framework.NodeScore{
   702  				{Name: "node-a", Score: 20},
   703  				{Name: "node-b", Score: 60},
   704  				{Name: "node-c", Score: 100},
   705  				{Name: "node-d", Score: 0},
   706  			},
   707  		},
   708  		{
   709  			name: "one constraint on node, null selector",
   710  			pod: st.MakePod().Name("p").Label("foo", "").
   711  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, nil, nil, nil, nil, nil).
   712  				Obj(),
   713  			existingPods: []*v1.Pod{
   714  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   715  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   716  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   717  				st.MakePod().Name("p-d1").Node("node-d").Label("foo", "").Obj(),
   718  				st.MakePod().Name("p-d2").Node("node-d").Label("foo", "").Obj(),
   719  				st.MakePod().Name("p-d3").Node("node-d").Label("foo", "").Obj(),
   720  			},
   721  			nodes: []*v1.Node{
   722  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   723  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   724  				st.MakeNode().Name("node-c").Label(v1.LabelHostname, "node-c").Obj(),
   725  				st.MakeNode().Name("node-d").Label(v1.LabelHostname, "node-d").Obj(),
   726  			},
   727  			want: []framework.NodeScore{
   728  				{Name: "node-a", Score: 100},
   729  				{Name: "node-b", Score: 100},
   730  				{Name: "node-c", Score: 100},
   731  				{Name: "node-d", Score: 100},
   732  			},
   733  		},
   734  		{
   735  			name: "one constraint on node, all 4 nodes are candidates, maxSkew=2",
   736  			pod: st.MakePod().Name("p").Label("foo", "").
   737  				SpreadConstraint(2, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   738  				Obj(),
   739  			// matching pods spread as 2/1/0/3.
   740  			existingPods: []*v1.Pod{
   741  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   742  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   743  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   744  				st.MakePod().Name("p-d1").Node("node-d").Label("foo", "").Obj(),
   745  				st.MakePod().Name("p-d2").Node("node-d").Label("foo", "").Obj(),
   746  				st.MakePod().Name("p-d3").Node("node-d").Label("foo", "").Obj(),
   747  			},
   748  			nodes: []*v1.Node{
   749  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   750  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   751  				st.MakeNode().Name("node-c").Label(v1.LabelHostname, "node-c").Obj(),
   752  				st.MakeNode().Name("node-d").Label(v1.LabelHostname, "node-d").Obj(),
   753  			},
   754  			failedNodes: []*v1.Node{},
   755  			want: []framework.NodeScore{
   756  				{Name: "node-a", Score: 33}, // +13, compared to maxSkew=1
   757  				{Name: "node-b", Score: 66}, // +6, compared to maxSkew=1
   758  				{Name: "node-c", Score: 100},
   759  				{Name: "node-d", Score: 16}, // +16, compared to maxSkew=1
   760  			},
   761  		},
   762  		{
   763  			name: "one constraint on node, all 4 nodes are candidates, maxSkew=3",
   764  			pod: st.MakePod().Name("p").Label("foo", "").
   765  				SpreadConstraint(3, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   766  				Obj(),
   767  			existingPods: []*v1.Pod{
   768  				// matching pods spread as 4/3/2/1.
   769  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   770  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   771  				st.MakePod().Name("p-a3").Node("node-a").Label("foo", "").Obj(),
   772  				st.MakePod().Name("p-a4").Node("node-a").Label("foo", "").Obj(),
   773  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   774  				st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
   775  				st.MakePod().Name("p-b3").Node("node-b").Label("foo", "").Obj(),
   776  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
   777  				st.MakePod().Name("p-c2").Node("node-c").Label("foo", "").Obj(),
   778  				st.MakePod().Name("p-d1").Node("node-d").Label("foo", "").Obj(),
   779  			},
   780  			nodes: []*v1.Node{
   781  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   782  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   783  				st.MakeNode().Name("node-c").Label(v1.LabelHostname, "node-c").Obj(),
   784  				st.MakeNode().Name("node-d").Label(v1.LabelHostname, "node-d").Obj(),
   785  			},
   786  			failedNodes: []*v1.Node{},
   787  			want: []framework.NodeScore{
   788  				{Name: "node-a", Score: 44}, // +16 compared to maxSkew=1
   789  				{Name: "node-b", Score: 66}, // +9 compared to maxSkew=1
   790  				{Name: "node-c", Score: 77}, // +6 compared to maxSkew=1
   791  				{Name: "node-d", Score: 100},
   792  			},
   793  		},
   794  		{
   795  			name: "system defaulting, nodes don't have zone, pods match service",
   796  			pod:  st.MakePod().Name("p").Label("foo", "").Obj(),
   797  			existingPods: []*v1.Pod{
   798  				// matching pods spread as 4/3/2/1.
   799  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   800  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   801  				st.MakePod().Name("p-a3").Node("node-a").Label("foo", "").Obj(),
   802  				st.MakePod().Name("p-a4").Node("node-a").Label("foo", "").Obj(),
   803  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   804  				st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
   805  				st.MakePod().Name("p-b3").Node("node-b").Label("foo", "").Obj(),
   806  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
   807  				st.MakePod().Name("p-c2").Node("node-c").Label("foo", "").Obj(),
   808  				st.MakePod().Name("p-d1").Node("node-d").Label("foo", "").Obj(),
   809  			},
   810  			nodes: []*v1.Node{
   811  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   812  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   813  				st.MakeNode().Name("node-c").Label(v1.LabelHostname, "node-c").Obj(),
   814  				st.MakeNode().Name("node-d").Label(v1.LabelHostname, "node-d").Obj(),
   815  			},
   816  			failedNodes: []*v1.Node{},
   817  			objs: []runtime.Object{
   818  				&v1.Service{Spec: v1.ServiceSpec{Selector: map[string]string{"foo": ""}}},
   819  			},
   820  			want: []framework.NodeScore{
   821  				// Same scores as if we were using one spreading constraint.
   822  				{Name: "node-a", Score: 44},
   823  				{Name: "node-b", Score: 66},
   824  				{Name: "node-c", Score: 77},
   825  				{Name: "node-d", Score: 100},
   826  			},
   827  		},
   828  		{
   829  			// matching pods spread as 4/2/1/~3~ (node4 is not a candidate)
   830  			name: "one constraint on node, 3 out of 4 nodes are candidates",
   831  			pod: st.MakePod().Name("p").Label("foo", "").
   832  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   833  				Obj(),
   834  			existingPods: []*v1.Pod{
   835  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   836  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   837  				st.MakePod().Name("p-a3").Node("node-a").Label("foo", "").Obj(),
   838  				st.MakePod().Name("p-a4").Node("node-a").Label("foo", "").Obj(),
   839  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   840  				st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
   841  				st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
   842  				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
   843  				st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
   844  				st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
   845  			},
   846  			nodes: []*v1.Node{
   847  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   848  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
   849  				st.MakeNode().Name("node-x").Label(v1.LabelHostname, "node-x").Obj(),
   850  			},
   851  			failedNodes: []*v1.Node{
   852  				st.MakeNode().Name("node-y").Label(v1.LabelHostname, "node-y").Obj(),
   853  			},
   854  			want: []framework.NodeScore{
   855  				{Name: "node-a", Score: 33},
   856  				{Name: "node-b", Score: 83},
   857  				{Name: "node-x", Score: 100},
   858  			},
   859  		},
   860  		{
   861  			// matching pods spread as 4/?2?/1/~3~, total = 4+?+1 = 5 (as node2 is problematic)
   862  			name: "one constraint on node, 3 out of 4 nodes are candidates, one node doesn't match topology key",
   863  			pod: st.MakePod().Name("p").Label("foo", "").
   864  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   865  				Obj(),
   866  			existingPods: []*v1.Pod{
   867  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   868  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   869  				st.MakePod().Name("p-a3").Node("node-a").Label("foo", "").Obj(),
   870  				st.MakePod().Name("p-a4").Node("node-a").Label("foo", "").Obj(),
   871  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   872  				st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
   873  				st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
   874  				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
   875  				st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
   876  				st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
   877  			},
   878  			nodes: []*v1.Node{
   879  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
   880  				st.MakeNode().Name("node-b").Label("n", "node-b").Obj(), // label `n` doesn't match topologyKey
   881  				st.MakeNode().Name("node-x").Label(v1.LabelHostname, "node-x").Obj(),
   882  			},
   883  			failedNodes: []*v1.Node{
   884  				st.MakeNode().Name("node-y").Label(v1.LabelHostname, "node-y").Obj(),
   885  			},
   886  			want: []framework.NodeScore{
   887  				{Name: "node-a", Score: 16},
   888  				{Name: "node-b", Score: 0},
   889  				{Name: "node-x", Score: 100},
   890  			},
   891  		},
   892  		{
   893  			// matching pods spread as 4/2/1/~3~
   894  			name: "one constraint on zone, 3 out of 4 nodes are candidates",
   895  			pod: st.MakePod().Name("p").Label("foo", "").
   896  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   897  				Obj(),
   898  			existingPods: []*v1.Pod{
   899  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   900  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   901  				st.MakePod().Name("p-a3").Node("node-a").Label("foo", "").Obj(),
   902  				st.MakePod().Name("p-a4").Node("node-a").Label("foo", "").Obj(),
   903  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   904  				st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
   905  				st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
   906  				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
   907  				st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
   908  				st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
   909  			},
   910  			nodes: []*v1.Node{
   911  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
   912  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
   913  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   914  			},
   915  			failedNodes: []*v1.Node{
   916  				st.MakeNode().Name("node-y").Label("zone", "zone2").Label(v1.LabelHostname, "node-y").Obj(),
   917  			},
   918  			want: []framework.NodeScore{
   919  				{Name: "node-a", Score: 75},
   920  				{Name: "node-b", Score: 75},
   921  				{Name: "node-x", Score: 100},
   922  			},
   923  		},
   924  		{
   925  			// matching pods spread as 2/~1~/2/~4~.
   926  			name: "two Constraints on zone and node, 2 out of 4 nodes are candidates",
   927  			pod: st.MakePod().Name("p").Label("foo", "").
   928  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   929  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   930  				Obj(),
   931  			existingPods: []*v1.Pod{
   932  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   933  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
   934  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
   935  				st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
   936  				st.MakePod().Name("p-x2").Node("node-x").Label("foo", "").Obj(),
   937  				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
   938  				st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
   939  				st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
   940  				st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(),
   941  			},
   942  			nodes: []*v1.Node{
   943  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
   944  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   945  			},
   946  			failedNodes: []*v1.Node{
   947  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
   948  				st.MakeNode().Name("node-y").Label("zone", "zone2").Label(v1.LabelHostname, "node-y").Obj(),
   949  			},
   950  			want: []framework.NodeScore{
   951  				{Name: "node-a", Score: 100},
   952  				{Name: "node-x", Score: 63},
   953  			},
   954  		},
   955  		{
   956  			// If Constraints hold different labelSelectors, it's a little complex.
   957  			// +----------------------+------------------------+
   958  			// |         zone1        |          zone2         |
   959  			// +----------------------+------------------------+
   960  			// | node-a |    node-b   | node-x |     node-y    |
   961  			// +--------+-------------+--------+---------------+
   962  			// | P{foo} | P{foo, bar} |        | P{foo} P{bar} |
   963  			// +--------+-------------+--------+---------------+
   964  			// For the first constraint (zone): the matching pods spread as 2/2/1/1
   965  			// For the second constraint (node): the matching pods spread as 0/1/0/1
   966  			name: "two Constraints on zone and node, with different labelSelectors",
   967  			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
   968  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   969  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
   970  				Obj(),
   971  			existingPods: []*v1.Pod{
   972  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
   973  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
   974  				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
   975  				st.MakePod().Name("p-y2").Node("node-y").Label("bar", "").Obj(),
   976  			},
   977  			nodes: []*v1.Node{
   978  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
   979  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
   980  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
   981  				st.MakeNode().Name("node-y").Label("zone", "zone2").Label(v1.LabelHostname, "node-y").Obj(),
   982  			},
   983  			failedNodes: []*v1.Node{},
   984  			want: []framework.NodeScore{
   985  				{Name: "node-a", Score: 60},
   986  				{Name: "node-b", Score: 20},
   987  				{Name: "node-x", Score: 100},
   988  				{Name: "node-y", Score: 60},
   989  			},
   990  		},
   991  		{
   992  			// For the first constraint (zone): the matching pods spread as 0/0/2/2
   993  			// For the second constraint (node): the matching pods spread as 0/1/0/1
   994  			name: "two Constraints on zone and node, with different labelSelectors, some nodes have 0 pods",
   995  			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
   996  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
   997  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
   998  				Obj(),
   999  			existingPods: []*v1.Pod{
  1000  				st.MakePod().Name("p-b1").Node("node-b").Label("bar", "").Obj(),
  1001  				st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
  1002  				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Label("bar", "").Obj(),
  1003  			},
  1004  			nodes: []*v1.Node{
  1005  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
  1006  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
  1007  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
  1008  				st.MakeNode().Name("node-y").Label("zone", "zone2").Label(v1.LabelHostname, "node-y").Obj(),
  1009  			},
  1010  			failedNodes: []*v1.Node{},
  1011  			want: []framework.NodeScore{
  1012  				{Name: "node-a", Score: 100},
  1013  				{Name: "node-b", Score: 60},
  1014  				{Name: "node-x", Score: 40},
  1015  				{Name: "node-y", Score: 0},
  1016  			},
  1017  		},
  1018  		{
  1019  			// For the first constraint (zone): the matching pods spread as 2/2/1/~1~
  1020  			// For the second constraint (node): the matching pods spread as 0/1/0/~1~
  1021  			name: "two Constraints on zone and node, with different labelSelectors, 3 out of 4 nodes are candidates",
  1022  			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
  1023  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1024  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
  1025  				Obj(),
  1026  			existingPods: []*v1.Pod{
  1027  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1028  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
  1029  				st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
  1030  				st.MakePod().Name("p-y2").Node("node-y").Label("bar", "").Obj(),
  1031  			},
  1032  			nodes: []*v1.Node{
  1033  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
  1034  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
  1035  				st.MakeNode().Name("node-x").Label("zone", "zone2").Label(v1.LabelHostname, "node-x").Obj(),
  1036  			},
  1037  			failedNodes: []*v1.Node{
  1038  				st.MakeNode().Name("node-y").Label("zone", "zone2").Label(v1.LabelHostname, "node-y").Obj(),
  1039  			},
  1040  			want: []framework.NodeScore{
  1041  				{Name: "node-a", Score: 50},
  1042  				{Name: "node-b", Score: 25},
  1043  				{Name: "node-x", Score: 100},
  1044  			},
  1045  		},
  1046  		{
  1047  			name: "existing pods in a different namespace do not count",
  1048  			pod: st.MakePod().Name("p").Label("foo", "").
  1049  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1050  				Obj(),
  1051  			existingPods: []*v1.Pod{
  1052  				st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(),
  1053  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1054  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1055  				st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
  1056  			},
  1057  			nodes: []*v1.Node{
  1058  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
  1059  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
  1060  			},
  1061  			want: []framework.NodeScore{
  1062  				{Name: "node-a", Score: 100},
  1063  				{Name: "node-b", Score: 33},
  1064  			},
  1065  		},
  1066  		{
  1067  			name: "terminating Pods should be excluded",
  1068  			pod: st.MakePod().Name("p").Label("foo", "").
  1069  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1070  				Obj(),
  1071  			nodes: []*v1.Node{
  1072  				st.MakeNode().Name("node-a").Label(v1.LabelHostname, "node-a").Obj(),
  1073  				st.MakeNode().Name("node-b").Label(v1.LabelHostname, "node-b").Obj(),
  1074  			},
  1075  			existingPods: []*v1.Pod{
  1076  				st.MakePod().Name("p-a").Node("node-a").Label("foo", "").Terminating().Obj(),
  1077  				st.MakePod().Name("p-b").Node("node-b").Label("foo", "").Obj(),
  1078  			},
  1079  			want: []framework.NodeScore{
  1080  				{Name: "node-a", Score: 100},
  1081  				{Name: "node-b", Score: 0},
  1082  			},
  1083  		},
  1084  		{
  1085  			// This test is artificial. In the real world, API Server would fail a pod's creation
  1086  			// when non-default minDomains is specified along with SchedulingAnyway.
  1087  			name: "minDomains has no effect when ScheduleAnyway",
  1088  			pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
  1089  				2,
  1090  				"node",
  1091  				v1.ScheduleAnyway,
  1092  				fooSelector,
  1093  				ptr.To[int32](10), // larger than the number of domains(3)
  1094  				nil,
  1095  				nil,
  1096  				nil,
  1097  			).Obj(),
  1098  			nodes: []*v1.Node{
  1099  				st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
  1100  				st.MakeNode().Name("node-b").Label("node", "node-b").Obj(),
  1101  				st.MakeNode().Name("node-c").Label("node", "node-c").Obj(),
  1102  			},
  1103  			existingPods: []*v1.Pod{
  1104  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1105  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1106  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1107  				st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
  1108  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1109  			},
  1110  			want: []framework.NodeScore{
  1111  				{Name: "node-a", Score: 75},
  1112  				{Name: "node-b", Score: 75},
  1113  				{Name: "node-c", Score: 100},
  1114  			},
  1115  		},
  1116  		{
  1117  			name: "NodeAffinityPolicy honoed with labelSelectors",
  1118  			pod: st.MakePod().Name("p").Label("foo", "").
  1119  				NodeSelector(map[string]string{"foo": ""}).
  1120  				SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1121  				Obj(),
  1122  			nodes: []*v1.Node{
  1123  				st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
  1124  				st.MakeNode().Name("node-b").Label("node", "node-b").Label("foo", "").Obj(),
  1125  				st.MakeNode().Name("node-c").Label("node", "node-c").Obj(),
  1126  			},
  1127  			existingPods: []*v1.Pod{
  1128  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1129  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1130  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1131  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1132  			},
  1133  			want: []framework.NodeScore{
  1134  				{Name: "node-a", Score: 0},
  1135  				{Name: "node-b", Score: 33},
  1136  				{Name: "node-c", Score: 100},
  1137  			},
  1138  			enableNodeInclusionPolicy: true,
  1139  		},
  1140  		{
  1141  			name: "NodeAffinityPolicy ignored with labelSelectors",
  1142  			pod: st.MakePod().Name("p").Label("foo", "").
  1143  				NodeSelector(map[string]string{"foo": ""}).
  1144  				SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, &ignorePolicy, nil, nil).
  1145  				Obj(),
  1146  			nodes: []*v1.Node{
  1147  				st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
  1148  				st.MakeNode().Name("node-b").Label("node", "node-b").Label("foo", "").Obj(),
  1149  				st.MakeNode().Name("node-c").Label("node", "node-c").Obj(),
  1150  			},
  1151  			existingPods: []*v1.Pod{
  1152  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1153  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1154  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1155  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1156  			},
  1157  			want: []framework.NodeScore{
  1158  				{Name: "node-a", Score: 66},
  1159  				{Name: "node-b", Score: 100},
  1160  				{Name: "node-c", Score: 100},
  1161  			},
  1162  			enableNodeInclusionPolicy: true,
  1163  		},
  1164  		{
  1165  			name: "NodeAffinityPolicy honoed with nodeAffinity",
  1166  			pod: st.MakePod().Name("p").Label("foo", "").
  1167  				NodeAffinityIn("foo", []string{""}).
  1168  				SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1169  				Obj(),
  1170  			nodes: []*v1.Node{
  1171  				st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
  1172  				st.MakeNode().Name("node-b").Label("node", "node-b").Label("foo", "").Obj(),
  1173  				st.MakeNode().Name("node-c").Label("node", "node-c").Obj(),
  1174  			},
  1175  			existingPods: []*v1.Pod{
  1176  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1177  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1178  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1179  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1180  			},
  1181  			want: []framework.NodeScore{
  1182  				{Name: "node-a", Score: 0},
  1183  				{Name: "node-b", Score: 33},
  1184  				{Name: "node-c", Score: 100},
  1185  			},
  1186  			enableNodeInclusionPolicy: true,
  1187  		},
  1188  		{
  1189  			name: "NodeAffinityPolicy ignored with nodeAffinity",
  1190  			pod: st.MakePod().Name("p").Label("foo", "").
  1191  				NodeAffinityIn("foo", []string{""}).
  1192  				SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, &ignorePolicy, nil, nil).
  1193  				Obj(),
  1194  			nodes: []*v1.Node{
  1195  				st.MakeNode().Name("node-a").Label("node", "node-a").Label("foo", "").Obj(),
  1196  				st.MakeNode().Name("node-b").Label("node", "node-b").Label("foo", "").Obj(),
  1197  				st.MakeNode().Name("node-c").Label("node", "node-c").Obj(),
  1198  			},
  1199  			existingPods: []*v1.Pod{
  1200  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1201  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1202  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1203  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1204  			},
  1205  			want: []framework.NodeScore{
  1206  				{Name: "node-a", Score: 66},
  1207  				{Name: "node-b", Score: 100},
  1208  				{Name: "node-c", Score: 100},
  1209  			},
  1210  			enableNodeInclusionPolicy: true,
  1211  		},
  1212  		{
  1213  			name: "NodeTaintsPolicy honored",
  1214  			pod: st.MakePod().Name("p").Label("foo", "").
  1215  				SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, &honorPolicy, nil).
  1216  				Obj(),
  1217  			nodes: []*v1.Node{
  1218  				st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
  1219  				st.MakeNode().Name("node-b").Label("node", "node-b").Obj(),
  1220  				st.MakeNode().Name("node-c").Label("node", "node-c").Taints(taints).Obj(),
  1221  			},
  1222  			existingPods: []*v1.Pod{
  1223  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1224  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1225  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1226  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1227  			},
  1228  			want: []framework.NodeScore{
  1229  				{Name: "node-a", Score: 0},
  1230  				{Name: "node-b", Score: 33},
  1231  				{Name: "node-c", Score: 100},
  1232  			},
  1233  			enableNodeInclusionPolicy: true,
  1234  		},
  1235  		{
  1236  			name: "NodeTaintsPolicy ignored",
  1237  			pod: st.MakePod().Name("p").Label("foo", "").
  1238  				SpreadConstraint(1, "node", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1239  				Obj(),
  1240  			nodes: []*v1.Node{
  1241  				st.MakeNode().Name("node-a").Label("node", "node-a").Obj(),
  1242  				st.MakeNode().Name("node-b").Label("node", "node-b").Obj(),
  1243  				st.MakeNode().Name("node-c").Label("node", "node-c").Taints(taints).Obj(),
  1244  			},
  1245  			existingPods: []*v1.Pod{
  1246  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1247  				st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
  1248  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
  1249  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1250  			},
  1251  			want: []framework.NodeScore{
  1252  				{Name: "node-a", Score: 66},
  1253  				{Name: "node-b", Score: 100},
  1254  				{Name: "node-c", Score: 100},
  1255  			},
  1256  			enableNodeInclusionPolicy: true,
  1257  		},
  1258  		{
  1259  			name: "matchLabelKeys ignored when feature gate disabled",
  1260  			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Label("baz", "").
  1261  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, []string{"baz"}).
  1262  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, []string{"baz"}).
  1263  				Obj(),
  1264  			existingPods: []*v1.Pod{
  1265  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1266  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
  1267  				st.MakePod().Name("p-y1").Node("node-c").Label("foo", "").Obj(),
  1268  				st.MakePod().Name("p-y2").Node("node-c").Label("bar", "").Obj(),
  1269  			},
  1270  			nodes: []*v1.Node{
  1271  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
  1272  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
  1273  				st.MakeNode().Name("node-c").Label("zone", "zone2").Label(v1.LabelHostname, "node-c").Obj(),
  1274  				st.MakeNode().Name("node-d").Label("zone", "zone2").Label(v1.LabelHostname, "node-d").Obj(),
  1275  			},
  1276  			want: []framework.NodeScore{
  1277  				{Name: "node-a", Score: 60},
  1278  				{Name: "node-b", Score: 20},
  1279  				{Name: "node-c", Score: 60},
  1280  				{Name: "node-d", Score: 100},
  1281  			},
  1282  			enableMatchLabelKeys: false,
  1283  		},
  1284  		{
  1285  			name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty",
  1286  			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
  1287  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}).
  1288  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"bar"}).
  1289  				Obj(),
  1290  			existingPods: []*v1.Pod{
  1291  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1292  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
  1293  				st.MakePod().Name("p-y1").Node("node-c").Label("foo", "").Obj(),
  1294  				st.MakePod().Name("p-y2").Node("node-c").Label("bar", "").Obj(),
  1295  			},
  1296  			nodes: []*v1.Node{
  1297  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
  1298  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
  1299  				st.MakeNode().Name("node-c").Label("zone", "zone2").Label(v1.LabelHostname, "node-c").Obj(),
  1300  				st.MakeNode().Name("node-d").Label("zone", "zone2").Label(v1.LabelHostname, "node-d").Obj(),
  1301  			},
  1302  			want: []framework.NodeScore{
  1303  				{Name: "node-a", Score: 60},
  1304  				{Name: "node-b", Score: 20},
  1305  				{Name: "node-c", Score: 60},
  1306  				{Name: "node-d", Score: 100},
  1307  			},
  1308  			enableMatchLabelKeys: true,
  1309  		},
  1310  		{
  1311  			name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty",
  1312  			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Label("baz", "").
  1313  				SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1314  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, []string{"baz"}).
  1315  				Obj(),
  1316  			existingPods: []*v1.Pod{
  1317  				st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
  1318  				st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Label("baz", "").Obj(),
  1319  				st.MakePod().Name("p-c1").Node("node-c").Label("foo", "").Obj(),
  1320  				st.MakePod().Name("p-c2").Node("node-c").Label("bar", "").Obj(),
  1321  				st.MakePod().Name("p-d3").Node("node-c").Label("bar", "").Label("baz", "").Obj(),
  1322  			},
  1323  			nodes: []*v1.Node{
  1324  				st.MakeNode().Name("node-a").Label("zone", "zone1").Label(v1.LabelHostname, "node-a").Obj(),
  1325  				st.MakeNode().Name("node-b").Label("zone", "zone1").Label(v1.LabelHostname, "node-b").Obj(),
  1326  				st.MakeNode().Name("node-c").Label("zone", "zone2").Label(v1.LabelHostname, "node-c").Obj(),
  1327  				st.MakeNode().Name("node-d").Label("zone", "zone2").Label(v1.LabelHostname, "node-d").Obj(),
  1328  			},
  1329  			want: []framework.NodeScore{
  1330  				{Name: "node-a", Score: 60},
  1331  				{Name: "node-b", Score: 20},
  1332  				{Name: "node-c", Score: 60},
  1333  				{Name: "node-d", Score: 100},
  1334  			},
  1335  			enableMatchLabelKeys: true,
  1336  		},
  1337  	}
  1338  	for _, tt := range tests {
  1339  		t.Run(tt.name, func(t *testing.T) {
  1340  			_, ctx := ktesting.NewTestContext(t)
  1341  			ctx, cancel := context.WithCancel(ctx)
  1342  			t.Cleanup(cancel)
  1343  			allNodes := append([]*v1.Node{}, tt.nodes...)
  1344  			allNodes = append(allNodes, tt.failedNodes...)
  1345  			state := framework.NewCycleState()
  1346  			pl := plugintesting.SetupPluginWithInformers(ctx, t, podTopologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.SystemDefaulting}, cache.NewSnapshot(tt.existingPods, allNodes), tt.objs)
  1347  			p := pl.(*PodTopologySpread)
  1348  			p.enableNodeInclusionPolicyInPodTopologySpread = tt.enableNodeInclusionPolicy
  1349  			p.enableMatchLabelKeysInPodTopologySpread = tt.enableMatchLabelKeys
  1350  
  1351  			status := p.PreScore(ctx, state, tt.pod, tf.BuildNodeInfos(tt.nodes))
  1352  			if !status.IsSuccess() {
  1353  				t.Errorf("unexpected error: %v", status)
  1354  			}
  1355  
  1356  			var gotList framework.NodeScoreList
  1357  			for _, n := range tt.nodes {
  1358  				nodeName := n.Name
  1359  				score, status := p.Score(ctx, state, tt.pod, nodeName)
  1360  				if !status.IsSuccess() {
  1361  					t.Errorf("unexpected error: %v", status)
  1362  				}
  1363  				gotList = append(gotList, framework.NodeScore{Name: nodeName, Score: score})
  1364  			}
  1365  
  1366  			status = p.NormalizeScore(ctx, state, tt.pod, gotList)
  1367  			if !status.IsSuccess() {
  1368  				t.Errorf("unexpected error: %v", status)
  1369  			}
  1370  			if diff := cmp.Diff(tt.want, gotList, cmpOpts...); diff != "" {
  1371  				t.Errorf("unexpected scores (-want,+got):\n%s", diff)
  1372  			}
  1373  		})
  1374  	}
  1375  }
  1376  
  1377  func BenchmarkTestPodTopologySpreadScore(b *testing.B) {
  1378  	tests := []struct {
  1379  		name             string
  1380  		pod              *v1.Pod
  1381  		existingPodsNum  int
  1382  		allNodesNum      int
  1383  		filteredNodesNum int
  1384  	}{
  1385  		{
  1386  			name: "1000nodes/single-constraint-zone",
  1387  			pod: st.MakePod().Name("p").Label("foo", "").
  1388  				SpreadConstraint(1, v1.LabelTopologyZone, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1389  				Obj(),
  1390  			existingPodsNum:  10000,
  1391  			allNodesNum:      1000,
  1392  			filteredNodesNum: 500,
  1393  		},
  1394  		{
  1395  			name: "1000nodes/single-constraint-node",
  1396  			pod: st.MakePod().Name("p").Label("foo", "").
  1397  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1398  				Obj(),
  1399  			existingPodsNum:  10000,
  1400  			allNodesNum:      1000,
  1401  			filteredNodesNum: 500,
  1402  		},
  1403  		{
  1404  			name: "1000nodes/two-Constraints-zone-node",
  1405  			pod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
  1406  				SpreadConstraint(1, v1.LabelTopologyZone, v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil).
  1407  				SpreadConstraint(1, v1.LabelHostname, v1.ScheduleAnyway, barSelector, nil, nil, nil, nil).
  1408  				Obj(),
  1409  			existingPodsNum:  10000,
  1410  			allNodesNum:      1000,
  1411  			filteredNodesNum: 500,
  1412  		},
  1413  	}
  1414  	for _, tt := range tests {
  1415  		b.Run(tt.name, func(b *testing.B) {
  1416  			_, ctx := ktesting.NewTestContext(b)
  1417  			existingPods, allNodes, filteredNodes := st.MakeNodesAndPodsForEvenPodsSpread(tt.pod.Labels, tt.existingPodsNum, tt.allNodesNum, tt.filteredNodesNum)
  1418  			state := framework.NewCycleState()
  1419  			pl := plugintesting.SetupPlugin(ctx, b, podTopologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, cache.NewSnapshot(existingPods, allNodes))
  1420  			p := pl.(*PodTopologySpread)
  1421  
  1422  			status := p.PreScore(ctx, state, tt.pod, tf.BuildNodeInfos(filteredNodes))
  1423  			if !status.IsSuccess() {
  1424  				b.Fatalf("unexpected error: %v", status)
  1425  			}
  1426  			b.ResetTimer()
  1427  
  1428  			for i := 0; i < b.N; i++ {
  1429  				var gotList framework.NodeScoreList
  1430  				for _, n := range filteredNodes {
  1431  					nodeName := n.Name
  1432  					score, status := p.Score(context.Background(), state, tt.pod, nodeName)
  1433  					if !status.IsSuccess() {
  1434  						b.Fatalf("unexpected error: %v", status)
  1435  					}
  1436  					gotList = append(gotList, framework.NodeScore{Name: nodeName, Score: score})
  1437  				}
  1438  
  1439  				status = p.NormalizeScore(context.Background(), state, tt.pod, gotList)
  1440  				if !status.IsSuccess() {
  1441  					b.Fatal(status)
  1442  				}
  1443  			}
  1444  		})
  1445  	}
  1446  }