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 }