google.golang.org/grpc@v1.62.1/resolver_test.go (about) 1 /* 2 * 3 * Copyright 2023 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package grpc 20 21 import ( 22 "context" 23 "fmt" 24 "net" 25 "testing" 26 27 "github.com/google/go-cmp/cmp" 28 "google.golang.org/grpc/attributes" 29 "google.golang.org/grpc/balancer" 30 "google.golang.org/grpc/credentials/insecure" 31 "google.golang.org/grpc/internal/balancer/stub" 32 "google.golang.org/grpc/resolver" 33 "google.golang.org/grpc/resolver/manual" 34 ) 35 36 type wrapResolverBuilder struct { 37 resolver.Builder 38 scheme string 39 } 40 41 func (w *wrapResolverBuilder) Scheme() string { 42 return w.scheme 43 } 44 45 func init() { 46 resolver.Register(&wrapResolverBuilder{Builder: resolver.Get("passthrough"), scheme: "casetest"}) 47 resolver.Register(&wrapResolverBuilder{Builder: resolver.Get("dns"), scheme: "caseTest"}) 48 } 49 50 func (s) TestResolverCaseSensitivity(t *testing.T) { 51 // This should find the "casetest" resolver instead of the "caseTest" 52 // resolver, even though the latter was registered later. "casetest" is 53 // "passthrough" and "caseTest" is "dns". With "passthrough" the dialer 54 // should see the target's address directly, but "dns" would be converted 55 // into a loopback IP (v4 or v6) address. 56 target := "caseTest:///localhost:1234" 57 addrCh := make(chan string, 1) 58 customDialer := func(ctx context.Context, addr string) (net.Conn, error) { 59 select { 60 case addrCh <- addr: 61 default: 62 } 63 return nil, fmt.Errorf("not dialing with custom dialer") 64 } 65 66 cc, err := Dial(target, WithContextDialer(customDialer), WithTransportCredentials(insecure.NewCredentials())) 67 if err != nil { 68 t.Fatalf("Unexpected Dial(%q) error: %v", target, err) 69 } 70 cc.Connect() 71 if got, want := <-addrCh, "localhost:1234"; got != want { 72 cc.Close() 73 t.Fatalf("Dialer got address %q; wanted %q", got, want) 74 } 75 cc.Close() 76 77 // Clear addrCh for future use. 78 select { 79 case <-addrCh: 80 default: 81 } 82 83 res := &wrapResolverBuilder{Builder: resolver.Get("dns"), scheme: "caseTest2"} 84 // This should not find the injected resolver due to the case not matching. 85 // This results in "passthrough" being used with the address as the whole 86 // target. 87 target = "caseTest2:///localhost:1234" 88 cc, err = Dial(target, WithContextDialer(customDialer), WithResolvers(res), WithTransportCredentials(insecure.NewCredentials())) 89 if err != nil { 90 t.Fatalf("Unexpected Dial(%q) error: %v", target, err) 91 } 92 cc.Connect() 93 if got, want := <-addrCh, target; got != want { 94 cc.Close() 95 t.Fatalf("Dialer got address %q; wanted %q", got, want) 96 } 97 cc.Close() 98 } 99 100 // TestResolverAddressesToEndpoints ensures one Endpoint is created for each 101 // entry in resolver.State.Addresses automatically. 102 func (s) TestResolverAddressesToEndpoints(t *testing.T) { 103 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 104 defer cancel() 105 106 const scheme = "testresolveraddressestoendpoints" 107 r := manual.NewBuilderWithScheme(scheme) 108 109 stateCh := make(chan balancer.ClientConnState, 1) 110 bf := stub.BalancerFuncs{ 111 UpdateClientConnState: func(_ *stub.BalancerData, ccs balancer.ClientConnState) error { 112 stateCh <- ccs 113 return nil 114 }, 115 } 116 balancerName := "stub-balancer-" + scheme 117 stub.Register(balancerName, bf) 118 119 a1 := attributes.New("x", "y") 120 a2 := attributes.New("a", "b") 121 r.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "addr1", BalancerAttributes: a1}, {Addr: "addr2", BalancerAttributes: a2}}}) 122 123 cc, err := Dial(r.Scheme()+":///", 124 WithTransportCredentials(insecure.NewCredentials()), 125 WithResolvers(r), 126 WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, balancerName))) 127 if err != nil { 128 t.Fatalf("Unexpected error dialing: %v", err) 129 } 130 defer cc.Close() 131 132 select { 133 case got := <-stateCh: 134 want := []resolver.Endpoint{ 135 {Addresses: []resolver.Address{{Addr: "addr1"}}, Attributes: a1}, 136 {Addresses: []resolver.Address{{Addr: "addr2"}}, Attributes: a2}, 137 } 138 if diff := cmp.Diff(got.ResolverState.Endpoints, want); diff != "" { 139 t.Errorf("Did not receive expected endpoints. Diff (-got +want):\n%v", diff) 140 } 141 case <-ctx.Done(): 142 t.Fatalf("timed out waiting for endpoints") 143 } 144 } 145 146 // Test ensures that there is no panic if the attributes within 147 // resolver.State.Addresses contains a typed-nil value. 148 func (s) TestResolverAddressesWithTypedNilAttribute(t *testing.T) { 149 r := manual.NewBuilderWithScheme(t.Name()) 150 resolver.Register(r) 151 152 addrAttr := attributes.New("typed_nil", (*stringerVal)(nil)) 153 r.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "addr1", Attributes: addrAttr}}}) 154 155 cc, err := Dial(r.Scheme()+":///", WithTransportCredentials(insecure.NewCredentials()), WithResolvers(r)) 156 if err != nil { 157 t.Fatalf("Unexpected error dialing: %v", err) 158 } 159 defer cc.Close() 160 } 161 162 type stringerVal struct{ s string } 163 164 func (s stringerVal) String() string { return s.s }