/* * * 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 ( "context" "regexp" "testing" "time" xxhash "github.com/cespare/xxhash/v2" "github.com/google/go-cmp/cmp" "google.golang.org/grpc/internal/grpctest" "google.golang.org/grpc/internal/grpcutil" iresolver "google.golang.org/grpc/internal/resolver" _ "google.golang.org/grpc/internal/xds/balancer/cdsbalancer" // To parse LB config "google.golang.org/grpc/internal/xds/xdsclient/xdsresource" "google.golang.org/grpc/metadata" ) var defaultTestTimeout = 10 * time.Second type s struct { grpctest.Tester } func Test(t *testing.T) { grpctest.RunSubTests(t, s{}) } func (s) TestPruneActiveClusters(t *testing.T) { newClusterInfo := func(ref int32, unsubscribe func()) *clusterInfo { ci := &clusterInfo{unsubscribe: unsubscribe} ci.refCount.Store(ref) return ci } r := &xdsResolver{ activeClusters: map[string]*clusterInfo{ "zero": newClusterInfo(0, func() {}), "one": newClusterInfo(1, func() {}), "two": newClusterInfo(2, func() {}), "anotherzero": newClusterInfo(0, func() {}), }, activePlugins: map[string]*clusterInfo{ "zero": newClusterInfo(0, nil), "one": newClusterInfo(1, nil), "two": newClusterInfo(2, nil), "anotherzero": newClusterInfo(0, nil), }, } wantActiveClusters := map[string]int32{ "one": 1, "two": 2, } wantActivePlugins := map[string]int32{ "one": 1, "two": 2, } r.pruneActiveClustersAndPlugins() getRefCounts := func(m map[string]*clusterInfo) map[string]int32 { res := make(map[string]int32) for k, v := range m { res[k] = v.refCount.Load() } return res } if d := cmp.Diff(getRefCounts(r.activeClusters), wantActiveClusters); d != "" { t.Fatalf("r.activeClusters refCounts mismatch (-got +want):\n%s", d) } if d := cmp.Diff(getRefCounts(r.activePlugins), wantActivePlugins); d != "" { t.Fatalf("r.activePlugins refCounts mismatch (-got +want):\n%s", d) } } func (s) TestGenerateRequestHash(t *testing.T) { const channelID = 12378921 cs := &configSelector{channelID: channelID} ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() tests := []struct { name string hashPolicies []*xdsresource.HashPolicy requestHashWant uint64 rpcInfo iresolver.RPCInfo }{ // TestGenerateRequestHashHeaders tests generating request hashes for // hash policies that specify to hash headers. { name: "test-generate-request-hash-headers", hashPolicies: []*xdsresource.HashPolicy{{ HashPolicyType: xdsresource.HashPolicyTypeHeader, HeaderName: ":path", Regex: func() *regexp.Regexp { return regexp.MustCompile("/products") }(), // Will replace /products with /new-products, to test find and replace functionality. RegexSubstitution: "/new-products", }}, requestHashWant: xxhash.Sum64String("/new-products"), rpcInfo: iresolver.RPCInfo{ Context: metadata.NewOutgoingContext(ctx, metadata.Pairs(":path", "/products")), Method: "/some-method", }, }, // TestGenerateHashChannelID tests generating request hashes for hash // policies that specify to hash something that uniquely identifies the // ClientConn (the pointer). { name: "test-generate-request-hash-channel-id", hashPolicies: []*xdsresource.HashPolicy{{ HashPolicyType: xdsresource.HashPolicyTypeChannelID, }}, requestHashWant: channelID, rpcInfo: iresolver.RPCInfo{}, }, // TestGenerateRequestHashEmptyString tests generating request hashes // for hash policies that specify to hash headers and replace empty // strings in the headers. { name: "test-generate-request-hash-empty-string", hashPolicies: []*xdsresource.HashPolicy{{ HashPolicyType: xdsresource.HashPolicyTypeHeader, HeaderName: ":path", Regex: func() *regexp.Regexp { return regexp.MustCompile("") }(), RegexSubstitution: "e", }}, requestHashWant: xxhash.Sum64String("eaebece"), rpcInfo: iresolver.RPCInfo{ Context: metadata.NewOutgoingContext(ctx, metadata.Pairs(":path", "abc")), Method: "/some-method", }, }, // Tests that bin headers are skipped. { name: "skip-bin", hashPolicies: []*xdsresource.HashPolicy{{ HashPolicyType: xdsresource.HashPolicyTypeHeader, HeaderName: "something-bin", }, { HashPolicyType: xdsresource.HashPolicyTypeChannelID, }}, requestHashWant: channelID, rpcInfo: iresolver.RPCInfo{ Context: metadata.NewOutgoingContext(ctx, metadata.Pairs("something-bin", "xyz")), }, }, // Tests that extra metadata takes precedence over the user's metadata. { name: "extra-metadata", hashPolicies: []*xdsresource.HashPolicy{{ HashPolicyType: xdsresource.HashPolicyTypeHeader, HeaderName: "content-type", }}, requestHashWant: xxhash.Sum64String("grpc value"), rpcInfo: iresolver.RPCInfo{ Context: grpcutil.WithExtraMetadata( metadata.NewOutgoingContext(ctx, metadata.Pairs("content-type", "user value")), metadata.Pairs("content-type", "grpc value"), ), }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { requestHashGot := cs.generateHash(test.rpcInfo, test.hashPolicies) if requestHashGot != test.requestHashWant { t.Fatalf("requestHashGot = %v, requestHashWant = %v", requestHashGot, test.requestHashWant) } }) } }