github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/testutil/k8s/statefulset_util.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package testutil 21 22 import ( 23 "context" 24 "fmt" 25 "reflect" 26 27 "github.com/onsi/gomega" 28 appsv1 "k8s.io/api/apps/v1" 29 corev1 "k8s.io/api/core/v1" 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/types" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 35 "github.com/1aal/kubeblocks/pkg/constant" 36 "github.com/1aal/kubeblocks/pkg/testutil" 37 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 38 ) 39 40 const ( 41 testFinalizer = "test.kubeblocks.io/finalizer" 42 ) 43 44 // NewFakeStatefulSet creates a fake StatefulSet workload object for testing. 45 func NewFakeStatefulSet(name string, replicas int) *appsv1.StatefulSet { 46 template := corev1.PodTemplateSpec{ 47 Spec: corev1.PodSpec{ 48 Containers: []corev1.Container{ 49 { 50 Name: "nginx", 51 Image: "nginx", 52 }, 53 }, 54 }, 55 } 56 57 template.Labels = map[string]string{"foo": "bar"} 58 statefulSetReplicas := int32(replicas) 59 Revision := name + "-d5df5b8d6" 60 return &appsv1.StatefulSet{ 61 ObjectMeta: metav1.ObjectMeta{ 62 Name: name, 63 Namespace: corev1.NamespaceDefault, 64 }, 65 Spec: appsv1.StatefulSetSpec{ 66 Selector: &metav1.LabelSelector{ 67 MatchLabels: map[string]string{"foo": "bar"}, 68 }, 69 Replicas: &statefulSetReplicas, 70 Template: template, 71 ServiceName: "governingsvc", 72 }, 73 Status: appsv1.StatefulSetStatus{ 74 AvailableReplicas: statefulSetReplicas, 75 ObservedGeneration: 0, 76 ReadyReplicas: statefulSetReplicas, 77 UpdatedReplicas: statefulSetReplicas, 78 CurrentRevision: Revision, 79 UpdateRevision: Revision, 80 }, 81 } 82 } 83 84 // NewFakeStatefulSetPod creates a fake pod of the StatefulSet workload for testing. 85 func NewFakeStatefulSetPod(set *appsv1.StatefulSet, ordinal int) *corev1.Pod { 86 pod := &corev1.Pod{} 87 pod.Name = fmt.Sprintf("%s-%d", set.Name, ordinal) 88 return pod 89 } 90 91 // MockStatefulSetReady mocks the StatefulSet workload is ready. 92 func MockStatefulSetReady(sts *appsv1.StatefulSet) { 93 sts.Status.AvailableReplicas = *sts.Spec.Replicas 94 sts.Status.ObservedGeneration = sts.Generation 95 sts.Status.Replicas = *sts.Spec.Replicas 96 sts.Status.ReadyReplicas = *sts.Spec.Replicas 97 sts.Status.CurrentRevision = sts.Status.UpdateRevision 98 } 99 100 // DeletePodLabelKey deletes the specified label of the pod. 101 func DeletePodLabelKey(ctx context.Context, testCtx testutil.TestContext, podName, labelKey string) { 102 pod := &corev1.Pod{} 103 gomega.Expect(testCtx.Cli.Get(ctx, client.ObjectKey{Name: podName, Namespace: testCtx.DefaultNamespace}, pod)).Should(gomega.Succeed()) 104 if pod.Labels == nil { 105 return 106 } 107 patch := client.MergeFrom(pod.DeepCopy()) 108 delete(pod.Labels, labelKey) 109 gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) 110 gomega.Eventually(func(g gomega.Gomega) { 111 tmpPod := &corev1.Pod{} 112 _ = testCtx.Cli.Get(context.Background(), client.ObjectKey{Name: podName, Namespace: testCtx.DefaultNamespace}, tmpPod) 113 g.Expect(tmpPod.Labels == nil || tmpPod.Labels[labelKey] == "").Should(gomega.BeTrue()) 114 }).Should(gomega.Succeed()) 115 } 116 117 // UpdatePodStatusScheduleFailed updates the pod status to mock the schedule failure. 118 func UpdatePodStatusScheduleFailed(ctx context.Context, testCtx testutil.TestContext, podName, namespace string) { 119 pod := &corev1.Pod{} 120 gomega.Expect(testCtx.Cli.Get(ctx, client.ObjectKey{Name: podName, Namespace: namespace}, pod)).Should(gomega.Succeed()) 121 patch := client.MergeFrom(pod.DeepCopy()) 122 pod.Status.Conditions = []corev1.PodCondition{ 123 { 124 Type: corev1.PodScheduled, 125 Status: corev1.ConditionFalse, 126 Message: "0/1 node cpu Insufficient", 127 Reason: "Unschedulable", 128 }, 129 } 130 gomega.Expect(testCtx.Cli.Status().Patch(ctx, pod, patch)).Should(gomega.Succeed()) 131 } 132 133 // MockPodIsTerminating mocks pod is terminating. 134 func MockPodIsTerminating(ctx context.Context, testCtx testutil.TestContext, pod *corev1.Pod) { 135 patch := client.MergeFrom(pod.DeepCopy()) 136 pod.Finalizers = []string{testFinalizer} 137 gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) 138 gomega.Expect(testCtx.Cli.Delete(ctx, pod)).Should(gomega.Succeed()) 139 gomega.Eventually(func(g gomega.Gomega) { 140 tmpPod := &corev1.Pod{} 141 _ = testCtx.Cli.Get(context.Background(), 142 client.ObjectKey{Name: pod.Name, Namespace: testCtx.DefaultNamespace}, tmpPod) 143 g.Expect(!tmpPod.DeletionTimestamp.IsZero()).Should(gomega.BeTrue()) 144 }).Should(gomega.Succeed()) 145 } 146 147 // RemovePodFinalizer removes the pod finalizer to delete the pod finally. 148 func RemovePodFinalizer(ctx context.Context, testCtx testutil.TestContext, pod *corev1.Pod) { 149 patch := client.MergeFrom(pod.DeepCopy()) 150 pod.Finalizers = []string{} 151 gomega.Expect(testCtx.Cli.Patch(ctx, pod, patch)).Should(gomega.Succeed()) 152 gomega.Eventually(func() error { 153 return testCtx.Cli.Get(context.Background(), 154 client.ObjectKey{Name: pod.Name, Namespace: testCtx.DefaultNamespace}, &corev1.Pod{}) 155 }).Should(gomega.Satisfy(apierrors.IsNotFound)) 156 } 157 158 func ListAndCheckStatefulSet(testCtx *testutil.TestContext, key types.NamespacedName) *appsv1.StatefulSetList { 159 stsList := &appsv1.StatefulSetList{} 160 gomega.Eventually(func(g gomega.Gomega) { 161 g.Expect(testCtx.Cli.List(testCtx.Ctx, stsList, client.MatchingLabels{ 162 constant.AppInstanceLabelKey: key.Name, 163 }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) 164 g.Expect(stsList.Items).ShouldNot(gomega.BeNil()) 165 g.Expect(stsList.Items).ShouldNot(gomega.BeEmpty()) 166 }).Should(gomega.Succeed()) 167 return stsList 168 } 169 170 func ListAndCheckStatefulSetItemsCount(testCtx *testutil.TestContext, key types.NamespacedName, cnt int) *appsv1.StatefulSetList { 171 stsList := &appsv1.StatefulSetList{} 172 gomega.Eventually(func(g gomega.Gomega) { 173 g.Expect(testCtx.Cli.List(testCtx.Ctx, stsList, client.MatchingLabels{ 174 constant.AppInstanceLabelKey: key.Name, 175 }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) 176 g.Expect(len(stsList.Items)).Should(gomega.Equal(cnt)) 177 }).Should(gomega.Succeed()) 178 return stsList 179 } 180 181 func ListAndCheckStatefulSetWithComponent(testCtx *testutil.TestContext, key types.NamespacedName, componentName string) *appsv1.StatefulSetList { 182 stsList := &appsv1.StatefulSetList{} 183 gomega.Eventually(func(g gomega.Gomega) { 184 g.Expect(testCtx.Cli.List(testCtx.Ctx, stsList, client.MatchingLabels{ 185 constant.AppInstanceLabelKey: key.Name, 186 constant.KBAppComponentLabelKey: componentName, 187 }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) 188 g.Expect(stsList.Items).ShouldNot(gomega.BeNil()) 189 g.Expect(stsList.Items).ShouldNot(gomega.BeEmpty()) 190 }).Should(gomega.Succeed()) 191 return stsList 192 } 193 194 func ListAndCheckPodCountWithComponent(testCtx *testutil.TestContext, key types.NamespacedName, componentName string, cnt int) *corev1.PodList { 195 podList := &corev1.PodList{} 196 gomega.Eventually(func(g gomega.Gomega) { 197 g.Expect(testCtx.Cli.List(testCtx.Ctx, podList, client.MatchingLabels{ 198 constant.AppInstanceLabelKey: key.Name, 199 constant.KBAppComponentLabelKey: componentName, 200 }, client.InNamespace(key.Namespace))).Should(gomega.Succeed()) 201 g.Expect(len(podList.Items)).Should(gomega.Equal(cnt)) 202 }).Should(gomega.Succeed()) 203 return podList 204 } 205 206 func PatchStatefulSetStatus(testCtx *testutil.TestContext, stsName string, status appsv1.StatefulSetStatus) { 207 objectKey := client.ObjectKey{Name: stsName, Namespace: testCtx.DefaultNamespace} 208 gomega.Expect(testapps.GetAndChangeObjStatus(testCtx, objectKey, func(newSts *appsv1.StatefulSet) { 209 newSts.Status = status 210 })()).Should(gomega.Succeed()) 211 gomega.Eventually(testapps.CheckObj(testCtx, objectKey, func(g gomega.Gomega, newSts *appsv1.StatefulSet) { 212 g.Expect(reflect.DeepEqual(newSts.Status, status)).Should(gomega.BeTrue()) 213 })).Should(gomega.Succeed()) 214 } 215 216 func InitStatefulSetStatus(testCtx testutil.TestContext, statefulset *appsv1.StatefulSet, controllerRevision string) { 217 gomega.Expect(testapps.ChangeObjStatus(&testCtx, statefulset, func() { 218 statefulset.Status.UpdateRevision = controllerRevision 219 statefulset.Status.CurrentRevision = controllerRevision 220 statefulset.Status.ObservedGeneration = statefulset.Generation 221 })).Should(gomega.Succeed()) 222 }