From 7bf175d5a2ff2783a84b1f3c781836802fa5e97d Mon Sep 17 00:00:00 2001 From: pwschuurman Date: Fri, 3 Feb 2023 05:26:29 -0800 Subject: [PATCH] Add integration tests for StatefulSetStartOrdinal feature (#115466) * Add integration tests for StatefulSetStartOrdinal feature * Move expensive test setup (apiserver and running controller) to be run once in StatefulSetStartOrdinal parameterized tests --- .../statefulset/statefulset_test.go | 96 +++++++++++++++++++ test/integration/statefulset/util.go | 14 ++- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/test/integration/statefulset/statefulset_test.go b/test/integration/statefulset/statefulset_test.go index f83d7da600b..f082bde28d8 100644 --- a/test/integration/statefulset/statefulset_test.go +++ b/test/integration/statefulset/statefulset_test.go @@ -22,6 +22,8 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -479,3 +481,97 @@ func TestAutodeleteOwnerRefs(t *testing.T) { }) } } + +func TestStatefulSetStartOrdinal(t *testing.T) { + tests := []struct { + ordinals *appsv1.StatefulSetOrdinals + name string + namespace string + replicas int + expectedPodNames []string + }{ + { + name: "default start ordinal, no ordinals set", + namespace: "no-ordinals", + replicas: 3, + expectedPodNames: []string{"sts-0", "sts-1", "sts-2"}, + }, + { + name: "default start ordinal", + namespace: "no-start-ordinals", + ordinals: &appsv1.StatefulSetOrdinals{}, + replicas: 3, + expectedPodNames: []string{"sts-0", "sts-1", "sts-2"}, + }, + { + name: "start ordinal 4", + namespace: "start-ordinal-4", + ordinals: &appsv1.StatefulSetOrdinals{ + Start: 4, + }, + replicas: 4, + expectedPodNames: []string{"sts-4", "sts-5", "sts-6", "sts-7"}, + }, + { + name: "start ordinal 5", + namespace: "start-ordinal-5", + ordinals: &appsv1.StatefulSetOrdinals{ + Start: 2, + }, + replicas: 7, + expectedPodNames: []string{"sts-2", "sts-3", "sts-4", "sts-5", "sts-6", "sts-7", "sts-8"}, + }, + } + + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, true)() + closeFn, rm, informers, c := scSetup(t) + defer closeFn() + cancel := runControllerAndInformers(rm, informers) + defer cancel() + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ns := framework.CreateNamespaceOrDie(c, test.namespace, t) + defer framework.DeleteNamespaceOrDie(c, ns, t) + + // Label map is the map of pod labels used in newSTS() + labelMap := labelMap() + sts := newSTS("sts", ns.Name, test.replicas) + sts.Spec.Ordinals = test.ordinals + stss := createSTSs(t, c, []*appsv1.StatefulSet{sts}) + sts = stss[0] + waitSTSStable(t, c, sts) + + podClient := c.CoreV1().Pods(ns.Name) + pods := getPods(t, podClient, labelMap) + if len(pods.Items) != test.replicas { + t.Errorf("len(pods) = %v, want %v", len(pods.Items), test.replicas) + } + + var podNames []string + for _, pod := range pods.Items { + podNames = append(podNames, pod.Name) + } + ignoreOrder := cmpopts.SortSlices(func(a, b string) bool { + return a < b + }) + + // Validate all the expected pods were created. + if diff := cmp.Diff(test.expectedPodNames, podNames, ignoreOrder); diff != "" { + t.Errorf("Unexpected pod names: (-want +got): %v", diff) + } + + // Scale down to 1 pod and verify it matches the first pod. + scaleSTS(t, c, sts, 1) + waitSTSStable(t, c, sts) + + pods = getPods(t, podClient, labelMap) + if len(pods.Items) != 1 { + t.Errorf("len(pods) = %v, want %v", len(pods.Items), 1) + } + if pods.Items[0].Name != test.expectedPodNames[0] { + t.Errorf("Unexpected singleton pod name: got = %v, want %v", pods.Items[0].Name, test.expectedPodNames[0]) + } + }) + } +} diff --git a/test/integration/statefulset/util.go b/test/integration/statefulset/util.go index c77240dad70..8ab42e8ca4d 100644 --- a/test/integration/statefulset/util.go +++ b/test/integration/statefulset/util.go @@ -199,9 +199,8 @@ func createHeadlessService(t *testing.T, clientSet clientset.Interface, headless } } -func createSTSsPods(t *testing.T, clientSet clientset.Interface, stss []*appsv1.StatefulSet, pods []*v1.Pod) ([]*appsv1.StatefulSet, []*v1.Pod) { +func createSTSs(t *testing.T, clientSet clientset.Interface, stss []*appsv1.StatefulSet) []*appsv1.StatefulSet { var createdSTSs []*appsv1.StatefulSet - var createdPods []*v1.Pod for _, sts := range stss { createdSTS, err := clientSet.AppsV1().StatefulSets(sts.Namespace).Create(context.TODO(), sts, metav1.CreateOptions{}) if err != nil { @@ -209,6 +208,11 @@ func createSTSsPods(t *testing.T, clientSet clientset.Interface, stss []*appsv1. } createdSTSs = append(createdSTSs, createdSTS) } + return createdSTSs +} + +func createPods(t *testing.T, clientSet clientset.Interface, pods []*v1.Pod) []*v1.Pod { + var createdPods []*v1.Pod for _, pod := range pods { createdPod, err := clientSet.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) if err != nil { @@ -217,7 +221,11 @@ func createSTSsPods(t *testing.T, clientSet clientset.Interface, stss []*appsv1. createdPods = append(createdPods, createdPod) } - return createdSTSs, createdPods + return createdPods +} + +func createSTSsPods(t *testing.T, clientSet clientset.Interface, stss []*appsv1.StatefulSet, pods []*v1.Pod) ([]*appsv1.StatefulSet, []*v1.Pod) { + return createSTSs(t, clientSet, stss), createPods(t, clientSet, pods) } // Verify .Status.Replicas is equal to .Spec.Replicas