mirror of https://github.com/grpc/grpc-go.git
internal/resolver: introduce a new resolver to handle target URI and proxy address resolution (#7857)
This commit is contained in:
parent
10c7e13311
commit
063d352de0
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
*
|
||||
* 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 proxyattributes contains functions for getting and setting proxy
|
||||
// attributes like the CONNECT address and user info.
|
||||
package proxyattributes
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
|
||||
type keyType string
|
||||
|
||||
const proxyOptionsKey = keyType("grpc.resolver.delegatingresolver.proxyOptions")
|
||||
|
||||
// Options holds the proxy connection details needed during the CONNECT
|
||||
// handshake.
|
||||
type Options struct {
|
||||
User url.Userinfo
|
||||
ConnectAddr string
|
||||
}
|
||||
|
||||
// Set returns a copy of addr with opts set in its attributes.
|
||||
func Set(addr resolver.Address, opts Options) resolver.Address {
|
||||
addr.Attributes = addr.Attributes.WithValue(proxyOptionsKey, opts)
|
||||
return addr
|
||||
}
|
||||
|
||||
// Get returns the Options for the proxy [resolver.Address] and a boolean
|
||||
// value representing if the attribute is present or not.
|
||||
func Get(addr resolver.Address) (Options, bool) {
|
||||
if a := addr.Attributes.Value(proxyOptionsKey); a != nil {
|
||||
return a.(Options), true
|
||||
}
|
||||
return Options{}, false
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
*
|
||||
* 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 proxyattributes
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc/attributes"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
// Tests that Get returns a valid proxy attribute.
|
||||
func (s) TestGet(t *testing.T) {
|
||||
user := url.UserPassword("username", "password")
|
||||
tests := []struct {
|
||||
name string
|
||||
addr resolver.Address
|
||||
wantConnectAddr string
|
||||
wantUser url.Userinfo
|
||||
wantAttrPresent bool
|
||||
}{
|
||||
{
|
||||
name: "connect_address_in_attribute",
|
||||
addr: resolver.Address{
|
||||
Addr: "test-address",
|
||||
Attributes: attributes.New(proxyOptionsKey, Options{
|
||||
ConnectAddr: "proxy-address",
|
||||
}),
|
||||
},
|
||||
wantConnectAddr: "proxy-address",
|
||||
wantAttrPresent: true,
|
||||
},
|
||||
{
|
||||
name: "user_in_attribute",
|
||||
addr: resolver.Address{
|
||||
Addr: "test-address",
|
||||
Attributes: attributes.New(proxyOptionsKey, Options{
|
||||
User: *user,
|
||||
}),
|
||||
},
|
||||
wantUser: *user,
|
||||
wantAttrPresent: true,
|
||||
},
|
||||
{
|
||||
name: "no_attribute",
|
||||
addr: resolver.Address{Addr: "test-address"},
|
||||
wantAttrPresent: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotOption, attrPresent := Get(tt.addr)
|
||||
if attrPresent != tt.wantAttrPresent {
|
||||
t.Errorf("Get(%v) = %v, want %v", tt.addr, attrPresent, tt.wantAttrPresent)
|
||||
}
|
||||
|
||||
if gotOption.ConnectAddr != tt.wantConnectAddr {
|
||||
t.Errorf("ConnectAddr(%v) = %v, want %v", tt.addr, gotOption.ConnectAddr, tt.wantConnectAddr)
|
||||
}
|
||||
|
||||
if gotOption.User != tt.wantUser {
|
||||
t.Errorf("User(%v) = %v, want %v", tt.addr, gotOption.User, tt.wantUser)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that Set returns a copy of addr with attributes containing correct
|
||||
// user and connect address.
|
||||
func (s) TestSet(t *testing.T) {
|
||||
addr := resolver.Address{Addr: "test-address"}
|
||||
pOpts := Options{
|
||||
User: *url.UserPassword("username", "password"),
|
||||
ConnectAddr: "proxy-address",
|
||||
}
|
||||
|
||||
// Call Set and validate attributes
|
||||
populatedAddr := Set(addr, pOpts)
|
||||
gotOption, attrPresent := Get(populatedAddr)
|
||||
if !attrPresent {
|
||||
t.Errorf("Get(%v) = %v, want %v ", populatedAddr, attrPresent, true)
|
||||
}
|
||||
if got, want := gotOption.ConnectAddr, pOpts.ConnectAddr; got != want {
|
||||
t.Errorf("Unexpected ConnectAddr proxy atrribute = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := gotOption.User, pOpts.User; got != want {
|
||||
t.Errorf("unexpected User proxy attribute = %v, want %v", got, want)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
*
|
||||
* 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 implements a resolver capable of resolving both
|
||||
// target URIs and proxy addresses.
|
||||
package delegatingresolver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/internal/proxyattributes"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = grpclog.Component("delegating-resolver")
|
||||
// HTTPSProxyFromEnvironment will be overwritten in the tests
|
||||
HTTPSProxyFromEnvironment = http.ProxyFromEnvironment
|
||||
)
|
||||
|
||||
// delegatingResolver manages both target URI and proxy address resolution by
|
||||
// delegating these tasks to separate child resolvers. Essentially, it acts as
|
||||
// a intermediary between the gRPC ClientConn and the child resolvers.
|
||||
//
|
||||
// It implements the [resolver.Resolver] interface.
|
||||
type delegatingResolver struct {
|
||||
target resolver.Target // parsed target URI to be resolved
|
||||
cc resolver.ClientConn // gRPC ClientConn
|
||||
targetResolver resolver.Resolver // resolver for the target URI, based on its scheme
|
||||
proxyResolver resolver.Resolver // resolver for the proxy URI; nil if no proxy is configured
|
||||
proxyURL *url.URL // proxy URL, derived from proxy environment and target
|
||||
|
||||
mu sync.Mutex // protects all the fields below
|
||||
targetResolverState *resolver.State // state of the target resolver
|
||||
proxyAddrs []resolver.Address // resolved proxy addresses; empty if no proxy is configured
|
||||
}
|
||||
|
||||
// nopResolver is a resolver that does nothing.
|
||||
type nopResolver struct{}
|
||||
|
||||
func (nopResolver) ResolveNow(resolver.ResolveNowOptions) {}
|
||||
|
||||
func (nopResolver) Close() {}
|
||||
|
||||
// proxyURLForTarget determines the proxy URL for the given address based on
|
||||
// the environment. It can return the following:
|
||||
// - nil URL, nil error: No proxy is configured or the address is excluded
|
||||
// using the `NO_PROXY` environment variable or if req.URL.Host is
|
||||
// "localhost" (with or without // a port number)
|
||||
// - nil URL, non-nil error: An error occurred while retrieving the proxy URL.
|
||||
// - non-nil URL, nil error: A proxy is configured, and the proxy URL was
|
||||
// retrieved successfully without any errors.
|
||||
func proxyURLForTarget(address string) (*url.URL, error) {
|
||||
req := &http.Request{URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: address,
|
||||
}}
|
||||
return HTTPSProxyFromEnvironment(req)
|
||||
}
|
||||
|
||||
// New creates a new delegating resolver that can create up to two child
|
||||
// resolvers:
|
||||
// - one to resolve the proxy address specified using the supported
|
||||
// environment variables. This uses the registered resolver for the "dns"
|
||||
// scheme.
|
||||
// - one to resolve the target URI using the resolver specified by the scheme
|
||||
// in the target URI or specified by the user using the WithResolvers dial
|
||||
// option. As a special case, if the target URI's scheme is "dns" and a
|
||||
// proxy is specified using the supported environment variables, the target
|
||||
// URI's path portion is used as the resolved address unless target
|
||||
// resolution is enabled using the dial option.
|
||||
func New(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions, targetResolverBuilder resolver.Builder, targetResolutionEnabled bool) (resolver.Resolver, error) {
|
||||
r := &delegatingResolver{
|
||||
target: target,
|
||||
cc: cc,
|
||||
}
|
||||
|
||||
var err error
|
||||
r.proxyURL, err = proxyURLForTarget(target.Endpoint())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("delegating_resolver: failed to determine proxy URL for target %s: %v", target, err)
|
||||
}
|
||||
|
||||
// proxy is not configured or proxy address excluded using `NO_PROXY` env
|
||||
// var, so only target resolver is used.
|
||||
if r.proxyURL == nil {
|
||||
return targetResolverBuilder.Build(target, cc, opts)
|
||||
}
|
||||
|
||||
if logger.V(2) {
|
||||
logger.Infof("Proxy URL detected : %s", r.proxyURL)
|
||||
}
|
||||
|
||||
// When the scheme is 'dns' and target resolution on client is not enabled,
|
||||
// resolution should be handled by the proxy, not the client. Therefore, we
|
||||
// bypass the target resolver and store the unresolved target address.
|
||||
if target.URL.Scheme == "dns" && !targetResolutionEnabled {
|
||||
state := resolver.State{
|
||||
Addresses: []resolver.Address{{Addr: target.Endpoint()}},
|
||||
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: target.Endpoint()}}}},
|
||||
}
|
||||
r.targetResolverState = &state
|
||||
} else {
|
||||
wcc := &wrappingClientConn{
|
||||
stateListener: r.updateTargetResolverState,
|
||||
parent: r,
|
||||
}
|
||||
if r.targetResolver, err = targetResolverBuilder.Build(target, wcc, opts); err != nil {
|
||||
return nil, fmt.Errorf("delegating_resolver: unable to build the resolver for target %s: %v", target, err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.proxyResolver, err = r.proxyURIResolver(opts); err != nil {
|
||||
return nil, fmt.Errorf("delegating_resolver: failed to build resolver for proxy URL %q: %v", r.proxyURL, err)
|
||||
}
|
||||
|
||||
if r.targetResolver == nil {
|
||||
r.targetResolver = nopResolver{}
|
||||
}
|
||||
if r.proxyResolver == nil {
|
||||
r.proxyResolver = nopResolver{}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// proxyURIResolver creates a resolver for resolving proxy URIs using the
|
||||
// "dns" scheme. It adjusts the proxyURL to conform to the "dns:///" format and
|
||||
// builds a resolver with a wrappingClientConn to capture resolved addresses.
|
||||
func (r *delegatingResolver) proxyURIResolver(opts resolver.BuildOptions) (resolver.Resolver, error) {
|
||||
proxyBuilder := resolver.Get("dns")
|
||||
if proxyBuilder == nil {
|
||||
panic("delegating_resolver: resolver for proxy not found for scheme dns")
|
||||
}
|
||||
url := *r.proxyURL
|
||||
url.Scheme = "dns"
|
||||
url.Path = "/" + r.proxyURL.Host
|
||||
url.Host = "" // Clear the Host field to conform to the "dns:///" format
|
||||
|
||||
proxyTarget := resolver.Target{URL: url}
|
||||
wcc := &wrappingClientConn{
|
||||
stateListener: r.updateProxyResolverState,
|
||||
parent: r,
|
||||
}
|
||||
return proxyBuilder.Build(proxyTarget, wcc, opts)
|
||||
}
|
||||
|
||||
func (r *delegatingResolver) ResolveNow(o resolver.ResolveNowOptions) {
|
||||
r.targetResolver.ResolveNow(o)
|
||||
r.proxyResolver.ResolveNow(o)
|
||||
}
|
||||
|
||||
func (r *delegatingResolver) Close() {
|
||||
r.targetResolver.Close()
|
||||
r.targetResolver = nil
|
||||
|
||||
r.proxyResolver.Close()
|
||||
r.proxyResolver = nil
|
||||
}
|
||||
|
||||
// updateClientConnStateLocked creates a list of combined addresses by
|
||||
// pairing each proxy address with every target address. For each pair, it
|
||||
// generates a new [resolver.Address] using the proxy address, and adding the
|
||||
// target address as the attribute along with user info. It returns nil if
|
||||
// either resolver has not sent update even once and returns the error from
|
||||
// ClientConn update once both resolvers have sent update atleast once.
|
||||
func (r *delegatingResolver) updateClientConnStateLocked() error {
|
||||
if r.targetResolverState == nil || r.proxyAddrs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
curState := *r.targetResolverState
|
||||
// If multiple resolved proxy addresses are present, we send only the
|
||||
// unresolved proxy host and let net.Dial handle the proxy host name
|
||||
// resolution when creating the transport. Sending all resolved addresses
|
||||
// would increase the number of addresses passed to the ClientConn and
|
||||
// subsequently to load balancing (LB) policies like Round Robin, leading
|
||||
// to additional TCP connections. However, if there's only one resolved
|
||||
// proxy address, we send it directly, as it doesn't affect the address
|
||||
// count returned by the target resolver and the address count sent to the
|
||||
// ClientConn.
|
||||
var proxyAddr resolver.Address
|
||||
if len(r.proxyAddrs) == 1 {
|
||||
proxyAddr = r.proxyAddrs[0]
|
||||
} else {
|
||||
proxyAddr = resolver.Address{Addr: r.proxyURL.Host}
|
||||
}
|
||||
var addresses []resolver.Address
|
||||
var user url.Userinfo
|
||||
if r.proxyURL.User != nil {
|
||||
user = *r.proxyURL.User
|
||||
}
|
||||
for _, targetAddr := range (*r.targetResolverState).Addresses {
|
||||
addresses = append(addresses, proxyattributes.Set(proxyAddr, proxyattributes.Options{
|
||||
User: user,
|
||||
ConnectAddr: targetAddr.Addr,
|
||||
}))
|
||||
}
|
||||
|
||||
// Create a list of combined endpoints by pairing all proxy endpoints
|
||||
// with every target endpoint. Each time, it constructs a new
|
||||
// [resolver.Endpoint] using the all addresses from all the proxy endpoint
|
||||
// and the target addresses from one endpoint. The target address and user
|
||||
// information from the proxy URL are added as attributes to the proxy
|
||||
// address.The resulting list of addresses is then grouped into endpoints,
|
||||
// covering all combinations of proxy and target endpoints.
|
||||
var endpoints []resolver.Endpoint
|
||||
for _, endpt := range (*r.targetResolverState).Endpoints {
|
||||
var addrs []resolver.Address
|
||||
for _, proxyAddr := range r.proxyAddrs {
|
||||
for _, targetAddr := range endpt.Addresses {
|
||||
addrs = append(addrs, proxyattributes.Set(proxyAddr, proxyattributes.Options{
|
||||
User: user,
|
||||
ConnectAddr: targetAddr.Addr,
|
||||
}))
|
||||
}
|
||||
}
|
||||
endpoints = append(endpoints, resolver.Endpoint{Addresses: addrs})
|
||||
}
|
||||
// Use the targetResolverState for its service config and attributes
|
||||
// contents. The state update is only sent after both the target and proxy
|
||||
// resolvers have sent their updates, and curState has been updated with
|
||||
// the combined addresses.
|
||||
curState.Addresses = addresses
|
||||
curState.Endpoints = endpoints
|
||||
return r.cc.UpdateState(curState)
|
||||
}
|
||||
|
||||
// updateProxyResolverState updates the proxy resolver state by storing proxy
|
||||
// addresses and endpoints, marking the resolver as ready, and triggering a
|
||||
// state update if both proxy and target resolvers are ready. If the ClientConn
|
||||
// returns a non-nil error, it calls `ResolveNow()` on the target resolver. It
|
||||
// is a StateListener function of wrappingClientConn passed to the proxy resolver.
|
||||
func (r *delegatingResolver) updateProxyResolverState(state resolver.State) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if logger.V(2) {
|
||||
logger.Infof("Addresses received from proxy resolver: %s", state.Addresses)
|
||||
}
|
||||
if len(state.Endpoints) > 0 {
|
||||
// We expect exactly one address per endpoint because the proxy
|
||||
// resolver uses "dns" resolution.
|
||||
r.proxyAddrs = make([]resolver.Address, 0, len(state.Endpoints))
|
||||
for _, endpoint := range state.Endpoints {
|
||||
r.proxyAddrs = append(r.proxyAddrs, endpoint.Addresses...)
|
||||
}
|
||||
} else if state.Addresses != nil {
|
||||
r.proxyAddrs = state.Addresses
|
||||
} else {
|
||||
r.proxyAddrs = []resolver.Address{} // ensure proxyAddrs is non-nil to indicate an update has been received
|
||||
}
|
||||
err := r.updateClientConnStateLocked()
|
||||
// Another possible approach was to block until updates are received from
|
||||
// both resolvers. But this is not used because calling `New()` triggers
|
||||
// `Build()` for the first resolver, which calls `UpdateState()`. And the
|
||||
// second resolver hasn't sent an update yet, so it would cause `New()` to
|
||||
// block indefinitely.
|
||||
if err != nil {
|
||||
r.targetResolver.ResolveNow(resolver.ResolveNowOptions{})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// updateTargetResolverState updates the target resolver state by storing target
|
||||
// addresses, endpoints, and service config, marking the resolver as ready, and
|
||||
// triggering a state update if both resolvers are ready. If the ClientConn
|
||||
// returns a non-nil error, it calls `ResolveNow()` on the proxy resolver. It
|
||||
// is a StateListener function of wrappingClientConn passed to the target resolver.
|
||||
func (r *delegatingResolver) updateTargetResolverState(state resolver.State) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if logger.V(2) {
|
||||
logger.Infof("Addresses received from target resolver: %v", state.Addresses)
|
||||
}
|
||||
r.targetResolverState = &state
|
||||
err := r.updateClientConnStateLocked()
|
||||
if err != nil {
|
||||
r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wrappingClientConn serves as an intermediary between the parent ClientConn
|
||||
// and the child resolvers created here. It implements the resolver.ClientConn
|
||||
// interface and is passed in that capacity to the child resolvers.
|
||||
type wrappingClientConn struct {
|
||||
// Callback to deliver resolver state updates
|
||||
stateListener func(state resolver.State) error
|
||||
parent *delegatingResolver
|
||||
}
|
||||
|
||||
// UpdateState receives resolver state updates and forwards them to the
|
||||
// appropriate listener function (either for the proxy or target resolver).
|
||||
func (wcc *wrappingClientConn) UpdateState(state resolver.State) error {
|
||||
return wcc.stateListener(state)
|
||||
}
|
||||
|
||||
// ReportError intercepts errors from the child resolvers and passes them to ClientConn.
|
||||
func (wcc *wrappingClientConn) ReportError(err error) {
|
||||
wcc.parent.cc.ReportError(err)
|
||||
}
|
||||
|
||||
// NewAddress intercepts the new resolved address from the child resolvers and
|
||||
// passes them to ClientConn.
|
||||
func (wcc *wrappingClientConn) NewAddress(addrs []resolver.Address) {
|
||||
wcc.UpdateState(resolver.State{Addresses: addrs})
|
||||
}
|
||||
|
||||
// ParseServiceConfig parses the provided service config and returns an
|
||||
// object that provides the parsed config.
|
||||
func (wcc *wrappingClientConn) ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult {
|
||||
return wcc.parent.cc.ParseServiceConfig(serviceConfigJSON)
|
||||
}
|
|
@ -0,0 +1,550 @@
|
|||
/*
|
||||
*
|
||||
* 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
*
|
||||
* 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
const (
|
||||
targetTestAddr = "test.com"
|
||||
envProxyAddr = "proxytest.com"
|
||||
)
|
||||
|
||||
// overrideHTTPSProxyFromEnvironment function overwrites HTTPSProxyFromEnvironment and
|
||||
// returns a function to restore the default values.
|
||||
func overrideHTTPSProxyFromEnvironment(hpfe func(req *http.Request) (*url.URL, error)) func() {
|
||||
HTTPSProxyFromEnvironment = hpfe
|
||||
return func() {
|
||||
HTTPSProxyFromEnvironment = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the proxyURLForTarget function correctly resolves the proxy URL
|
||||
// for a given target address. Tests all the possible output cases.
|
||||
func (s) TestproxyURLForTargetEnv(t *testing.T) {
|
||||
err := errors.New("invalid proxy url")
|
||||
tests := []struct {
|
||||
name string
|
||||
hpfeFunc func(req *http.Request) (*url.URL, error)
|
||||
wantURL *url.URL
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "valid_proxy_url_and_nil_error",
|
||||
hpfeFunc: func(_ *http.Request) (*url.URL, error) {
|
||||
return &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "proxy.example.com",
|
||||
}, nil
|
||||
},
|
||||
wantURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "proxy.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid_proxy_url_and_non-nil_error",
|
||||
hpfeFunc: func(_ *http.Request) (*url.URL, error) {
|
||||
return &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "notproxy.example.com",
|
||||
}, err
|
||||
},
|
||||
wantURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "notproxy.example.com",
|
||||
},
|
||||
wantErr: err,
|
||||
},
|
||||
{
|
||||
name: "nil_proxy_url_and_nil_error",
|
||||
hpfeFunc: func(_ *http.Request) (*url.URL, error) {
|
||||
return nil, nil
|
||||
},
|
||||
wantURL: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer overrideHTTPSProxyFromEnvironment(tt.hpfeFunc)()
|
||||
got, err := proxyURLForTarget(targetTestAddr)
|
||||
if err != tt.wantErr {
|
||||
t.Errorf("parsedProxyURLForProxy(%v) failed with error :%v, want %v\n", targetTestAddr, err, tt.wantErr)
|
||||
}
|
||||
if !cmp.Equal(got, tt.wantURL) {
|
||||
t.Fatalf("parsedProxyURLForProxy(%v) = %v, want %v\n", targetTestAddr, got, tt.wantURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue