istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/route/retry/retry.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package retry
    16  
    17  import (
    18  	"net/http"
    19  	"strconv"
    20  	"strings"
    21  
    22  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    23  	previouspriorities "github.com/envoyproxy/go-control-plane/envoy/extensions/retry/priority/previous_priorities/v3"
    24  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    25  
    26  	networking "istio.io/api/networking/v1alpha3"
    27  	"istio.io/istio/pilot/pkg/util/protoconv"
    28  	xdsfilters "istio.io/istio/pilot/pkg/xds/filters"
    29  )
    30  
    31  var defaultRetryPriorityTypedConfig = protoconv.MessageToAny(buildPreviousPrioritiesConfig())
    32  
    33  // DefaultPolicy gets a copy of the default retry policy.
    34  func DefaultPolicy() *route.RetryPolicy {
    35  	policy := defaultPolicy()
    36  	policy.RetryHostPredicate = []*route.RetryPolicy_RetryHostPredicate{
    37  		// to configure retries to prefer hosts that haven’t been attempted already,
    38  		// the builtin `envoy.retry_host_predicates.previous_hosts` predicate can be used.
    39  		xdsfilters.RetryPreviousHosts,
    40  	}
    41  	return policy
    42  }
    43  
    44  func defaultPolicy() *route.RetryPolicy {
    45  	policy := route.RetryPolicy{
    46  		NumRetries:           &wrappers.UInt32Value{Value: 2},
    47  		RetryOn:              "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
    48  		RetriableStatusCodes: []uint32{http.StatusServiceUnavailable},
    49  		// TODO: allow this to be configured via API.
    50  		HostSelectionRetryMaxAttempts: 5,
    51  	}
    52  	return &policy
    53  }
    54  
    55  // DefaultConsistentHashPolicy gets a copy of the default retry policy without previous host predicate.
    56  // When Consistent Hashing is enabled, we don't want to use other hosts during retries.
    57  func DefaultConsistentHashPolicy() *route.RetryPolicy {
    58  	return defaultPolicy()
    59  }
    60  
    61  // ConvertPolicy converts the given Istio retry policy to an Envoy policy.
    62  //
    63  // If in is nil, DefaultPolicy is returned.
    64  //
    65  // If in.Attempts == 0, returns nil.
    66  //
    67  // Otherwise, the returned policy is DefaultPolicy with the following overrides:
    68  //
    69  // - NumRetries: set from in.Attempts
    70  //
    71  // - RetryOn, RetriableStatusCodes: set from in.RetryOn (if specified). RetriableStatusCodes
    72  // is appended when encountering parts that are valid HTTP status codes.
    73  //
    74  // - PerTryTimeout: set from in.PerTryTimeout (if specified)
    75  func ConvertPolicy(in *networking.HTTPRetry, hashPolicy bool) *route.RetryPolicy {
    76  	var out *route.RetryPolicy
    77  	if hashPolicy {
    78  		out = DefaultConsistentHashPolicy()
    79  	} else {
    80  		out = DefaultPolicy()
    81  	}
    82  	if in == nil {
    83  		// No policy was set, use a default.
    84  		return out
    85  	}
    86  
    87  	if in.Attempts <= 0 {
    88  		// Configuration is explicitly disabling the retry policy.
    89  		return nil
    90  	}
    91  
    92  	// A policy was specified. Start with the default and override with user-provided fields where appropriate.
    93  	out.NumRetries = &wrappers.UInt32Value{Value: uint32(in.Attempts)}
    94  
    95  	if in.RetryOn != "" {
    96  		// Allow the incoming configuration to specify both Envoy RetryOn and RetriableStatusCodes. Any integers are
    97  		// assumed to be status codes.
    98  		out.RetryOn, out.RetriableStatusCodes = parseRetryOn(in.RetryOn)
    99  		// If user has just specified HTTP status codes in retryOn but have not specified "retriable-status-codes", let us add it.
   100  		if len(out.RetriableStatusCodes) > 0 && !strings.Contains(out.RetryOn, "retriable-status-codes") {
   101  			out.RetryOn += ",retriable-status-codes"
   102  		}
   103  	}
   104  
   105  	if in.PerTryTimeout != nil {
   106  		out.PerTryTimeout = in.PerTryTimeout
   107  	}
   108  
   109  	if in.RetryRemoteLocalities != nil && in.RetryRemoteLocalities.GetValue() {
   110  		out.RetryPriority = &route.RetryPolicy_RetryPriority{
   111  			Name: "envoy.retry_priorities.previous_priorities",
   112  			ConfigType: &route.RetryPolicy_RetryPriority_TypedConfig{
   113  				TypedConfig: defaultRetryPriorityTypedConfig,
   114  			},
   115  		}
   116  	}
   117  
   118  	return out
   119  }
   120  
   121  func parseRetryOn(retryOn string) (string, []uint32) {
   122  	codes := make([]uint32, 0)
   123  	tojoin := make([]string, 0)
   124  
   125  	parts := strings.Split(retryOn, ",")
   126  	for _, part := range parts {
   127  		part = strings.TrimSpace(part)
   128  		if part == "" {
   129  			continue
   130  		}
   131  
   132  		// Try converting it to an integer to see if it's a valid HTTP status code.
   133  		i, err := strconv.Atoi(part)
   134  
   135  		if err == nil && http.StatusText(i) != "" {
   136  			codes = append(codes, uint32(i))
   137  		} else {
   138  			tojoin = append(tojoin, part)
   139  		}
   140  	}
   141  
   142  	return strings.Join(tojoin, ","), codes
   143  }
   144  
   145  // buildPreviousPrioritiesConfig builds a PreviousPrioritiesConfig with a default
   146  // value for UpdateFrequency which indicated how often to update the priority.
   147  func buildPreviousPrioritiesConfig() *previouspriorities.PreviousPrioritiesConfig {
   148  	return &previouspriorities.PreviousPrioritiesConfig{
   149  		UpdateFrequency: int32(2),
   150  	}
   151  }