mirror of https://github.com/grpc/grpc-go.git
				
				
				
			
		
			
				
	
	
		
			368 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			368 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
|  *
 | |
|  * Copyright 2020 gRPC authors.
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| package resolver
 | |
| 
 | |
| import (
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/google/go-cmp/cmp"
 | |
| 	"google.golang.org/grpc/internal"
 | |
| 	"google.golang.org/grpc/internal/grpcrand"
 | |
| 	"google.golang.org/grpc/serviceconfig"
 | |
| 	_ "google.golang.org/grpc/xds/internal/balancer/weightedtarget"
 | |
| 	_ "google.golang.org/grpc/xds/internal/balancer/xdsrouting"
 | |
| 	xdsclient "google.golang.org/grpc/xds/internal/client"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	testCluster1           = "test-cluster-1"
 | |
| 	testOneClusterOnlyJSON = `{"loadBalancingConfig":[{
 | |
|     "xds_routing_experimental":{
 | |
|       "action":{
 | |
|         "test-cluster-1_0":{
 | |
|           "childPolicy":[{
 | |
|             "weighted_target_experimental":{
 | |
|               "targets":{
 | |
|                 "test-cluster-1":{
 | |
|                   "weight":1,
 | |
|                   "childPolicy":[{"cds_experimental":{"cluster":"test-cluster-1"}}]
 | |
|                 }
 | |
|               }}}]
 | |
|         }
 | |
|       },
 | |
|       "route":[{"prefix":"","action":"test-cluster-1_0"}]
 | |
|     }}]}`
 | |
| 	testWeightedCDSJSON = `{"loadBalancingConfig":[{
 | |
|     "xds_routing_experimental":{
 | |
|       "action":{
 | |
|         "cluster_1_cluster_2_1":{
 | |
|           "childPolicy":[{
 | |
|             "weighted_target_experimental":{
 | |
|               "targets":{
 | |
|                 "cluster_1":{
 | |
|                   "weight":75,
 | |
|                   "childPolicy":[{"cds_experimental":{"cluster":"cluster_1"}}]
 | |
|                 },
 | |
|                 "cluster_2":{
 | |
|                   "weight":25,
 | |
|                   "childPolicy":[{"cds_experimental":{"cluster":"cluster_2"}}]
 | |
|                 }
 | |
|               }}}]
 | |
|         }
 | |
|       },
 | |
|       "route":[{"prefix":"","action":"cluster_1_cluster_2_1"}]
 | |
|     }}]}`
 | |
| 
 | |
| 	testRoutingJSON = `{"loadBalancingConfig":[{
 | |
|     "xds_routing_experimental": {
 | |
|       "action":{
 | |
|         "cluster_1_cluster_2_0":{
 | |
|           "childPolicy":[{
 | |
| 			"weighted_target_experimental": {
 | |
| 			  "targets": {
 | |
| 				"cluster_1" : {
 | |
| 				  "weight":75,
 | |
| 				  "childPolicy":[{"cds_experimental":{"cluster":"cluster_1"}}]
 | |
| 				},
 | |
| 				"cluster_2" : {
 | |
| 				  "weight":25,
 | |
| 				  "childPolicy":[{"cds_experimental":{"cluster":"cluster_2"}}]
 | |
| 				}
 | |
| 			  }
 | |
| 			}
 | |
|           }]
 | |
|         }
 | |
|       },
 | |
| 
 | |
|       "route":[{
 | |
|         "path":"/service_1/method_1",
 | |
|         "action":"cluster_1_cluster_2_0"
 | |
|       }]
 | |
|     }
 | |
|     }]}
 | |
| `
 | |
| 	testRoutingAllMatchersJSON = `{"loadBalancingConfig":[{
 | |
|     "xds_routing_experimental": {
 | |
|       "action":{
 | |
|         "cluster_1_0":{
 | |
|           "childPolicy":[{
 | |
| 			"weighted_target_experimental": {
 | |
| 			  "targets": {
 | |
| 				"cluster_1" : {
 | |
| 				  "weight":1,
 | |
| 				  "childPolicy":[{"cds_experimental":{"cluster":"cluster_1"}}]
 | |
| 				}
 | |
| 			  }
 | |
| 			}
 | |
|           }]
 | |
|         },
 | |
|         "cluster_2_0":{
 | |
|           "childPolicy":[{
 | |
| 			"weighted_target_experimental": {
 | |
| 			  "targets": {
 | |
| 				"cluster_2" : {
 | |
| 				  "weight":1,
 | |
| 				  "childPolicy":[{"cds_experimental":{"cluster":"cluster_2"}}]
 | |
| 				}
 | |
| 			  }
 | |
| 			}
 | |
|           }]
 | |
|         },
 | |
|         "cluster_3_0":{
 | |
|           "childPolicy":[{
 | |
| 			"weighted_target_experimental": {
 | |
| 			  "targets": {
 | |
| 				"cluster_3" : {
 | |
| 				  "weight":1,
 | |
| 				  "childPolicy":[{"cds_experimental":{"cluster":"cluster_3"}}]
 | |
| 				}
 | |
| 			  }
 | |
| 			}
 | |
|           }]
 | |
|         }
 | |
|       },
 | |
| 
 | |
|       "route":[{
 | |
|         "path":"/service_1/method_1",
 | |
|         "action":"cluster_1_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"/service_2/method_1",
 | |
|         "caseInsensitive":true,
 | |
|         "action":"cluster_1_0"
 | |
|       },
 | |
|       {
 | |
|         "regex":"^/service_2/method_3$",
 | |
|         "action":"cluster_1_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"",
 | |
|         "headers":[{"name":"header-1", "exactMatch":"value-1", "invertMatch":true}],
 | |
|         "action":"cluster_2_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"",
 | |
|         "headers":[{"name":"header-1", "regexMatch":"^value-1$"}],
 | |
|         "action":"cluster_2_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"",
 | |
|         "headers":[{"name":"header-1", "rangeMatch":{"start":-1, "end":7}}],
 | |
|         "action":"cluster_3_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"",
 | |
|         "headers":[{"name":"header-1", "presentMatch":true}],
 | |
|         "action":"cluster_3_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"",
 | |
|         "headers":[{"name":"header-1", "prefixMatch":"value-1"}],
 | |
|         "action":"cluster_2_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"",
 | |
|         "headers":[{"name":"header-1", "suffixMatch":"value-1"}],
 | |
|         "action":"cluster_2_0"
 | |
|       },
 | |
|       {
 | |
|         "prefix":"",
 | |
|         "matchFraction":{"value": 31415},
 | |
|         "action":"cluster_3_0"
 | |
|       }]
 | |
|     }
 | |
|     }]}
 | |
| `
 | |
| )
 | |
