diff --git a/clientconn.go b/clientconn.go index 4a98265a..c072f69f 100644 --- a/clientconn.go +++ b/clientconn.go @@ -98,8 +98,10 @@ type dialOptions struct { // This is to support v1 balancer. balancerBuilder balancer.Builder // This is to support grpclb. - resolverBuilder resolver.Builder - waitForHandshake bool + resolverBuilder resolver.Builder + // Custom user options for resolver.Build. + resolverBuildUserOptions interface{} + waitForHandshake bool } const ( @@ -223,6 +225,14 @@ func withResolverBuilder(b resolver.Builder) DialOption { } } +// WithResolverUserOptions returns a DialOption which sets the UserOptions +// field of resolver's BuildOption. +func WithResolverUserOptions(userOpt interface{}) DialOption { + return func(o *dialOptions) { + o.resolverBuildUserOptions = userOpt + } +} + // WithServiceConfig returns a DialOption which has a channel to read the service configuration. // DEPRECATED: service config should be received through name resolver, as specified here. // https://github.com/grpc/grpc/blob/master/doc/service_config.md diff --git a/clientconn_test.go b/clientconn_test.go index 2142ff15..512a9462 100644 --- a/clientconn_test.go +++ b/clientconn_test.go @@ -583,41 +583,6 @@ func TestNonblockingDialWithEmptyBalancer(t *testing.T) { } } -func TestResolverServiceConfigBeforeAddressNotPanic(t *testing.T) { - defer leakcheck.Check(t) - r, rcleanup := manual.GenerateAndRegisterManualResolver() - defer rcleanup() - - cc, err := Dial(r.Scheme()+":///test.server", WithInsecure()) - if err != nil { - t.Fatalf("failed to dial: %v", err) - } - defer cc.Close() - - // SwitchBalancer before NewAddress. There was no balancer created, this - // makes sure we don't call close on nil balancerWrapper. - r.NewServiceConfig(`{"loadBalancingPolicy": "round_robin"}`) // This should not panic. - - time.Sleep(time.Second) // Sleep to make sure the service config is handled by ClientConn. -} - -func TestResolverEmptyUpdateNotPanic(t *testing.T) { - defer leakcheck.Check(t) - r, rcleanup := manual.GenerateAndRegisterManualResolver() - defer rcleanup() - - cc, err := Dial(r.Scheme()+":///test.server", WithInsecure()) - if err != nil { - t.Fatalf("failed to dial: %v", err) - } - defer cc.Close() - - // This make sure we don't create addrConn with empty address list. - r.NewAddress([]resolver.Address{}) // This should not panic. - - time.Sleep(time.Second) // Sleep to make sure the service config is handled by ClientConn. -} - func TestClientUpdatesParamsAfterGoAway(t *testing.T) { defer leakcheck.Check(t) lis, err := net.Listen("tcp", "localhost:0") diff --git a/resolver/resolver.go b/resolver/resolver.go index 9efcffb3..df097eed 100644 --- a/resolver/resolver.go +++ b/resolver/resolver.go @@ -90,6 +90,9 @@ type Address struct { // BuildOption includes additional information for the builder to create // the resolver. type BuildOption struct { + // UserOptions can be used to pass configuration between DialOptions and the + // resolver. + UserOptions interface{} } // ClientConn contains the callbacks for resolver to notify any updates diff --git a/resolver_conn_wrapper.go b/resolver_conn_wrapper.go index 1a1591e8..ef5d4c28 100644 --- a/resolver_conn_wrapper.go +++ b/resolver_conn_wrapper.go @@ -83,7 +83,9 @@ func newCCResolverWrapper(cc *ClientConn) (*ccResolverWrapper, error) { } var err error - ccr.resolver, err = rb.Build(cc.parsedTarget, ccr, resolver.BuildOption{}) + ccr.resolver, err = rb.Build(cc.parsedTarget, ccr, resolver.BuildOption{ + UserOptions: cc.dopts.resolverBuildUserOptions, + }) if err != nil { return nil, err } diff --git a/resolver_test.go b/resolver_test.go new file mode 100644 index 00000000..6aba13c1 --- /dev/null +++ b/resolver_test.go @@ -0,0 +1,99 @@ +/* + * + * Copyright 2017 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package grpc + +import ( + "fmt" + "strings" + "testing" + "time" + + "google.golang.org/grpc/resolver" + "google.golang.org/grpc/resolver/manual" + "google.golang.org/grpc/test/leakcheck" +) + +func TestResolverServiceConfigBeforeAddressNotPanic(t *testing.T) { + defer leakcheck.Check(t) + r, rcleanup := manual.GenerateAndRegisterManualResolver() + defer rcleanup() + + cc, err := Dial(r.Scheme()+":///test.server", WithInsecure()) + if err != nil { + t.Fatalf("failed to dial: %v", err) + } + defer cc.Close() + + // SwitchBalancer before NewAddress. There was no balancer created, this + // makes sure we don't call close on nil balancerWrapper. + r.NewServiceConfig(`{"loadBalancingPolicy": "round_robin"}`) // This should not panic. + + time.Sleep(time.Second) // Sleep to make sure the service config is handled by ClientConn. +} + +func TestResolverEmptyUpdateNotPanic(t *testing.T) { + defer leakcheck.Check(t) + r, rcleanup := manual.GenerateAndRegisterManualResolver() + defer rcleanup() + + cc, err := Dial(r.Scheme()+":///test.server", WithInsecure()) + if err != nil { + t.Fatalf("failed to dial: %v", err) + } + defer cc.Close() + + // This make sure we don't create addrConn with empty address list. + r.NewAddress([]resolver.Address{}) // This should not panic. + + time.Sleep(time.Second) // Sleep to make sure the service config is handled by ClientConn. +} + +var ( + errTestResolverFailBuild = fmt.Errorf("test resolver build error") +) + +type testResolverFailBuilder struct { + buildOpt resolver.BuildOption +} + +func (r *testResolverFailBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { + r.buildOpt = opts + return nil, errTestResolverFailBuild +} +func (r *testResolverFailBuilder) Scheme() string { + return "testResolverFailBuilderScheme" +} + +// Tests that options in WithResolverUserOptions are passed to resolver.Build(). +func TestResolverUserOptions(t *testing.T) { + r := &testResolverFailBuilder{} + + userOpt := "testUserOpt" + _, err := Dial("scheme:///test.server", WithInsecure(), + withResolverBuilder(r), + WithResolverUserOptions(userOpt), + ) + if err == nil || !strings.Contains(err.Error(), errTestResolverFailBuild.Error()) { + t.Fatalf("Dial with testResolverFailBuilder returns err: %v, want: %v", err, errTestResolverFailBuild) + } + + if r.buildOpt.UserOptions != userOpt { + t.Fatalf("buildOpt.UserOptions = %T %+v, want %v", r.buildOpt.UserOptions, r.buildOpt.UserOptions, userOpt) + } +}