google.golang.org/grpc@v1.72.2/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(_ 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 := NewClient(r.Scheme()+":///",
   124  		WithTransportCredentials(insecure.NewCredentials()),
   125  		WithResolvers(r),
   126  		WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, balancerName)))
   127  	if err != nil {
   128  		t.Fatalf("grpc.NewClient() failed: %v", err)
   129  	}
   130  	cc.Connect()
   131  	defer cc.Close()
   132  
   133  	select {
   134  	case got := <-stateCh:
   135  		want := []resolver.Endpoint{
   136  			{Addresses: []resolver.Address{{Addr: "addr1"}}, Attributes: a1},
   137  			{Addresses: []resolver.Address{{Addr: "addr2"}}, Attributes: a2},
   138  		}
   139  		if diff := cmp.Diff(got.ResolverState.Endpoints, want); diff != "" {
   140  			t.Errorf("Did not receive expected endpoints.  Diff (-got +want):\n%v", diff)
   141  		}
   142  	case <-ctx.Done():
   143  		t.Fatalf("timed out waiting for endpoints")
   144  	}
   145  }
   146  
   147  // Test ensures one Endpoint is created for each entry in
   148  // resolver.State.Addresses automatically. The test calls the deprecated
   149  // NewAddresses API to send a list of addresses to the channel.
   150  func (s) TestResolverAddressesToEndpointsUsingNewAddresses(t *testing.T) {
   151  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   152  	defer cancel()
   153  
   154  	const scheme = "testresolveraddressestoendpoints"
   155  	r := manual.NewBuilderWithScheme(scheme)
   156  
   157  	stateCh := make(chan balancer.ClientConnState, 1)
   158  	bf := stub.BalancerFuncs{
   159  		UpdateClientConnState: func(_ *stub.BalancerData, ccs balancer.ClientConnState) error {
   160  			stateCh <- ccs
   161  			return nil
   162  		},
   163  	}
   164  	balancerName := "stub-balancer-" + scheme
   165  	stub.Register(balancerName, bf)
   166  
   167  	a1 := attributes.New("x", "y")
   168  	a2 := attributes.New("a", "b")
   169  	addrs := []resolver.Address{
   170  		{Addr: "addr1", BalancerAttributes: a1},
   171  		{Addr: "addr2", BalancerAttributes: a2},
   172  	}
   173  
   174  	cc, err := NewClient(r.Scheme()+":///",
   175  		WithTransportCredentials(insecure.NewCredentials()),
   176  		WithResolvers(r),
   177  		WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, balancerName)))
   178  	if err != nil {
   179  		t.Fatalf("grpc.NewClient() failed: %v", err)
   180  	}
   181  	cc.Connect()
   182  	defer cc.Close()
   183  	r.CC().NewAddress(addrs)
   184  
   185  	select {
   186  	case got := <-stateCh:
   187  		want := []resolver.Endpoint{
   188  			{Addresses: []resolver.Address{{Addr: "addr1"}}, Attributes: a1},
   189  			{Addresses: []resolver.Address{{Addr: "addr2"}}, Attributes: a2},
   190  		}
   191  		if diff := cmp.Diff(got.ResolverState.Endpoints, want); diff != "" {
   192  			t.Errorf("Did not receive expected endpoints.  Diff (-got +want):\n%v", diff)
   193  		}
   194  	case <-ctx.Done():
   195  		t.Fatalf("timed out waiting for endpoints")
   196  	}
   197  }
   198  
   199  // Test ensures that there is no panic if the attributes within
   200  // resolver.State.Addresses contains a typed-nil value.
   201  func (s) TestResolverAddressesWithTypedNilAttribute(t *testing.T) {
   202  	r := manual.NewBuilderWithScheme(t.Name())
   203  	resolver.Register(r)
   204  
   205  	addrAttr := attributes.New("typed_nil", (*stringerVal)(nil))
   206  	r.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "addr1", Attributes: addrAttr}}})
   207  
   208  	cc, err := Dial(r.Scheme()+":///", WithTransportCredentials(insecure.NewCredentials()), WithResolvers(r))
   209  	if err != nil {
   210  		t.Fatalf("Unexpected error dialing: %v", err)
   211  	}
   212  	defer cc.Close()
   213  }
   214  
   215  type stringerVal struct{ s string }
   216  
   217  func (s stringerVal) String() string { return s.s }