| 
 | |
| func TestRoutesToJSON(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name     string
 | |
| 		routes   []*xdsclient.Route
 | |
| 		wantJSON string
 | |
| 		wantErr  bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "one route",
 | |
| 			routes: []*xdsclient.Route{{
 | |
| 				Path:   newStringP("/service_1/method_1"),
 | |
| 				Action: map[string]uint32{"cluster_1": 75, "cluster_2": 25},
 | |
| 			}},
 | |
| 			wantJSON: testRoutingJSON,
 | |
| 			wantErr:  false,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "all matchers",
 | |
| 			routes: []*xdsclient.Route{
 | |
| 				{
 | |
| 					Path:   newStringP("/service_1/method_1"),
 | |
| 					Action: map[string]uint32{"cluster_1": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix:          newStringP("/service_2/method_1"),
 | |
| 					CaseInsensitive: true,
 | |
| 					Action:          map[string]uint32{"cluster_1": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Regex:  newStringP("^/service_2/method_3$"),
 | |
| 					Action: map[string]uint32{"cluster_1": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix: newStringP(""),
 | |
| 					Headers: []*xdsclient.HeaderMatcher{{
 | |
| 						Name:        "header-1",
 | |
| 						InvertMatch: newBoolP(true),
 | |
| 						ExactMatch:  newStringP("value-1"),
 | |
| 					}},
 | |
| 					Action: map[string]uint32{"cluster_2": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix: newStringP(""),
 | |
| 					Headers: []*xdsclient.HeaderMatcher{{
 | |
| 						Name:       "header-1",
 | |
| 						RegexMatch: newStringP("^value-1$"),
 | |
| 					}},
 | |
| 					Action: map[string]uint32{"cluster_2": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix: newStringP(""),
 | |
| 					Headers: []*xdsclient.HeaderMatcher{{
 | |
| 						Name:       "header-1",
 | |
| 						RangeMatch: &xdsclient.Int64Range{Start: -1, End: 7},
 | |
| 					}},
 | |
| 					Action: map[string]uint32{"cluster_3": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix: newStringP(""),
 | |
| 					Headers: []*xdsclient.HeaderMatcher{{
 | |
| 						Name:         "header-1",
 | |
| 						PresentMatch: newBoolP(true),
 | |
| 					}},
 | |
| 					Action: map[string]uint32{"cluster_3": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix: newStringP(""),
 | |
| 					Headers: []*xdsclient.HeaderMatcher{{
 | |
| 						Name:        "header-1",
 | |
| 						PrefixMatch: newStringP("value-1"),
 | |
| 					}},
 | |
| 					Action: map[string]uint32{"cluster_2": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix: newStringP(""),
 | |
| 					Headers: []*xdsclient.HeaderMatcher{{
 | |
| 						Name:        "header-1",
 | |
| 						SuffixMatch: newStringP("value-1"),
 | |
| 					}},
 | |
| 					Action: map[string]uint32{"cluster_2": 1},
 | |
| 				},
 | |
| 				{
 | |
| 					Prefix:   newStringP(""),
 | |
| 					Fraction: newUint32P(31415),
 | |
| 					Action:   map[string]uint32{"cluster_3": 1},
 | |
| 				},
 | |
| 			},
 | |
| 			wantJSON: testRoutingAllMatchersJSON,
 | |
| 			wantErr:  false,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			// Note this random number function only generates 0. This is
 | |
| 			// because the test doesn't handle action update, and there's only
 | |
| 			// one action for each cluster bundle.
 | |
| 			//
 | |
| 			// This is necessary so the output is deterministic.
 | |
| 			grpcrandInt63n = func(int64) int64 { return 0 }
 | |
| 			defer func() { grpcrandInt63n = grpcrand.Int63n }()
 | |
| 
 | |
| 			gotJSON, err := (&xdsResolver{}).routesToJSON(tt.routes)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("routesToJSON returned error: %v", err)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			gotParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(gotJSON)
 | |
| 			wantParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(tt.wantJSON)
 | |
| 
 | |
| 			if !internal.EqualServiceConfigForTesting(gotParsed.Config, wantParsed.Config) {
 | |
| 				t.Errorf("serviceUpdateToJSON() = %v, want %v", gotJSON, tt.wantJSON)
 | |
| 				t.Error("gotParsed: ", cmp.Diff(nil, gotParsed))
 | |
| 				t.Error("wantParsed: ", cmp.Diff(nil, wantParsed))
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestServiceUpdateToJSON(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name     string
 | |
| 		su       serviceUpdate
 | |
| 		wantJSON string
 | |
| 		wantErr  bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "routing",
 | |
| 			su: serviceUpdate{
 | |
| 				Routes: []*xdsclient.Route{{
 | |
| 					Path:   newStringP("/service_1/method_1"),
 | |
| 					Action: map[string]uint32{"cluster_1": 75, "cluster_2": 25},
 | |
| 				}},
 | |
| 			},
 | |
| 			wantJSON: testRoutingJSON,
 | |
| 			wantErr:  false,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			defer replaceRandNumGenerator(0)()
 | |
| 			gotJSON, err := (&xdsResolver{}).serviceUpdateToJSON(tt.su)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("serviceUpdateToJSON returned error: %v", err)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			gotParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(gotJSON)
 | |
| 			wantParsed := internal.ParseServiceConfigForTesting.(func(string) *serviceconfig.ParseResult)(tt.wantJSON)
 | |
| 
 | |
| 			if !internal.EqualServiceConfigForTesting(gotParsed.Config, wantParsed.Config) {
 | |
| 				t.Errorf("serviceUpdateToJSON() = %v, want %v", gotJSON, tt.wantJSON)
 | |
| 				t.Error("gotParsed: ", cmp.Diff(nil, gotParsed))
 | |
| 				t.Error("wantParsed: ", cmp.Diff(nil, wantParsed))
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Two updates to the same resolver, test that action names are reused.
 | |
| func TestServiceUpdateToJSON_TwoConfig_UpdateActions(t *testing.T) {
 | |
| }
 | |
| 
 | |
| func newStringP(s string) *string {
 | |
| 	return &s
 | |
| }
 | |
| 
 | |
| func newBoolP(b bool) *bool {
 | |
| 	return &b
 | |
| }
 | |
| 
 | |
| func newUint32P(i uint32) *uint32 {
 | |
| 	return &i
 | |
| }
 |