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  }