github.com/verrazzano/verrazzano@v1.7.0/pkg/controller/errors/errors.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package spi
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	controllerruntime "sigs.k8s.io/controller-runtime"
    11  )
    12  
    13  // RetryableError an error that can be used to indicate to a controller that a requeue is needed, with an optional custom result
    14  type RetryableError struct {
    15  	// The source of the error
    16  	Source string
    17  	// The operation type
    18  	Operation string
    19  	// The root cause error
    20  	Cause error
    21  	// An optional Result type to return to the controllerruntime
    22  	Result controllerruntime.Result
    23  }
    24  
    25  // HasCause indicates whether or not the error has a root cause
    26  func (r RetryableError) HasCause() bool {
    27  	return r.Cause != nil
    28  }
    29  
    30  var _ error = RetryableError{}
    31  
    32  // Error implements the basic Go error contract
    33  func (r RetryableError) Error() string {
    34  	var builder strings.Builder
    35  	builder.WriteString("Retryable error")
    36  	if r.Source != "" {
    37  		builder.WriteString(fmt.Sprintf(", source: %s", r.Source))
    38  	}
    39  	if r.Operation != "" {
    40  		builder.WriteString(fmt.Sprintf(", operation: %s", r.Operation))
    41  	}
    42  	if r.Cause != nil {
    43  		builder.WriteString(fmt.Sprintf(", cause: %s", r.Cause))
    44  	}
    45  	if !r.Result.IsZero() {
    46  		builder.WriteString(fmt.Sprintf(", result: {requeue: %v, requeueAfter: %s}", r.Result.Requeue, r.Result.RequeueAfter))
    47  	}
    48  	return builder.String()
    49  }
    50  
    51  // IsRetryableError returns true if the error is a RetryableError.
    52  func IsRetryableError(err error) bool {
    53  	_, ok := err.(RetryableError)
    54  	return ok
    55  }
    56  
    57  // IsUpdateConflict returns true if the error is an update conflict error. This is occurs when the controller-runtime cache
    58  // is out of sync with the etcd database
    59  func IsUpdateConflict(err error) bool {
    60  	return strings.Contains(err.Error(), "the object has been modified; please apply your changes to the latest version")
    61  }
    62  
    63  // ShouldLogKubernetesAPIError returns true if error should be logged.  This is used
    64  // when calling the Kubernetes API, so conflict and webhook
    65  // errors are not logged, the controller will just retry.
    66  func ShouldLogKubernetesAPIError(err error) bool {
    67  	if err == nil {
    68  		return false
    69  	}
    70  	if IsUpdateConflict(err) || IsRetryableError(err) {
    71  		return false
    72  	}
    73  	return true
    74  }
    75  
    76  // IsK8sAPIServerError returns false if the error is not due to Kubernetes API server load or too many requests rate limit.
    77  // The goal is to reduce log noise in platform operator and provide meaningful information
    78  func IsK8sAPIServerError(err error) (bool, string) {
    79  	if err == nil {
    80  		return false, ""
    81  	}
    82  	var message string
    83  	if strings.Contains(err.Error(), "the server is currently unable to handle the request") {
    84  		message = "KubernetesAPIServerError: the server is currently unable to handle the request"
    85  		return true, message
    86  	}
    87  
    88  	if strings.Contains(err.Error(), "too many requests and has asked us to try again") {
    89  		message = "KubernetesAPIServerError: the server has received too many requests and has asked us to try again later"
    90  		return true, message
    91  	}
    92  
    93  	return false, message
    94  }