github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/k8score/event_controller_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 k8score 21 22 import ( 23 "fmt" 24 "strings" 25 "time" 26 27 . "github.com/onsi/ginkgo/v2" 28 . "github.com/onsi/gomega" 29 30 "github.com/sethvargo/go-password/password" 31 corev1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/types" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/controller-runtime/pkg/log" 36 37 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 38 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 39 "github.com/1aal/kubeblocks/pkg/constant" 40 "github.com/1aal/kubeblocks/pkg/controller/builder" 41 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 42 "github.com/1aal/kubeblocks/pkg/generics" 43 lorryutil "github.com/1aal/kubeblocks/pkg/lorry/util" 44 testapps "github.com/1aal/kubeblocks/pkg/testutil/apps" 45 ) 46 47 var _ = Describe("Event Controller", func() { 48 cleanEnv := func() { 49 // must wait till resources deleted and no longer existed before the testcases start, 50 // otherwise if later it needs to create some new resource objects with the same name, 51 // in race conditions, it will find the existence of old objects, resulting failure to 52 // create the new objects. 53 By("clean resources") 54 55 testapps.ClearClusterResources(&testCtx) 56 57 // delete rest mocked objects 58 inNS := client.InNamespace(testCtx.DefaultNamespace) 59 ml := client.HasLabels{testCtx.TestObjLabelKey} 60 // namespaced 61 testapps.ClearResources(&testCtx, generics.EventSignature, inNS, ml) 62 testapps.ClearResources(&testCtx, generics.PodSignature, inNS, ml) 63 } 64 65 var ( 66 beforeLastTS = time.Date(2021, time.January, 1, 12, 0, 0, 0, time.UTC) 67 initLastTS = time.Date(2022, time.January, 1, 12, 0, 0, 0, time.UTC) 68 afterLastTS = time.Date(2023, time.January, 1, 12, 0, 0, 0, time.UTC) 69 ) 70 71 createRoleChangedEvent := func(podName, role string, podUid types.UID) *corev1.Event { 72 seq, _ := password.Generate(16, 16, 0, true, true) 73 objectRef := corev1.ObjectReference{ 74 APIVersion: "v1", 75 Kind: "Pod", 76 Namespace: testCtx.DefaultNamespace, 77 Name: podName, 78 UID: podUid, 79 } 80 eventName := strings.Join([]string{podName, seq}, ".") 81 return builder.NewEventBuilder(testCtx.DefaultNamespace, eventName). 82 SetInvolvedObject(objectRef). 83 SetMessage(fmt.Sprintf("{\"event\":\"roleChanged\",\"originalRole\":\"secondary\",\"role\":\"%s\"}", role)). 84 SetReason(string(lorryutil.CheckRoleOperation)). 85 SetType(corev1.EventTypeNormal). 86 SetFirstTimestamp(metav1.NewTime(initLastTS)). 87 SetLastTimestamp(metav1.NewTime(initLastTS)). 88 SetEventTime(metav1.NewMicroTime(initLastTS)). 89 SetReportingController("lorry"). 90 SetReportingInstance(podName). 91 SetAction("mock-create-event-action"). 92 GetObject() 93 } 94 95 createInvolvedPod := func(name, clusterName, componentName string) *corev1.Pod { 96 return builder.NewPodBuilder(testCtx.DefaultNamespace, name). 97 AddLabels(constant.AppInstanceLabelKey, clusterName). 98 AddLabels(constant.KBAppComponentLabelKey, componentName). 99 SetContainers([]corev1.Container{ 100 { 101 Image: "foo", 102 Name: "bar", 103 }, 104 }). 105 GetObject() 106 } 107 108 BeforeEach(cleanEnv) 109 110 AfterEach(cleanEnv) 111 112 Context("When receiving role changed event", func() { 113 It("should handle it properly", func() { 114 By("create cluster & clusterDef") 115 clusterDefName := "foo" 116 consensusCompName := "consensus" 117 consensusCompDefName := "consensus" 118 clusterDefObj := testapps.NewClusterDefFactory(clusterDefName). 119 AddComponentDef(testapps.ConsensusMySQLComponent, consensusCompDefName). 120 Create(&testCtx).GetObject() 121 clusterObj := testapps.NewClusterFactory(testCtx.DefaultNamespace, "", 122 clusterDefObj.Name, "").WithRandomName(). 123 AddComponent(consensusCompName, consensusCompDefName). 124 Create(&testCtx).GetObject() 125 Eventually(testapps.CheckObjExists(&testCtx, client.ObjectKeyFromObject(clusterObj), &appsv1alpha1.Cluster{}, true)).Should(Succeed()) 126 127 rsmName := fmt.Sprintf("%s-%s", clusterObj.Name, consensusCompName) 128 rsm := testapps.NewRSMFactory(clusterObj.Namespace, rsmName, clusterObj.Name, consensusCompName). 129 SetReplicas(int32(3)). 130 AddContainer(corev1.Container{Name: testapps.DefaultMySQLContainerName, Image: testapps.ApeCloudMySQLImage}). 131 Create(&testCtx).GetObject() 132 Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(rsm), func(tmpRSM *workloads.ReplicatedStateMachine) { 133 tmpRSM.Spec.Roles = []workloads.ReplicaRole{ 134 { 135 Name: "leader", 136 IsLeader: true, 137 AccessMode: workloads.ReadWriteMode, 138 CanVote: true, 139 }, 140 { 141 Name: "follower", 142 IsLeader: false, 143 AccessMode: workloads.ReadonlyMode, 144 CanVote: true, 145 }, 146 } 147 })()).Should(Succeed()) 148 By("create involved pod") 149 var uid types.UID 150 podName := fmt.Sprintf("%s-%d", rsmName, 0) 151 pod := createInvolvedPod(podName, clusterObj.Name, consensusCompName) 152 Expect(testCtx.CreateObj(ctx, pod)).Should(Succeed()) 153 Eventually(func() error { 154 p := &corev1.Pod{} 155 defer func() { 156 uid = p.UID 157 }() 158 return k8sClient.Get(ctx, types.NamespacedName{ 159 Namespace: pod.Namespace, 160 Name: pod.Name, 161 }, p) 162 }).Should(Succeed()) 163 Expect(uid).ShouldNot(BeNil()) 164 165 By("send role changed event") 166 role := "leader" 167 sndEvent := createRoleChangedEvent(podName, role, uid) 168 Expect(testCtx.CreateObj(ctx, sndEvent)).Should(Succeed()) 169 Eventually(func() string { 170 event := &corev1.Event{} 171 if err := k8sClient.Get(ctx, types.NamespacedName{ 172 Namespace: sndEvent.Namespace, 173 Name: sndEvent.Name, 174 }, event); err != nil { 175 return err.Error() 176 } 177 return event.InvolvedObject.Name 178 }).Should(Equal(sndEvent.InvolvedObject.Name)) 179 180 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pod), func(g Gomega, p *corev1.Pod) { 181 g.Expect(p).ShouldNot(BeNil()) 182 g.Expect(p.Labels).ShouldNot(BeNil()) 183 g.Expect(p.Labels[constant.RoleLabelKey]).Should(Equal(role)) 184 g.Expect(p.Annotations[constant.LastRoleSnapshotVersionAnnotationKey]).Should(Equal(sndEvent.EventTime.Time.Format(time.RFC3339Nano))) 185 })).Should(Succeed()) 186 187 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(sndEvent), func(g Gomega, e *corev1.Event) { 188 g.Expect(e).ShouldNot(BeNil()) 189 g.Expect(e.Annotations).ShouldNot(BeNil()) 190 g.Expect(e.Annotations[roleChangedAnnotKey]).Should(Equal("count-0")) 191 })).Should(Succeed()) 192 193 By("check whether the duration and number of events reach the threshold") 194 Expect(IsOvertimeEvent(sndEvent, 5*time.Second)).Should(BeFalse()) 195 196 By("send role changed event with beforeLastTS earlier than pod last role changes event timestamp annotation should not be update successfully") 197 role = "follower" 198 sndInvalidEvent := createRoleChangedEvent(podName, role, uid) 199 sndInvalidEvent.EventTime = metav1.NewMicroTime(beforeLastTS) 200 Expect(testCtx.CreateObj(ctx, sndInvalidEvent)).Should(Succeed()) 201 Eventually(func() string { 202 event := &corev1.Event{} 203 if err := k8sClient.Get(ctx, types.NamespacedName{ 204 Namespace: sndInvalidEvent.Namespace, 205 Name: sndInvalidEvent.Name, 206 }, event); err != nil { 207 return err.Error() 208 } 209 return event.InvolvedObject.Name 210 }).Should(Equal(sndInvalidEvent.InvolvedObject.Name)) 211 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pod), func(g Gomega, p *corev1.Pod) { 212 g.Expect(p).ShouldNot(BeNil()) 213 g.Expect(p.Labels).ShouldNot(BeNil()) 214 g.Expect(p.Labels[constant.RoleLabelKey]).ShouldNot(Equal(role)) 215 g.Expect(p.Annotations[constant.LastRoleSnapshotVersionAnnotationKey]).ShouldNot(Equal(sndInvalidEvent.EventTime.Time.Format(time.RFC3339Nano))) 216 })).Should(Succeed()) 217 218 By("send role changed event with afterLastTS later than pod last role changes event timestamp annotation should be update successfully") 219 role = "follower" 220 sndValidEvent := createRoleChangedEvent(podName, role, uid) 221 sndValidEvent.EventTime = metav1.NewMicroTime(afterLastTS) 222 Expect(testCtx.CreateObj(ctx, sndValidEvent)).Should(Succeed()) 223 Eventually(func() string { 224 event := &corev1.Event{} 225 if err := k8sClient.Get(ctx, types.NamespacedName{ 226 Namespace: sndValidEvent.Namespace, 227 Name: sndValidEvent.Name, 228 }, event); err != nil { 229 return err.Error() 230 } 231 return event.InvolvedObject.Name 232 }).Should(Equal(sndValidEvent.InvolvedObject.Name)) 233 Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(pod), func(g Gomega, p *corev1.Pod) { 234 g.Expect(p).ShouldNot(BeNil()) 235 g.Expect(p.Labels).ShouldNot(BeNil()) 236 g.Expect(p.Labels[constant.RoleLabelKey]).Should(Equal(role)) 237 g.Expect(p.Annotations[constant.LastRoleSnapshotVersionAnnotationKey]).Should(Equal(sndValidEvent.EventTime.Time.Format(time.RFC3339Nano))) 238 })).Should(Succeed()) 239 }) 240 }) 241 242 Context("ParseProbeEventMessage function", func() { 243 It("should work well", func() { 244 reqCtx := intctrlutil.RequestCtx{ 245 Ctx: testCtx.Ctx, 246 Log: log.FromContext(ctx).WithValues("event", testCtx.DefaultNamespace), 247 } 248 event := createRoleChangedEvent("foo", "", "bar") 249 event.Message = "not-a-role-message" 250 eventMessage := ParseProbeEventMessage(reqCtx, event) 251 Expect(eventMessage).Should(BeNil()) 252 }) 253 }) 254 255 Context("IsOvertimeEvent function", func() { 256 It("should work well", func() { 257 event := createRoleChangedEvent("foo", "", "bar") 258 timeout := 50 * time.Millisecond 259 event.FirstTimestamp = metav1.NewTime(time.Now()) 260 event.LastTimestamp = metav1.NewTime(time.Now()) 261 Expect(IsOvertimeEvent(event, timeout)).Should(BeFalse()) 262 event.LastTimestamp = metav1.NewTime(event.LastTimestamp.Time.Add(2 * timeout)) 263 Expect(IsOvertimeEvent(event, timeout)).Should(BeTrue()) 264 265 event.EventTime = metav1.NewMicroTime(time.Now()) 266 event.Series = &corev1.EventSeries{LastObservedTime: metav1.NewMicroTime(time.Now())} 267 Expect(IsOvertimeEvent(event, timeout)).Should(BeFalse()) 268 event.Series = &corev1.EventSeries{LastObservedTime: metav1.NewMicroTime(time.Now().Add(2 * timeout))} 269 Expect(IsOvertimeEvent(event, timeout)).Should(BeTrue()) 270 }) 271 }) 272 })