mirror of https://github.com/grpc/grpc-go.git
948 lines
36 KiB
Go
948 lines
36 KiB
Go
/*
|
|
*
|
|
* Copyright 2024 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 delegatingresolver_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"google.golang.org/grpc/internal/grpctest"
|
|
"google.golang.org/grpc/internal/proxyattributes"
|
|
"google.golang.org/grpc/internal/resolver/delegatingresolver"
|
|
"google.golang.org/grpc/internal/testutils"
|
|
"google.golang.org/grpc/internal/transport/networktype"
|
|
"google.golang.org/grpc/resolver"
|
|
"google.golang.org/grpc/resolver/manual"
|
|
"google.golang.org/grpc/serviceconfig"
|
|
|
|
_ "google.golang.org/grpc/resolver/dns" // To register dns resolver.
|
|
)
|
|
|
|
type s struct {
|
|
grpctest.Tester
|
|
}
|
|
|
|
func Test(t *testing.T) {
|
|
grpctest.RunSubTests(t, s{})
|
|
}
|
|
|
|
const (
|
|
defaultTestTimeout = 10 * time.Second
|
|
defaultTestShortTimeout = 10 * time.Millisecond
|
|
)
|
|
|
|
// createTestResolverClientConn initializes a [testutils.ResolverClientConn] and
|
|
// returns it along with channels for resolver state updates and errors.
|
|
func createTestResolverClientConn(t *testing.T) (*testutils.ResolverClientConn, chan resolver.State, chan error) {
|
|
t.Helper()
|
|
stateCh := make(chan resolver.State, 1)
|
|
errCh := make(chan error, 1)
|
|
|
|
tcc := &testutils.ResolverClientConn{
|
|
Logger: t,
|
|
UpdateStateF: func(s resolver.State) error { stateCh <- s; return nil },
|
|
ReportErrorF: func(err error) { errCh <- err },
|
|
}
|
|
return tcc, stateCh, errCh
|
|
}
|
|
|
|
// Tests the scenario where no proxy environment variables are set or proxying
|
|
// is disabled by the `NO_PROXY` environment variable. The test verifies that
|
|
// the delegating resolver creates only a target resolver and that the
|
|
// addresses returned by the delegating resolver exactly match those returned
|
|
// by the target resolver.
|
|
func (s) TestDelegatingResolverNoProxyEnvVarsSet(t *testing.T) {
|
|
hpfe := func(*http.Request) (*url.URL, error) { return nil, nil }
|
|
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
|
|
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
|
|
defer func() {
|
|
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
|
|
}()
|
|
|
|
const (
|
|
targetTestAddr = "test.com"
|
|
resolvedTargetTestAddr1 = "1.1.1.1:8080"
|
|
resolvedTargetTestAddr2 = "2.2.2.2:8080"
|
|
)
|
|
|
|
// Set up a manual resolver to control the address resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
|
|
// Create a delegating resolver with no proxy configuration
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
// Update the manual resolver with a test address.
|
|
targetResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedTargetTestAddr1},
|
|
{Addr: resolvedTargetTestAddr2},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
// Verify that the delegating resolver outputs the same addresses, as returned
|
|
// by the target resolver.
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedTargetTestAddr1},
|
|
{Addr: resolvedTargetTestAddr2},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-time.After(defaultTestTimeout):
|
|
t.Fatal("Timeout when waiting for a state update from the delegating resolver")
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|
|
|
|
// setupDNS registers a new manual resolver for the DNS scheme, effectively
|
|
// overwriting the previously registered DNS resolver. This allows the test to
|
|
// mock the DNS resolution for the proxy resolver. It also registers the
|
|
// original DNS resolver after the test is done.
|
|
func setupDNS(t *testing.T) (*manual.Resolver, chan struct{}) {
|
|
t.Helper()
|
|
mr := manual.NewBuilderWithScheme("dns")
|
|
|
|
dnsResolverBuilder := resolver.Get("dns")
|
|
resolver.Register(mr)
|
|
|
|
resolverBuilt := make(chan struct{})
|
|
mr.BuildCallback = func(resolver.Target, resolver.ClientConn, resolver.BuildOptions) {
|
|
close(resolverBuilt)
|
|
}
|
|
|
|
t.Cleanup(func() { resolver.Register(dnsResolverBuilder) })
|
|
return mr, resolverBuilt
|
|
}
|
|
|
|
func mustBuildResolver(ctx context.Context, t *testing.T, buildCh chan struct{}) {
|
|
t.Helper()
|
|
select {
|
|
case <-buildCh:
|
|
case <-ctx.Done():
|
|
t.Fatalf("Context timed out waiting for resolver to be built.")
|
|
}
|
|
}
|
|
|
|
// proxyAddressWithTargetAttribute creates a resolver.Address for the proxy,
|
|
// adding the target address as an attribute.
|
|
func proxyAddressWithTargetAttribute(proxyAddr string, targetAddr string) resolver.Address {
|
|
addr := resolver.Address{Addr: proxyAddr}
|
|
addr = proxyattributes.Set(addr, proxyattributes.Options{ConnectAddr: targetAddr})
|
|
return addr
|
|
}
|
|
|
|
func overrideTestHTTPSProxy(t *testing.T, proxyAddr string) {
|
|
t.Helper()
|
|
hpfe := func(*http.Request) (*url.URL, error) {
|
|
return &url.URL{
|
|
Scheme: "https",
|
|
Host: proxyAddr,
|
|
}, nil
|
|
}
|
|
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
|
|
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
|
|
t.Cleanup(func() { delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe })
|
|
}
|
|
|
|
// Tests the scenario where proxy is configured and the target URI contains the
|
|
// "dns" scheme and target resolution is enabled. The test verifies that the
|
|
// addresses returned by the delegating resolver combines the addresses
|
|
// returned by the proxy resolver and the target resolver.
|
|
func (s) TestDelegatingResolverwithDNSAndProxyWithTargetResolution(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.com"
|
|
resolvedTargetTestAddr1 = "1.1.1.1:8080"
|
|
resolvedTargetTestAddr2 = "2.2.2.2:8080"
|
|
envProxyAddr = "proxytest.com"
|
|
resolvedProxyTestAddr1 = "11.11.11.11:7687"
|
|
)
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("dns")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, true); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
targetResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedTargetTestAddr1},
|
|
{Addr: resolvedTargetTestAddr2},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
select {
|
|
case <-stateCh:
|
|
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr1}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
// Verify that the delegating resolver outputs the expected address.
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2),
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-ctx.Done():
|
|
t.Fatal("Context timeed out when waiting for a state update from the delegating resolver")
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|
|
|
|
// Tests the scenario where a proxy is configured, the target URI contains the
|
|
// "dns" scheme, and target resolution is disabled(default behavior). The test
|
|
// verifies that the addresses returned by the delegating resolver include the
|
|
// proxy resolver's addresses, with the unresolved target URI as an attribute
|
|
// of the proxy address.
|
|
func (s) TestDelegatingResolverwithDNSAndProxyWithNoTargetResolution(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.com"
|
|
envProxyAddr = "proxytest.com"
|
|
resolvedProxyTestAddr1 = "11.11.11.11:7687"
|
|
)
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
targetResolver := manual.NewBuilderWithScheme("dns")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedProxyTestAddr1},
|
|
},
|
|
})
|
|
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, targetTestAddr)},
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, targetTestAddr)}}},
|
|
}
|
|
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-ctx.Done():
|
|
t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|
|
|
|
// Tests the scenario where a proxy is configured, and the target URI scheme is
|
|
// not "dns". The test verifies that the addresses returned by the delegating
|
|
// resolver include the resolved proxy address and the custom resolved target
|
|
// address as attributes of the proxy address.
|
|
func (s) TestDelegatingResolverwithCustomResolverAndProxy(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.com"
|
|
resolvedTargetTestAddr1 = "1.1.1.1:8080"
|
|
resolvedTargetTestAddr2 = "2.2.2.2:8080"
|
|
envProxyAddr = "proxytest.com"
|
|
resolvedProxyTestAddr1 = "11.11.11.11:7687"
|
|
)
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
targetResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedTargetTestAddr1},
|
|
{Addr: resolvedTargetTestAddr2},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
select {
|
|
case <-stateCh:
|
|
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr1}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2),
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-ctx.Done():
|
|
t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|
|
|
|
// Tests the scenario where a proxy is configured, the target URI scheme is not
|
|
// "dns," and both the proxy and target resolvers return endpoints. The test
|
|
// verifies that the delegating resolver combines resolved proxy and target
|
|
// addresses correctly, returning endpoints with the proxy address populated
|
|
// and the target address included as an attribute of the proxy address for
|
|
// each combination of proxy and target endpoints.
|
|
func (s) TestDelegatingResolverForEndpointsWithProxy(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.com"
|
|
resolvedTargetTestAddr1 = "1.1.1.1:8080"
|
|
resolvedTargetTestAddr2 = "2.2.2.2:8080"
|
|
resolvedTargetTestAddr3 = "3.3.3.3:8080"
|
|
resolvedTargetTestAddr4 = "4.4.4.4:8080"
|
|
envProxyAddr = "proxytest.com"
|
|
resolvedProxyTestAddr1 = "11.11.11.11:7687"
|
|
resolvedProxyTestAddr2 = "22.22.22.22:7687"
|
|
)
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
targetResolver.UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{
|
|
{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedTargetTestAddr1},
|
|
{Addr: resolvedTargetTestAddr2}},
|
|
},
|
|
{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedTargetTestAddr3},
|
|
{Addr: resolvedTargetTestAddr4}},
|
|
},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
select {
|
|
case <-stateCh:
|
|
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{
|
|
{Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr1}}},
|
|
{Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr2}}},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
wantState := resolver.State{
|
|
Endpoints: []resolver.Endpoint{
|
|
{
|
|
Addresses: []resolver.Address{
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr1),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr2),
|
|
},
|
|
},
|
|
{
|
|
Addresses: []resolver.Address{
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr3),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr3),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr4),
|
|
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr4),
|
|
},
|
|
},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-ctx.Done():
|
|
t.Fatal("Contex timed out when waiting for a state update from the delegating resolver")
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|
|
|
|
// Tests the scenario where a proxy is configured, the target URI scheme is not
|
|
// "dns," and both the proxy and target resolvers return multiple addresses.
|
|
// The test verifies that the delegating resolver combines unresolved proxy
|
|
// host and target addresses correctly, returning addresses with the proxy host
|
|
// populated and the target address included as an attribute.
|
|
func (s) TestDelegatingResolverForMultipleProxyAddress(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.com"
|
|
resolvedTargetTestAddr1 = "1.1.1.1:8080"
|
|
resolvedTargetTestAddr2 = "2.2.2.2:8080"
|
|
envProxyAddr = "proxytest.com"
|
|
resolvedProxyTestAddr1 = "11.11.11.11:7687"
|
|
resolvedProxyTestAddr2 = "22.22.22.22:7687"
|
|
)
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
targetResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedTargetTestAddr1},
|
|
{Addr: resolvedTargetTestAddr2},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
select {
|
|
case <-stateCh:
|
|
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{
|
|
{Addr: resolvedProxyTestAddr1},
|
|
{Addr: resolvedProxyTestAddr2},
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{
|
|
proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr1),
|
|
proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2),
|
|
},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-ctx.Done():
|
|
t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|
|
|
|
// Tests that delegatingresolver doesn't panic when the channel closes the
|
|
// resolver while it's handling an update from it's child. The test closes the
|
|
// delegating resolver, verifies the target resolver is closed and blocks the
|
|
// proxy resolver from being closed. The test sends an update from the proxy
|
|
// resolver and verifies that the target resolver's ResolveNow method is not
|
|
// called after the channels returns an error.
|
|
func (s) TestDelegatingResolverUpdateStateDuringClose(t *testing.T) {
|
|
const envProxyAddr = "proxytest.com"
|
|
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
targetResolverCalled := make(chan struct{})
|
|
targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
|
|
close(targetResolverCalled)
|
|
}
|
|
targetResolverCloseCalled := make(chan struct{})
|
|
targetResolver.CloseCallback = func() {
|
|
close(targetResolverCloseCalled)
|
|
t.Log("Target resolver is closed.")
|
|
}
|
|
|
|
target := targetResolver.Scheme() + ":///" + "ignored"
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
unblockProxyResolverClose := make(chan struct{}, 1)
|
|
proxyResolver.CloseCallback = func() {
|
|
<-unblockProxyResolverClose
|
|
t.Log("Proxy resolver is closed.")
|
|
}
|
|
|
|
tcc, _, _ := createTestResolverClientConn(t)
|
|
tcc.UpdateStateF = func(resolver.State) error {
|
|
return errors.New("test error")
|
|
}
|
|
dr, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
targetResolver.UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
|
|
})
|
|
|
|
// Wait for the proxy resolver to be built before calling Close.
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
// Closing the delegating resolver will block until the test writes to the
|
|
// unblockProxyResolverClose channel.
|
|
go dr.Close()
|
|
select {
|
|
case <-targetResolverCloseCalled:
|
|
case <-ctx.Done():
|
|
t.Fatalf("Context timed out waiting for target resolver's Close method to be called.")
|
|
}
|
|
|
|
// Updating the channel will result in an error being returned. Since the
|
|
// target resolver's Close method is already called, the delegating resolver
|
|
// must not call "ResolveNow" on it.
|
|
proxyUpdateCh := make(chan struct{})
|
|
go func() {
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
|
|
})
|
|
close(proxyUpdateCh)
|
|
}()
|
|
unblockProxyResolverClose <- struct{}{}
|
|
|
|
select {
|
|
case <-targetResolverCalled:
|
|
t.Fatalf("targetResolver.ResolveNow() called unexpectedly.")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
// Wait for the proxy update to complete before returning from the test and
|
|
// before the deferred reassignment of
|
|
// delegatingresolver.HTTPSProxyFromEnvironment. This ensures that we read
|
|
// from the function before it is reassigned, preventing a race condition.
|
|
select {
|
|
case <-proxyUpdateCh:
|
|
case <-ctx.Done():
|
|
t.Fatalf("Context timed out waiting for proxyResolver.UpdateState() to be called.")
|
|
}
|
|
}
|
|
|
|
// Tests that calling cc.UpdateState in a blocking manner from a child resolver
|
|
// while handling a ResolveNow call doesn't result in a deadlock. The test uses
|
|
// a fake ClientConn that returns an error when calling cc.UpdateState. The test
|
|
// makes the proxy resolver update the resolver state. The test verifies that
|
|
// the delegating resolver calls ResolveNow on the target resolver when the
|
|
// ClientConn returns an error.
|
|
func (s) TestDelegatingResolverUpdateStateFromResolveNow(t *testing.T) {
|
|
const envProxyAddr = "proxytest.com"
|
|
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
targetResolverCalled := make(chan struct{})
|
|
targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
|
|
// Updating the resolver state should not deadlock.
|
|
targetResolver.CC().UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
|
|
})
|
|
close(targetResolverCalled)
|
|
}
|
|
|
|
target := targetResolver.Scheme() + ":///" + "ignored"
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, _, _ := createTestResolverClientConn(t)
|
|
tcc.UpdateStateF = func(resolver.State) error {
|
|
return errors.New("test error")
|
|
}
|
|
_, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
targetResolver.UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
|
|
// Updating the channel will result in an error being returned. The
|
|
// delegating resolver should call call "ResolveNow" on the target resolver.
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
|
|
})
|
|
|
|
select {
|
|
case <-targetResolverCalled:
|
|
case <-ctx.Done():
|
|
t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
|
|
}
|
|
}
|
|
|
|
// Tests that calling cc.UpdateState in a blocking manner from child resolvers
|
|
// doesn't result in deadlocks.
|
|
func (s) TestDelegatingResolverResolveNow(t *testing.T) {
|
|
const envProxyAddr = "proxytest.com"
|
|
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
targetResolverCalled := make(chan struct{}, 1)
|
|
targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
|
|
// Updating the resolver state should not deadlock.
|
|
targetResolver.CC().UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
|
|
})
|
|
targetResolverCalled <- struct{}{}
|
|
}
|
|
|
|
target := targetResolver.Scheme() + ":///" + "ignored"
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
proxyResolverCalled := make(chan struct{})
|
|
proxyResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
|
|
// Updating the resolver state should not deadlock.
|
|
proxyResolver.CC().UpdateState(resolver.State{
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
|
|
})
|
|
close(proxyResolverCalled)
|
|
}
|
|
|
|
tcc, _, _ := createTestResolverClientConn(t)
|
|
dr, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
// ResolveNow of manual proxy resolver will not be called. Proxy resolver is
|
|
// only built when we get the first update from target resolver. Therefore
|
|
// in the first ResolveNow, proxy resolver will be a no-op resolver and only
|
|
// target resolver's ResolveNow will be called.
|
|
dr.ResolveNow(resolver.ResolveNowOptions{})
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
select {
|
|
case <-targetResolverCalled:
|
|
case <-ctx.Done():
|
|
t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
|
|
}
|
|
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
|
|
dr.ResolveNow(resolver.ResolveNowOptions{})
|
|
|
|
select {
|
|
case <-targetResolverCalled:
|
|
case <-ctx.Done():
|
|
t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
|
|
}
|
|
select {
|
|
case <-proxyResolverCalled:
|
|
case <-ctx.Done():
|
|
t.Fatalf("context timed out waiting for proxyResolver.ResolveNow() to be called.")
|
|
}
|
|
}
|
|
|
|
// Tests the scenario where a proxy is configured, and the resolver returns a
|
|
// network type other than tcp for all addresses. The test verifies that the
|
|
// delegating resolver avoids the proxy build and directly sends the update
|
|
// from target resolver to clientconn.
|
|
func (s) TestDelegatingResolverForNonTCPTarget(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.target"
|
|
resolvedTargetTestAddr1 = "1.1.1.1:8080"
|
|
envProxyAddr = "proxytest.com"
|
|
)
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
_, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
// Set network to anything other than tcp.
|
|
nonTCPAddr := networktype.Set(resolver.Address{Addr: resolvedTargetTestAddr1}, "unix")
|
|
targetResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{nonTCPAddr},
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr}}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-time.After(defaultTestTimeout):
|
|
t.Fatal("Timeout when waiting for a state update from the delegating resolver")
|
|
}
|
|
|
|
// Verify that the delegating resolver doesn't call proxy resolver's
|
|
// UpdateState since we have no tcp address
|
|
select {
|
|
case <-proxyResolverBuilt:
|
|
t.Fatal("Unexpected call to proxy resolver update state")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{nonTCPAddr},
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr}}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
|
|
// Verify that the state clientconn receives is same as updated by target
|
|
// resolver, since we want to avoid proxy for any network type apart from
|
|
// tcp.
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
// Tests the scenario where a proxy is configured, and the resolver returns tcp
|
|
// and non-tcp addresses. The test verifies that the delegating resolver doesn't
|
|
// add proxyatrribute to adresses with network type other than tcp, but adds
|
|
// the proxyattribute to addresses with network type tcp.
|
|
func (s) TestDelegatingResolverForMixNetworkType(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.target"
|
|
resolvedTargetTestAddr1 = "1.1.1.1:8080"
|
|
resolvedTargetTestAddr2 = "2.2.2.2:8080"
|
|
envProxyAddr = "proxytest.com"
|
|
)
|
|
overrideTestHTTPSProxy(t, envProxyAddr)
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
// Set network to anything other than tcp.
|
|
nonTCPAddr := networktype.Set(resolver.Address{Addr: resolvedTargetTestAddr1}, "unix")
|
|
targetResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{nonTCPAddr, {Addr: resolvedTargetTestAddr2}},
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr, {Addr: resolvedTargetTestAddr2}}}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
select {
|
|
case <-stateCh:
|
|
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{{Addr: envProxyAddr}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-ctx.Done():
|
|
t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
|
|
}
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{nonTCPAddr, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2)},
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{nonTCPAddr, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr2)}}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|
|
|
|
// Tests the scenario where a proxy is configured but some addresses are
|
|
// excluded (by using the NO_PROXY environment variable). The test verifies that
|
|
// the delegating resolver doesn't add proxyatrribute to adresses excluded, but
|
|
// adds the proxyattribute to all other addresses.
|
|
func (s) TestDelegatingResolverWithNoProxyEnvUsed(t *testing.T) {
|
|
const (
|
|
targetTestAddr = "test.target"
|
|
noproxyresolvedTargetAddr = "1.1.1.1:8080"
|
|
resolvedTargetTestAddr = "2.2.2.2:8080"
|
|
envProxyAddr = "proxytest.com"
|
|
)
|
|
hpfe := func(req *http.Request) (*url.URL, error) {
|
|
// return nil to mimick the scenario where the address is excluded using
|
|
// `NO_PROXY` env variable.
|
|
if req.URL.Host == noproxyresolvedTargetAddr {
|
|
return nil, nil
|
|
}
|
|
return &url.URL{
|
|
Scheme: "https",
|
|
Host: envProxyAddr,
|
|
}, nil
|
|
}
|
|
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
|
|
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
|
|
defer func() {
|
|
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
|
|
}()
|
|
|
|
// Manual resolver to control the target resolution.
|
|
targetResolver := manual.NewBuilderWithScheme("test")
|
|
target := targetResolver.Scheme() + ":///" + targetTestAddr
|
|
// Set up a manual DNS resolver to control the proxy address resolution.
|
|
proxyResolver, proxyResolverBuilt := setupDNS(t)
|
|
|
|
tcc, stateCh, _ := createTestResolverClientConn(t)
|
|
if _, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false); err != nil {
|
|
t.Fatalf("Failed to create delegating resolver: %v", err)
|
|
}
|
|
|
|
targetResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{{Addr: noproxyresolvedTargetAddr}, {Addr: resolvedTargetTestAddr}},
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: noproxyresolvedTargetAddr}, {Addr: resolvedTargetTestAddr}}}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
select {
|
|
case <-stateCh:
|
|
t.Fatalf("Delegating resolver invoked UpdateState before both the proxy and target resolvers had updated their states.")
|
|
case <-time.After(defaultTestShortTimeout):
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
|
defer cancel()
|
|
|
|
// Wait for the proxy resolver to be built before calling UpdateState.
|
|
mustBuildResolver(ctx, t, proxyResolverBuilt)
|
|
proxyResolver.UpdateState(resolver.State{
|
|
Addresses: []resolver.Address{{Addr: envProxyAddr}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
})
|
|
|
|
var gotState resolver.State
|
|
select {
|
|
case gotState = <-stateCh:
|
|
case <-ctx.Done():
|
|
t.Fatal("Context timed out when waiting for a state update from the delegating resolver")
|
|
}
|
|
wantState := resolver.State{
|
|
Addresses: []resolver.Address{{Addr: noproxyresolvedTargetAddr}, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr)},
|
|
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: noproxyresolvedTargetAddr}, proxyAddressWithTargetAttribute(envProxyAddr, resolvedTargetTestAddr)}}},
|
|
ServiceConfig: &serviceconfig.ParseResult{},
|
|
}
|
|
|
|
if diff := cmp.Diff(gotState, wantState); diff != "" {
|
|
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
|
|
}
|
|
}
|