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 })