| /* |
| * |
| * Copyright 2023 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 grpc |
| |
| import ( |
| "context" |
| "fmt" |
| "net" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "google.golang.org/grpc/attributes" |
| "google.golang.org/grpc/balancer" |
| "google.golang.org/grpc/credentials/insecure" |
| "google.golang.org/grpc/internal/balancer/stub" |
| "google.golang.org/grpc/resolver" |
| "google.golang.org/grpc/resolver/manual" |
| ) |
| |
| type wrapResolverBuilder struct { |
| resolver.Builder |
| scheme string |
| } |
| |
| func (w *wrapResolverBuilder) Scheme() string { |
| return w.scheme |
| } |
| |
| func init() { |
| resolver.Register(&wrapResolverBuilder{Builder: resolver.Get("passthrough"), scheme: "casetest"}) |
| resolver.Register(&wrapResolverBuilder{Builder: resolver.Get("dns"), scheme: "caseTest"}) |
| } |
| |
| func (s) TestResolverCaseSensitivity(t *testing.T) { |
| // This should find the "casetest" resolver instead of the "caseTest" |
| // resolver, even though the latter was registered later. "casetest" is |
| // "passthrough" and "caseTest" is "dns". With "passthrough" the dialer |
| // should see the target's address directly, but "dns" would be converted |
| // into a loopback IP (v4 or v6) address. |
| target := "caseTest:///localhost:1234" |
| addrCh := make(chan string, 1) |
| customDialer := func(ctx context.Context, addr string) (net.Conn, error) { |
| select { |
| case addrCh <- addr: |
| default: |
| } |
| return nil, fmt.Errorf("not dialing with custom dialer") |
| } |
| |
| cc, err := Dial(target, WithContextDialer(customDialer), WithTransportCredentials(insecure.NewCredentials())) |
| if err != nil { |
| t.Fatalf("Unexpected Dial(%q) error: %v", target, err) |
| } |
| cc.Connect() |
| if got, want := <-addrCh, "localhost:1234"; got != want { |
| cc.Close() |
| t.Fatalf("Dialer got address %q; wanted %q", got, want) |
| } |
| cc.Close() |
| |
| // Clear addrCh for future use. |
| select { |
| case <-addrCh: |
| default: |
| } |
| |
| res := &wrapResolverBuilder{Builder: resolver.Get("dns"), scheme: "caseTest2"} |
| // This should not find the injected resolver due to the case not matching. |
| // This results in "passthrough" being used with the address as the whole |
| // target. |
| target = "caseTest2:///localhost:1234" |
| cc, err = Dial(target, WithContextDialer(customDialer), WithResolvers(res), WithTransportCredentials(insecure.NewCredentials())) |
| if err != nil { |
| t.Fatalf("Unexpected Dial(%q) error: %v", target, err) |
| } |
| cc.Connect() |
| if got, want := <-addrCh, target; got != want { |
| cc.Close() |
| t.Fatalf("Dialer got address %q; wanted %q", got, want) |
| } |
| cc.Close() |
| } |
| |
| // TestResolverAddressesToEndpoints ensures one Endpoint is created for each |
| // entry in resolver.State.Addresses automatically. |
| func (s) TestResolverAddressesToEndpoints(t *testing.T) { |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| |
| const scheme = "testresolveraddressestoendpoints" |
| r := manual.NewBuilderWithScheme(scheme) |
| |
| stateCh := make(chan balancer.ClientConnState, 1) |
| bf := stub.BalancerFuncs{ |
| UpdateClientConnState: func(_ *stub.BalancerData, ccs balancer.ClientConnState) error { |
| stateCh <- ccs |
| return nil |
| }, |
| } |
| balancerName := "stub-balancer-" + scheme |
| stub.Register(balancerName, bf) |
| |
| a1 := attributes.New("x", "y") |
| a2 := attributes.New("a", "b") |
| r.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "addr1", BalancerAttributes: a1}, {Addr: "addr2", BalancerAttributes: a2}}}) |
| |
| cc, err := Dial(r.Scheme()+":///", |
| WithTransportCredentials(insecure.NewCredentials()), |
| WithResolvers(r), |
| WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, balancerName))) |
| if err != nil { |
| t.Fatalf("Unexpected error dialing: %v", err) |
| } |
| defer cc.Close() |
| |
| select { |
| case got := <-stateCh: |
| want := []resolver.Endpoint{ |
| {Addresses: []resolver.Address{{Addr: "addr1"}}, Attributes: a1}, |
| {Addresses: []resolver.Address{{Addr: "addr2"}}, Attributes: a2}, |
| } |
| if diff := cmp.Diff(got.ResolverState.Endpoints, want); diff != "" { |
| t.Errorf("Did not receive expected endpoints. Diff (-got +want):\n%v", diff) |
| } |
| case <-ctx.Done(): |
| t.Fatalf("timed out waiting for endpoints") |
| } |
| } |
| |
| // Test ensures that there is no panic if the attributes within |
| // resolver.State.Addresses contains a typed-nil value. |
| func (s) TestResolverAddressesWithTypedNilAttribute(t *testing.T) { |
| r := manual.NewBuilderWithScheme(t.Name()) |
| resolver.Register(r) |
| |
| addrAttr := attributes.New("typed_nil", (*stringerVal)(nil)) |
| r.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "addr1", Attributes: addrAttr}}}) |
| |
| cc, err := Dial(r.Scheme()+":///", WithTransportCredentials(insecure.NewCredentials()), WithResolvers(r)) |
| if err != nil { |
| t.Fatalf("Unexpected error dialing: %v", err) |
| } |
| defer cc.Close() |
| } |
| |
| type stringerVal struct{ s string } |
| |
| func (s stringerVal) String() string { return s.s } |