Skip to content

Commit c93dffd

Browse files
committed
E2E tests for PodTopologySpread
1 parent 0e37bce commit c93dffd

File tree

7 files changed

+362
-5
lines changed

7 files changed

+362
-5
lines changed

pkg/features/kube_features.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ const (
503503
EndpointSliceProxying featuregate.Feature = "EndpointSliceProxying"
504504

505505
// owner: @Huang-Wei
506-
// alpha: v1.16
506+
// beta: v1.18
507507
//
508508
// Schedule pods evenly across available topology domains.
509509
EvenPodsSpread featuregate.Feature = "EvenPodsSpread"
@@ -618,7 +618,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
618618
IPv6DualStack: {Default: false, PreRelease: featuregate.Alpha},
619619
EndpointSlice: {Default: true, PreRelease: featuregate.Beta},
620620
EndpointSliceProxying: {Default: false, PreRelease: featuregate.Alpha},
621-
EvenPodsSpread: {Default: false, PreRelease: featuregate.Alpha},
621+
EvenPodsSpread: {Default: true, PreRelease: featuregate.Beta},
622622
StartupProbe: {Default: true, PreRelease: featuregate.Beta},
623623
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta},
624624
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},

pkg/scheduler/algorithmprovider/registry_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func TestClusterAutoscalerProvider(t *testing.T) {
5656
{Name: noderesources.FitName},
5757
{Name: nodeports.Name},
5858
{Name: interpodaffinity.Name},
59+
{Name: podtopologyspread.Name},
5960
},
6061
},
6162
Filter: &schedulerapi.PluginSet{
@@ -74,13 +75,15 @@ func TestClusterAutoscalerProvider(t *testing.T) {
7475
{Name: volumebinding.Name},
7576
{Name: volumezone.Name},
7677
{Name: interpodaffinity.Name},
78+
{Name: podtopologyspread.Name},
7779
},
7880
},
7981
PreScore: &schedulerapi.PluginSet{
8082
Enabled: []schedulerapi.Plugin{
8183
{Name: interpodaffinity.Name},
8284
{Name: defaultpodtopologyspread.Name},
8385
{Name: tainttoleration.Name},
86+
{Name: podtopologyspread.Name},
8487
},
8588
},
8689
Score: &schedulerapi.PluginSet{
@@ -93,6 +96,7 @@ func TestClusterAutoscalerProvider(t *testing.T) {
9396
{Name: nodepreferavoidpods.Name, Weight: 10000},
9497
{Name: defaultpodtopologyspread.Name, Weight: 1},
9598
{Name: tainttoleration.Name, Weight: 1},
99+
{Name: podtopologyspread.Name, Weight: 1},
96100
},
97101
},
98102
Bind: &schedulerapi.PluginSet{

pkg/scheduler/apis/config/testing/compatibility_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,7 @@ func TestAlgorithmProviderCompatibility(t *testing.T) {
14231423
{Name: "NodeResourcesFit"},
14241424
{Name: "NodePorts"},
14251425
{Name: "InterPodAffinity"},
1426+
{Name: "PodTopologySpread"},
14261427
},
14271428
"FilterPlugin": {
14281429
{Name: "NodeUnschedulable"},
@@ -1439,11 +1440,13 @@ func TestAlgorithmProviderCompatibility(t *testing.T) {
14391440
{Name: "VolumeBinding"},
14401441
{Name: "VolumeZone"},
14411442
{Name: "InterPodAffinity"},
1443+
{Name: "PodTopologySpread"},
14421444
},
14431445
"PreScorePlugin": {
14441446
{Name: "InterPodAffinity"},
14451447
{Name: "DefaultPodTopologySpread"},
14461448
{Name: "TaintToleration"},
1449+
{Name: "PodTopologySpread"},
14471450
},
14481451
"ScorePlugin": {
14491452
{Name: "NodeResourcesBalancedAllocation", Weight: 1},
@@ -1454,6 +1457,7 @@ func TestAlgorithmProviderCompatibility(t *testing.T) {
14541457
{Name: "NodePreferAvoidPods", Weight: 10000},
14551458
{Name: "DefaultPodTopologySpread", Weight: 1},
14561459
{Name: "TaintToleration", Weight: 1},
1460+
{Name: "PodTopologySpread", Weight: 1},
14571461
},
14581462
"BindPlugin": {{Name: "DefaultBinder"}},
14591463
}
@@ -1483,6 +1487,7 @@ func TestAlgorithmProviderCompatibility(t *testing.T) {
14831487
{Name: "NodeResourcesFit"},
14841488
{Name: "NodePorts"},
14851489
{Name: "InterPodAffinity"},
1490+
{Name: "PodTopologySpread"},
14861491
},
14871492
"FilterPlugin": {
14881493
{Name: "NodeUnschedulable"},
@@ -1499,11 +1504,13 @@ func TestAlgorithmProviderCompatibility(t *testing.T) {
14991504
{Name: "VolumeBinding"},
15001505
{Name: "VolumeZone"},
15011506
{Name: "InterPodAffinity"},
1507+
{Name: "PodTopologySpread"},
15021508
},
15031509
"PreScorePlugin": {
15041510
{Name: "InterPodAffinity"},
15051511
{Name: "DefaultPodTopologySpread"},
15061512
{Name: "TaintToleration"},
1513+
{Name: "PodTopologySpread"},
15071514
},
15081515
"ScorePlugin": {
15091516
{Name: "NodeResourcesBalancedAllocation", Weight: 1},
@@ -1514,6 +1521,7 @@ func TestAlgorithmProviderCompatibility(t *testing.T) {
15141521
{Name: "NodePreferAvoidPods", Weight: 10000},
15151522
{Name: "DefaultPodTopologySpread", Weight: 1},
15161523
{Name: "TaintToleration", Weight: 1},
1524+
{Name: "PodTopologySpread", Weight: 1},
15171525
},
15181526
"BindPlugin": {{Name: "DefaultBinder"}},
15191527
},

test/e2e/scheduling/predicates.go

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type pausePodConfig struct {
6767
OwnerReferences []metav1.OwnerReference
6868
PriorityClassName string
6969
DeletionGracePeriodSeconds *int64
70+
TopologySpreadConstraints []v1.TopologySpreadConstraint
7071
}
7172

7273
var _ = SIGDescribe("SchedulerPredicates [Serial]", func() {
@@ -604,6 +605,84 @@ var _ = SIGDescribe("SchedulerPredicates [Serial]", func() {
604605
ginkgo.By(fmt.Sprintf("Trying to create another pod(pod5) with hostport %v but hostIP 127.0.0.1 on the node which pod4 resides and expect not scheduled", port))
605606
createHostPortPodOnNode(f, "pod5", ns, "127.0.0.1", port, v1.ProtocolTCP, nodeSelector, false)
606607
})
608+
609+
ginkgo.Context("PodTopologySpread Filtering", func() {
610+
var nodeNames []string
611+
topologyKey := "kubernetes.io/e2e-pts-filter"
612+
613+
ginkgo.BeforeEach(func() {
614+
ginkgo.By("Trying to get 2 available nodes which can run pod")
615+
nodeNames = Get2NodesThatCanRunPod(f)
616+
ginkgo.By(fmt.Sprintf("Apply dedicated topologyKey %v for this test on the 2 nodes.", topologyKey))
617+
for _, nodeName := range nodeNames {
618+
framework.AddOrUpdateLabelOnNode(cs, nodeName, topologyKey, nodeName)
619+
}
620+
})
621+
ginkgo.AfterEach(func() {
622+
for _, nodeName := range nodeNames {
623+
framework.RemoveLabelOffNode(cs, nodeName, topologyKey)
624+
}
625+
})
626+
627+
ginkgo.It("validates 4 pods with MaxSkew=1 are evenly distributed into 2 nodes", func() {
628+
podLabel := "e2e-pts-filter"
629+
replicas := 4
630+
rsConfig := pauseRSConfig{
631+
Replicas: int32(replicas),
632+
PodConfig: pausePodConfig{
633+
Name: podLabel,
634+
Namespace: ns,
635+
Labels: map[string]string{podLabel: ""},
636+
Affinity: &v1.Affinity{
637+
NodeAffinity: &v1.NodeAffinity{
638+
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
639+
NodeSelectorTerms: []v1.NodeSelectorTerm{
640+
{
641+
MatchExpressions: []v1.NodeSelectorRequirement{
642+
{
643+
Key: topologyKey,
644+
Operator: v1.NodeSelectorOpIn,
645+
Values: nodeNames,
646+
},
647+
},
648+
},
649+
},
650+
},
651+
},
652+
},
653+
TopologySpreadConstraints: []v1.TopologySpreadConstraint{
654+
{
655+
MaxSkew: 1,
656+
TopologyKey: topologyKey,
657+
WhenUnsatisfiable: v1.DoNotSchedule,
658+
LabelSelector: &metav1.LabelSelector{
659+
MatchExpressions: []metav1.LabelSelectorRequirement{
660+
{
661+
Key: podLabel,
662+
Operator: metav1.LabelSelectorOpExists,
663+
},
664+
},
665+
},
666+
},
667+
},
668+
},
669+
}
670+
runPauseRS(f, rsConfig)
671+
podList, err := cs.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{})
672+
framework.ExpectNoError(err)
673+
numInNode1, numInNode2 := 0, 0
674+
for _, pod := range podList.Items {
675+
if pod.Spec.NodeName == nodeNames[0] {
676+
numInNode1++
677+
} else if pod.Spec.NodeName == nodeNames[1] {
678+
numInNode2++
679+
}
680+
}
681+
expected := replicas / len(nodeNames)
682+
framework.ExpectEqual(numInNode1, expected, fmt.Sprintf("Pods are not distributed as expected on node %q", nodeNames[0]))
683+
framework.ExpectEqual(numInNode2, expected, fmt.Sprintf("Pods are not distributed as expected on node %q", nodeNames[1]))
684+
})
685+
})
607686
})
608687

609688
// printAllKubeletPods outputs status of all kubelet pods into log.
@@ -633,8 +712,9 @@ func initPausePod(f *framework.Framework, conf pausePodConfig) *v1.Pod {
633712
OwnerReferences: conf.OwnerReferences,
634713
},
635714
Spec: v1.PodSpec{
636-
NodeSelector: conf.NodeSelector,
637-
Affinity: conf.Affinity,
715+
NodeSelector: conf.NodeSelector,
716+
Affinity: conf.Affinity,
717+
TopologySpreadConstraints: conf.TopologySpreadConstraints,
638718
Containers: []v1.Container{
639719
{
640720
Name: conf.Name,
@@ -669,7 +749,7 @@ func createPausePod(f *framework.Framework, conf pausePodConfig) *v1.Pod {
669749

670750
func runPausePod(f *framework.Framework, conf pausePodConfig) *v1.Pod {
671751
pod := createPausePod(f, conf)
672-
framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(f.ClientSet, pod))
752+
framework.ExpectNoError(e2epod.WaitTimeoutForPodRunningInNamespace(f.ClientSet, pod.Name, pod.Namespace, framework.PollShortTimeout))
673753
pod, err := f.ClientSet.CoreV1().Pods(pod.Namespace).Get(context.TODO(), conf.Name, metav1.GetOptions{})
674754
framework.ExpectNoError(err)
675755
return pod
@@ -750,6 +830,30 @@ func GetNodeThatCanRunPod(f *framework.Framework) string {
750830
return runPodAndGetNodeName(f, pausePodConfig{Name: "without-label"})
751831
}
752832

833+
// Get2NodesThatCanRunPod return a 2-node slice where can run pod.
834+
func Get2NodesThatCanRunPod(f *framework.Framework) []string {
835+
firstNode := GetNodeThatCanRunPod(f)
836+
ginkgo.By("Trying to launch a pod without a label to get a node which can launch it.")
837+
pod := pausePodConfig{
838+
Name: "without-label",
839+
Affinity: &v1.Affinity{
840+
NodeAffinity: &v1.NodeAffinity{
841+
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
842+
NodeSelectorTerms: []v1.NodeSelectorTerm{
843+
{
844+
MatchFields: []v1.NodeSelectorRequirement{
845+
{Key: "metadata.name", Operator: v1.NodeSelectorOpNotIn, Values: []string{firstNode}},
846+
},
847+
},
848+
},
849+
},
850+
},
851+
},
852+
}
853+
secondNode := runPodAndGetNodeName(f, pod)
854+
return []string{firstNode, secondNode}
855+
}
856+
753857
func getNodeThatCanRunPodWithoutToleration(f *framework.Framework) string {
754858
ginkgo.By("Trying to launch a pod without a toleration to get a node which can launch it.")
755859
return runPodAndGetNodeName(f, pausePodConfig{Name: "without-toleration"})

0 commit comments

Comments
 (0)