github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/components/utils_test.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 components
    21  
    22  import (
    23  	"fmt"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  
    28  	. "github.com/onsi/ginkgo/v2"
    29  	. "github.com/onsi/gomega"
    30  
    31  	appsv1 "k8s.io/api/apps/v1"
    32  	corev1 "k8s.io/api/core/v1"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/util/intstr"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  
    37  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    38  	"github.com/1aal/kubeblocks/pkg/constant"
    39  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    40  	"github.com/1aal/kubeblocks/pkg/controller/component"
    41  	"github.com/1aal/kubeblocks/pkg/controller/graph"
    42  	"github.com/1aal/kubeblocks/pkg/controller/model"
    43  	"github.com/1aal/kubeblocks/pkg/generics"
    44  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    45  )
    46  
    47  func TestIsFailedOrAbnormal(t *testing.T) {
    48  	if !IsFailedOrAbnormal(appsv1alpha1.AbnormalClusterCompPhase) {
    49  		t.Error("isAbnormal should be true")
    50  	}
    51  }
    52  
    53  func TestIsProbeTimeout(t *testing.T) {
    54  	podsReadyTime := &metav1.Time{Time: time.Now().Add(-10 * time.Minute)}
    55  	compDef := &appsv1alpha1.ClusterComponentDefinition{
    56  		Probes: &appsv1alpha1.ClusterDefinitionProbes{
    57  			RoleProbe:                      &appsv1alpha1.ClusterDefinitionProbe{},
    58  			RoleProbeTimeoutAfterPodsReady: appsv1alpha1.DefaultRoleProbeTimeoutAfterPodsReady,
    59  		},
    60  	}
    61  	if !isProbeTimeout(compDef.Probes, podsReadyTime) {
    62  		t.Error("probe timed out should be true")
    63  	}
    64  }
    65  
    66  var _ = Describe("Component Utils", func() {
    67  	var (
    68  		randomStr          = testCtx.GetRandomStr()
    69  		clusterDefName     = "mysql-clusterdef-" + randomStr
    70  		clusterVersionName = "mysql-clusterversion-" + randomStr
    71  		clusterName        = "mysql-" + randomStr
    72  	)
    73  
    74  	const (
    75  		consensusCompDefRef = "consensus"
    76  		consensusCompName   = "consensus"
    77  		statelessCompName   = "stateless"
    78  	)
    79  
    80  	cleanAll := func() {
    81  		// must wait until resources deleted and no longer exist before the testcases start,
    82  		// otherwise if later it needs to create some new resource objects with the same name,
    83  		// in race conditions, it will find the existence of old objects, resulting failure to
    84  		// create the new objects.
    85  		By("clean resources")
    86  		// delete cluster(and all dependent sub-resources), clusterversion and clusterdef
    87  		testapps.ClearClusterResources(&testCtx)
    88  
    89  		// clear rest resources
    90  		inNS := client.InNamespace(testCtx.DefaultNamespace)
    91  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    92  		// namespaced resources
    93  		testapps.ClearResources(&testCtx, generics.StatefulSetSignature, inNS, ml)
    94  		testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml, client.GracePeriodSeconds(0))
    95  	}
    96  
    97  	BeforeEach(cleanAll)
    98  
    99  	AfterEach(cleanAll)
   100  
   101  	Context("Component test", func() {
   102  		It("Component test", func() {
   103  			By(" init cluster, statefulSet, pods")
   104  			_, _, cluster := testapps.InitClusterWithHybridComps(&testCtx, clusterDefName,
   105  				clusterVersionName, clusterName, statelessCompName, "stateful", consensusCompName)
   106  			sts := testapps.MockConsensusComponentStatefulSet(&testCtx, clusterName, consensusCompName)
   107  			testapps.MockStatelessComponentDeploy(&testCtx, clusterName, statelessCompName)
   108  			_ = testapps.MockConsensusComponentPods(&testCtx, sts, clusterName, consensusCompName)
   109  
   110  			By("test GetComponentDefByCluster function")
   111  			componentDef, _ := appsv1alpha1.GetComponentDefByCluster(ctx, k8sClient, *cluster, consensusCompDefRef)
   112  			Expect(componentDef != nil).Should(BeTrue())
   113  
   114  			By("test GetClusterByObject function")
   115  			newCluster, _ := GetClusterByObject(ctx, k8sClient, sts)
   116  			Expect(newCluster != nil).Should(BeTrue())
   117  
   118  			By("test consensusSet initClusterComponentStatusIfNeed function")
   119  			err := initClusterComponentStatusIfNeed(cluster, consensusCompName, componentDef.WorkloadType)
   120  			Expect(err).Should(Succeed())
   121  			Expect(cluster.Status.Components[consensusCompName].ConsensusSetStatus).ShouldNot(BeNil())
   122  			Expect(cluster.Status.Components[consensusCompName].ConsensusSetStatus.Leader.Pod).Should(Equal(constant.ComponentStatusDefaultPodName))
   123  
   124  			By("test replicationSet initClusterComponentStatusIfNeed function")
   125  			componentDef.WorkloadType = appsv1alpha1.Replication
   126  			err = initClusterComponentStatusIfNeed(cluster, consensusCompName, componentDef.WorkloadType)
   127  			Expect(err).Should(Succeed())
   128  			Expect(cluster.Status.Components[consensusCompName].ReplicationSetStatus).ShouldNot(BeNil())
   129  			Expect(cluster.Status.Components[consensusCompName].ReplicationSetStatus.Primary.Pod).Should(Equal(constant.ComponentStatusDefaultPodName))
   130  
   131  			By("test getObjectListByComponentName function")
   132  			stsList := &appsv1.StatefulSetList{}
   133  			_ = getObjectListByComponentName(ctx, k8sClient, *cluster, stsList, consensusCompName)
   134  			Expect(len(stsList.Items) > 0).Should(BeTrue())
   135  
   136  			By("test getObjectListByCustomLabels function")
   137  			stsList = &appsv1.StatefulSetList{}
   138  			matchLabel := getComponentMatchLabels(cluster.Name, consensusCompName)
   139  			_ = getObjectListByCustomLabels(ctx, k8sClient, *cluster, stsList, client.MatchingLabels(matchLabel))
   140  			Expect(len(stsList.Items) > 0).Should(BeTrue())
   141  
   142  			By("test getClusterComponentSpecByName function")
   143  			clusterComp := getClusterComponentSpecByName(*cluster, consensusCompName)
   144  			Expect(clusterComp).ShouldNot(BeNil())
   145  
   146  			By("test GetComponentStsMinReadySeconds")
   147  			minReadySeconds, _ := GetComponentWorkloadMinReadySeconds(ctx, k8sClient, *cluster,
   148  				appsv1alpha1.Stateless, statelessCompName)
   149  			Expect(minReadySeconds).To(Equal(int32(10)))
   150  			minReadySeconds, _ = GetComponentWorkloadMinReadySeconds(ctx, k8sClient, *cluster,
   151  				appsv1alpha1.Consensus, statelessCompName)
   152  			Expect(minReadySeconds).To(Equal(int32(0)))
   153  
   154  			By("test getCompRelatedObjectList function")
   155  			stsList = &appsv1.StatefulSetList{}
   156  			podList, _ := getCompRelatedObjectList(ctx, k8sClient, *cluster, consensusCompName, stsList)
   157  			Expect(len(stsList.Items) > 0 && len(podList.Items) > 0).Should(BeTrue())
   158  
   159  			By("test GetComponentInfoByPod function")
   160  			componentName, componentDef, err := GetComponentInfoByPod(ctx, k8sClient, *cluster, &podList.Items[0])
   161  			Expect(err).Should(Succeed())
   162  			Expect(componentName).Should(Equal(consensusCompName))
   163  			Expect(componentDef).ShouldNot(BeNil())
   164  			By("test GetComponentInfoByPod function when Pod is nil")
   165  			_, _, err = GetComponentInfoByPod(ctx, k8sClient, *cluster, nil)
   166  			Expect(err).ShouldNot(Succeed())
   167  			By("test GetComponentInfoByPod function when Pod component label is nil")
   168  			podNoLabel := &podList.Items[0]
   169  			delete(podNoLabel.Labels, constant.KBAppComponentLabelKey)
   170  			_, _, err = GetComponentInfoByPod(ctx, k8sClient, *cluster, podNoLabel)
   171  			Expect(err).ShouldNot(Succeed())
   172  		})
   173  
   174  		It("test GetComponentInfoByPod with no cluster componentSpec", func() {
   175  			_, _, cluster := testapps.InitClusterWithHybridComps(&testCtx, clusterDefName,
   176  				clusterVersionName, clusterName, statelessCompName, "stateful", consensusCompName)
   177  			By("set componentSpec to nil")
   178  			cluster.Spec.ComponentSpecs = nil
   179  			pod := corev1.Pod{
   180  				ObjectMeta: metav1.ObjectMeta{
   181  					Labels: map[string]string{
   182  						constant.KBAppComponentLabelKey: consensusCompName,
   183  					},
   184  				},
   185  			}
   186  			componentName, componentDef, err := GetComponentInfoByPod(ctx, k8sClient, *cluster, &pod)
   187  			Expect(err).Should(Succeed())
   188  			Expect(componentName).Should(Equal(consensusCompName))
   189  			Expect(componentDef).ShouldNot(BeNil())
   190  		})
   191  	})
   192  
   193  	Context("Custom Label test", func() {
   194  		Context("parseCustomLabelPattern func", func() {
   195  			It("should parse pattern well", func() {
   196  				pattern := "v1/Pod"
   197  				gvk, err := parseCustomLabelPattern(pattern)
   198  				Expect(err).Should(BeNil())
   199  				Expect(gvk.Group).Should(BeEmpty())
   200  				Expect(gvk.Version).Should(Equal("v1"))
   201  				Expect(gvk.Kind).Should(Equal("Pod"))
   202  				pattern = "apps/v1/StatefulSet"
   203  				gvk, err = parseCustomLabelPattern(pattern)
   204  				Expect(err).Should(BeNil())
   205  				Expect(gvk.Group).Should(Equal("apps"))
   206  				Expect(gvk.Version).Should(Equal("v1"))
   207  				Expect(gvk.Kind).Should(Equal("StatefulSet"))
   208  			})
   209  		})
   210  
   211  		Context("updateCustomLabelToObjs func", func() {
   212  			It("should update label well", func() {
   213  				resource := &appsv1alpha1.GVKResource{GVK: "v1/Pod"}
   214  				customLabelSpec := appsv1alpha1.CustomLabelSpec{
   215  					Key:       "custom-label-key",
   216  					Value:     "$(KB_CLUSTER_NAME)-$(KB_COMP_NAME)",
   217  					Resources: []appsv1alpha1.GVKResource{*resource},
   218  				}
   219  				pod := builder.NewPodBuilder("foo", "bar").GetObject()
   220  				clusterName, uid, componentName := "foo", "1234-5678", "workload"
   221  				err := updateCustomLabelToObjs(clusterName, uid, componentName, []appsv1alpha1.CustomLabelSpec{customLabelSpec}, []client.Object{pod})
   222  				Expect(err).Should(BeNil())
   223  				Expect(pod.Labels).ShouldNot(BeNil())
   224  				Expect(pod.Labels[customLabelSpec.Key]).Should(Equal(fmt.Sprintf("%s-%s", clusterName, componentName)))
   225  			})
   226  		})
   227  
   228  		Context("updateCustomLabelToPods func", func() {
   229  			It("should work well", func() {
   230  				_, _, cluster := testapps.InitClusterWithHybridComps(&testCtx, clusterDefName,
   231  					clusterVersionName, clusterName, statelessCompName, "stateful", consensusCompName)
   232  				sts := testapps.MockConsensusComponentStatefulSet(&testCtx, clusterName, consensusCompName)
   233  				pods := testapps.MockConsensusComponentPods(&testCtx, sts, clusterName, consensusCompName)
   234  				resource := &appsv1alpha1.GVKResource{GVK: "v1/Pod"}
   235  				customLabelSpec := appsv1alpha1.CustomLabelSpec{
   236  					Key:       "custom-label-key",
   237  					Value:     "$(KB_CLUSTER_NAME)-$(KB_COMP_NAME)",
   238  					Resources: []appsv1alpha1.GVKResource{*resource},
   239  				}
   240  				comp := &component.SynthesizedComponent{
   241  					Name:             consensusCompName,
   242  					CustomLabelSpecs: []appsv1alpha1.CustomLabelSpec{customLabelSpec},
   243  				}
   244  				dag := graph.NewDAG()
   245  				dag.AddVertex(&model.ObjectVertex{Obj: pods[0], Action: model.ActionUpdatePtr()})
   246  				Expect(updateCustomLabelToPods(testCtx.Ctx, k8sClient, cluster, comp, dag)).Should(Succeed())
   247  				graphCli := model.NewGraphClient(k8sClient)
   248  				podList := graphCli.FindAll(dag, &corev1.Pod{})
   249  				Expect(podList).Should(HaveLen(3))
   250  				for _, pod := range podList {
   251  					Expect(pod.GetLabels()).ShouldNot(BeNil())
   252  					Expect(pod.GetLabels()[customLabelSpec.Key]).Should(Equal(fmt.Sprintf("%s-%s", clusterName, comp.Name)))
   253  				}
   254  			})
   255  		})
   256  	})
   257  
   258  	Context("test mergeServiceAnnotations", func() {
   259  		It("should merge annotations from original that not exist in target to final result", func() {
   260  			originalKey := "only-existing-in-original"
   261  			targetKey := "only-existing-in-target"
   262  			updatedKey := "updated-in-target"
   263  			originalAnnotations := map[string]string{
   264  				originalKey: "true",
   265  				updatedKey:  "false",
   266  			}
   267  			targetAnnotations := map[string]string{
   268  				targetKey:  "true",
   269  				updatedKey: "true",
   270  			}
   271  			mergeAnnotations(originalAnnotations, &targetAnnotations)
   272  			Expect(targetAnnotations[targetKey]).ShouldNot(BeEmpty())
   273  			Expect(targetAnnotations[originalKey]).ShouldNot(BeEmpty())
   274  			Expect(targetAnnotations[updatedKey]).Should(Equal("true"))
   275  			By("merging with target being nil")
   276  			var nilAnnotations map[string]string
   277  			mergeAnnotations(originalAnnotations, &nilAnnotations)
   278  			Expect(nilAnnotations).ShouldNot(BeNil())
   279  		})
   280  
   281  		It("test sync pod spec default values set by k8s", func() {
   282  			var (
   283  				clusterName = "cluster"
   284  				compName    = "component"
   285  				podName     = "pod"
   286  				role        = "leader"
   287  				mode        = "ReadWrite"
   288  			)
   289  			pod := testapps.MockConsensusComponentStsPod(&testCtx, nil, clusterName, compName, podName, role, mode)
   290  			ppod := testapps.NewPodFactory(testCtx.DefaultNamespace, "pod").
   291  				SetOwnerReferences("apps/v1", constant.StatefulSetKind, nil).
   292  				AddAppInstanceLabel(clusterName).
   293  				AddAppComponentLabel(compName).
   294  				AddAppManagedByLabel().
   295  				AddRoleLabel(role).
   296  				AddConsensusSetAccessModeLabel(mode).
   297  				AddControllerRevisionHashLabel("").
   298  				AddContainer(corev1.Container{
   299  					Name:  testapps.DefaultMySQLContainerName,
   300  					Image: testapps.ApeCloudMySQLImage,
   301  					LivenessProbe: &corev1.Probe{
   302  						ProbeHandler: corev1.ProbeHandler{
   303  							HTTPGet: &corev1.HTTPGetAction{
   304  								Path: "/hello",
   305  								Port: intstr.FromInt(1024),
   306  							},
   307  						},
   308  						TimeoutSeconds:   1,
   309  						PeriodSeconds:    1,
   310  						FailureThreshold: 1,
   311  					},
   312  					StartupProbe: &corev1.Probe{
   313  						ProbeHandler: corev1.ProbeHandler{
   314  							TCPSocket: &corev1.TCPSocketAction{
   315  								Port: intstr.FromInt(1024),
   316  							},
   317  						},
   318  					},
   319  				}).
   320  				GetObject()
   321  			resolvePodSpecDefaultFields(pod.Spec, &ppod.Spec)
   322  			Expect(reflect.DeepEqual(pod.Spec, ppod.Spec)).Should(BeTrue())
   323  		})
   324  	})
   325  })