grpc-go/internal/resolver/delegatingresolver/delegatingresolver_ext_test.go

551 lines
20 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 (
"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/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(req *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 {
t.Helper()
mr := manual.NewBuilderWithScheme("dns")
dnsResolverBuilder := resolver.Get("dns")
resolver.Register(mr)
t.Cleanup(func() { resolver.Register(dnsResolverBuilder) })
return mr
}
// 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
}
// 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"
)
hpfe := func(req *http.Request) (*url.URL, error) {
if req.URL.Host == targetTestAddr {
return &url.URL{
Scheme: "https",
Host: envProxyAddr,
}, nil
}
t.Errorf("Unexpected request host to proxy: %s want %s", req.URL.Host, targetTestAddr)
return nil, nil
}
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
defer func() {
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
}()
// 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 := 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)
}
proxyResolver.UpdateState(resolver.State{
Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr1}},
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):
}
targetResolver.UpdateState(resolver.State{
Addresses: []resolver.Address{
{Addr: resolvedTargetTestAddr1},
{Addr: resolvedTargetTestAddr2},
},
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 <-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)
}
}
// 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"
)
hpfe := func(req *http.Request) (*url.URL, error) {
if req.URL.Host == targetTestAddr {
return &url.URL{
Scheme: "https",
Host: envProxyAddr,
}, nil
}
t.Errorf("Unexpected request host to proxy: %s want %s", req.URL.Host, targetTestAddr)
return nil, nil
}
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
defer func() {
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
}()
targetResolver := manual.NewBuilderWithScheme("dns")
target := targetResolver.Scheme() + ":///" + targetTestAddr
// Set up a manual DNS resolver to control the proxy address resolution.
proxyResolver := 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)
}
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 <-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)
}
}
// 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"
)
hpfe := func(req *http.Request) (*url.URL, error) {
if req.URL.Host == targetTestAddr {
return &url.URL{
Scheme: "https",
Host: envProxyAddr,
}, nil
}
t.Errorf("Unexpected request host to proxy: %s want %s", req.URL.Host, targetTestAddr)
return nil, 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 := 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)
}
proxyResolver.UpdateState(resolver.State{
Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr1}},
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):
}
targetResolver.UpdateState(resolver.State{
Addresses: []resolver.Address{
{Addr: resolvedTargetTestAddr1},
{Addr: resolvedTargetTestAddr2},
},
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 <-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)
}
}
// 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"
)
hpfe := func(req *http.Request) (*url.URL, error) {
if req.URL.Host == targetTestAddr {
return &url.URL{
Scheme: "https",
Host: envProxyAddr,
}, nil
}
t.Errorf("Unexpected request host to proxy: %s want %s", req.URL.Host, targetTestAddr)
return nil, 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 := 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)
}
proxyResolver.UpdateState(resolver.State{
Endpoints: []resolver.Endpoint{
{Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr1}}},
{Addresses: []resolver.Address{{Addr: resolvedProxyTestAddr2}}},
},
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):
}
targetResolver.UpdateState(resolver.State{
Endpoints: []resolver.Endpoint{
{
Addresses: []resolver.Address{
{Addr: resolvedTargetTestAddr1},
{Addr: resolvedTargetTestAddr2}},
},
{
Addresses: []resolver.Address{
{Addr: resolvedTargetTestAddr3},
{Addr: resolvedTargetTestAddr4}},
},
},
ServiceConfig: &serviceconfig.ParseResult{},
})
wantState := resolver.State{
Endpoints: []resolver.Endpoint{
{
Addresses: []resolver.Address{
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr1),
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr2),
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr1),
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr2),
},
},
{
Addresses: []resolver.Address{
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr3),
proxyAddressWithTargetAttribute(resolvedProxyTestAddr1, resolvedTargetTestAddr4),
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr3),
proxyAddressWithTargetAttribute(resolvedProxyTestAddr2, resolvedTargetTestAddr4),
},
},
},
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)
}
}
// 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) TestDelegatingResolverForMutipleProxyAddress(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"
)
hpfe := func(req *http.Request) (*url.URL, error) {
if req.URL.Host == targetTestAddr {
return &url.URL{
Scheme: "https",
Host: envProxyAddr,
}, nil
}
t.Errorf("Unexpected request host to proxy: %s want %s", req.URL.Host, targetTestAddr)
return nil, 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 := 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)
}
proxyResolver.UpdateState(resolver.State{
Addresses: []resolver.Address{
{Addr: resolvedProxyTestAddr1},
{Addr: resolvedProxyTestAddr2},
},
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):
}
targetResolver.UpdateState(resolver.State{
Addresses: []resolver.Address{
{Addr: resolvedTargetTestAddr1},
{Addr: resolvedTargetTestAddr2},
},
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 <-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)
}
}