istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/route/retry/retry_test.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_test
    16  
    17  import (
    18  	"reflect"
    19  	"testing"
    20  	"time"
    21  
    22  	envoyroute "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  	. "github.com/onsi/gomega"
    25  	"google.golang.org/protobuf/types/known/durationpb"
    26  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    27  
    28  	networking "istio.io/api/networking/v1alpha3"
    29  	"istio.io/istio/pilot/pkg/networking/core/route/retry"
    30  	"istio.io/istio/pilot/pkg/util/protoconv"
    31  )
    32  
    33  func TestRetry(t *testing.T) {
    34  	testCases := []struct {
    35  		name       string
    36  		route      *networking.HTTPRoute
    37  		assertFunc func(g *WithT, policy *envoyroute.RetryPolicy)
    38  	}{
    39  		{
    40  			name: "TestNilRetryShouldReturnDefault",
    41  			// Create a route where no retry policy has been explicitly set.
    42  			route: &networking.HTTPRoute{},
    43  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
    44  				g.Expect(policy).To(Not(BeNil()))
    45  				g.Expect(policy).To(Equal(retry.DefaultPolicy()))
    46  			},
    47  		},
    48  		{
    49  			name: "TestZeroAttemptsShouldReturnNilPolicy",
    50  			// Create a route with a retry policy with zero attempts configured.
    51  			route: &networking.HTTPRoute{
    52  				Retries: &networking.HTTPRetry{
    53  					// Explicitly not retrying.
    54  					Attempts: 0,
    55  				},
    56  			},
    57  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
    58  				g.Expect(policy).To(BeNil())
    59  			},
    60  		},
    61  		{
    62  			name: "TestRetryWithAllFieldsSet",
    63  			// Create a route with a retry policy with all fields configured.
    64  			route: &networking.HTTPRoute{
    65  				Retries: &networking.HTTPRetry{
    66  					Attempts:      2,
    67  					RetryOn:       "some,fake,conditions",
    68  					PerTryTimeout: durationpb.New(time.Second * 3),
    69  				},
    70  			},
    71  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
    72  				g.Expect(policy).To(Not(BeNil()))
    73  				g.Expect(policy.RetryOn).To(Equal("some,fake,conditions"))
    74  				g.Expect(policy.PerTryTimeout).To(Equal(durationpb.New(time.Second * 3)))
    75  				g.Expect(policy.NumRetries.Value).To(Equal(uint32(2)))
    76  				g.Expect(policy.RetriableStatusCodes).To(Equal(make([]uint32, 0)))
    77  				g.Expect(policy.RetryPriority).To(BeNil())
    78  				g.Expect(policy.HostSelectionRetryMaxAttempts).To(Equal(retry.DefaultPolicy().HostSelectionRetryMaxAttempts))
    79  				g.Expect(policy.RetryHostPredicate).To(Equal(retry.DefaultPolicy().RetryHostPredicate))
    80  			},
    81  		},
    82  		{
    83  			name: "TestRetryOnWithEmptyParts",
    84  			// Create a route with a retry policy with empty retry conditions configured.
    85  			route: &networking.HTTPRoute{
    86  				Retries: &networking.HTTPRetry{
    87  					// Explicitly not retrying.
    88  					Attempts: 2,
    89  					RetryOn:  "some,fake,conditions,,,",
    90  				},
    91  			},
    92  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
    93  				g.Expect(policy).To(Not(BeNil()))
    94  				g.Expect(policy.RetryOn).To(Equal("some,fake,conditions"))
    95  				g.Expect(policy.RetriableStatusCodes).To(Equal([]uint32{}))
    96  			},
    97  		},
    98  		{
    99  			name: "TestRetryOnWithRetriableStatusCodes",
   100  			// Create a route with a retry policy with retriable status code.
   101  			route: &networking.HTTPRoute{
   102  				Retries: &networking.HTTPRetry{
   103  					// Explicitly not retrying.
   104  					Attempts: 2,
   105  					RetryOn:  "gateway-error,retriable-status-codes,503",
   106  				},
   107  			},
   108  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
   109  				g.Expect(policy).To(Not(BeNil()))
   110  				g.Expect(policy.RetryOn).To(Equal("gateway-error,retriable-status-codes"))
   111  				g.Expect(policy.RetriableStatusCodes).To(Equal([]uint32{503}))
   112  			},
   113  		},
   114  		{
   115  			name: "TestRetryOnWithWhitespace",
   116  			// Create a route with a retry policy with retryOn having white spaces.
   117  			route: &networking.HTTPRoute{
   118  				Retries: &networking.HTTPRetry{
   119  					// Explicitly not retrying.
   120  					Attempts: 2,
   121  					RetryOn:  " some,	,fake ,	conditions, ,",
   122  				},
   123  			},
   124  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
   125  				g.Expect(policy).To(Not(BeNil()))
   126  				g.Expect(policy.RetryOn).To(Equal("some,fake,conditions"))
   127  				g.Expect(policy.RetriableStatusCodes).To(Equal([]uint32{}))
   128  			},
   129  		},
   130  		{
   131  			name: "TestRetryOnContainingStatusCodes",
   132  			// Create a route with a retry policy with status codes.
   133  			route: &networking.HTTPRoute{
   134  				Retries: &networking.HTTPRetry{
   135  					Attempts: 2,
   136  					RetryOn:  "some,fake,5xx,404,conditions,503",
   137  				},
   138  			},
   139  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
   140  				g.Expect(policy).To(Not(BeNil()))
   141  				g.Expect(policy.RetryOn).To(Equal("some,fake,5xx,conditions,retriable-status-codes"))
   142  				g.Expect(policy.RetriableStatusCodes).To(Equal([]uint32{404, 503}))
   143  			},
   144  		},
   145  		{
   146  			name: "TestRetryOnWithInvalidStatusCodesShouldAddToRetryOn",
   147  			// Create a route with a retry policy with invalid status codes.
   148  			route: &networking.HTTPRoute{
   149  				Retries: &networking.HTTPRetry{
   150  					Attempts: 2,
   151  					RetryOn:  "some,fake,conditions,1000",
   152  				},
   153  			},
   154  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
   155  				g.Expect(policy).To(Not(BeNil()))
   156  				g.Expect(policy.RetryOn).To(Equal("some,fake,conditions,1000"))
   157  				g.Expect(policy.RetriableStatusCodes).To(Equal([]uint32{}))
   158  			},
   159  		},
   160  		{
   161  			name: "TestMissingRetryOnShouldReturnDefaults",
   162  			// Create a route with a retry policy with two attempts configured.
   163  			route: &networking.HTTPRoute{
   164  				Retries: &networking.HTTPRetry{
   165  					Attempts: 2,
   166  				},
   167  			},
   168  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
   169  				g.Expect(policy).To(Not(BeNil()))
   170  				g.Expect(policy.RetryOn).To(Equal(retry.DefaultPolicy().RetryOn))
   171  				g.Expect(policy.RetriableStatusCodes).To(Equal(retry.DefaultPolicy().RetriableStatusCodes))
   172  			},
   173  		},
   174  		{
   175  			name: "TestMissingPerTryTimeoutShouldReturnNil",
   176  			// Create a route with a retry policy without per try timeout.
   177  			route: &networking.HTTPRoute{
   178  				Retries: &networking.HTTPRetry{
   179  					Attempts: 2,
   180  				},
   181  			},
   182  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
   183  				g.Expect(policy).To(Not(BeNil()))
   184  				g.Expect(policy.PerTryTimeout).To(BeNil())
   185  			},
   186  		},
   187  		{
   188  			name: "TestRetryRemoteLocalities",
   189  			// Create a route with a retry policy with RetryRemoteLocalities enabled.
   190  			route: &networking.HTTPRoute{
   191  				Retries: &networking.HTTPRetry{
   192  					Attempts: 2,
   193  					RetryRemoteLocalities: &wrappers.BoolValue{
   194  						Value: true,
   195  					},
   196  				},
   197  			},
   198  			assertFunc: func(g *WithT, policy *envoyroute.RetryPolicy) {
   199  				g.Expect(policy).To(Not(BeNil()))
   200  				g.Expect(policy.RetryOn).To(Equal(retry.DefaultPolicy().RetryOn))
   201  				g.Expect(policy.RetriableStatusCodes).To(Equal(retry.DefaultPolicy().RetriableStatusCodes))
   202  
   203  				previousPrioritiesConfig := &previouspriorities.PreviousPrioritiesConfig{
   204  					UpdateFrequency: int32(2),
   205  				}
   206  				expected := &envoyroute.RetryPolicy_RetryPriority{
   207  					Name: "envoy.retry_priorities.previous_priorities",
   208  					ConfigType: &envoyroute.RetryPolicy_RetryPriority_TypedConfig{
   209  						TypedConfig: protoconv.MessageToAny(previousPrioritiesConfig),
   210  					},
   211  				}
   212  				if !reflect.DeepEqual(policy.RetryPriority, expected) {
   213  					t.Fatalf("Expected %v, actual %v", expected, policy.RetryPriority)
   214  				}
   215  			},
   216  		},
   217  	}
   218  	for _, tc := range testCases {
   219  		t.Run(tc.name, func(t *testing.T) {
   220  			g := NewWithT(t)
   221  			policy := retry.ConvertPolicy(tc.route.Retries, false)
   222  			if tc.assertFunc != nil {
   223  				tc.assertFunc(g, policy)
   224  			}
   225  		})
   226  	}
   227  }