diff --git a/clientconn.go b/clientconn.go index 2359f94b8a45..6dcb4c2652b7 100644 --- a/clientconn.go +++ b/clientconn.go @@ -152,6 +152,25 @@ func NewClient(target string, opts ...DialOption) (conn *ClientConn, err error) for _, opt := range opts { opt.apply(&cc.dopts) } + + // Register ClientConn with channelz. + cc.channelzRegistration(target) + // TODO: Ideally it should be impossible to error from this function after + // channelz registration. This will require removing some channelz logs + // from the following functions that can error. Errors can be returned to + // the user, and successful logs can be emitted here, after the checks have + // passed and channelz is subsequently registered. + + // Determine the resolver to use. + if err := cc.parseTargetAndFindResolver(); err != nil { + channelz.RemoveEntry(cc.channelz.ID) + return nil, err + } + + for _, lateApplyOpt := range globalLateApplyDialOptions { + lateApplyOpt.DialOption(cc.parsedTarget.URL).apply(&cc.dopts) + } + chainUnaryClientInterceptors(cc) chainStreamClientInterceptors(cc) @@ -168,20 +187,6 @@ func NewClient(target string, opts ...DialOption) (conn *ClientConn, err error) } cc.mkp = cc.dopts.copts.KeepaliveParams - // Register ClientConn with channelz. - cc.channelzRegistration(target) - - // TODO: Ideally it should be impossible to error from this function after - // channelz registration. This will require removing some channelz logs - // from the following functions that can error. Errors can be returned to - // the user, and successful logs can be emitted here, after the checks have - // passed and channelz is subsequently registered. - - // Determine the resolver to use. - if err := cc.parseTargetAndFindResolver(); err != nil { - channelz.RemoveEntry(cc.channelz.ID) - return nil, err - } if err = cc.determineAuthority(); err != nil { channelz.RemoveEntry(cc.channelz.ID) return nil, err diff --git a/default_dial_option_server_option_test.go b/default_dial_option_server_option_test.go index c4e7143c449f..692979cd863b 100644 --- a/default_dial_option_server_option_test.go +++ b/default_dial_option_server_option_test.go @@ -20,6 +20,7 @@ package grpc import ( "fmt" + "net/url" "strings" "testing" @@ -74,6 +75,30 @@ func (s) TestDisableGlobalOptions(t *testing.T) { internal.ClearGlobalDialOptions() } +type testLateApplyDialOption struct{} + +func (do *testLateApplyDialOption) DialOption(parsedTarget url.URL) DialOption { + print("parsedTarget.Scheme: ", parsedTarget.Scheme) + if parsedTarget.Scheme == "passthrough" { + return WithTransportCredentials(insecure.NewCredentials()) // credentials provided, should pass NewClient. + } + return WithReadBufferSize(10) // no credentials, should fail NewClient +} + +func (s) TestGlobalLateApplyDialOption(t *testing.T) { + internal.AddGlobalLateApplyDialOptions.(func(opt ...LateApplyDialOption))(&testLateApplyDialOption{}) // Do I need a clear? + noTSecStr := "no transport security set" + if _, err := NewClient("fake"); !strings.Contains(fmt.Sprint(err), noTSecStr) { + t.Fatalf("Dialing received unexpected error: %v, want error containing \"%v\"", err, noTSecStr) + } + cc, err := NewClient("passthrough:///nice") + if err != nil { + t.Fatalf("Dialing with insecure credentials failed: %v", err) + } + defer cc.Close() + internal.ClearGlobalLateApplyDialOptions() +} + func (s) TestAddGlobalServerOptions(t *testing.T) { const maxRecvSize = 998765 // Set and check the ServerOptions diff --git a/dialoptions.go b/dialoptions.go index 00273702b694..531b48c0ccc2 100644 --- a/dialoptions.go +++ b/dialoptions.go @@ -21,6 +21,7 @@ package grpc import ( "context" "net" + "net/url" "time" "google.golang.org/grpc/backoff" @@ -43,6 +44,12 @@ func init() { internal.ClearGlobalDialOptions = func() { globalDialOptions = nil } + internal.AddGlobalLateApplyDialOptions = func(opt ...LateApplyDialOption) { + globalLateApplyDialOptions = append(globalLateApplyDialOptions, opt...) + } + internal.ClearGlobalLateApplyDialOptions = func() { + globalLateApplyDialOptions = nil + } internal.WithBinaryLogger = withBinaryLogger internal.JoinDialOptions = newJoinDialOption internal.DisableGlobalDialOptions = newDisableGlobalDialOptions @@ -89,6 +96,14 @@ type DialOption interface { var globalDialOptions []DialOption +// LateApplyDialOption takes a parsed target and returns a dial option to apply. +type LateApplyDialOption interface { + // DialOption returns a Dial Option to apply. + DialOption(parsedTarget url.URL) DialOption +} + +var globalLateApplyDialOptions []LateApplyDialOption + // EmptyDialOption does not alter the dial configuration. It can be embedded in // another structure to build custom dial options. // diff --git a/internal/internal.go b/internal/internal.go index 48d24bdb4e69..c71942d47cbf 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -106,6 +106,14 @@ var ( // This is used in the 1.0 release of gcp/observability, and thus must not be // deleted or changed. ClearGlobalDialOptions func() + + // AddGlobalLateApplyDialOptions adds a slice of lateApplyDialOptions that + // will be configured for newly created ClientConns. + AddGlobalLateApplyDialOptions any // func (opt ...lateApplyDialOption) + // ClearGlobalLateApplyDialOptions clears the slice of global late apply + // dial options. + ClearGlobalLateApplyDialOptions func() + // JoinDialOptions combines the dial options passed as arguments into a // single dial option. JoinDialOptions any // func(...grpc.DialOption) grpc.DialOption