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  }