k8s.io/kubernetes@v1.29.3/test/integration/scheduler/scoring/priorities_test.go (about)

     1  /*
     2  Copyright 2017 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 scoring
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    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/apimachinery/pkg/util/intstr"
    30  	"k8s.io/apimachinery/pkg/util/wait"
    31  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    32  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    33  	configv1 "k8s.io/kube-scheduler/config/v1"
    34  	"k8s.io/kubernetes/pkg/features"
    35  	"k8s.io/kubernetes/pkg/scheduler"
    36  	configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
    37  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/imagelocality"
    38  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity"
    39  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity"
    40  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources"
    41  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/podtopologyspread"
    42  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    43  	testutils "k8s.io/kubernetes/test/integration/util"
    44  	imageutils "k8s.io/kubernetes/test/utils/image"
    45  	"k8s.io/utils/pointer"
    46  )
    47  
    48  // imported from testutils
    49  var (
    50  	runPausePod                  = testutils.RunPausePod
    51  	createAndWaitForNodesInCache = testutils.CreateAndWaitForNodesInCache
    52  	createNode                   = testutils.CreateNode
    53  	createNamespacesWithLabels   = testutils.CreateNamespacesWithLabels
    54  	runPodWithContainers         = testutils.RunPodWithContainers
    55  	initPausePod                 = testutils.InitPausePod
    56  	initPodWithContainers        = testutils.InitPodWithContainers
    57  	podScheduledIn               = testutils.PodScheduledIn
    58  	podUnschedulable             = testutils.PodUnschedulable
    59  )
    60  
    61  var (
    62  	hardSpread   = v1.DoNotSchedule
    63  	softSpread   = v1.ScheduleAnyway
    64  	ignorePolicy = v1.NodeInclusionPolicyIgnore
    65  	honorPolicy  = v1.NodeInclusionPolicyHonor
    66  	taints       = []v1.Taint{{Key: v1.TaintNodeUnschedulable, Value: "", Effect: v1.TaintEffectPreferNoSchedule}}
    67  )
    68  
    69  const (
    70  	resourceGPU  = "example.com/gpu"
    71  	pollInterval = 100 * time.Millisecond
    72  )
    73  
    74  // This file tests the scheduler priority functions.
    75  func initTestSchedulerForPriorityTest(t *testing.T, preScorePluginName, scorePluginName string) *testutils.TestContext {
    76  	cc := configv1.KubeSchedulerConfiguration{
    77  		Profiles: []configv1.KubeSchedulerProfile{{
    78  			SchedulerName: pointer.String(v1.DefaultSchedulerName),
    79  			Plugins: &configv1.Plugins{
    80  				PreScore: configv1.PluginSet{
    81  					Disabled: []configv1.Plugin{
    82  						{Name: "*"},
    83  					},
    84  				},
    85  				Score: configv1.PluginSet{
    86  					Enabled: []configv1.Plugin{
    87  						{Name: scorePluginName, Weight: pointer.Int32(1)},
    88  					},
    89  					Disabled: []configv1.Plugin{
    90  						{Name: "*"},
    91  					},
    92  				},
    93  			},
    94  		}},
    95  	}
    96  	if preScorePluginName != "" {
    97  		cc.Profiles[0].Plugins.PreScore.Enabled = append(cc.Profiles[0].Plugins.PreScore.Enabled, configv1.Plugin{Name: preScorePluginName})
    98  	}
    99  	cfg := configtesting.V1ToInternalWithDefaults(t, cc)
   100  	testCtx := testutils.InitTestSchedulerWithOptions(
   101  		t,
   102  		testutils.InitTestAPIServer(t, strings.ToLower(scorePluginName), nil),
   103  		0,
   104  		scheduler.WithProfiles(cfg.Profiles...),
   105  	)
   106  	testutils.SyncSchedulerInformerFactory(testCtx)
   107  	go testCtx.Scheduler.Run(testCtx.Ctx)
   108  	return testCtx
   109  }
   110  
   111  func initTestSchedulerForNodeResourcesTest(t *testing.T) *testutils.TestContext {
   112  	cfg := configtesting.V1ToInternalWithDefaults(t, configv1.KubeSchedulerConfiguration{
   113  		Profiles: []configv1.KubeSchedulerProfile{
   114  			{
   115  				SchedulerName: pointer.String(v1.DefaultSchedulerName),
   116  			},
   117  			{
   118  				SchedulerName: pointer.String("gpu-binpacking-scheduler"),
   119  				PluginConfig: []configv1.PluginConfig{
   120  					{
   121  						Name: noderesources.Name,
   122  						Args: runtime.RawExtension{Object: &configv1.NodeResourcesFitArgs{
   123  							ScoringStrategy: &configv1.ScoringStrategy{
   124  								Type: configv1.MostAllocated,
   125  								Resources: []configv1.ResourceSpec{
   126  									{Name: string(v1.ResourceCPU), Weight: 1},
   127  									{Name: string(v1.ResourceMemory), Weight: 1},
   128  									{Name: resourceGPU, Weight: 2}},
   129  							},
   130  						}},
   131  					},
   132  				},
   133  			},
   134  		},
   135  	})
   136  	testCtx := testutils.InitTestSchedulerWithOptions(
   137  		t,
   138  		testutils.InitTestAPIServer(t, strings.ToLower(noderesources.Name), nil),
   139  		0,
   140  		scheduler.WithProfiles(cfg.Profiles...),
   141  	)
   142  	testutils.SyncSchedulerInformerFactory(testCtx)
   143  	go testCtx.Scheduler.Run(testCtx.Ctx)
   144  	return testCtx
   145  }
   146  
   147  // TestNodeResourcesScoring verifies that scheduler's node resources priority function
   148  // works correctly.
   149  func TestNodeResourcesScoring(t *testing.T) {
   150  	testCtx := initTestSchedulerForNodeResourcesTest(t)
   151  	// Add a few nodes.
   152  	_, err := createAndWaitForNodesInCache(testCtx, "testnode", st.MakeNode().Capacity(
   153  		map[v1.ResourceName]string{
   154  			v1.ResourceCPU:    "8",
   155  			v1.ResourceMemory: "16G",
   156  			resourceGPU:       "4",
   157  		}), 2)
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  	cpuBoundPod1, err := runPausePod(testCtx.ClientSet, st.MakePod().Namespace(testCtx.NS.Name).Name("cpubound1").Res(
   162  		map[v1.ResourceName]string{
   163  			v1.ResourceCPU:    "2",
   164  			v1.ResourceMemory: "4G",
   165  			resourceGPU:       "1",
   166  		},
   167  	).Obj())
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	gpuBoundPod1, err := runPausePod(testCtx.ClientSet, st.MakePod().Namespace(testCtx.NS.Name).Name("gpubound1").Res(
   172  		map[v1.ResourceName]string{
   173  			v1.ResourceCPU:    "1",
   174  			v1.ResourceMemory: "2G",
   175  			resourceGPU:       "2",
   176  		},
   177  	).Obj())
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  	if cpuBoundPod1.Spec.NodeName == "" || gpuBoundPod1.Spec.NodeName == "" {
   182  		t.Fatalf("pods should have nodeName assigned, got %q and %q",
   183  			cpuBoundPod1.Spec.NodeName, gpuBoundPod1.Spec.NodeName)
   184  	}
   185  
   186  	// Since both pods used the default scheduler, then they should land on two different
   187  	// nodes because the default configuration uses LeastAllocated.
   188  	if cpuBoundPod1.Spec.NodeName == gpuBoundPod1.Spec.NodeName {
   189  		t.Fatalf("pods should have landed on different nodes, both scheduled on %q",
   190  			cpuBoundPod1.Spec.NodeName)
   191  	}
   192  
   193  	// The following pod is using the gpu-binpacking-scheduler profile, which gives a higher weight to
   194  	// GPU-based binpacking, and so it should land on the node with higher GPU utilization.
   195  	cpuBoundPod2, err := runPausePod(testCtx.ClientSet, st.MakePod().Namespace(testCtx.NS.Name).Name("cpubound2").SchedulerName("gpu-binpacking-scheduler").Res(
   196  		map[v1.ResourceName]string{
   197  			v1.ResourceCPU:    "2",
   198  			v1.ResourceMemory: "4G",
   199  			resourceGPU:       "1",
   200  		},
   201  	).Obj())
   202  	if err != nil {
   203  		t.Fatal(err)
   204  	}
   205  	if cpuBoundPod2.Spec.NodeName != gpuBoundPod1.Spec.NodeName {
   206  		t.Errorf("pods should have landed on the same node")
   207  	}
   208  }
   209  
   210  // TestNodeAffinityScoring verifies that scheduler's node affinity priority function
   211  // works correctly.
   212  func TestNodeAffinityScoring(t *testing.T) {
   213  	testCtx := initTestSchedulerForPriorityTest(t, nodeaffinity.Name, nodeaffinity.Name)
   214  	// Add a few nodes.
   215  	_, err := createAndWaitForNodesInCache(testCtx, "testnode", st.MakeNode(), 4)
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	// Add a label to one of the nodes.
   220  	labelKey := "kubernetes.io/node-topologyKey"
   221  	labelValue := "topologyvalue"
   222  	labeledNode, err := createNode(testCtx.ClientSet, st.MakeNode().Name("testnode-4").Label(labelKey, labelValue).Obj())
   223  	if err != nil {
   224  		t.Fatalf("Cannot create labeled node: %v", err)
   225  	}
   226  
   227  	// Create a pod with node affinity.
   228  	podName := "pod-with-node-affinity"
   229  	pod, err := runPausePod(testCtx.ClientSet, initPausePod(&testutils.PausePodConfig{
   230  		Name:      podName,
   231  		Namespace: testCtx.NS.Name,
   232  		Affinity: &v1.Affinity{
   233  			NodeAffinity: &v1.NodeAffinity{
   234  				PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
   235  					{
   236  						Preference: v1.NodeSelectorTerm{
   237  							MatchExpressions: []v1.NodeSelectorRequirement{
   238  								{
   239  									Key:      labelKey,
   240  									Operator: v1.NodeSelectorOpIn,
   241  									Values:   []string{labelValue},
   242  								},
   243  							},
   244  						},
   245  						Weight: 20,
   246  					},
   247  				},
   248  			},
   249  		},
   250  	}))
   251  	if err != nil {
   252  		t.Fatalf("Error running pause pod: %v", err)
   253  	}
   254  	if pod.Spec.NodeName != labeledNode.Name {
   255  		t.Errorf("Pod %v got scheduled on an unexpected node: %v. Expected node: %v.", podName, pod.Spec.NodeName, labeledNode.Name)
   256  	} else {
   257  		t.Logf("Pod %v got successfully scheduled on node %v.", podName, pod.Spec.NodeName)
   258  	}
   259  }
   260  
   261  // TestPodAffinityScoring verifies that scheduler's pod affinity priority function
   262  // works correctly.
   263  func TestPodAffinityScoring(t *testing.T) {
   264  	labelKey := "service"
   265  	labelValue := "S1"
   266  	topologyKey := "node-topologykey"
   267  	topologyValues := []string{}
   268  	for i := 0; i < 5; i++ {
   269  		topologyValues = append(topologyValues, fmt.Sprintf("topologyvalue%d", i))
   270  	}
   271  	tests := []struct {
   272  		name         string
   273  		pod          *testutils.PausePodConfig
   274  		existingPods []*testutils.PausePodConfig
   275  		nodes        []*v1.Node
   276  		// expectedNodeName is the list of node names. The pod should be scheduled on either of them.
   277  		expectedNodeName               []string
   278  		enableMatchLabelKeysInAffinity bool
   279  	}{
   280  		{
   281  			name: "pod affinity",
   282  			pod: &testutils.PausePodConfig{
   283  				Name:      "pod1",
   284  				Namespace: "ns1",
   285  				Affinity: &v1.Affinity{
   286  					PodAffinity: &v1.PodAffinity{
   287  						PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
   288  							{
   289  								PodAffinityTerm: v1.PodAffinityTerm{
   290  									LabelSelector: &metav1.LabelSelector{
   291  										MatchExpressions: []metav1.LabelSelectorRequirement{
   292  											{
   293  												Key:      labelKey,
   294  												Operator: metav1.LabelSelectorOpIn,
   295  												Values:   []string{labelValue, "S3"},
   296  											},
   297  										},
   298  									},
   299  									TopologyKey: topologyKey,
   300  								},
   301  								Weight: 50,
   302  							},
   303  						},
   304  					},
   305  				},
   306  			},
   307  			existingPods: []*testutils.PausePodConfig{
   308  				{
   309  					Name:      "attractor-pod",
   310  					Namespace: "ns1",
   311  					Labels:    map[string]string{labelKey: labelValue},
   312  					NodeName:  "node1",
   313  				},
   314  			},
   315  			nodes: []*v1.Node{
   316  				st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
   317  				st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
   318  				st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
   319  				st.MakeNode().Name("node4").Label(topologyKey, topologyValues[3]).Obj(),
   320  				st.MakeNode().Name("node5").Label(topologyKey, topologyValues[4]).Obj(),
   321  				st.MakeNode().Name("node6").Label(topologyKey, topologyValues[0]).Obj(),
   322  				st.MakeNode().Name("node7").Label(topologyKey, topologyValues[1]).Obj(),
   323  				st.MakeNode().Name("node8").Label(topologyKey, topologyValues[2]).Obj(),
   324  				st.MakeNode().Name("node9").Label(topologyKey, topologyValues[3]).Obj(),
   325  				st.MakeNode().Name("node10").Label(topologyKey, topologyValues[4]).Obj(),
   326  				st.MakeNode().Name("other-node1").Obj(),
   327  				st.MakeNode().Name("other-node2").Obj(),
   328  			},
   329  			expectedNodeName: []string{"node1", "node6"},
   330  		},
   331  		{
   332  			name: "pod affinity with namespace selector",
   333  			pod: &testutils.PausePodConfig{
   334  				Name:      "pod1",
   335  				Namespace: "ns2",
   336  				Affinity: &v1.Affinity{
   337  					PodAffinity: &v1.PodAffinity{
   338  						PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
   339  							{
   340  								PodAffinityTerm: v1.PodAffinityTerm{
   341  									NamespaceSelector: &metav1.LabelSelector{}, // all namespaces
   342  									LabelSelector: &metav1.LabelSelector{
   343  										MatchExpressions: []metav1.LabelSelectorRequirement{
   344  											{
   345  												Key:      labelKey,
   346  												Operator: metav1.LabelSelectorOpIn,
   347  												Values:   []string{labelValue, "S3"},
   348  											},
   349  										},
   350  									},
   351  									TopologyKey: topologyKey,
   352  								},
   353  								Weight: 50,
   354  							},
   355  						},
   356  					},
   357  				},
   358  			},
   359  			existingPods: []*testutils.PausePodConfig{
   360  				{
   361  					Name:      "attractor-pod",
   362  					Namespace: "ns1",
   363  					Labels:    map[string]string{labelKey: labelValue},
   364  					NodeName:  "node1",
   365  				},
   366  			},
   367  			nodes: []*v1.Node{
   368  				st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
   369  				st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
   370  				st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
   371  				st.MakeNode().Name("node4").Label(topologyKey, topologyValues[3]).Obj(),
   372  				st.MakeNode().Name("node5").Label(topologyKey, topologyValues[4]).Obj(),
   373  				st.MakeNode().Name("node6").Label(topologyKey, topologyValues[0]).Obj(),
   374  				st.MakeNode().Name("node7").Label(topologyKey, topologyValues[1]).Obj(),
   375  				st.MakeNode().Name("node8").Label(topologyKey, topologyValues[2]).Obj(),
   376  				st.MakeNode().Name("node9").Label(topologyKey, topologyValues[3]).Obj(),
   377  				st.MakeNode().Name("node10").Label(topologyKey, topologyValues[4]).Obj(),
   378  				st.MakeNode().Name("other-node1").Obj(),
   379  				st.MakeNode().Name("other-node2").Obj(),
   380  			},
   381  			expectedNodeName: []string{"node1", "node6"},
   382  		},
   383  		{
   384  			name: "anti affinity: matchLabelKeys is merged into LabelSelector with In operator (feature flag: enabled)",
   385  			pod: &testutils.PausePodConfig{
   386  				Name:      "incoming",
   387  				Namespace: "ns1",
   388  				Labels:    map[string]string{"foo": "", "bar": "a"},
   389  				Affinity: &v1.Affinity{
   390  					PodAntiAffinity: &v1.PodAntiAffinity{
   391  						PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
   392  							{
   393  								PodAffinityTerm: v1.PodAffinityTerm{
   394  									TopologyKey: topologyKey,
   395  									LabelSelector: &metav1.LabelSelector{
   396  										MatchExpressions: []metav1.LabelSelectorRequirement{
   397  											{
   398  												Key:      "foo",
   399  												Operator: metav1.LabelSelectorOpExists,
   400  											},
   401  										},
   402  									},
   403  									MatchLabelKeys: []string{"bar"},
   404  								},
   405  								Weight: 50,
   406  							},
   407  						},
   408  					},
   409  				},
   410  			},
   411  			existingPods: []*testutils.PausePodConfig{
   412  				// It matches the incoming Pod's anti affinity's labelSelector.
   413  				// BUT, the matchLabelKeys make the existing Pod's anti affinity's labelSelector not match with this label.
   414  				{
   415  					NodeName:  "node1",
   416  					Name:      "pod1",
   417  					Namespace: "ns1",
   418  					Labels:    map[string]string{"foo": "", "bar": "fuga"},
   419  				},
   420  				// It matches the incoming Pod's anti affinity.
   421  				{
   422  					NodeName:  "node2",
   423  					Name:      "pod2",
   424  					Namespace: "ns1",
   425  					Labels:    map[string]string{"foo": "", "bar": "a"},
   426  				},
   427  			},
   428  			nodes: []*v1.Node{
   429  				st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
   430  				st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
   431  			},
   432  			expectedNodeName:               []string{"node1"},
   433  			enableMatchLabelKeysInAffinity: true,
   434  		},
   435  		{
   436  			name: "anti affinity: mismatchLabelKeys is merged into LabelSelector with NotIn operator  (feature flag: enabled)",
   437  			pod: &testutils.PausePodConfig{
   438  				Name:      "incoming",
   439  				Namespace: "ns1",
   440  				Labels:    map[string]string{"foo": "", "bar": "a"},
   441  				Affinity: &v1.Affinity{
   442  					PodAntiAffinity: &v1.PodAntiAffinity{
   443  						PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
   444  							{
   445  								PodAffinityTerm: v1.PodAffinityTerm{
   446  									TopologyKey: topologyKey,
   447  									LabelSelector: &metav1.LabelSelector{
   448  										MatchExpressions: []metav1.LabelSelectorRequirement{
   449  											{
   450  												Key:      "foo",
   451  												Operator: metav1.LabelSelectorOpExists,
   452  											},
   453  										},
   454  									},
   455  									MismatchLabelKeys: []string{"bar"},
   456  								},
   457  								Weight: 50,
   458  							},
   459  						},
   460  					},
   461  				},
   462  			},
   463  			existingPods: []*testutils.PausePodConfig{
   464  				// It matches the incoming Pod's anti affinity's labelSelector.
   465  				{
   466  					NodeName:  "node1",
   467  					Name:      "pod1",
   468  					Namespace: "ns1",
   469  					Labels:    map[string]string{"foo": "", "bar": "fuga"},
   470  				},
   471  				// It matches the incoming Pod's affinity.
   472  				// But, the mismatchLabelKeys make the existing Pod's anti affinity's labelSelector not match with this label.
   473  				{
   474  					NodeName:  "node2",
   475  					Name:      "pod2",
   476  					Namespace: "ns1",
   477  					Labels:    map[string]string{"foo": "", "bar": "a"},
   478  				},
   479  			},
   480  			nodes: []*v1.Node{
   481  				st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
   482  				st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
   483  			},
   484  			expectedNodeName:               []string{"node2"},
   485  			enableMatchLabelKeysInAffinity: true,
   486  		},
   487  		{
   488  			name: "affinity: matchLabelKeys is merged into LabelSelector with In operator (feature flag: enabled)",
   489  			pod: &testutils.PausePodConfig{
   490  				Affinity: &v1.Affinity{
   491  					PodAffinity: &v1.PodAffinity{
   492  						PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
   493  							{
   494  								// affinity with pod3.
   495  								PodAffinityTerm: v1.PodAffinityTerm{
   496  									TopologyKey: topologyKey,
   497  									LabelSelector: &metav1.LabelSelector{
   498  										MatchExpressions: []metav1.LabelSelectorRequirement{
   499  											{
   500  												Key:      "foo",
   501  												Operator: metav1.LabelSelectorOpExists,
   502  											},
   503  										},
   504  									},
   505  									MatchLabelKeys: []string{"bar"},
   506  								},
   507  								Weight: 50,
   508  							},
   509  							{
   510  								// affinity with pod1 and pod2.
   511  								// schedule this Pod by this weaker affinity
   512  								// if `matchLabelKeys` above isn't working correctly.
   513  								PodAffinityTerm: v1.PodAffinityTerm{
   514  									TopologyKey: topologyKey,
   515  									LabelSelector: &metav1.LabelSelector{
   516  										MatchExpressions: []metav1.LabelSelectorRequirement{
   517  											{
   518  												Key:      "bar",
   519  												Operator: metav1.LabelSelectorOpIn,
   520  												Values:   []string{"hoge"},
   521  											},
   522  										},
   523  									},
   524  								},
   525  								Weight: 10,
   526  							},
   527  						},
   528  					},
   529  				},
   530  				Name:      "incoming",
   531  				Namespace: "ns1",
   532  				Labels:    map[string]string{"foo": "", "bar": "a"},
   533  			},
   534  			existingPods: []*testutils.PausePodConfig{
   535  				{
   536  					NodeName:  "node1",
   537  					Name:      "pod1",
   538  					Namespace: "ns1",
   539  					Labels:    map[string]string{"foo": "", "bar": "hoge"},
   540  				},
   541  				{
   542  					NodeName:  "node2",
   543  					Name:      "pod2",
   544  					Namespace: "ns1",
   545  					Labels:    map[string]string{"foo": "", "bar": "hoge"},
   546  				},
   547  				{
   548  					NodeName:  "node3",
   549  					Name:      "pod3",
   550  					Namespace: "ns1",
   551  					Labels:    map[string]string{"foo": "", "bar": "a"},
   552  				},
   553  			},
   554  			enableMatchLabelKeysInAffinity: true,
   555  			nodes: []*v1.Node{
   556  				st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
   557  				st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
   558  				st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
   559  				st.MakeNode().Name("node4").Label(topologyKey, topologyValues[0]).Obj(),
   560  				st.MakeNode().Name("node5").Label(topologyKey, topologyValues[1]).Obj(),
   561  				st.MakeNode().Name("node6").Label(topologyKey, topologyValues[2]).Obj(),
   562  			},
   563  			expectedNodeName: []string{"node3", "node6"},
   564  		},
   565  		{
   566  			name: "affinity: mismatchLabelKeys is merged into LabelSelector with NotIn operator (feature flag: enabled)",
   567  			pod: &testutils.PausePodConfig{
   568  				Affinity: &v1.Affinity{
   569  					PodAffinity: &v1.PodAffinity{
   570  						PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
   571  							{
   572  								// affinity with pod3.
   573  								PodAffinityTerm: v1.PodAffinityTerm{
   574  									TopologyKey: topologyKey,
   575  									LabelSelector: &metav1.LabelSelector{
   576  										MatchExpressions: []metav1.LabelSelectorRequirement{
   577  											{
   578  												Key:      "foo",
   579  												Operator: metav1.LabelSelectorOpExists,
   580  											},
   581  										},
   582  									},
   583  									MismatchLabelKeys: []string{"bar"},
   584  								},
   585  								Weight: 50,
   586  							},
   587  							{
   588  								// affinity with pod1 and pod2.
   589  								// schedule this Pod by this weaker affinity
   590  								// if `matchLabelKeys` above isn't working correctly.
   591  								PodAffinityTerm: v1.PodAffinityTerm{
   592  									TopologyKey: topologyKey,
   593  									LabelSelector: &metav1.LabelSelector{
   594  										MatchExpressions: []metav1.LabelSelectorRequirement{
   595  											{
   596  												Key:      "bar",
   597  												Operator: metav1.LabelSelectorOpIn,
   598  												Values:   []string{"hoge"},
   599  											},
   600  										},
   601  									},
   602  								},
   603  								Weight: 10,
   604  							},
   605  						},
   606  					},
   607  				},
   608  				Name:      "incoming",
   609  				Namespace: "ns1",
   610  				Labels:    map[string]string{"foo": "", "bar": "a"},
   611  			},
   612  			existingPods: []*testutils.PausePodConfig{
   613  				{
   614  					NodeName:  "node1",
   615  					Name:      "pod1",
   616  					Namespace: "ns1",
   617  					Labels:    map[string]string{"foo": "", "bar": "a"},
   618  				},
   619  				{
   620  					NodeName:  "node2",
   621  					Name:      "pod2",
   622  					Namespace: "ns1",
   623  					Labels:    map[string]string{"foo": "", "bar": "a"},
   624  				},
   625  				{
   626  					NodeName:  "node3",
   627  					Name:      "pod3",
   628  					Namespace: "ns1",
   629  					Labels:    map[string]string{"foo": "", "bar": "hoge"},
   630  				},
   631  			},
   632  			enableMatchLabelKeysInAffinity: true,
   633  			nodes: []*v1.Node{
   634  				st.MakeNode().Name("node1").Label(topologyKey, topologyValues[0]).Obj(),
   635  				st.MakeNode().Name("node2").Label(topologyKey, topologyValues[1]).Obj(),
   636  				st.MakeNode().Name("node3").Label(topologyKey, topologyValues[2]).Obj(),
   637  				st.MakeNode().Name("node4").Label(topologyKey, topologyValues[0]).Obj(),
   638  				st.MakeNode().Name("node5").Label(topologyKey, topologyValues[1]).Obj(),
   639  				st.MakeNode().Name("node6").Label(topologyKey, topologyValues[2]).Obj(),
   640  			},
   641  			expectedNodeName: []string{"node3", "node6"},
   642  		},
   643  	}
   644  
   645  	for _, tt := range tests {
   646  		t.Run(tt.name, func(t *testing.T) {
   647  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodAffinity, tt.enableMatchLabelKeysInAffinity)()
   648  
   649  			testCtx := initTestSchedulerForPriorityTest(t, interpodaffinity.Name, interpodaffinity.Name)
   650  			if err := createNamespacesWithLabels(testCtx.ClientSet, []string{"ns1", "ns2"}, map[string]string{"team": "team1"}); err != nil {
   651  				t.Fatal(err)
   652  			}
   653  
   654  			for _, n := range tt.nodes {
   655  				if _, err := createNode(testCtx.ClientSet, n); err != nil {
   656  					t.Fatalf("failed to create node: %v", err)
   657  				}
   658  			}
   659  
   660  			for _, p := range tt.existingPods {
   661  				if _, err := runPausePod(testCtx.ClientSet, initPausePod(p)); err != nil {
   662  					t.Fatalf("failed to create existing pod: %v", err)
   663  				}
   664  			}
   665  
   666  			pod, err := runPausePod(testCtx.ClientSet, initPausePod(tt.pod))
   667  			if err != nil {
   668  				t.Fatalf("Error running pause pod: %v", err)
   669  			}
   670  
   671  			err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, podScheduledIn(testCtx.ClientSet, pod.Namespace, pod.Name, tt.expectedNodeName))
   672  			if err != nil {
   673  				t.Errorf("Error while trying to wait for a pod to be scheduled: %v", err)
   674  			}
   675  		})
   676  	}
   677  }
   678  
   679  // TestImageLocalityScoring verifies that the scheduler's image locality priority function
   680  // works correctly, i.e., the pod gets scheduled to the node where its container images are ready.
   681  func TestImageLocalityScoring(t *testing.T) {
   682  	testCtx := initTestSchedulerForPriorityTest(t, "", imagelocality.Name)
   683  
   684  	// Create a node with the large image.
   685  	// We use a fake large image as the test image used by the pod, which has
   686  	// relatively large image size.
   687  	imageName := "fake-large-image:v1"
   688  	nodeWithLargeImage, err := createNode(
   689  		testCtx.ClientSet,
   690  		st.MakeNode().Name("testnode-large-image").Images(map[string]int64{imageName: 3000 * 1024 * 1024}).Obj(),
   691  	)
   692  	if err != nil {
   693  		t.Fatalf("cannot create node with a large image: %v", err)
   694  	}
   695  
   696  	// Add a few nodes.
   697  	_, err = createAndWaitForNodesInCache(testCtx, "testnode", st.MakeNode(), 10)
   698  	if err != nil {
   699  		t.Fatal(err)
   700  	}
   701  
   702  	// Create a pod with containers each having the specified image.
   703  	podName := "pod-using-large-image"
   704  	pod, err := runPodWithContainers(testCtx.ClientSet, initPodWithContainers(testCtx.ClientSet, &testutils.PodWithContainersConfig{
   705  		Name:       podName,
   706  		Namespace:  testCtx.NS.Name,
   707  		Containers: makeContainersWithImages([]string{imageName}),
   708  	}))
   709  	if err != nil {
   710  		t.Fatalf("error running pod with images: %v", err)
   711  	}
   712  	if pod.Spec.NodeName != nodeWithLargeImage.Name {
   713  		t.Errorf("pod %v got scheduled on an unexpected node: %v. Expected node: %v.", podName, pod.Spec.NodeName, nodeWithLargeImage.Name)
   714  	} else {
   715  		t.Logf("pod %v got successfully scheduled on node %v.", podName, pod.Spec.NodeName)
   716  	}
   717  }
   718  
   719  // makeContainerWithImage returns a list of v1.Container objects for each given image. Duplicates of an image are ignored,
   720  // i.e., each image is used only once.
   721  func makeContainersWithImages(images []string) []v1.Container {
   722  	var containers []v1.Container
   723  	usedImages := make(map[string]struct{})
   724  
   725  	for _, image := range images {
   726  		if _, ok := usedImages[image]; !ok {
   727  			containers = append(containers, v1.Container{
   728  				Name:  strings.Replace(image, ":", "-", -1) + "-container",
   729  				Image: image,
   730  			})
   731  			usedImages[image] = struct{}{}
   732  		}
   733  	}
   734  	return containers
   735  }
   736  
   737  // TestPodTopologySpreadScoring verifies that the PodTopologySpread Score plugin works.
   738  func TestPodTopologySpreadScoring(t *testing.T) {
   739  	pause := imageutils.GetPauseImageName()
   740  	taint := v1.Taint{
   741  		Key:    "k1",
   742  		Value:  "v1",
   743  		Effect: v1.TaintEffectNoSchedule,
   744  	}
   745  
   746  	//  default nodes with labels "zone: zone-{0,1}" and "node: <node name>".
   747  	defaultNodes := []*v1.Node{
   748  		st.MakeNode().Name("node-0").Label("node", "node-0").Label("zone", "zone-0").Taints([]v1.Taint{taint}).Obj(),
   749  		st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-0").Obj(),
   750  		st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Obj(),
   751  		st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-1").Obj(),
   752  	}
   753  
   754  	tests := []struct {
   755  		name                      string
   756  		incomingPod               *v1.Pod
   757  		existingPods              []*v1.Pod
   758  		fits                      bool
   759  		nodes                     []*v1.Node
   760  		want                      []string // nodes expected to schedule onto
   761  		enableNodeInclusionPolicy bool
   762  		enableMatchLabelKeys      bool
   763  	}{
   764  		// note: naming starts at index 0
   765  		// the symbol ~X~ means that node is infeasible
   766  		{
   767  			name: "place pod on a ~0~/1/2/3 cluster with MaxSkew=1, node-1 is the preferred fit",
   768  			incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
   769  				SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
   770  				Obj(),
   771  			existingPods: []*v1.Pod{
   772  				st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
   773  				st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
   774  				st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
   775  				st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
   776  				st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
   777  				st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
   778  			},
   779  			fits:  true,
   780  			nodes: defaultNodes,
   781  			want:  []string{"node-1"},
   782  		},
   783  		{
   784  			name: "combined with hardSpread constraint on a ~4~/0/1/2 cluster",
   785  			incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
   786  				SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
   787  				SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
   788  				Obj(),
   789  			existingPods: []*v1.Pod{
   790  				st.MakePod().Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(),
   791  				st.MakePod().Name("p0b").Node("node-0").Label("foo", "").Container(pause).Obj(),
   792  				st.MakePod().Name("p0c").Node("node-0").Label("foo", "").Container(pause).Obj(),
   793  				st.MakePod().Name("p0d").Node("node-0").Label("foo", "").Container(pause).Obj(),
   794  				st.MakePod().Name("p2").Node("node-2").Label("foo", "").Container(pause).Obj(),
   795  				st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
   796  				st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
   797  			},
   798  			fits:  true,
   799  			nodes: defaultNodes,
   800  			want:  []string{"node-2"},
   801  		},
   802  		{
   803  			// 1. to fulfil "zone" constraint, pods spread across zones as ~3~/0
   804  			// 2. to fulfil "node" constraint, pods spread across zones as 1/~2~/0/~0~
   805  			// node-2 and node 4 are filtered out by plugins
   806  			name: "soft constraint with two node inclusion Constraints, zone: honor/ignore, node: honor/ignore",
   807  			incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
   808  				NodeSelector(map[string]string{"foo": ""}).
   809  				SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
   810  				SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, nil).
   811  				Obj(),
   812  			existingPods: []*v1.Pod{
   813  				st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
   814  				st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
   815  				st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
   816  				st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(),
   817  			},
   818  			fits: true,
   819  			nodes: []*v1.Node{
   820  				st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(),
   821  				st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Taints(taints).Obj(),
   822  				st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Label("foo", "").Obj(),
   823  				st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Obj(),
   824  			},
   825  			want:                      []string{"node-3"},
   826  			enableNodeInclusionPolicy: true,
   827  		},
   828  		{
   829  			// 1. to fulfil "zone" constraint, pods spread across zones as ~3~/~1~
   830  			// 2. to fulfil "node" constraint, pods spread across zones as 1/~0~/0/~0~
   831  			// node-2 and node 4 are filtered out by plugins
   832  			name: "soft constraint with two node inclusion Constraints, zone: ignore/ignore, node: honor/honor",
   833  			incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
   834  				NodeSelector(map[string]string{"foo": ""}).
   835  				SpreadConstraint(1, "zone", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, &ignorePolicy, nil, nil).
   836  				SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, &honorPolicy, nil).
   837  				Obj(),
   838  			existingPods: []*v1.Pod{
   839  				st.MakePod().Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
   840  				st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
   841  				st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
   842  				st.MakePod().Name("p4a").Node("node-4").Label("foo", "").Container(pause).Obj(),
   843  			},
   844  			fits: true,
   845  			nodes: []*v1.Node{
   846  				st.MakeNode().Name("node-1").Label("node", "node-1").Label("zone", "zone-1").Label("foo", "").Obj(),
   847  				st.MakeNode().Name("node-2").Label("node", "node-2").Label("zone", "zone-1").Label("foo", "").Taints(taints).Obj(),
   848  				st.MakeNode().Name("node-3").Label("node", "node-3").Label("zone", "zone-2").Label("foo", "").Obj(),
   849  				st.MakeNode().Name("node-4").Label("node", "node-4").Label("zone", "zone-2").Obj(),
   850  			},
   851  			want:                      []string{"node-3"},
   852  			enableNodeInclusionPolicy: true,
   853  		},
   854  		{
   855  			name: "matchLabelKeys ignored when feature gate disabled, node-1 is the preferred fit",
   856  			incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
   857  				SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
   858  				Obj(),
   859  			existingPods: []*v1.Pod{
   860  				st.MakePod().Name("p1").Node("node-1").Label("foo", "").Label("bar", "").Container(pause).Obj(),
   861  				st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
   862  				st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
   863  				st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
   864  				st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
   865  				st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
   866  			},
   867  			fits:                 true,
   868  			nodes:                defaultNodes,
   869  			want:                 []string{"node-1"},
   870  			enableMatchLabelKeys: false,
   871  		},
   872  		{
   873  			name: "matchLabelKeys ANDed with LabelSelector when LabelSelector isn't empty, node-2 is the preferred fit",
   874  			incomingPod: st.MakePod().Name("p").Label("foo", "").Label("bar", "").Container(pause).
   875  				SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Exists("foo").Obj(), nil, nil, nil, []string{"bar"}).
   876  				Obj(),
   877  			existingPods: []*v1.Pod{
   878  				st.MakePod().Name("p1").Node("node-1").Label("foo", "").Label("bar", "").Container(pause).Obj(),
   879  				st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
   880  				st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
   881  				st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
   882  				st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Label("bar", "").Container(pause).Obj(),
   883  				st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
   884  			},
   885  			fits:                 true,
   886  			nodes:                defaultNodes,
   887  			want:                 []string{"node-2"},
   888  			enableMatchLabelKeys: true,
   889  		},
   890  		{
   891  			name: "matchLabelKeys ANDed with LabelSelector when LabelSelector is empty, node-1 is the preferred fit",
   892  			incomingPod: st.MakePod().Name("p").Label("foo", "").Container(pause).
   893  				SpreadConstraint(1, "node", softSpread, st.MakeLabelSelector().Obj(), nil, nil, nil, []string{"foo"}).
   894  				Obj(),
   895  			existingPods: []*v1.Pod{
   896  				st.MakePod().Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
   897  				st.MakePod().Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
   898  				st.MakePod().Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
   899  				st.MakePod().Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
   900  				st.MakePod().Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
   901  				st.MakePod().Name("p3c").Node("node-3").Label("foo", "").Container(pause).Obj(),
   902  			},
   903  			fits:                 true,
   904  			nodes:                defaultNodes,
   905  			want:                 []string{"node-1"},
   906  			enableMatchLabelKeys: true,
   907  		},
   908  	}
   909  	for _, tt := range tests {
   910  		t.Run(tt.name, func(t *testing.T) {
   911  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.NodeInclusionPolicyInPodTopologySpread, tt.enableNodeInclusionPolicy)()
   912  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MatchLabelKeysInPodTopologySpread, tt.enableMatchLabelKeys)()
   913  
   914  			testCtx := initTestSchedulerForPriorityTest(t, podtopologyspread.Name, podtopologyspread.Name)
   915  			cs := testCtx.ClientSet
   916  			ns := testCtx.NS.Name
   917  
   918  			for i := range tt.nodes {
   919  				if _, err := createNode(cs, tt.nodes[i]); err != nil {
   920  					t.Fatalf("Cannot create node: %v", err)
   921  				}
   922  			}
   923  
   924  			// set namespace to pods
   925  			for i := range tt.existingPods {
   926  				tt.existingPods[i].SetNamespace(ns)
   927  			}
   928  			tt.incomingPod.SetNamespace(ns)
   929  
   930  			allPods := append(tt.existingPods, tt.incomingPod)
   931  			defer testutils.CleanupPods(testCtx.Ctx, cs, t, allPods)
   932  			for _, pod := range tt.existingPods {
   933  				createdPod, err := cs.CoreV1().Pods(pod.Namespace).Create(testCtx.Ctx, pod, metav1.CreateOptions{})
   934  				if err != nil {
   935  					t.Fatalf("Test Failed: error while creating pod during test: %v", err)
   936  				}
   937  				err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false,
   938  					testutils.PodScheduled(cs, createdPod.Namespace, createdPod.Name))
   939  				if err != nil {
   940  					t.Errorf("Test Failed: error while waiting for pod during test: %v", err)
   941  				}
   942  			}
   943  
   944  			testPod, err := cs.CoreV1().Pods(tt.incomingPod.Namespace).Create(testCtx.Ctx, tt.incomingPod, metav1.CreateOptions{})
   945  			if err != nil {
   946  				t.Fatalf("Test Failed: error while creating pod during test: %v", err)
   947  			}
   948  
   949  			if tt.fits {
   950  				err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false,
   951  					podScheduledIn(cs, testPod.Namespace, testPod.Name, tt.want))
   952  			} else {
   953  				err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false,
   954  					podUnschedulable(cs, testPod.Namespace, testPod.Name))
   955  			}
   956  			if err != nil {
   957  				t.Errorf("Test Failed: %v", err)
   958  			}
   959  		})
   960  	}
   961  }
   962  
   963  // TestDefaultPodTopologySpreadScoring verifies that the PodTopologySpread Score plugin
   964  // with the system default spreading spreads Pods belonging to a Service.
   965  // The setup has 300 nodes over 3 zones.
   966  func TestDefaultPodTopologySpreadScoring(t *testing.T) {
   967  	testCtx := initTestSchedulerForPriorityTest(t, podtopologyspread.Name, podtopologyspread.Name)
   968  	cs := testCtx.ClientSet
   969  	ns := testCtx.NS.Name
   970  
   971  	zoneForNode := make(map[string]string)
   972  	for i := 0; i < 300; i++ {
   973  		nodeName := fmt.Sprintf("node-%d", i)
   974  		zone := fmt.Sprintf("zone-%d", i%3)
   975  		zoneForNode[nodeName] = zone
   976  		_, err := createNode(cs, st.MakeNode().Name(nodeName).Label(v1.LabelHostname, nodeName).Label(v1.LabelTopologyZone, zone).Obj())
   977  		if err != nil {
   978  			t.Fatalf("Cannot create node: %v", err)
   979  		}
   980  	}
   981  
   982  	serviceName := "test-service"
   983  	svc := &v1.Service{
   984  		ObjectMeta: metav1.ObjectMeta{
   985  			Name:      serviceName,
   986  			Namespace: ns,
   987  		},
   988  		Spec: v1.ServiceSpec{
   989  			Selector: map[string]string{
   990  				"service": serviceName,
   991  			},
   992  			Ports: []v1.ServicePort{{
   993  				Port:       80,
   994  				TargetPort: intstr.FromInt32(80),
   995  			}},
   996  		},
   997  	}
   998  	_, err := cs.CoreV1().Services(ns).Create(testCtx.Ctx, svc, metav1.CreateOptions{})
   999  	if err != nil {
  1000  		t.Fatalf("Cannot create Service: %v", err)
  1001  	}
  1002  
  1003  	pause := imageutils.GetPauseImageName()
  1004  	totalPodCnt := 0
  1005  	for _, nPods := range []int{3, 9, 15} {
  1006  		// Append nPods each iteration.
  1007  		t.Run(fmt.Sprintf("%d-pods", totalPodCnt+nPods), func(t *testing.T) {
  1008  			for i := 0; i < nPods; i++ {
  1009  				p := st.MakePod().Name(fmt.Sprintf("p-%d", totalPodCnt)).Label("service", serviceName).Container(pause).Obj()
  1010  				_, err = cs.CoreV1().Pods(ns).Create(testCtx.Ctx, p, metav1.CreateOptions{})
  1011  				if err != nil {
  1012  					t.Fatalf("Cannot create Pod: %v", err)
  1013  				}
  1014  				totalPodCnt++
  1015  			}
  1016  			var pods []v1.Pod
  1017  			// Wait for all Pods scheduled.
  1018  			err = wait.PollUntilContextTimeout(testCtx.Ctx, pollInterval, wait.ForeverTestTimeout, false, func(ctx context.Context) (bool, error) {
  1019  				podList, err := cs.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{})
  1020  				if err != nil {
  1021  					t.Fatalf("Cannot list pods to verify scheduling: %v", err)
  1022  				}
  1023  				for _, p := range podList.Items {
  1024  					if p.Spec.NodeName == "" {
  1025  						return false, nil
  1026  					}
  1027  				}
  1028  				pods = podList.Items
  1029  				return true, nil
  1030  			})
  1031  			// Verify zone spreading.
  1032  			zoneCnts := make(map[string]int)
  1033  			for _, p := range pods {
  1034  				zoneCnts[zoneForNode[p.Spec.NodeName]]++
  1035  			}
  1036  			maxCnt := 0
  1037  			minCnt := len(pods)
  1038  			for _, c := range zoneCnts {
  1039  				if c > maxCnt {
  1040  					maxCnt = c
  1041  				}
  1042  				if c < minCnt {
  1043  					minCnt = c
  1044  				}
  1045  			}
  1046  			if skew := maxCnt - minCnt; skew != 0 {
  1047  				t.Errorf("Zone skew is %d, should be 0", skew)
  1048  			}
  1049  		})
  1050  	}
  1051  }