github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/cluster_status_event_handler.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 apps 21 22 import ( 23 "context" 24 "time" 25 26 "golang.org/x/exp/slices" 27 appsv1 "k8s.io/api/apps/v1" 28 corev1 "k8s.io/api/core/v1" 29 "k8s.io/client-go/tools/record" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 "github.com/1aal/kubeblocks/controllers/k8score" 33 "github.com/1aal/kubeblocks/pkg/constant" 34 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 35 lorryutil "github.com/1aal/kubeblocks/pkg/lorry/util" 36 ) 37 38 // EventTimeOut timeout of the event 39 const EventTimeOut = 30 * time.Second 40 41 // ClusterStatusEventHandler is the event handler for the cluster status event 42 type ClusterStatusEventHandler struct{} 43 44 var _ k8score.EventHandler = &ClusterStatusEventHandler{} 45 46 func init() { 47 k8score.EventHandlerMap["cluster-status-handler"] = &ClusterStatusEventHandler{} 48 } 49 50 // Handle handles the cluster status events. 51 func (r *ClusterStatusEventHandler) Handle(cli client.Client, reqCtx intctrlutil.RequestCtx, recorder record.EventRecorder, event *corev1.Event) error { 52 if event.Reason != string(lorryutil.CheckRoleOperation) { 53 return handleEventForClusterStatus(reqCtx.Ctx, cli, recorder, event) 54 } 55 56 // parse probe event message when field path is probe-role-changed-check 57 message := k8score.ParseProbeEventMessage(reqCtx, event) 58 if message == nil { 59 reqCtx.Log.Info("parse probe event message failed", "message", event.Message) 60 return nil 61 } 62 63 // if probe message event is checkRoleFailed, it means the cluster is abnormal, need to handle the cluster status 64 if message.Event == lorryutil.OperationFailed { 65 return handleEventForClusterStatus(reqCtx.Ctx, cli, recorder, event) 66 } 67 return nil 68 } 69 70 // TODO: Unified cluster event processing 71 // handleEventForClusterStatus handles event for cluster Warning and Failed phase 72 func handleEventForClusterStatus(ctx context.Context, cli client.Client, recorder record.EventRecorder, event *corev1.Event) error { 73 74 type predicateProcessor struct { 75 pred func() bool 76 processor func() error 77 } 78 79 nilReturnHandler := func() error { return nil } 80 81 pps := []predicateProcessor{ 82 { 83 pred: func() bool { 84 return event.Type != corev1.EventTypeWarning || 85 !isTargetKindForEvent(event) 86 }, 87 processor: nilReturnHandler, 88 }, 89 { 90 pred: func() bool { 91 // the error repeated several times, so we can be sure it's a real error to the cluster. 92 return !k8score.IsOvertimeEvent(event, EventTimeOut) 93 }, 94 processor: nilReturnHandler, 95 }, 96 { 97 // handle cluster workload error events such as pod/statefulset/deployment errors 98 // must be the last one 99 pred: func() bool { 100 return true 101 }, 102 processor: func() error { 103 return handleClusterStatusByEvent(ctx, cli, recorder, event) 104 }, 105 }, 106 } 107 108 for _, pp := range pps { 109 if pp.pred() { 110 return pp.processor() 111 } 112 } 113 return nil 114 } 115 116 // handleClusterStatusByEvent handles the cluster status when warning event happened 117 func handleClusterStatusByEvent(ctx context.Context, cli client.Client, recorder record.EventRecorder, event *corev1.Event) error { 118 object, err := getEventInvolvedObject(ctx, cli, event) 119 if err != nil { 120 return err 121 } 122 return notifyClusterStatusChange(ctx, cli, recorder, object, event) 123 } 124 125 // getEventInvolvedObject gets event involved object for StatefulSet/Deployment/Pod workload 126 func getEventInvolvedObject(ctx context.Context, cli client.Client, event *corev1.Event) (client.Object, error) { 127 objectKey := client.ObjectKey{ 128 Name: event.InvolvedObject.Name, 129 Namespace: event.InvolvedObject.Namespace, 130 } 131 var err error 132 // If client.object interface object is used as a parameter, it will not return an error when the object is not found. 133 // so we should specify the object type to get the object. 134 switch event.InvolvedObject.Kind { 135 case constant.PodKind: 136 pod := &corev1.Pod{} 137 err = cli.Get(ctx, objectKey, pod) 138 return pod, err 139 case constant.StatefulSetKind: 140 sts := &appsv1.StatefulSet{} 141 err = cli.Get(ctx, objectKey, sts) 142 return sts, err 143 case constant.DeploymentKind: 144 deployment := &appsv1.Deployment{} 145 err = cli.Get(ctx, objectKey, deployment) 146 return deployment, err 147 } 148 return nil, err 149 } 150 151 // isTargetKindForEvent checks the event involved object is one of the target resources 152 func isTargetKindForEvent(event *corev1.Event) bool { 153 return slices.Index([]string{constant.PodKind, constant.DeploymentKind, constant.StatefulSetKind}, event.InvolvedObject.Kind) != -1 154 }